spring mvc rest service redirect/forward/proxy

nil*_*gun 43 java spring-mvc resttemplate spring-boot

我使用spring mvc框架构建了一个Web应用程序来发布REST服务.例如:

@Controller
@RequestMapping("/movie")
public class MovieController {

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public @ResponseBody Movie getMovie(@PathVariable String id, @RequestBody user) {

    return dataProvider.getMovieById(user,id);

}
Run Code Online (Sandbox Code Playgroud)

现在我需要部署我的应用程序,但是我遇到以下问题:客户端无法直接访问应用程序所在的计算机(有防火墙).因此,我需要在代理机器上(可由客户端访问)调用实际的休息服务的重定向层.

我尝试使用RestTemplate进行新的调用:例如:

@Controller
@RequestMapping("/movieProxy")
public class MovieProxyController {

    private String address= "http://xxx.xxx.xxx.xxx:xx/MyApp";

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public @ResponseBody Movie getMovie(@PathVariable String id,@RequestBody user,final HttpServletResponse response,final HttpServletRequest request) {

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate.exchange( address+ request.getPathInfo(), request.getMethod(), new HttpEntity<T>(user, headers), Movie.class);

}
Run Code Online (Sandbox Code Playgroud)

这没关系,但我需要重写控制器中的每个方法以使用resttemplate.此外,这会导致代理计算机上的冗余序列化/反序列化.

我尝试使用restemplate编写泛型函数,但它没有用完:

@Controller
@RequestMapping("/movieProxy")
public class MovieProxyController {

    private String address= "http://xxx.xxx.xxx.xxx:xx/MyApp";

    @RequestMapping(value = "/**")
    public ? redirect(final HttpServletResponse response,final HttpServletRequest request) {        
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate.exchange( address+ request.getPathInfo(), request.getMethod(), ? , ?);

}
Run Code Online (Sandbox Code Playgroud)

我找不到一个与请求和响应对象一起使用的resttemplate方法.

我也尝试过弹簧重定向和前进.但重定向不会改变请求的客户端IP地址,所以我认为在这种情况下它是无用的.我也无法转发到其他网址.

有没有更合适的方法来实现这一目标?提前致谢.

koe*_*koe 59

您可以使用以下方式镜像/代理所有请求:

private String server = "localhost";
private int port = 8080;

@RequestMapping("/**")
@ResponseBody
public String mirrorRest(@RequestBody String body, HttpMethod method, HttpServletRequest request) throws URISyntaxException
{
    URI uri = new URI("http", null, server, port, request.getRequestURI(), request.getQueryString(), null);

    ResponseEntity<String> responseEntity =
        restTemplate.exchange(uri, method, new HttpEntity<String>(body), String.class);

    return responseEntity.getBody();
}
Run Code Online (Sandbox Code Playgroud)

这不会镜像任何标头.

  • 如果 REST API 返回 404 响应或类似的响应,它也会通过吗?或者代码只会抛出异常? (2认同)

Mua*_*tik 18

您可以使用Netflix Zuul将发送到spring应用程序的请求路由到另一个spring应用程序.

假设您有两个应用程序:1.songs-app,2.api-gateway

在api-gateway应用程序中,首先添加zuul依赖,然后您可以在application.yml中简单地定义路由规则,如下所示:

的pom.xml

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    <version>LATEST</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

application.yml

server:
  port: 8080
zuul:
  routes:
    foos:
      path: /api/songs/**
      url: http://localhost:8081/songs/
Run Code Online (Sandbox Code Playgroud)

最后运行api-gateway应用程序,如:

@EnableZuulProxy
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,网关将所有/api/songs/请求路由到http://localhost:8081/songs/.

一个工作的例子在这里:https://github.com/muatik/spring-playground/tree/master/spring-api-gateway

另一种资源:http://www.baeldung.com/spring-rest-with-zuul-proxy

  • 这不会给应用程序带来所有云依赖项的负担吗?尝试单独使用Zuul,但没有效果。有在没有云的情况下使用 Zuul 的示例吗? (2认同)
  • Zuul 似乎已被弃用。Spring Cloud Gateway是现在推荐的方式。 (2认同)

Vel*_*ria 14

这是我原始答案的修改版本,它有四点不同:

  1. 它不会强制要求主体,​​因此不会让GET请求失败.
  2. 它复制原始请求中存在的所有标头.如果您使用其他代理/ Web服务器,这可能会导致内容长度/ gzip压缩导致的问题.将标题限制为您真正需要的标题.
  3. 它并没有重新编码的查询参数或路径.我们期望它们无论如何都要编码.请注意,您的网址的其他部分也可能会被编码.如果是这种情况,请充分利用UriComponentsBuilder.
  4. 它确实从服务器返回错误代码.

@RequestMapping("/**")
public ResponseEntity mirrorRest(@RequestBody(required = false) String body, 
    HttpMethod method, HttpServletRequest request, HttpServletResponse response) 
    throws URISyntaxException {
    String requestUrl = request.getRequestURI();

    URI uri = new URI("http", null, server, port, null, null, null);
    uri = UriComponentsBuilder.fromUri(uri)
                              .path(requestUrl)
                              .query(request.getQueryString())
                              .build(true).toUri();

    HttpHeaders headers = new HttpHeaders();
    Enumeration<String> headerNames = request.getHeaderNames();
    while (headerNames.hasMoreElements()) {
        String headerName = headerNames.nextElement();
        headers.set(headerName, request.getHeader(headerName));
    }

    HttpEntity<String> httpEntity = new HttpEntity<>(body, headers);
    RestTemplate restTemplate = new RestTemplate();
    try {
        return restTemplate.exchange(uri, method, httpEntity, String.class);
    } catch(HttpStatusCodeException e) {
        return ResponseEntity.status(e.getRawStatusCode())
                             .headers(e.getResponseHeaders())
                             .body(e.getResponseBodyAsString());
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @Veluria这不是我编写的最漂亮的代码,但在我们的案例中可以使用https://pastebin.com/LApUmpxx (2认同)

Jon*_*her 7

@derkoe 发布了一个很好的答案,对我帮助很大!

在 2021 年尝试这个,我能够稍微改进一下:

  1. 如果您的类是 @RestController,则不需要 @ResponseBody
  2. @RequestBody(required = false) 允许没有正文的请求(例如 GET)
  3. 这些 ssl 加密端点的 https 和端口 443(如果您的服务器在端口 443 上提供 https)
  4. 如果您返回整个responseEntity而不是仅返回正文,您还会获得标头和响应代码。
  5. 添加(可选)标头的示例,例如headers.put("Authorization", Arrays.asList(String[] { "Bearer 234asdf234"})
  6. 异常处理(捕获并转发 404 之类的 HttpStatuse,而不是抛出 500 服务器错误)

private String server = "localhost";
private int port = 443;

@Autowired
MultiValueMap<String, String> headers;

@Autowired
RestTemplate restTemplate;

@RequestMapping("/**")
public ResponseEntity<String> mirrorRest(@RequestBody(required = false) String body, HttpMethod method, HttpServletRequest request) throws URISyntaxException
{
    URI uri = new URI("https", null, server, port, request.getRequestURI(), request.getQueryString(), null);

    HttpEntity<String> entity = new HttpEntity<>(body, headers);    
    
    try {
        ResponseEntity<String> responseEntity =
            restTemplate.exchange(uri, method, entity, String.class);
            return responseEntity;
    } catch (HttpClientErrorException ex) {
        return ResponseEntity
            .status(ex.getStatusCode())
            .headers(ex.getResponseHeaders())
            .body(ex.getResponseBodyAsString());
    }

    return responseEntity;
}
Run Code Online (Sandbox Code Playgroud)


Chr*_* H. 3

如果您可以使用 mod_proxy 等较低级别的解决方案,那将是更简单的方法,但如果您需要更多控制(例如安全性、翻译、业务逻辑),您可能需要看看 Apache Camel:http ://camel.apache.org/how-to-use-camel-as-a-http-proxy- Between-a-client-and-server.html