Servlets 带有响应HTML URL重写的Java http反向代理
我希望我的Java web应用程序能够无缝地代理其他web服务器提供的内容Servlets 带有响应HTML URL重写的Java http反向代理,servlets,url-rewriting,servlet-filters,Servlets,Url Rewriting,Servlet Filters,我希望我的Java web应用程序能够无缝地代理其他web服务器提供的内容 http://myapp.com/proxy/*->http://other_app_to_proxy.com:9090/ 我的应用程序将处理所有与身份验证相关的事宜,并在同一域下的子路径上为其他应用程序提供服务 我找到了如何执行反向代理: 现在的问题是,其他应用程序具有绝对URL,如/css/style.css,当页面在我的应用程序中打开时,此URL不可访问,因为在我的设置中它应该是/proxy/css/style.c
http://myapp.com/proxy/*
->
http://other_app_to_proxy.com:9090/
我的应用程序将处理所有与身份验证相关的事宜,并在同一域下的子路径上为其他应用程序提供服务
我找到了如何执行反向代理:
现在的问题是,其他应用程序具有绝对URL,如/css/style.css
,当页面在我的应用程序中打开时,此URL不可访问,因为在我的设置中它应该是/proxy/css/style.css
我发现我需要某种URL重写过滤器来改变发送到客户端的出站响应。我试着去研究,但它看起来有不同的用途——它有很多工具可以更改入站URL并将请求重定向到其他位置
有人能告诉我一些解决方案吗?尝试在反向代理服务器的配置文件中将location/proxy更改为/proxy/(尾部斜杠) 如果您使用nginx作为反向代理服务器,请参阅链接
我找到了几个类,它们允许在过滤器中重写完整响应体 几个基类:
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);
}
这里是最终用户类:
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;
}
}
import org.slf4j.Logger;
导入org.slf4j.LoggerFactory;
导入org.springframework.beans.factory.annotation.Value;
公共类ProxyResponseBodyRewriteFilter扩展了AbstractResponseBodyAlteringFilter{
@值(${proxy.filter.prefix:/proxy/}”)
字符串前缀;
私有最终静态记录器Logger=LoggerFactory.getLogger(ProxyResponseBodyRewriteFilter.class);
受保护的字符串modifyResponseBody(字符串正文){
body=body.replaceAll(“href\\s*=\\s*\”\\s*/“,“href=\”+前缀);
body=body.replaceAll(“src\\s*=\\s*\”\\s*/“,“src=\”+前缀);
主体=主体。替换(“,”);
返回体;
}
}
我面临一个类似的问题,想要使用引用的HttpProxyServlet,但也需要更改响应中嵌入的URL。作为ProxyServlet子类化的一部分,我做了以下工作,它是有效的(尽管代码仍然有点倾向于我的用例,并且不是完全通用的),但我很好奇,您是否发现过更简单/已经编写的@snowindy
public class CustomProxyServlet extends ProxyServlet {
@Override
protected void copyResponseEntity(
HttpResponse proxyResponse, HttpServletResponse servletResponse,
HttpRequest proxyRequest, HttpServletRequest servletRequest
) throws IOException {
HttpEntity entity = proxyResponse.getEntity();
if (entity != null) {
OutputStream servletOutputStream = servletResponse.getOutputStream();
if (isRewritable(proxyResponse)) {
RewriteOutputStream rewriter = null;
try {
rewriter = new RewriteOutputStream(servletOutputStream);
entity.writeTo(rewriter);
}
finally { rewriter.flush(); }
}
else {
// parent's default behavior
entity.writeTo(servletOutputStream);
}
}
}
private boolean isRewritable(HttpResponse httpResponse) {
boolean rewriteable = false;
Header[] contentTypeHeaders = httpResponse.getHeaders("Content-Type");
for (Header header : contentTypeHeaders) {
// May need to accept other types
if (header.getValue().contains("html")) rewriteable = true;
}
return rewriteable;
}
}
RewriteOutputStream在哪里
public class RewriteOutputStream extends FilterOutputStream {
// Provided implementation based on BufferedOutputStream adding
// config-based string replacement before writing to output.
}
诚然,这是一种变通方法,而不是实际的解决方案,但是这些信息对于那些控制代理应用程序的人来说可能很有用。我们能够按原样使用Smiley的代理Servlet,并通过使用与代理Servlet提供代理页面的结构相匹配的结构部署代理应用程序,避免了URL重写
*->http://other_app_to_proxy.com:9090/proxy/*您的答案不相关,因为我不使用Nginx。在这种情况下,您可以使用用于反向代理的服务器配置文件来解决这个问题。您正在重新发明轮子。将Nginx或Apache放在您的应用程序服务器前面,并利用经过多年测试和修复的代理代码。@stdunbar然后我需要在这些服务器中插入身份验证代码,这看起来也不太好。您认为这是为什么?我在多个Java服务器前安装了Apache,所有授权和身份验证都在Java服务器中完成。@stdunbar请仔细阅读问题。我希望代理应用程序受到完全相同的代码的保护,就像它是我的应用程序的一部分一样。因此,身份验证应该在代理到最终应用程序之前进行。因此,我要么在我的应用程序中实现代理,要么将auth外部化到nginx/apache,这比代理servlet麻烦多了。对于基于react的应用程序,我添加了:
body=body.replaceAll(“\uuuuu webpack\u require\uuuu.p*=\\s*\”\\s*/\”、“\uu webpack\u require\uuu.p=\”/proxy/\”)代码>