java Spring MVC REST服务重定向/转发/代理

zzlelutf  于 2022-12-25  发布在  Java
关注(0)|答案(8)|浏览(289)

我已经使用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的方法,它可以处理请求和响应对象。
我也尝试了spring的重定向和转发,但是重定向并不改变请求的客户端IP地址,所以我认为在这种情况下它是无用的,我也不能转发到另一个URL。
有没有更适当的方法来达到这个目的?

1bqhqjot

1bqhqjot1#

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

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

这不会镜像任何标头。

0g0grzrc

0g0grzrc2#

以下是我对原始答案的修改版本,有四点不同:
1.它没有强制要求请求主体,因此不会让GET请求失败。
1.它会复制原始请求中的所有标头。如果您使用其他代理/Web服务器,这可能会导致内容长度/gzip压缩的问题。请将标头限制为您真正需要的标头。
1.它不会重新编码查询参数或路径。我们希望它们无论如何都被编码。请注意,URL的其他部分也可能被编码。如果您是这种情况,请充分利用UriComponentsBuilder的潜力。
1.它确实从服务器正确返回错误代码。

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

beq87vna3#

您可以使用Netflix Zuul将来自一个Spring应用程序的请求路由到另一个Spring应用程序。
假设您有两个应用程序:1.歌曲应用程序,2. API网关
在api-gateway应用程序中,首先添加zuul dependecy,然后您可以在application.yml中简单地定义路由规则,如下所示:
pom.xml

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    <version>LATEST</version>
</dependency>

application.yml

server:
  port: 8080
zuul:
  routes:
    foos:
      path: /api/songs/**
      url: http://localhost:8081/songs/

并且最后运行API网关应用,如:

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

现在,网关将把所有/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

8ehkhllq

8ehkhllq4#

@derkoe贴出了一个很棒的答案,对我帮助很大!
在2021年尝试了这个,我能够在这个基础上做一点改进:
1.如果类是@RestController,则不需要@ResponseBody
1.@RequestBody(required = false)允许不带正文的请求(例如GET)

  1. https和端口443用于那些ssl加密的端点(如果你的服务器在端口443上提供https)
    1.如果您返回整个responseEntity而不是仅返回主体,那么您还将获得头部和响应代码。
    1.添加(可选)标头的示例,例如headers.put("Authorization", Arrays.asList(String[] { "Bearer 234asdf234"})
    1.异常处理(捕获并转发HttpStatuses,如404,而不是抛出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;
}
qvtsj1bj

qvtsj1bj5#

具有oauth2的代理控制器

@RequestMapping("v9")
@RestController
@EnableConfigurationProperties
public class ProxyRestController {
    Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails;

    @Autowired
    private ClientCredentialsResourceDetails clientCredentialsResourceDetails;

    @Autowired
    OAuth2RestTemplate oAuth2RestTemplate;

    @Value("${gateway.url:http://gateway/}")
    String gatewayUrl;

    @RequestMapping(value = "/proxy/**")
    public String proxy(@RequestBody(required = false) String body, HttpMethod method, HttpServletRequest request, HttpServletResponse response,
                        @RequestHeader HttpHeaders headers) throws ServletException, IOException, URISyntaxException {

        body = body == null ? "" : body;
        String path = request.getRequestURI();
        String query = request.getQueryString();
        path = path.replaceAll(".*/v9/proxy", "");
        StringBuffer urlBuilder = new StringBuffer(gatewayUrl);
        if (path != null) {
            urlBuilder.append(path);
        }
        if (query != null) {
            urlBuilder.append('?');
            urlBuilder.append(query);
        }
        URI url = new URI(urlBuilder.toString());
        if (logger.isInfoEnabled()) {
            logger.info("url: {} ", url);
            logger.info("method: {} ", method);
            logger.info("body: {} ", body);
            logger.info("headers: {} ", headers);
        }
        ResponseEntity<String> responseEntity
                = oAuth2RestTemplate.exchange(url, method, new HttpEntity<String>(body, headers), String.class);
        return responseEntity.getBody();
    }

    @Bean
    @ConfigurationProperties("security.oauth2.client")
    @ConditionalOnMissingBean(ClientCredentialsResourceDetails.class)
    public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
        return new ClientCredentialsResourceDetails();
    }

    @Bean
    @ConditionalOnMissingBean
    public OAuth2RestTemplate oAuth2RestTemplate() {
        return new OAuth2RestTemplate(clientCredentialsResourceDetails);
    }
dwbf0jvd

dwbf0jvd6#

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

gpnt7bae

gpnt7bae7#

Veluria的解决方案给了我灵感,但我在从目标资源发送gzip压缩时遇到了问题。
目标是省略Accept-Encoding标头:

@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();
        if (!headerName.equals("Accept-Encoding")) {
            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());
    }
}
ovfsdjhp

ovfsdjhp8#

您需要类似jetty transparent proxy的代码,它实际上会重定向您的调用,并且如果需要,您可以覆盖请求。

相关问题