MockRestServiceServer test sending a file upload through Multipart-formdata - java

I have a method that sends a rest request to an api with multipart-formdata, this will upload a file to the external api. However, I am not able to finish the unit test method for this.
The first problem I am finding is that the content-type that I am expecting is always different than the one that method create. For some reason when sending the request the mediatype is multipart-formdata but the header is set as that in addition to charset and boundary. The latter, boundary, is always changing its value therefore I can not set the expected value on the unit tests because it will always be different.
Apart from that, how do I also expect that the content of the request is the same content that I initiated the test with? How do I assert that the payload is the same.
Please check the code:
Service class:
#Service
#Slf4j
public class JiraService {
private HttpHeaders createRequestHeaders(JiraClient jiraClient, MediaType contenType) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(contenType);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setBasicAuth(jiraClient.getUsername(), jiraClient.getPassword());
return headers;
}
private <EC, RC> ResponseEntity<RC> createRequestAndSend(HttpMethod method, String url, HttpHeaders headers,
EC payload, Class<RC> responseType) {
HttpEntity<EC> requestEntity = new HttpEntity<>(payload, headers);
ResponseEntity<RC> responseEntity = restTemplate.exchange(url, method, requestEntity, responseType);
// TODO deal with response
log.error("Loggin something");
return responseEntity;
}
public void addAttachment(JiraClient jiraClient, JiraIssue jiraIssue, JiraAttachment jiraAttachment)
throws MalformedURLException, IOException {
String url = jiraClient.getHost() + "/rest/api/2/issue/" + jiraIssue.getKey() + "/attachments";
HttpHeaders headers = createRequestHeaders(jiraClient, MediaType.MULTIPART_FORM_DATA); // What to do here?
headers.set("X-Atlassian-Token", "no-check");
FileSystemResource file = jiraAttachment.downloadFileFromWeb();
MultiValueMap<String, Object> payload = new LinkedMultiValueMap<>();
payload.add("file", file);
createRequestAndSend(HttpMethod.POST, url, headers, payload, String.class);
jiraAttachment.deleteFileFromSystem();
}
}
ServiceTest.class
#ActiveProfiles("test")
#RestClientTest(JiraService.class)
public class JiraServiceTest {
#Value("classpath:jira/add_attachment/validJiraAttachmentAddition.json")
private Resource validJiraAttachmentAddition;
#Autowired
private MockRestServiceServer server;
#Autowired
private JiraService jiraService;
#Mock
private JiraAttachment mockJiraAttachment;
private FileSystemResource attachmentFileSystemResource;
#BeforeEach
public void setupTests() throws IOException {
// initialize mocks
}
#Test
public void addAttachment_WithValidData_ShouldAddAttachmentToJiraIssue() throws Exception {
String url = host + "/rest/api/2/issue/" + issueKey + "/attachments";
ResponseActions stub = createServiceStub(HttpMethod.POST, url, MediaType.MULTIPART_FORM_DATA_VALUE);
stub = stub.andExpect(header("X-Atlassian-Token", "no-check"));
stub.andRespond(withSuccess());
// How to assert that the content of the request is the same as the resource?
when(mockJiraAttachment.downloadFileFromWeb()).thenReturn(attachmentFileSystemResource);
jiraService.addAttachment(mockJiraClient, mockJiraIssue, mockJiraAttachment);
}
private ResponseActions createServiceStub(HttpMethod method, String url, String contenType) {
String encodedCredentials = Base64.getEncoder().encodeToString((username + ":" + password).getBytes());
ResponseActions stub = server.expect(ExpectedCount.once(), requestTo(url));
stub = stub.andExpect(method(method));
stub = stub.andExpect(header("Content-Type", contenType)); // How to expect the content type here ?
stub = stub.andExpect(header("Authorization", "Basic " + encodedCredentials));
return stub;
}
}

Use ContentRequestMatchers.contentTypeCompatibleWith(MediaType contentType)
import static org.springframework.test.web.client.match.MockRestRequestMatchers.content;
...
stub.andExpect(content().contentTypeCompatibleWith(MediaType.MULTIPART_FORM_DATA))

Related

How to test a spring postmapping? (Junit)

Im trying to test a sample post where you have a endpoint called /message which is a path parameter on localhost. The message is a var so if I post /samplemsg as an example it should print that. However Im not quite sure how to test the post. Here's what I've managed to do so far
#RestController
#Component
#RequestMapping(path = "/message")
public class HomeController {
#PostMapping(path="/message")
public ResponseEntity<Object> postName(#PathParam ("message") #RequestBody String message) {
URI location = URI.create(ServletUriComponentsBuilder.fromCurrentRequest().buildAndExpand(message + " was created").toUriString());
return ResponseEntity.created(location).build();
}
Test Class
#RunWith(SpringRunner.class)
#SpringBootTest(classes= RunApplication.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class RunApplicationTests {
#Autowired
private TestRestTemplate restTemplate;
#Test
void sendPost() throws URISyntaxException {
final String baseUrl = "http://localhost:8080/message";
URI uri = new URI(baseUrl);
String message = "sample msg";
HttpHeaders headers = new HttpHeaders();
headers.set("X-COM-PERSIST", "true");
HttpEntity<String> request = new HttpEntity<>(message, headers);
ResponseEntity<String> result = this.restTemplate.postForEntity(uri, request, String.class);
Assert.assertEquals("sample msg was created", result.getBody().toString());
}
}
Error Message:
org.junit.ComparisonFailure: expected:<[sample msg was created]> but was:<[{"timestamp":"2022-02-14T21:54:29.152+00:00","status":404,"error":"Not Found","path":"/message/"}]>
Expected :sample msg was created
Actual :{"timestamp":"2022-02-14T21:54:29.152+00:00","status":404,"error":"Not Found","path":"/message/"}

Call rest void method in controller using Spring Boot

i'm using Spring Boot for making Rest Controllers.
my controller is :
#RestController
public class VersionRestController {
#Autowired
VersionService versionService;
#GetMapping(value = "/csv", produces = "text/csv")
#ResponseStatus(value = HttpStatus.OK)
public void exportCsv(HttpServletResponse response) throws Exception {
String fileName = "allVersions.csv";
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"");
StatefulBeanToCsv<Version> writer = new StatefulBeanToCsvBuilder<Version>(response.getWriter())
.withQuotechar(CSVWriter.NO_QUOTE_CHARACTER).withSeparator(CSVWriter.DEFAULT_SEPARATOR)
.withOrderedResults(true).build();
writer.write(versionService.findAll());
}
}
im calling it like this
public void exportVersionAsCsv(){
final String uri = "http://localhost:8070/csv";
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<Version> response = restTemplate.getForEntity(uri, Version.class);
}
when i call the rest method direct in the browser it works fine, but when i call exportVersionAsCsv() in vaadin on click button it gives me org.springframework.web.client.HttpClientErrorException$NotAcceptable: 406 Not Acceptable
why is this happening ? any suggestion ?
thank you
Try this set setAccept to Media type to that is being produced by url you are consuming.
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
HttpEntity<String> entity = new HttpEntity<>("body", headers);
restTemplate.exchange(url, HttpMethod.POST, entity, String.class);

How can I pass headers using RestTemplate?

In my method I initially used RestTemplate postForObject method to post request to an endpoint. Now I have to add default OAuth token and pass it as Post request. Is there any way I can pass both request as well as Default Header as part of POST request by using postForObject?
Initiall I used below postForObject
String result = restTemplate.postForObject(url, request, String.class);
I am looking for something like below
restTemplate.exchange(url,HttpMethod.POST,getEntity(),String.class );
Here is my code
private final String url;
private final MarkBuild header;
public DataImpl(#Qualifier(OAuth) MarkBuild header,RestTemplate restTemplate) {
this.restTemplate= restTemplate;
this.header = header;
}
public void postJson(Set<String> results){
try {
Map<String, String> requestBody = new HashMap<>();
requestBody.put("news", "data");
JSONObject jsonObject = new JSONObject(requestBody);
HttpEntity<String> request = new HttpEntity<String>(jsonObject.toString(), null);
String result = restTemplate.postForObject(url, request, String.class);
}
}
Below is getHttpEntity which I want to pass with Post request
private HttpEntity getHttpEntity(Set <String>results) {
return new HttpEntity<>( null, getHttpHeaders() );
}
private HttpHeaders getHttpHeaders() {
return header.build();
}
}
Is there any way I can pass both request as well as Default Header as
part of POST request by using postForObject?
Yes, there is a way to do that, I can give a basic example:
HttpHeaders lHttpHeaders = new HttpHeaders();
lHttpHeaders.setContentType( MediaType.APPLICATION_JSON );//or whatever it's in your case
String payload="<PAYLOAD HERE>"
try
{
String lResponseJson = mRestTemplate.postForObject( url, new HttpEntity<Object>( payload, lHttpHeaders ), String.class);
return lResponseJson;
}
catch( Exception lExcp )
{
logger.error( lExcp.getMessage(), lExcp );
}
Let me know if this doesn't work!!

Spring boot ClientHttpRequestInterceptor resend on 401

So i have below scenario to implement using Spring boot rest template to consume a REST-API (involves token authentication mechanism). To perform test i've created simple mock REST API in spring boot. Here's the process,
From my API consumer app,
sends a request using rest-template to consume a protected API, this API requires Authorization: Bearer <token> header to be present in request.
if something is wrong with this token (missing header, invalid token), protected API returns HTTP-Unauthorized (401).
when this happens, consumer API should send another request to another protected API that returns a valid access token, this protected API requires Authorization: Basic <token> header to be present. New access token will be stored in a static field and it will be used in all other requests to authenticate.
This can be achieved by simply catching 401-HttpClientErrorException in RestTemplate consumer methods (postForObject), but the idea was to decouple it from REST-API consumer classes. To achieve it, i tried to use ClientHttpRequestInterceptor
Here's the code, that i tried so far.
Interceptor class
public class AuthRequestInterceptor implements ClientHttpRequestInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthRequestInterceptor.class);
private static final String BASIC_AUTH_HEADER_PREFIX = "Basic ";
private static final String BEARER_AUTH_HEADER_PREFIX = "Bearer ";
//stores access token
private static String accessToken = null;
#Value("${app.mife.apiKey}")
private String apiKey;
#Autowired
private GenericResourceIntegration resourceIntegration; // contains methods of rest template
#Override
public ClientHttpResponse intercept(
HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution
) throws IOException {
LOGGER.info("ReqOn|URI:[{}]{}, Headers|{}, Body|{}", request.getMethod(), request.getURI(), request.getHeaders(), new String(body));
request.getHeaders().add(ACCEPT, APPLICATION_JSON_VALUE);
request.getHeaders().add(CONTENT_TYPE, APPLICATION_JSON_VALUE);
try {
//URI is a token generate URI, request
if (isBasicUri(request)) {
request.getHeaders().remove(AUTHORIZATION);
//sets BASIC auth header
request.getHeaders().add(AUTHORIZATION, (BASIC_AUTH_HEADER_PREFIX + apiKey));
ClientHttpResponse res = execution.execute(request, body);
LOGGER.info("ClientResponse:[{}], status|{}", "BASIC", res.getStatusCode());
return res;
}
//BEARER URI, protected API access
ClientHttpResponse response = null;
request.getHeaders().add(AUTHORIZATION, BEARER_AUTH_HEADER_PREFIX + getAccessToken());
response = execution.execute(request, body);
LOGGER.info("ClientResponse:[{}], status|{}", "BEARER", response.getStatusCode());
if (unauthorized(response)) {
LOGGER.info("GetToken Res|{}", response.getStatusCode());
String newAccessToken = generateNewAccessCode();
request.getHeaders().remove(AUTHORIZATION);
request.getHeaders().add(AUTHORIZATION, (BEARER_AUTH_HEADER_PREFIX + newAccessToken));
LOGGER.info("NewToken|{}", newAccessToken);
return execution.execute(request, body);
}
if (isClientError(response) || isServerError(response)) {
LOGGER.error("Error[Client]|statusCode|{}, body|{}", response.getStatusCode(), CommonUtills.streamToString(response.getBody()));
throw new AccessException(response.getStatusText(),
ServiceMessage.error().code(90).payload(response.getRawStatusCode() + ":" + response.getStatusText()).build());
}
return response;
} catch (IOException exception) {
LOGGER.error("AccessError", exception);
throw new AccessException("Internal service call error",
ServiceMessage.error().code(90).payload("Internal service call error", exception.getMessage()).build()
);
} finally {
LOGGER.info("ReqCompletedOn|{}", request.getURI());
}
}
private String generateNewAccessCode() {
Optional<String> accessToken = resourceIntegration.getAccessToken();
setAccessToken(accessToken.get());
return getAccessToken();
}
private static void setAccessToken(String token) {
accessToken = token;
}
private static String getAccessToken() {
return accessToken;
}
private boolean isClientError(ClientHttpResponse response) throws IOException {
return (response.getRawStatusCode() / 100 == 4);
}
private boolean isServerError(ClientHttpResponse response) throws IOException {
return (response.getRawStatusCode() / 100 == 5);
}
private boolean unauthorized(ClientHttpResponse response) throws IOException {
return (response.getStatusCode().value() == HttpStatus.UNAUTHORIZED.value());
}
private boolean isBasicUri(HttpRequest request) {
return Objects.equals(request.getURI().getRawPath(), "/apicall/token");
}
private boolean isMifeRequest(HttpRequest request) {
return request.getURI().toString().startsWith("https://api.examplexx.com/");
}
}
Token generate method- In resourceIntegration
public Optional<String> getAccessToken() {
ResponseEntity<AccessTokenResponse> res = getRestTemplate().exchange(
getAccessTokenGenUrl(),
HttpMethod.POST,
null,
AccessTokenResponse.class
);
if (res.hasBody()) {
LOGGER.info(res.getBody().toString());
return Optional.of(res.getBody().getAccess_token());
} else {
return Optional.empty();
}
}
Another sample protected API call method
public Optional<String> getMobileNumberState(String msisdn) {
try {
String jsonString = getRestTemplate().getForObject(
getQueryMobileSimImeiDetailsUrl(),
String.class,
msisdn
);
ObjectNode node = new ObjectMapper().readValue(jsonString, ObjectNode.class);
if (node.has("PRE_POST")) {
return Optional.of(node.get("PRE_POST").asText());
}
LOGGER.debug(jsonString);
} catch (IOException ex) {
java.util.logging.Logger.getLogger(RestApiConsumerService.class.getName()).log(Level.SEVERE, null, ex);
}
return Optional.empty();
}
Problem
Here's the log of mock API,
//first time no Bearer token, this returns 401 for API /simulate/unauthorized
accept:text/plain, application/json, application/*+json, */*
authorization:Bearer null
/simulate/unauthorized
//then it sends Basic request to get a token, this is the log
accept:application/json, application/*+json
authorization:Basic M3ZLYmZQbE1ERGhJZWRHVFNiTEd2Vlh3RThnYTp4NjJIa0QzakZUcmFkRkVOSEhpWHNkTFhsZllh
Generated Token:: 57f21374-1188-4c59-b5a7-370eac0a0aed
/apicall/token
//finally consumer API sends the previous request to access protected API and it contains newly generated token in bearer header
accept:text/plain, application/json, application/*+json, */*
authorization:Bearer 57f21374-1188-4c59-b5a7-370eac0a0aed
/simulate/unauthorized
The problem is even-though mock API log had the correct flow, consumer API does not get any response for third call, here's the log of it (unnecessary logs are omitted).
RequestInterceptor.intercept() - ReqOn|URI:[GET]http://localhost:8080/simulate/unauthorized?x=GlobGlob, Headers|{Accept=[text/plain, application/json, application/*+json, */*], Content-Length=[0]}, Body|
RequestInterceptor.intercept() - ClientResponse:[BEARER], status|401 UNAUTHORIZED
RequestInterceptor.intercept() - GetToken Res|401 UNAUTHORIZED
RequestInterceptor.intercept() - ReqOn|URI:[POST]http://localhost:8080/apicall/token?grant_type=client_credentials, Headers|{Accept=[application/json, application/*+json], Content-Length=[0]}, Body|
RequestInterceptor.intercept() - ClientResponse:[BASIC], status|200 OK
RequestInterceptor.intercept() - ReqCompletedOn|http://localhost:8080/apicall/token?grant_type=client_credentials
RestApiConsumerService.getAccessToken() - |access_token2163b0d4-8d00-4eba-92d0-7e0bb609b982,scopeam_application_scope default,token_typeBearer,expires_in34234|
RequestInterceptor.intercept() - NewToken|2163b0d4-8d00-4eba-92d0-7e0bb609b982
RequestInterceptor.intercept() - ReqCompletedOn|http://localhost:8080/simulate/unauthorized?x=GlobGlob
http://localhost:8080/simulate/unauthorized third time does not return any response, but mock API log says it hit the request. What did i do wrong ?, is it possible to achieve this task using this techniques ? or is there any other alternative way to do this ? any help is highly appreciated.
I have tried this:
Add an interceptor ClientHttpRequestInterceptor
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.StringUtils;
public class RequestResponseHandlerInterceptor implements ClientHttpRequestInterceptor {
#Autowired
private TokenService tokenService;
#Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String AUTHORIZATION = "Authorization";
/**
* This method will intercept every request and response and based on response status code if its 401 then will retry
* once
*/
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
ClientHttpResponse response = execution.execute(request, body);
if (HttpStatus.UNAUTHORIZED == response.getStatusCode()) {
String accessToken = tokenService.getAccessToken();
if (!StringUtils.isEmpty(accessToken)) {
request.getHeaders().remove(AUTHORIZATION);
request.getHeaders().add(AUTHORIZATION, accessToken);
//retry
response = execution.execute(request, body);
}
}
return response;
}
}
Apart from this you need to override RestTemplate initialization as well.
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList(new RequestResponseHandlerInterceptor()));
return restTemplate;
}

spring mvc rest service redirect / forward / proxy

I have build a web application using spring mvc framework to publish REST services.
For example:
#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);
}
Now I need to deploy my application but I have the following problem:
The clients do not have direct access to the computer on which the application resides (There is a firewall). Therefore I need a redirection layer on a proxy machine (accessible by the clients) which calls the actual rest service.
I tried making a new call using RestTemplate:
For Example:
#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);
}
This is ok but I need to rewrite each method in the controller to use the resttemplate. Also, this causes redundant serialization/deserialization on the proxy machine.
I tried writing a generic function using restemplate, but it did not work out:
#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(), ? , ?);
}
I could not find a method of resttemplate which works with request and response objects.
I also tried spring redirect and forward. But redirect does not change the request's client ip address so i think it is useless in this case. I could not forward to another URL either.
Is there a more appropriate way to achieve this?
You can mirror/proxy all requests with this:
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();
}
This will not mirror any headers.
Here's my modified version of the original answer, which differs in four points:
It does not make the request body mandatory, and as such does not let GET requests fail.
It copies all headers present in the original request. If you are using another proxy/web server, this can cause issues due to content length/gzip compression. Limit the headers to the ones you really need.
It does not reencode the query params or the path. We expect them to be encoded anyway. Note that other parts of your URL might also be encoded. If that is the case for you, leverage the full potential of UriComponentsBuilder.
It does return error codes from the server properly.
#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());
}
}
You can use Netflix Zuul to route requests coming to a spring application to another spring application.
Let's say you have two application: 1.songs-app, 2.api-gateway
In the api-gateway application, first add the zuul dependecy, then you can simply define your routing rule in application.yml as follows:
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/
and lastly run the api-gateway application like:
#EnableZuulProxy
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Now, the gateway will route all the /api/songs/ requests to http://localhost:8081/songs/.
A working example is here: https://github.com/muatik/spring-playground/tree/master/spring-api-gateway
Another resource: http://www.baeldung.com/spring-rest-with-zuul-proxy
#derkoe has posted a great answer that helped me a lot!
Trying this in 2021, I was able to improve on it a little:
You don't need #ResponseBody if your class is a #RestController
#RequestBody(required = false) allows for requests without a body (e.g. GET)
https and port 443 for those ssl encrypted endpoints (if your server serves https on port 443)
If you return the entire responseEntity instead of only the body, you also get the headers and response code.
Example of added (optional) headers, e.g. headers.put("Authorization", Arrays.asList(String[] { "Bearer 234asdf234"})
Exception handling (catches and forwards HttpStatuses like 404 instead of throwing a 500 Server Error)
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;
}
proxy controller with 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);
}
If you can get away with using a lower-level solution like mod_proxy that would be the simpler way to go, but if you need more control (e.g. security, translation, business logic) you may want to take a look at Apache Camel: http://camel.apache.org/how-to-use-camel-as-a-http-proxy-between-a-client-and-server.html
I got inspired by Veluria's solution, but I had issues with gzip compression sent from the target resource.
The goal was to omit Accept-Encoding header:
#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());
}
}
You need something like jetty transparent proxy, which actually will redirect your call, and you get a chance to overwrite the request if you needed. You may get its detail at http://reanimatter.com/2016/01/25/embedded-jetty-as-http-proxy/

Categories