春季 mvc 休息服务重定向/转发/代理

2022-08-31 13:26:48

我使用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);

}

现在我需要部署我的应用程序,但我有以下问题:客户端不能直接访问应用程序所在的计算机(有防火墙)。因此,我需要在代理机器上(客户端可访问)上建立一个重定向层,该层调用实际的 rest 服务。

我尝试使用 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);

}

这没关系,但我需要重写控制器中的每个方法才能使用 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(), ? , ?);

}

我找不到一种适用于请求和响应对象的 resttemplate 方法。

我还尝试了弹簧重定向和前进。但是重定向不会更改请求的客户端IP地址,因此我认为在这种情况下它是无用的。我也无法转发到另一个URL。

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


答案 1

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

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();
}

这不会镜像任何标头。


答案 2

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

  1. 它不会使请求正文成为必需的,因此不会让 GET 请求失败。
  2. 它复制原始请求中存在的所有标头。如果您使用的是其他代理/Web 服务器,则可能会由于内容长度/gzip 压缩而导致问题。将标头限制为真正需要的标头。
  3. 它不会重新编码查询参数或路径。我们希望它们无论如何都要被编码。请注意,URL 的其他部分也可能被编码。如果是这种情况,请充分利用 .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());
    }
}

推荐