[JAVA] Die Geschichte, einen Reverse-Proxy mit ProxyServlet zu erstellen

Dieser Artikel ist ein Relaisartikel von 2018 Adventskalender von Link Information System. .. Weitergeleitet von einem Gruppenmitglied von engineer.hanzomon. (Klicken Sie hier für das Link-Informationssystem Facebook) (https://ja-jp.facebook.com/lis.co.jp/)

Einführung

Mein Name ist @shinevillage und ich bin verantwortlich für den 13. Tag des Adventskalenders. Ich bin hauptsächlich für die Entwicklung von Webanwendungen mit Java verantwortlich. Dieses Mal, als ich für einen bestimmten Job verantwortlich war, werde ich darüber sprechen, einen Reverse-Proxy in Java als Testwerkzeug zu erstellen.

Warum hast du es geschafft?

Der Begriff "Reverse Proxy" ist den Infrastrukturmitarbeitern vertrauter, kann aber manchmal auch von Webentwicklern gewünscht werden. (Wenn Sie fragen "Was ist überhaupt ein Reverse-Proxy?")

In meinem Fall ist beim Testen einer App mit der folgenden Konfiguration ein domänenübergreifendes Problem aufgetreten, und ich wollte es. qiita1.png

Es scheint, dass die Verwendung von Apache oder Nginx die beliebteste Methode zum Erstellen eines Reverse-Proxys ist, aber in meinem Fall leider ** "Die Installation von Exe für freie Software ist verboten. Von mir geschriebene Skripte sind in Ordnung. Ich arbeite in einer Umgebung wie "Ich werde dir vergeben" ** und konnte die oben genannte Software nicht verwenden. Deshalb habe ich einen Reverse-Proxy zum Testen in Java geschrieben, der Sprache, in der die App geschrieben ist.

Was ich benutzt habe

Verfassung

Das HTTP-Proxyservlet von Smiley bietet ein Servlet (ProxyServlet), das als Proxyserver fungiert. Mit ** URITemplateProxyServlet ** können Sie das Weiterleitungsziel (Hostname, Portnummer, Kontextpfad) mit der Abfragezeichenfolge dynamisch steuern. Das Verhalten des Reverse-Proxys wird reproduziert, indem die Abfragezeichenfolge gemäß der angeforderten URL mit dem Servlet-Filter festgelegt wird. seq.png

Implementierung

Mit Maven bauen.

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>

Als nächstes kommt die Hauptklasse. Registrieren Sie hier den Servlet-Filter und das ProxyServlet.

Main.java



@SpringBootApplication
public class Main extends SpringBootServletInitializer implements WebApplicationInitializer {

  /**
   *Einstiegspunkt beim selbständigen Starten.
   */
  public static void main(String[] args) throws Throwable {
    SpringApplicationBuilder builder = new SpringApplicationBuilder(Main.class);
    builder.run();
  }
  
  /**
   *Einstiegspunkt beim Starten des Servlet-Containers.
   */
  @Override
  protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    return application.sources(Main.class);
  }

  /**
   * {@see org.springframework.web.filter.HiddenHttpMethodFilter}Deaktivieren.
   * 
   *URITemplateProxyServlet ist{@link ServletRequest#getInputStream}Benutzen
   *Wenn auf den Anforderungsparameter auf der Filterseite zugegriffen wird, wird auf der Prozessservlet-Seite
   *Anforderungsparameter können nicht erfasst werden.
   *Daher ist in diesem Tool die von HiddenHttpMethodFilter bereitgestellte Funktion nicht erforderlich, sodass der Filter deaktiviert ist.
   */
  @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}Deaktivieren.
   * 
   *URITemplateProxyServlet ist{@link ServletRequest#getInputStream}Benutzen
   *Wenn auf den Anforderungsparameter auf der Filterseite zugegriffen wird, wird auf der Proxy-Servlet-Seite
   *Anforderungsparameter können nicht erfasst werden.
   *Daher ist in diesem Tool die von HttpPutFormContentFilter bereitgestellte Funktion nicht erforderlich, sodass der Filter deaktiviert ist.
   */
  @Bean
  public FilterRegistrationBean httpPutFormContentFilterRegistration(HttpPutFormContentFilter filter) {
    FilterRegistrationBean registration = new FilterRegistrationBean(filter);
    registration.setFilter(filter);
    registration.setEnabled(false);
    return registration;
  }
  
  /**
   *Registrierung des Servlet-Filters für die Webanwendung 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;
  }

  /**
   *Registrierung des Servlet-Filters für die Webanwendung 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;
  }
  
  /**
   *Registrierung des Proxy-Servlets.
   * @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;
  }
}

Als nächstes kommt der Servlet-Filter. Das Hauptverhalten wird als abstrakte Klasse definiert, und die Verarbeitung, die vom Übertragungsziel auf der Unterklassenseite abhängt, wird beschrieben.

AbstractRiverseProxyFilter.java


/**
 *Reverse-Proxy-Filter.
 * {@see org.mitre.dsmiley.httpproxy.URITemplateProxyServlet}Zu
 *Passen Sie die Anforderung und Antwort so an, dass sie als Reverse-Proxy fungieren.
 */
abstract public class AbstractRiverseProxyFilter implements Filter {
  
  /**
   *Holen Sie sich den Übertragungsziel-Host
   */
  abstract protected String getTransfarHost();

  /**
   *Forwarding Context Root
   */
  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}Zu
    //Fügen Sie den Parameter hinzu, um das Übertragungsziel für die Abfragezeichenfolge anzugeben
    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()));

    //Verhindert, dass die Standardcodierung des Servlet-Containers festgelegt wird
    _response.setCharacterEncoding(null);

    //Decken Sie das Anforderungsobjekt mit einem Wrapper ab und übergeben Sie es an das Proxy-Servlet
    RequestRewriteWrapper wrapRequest 
       = new RequestRewriteWrapper(request, routingQuery.toString());
    filterChain.doFilter(wrapRequest, _response);
  }

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

  @Override
  public void destroy() {}

  /**
   *Request Wrapper zum Umschreiben von Abfragezeichenfolgen
   */
  private static class RequestRewriteWrapper extends HttpServletRequestWrapper {

    private final String queryString;

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

    /**
     * {@link HttpServletRequest#getQueryString()}Verpackung.
     *Gibt eine Abfragezeichenfolge mit dem hinzugefügten Weiterleitungsziel zurück.
     */
    @Override
    public String getQueryString() {
      return this.queryString;
    }
  }
}

AAppRiverseProxyFilter.java


/**
 *Reverse-Proxy-Filter für Web-App A..
 */
public class AAppRiverseProxyFilter extends AbstractRiverseProxyFilter {

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

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

BAppRiverseProxyFilter.java


/**
 *Reverse-Proxy-Filter für Web-App B..
 */
public class BAppRiverseProxyFilter extends AbstractRiverseProxyFilter {

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

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

Lauf

Beginnen Sie mit dem folgenden Befehl.

$ mvn spring-boot:run

Wechseln Sie nach dem Starten des Tools im Browser zu Web App A mit "http : // localhost: 8080 / webapp-a /". Sie können auf Web App B mit "http : // localhost: 8080 / webapp-b /" zugreifen.

Schließlich

Der Inhalt dieses Artikels ist nur ein Auszug aus dem grundlegenden Teil des erstellten Tools. Bei der eigentlichen Erstellung eines Tools werden die Informationen zum Übertragungsziel nicht wie im obigen Beispiel in den Quellcode geschrieben, sondern @valueEs ist eine gute Idee, einen Mechanismus wie eine Anmerkung zum Abrufen zu verwenden.

Selbst wenn ich das Netz erwischt habe, gab es nicht viele Artikel, die Reverse Proxy in Java geschrieben haben, also habe ich es dieses Mal geschrieben. Der Reverse-Proxy in diesem Artikel ist nur ein ** "Test-Tool" **. Verwenden Sie daher ein anständiges Produkt für Ihre Produktionsumgebung. <(_ _)>


Morgen ist der 14. Tag. Dies ist ein Artikel von @modest.

Recommended Posts

Die Geschichte, einen Reverse-Proxy mit ProxyServlet zu erstellen
Die Geschichte eines Game Launcher mit automatischer Ladefunktion [Java]
Die Geschichte von dto, dao-like mit Java, SQLite
Die Geschichte der Erstellung einer Task-Management-Anwendung mit Swing, Java
Eine Geschichte voller Grundlagen von Spring Boot (gelöst)
Eine Geschichte über das Erreichen der League Of Legends-API mit JAVA
Eine Geschichte, die mit der Einführung von Web Apple Pay zu kämpfen hatte
Die Geschichte eines Othello-Spiels vom Kommunikationstyp mit Scala.
Eine Geschichte darüber, wie eine vorhandene Docker-Datei mit der GPU kompatibel gemacht werden kann
Die Geschichte der Optimierung der Android-App mit libGDX
Eine Geschichte über das Erstellen eines Builders, der den Builder erbt
Die Geschichte, ein Projekt zu bauen, das Maven mit Ant gebaut hat
Die Geschichte, Dr. Orchid mit LINE BOT zu machen
Eine Geschichte darüber, wie catkin_make von Rosjava offline kompatibel gemacht wird
Schreiben Sie einen Test, indem Sie die Geschichte von Mr. Nabeats in der Welt mit Rubin umsetzen
Eine Geschichte, die bei NotSerializableException steckt
Überprüfen Sie die Funktion von zwei Rollen mit einer Chat-Anwendung
Eine Geschichte, die von String () von Interface abhängig ist und von JdkDynamicAopProxy vertreten wird
Erläutern Sie die Vorzüge des staatlichen Musters anhand des Bewertungsurteils des Films
Finden Sie mit Kotlin die Anzahl der Tage in einem Monat
Mit der Software, die ich schon lange mache ...
Die Geschichte, wie ein Docker-Container mit GitHub-Aktionen in die GitHub-Paketregistrierung und den Docker-Hub verschoben wird
Die Geschichte des Erstellens einer Java-Version des Minecraft-Servers mit GCP (und dem Festlegen einer weißen Liste)
[Java Edition] Geschichte der Serialisierung
Rufen Sie in Spring Boot eine Proxy-Instanz der Komponente selbst ab
Eine Geschichte über die Verbindung zu einem CentOS 8-Server mit einem alten Ansible
Die Geschichte von @ViewScoped, die Speicher verschlingt
Die Geschichte von toString () beginnt mit der Übergabe eines Arrays an System.out.println
Ein Memorandum über das FizzBuzz-Problem
Eine Geschichte über die Herstellung eines Taschenrechners zur Berechnung der Muschelhügelrate
Eindrücke von Black Jack-Cli mit Ruby
[Illustration] Finden der Summe von Münzen mit einer rekursiven Funktion [Ruby]
Eine Geschichte, der ich mit der automatischen Starteinstellung von Tomcat 8 unter CentOS 8 zweimal verfallen war
Eine Geschichte, die ich mit der Stream-API von Java8 einem Prozess schreiben wollte, der einer while-Anweisung entspricht
Wie man IGV mit Socket-Kommunikation bedient und wie man einen Ruby Gem mit dieser Methode herstellt
Die Geschichte, zu vergessen, eine Datei in Java zu schließen und zu scheitern
Lassen Sie uns das Ergebnis der Analyse von Java-Bytecode in einem Klassendiagramm ausdrücken
Versuchen Sie, mit Keycloak (Security Proxy Edition) eine Konfiguration vom Typ Liverpro zu erstellen.
(Memo) Holen Sie sich mit Hilfe von Gradle eine Reihe von abhängigen Bibliotheksgläsern
Die Geschichte der Begegnung mit Spring Custom Annotation
Erstellen Sie mit dem Befehl eine JAR-Datei
Überprüfen Sie den Inhalt der Parameter mit pry
Die Geschichte der Aktualisierung des Docker-Containers von Sonar Qube
Die Geschichte von RxJava, das unter NoSuchElementException leidet
Führen Sie DMN mit der Camunda DMN Engine aus
Extrahieren Sie einen Teil einer Zeichenfolge in Ruby
Eine kleine süchtig machende Geschichte mit def initialize
Über die Behandlung von BigDecimal (mit Reflexion)
Die Geschichte des Schreibens von Java in Emacs
Formatieren Sie den Inhalt von LocalDate mit DateTimeFormatter
Finden Sie den Unterschied von einem Vielfachen von 10
Die Geschichte einer Android-Anwendung, mit der die Abtastfrequenz des Beschleunigungssensors angepasst werden kann
Der Name ist und das Alter ist Senden Sie eine Benachrichtigung an Slack mit der Java-freien Version von Sentry (mit Lambda)
Eine Geschichte, die die Implementierung der SendGrid-Java-Bibliothek bestätigt, wenn die E-Mail-Zustellung fehlschlägt
Überprüfen Sie das ID-Token eines von AWS Cognito in Java authentifizierten Benutzers
Ein kurzer Hinweis zur Verwendung von jshell mit dem offiziellen Docker-Image des JDK