【问题标题】:Forward HttpServletRequest to a different server将 HttpServletRequest 转发到不同的服务器
【发布时间】:2012-08-21 07:01:07
【问题描述】:

我的 Spring Servlet 中有一个 HttpServletRequest 请求,我想将其原样(即 GET 或 POST 内容)转发到不同的服务器。

使用 Spring Framework 最好的方法是什么?

我是否需要获取所有信息并构建一个新的HTTPUrlConnection?还是有更简单的方法?

【问题讨论】:

    标签: java spring http spring-mvc servlets


    【解决方案1】:

    关于您是否应该以这种方式转发的讨论,我是这样做的:

    package com.example.servlets;
    
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.Enumeration;
    
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import com.example.servlets.GlobalConstants;
    
    @SuppressWarnings("serial")
    public class ForwardServlet extends HttpServlet {
    
        @Override
        public void doGet(HttpServletRequest req, HttpServletResponse resp) {
            forwardRequest("GET", req, resp);
        }
    
        @Override
        public void doPost(HttpServletRequest req, HttpServletResponse resp) {
            forwardRequest("POST", req, resp);
        }
    
        private void forwardRequest(String method, HttpServletRequest req, HttpServletResponse resp) {
            final boolean hasoutbody = (method.equals("POST"));
    
            try {
                final URL url = new URL(GlobalConstants.CLIENT_BACKEND_HTTPS  // no trailing slash
                        + req.getRequestURI()
                        + (req.getQueryString() != null ? "?" + req.getQueryString() : ""));
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod(method);
    
                final Enumeration<String> headers = req.getHeaderNames();
                while (headers.hasMoreElements()) {
                    final String header = headers.nextElement();
                    final Enumeration<String> values = req.getHeaders(header);
                    while (values.hasMoreElements()) {
                        final String value = values.nextElement();
                        conn.addRequestProperty(header, value);
                    }
                }
    
              //conn.setFollowRedirects(false);  // throws AccessDenied exception
                conn.setUseCaches(false);
                conn.setDoInput(true);
                conn.setDoOutput(hasoutbody);
                conn.connect();
    
                final byte[] buffer = new byte[16384];
                while (hasoutbody) {
                    final int read = req.getInputStream().read(buffer);
                    if (read <= 0) break;
                    conn.getOutputStream().write(buffer, 0, read);
                }
    
                resp.setStatus(conn.getResponseCode());
                for (int i = 0; ; ++i) {
                    final String header = conn.getHeaderFieldKey(i);
                    if (header == null) break;
                    final String value = conn.getHeaderField(i);
                    resp.setHeader(header, value);
                }
    
                while (true) {
                    final int read = conn.getInputStream().read(buffer);
                    if (read <= 0) break;
                    resp.getOutputStream().write(buffer, 0, read);
                }
            } catch (Exception e) {
                e.printStackTrace();
                // pass
            }
        }
    }
    

    显然,这可能需要一些关于错误处理等的工作,但它是功能性的。但是,我停止使用它,因为在我的情况下,直接调用 CLIENT_BACKEND 比处理跨两个不同域的 cookie、auth 等更容易。

    【讨论】:

    • 我测试过了。但是,conn.getHeaderFieldKey(0) 返回 null。我已经把 i=1 所以它工作正常。 docs.oracle.com/javase/7/docs/api/java/net/…
    • 不知道该说什么。我(简要地)使用了这个,它没有按原样工作得很好。我不再运行它,所以我无法进行任何进一步的验证。
    【解决方案2】:

    我也需要这样做,在使用 Spring 控制器和 RestTemplate 进行了一些非优化之后,我找到了更好的解决方案:Smiley's HTTP Proxy Servlet。好处是,它确实做到了 AS-IS 代理,就像 Apache 的 mod_proxy 一样,而且它以流式方式进行,无需在内存中缓存完整的请求/响应。

    简单地说,您将一个新的 servlet 注册到您要代理到另一台服务器的路径,并将此 servlet 指定为目标主机作为 init 参数。如果您使用带有 web.xml 的传统 Web 应用程序,您可以像下面这样配置它:

    <servlet>
        <servlet-name>proxy</servlet-name>
        <servlet-class>org.mitre.dsmiley.httpproxy.ProxyServlet</servlet-class>
        <init-param>
          <param-name>targetUri</param-name>
          <param-value>http://target.uri/target.path</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
      <servlet-name>proxy</servlet-name>
      <url-pattern>/mapping-path/*</url-pattern>
    </servlet-mapping>
    

    当然,您也可以使用annotation config。

    如果你使用的是 Spring Boot,那就更简单了:你只需要创建一个 ServletRegistrationBean 类型的 bean,并进行所需的配置:

    @Bean
    public ServletRegistrationBean proxyServletRegistrationBean() {
        ServletRegistrationBean bean = new ServletRegistrationBean(
                new ProxyServlet(), "/mapping-path/*");
        bean.addInitParameter("targetUri", "http://target.uri/target.path");
        return bean;
    }
    

    这样,您还可以使用环境中可用的 Spring 属性。

    如果需要,您甚至可以扩展 ProxyServlet 类并覆盖其方法来自定义请求/响应标头等。

    更新:在使用Smiley 的代理servlet 一段时间后,我们遇到了一些超时问题,它不能可靠地工作。从 Netflix 切换到 Zuul,之后没有任何问题。在 this link 上可以找到使用 Spring Boot 配置它的教程。

    【讨论】:

      【解决方案3】:

      不幸的是,没有简单的方法可以做到这一点。基本上你必须重构请求,包括:

      • 正确的 HTTP 方法
      • 请求参数
      • 请求标头(HTTPUrlConnection 不允许设置任意用户代理,始终附加“Java/1.*”,您需要 HttpClient)
      • 身体

      这是很多工作,更不用说它不会扩展,因为每个这样的代理调用都会占用您机器上的一个线程。

      我的建议:使用原始套接字或 并在最低级别拦截 HTTP 协议,只需即时替换一些值(如 Host 标头)。您能否提供更多上下文,为什么需要这个?

      【讨论】:

      • 我有一个客户端、一个中间服务器和几个主服务器。客户端只与将他的呼叫分派给服务器的中间服务器对话。服务器向中间服务器返回一个响应,然后他处理它,然后将响应返回给客户端。
      • 不应该复制请求标头(3)和正文(4)来弥补复制请求参数(2)(因为post参数是请求正文的一部分,因为获取它们将是一部分网址)?如果我执行这两个步骤会是多余的(在 http 请求中)
      【解决方案4】:
      @RequestMapping(value = "/**")
      public ResponseEntity route(HttpServletRequest request) throws IOException {
          String body = IOUtils.toString(request.getInputStream(), Charset.forName(request.getCharacterEncoding()));
          try {
              ResponseEntity<Object> exchange = restTemplate.exchange(firstUrl + request.getRequestURI(),
                      HttpMethod.valueOf(request.getMethod()),
                      new HttpEntity<>(body),
                      Object.class,
                      request.getParameterMap());
              return exchange;
          } catch (final HttpClientErrorException e) {
              return new ResponseEntity<>(e.getResponseBodyAsByteArray(), e.getResponseHeaders(), e.getStatusCode());
          }
      }
      

      【讨论】:

      • 这适用于简单的请求。它不会处理多部分请求。
      【解决方案5】:

      如果强制使用spring,请查看rest template method exchange代理请求到第三方服务。

      Here你可以找到一个工作示例。

      【讨论】:

        【解决方案6】:

        使用Spring Cloud Gateway

        pom.xml

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-gateway-mvc</artifactId>
            </dependency>  
        
        
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>2020.0.2</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
        

        控制器

        import org.springframework.beans.factory.annotation.Value;
        import org.springframework.cloud.gateway.mvc.ProxyExchange;
        import org.springframework.http.ResponseEntity;
        import org.springframework.util.MultiValueMap;
        import org.springframework.web.bind.annotation.*;
        import org.springframework.web.util.UriComponents;
        import org.springframework.web.util.UriComponentsBuilder;
        
        @RestController
        public class Proxy extends BaseController {
        
            private String prefix="/proxy";
            private String Base="localhost:8080/proxy";  //destination
        
            void setHeaders(ProxyExchange<?> proxy){
                proxy.header("header1", "val1"); //add additional headers
            }
        
            @GetMapping("/proxy/**")
            public ResponseEntity<?> proxyPath(@RequestParam MultiValueMap<String,String> allParams, ProxyExchange<?> proxy) throws Exception {
                String path = proxy.path(prefix); //extract sub path
                proxy.header("Cache-Control", "no-cache");
                setHeaders(proxy);
        
                UriComponents uriComponents =  UriComponentsBuilder.fromHttpUrl(Base + path).queryParams(allParams).build();
                return proxy.uri(uriComponents.toUri().toString()).get();
            }
        
            @PutMapping("/proxy/**")
            public ResponseEntity<?> proxyPathPut(ProxyExchange<?> proxy) throws Exception {
                String path = proxy.path(prefix);
                setHeaders(proxy);
                return proxy.uri(Base + path).put();
            }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-02-07
          • 2021-09-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-01-23
          相关资源
          最近更新 更多