【问题标题】:Reverse Proxy: Accessing .Net Web Application from Java Web Application反向代理:从 Java Web 应用程序访问 .Net Web 应用程序
【发布时间】:2021-07-14 15:30:45
【问题描述】:

以下是我们拥有/想要实施的场景:

  1. 我们在 apache tomcat 上部署了一个基于 java spring 的 Web 应用程序。
  2. 在我们的 Web 应用程序中,我们将为用户提供一个基于 .net 的第三方 Web 应用程序链接。单击哪个用户将被重定向到 .net 应用程序,但浏览器 url 中显示的域和上下文根将保持与 java 应用程序相同。
  3. 我们不能要求第 3 方团队更改其应用程序中的任何内容。
  4. 我们必须更改的内容应该在我们的代码中,而且我们应该尽可能避免任何 apache tomcat 级别的更改。

以下是我们目前实现的内容:

  1. 我们尝试使用以下链接实现反向代理:https://github.com/mitre/HTTP-Proxy-Servlet
  2. 因此,基本原理是将第 3 方 url 调用为 web 服务并编辑 httpresponse 并将我们的域和上下文路径添加到作为响应接收的任何文件中的 url。虽然这不是一个好的解决方案并且会消耗大量时间和空间,但它确实可以完成工作。

我们面临的问题:

  1. 除 .net 应用程序中的 ajax 请求外,一切正常。
  2. 抛出的错误是:Sys.WebForms.PageRequestManagerParserErrorException:无法解析从服务器接收到的消息。此错误的常见原因是通过调用 Response.Write()、响应过滤器、HttpModules 或启用了服务器跟踪来修改响应。

这里是做httpresponse修改任务的代码片段:

protected void copyResponseEntity(HttpResponse proxyResponse, HttpServletResponse servletResponse,
                                    HttpRequest proxyRequest, HttpServletRequest servletRequest)throws IOException {
  
    HttpEntity entity = new BufferedHttpEntity(proxyResponse.getEntity());
    if (entity.isChunked()) {
        InputStream is = entity.getContent();
            String proxyBody = EntityUtils.toString(entity);
              proxyBody = proxyBody.replaceAll("/.netContextRoot/", "/ourContextRoot/.netContextRoot/");
    
            InputStream stream = new ByteArrayInputStream(proxyBody.getBytes(StandardCharsets.UTF_8));
    
            OutputStream os = servletResponse.getOutputStream();
            byte[] buffer = new byte[10 * 1024];
            int read;
            while ((read = stream.read(buffer)) != -1) {
              os.write(buffer, 0, read);
              if (doHandleCompression || stream.available() == 0 /* next is.read will block */) {
                os.flush();
              }
            }
            // Entity closing/cleanup is done in the caller (#service)
          } else {
    
            String proxyBody = EntityUtils.toString(entity);
            proxyBody = proxyBody.replaceAll("/.netContextRoot/", "/ourContextRoot/.netContextRoot/");
    
            EntityUtils.updateEntity(proxyResponse, new StringEntity(proxyBody));
            HttpEntity entity2  =  proxyResponse.getEntity();
    
            OutputStream servletOutputStream = servletResponse.getOutputStream();
    
            entity2.writeTo(servletOutputStream);
    
    }
}

任何人都可以帮助我们解决这种情况吗?另外,如果您有任何其他解决方案而不对 apache 级别进行任何更改,那么请提及。

提前致谢。

【问题讨论】:

  • 你不能使用 iframe 吗?它仍然可以是您的应用程序并允许第 3 方显示该应用程序
  • 如果您无法控制要求 3rd 方域甚至允许您的应用程序通过 IFrame 或类似要求访问它,那么您只剩下包装所有功能并在那个用户界面。其上的任何反向代理最终也会开始显示带有他们引入的安全层的阻止程序。想象一下,如果我们在任何银行域上做一个反向代理并在中间监听所有请求! (中间人)
  • 我认为@NagarajTantri 在推荐 iframe 使用时有最好的方法; CORS 异常可以通过在您的终端或 apache 上传递 "allow cross domain" 来解决(您说您应该避免使用它,但并不是说它根本无法完成 - 如果没有,它将是一个单一的设置) t 为你工作)。
  • @SammuelMiranda 我认为他还提到他们无法在 3rd 方应用程序中更改 CORS 标头。考虑到必须为应该允许(第三方)的服务启用这些 CORS,这将再次成为一个问题。
  • 如果您在上面包含的代码中遇到的唯一问题是来自客户端的特定类型请求的 c# 异常,那么我将通过尝试找出这些失败请求的原因来开始调试不同于上班族。我怀疑您的代码弄乱了一些请求并导致 c# 应用程序的行为不同。您可以使用 wireshark 分析请求和响应以查看差异。

标签: java .net proxy url-rewriting reverse-proxy


【解决方案1】:

我终于找到了一些躲避 PageRequestManagerParserErrorException 的答案。

  1. You can refer this link if you are allowed to make changes in 3rd party application:请阅读 cmets 以获得更好的理解。
  2. 您可以创建一个仅用于反向代理的新应用程序,其 contextroot 名称与第 3 方的 context root 名称相同,并将其部署在您的应用程序域中,因此您无需编辑 http 响应的正文内容来设置您的应用程序小路。然后您可以从旧的应用程序中调用此应用程序,因为两者都在同一个域中。
  3. 我确实在 PageRequestManagerParserErrorException 中进行了一些挖掘,并发现了一些流程,当知道您已经编辑了 httpresponse 并且他们使用的逻辑是他们只是用httpresponse的实际长度检查httpresponse的长度。因此,如果您以某种方式设法在不更改响应长度的情况下编辑 httpresponse,那么您已经完成了任务。

注意:

a) You can use this link to achieve reverse proxy:因为这只是代码级别的更改,所以实现起来非常简单,也不需要服务器级别的配置更改。

b) PageRequestManagerParserErrorException 发生在您在将 httpResponse 发送到客户端之前对其进行编辑并由 .net 的 eventValidation 触发。

c)如果您不允许像我的场景一样在 3rd 方应用程序中进行任何更改,则第一种解决方案将毫无用处。暂时我选择了第三个,因为它毫不费力地完成了这项工作。

【讨论】:

    【解决方案2】:

    这最好作为 Servlet 上的过滤器来完成。 Filter API 允许您插入一些有关处理请求的逻辑,但它应该不知道如何处理请求。 API 定义了一个巧妙的过滤器链,它就像一堆请求事件,每个事件都可以独立地处理响应。过滤器可以放置在链中的任何点,它可以在使用doFilter() 方法执行时观察请求和响应。您应该能够在不知道其实现的情况下检测到第 3 方请求并将其包装在过滤器中。

    Servlet vs Filter

    这里的常用方法是编写请求或响应包装器,以访问底层流的读取和写入方法。我已经修改了this example 中的示例以满足您的需要:

    public class ThirdPartyRoutingResponseWrapper extends HttpServletResponseWrapper {
        private CharArrayWriter writer;
        private boolean _isThirdParty;
          
        public ThirdPartyRoutingResponseWrapper (HttpServletResponse response, boolean isThirdParty) {
            super(response);
            writer = new CharArrayWriter();
            _isThirdParty=isThirdParty;
        }
          
        public PrintWriter getWriter() {
            return new PrintWriter(writer);
        }
          
        public String toString() {
              if(_isThirdParty){
                 return writer.toString().replaceAll("/.netContextRoot/", "/ourContextRoot/.netContextRoot/");
              }
              else {
                 return writer.toString();
              }
         }
    }
    

    如果在构造函数中设置了isThirdParty 标志,则此响应包装器将替换响应的内容。然后过滤器在调用doFilter 以将包装器添加到链中时设置标志:

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
                
        //decide if the request is routable:
        boolean needsRoute = ((HttpServletRequest) request).getRequestURI().startsWith("/.netContextRoot/")  ) ;
                
        // execute the request with the wrapper configured for this response
        chain.doFilter(request, new ThirdPartyRoutingResponseWrapper ( response, needsRoute ));
              
    }
        
         
    

    只要您的第三方请求在链上较低的过滤器中处理,这应该可以拦截和修改响应。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-22
      • 2018-07-03
      • 1970-01-01
      • 2018-04-18
      • 1970-01-01
      相关资源
      最近更新 更多