[JAVA] L'histoire de la création d'un proxy inverse avec ProxyServlet

Cet article est un article relais de 2018 Calendrier de l'Avent de Link Information System .. Relayé par un membre du groupe de engineer.hanzomon. (Cliquez ici pour le lien système d'information Facebook (https://ja-jp.facebook.com/lis.co.jp/))

introduction

Je m'appelle @shinevillage et je suis en charge du 13e jour du calendrier de l'Avent. Je suis principalement en charge du développement d'applications web utilisant Java. Cette fois, lorsque j'étais en charge d'un certain travail, je parlerai de la création d'un proxy inverse en Java comme outil de test.

Pourquoi avez-vous réussi?

Le terme «proxy inverse» est plus familier aux spécialistes de l'infrastructure, mais il peut parfois être également recherché par les développeurs Web. (Pour ceux qui demandent "Qu'est-ce qu'un proxy inverse en premier lieu?")

Dans mon cas, lors du test d'une application avec la configuration suivante, un problème interdomaine est survenu et je le voulais. qiita1.png

Il semble qu'utiliser Apache ou Nginx soit le moyen le plus populaire de créer un proxy inverse, mais dans mon cas, malheureusement ** "L'installation de logiciels libres exe est interdite. Les scripts que j'ai écrits sont ok. Je travaille dans un environnement comme "Pardonnez-moi" **, et je ne pouvais pas utiliser le logiciel ci-dessus, j'ai donc écrit un proxy inverse pour les tests en Java, le langage dans lequel l'application est écrite.

Ce que j'ai utilisé

--Java 1.8 (Raison de la sélection: Parce qu'il est utilisé au travail)

Constitution

Le servlet de proxy HTTP de Smiley fournit un servlet (ProxyServlet) qui agit comme un serveur proxy. Vous pouvez utiliser ** URITemplateProxyServlet ** pour contrôler dynamiquement la destination de transfert (nom d'hôte, numéro de port, chemin de contexte) avec la chaîne de requête. Le comportement du proxy inverse est reproduit en définissant la chaîne de caractères de requête en fonction de l'URL demandée avec le filtre de servlet. seq.png

la mise en oeuvre

Construisez avec Maven.

pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>riverse-proxy</groupId>
  <artifactId>riverse-proxy</artifactId>
  <version>1.0</version>
  <packaging>war</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.14.RELEASE</version>
    </parent>
    <properties>
        <project.build.sourceEncoding>Windows-31j</project.build.sourceEncoding>
        <project.reporting.outputEncoding>Windows-31j</project.reporting.outputEncoding>
        <maven.compile.source>1.8</maven.compile.source>
        <maven.compile.target>1.8</maven.compile.target>
        <java.version>1.8</java.version>
        <spring.version>4.3.18.RELEASE</spring.version>
    </properties>
    <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mitre.dsmiley.httpproxy</groupId>
            <artifactId>smiley-http-proxy-servlet</artifactId>
            <version>1.10</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Vient ensuite la classe principale. Enregistrez le filtre de servlet et le ProxyServlet ici.

Main.java



@SpringBootApplication
public class Main extends SpringBootServletInitializer implements WebApplicationInitializer {

  /**
   *Point d'entrée lors du démarrage indépendant.
   */
  public static void main(String[] args) throws Throwable {
    SpringApplicationBuilder builder = new SpringApplicationBuilder(Main.class);
    builder.run();
  }
  
  /**
   *Point d'entrée lors du démarrage du conteneur de servlet.
   */
  @Override
  protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    return application.sources(Main.class);
  }

  /**
   * {@see org.springframework.web.filter.HiddenHttpMethodFilter}Désactiver.
   * 
   *URITemplateProxyServlet est{@link ServletRequest#getInputStream}Utiliser
   *Lorsque le paramètre de requête est accédé côté filtre, le côté servlet de processus
   *Les paramètres de demande ne peuvent pas être acquis.
   *Par conséquent, dans cet outil, la fonction fournie par HiddenHttpMethodFilter n'est pas nécessaire, le filtre est donc désactivé.
   */
  @Bean
  public FilterRegistrationBean hiddenHttpMethodFilterRegistration(HiddenHttpMethodFilter filter) {
    FilterRegistrationBean registration = new FilterRegistrationBean(filter);
    registration.setFilter(filter);
    registration.setEnabled(false);
    return registration;
  }

  /**
   * {@see org.springframework.web.filter.HttpPutFormContentFilter}Désactiver.
   * 
   *URITemplateProxyServlet est{@link ServletRequest#getInputStream}Utiliser
   *Lorsque le paramètre de demande est accédé côté filtre, le côté servlet
   *Les paramètres de demande ne peuvent pas être acquis.
   *Par conséquent, dans cet outil, la fonction fournie par HttpPutFormContentFilter n'est pas nécessaire, le filtre est donc désactivé.
   */
  @Bean
  public FilterRegistrationBean httpPutFormContentFilterRegistration(HttpPutFormContentFilter filter) {
    FilterRegistrationBean registration = new FilterRegistrationBean(filter);
    registration.setFilter(filter);
    registration.setEnabled(false);
    return registration;
  }
  
  /**
   *Enregistrement du filtre de servlet pour l'application Web A.
   */
  @Bean
  public FilterRegistrationBean applicationARiverseProxyFilterRegistration() {
    Filter filter = new AAppRiverseProxyFilter();
    FilterRegistrationBean registration = new FilterRegistrationBean(filter);
    registration.setOrder(FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER);
    registration.setUrlPatterns(Arrays.asList("/webapp-a/*"));
    registration.setDispatcherTypes(EnumSet.of(DispatcherType.REQUEST));
    return registration;
  }

  /**
   *Enregistrement du filtre de servlet pour l'application Web B.
   */
  @Bean
  public FilterRegistrationBean applicationBRiverseProxyFilterRegistration() {
    Filter filter = new BAppRiverseProxyFilter();
    FilterRegistrationBean registration = new FilterRegistrationBean(filter);
    registration.setOrder(FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER);
    registration.setUrlPatterns(Arrays.asList("/webapp-b/*"));
    registration.setDispatcherTypes(EnumSet.of(DispatcherType.REQUEST));
    return registration;
  }
  
  /**
   *Enregistrement du servlet proxy.
   * @see https://github.com/mitre/HTTP-Proxy-Servlet
   */
  @Bean
  public ServletRegistrationBean riverseProxyServletRegistration() {
    HttpServlet servlet = new URITemplateProxyServlet();
    ServletRegistrationBean registration = new ServletRegistrationBean(servlet);
    registration.addInitParameter("preserveHost", "true");
    registration.addInitParameter("preserveCookies", "true");
    registration.addInitParameter("http.protocol.handle-redirects", "true");
    registration.addInitParameter("http.socket.timeout", "300000");
    registration.addInitParameter("http.read.timeout", "300000");
    registration.addInitParameter("targetUri", "http://{__proxyHost}/{__proxyContextRoot}");
    registration.setUrlMappings(Arrays.asList("/webapp-a/*", "/webapp-b/*"));
    return registration;
  }
}

Vient ensuite le filtre de servlet. Le comportement principal est défini comme une classe abstraite et le traitement qui dépend de la destination de transfert du côté de la sous-classe est décrit.

AbstractRiverseProxyFilter.java


/**
 *Filtre proxy inverse.
 * {@see org.mitre.dsmiley.httpproxy.URITemplateProxyServlet}À
 *Personnalisez la demande et la réponse pour fonctionner comme un proxy inverse.
 */
abstract public class AbstractRiverseProxyFilter implements Filter {
  
  /**
   *Obtenez l'hôte de destination du transfert
   */
  abstract protected String getTransfarHost();

  /**
   *Obtenir la racine du contexte de transfert
   */
  abstract protected String getTransfarContextRoot();
  
  @Override
  public void doFilter(ServletRequest _request, ServletResponse _response,
            FilterChain filterChain) throws IOException, ServletException {

    HttpServletRequest  request  = (HttpServletRequest)  _request;

    // {@see org.mitre.dsmiley.httpproxy.URITemplateProxyServlet}À
    //Ajoutez le paramètre pour spécifier la destination du transfert à la chaîne de caractères de la requête
    StringBuilder routingQuery = new StringBuilder();
    String query = request.getQueryString();
    if (!StringUtils.isEmpty(query)) {
      routingQuery.append(query);
      routingQuery.append("&");
    }
    routingQuery.append(String.format("__proxyHost=%s&__proxyContextRoot=%s", 
        this.getTransfarHost(), this.getTransfarContextRoot()));

    //Empêche le codage par défaut du conteneur de servlet d'être défini
    _response.setCharacterEncoding(null);

    //Couvrir l'objet de requête avec un wrapper et le transmettre au servlet proxy
    RequestRewriteWrapper wrapRequest 
       = new RequestRewriteWrapper(request, routingQuery.toString());
    filterChain.doFilter(wrapRequest, _response);
  }

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {}

  @Override
  public void destroy() {}

  /**
   *Requête wrapper pour réécrire les chaînes de requête
   */
  private static class RequestRewriteWrapper extends HttpServletRequestWrapper {

    private final String queryString;

    public RequestRewriteWrapper(HttpServletRequest request, String query) {
      super(request);
      this.queryString = query;
    }

    /**
     * {@link HttpServletRequest#getQueryString()}Wrapper.
     *Renvoie une chaîne de requête avec la destination de transfert ajoutée.
     */
    @Override
    public String getQueryString() {
      return this.queryString;
    }
  }
}

AAppRiverseProxyFilter.java


/**
 *Filtre de proxy inverse pour l'application Web A.
 */
public class AAppRiverseProxyFilter extends AbstractRiverseProxyFilter {

  @Override
  protected String getTransfarHost() {
    return "xxx.xxx.xxx.1";
  }

  @Override
  protected String getTransfarContextRoot() {
    return "webapp-a";
  }
}

BAppRiverseProxyFilter.java


/**
 *Filtre de proxy inverse pour l'application Web B.
 */
public class BAppRiverseProxyFilter extends AbstractRiverseProxyFilter {

  @Override
  protected String getTransfarHost() {
    return "xxx.xxx.xxx.2";
  }

  @Override
  protected String getTransfarContextRoot() {
    return "webapp-b";
  }
}

Courir

Commencez par la commande suivante.

$ mvn spring-boot:run

Après avoir démarré l'outil, accédez à l'application Web A avec "http : // localhost: 8080 / webapp-a /" depuis le navigateur. Vous pourrez accéder à l'application Web B avec "http : // localhost: 8080 / webapp-b /".

finalement

Le contenu de cet article n'est qu'un extrait de la partie de base de l'outil créé. Lors de la création d'un outil, les informations de destination de transfert ne sont pas écrites dans le code source comme dans l'exemple ci-dessus, mais @valueC'est une bonne idée d'utiliser un mécanisme tel qu'une annotation pour la récupérer.

Même si j'ai attrapé le net, il n'y avait pas beaucoup d'articles écrivant un proxy inverse en Java, donc je l'ai écrit cette fois. Le proxy inverse de cet article n'est qu'un ** "outil de test" **, utilisez donc un produit décent pour votre environnement de production. <(_ _)>


Demain, c'est le 14e jour. Ceci est un article de @modest.

Recommended Posts

L'histoire de la création d'un proxy inverse avec ProxyServlet
L'histoire de la création d'un lanceur de jeu avec une fonction de chargement automatique [Java]
L'histoire de la création de DTO, semblable à Dao avec Java, SQLite
Histoire de créer une application de gestion de tâches avec Swing, Java
Une histoire remplie des bases de Spring Boot (résolu)
Une histoire sur l'utilisation de l'API League Of Legends avec JAVA
Une histoire qui a eu du mal avec l'introduction de Web Apple Pay
L'histoire de la création d'un jeu d'Othello de type communication avec Scala.
Une histoire sur la compatibilité d'un Dockerfile existant avec le GPU
L'histoire du réglage de l'application Android avec libGDX
Une histoire sur la création d'un Builder qui hérite du Builder
L'histoire de rendre possible la construction d'un projet qui a été construit par Maven avec Ant
L'histoire de la création de Dr.Orchid avec LINE BOT
Une histoire de rendre catkin_make de rosjava compatible hors ligne
Ecrire un test en mettant en œuvre l'histoire de M. Nabeats dans le monde avec du rubis
Une histoire bloquée avec NotSerializableException
Vérifier le fonctionnement de deux rôles avec une application de chat
Une histoire à laquelle j'étais accro avec toString () d'Interface qui était proxy avec JdkDynamicAopProxy
Expliquez les mérites du modèle d'État avec le jugement de notation du film
Trouvez le nombre de jours dans un mois avec Kotlin
Avec le logiciel que je fais depuis longtemps ...
L'histoire du transfert d'un conteneur Docker vers le registre de packages GitHub et Docker Hub avec des actions GitHub
L'histoire de la création d'une version Java du serveur Minecraft avec GCP (et également de la création d'une liste blanche)
[Édition Java] Histoire de la sérialisation
Obtenez une instance proxy du composant lui-même dans Spring Boot
Une histoire sur la connexion à un serveur CentOS 8 avec un ancien Ansible
L'histoire de @ViewScoped dévore la mémoire
L'histoire de toString () commençant par le passage d'un tableau à System.out.println
Un mémorandum du problème FizzBuzz
Une histoire sur la fabrication d'une calculatrice pour calculer le taux de monticule d'obus
Impressions de faire Black Jack-cli avec Ruby
[Illustration] Recherche de la somme des pièces avec une fonction récursive [Ruby]
Une histoire à laquelle j'étais accro à deux reprises avec le paramètre de démarrage automatique de Tomcat 8 sur CentOS 8
Je voulais écrire un processus équivalent à une instruction while avec l'API Java 8 Stream
Comment faire fonctionner IGV en utilisant la communication par socket, et l'histoire de la création d'un Ruby Gem en utilisant cette méthode
L'histoire de l'oubli de fermer un fichier en Java et de l'échec
Exprimons le résultat de l'analyse du code d'octet Java dans un diagramme de classes
Essayez de créer une configuration de type Liverpro avec Keycloak (édition Security Proxy)
(Mémo) Obtenez un ensemble de jars de bibliothèque dépendants à l'aide de Gradle
L'histoire de la rencontre avec l'annotation personnalisée Spring
Créez un fichier jar avec la commande
Vérifiez le contenu des paramètres avec le levier
L'histoire de la mise à jour du Docker Container de Sonar Qube
L'histoire de RxJava souffrant de NoSuchElementException
Exécutez DMN à l'aide du moteur Camunda DMN
Extraire une partie d'une chaîne en Ruby
Une petite histoire addictive avec def initialize
À propos du traitement de BigDecimal (avec réflexion)
L'histoire de l'écriture de Java dans Emacs
Mettre en forme le contenu de LocalDate avec DateTimeFormatter
Trouvez la différence à partir d'un multiple de 10
L'histoire de la création d'une application Android capable d'ajuster la fréquence d'échantillonnage du capteur d'accélération
Envoyez des notifications à Slack avec la version gratuite de sentry (en utilisant lambda)
Une histoire confirmant l'implémentation de la bibliothèque Java SendGrid lorsque la livraison du courrier échoue
Valider le jeton d'ID d'un utilisateur authentifié par AWS Cognito en Java
Une note rapide sur l'utilisation de jshell avec l'image Docker officielle du JDK