web-dev-qa-db-fra.com

spring mvc rest service redirect/forward/proxy

J'ai construit une application Web à l'aide de Spring MVC Framework pour publier les services REST . Par exemple:

@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);

}

Maintenant, je dois déployer mon application, mais le problème est le suivant: Par conséquent, j'ai besoin d'une couche de redirection sur une machine proxy (accessible par les clients) qui appelle le service de repos réel.

J'ai essayé de faire un nouvel appel en utilisant RestTemplate: Par exemple:

@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);

}

C'est ok, mais je dois réécrire chaque méthode du contrôleur pour utiliser le reste du modèle. De plus, cela entraîne une sérialisation/désérialisation redondante sur la machine proxy. 

J'ai essayé d'écrire une fonction générique en utilisant restemplate, mais cela n'a pas fonctionné:

@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(), ? , ?);

}

Je n'ai pas trouvé de méthode de resttemplate qui fonctionne avec les objets request et response.

J'ai aussi essayé de rediriger le printemps et de transmettre. Mais la redirection ne change pas l'adresse IP du client de la requête, je pense donc qu'elle est inutile dans ce cas. Je ne pouvais pas transférer à une autre URL non plus.

Existe-t-il un moyen plus approprié d’y parvenir? Merci d’avance.

39
nilgun

Vous pouvez mettre en miroir/proxy toutes les demandes avec ceci:

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

Cela ne reflétera aucun en-tête.

51
koe

Vous pouvez utiliser Netflix Zuul pour acheminer les demandes arrivant d'une application de printemps vers une autre application de printemps.

Disons que vous avez deux applications: 1.songs-app, 2.api-gateway

Dans l'application api-gateway, ajoutez d'abord la dépendance zuul, puis vous pouvez simplement définir votre règle de routage dans application.yml comme suit:

pom.xml

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

application.yml

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

et enfin, lancez l'application api-gateway comme:

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

Maintenant, la passerelle routera toutes les requêtes /api/songs/ vers http://localhost:8081/songs/

Voici un exemple pratique: https://github.com/muatik/spring-playground/tree/master/spring-api-gateway

Une autre ressource: http://www.baeldung.com/spring-rest-with-zuul-proxy

13
Muatik

Voici ma version modifiée de la réponse originale, qui diffère en quatre points:

  1. Cela ne rend pas le corps de la requête obligatoire et, en tant que tel, ne laisse pas les requêtes GET échouer.
  2. Il copie tous les en-têtes présents dans la requête d'origine. Si vous utilisez un autre serveur proxy/Web, des problèmes peuvent survenir en raison de la compression de la longueur du contenu/gzip. Limitez les en-têtes à ceux dont vous avez réellement besoin. 
  3. Not ne recode pas les paramètres de requête ou le chemin. Nous nous attendons à ce qu'ils soient encodés de toute façon. Notez que d'autres parties de votre URL peuvent également être codées. Si tel est le cas pour vous, exploitez pleinement le potentiel de UriComponentsBuilder.
  4. Cela renvoie correctement les codes d'erreur du serveur. 

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

Si vous pouvez vous en tirer en utilisant une solution de niveau inférieur, telle que mod_proxy, ce serait la solution la plus simple, mais si vous avez besoin de plus de contrôle (par exemple sécurité, traduction, logique métier), vous voudrez peut-être jeter un coup d'œil à Apache Camel: http://camel.Apache.org/how-to-use-camel-as-a-http-proxy-between-a-client-and-server.html

1
Chris H.

contrôleur proxy avec 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);
    }


0
love adu

Vous avez besoin de quelque chose comme jetty transparent proxy, qui redirigera réellement votre appel, et vous aurez la possibilité d'écraser la demande si vous en aviez besoin. Vous pouvez obtenir ses détails à http://reanimatter.com/2016/01/25/embedded-jetty-as-http-proxy/

0
krmanish007