Issue
I want my Java web application to seamless proxy content provided by abother web server.
http://myapp.com/proxy/*
->
http://other_app_to_proxy.com:9090/
My application will handle all auth-related matters, and serve other application on sub-path under same domain.
I found how to do reverse proxy: HTTP-Proxy-Servlet.
The problem now is that other application has absolute URLs like /css/style.css
and when page gets open in my application, this URL is not accessible, as in my setup it should be /proxy/css/style.css
.
I figured out I need some kind of URL-rewriting filter that would alter outbound response that goes to client. I tried to study Tuckey UrlRewrite but it looks like it's for different purpose - it has plenty of tools to change inbound URL and redirect requests to other locations.
Could somebody point me to some solution?
Solution
I came up to several classes that allow full response body rewriting in filter.
A couple of base classes:
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
public abstract class AbstractResponseAlteringFilter implements Filter {
public void init(FilterConfig fConfig) throws ServletException {
}
protected static class ByteArrayServletStream extends ServletOutputStream {
ByteArrayOutputStream baos;
ByteArrayServletStream(ByteArrayOutputStream baos) {
this.baos = baos;
}
public void write(int param) throws IOException {
baos.write(param);
}
}
protected static class ByteArrayPrintWriter {
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
private PrintWriter pw = new PrintWriter(baos);
private ServletOutputStream sos = new ByteArrayServletStream(baos);
public PrintWriter getWriter() {
return pw;
}
public ServletOutputStream getStream() {
return sos;
}
byte[] toByteArray() {
return baos.toByteArray();
}
}
protected static class CharResponseWrapper extends HttpServletResponseWrapper {
private ByteArrayPrintWriter output;
private boolean usingWriter;
public CharResponseWrapper(HttpServletResponse response) {
super(response);
usingWriter = false;
output = new ByteArrayPrintWriter();
}
public byte[] getByteArray() {
return output.toByteArray();
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
// will error out, if in use
if (usingWriter) {
super.getOutputStream();
}
usingWriter = true;
return output.getStream();
}
@Override
public PrintWriter getWriter() throws IOException {
// will error out, if in use
if (usingWriter) {
super.getWriter();
}
usingWriter = true;
return output.getWriter();
}
public String toString() {
return output.toString();
}
}
public void destroy() {
}
}
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
public abstract class AbstractResponseBodyAlteringFilter extends AbstractResponseAlteringFilter {
private final static Logger logger = LoggerFactory.getLogger(AbstractResponseBodyAlteringFilter.class);
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
CharResponseWrapper wrappedResponse = new CharResponseWrapper(httpResponse);
chain.doFilter(request, wrappedResponse);
logger.info("wrappedResponse.getContentType() = {}", wrappedResponse.getContentType());
byte[] responseBytes = wrappedResponse.getByteArray();
if (StringUtils.containsAny(wrappedResponse.getContentType(), "text/", "javascript")) {
responseBytes = modifyResponseBody(new String(responseBytes, "UTF-8")).getBytes("UTF-8");
}
OutputStream out = httpResponse.getOutputStream();
logger.info("wrappedResponse.getStatus() = {}", wrappedResponse.getStatus());
httpResponse.setStatus(wrappedResponse.getStatus());
httpResponse.setContentType(wrappedResponse.getContentType());
httpResponse.setContentLength(responseBytes.length);
out.write(responseBytes);
out.flush();
httpResponse.flushBuffer();
}
protected abstract String modifyResponseBody(String body);
}
And here final user class:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
public class ProxyResponseBodyRewriteFilter extends AbstractResponseBodyAlteringFilter {
@Value("${proxy.filter.prefix:/proxy/}")
String prefix;
private final static Logger logger = LoggerFactory.getLogger(ProxyResponseBodyRewriteFilter.class);
protected String modifyResponseBody(String body) {
body = body.replaceAll("href\\s*=\\s*\"\\s*/", "href=\"" + prefix);
body = body.replaceAll("src\\s*=\\s*\"\\s*/", "src=\"" + prefix);
body = body.replace("</head>", "<base href=\"/proxy/\"></head>");
return body;
}
}
Answered By - snowindy