programing

spring mvc rest 서비스 리다이렉트/전송/프록시

powerit 2023. 3. 29. 21:59
반응형

spring mvc rest 서비스 리다이렉트/전송/프록시

REST 서비스를 공개하기 위해 spring mvc framework를 사용하여 웹 어플리케이션을 만들었습니다.예를 들어 다음과 같습니다.

@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를 사용하려면 컨트롤러의 각 메서드를 다시 작성해야 합니다.또한 이로 인해 프록시 머신에서 용장 시리얼화/디시리얼화가 발생합니다.

re스템plate를 사용하여 범용 함수를 작성하려고 했지만 잘 되지 않았습니다.

@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로도 전송할 수 없었습니다.

이것을 실현하기 위한 보다 적절한 방법이 있을까요?

다음을 사용하여 모든 요청을 미러링/프록시할 수 있습니다.

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

이렇게 하면 헤더가 미러링되지 않습니다.

다음은 원본 답변의 수정 버전입니다. 4가지 점에서 다릅니다.

  1. 요구 본문을 필수로 하지 않기 때문에 GET 요구가 실패하는 일은 없습니다.
  2. 원래 요청에 있는 모든 헤더를 복사합니다.다른 프록시/웹 서버를 사용하는 경우 콘텐츠 길이/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());
    }
}

Netflix Zuul을 사용하여 스프링 응용 프로그램에 들어오는 요청을 다른 스프링 응용 프로그램으로 라우팅할 수 있습니다.

예를 들어 두 가지 어플리케이션이 있다고 합시다.1 . songs - app 、 2. api - gateway

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

@derkoe가 나에게 많은 도움을 준 멋진 답변을 올려주었어!

2021년에 이것을 시도하면서, 조금 개선했습니다.

  1. 클래스가 @RestController인 경우 @ResponseBody는 필요 없습니다.
  2. @RequestBody(필수 = false)는 본문 없이 요청을 허용합니다(예: GET).
  3. SSL 암호화 엔드포인트용 https 및 포트 443(서버가 포트 443에서https를 서비스하는 경우)
  4. 본문뿐만 아니라 responseEntity 전체를 반환하면 헤더와 응답 코드도 가져옵니다.
  5. 추가된(선택사항) 헤더 예.headers.put("Authorization", Arrays.asList(String[] { "Bearer 234asdf234"})
  6. 예외 처리(500 Server Error를 발생시키지 않고 404와 같은HttpStatus를 캐치하여 전송)

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

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


mod_downloads와 같은 하위 수준의 솔루션을 사용하는 것이 더 간단하지만, 더 많은 제어(보안, 번역, 비즈니스 로직 등)가 필요한 경우 Apache Camel을 참조하십시오.http://camel.apache.org/how-to-use-camel-as-a-http-proxy-between-a-client-and-server.html

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

이런 게 필요하잖아요jetty transparent proxy그러면 실제로 콜이 리다이렉트되어 필요에 따라 요청을 덮어쓸 수 있습니다.자세한 것은, http://reanimatter.com/2016/01/25/embedded-jetty-as-http-proxy/ 를 참조해 주세요.

언급URL : https://stackoverflow.com/questions/14726082/spring-mvc-rest-service-redirect-forward-proxy

반응형