This article is a relay article of 2018 Advent Calendar of Link Information Systems. .. Relayed by a group member of engineer.hanzomon. (For Facebook of the link information system, click here](https://ja-jp.facebook.com/lis.co.jp/))
My name is @shinevillage and I am in charge of the 13th day of the Advent calendar. I am mainly in charge of developing web applications using Java. This time, when I was in charge of a certain job, I will talk about making a reverse proxy in Java as a test tool.
The term "reverse proxy" is more familiar to infrastructure people, but it can sometimes be wanted by web developers as well. (Click here for "What is a reverse proxy in the first place?")
In my case, when testing an app with the following configuration, a cross-domain problem broke out and I wanted it.
It seems that Apache and Nginx are the most popular for building reverse proxies, but sadly in my case ** "Free software exe installation is prohibited. Scripts I wrote are ok. I'm working in an environment like "I'll forgive you" **, and I couldn't use the above software, so I wrote a reverse proxy for testing in Java, the language in which the app is written.
--Java 1.8 (Reason for selection: Because it is used at work) -Smiley's HTTP Proxy Servlet 1.10 (Reason for selection: Because it is easy to use) -Spring Boot 1.5.14 (Reason for selection: Because the executable war can be created quickly)
Smiley's HTTP Proxy Servlet provides a Servlet (ProxyServlet) that acts as a proxy server. You can use ** URITemplateProxyServlet ** to dynamically control the forwarding destination (host name, port number, context path) with the query string. The behavior of the reverse proxy is reproduced by setting the query string according to the requested URL with the Servlet filter.
Build using 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>
Next is the main class. Register the Jakarta Servlet and ProxyServlet here.
Main.java
@SpringBootApplication
public class Main extends SpringBootServletInitializer implements WebApplicationInitializer {
/**
*Entry point when starting independently.
*/
public static void main(String[] args) throws Throwable {
SpringApplicationBuilder builder = new SpringApplicationBuilder(Main.class);
builder.run();
}
/**
*Entry point when starting the Servlet container.
*/
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Main.class);
}
/**
* {@see org.springframework.web.filter.HiddenHttpMethodFilter}Disable.
*
*URITemplateProxyServlet is{@link ServletRequest#getInputStream}To use
*When the request parameter is accessed on the filter side, the process Servlet side
*Request parameters cannot be acquired.
*Therefore, in this tool, the function provided by HiddenHttpMethodFilter is unnecessary, so the filter is disabled.
*/
@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}Disable.
*
*URITemplateProxyServlet is{@link ServletRequest#getInputStream}To use
*When the request parameter is accessed on the filter side, the proxy servlet side
*Request parameters cannot be acquired.
*Therefore, in this tool, the function provided by HttpPutFormContentFilter is unnecessary, so the filter is disabled.
*/
@Bean
public FilterRegistrationBean httpPutFormContentFilterRegistration(HttpPutFormContentFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setFilter(filter);
registration.setEnabled(false);
return registration;
}
/**
*Registration of Servlet filter for Web application 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;
}
/**
*Registration of Servlet filter for Web application 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;
}
/**
*Proxy Servlet registration.
* @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;
}
}
Next is the Servlet filter. The main behavior is defined as an abstract class, and the processing that depends on the transfer destination on the subclass side is described.
AbstractRiverseProxyFilter.java
/**
*Reverse proxy filter.
* {@see org.mitre.dsmiley.httpproxy.URITemplateProxyServlet}To
*Customize the request and response to operate as a reverse proxy.
*/
abstract public class AbstractRiverseProxyFilter implements Filter {
/**
*Get the transfer destination host
*/
abstract protected String getTransfarHost();
/**
*Get 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}To
//Add the parameter to specify the transfer destination to the query string
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()));
//Prevents the default encoding of the Servlet container from being set
_response.setCharacterEncoding(null);
//Cover the request object with a wrapper and pass it to the 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 for rewriting the query string
*/
private static class RequestRewriteWrapper extends HttpServletRequestWrapper {
private final String queryString;
public RequestRewriteWrapper(HttpServletRequest request, String query) {
super(request);
this.queryString = query;
}
/**
* {@link HttpServletRequest#getQueryString()}Rapper.
*Returns the query string with the forwarding destination added.
*/
@Override
public String getQueryString() {
return this.queryString;
}
}
}
AAppRiverseProxyFilter.java
/**
*Reverse proxy filter for 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 for web app B.
*/
public class BAppRiverseProxyFilter extends AbstractRiverseProxyFilter {
@Override
protected String getTransfarHost() {
return "xxx.xxx.xxx.2";
}
@Override
protected String getTransfarContextRoot() {
return "webapp-b";
}
}
Start with the following command.
$ mvn spring-boot:run
After starting the tool, go to Web App A from the browser with "http : // localhost: 8080 / webapp-a /". You will be able to access Web App B with "http : // localhost: 8080 / webapp-b /".
The content of this article is an excerpt of only the basic part of the created tool.
When actually making a tool, the transfer destination information is not written in the source code like the above sample, but
@value
It is a good idea to use a mechanism such as annotation to fetch it.
Even if I caught the net, there were not many articles writing reverse proxies in Java, so I wrote it this time. The reverse proxy in this article is just a ** "testing tool" **, so use a decent product for your production environment. <(_ _)>
Tomorrow is the 14th day. This is an article by @modest.
Recommended Posts