web proxy server by jetty |
Jetty9 makes it straightforward to build a Web Proxy Server. Using its ProxyServlet
class, you can intercept incoming web requests for a specific host and modify or transform the responses as needed.
Below is an example that demonstrates how to leverage ProxyServlet
to customize responses for specific incoming web requests.
Key Features
- Simple Integration with Jetty9: Use
ProxyServlet
to handle proxying logic seamlessly. - Intercept and Modify Responses: Customize or transform server responses before passing them to the client.
- Dynamic Proxy Behavior: Add logic for filtering, caching, or monitoring requests.
Maven
<dependency>
<groupid>org.eclipse.jetty</groupid>
<artifactid>jetty-server</artifactid>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupid>org.eclipse.jetty</groupid>
<artifactid>jetty-proxy</artifactid>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupid>org.eclipse.jetty</groupid>
<artifactid>jetty-servlet</artifactid>
<version>${jetty.version}</version>
</dependency>
Main
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(8080);
server.addConnector(connector);
ConnectHandler proxy = new ConnectHandler();
server.setHandler(proxy);
CustomProxyServlet customProxyServlet = new CustomProxyServlet();
customProxyServlet.addHostFilter("maybe.somewhere.com", new DefaultFilterableHost()).referHostFilterByUrl("maybe.somewhere.com", "mightbe.somewhere.com");
// Setup proxy servlet
ServletContextHandler context = new ServletContextHandler(proxy, "/", ServletContextHandler.SESSIONS);
ServletHolder proxyServlet = new ServletHolder(customProxyServlet);
proxyServlet.setInitParameter("maxThreads", "10");
context.addServlet(proxyServlet, "/*");
server.start();
server.join();
Filterable Host Interface
public interface FilterableHost {
boolean canHandle(URL url);
void process(final HttpServletRequest request, final HttpServletResponse response);
}
Default Filterable Host
public class DefaultFilterableHost implements FilterableHost {
@Override
public boolean canHandle(URL url) {
return url.getPath().endsWith("/somepath");
}
@Override
public void process(HttpServletRequest request, HttpServletResponse response) {
response.setStatus(HttpStatus.OK_200);
response.addHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.KEEP_ALIVE.asString());
response.addHeader(HttpHeader.TRANSFER_ENCODING.asString(), HttpHeaderValue.CHUNKED.asString());
response.getOutputStream().write("some your revised response");
return response;
}
}
Proxy servlet
public class CustomProxyServlet extends ProxyServlet {
private final Map filterMap;
public CustomProxyServlet() {
this.filterMap = new HashMap<>();
}
public CustomProxyServlet referHostFilterByUrl(String fromUrl, String toUrl) {
if(filterMap.containsKey(fromUrl)) {
filterMap.put(toUrl, filterMap.get(fromUrl));
return this;
} else {
throw new IllegalStateException("can't find " + fromUrl + " in filterMap. you should put hostFilter for " + fromUrl + " first");
}
}
public CustomProxyServlet addHostFilter(String url, FilterableHost filter) {
if(filterMap.containsKey(url) == false) {
filterMap.put(url, filter);
return this;
} else{
throw new IllegalStateException("host url " + url + " already exists");
}
}
@Override
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
URL url = new URL(request.getRequestURL().toString()); // Extract host from URL
if(filterMap.containsKey(url.getHost())) {
final FilterableHost filterableHost = filterMap.get(url.getHost());
if(filterableHost.canHandle(url)) {
final AsyncContext asyncContext = request.startAsync();
// We do not timeout the continuation, but the proxy request
asyncContext.setTimeout(0);
filterableHost.process(request, response);
asyncContext.complete();
}
} else {
super.service(request, response);
}
}
@Override
protected Response.Listener newProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
{
return new CustomProxyServletProxyResponseListener(request, response);
}
protected class CustomProxyServletProxyResponseListener extends Response.Listener.Adapter
{
private final HttpServletRequest request;
private final HttpServletResponse response;
protected CustomProxyServletProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
{
this.request = request;
this.response = response;
}
@Override
public void onBegin(Response proxyResponse)
{
response.setStatus(proxyResponse.getStatus());
}
@Override
public void onHeaders(Response proxyResponse)
{
onServerResponseHeaders(request, response, proxyResponse);
}
@Override
public void onContent(final Response proxyResponse, ByteBuffer content, final Callback callback)
{
byte[] buffer;
int offset;
int length = content.remaining();
if (content.hasArray())
{
buffer = content.array();
offset = content.arrayOffset();
}
else
{
buffer = new byte[length];
content.get(buffer);
offset = 0;
}
onResponseContent(request, response, proxyResponse, buffer, offset, length, new Callback.Nested(callback)
{
@Override
public void failed(Throwable x)
{
super.failed(x);
proxyResponse.abort(x);
}
});
}
@Override
public void onComplete(Result result)
{
if (result.isSucceeded())
onProxyResponseSuccess(request, response, result.getResponse());
else
onProxyResponseFailure(request, response, result.getResponse(), result.getFailure());
}
}
}
Full source: https://gist.github.com/magicsih/cd9fa8b96e58fa7a8f977c82ad7a8d5f
Use Cases
- Web Proxy Server:
- Proxy requests to a target server and apply transformations to the response.
- Content Filtering:
- Block or modify specific content in the response.
- Performance Monitoring:
- Log or analyze requests and responses for debugging or analytics.