How to send HTTP OPTIONS request with body using Spring rest template? - spring

I am trying to call a RESTfull web service resource, this resource is provided by a third party, the resource is exposed with OPTIONS http verb.
To integrate with the service, I should send a request with a specific body, which identities by a provider, but when I did that I got a bad request. After that I trace my code then I recognized that the body of the request is ignored by rest template based on the below code:
if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) ||
"PATCH".equals(httpMethod) || "DELETE".equals(httpMethod)) {
connection.setDoOutput(true);
}
else {
connection.setDoOutput(false);
}
my question, is there a standard way to override this behavior or I should use another tool?

The code you've pasted is from
SimpleClientHttpRequestFactory.prepareConnection(HttpURLConnection connection, String httpMethod)
I know because I've debugged that code few hours ago.
I had to do a HTTP GET with body using restTemplate. So I've extend SimpleClientHttpRequestFactory, override prepareConnection and create a new RestTemplate using the new factory.
public class SimpleClientHttpRequestWithGetBodyFactory extends SimpleClientHttpRequestFactory {
#Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
super.prepareConnection(connection, httpMethod);
if ("GET".equals(httpMethod)) {
connection.setDoOutput(true);
}
}
}
Create a new RestTemplate based on this factory
new RestTemplate(new SimpleClientHttpRequestWithGetBodyFactory());
A test to prove the solution is working using spring boot (#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT))
public class TestRestTemplateTests extends AbstractIntegrationTests {
#Test
public void testMethod() {
RestTemplate restTemplate = new RestTemplate(new SimpleClientHttpRequestWithBodyForGetFactory());
HttpEntity<String> requestEntity = new HttpEntity<>("expected body");
ResponseEntity<String> responseEntity = restTemplate.exchange("http://localhost:18181/test", HttpMethod.GET, requestEntity, String.class);
assertThat(responseEntity.getBody()).isEqualTo(requestEntity.getBody());
}
#Controller("/test")
static class TestController {
#RequestMapping
public #ResponseBody String testMethod(HttpServletRequest request) throws IOException {
return request.getReader().readLine();
}
}
}

Related

How to mock external rest services when writing integration test in spring boot

I have a controller from which gateway(Spring integration) is being called. Inside gateway I have several flows where I'm doing some outboundgateway calls. I've written my integration test as below -
#Tag("integrationtest")
#ExtendWith(SpringExtension.class)
#SpringBootTest(
classes = MyWebApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTest {
#LocalServerPort private int port;
TestRestTemplate testRestTemplate = new TestRestTemplate();
HttpHeaders headers = new HttpHeaders();
#Test
void testEntireApplication() {
HttpEntity<LoanProvisionRequest> entity =
new HttpEntity(TestHelper.generateValidLionRequest(), headers);
ResponseEntity<LoanProvisionResponse> response =
testRestTemplate.exchange(
createURLWithPort("/provision"), HttpMethod.POST, entity, LionResponse.class);
assertEquals(1, response.getBody().getASMCreditScoreResultCd());
}
private String createURLWithPort(String uri) {
return "http://localhost:" + port + "/lion-service/v1/decisions" + uri;
}
}
It's running the application and proceeding through from controller to the gateway and running the flows as expected. But for the outboundgateway calls it's failing by saying Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://someurl" because it's not able to access the url that's used in the outboundgateway. I want to stub/mock those url somehow. How do I do that?
I tried doing something below in the same class to mock the url -
MockRestServiceServer mockServer;
#BeforeEach
void setUp() throws JsonProcessingException {
RestTemplate restTemplate = new RestTemplate();
mockServer = MockRestServiceServer.bindTo(restTemplate).build();
DecisionResponse decisionResponse = new DecisionResponse();
creditDecisionResponse.setId("0013478");
creditDecisionResponse.setResponse(null);
creditDecisionResponse.setDescription("dummy Response");
mockServer
.expect(
requestTo(
"http://xyz-some-url:8080/some-other-service/v1/do-decisions/decision"))
.andExpect(method(HttpMethod.POST))
.andRespond(
withStatus(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(new ObjectMapper().writeValueAsString(decisionResponse )));
mockServer.verify();
}
But still the same error showing and somehow it's not getting called when it's hitting the outboundgateway call inside the gateway flows.
below is the controller code -
public ResponseEntity<LionResponse> getLionsNames(
#RequestBody final #Valid LionRequest req,
BindingResult bindingResult,
#RequestHeader HttpHeaders httpHeaders)
throws JsonProcessingException {
Long dbId = new SequenceGenerator().nextId();
lionsGateway.processLionRequest(
MessageBuilder.withPayload(req).build(),
dbId,
SourceSystem.ONE.getSourceSystemCode()));
below is the gateway -
#MessagingGateway
public interface LoansGateway {
#Gateway(requestChannel = "flow.input")
List<Object> processLoanRequest(
#Payload Message lionRequest,
#Header("dbID") Long dbID,
#Header("sourceSystemCode") String sourceSystemCode);
}
below is the SpringIntegrationConfiguration class -
#Bean
public IntegrationFlow flow() {
return flow ->
flow.handle(validatorService, "validateRequest")
.split()
.channel(c -> c.executor(Executors.newCachedThreadPool()))
.scatterGather(
scatterer ->
scatterer
.applySequence(true)
.recipientFlow(savingLionRequestToTheDB())
.recipientFlow(callingANativeMethod())
.recipientFlow(callingAExternalService()),
gatherer -> gatherer.outputProcessor(prepareCDRequest()))
.gateway(getDecision(), f -> f.errorChannel("lionDecisionErrorChannel"))
.to(getDataResp());
}
public IntegrationFlow callingAExternalService() {
return flow ->
flow.handle(
Http.outboundGateway(externalServiceURL)
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class))
.logAndReply("Cd response");
}
.... same way I have other flows that are using outboundgateway but I've not wired the Restemplate instance anywhere.
So, you do in your mock server setup:
RestTemplate restTemplate = new RestTemplate();
mockServer = MockRestServiceServer.bindTo(restTemplate).build();
And that's it. The mocked RestTemplate instance is not used anywhere.
The HttpRequestExecutingMessageHandler has a configuration based on the RestTemplate:
/**
* Create a handler that will send requests to the provided URI using a provided RestTemplate.
* #param uri The URI.
* #param restTemplate The rest template.
*/
public HttpRequestExecutingMessageHandler(String uri, RestTemplate restTemplate) {
So, you just need to instrument exactly that RestTemplate which you provide for your HTTP outbound gateway.
Right now your mocking code is dead end.

How to get the updated/modified HttpServletRequest object from AOP #Before advice to Spring controller method

I used Spring AOP #Before advice in Spring boot application, and it should execute before hitting any api's.
My task/requirement :- If in the request header application-name is not passed then we should modify the header and add to 'unknown' value to the application-name for every API.
I am modifying the header in the AOP #before advice using HttpServletWrapper class as shown below.
Problem is - the AOP should return updated HttpServletrequest to a controller method but it's not working, not returning the updated one in controller.
Controller:-
#GetMapping
#RequestMapping("/demo")
public ResponseEntity<String> getEmployee(HttpServletRequest request) {
System.out.println("Header, application-name"+request.getHeader("application-name"));
return new ResponseEntity<>("Success", HttpStatus.OK);
}
Spring AOP code,
#Aspect
#Component
public class AOPExample {
#Pointcut("#annotation(org.springframework.web.bind.annotation.GetMapping) ||"
+ "#annotation(org.springframework.web.bind.annotation.PostMapping)")
public void controllerRequestMapping() {}
#Before("controllerRequestMapping()")
public HttpServletRequest advice(JoinPoint jp) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
String header = request.getHeader("application-name");
if (header == null) {
HttpServletRequestWrapperClass wrappedRequest = new HttpServletRequestWrapperClass(request);
wrappedRequest.putHeader("application-name", "Unknown");
request = wrappedRequest;
} else {
//validate application name
//400 - throw bad request exception
}
System.out.println("After setting---"+request.getHeader("application-name"));
return request;
}
}
Finally I resolved the issue,
Instead of using #Before advice used #Around advice, Around advice should return the object using proceed method.
#Aspect
#Component
public class AOPExample {
#Pointcut("#annotation(org.springframework.web.bind.annotation.GetMapping) ||"
+ "#annotation(org.springframework.web.bind.annotation.PostMapping)")
public void controllerRequestMapping() {}
#Around("controllerRequestMapping()")
public Object advice(ProceedingJoinPoint jp) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
String header = request.getHeader("application-name");
System.out.println("Header in AOP"+header);
if (header == null) {
HttpServletRequestWrapperClass wrappedRequest = new HttpServletRequestWrapperClass(request);
wrappedRequest.putHeader("application-name", "Unknown");
request = wrappedRequest;
} else {
//validate application name
//400 - throw bad request exception
//throw new BadRequestException("Invalid application name");
}
System.out.println("After setting---"+request.getHeader("application-name"));
return jp.proceed(new Object[] {request});
}
}
Updated httpservlet request is getting in controller method. Thanks

adding request param to every request using spring resttemplate

I am trying to add common request parameters to every request using RestTemplate.
For example if my url is http://something.com/countries/US then I want to add common request param ?id=12345. This common request parameter needs to be added on all request. I don't want to add this on each call and want something common.
this post has answer that was marked correct, but I am not sure how you can add request parameters on org.springframework.http.HttpRequest
Any other way I can achieve this ?
To add request parameters to the HttpRequest , you can first use UriComponentsBuilder to build an new URI based on the existing URI but adding the query parameters that you want to add.
Then use HttpRequestWrapper to wrap the existing request but only override its URI with the updated URI.
Code wise it looks like:
public class AddQueryParamterInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
URI uri = UriComponentsBuilder.fromHttpRequest(request)
.queryParam("id", 12345)
.build().toUri();
HttpRequest modifiedRequest = new HttpRequestWrapper(request) {
#Override
public URI getURI() {
return uri;
}
};
return execution.execute(modifiedRequest, body);
}
}
And set this interceptor to the RestTemplate:
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(new AddQueryParamterInterceptor());
restTemplate.setInterceptors(interceptors);
Two things are required to add common request parameters to every request using RestTemplate.
Create a prototype bean RestTemplate
#Configuration
public class RestTemplateConfig {
#Bean
#Scope(
value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public RestTemplate restTemplate() {
RestTemplate localRestTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors = localRestTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new AddQueryParamterInterceptor());
localRestTemplate.setInterceptors(interceptors);
return localRestTemplate;
}
}
Use the Interceptor code as suggested by #ken-chan to add the request parameters. A new instance of Resttemaple will be created and for each and every request.
You can achieve this by adding interceptor to rest template

Getting the request URL of a SOAP request in handleResponse Method in ClientInterceptor

I am using spring boot. I have written a ClientInterceptor for a SOAP web service and i would like to log the URL that i am sending my request to, and i would like to do it in the handleResponse method. However i could not find a way to do it. Is it possible? Any help would be great.
public class SoapClientHttpRequestInterceptor implements ClientInterceptor {
#Override
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
// I would like to get the URL and log it here.
}
}
The way i create the restTemplate
#Bean
#Qualifier("testRestTemplate")
public RestTemplate testRestTemplate() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setOutputStreaming(false);
ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(requestFactory);
RestTemplate restTemplate = new RestTemplate(factory);
restTemplate.setInterceptors(Collections.singletonList(restClientHttpRequestInterceptor));
restTemplate.setErrorHandler(testErrorHandler);
restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
return restTemplate;
}
You can do this in your handleResponse method:
TransportContext context = TransportContextHolder.getTransportContext();
context.getConnection().getUri().toString()
i hope this helps you

415 error while ajax post in spring rest

after solving this Do we have to have to post json object with exactly same fields as in pojo object in controller? im getting 415 error while posting from AJAX ,I m using spring rest . And yes i have seen other similar questions but non of them solved my problem
controller:
#RequestMapping(value = "/createTest", method = RequestMethod.POST )
public #ResponseBody String createTest(#RequestBody TestJsonDTO testJson)
throws JsonProcessingException, IOException {
TestSet test = new TestSet();
//................
AJAX:
function createTest() {
$.ajax({
type : 'POST',
url : "http://localhost:8085/annotationBased/admin/createTest",
dataType : "json",
accept:"application/json",
contentType : "application/json",
data : testToJSON(),
success : function() {
alert("success")
},
complete : function(){
findAllTEst()
alert("OK")
},
});
function testToJSON() {
listOfQuestionForTest = questionToAdd;
return JSON.stringify({
"testSet" : {name : $('#testname').val(),
fullmark : parseInt($('#fullmark').val()),
passmark : parseInt($('#passmark').val())},
"questionsInTest" : listOfQuestionForTest
// "testDate":$('#testDate').value()
})
}
and i have added those class u suggested.
You're getting a 415 status code because the server is sending html in the response, while your client expects json.
This might indicate that a server-side exception occured. In such a case, application servers send back a html response.
You have to either make the server respond with json, even if an exception has occured, or let the client handle not only json responses, but also html ones.
I recommend you take the first approach:
#ControllerAdvice
public class ExceptionControllerAdvice {
#ExceptionHandler(Exception.class)
public ErrorResponse handleException(Exception ex) {
ErrorResponse err = new ErrorResponse();
err.setStatusCode(/* 4XX or 500, depending on exception type */);
err.setERrorMessage(ex.getMessage());
return err;
}
}
public class ErrorResponse {
private int statusCode;
private String errorMessage;
// getters and setters or make the fields public
}
A #ControllerAdvice is like a Spring controller, except that it works for every request. #ExceptionHandler tells Spring to intercept exceptions of the specified type and run the code within the annotated method.
Depending on the type of the exception, you should set the right status code in the ErrorResponse object you'll be returning. This is a very basic example, you can also extend from default Spring exception resolvers and overwrite the default behavior. Please refer to this article for further details.
EDIT:
Another thing you could try is to force response's Content-Type to be always application/json, no matter the http stastus returned. You can do this by adding an interceptor in the class where you configure message converters and JSON serialization/deserialization properties:
#Configuration
public class ServiceContext
extends WebMvcConfigurationSupport {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = this.getMappingJackson2HttpMessageConverter();
converters.add(converter);
}
#Bean
public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = this.getObjectMapper();
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
return mappingJackson2HttpMessageConverter;
}
#Bean
public ObjectMapper getObjectMapper() {
JsonFactory jsonFactory = new JsonFactory();
ObjectMapper objectMapper = new ObjectMapper(jsonFactory);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); // this is what you need
objectMapper.setSerializationInclusion(Include.NON_NULL); // this is to not serialize unset properties
return objectMapper;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
ResponseHeadersInterceptor headersInterceptor = this.getResponseHeadersInterceptor();
registry.addInterceptor(headersInterceptor).addPathPatterns("/**");
}
#Bean
public ResponseHeadersInterceptor getResponseHeadersInterceptor() {
return new ResponseHeadersInterceptor();
}
}
With ResponseHeadersInterceptor being as follows:
public class ResponseHeadersInterceptor
extends HandlerInterceptorAdapter {
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
response.setContentType(MediaType.APPLICATION_JSON_VALUE + "; charset=UTF-8");
}
}
This way, the server always responds JSON. If you still get 404 or 415, no doubt it's due to some error in the client side.

Resources