Let's see a test, which is using MockServer (org.mock-server:mockserver-netty:5.10.0) for mocking responses.
It is expected that the response body will be equal to string "something".
Nevertheless, this test fails, because the response body is an empty string.
#Test
void test1() throws Exception {
var server = ClientAndServer.startClientAndServer(9001);
server
.when(
request().withMethod("POST").withPath("/checks/"),
exactly(1)
)
.respond(
response()
.withBody("\"something\"")
.withStatusCode(205)
.withHeader("Content-Type", "application/json")
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:9001/checks/"))
.POST(BodyPublishers.noBody())
.build();
HttpResponse<String> response =
HttpClient.newHttpClient().send(request, BodyHandlers.ofString());
assertEquals(205, response.statusCode());
assertEquals("something", response.body()); // fails
}
How to make the response body be equal to the string provided in response().withBody(...)?
The problem is on the client side. It drops content.
Why!?
Because, HTTP 205 is RESET_CONTENT.
This status was chosen accidentally for test as "somenthing different from HTTP 200", and unfortunately caused this behaviour.
Looks like it is very popular "accidental" mistake (i.e. here), although it is strictly in accordance with the HTTP spec.
Related
I'm looking for a way how to forward POST request which has been made to endpoint in #RestController class and forward it to external URL with body and headers untouched (and return response from this API of course), is it possible to do it by using some spring features? The only solution which I have found is extracting a body from #RequestBody and headers from HttpServletRequest and use RestTemplate to perform a request. Is there any easier way?
#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());
}
}
The above code is taken from this answer.
This is more a matter of the HTTP spec than Spring where the server would be expected to return a 307 redirect status, indicating the client should follow the redirect using the same method and post data.
This is generally avoided in the wild as there's a lot of potential for misuse, and friction if you align with the W3.org spec that states the client should be prompted before re-executing the request at the new location.
One alternative is to have your Spring endpoint act as a proxy instead, making the POST call to the target location instead of issuing any form of redirect.
307 Temporary Redirect (since HTTP/1.1) In this occasion, the request should be repeated with another URI, but future requests can still use the original URI.2 In contrast to 303, the request method should not be changed when reissuing the original request. For instance, a POST request must be repeated using another POST request.
I want to make a Authorization request using Spring Webflux to Paysafe test environment. I tried this:
Mono<AuthorizeRequest> transactionMono = Mono.just(transaction);
return client.post().uri("https://api.test.paysafe.com/cardpayments/v1/accounts/{id}/auths", "123456789")
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.header(HttpHeaders.AUTHORIZATION, "Basic dGVz.......")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.body(transactionMono, AuthorizeRequest.class)
.retrieve()
.bodyToMono(AuthorizeResponse.class);
But I get:
org.springframework.web.reactive.function.client.WebClientResponseException$BadRequest: 400 Bad Request
at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:174)
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec.lambda$createResponseException$15(DefaultWebClient.java:512)
Do you know how I can solve this?
The server you sent the request to answered with http status code 400. That means, that your request seems to be malformed.
For example, you could have forgotten to include a parameter.
400 Bad Request could be of many reasons. To find out what exactly causing issue try printing the response body as below:
.exchange()
.flatMap(clientResponse -> {
if (clientResponse.statusCode().is5xxServerError()) {
clientResponse.body((clientHttpResponse, context) -> {
return clientHttpResponse.getBody();
});
return clientResponse.bodyToMono(String.class);
}
else
return clientResponse.bodyToMono(String.class);
})
.block();
This helps you to find the right reason and then you can fix
I'm creating test framework and using RestTemplate class for HTTP request creation.
In general cases I use next code:
Response response = null;
ResponseEntity<String> responseEntity = null;
try{
responseEntity = getRest().exchange(url, httpMethod, httpEntity, String.class);
response = new Response(
responseEntity.getStatusCodeValue(),
responseEntity.getStatusCode().getReasonPhrase(),
responseEntity.getBody(),
responseEntity.getHeaders()
);
} catch (HttpStatusCodeException e){
response = new Response(
e.getRawStatusCode(),
e.getStatusText(),
e.getResponseBodyAsString(),
e.getResponseHeaders()
);
}
It works perfectly for all cases except DELETE HTTP method which receives 204 status code and empty body as a response.
Now, I have to reinitialize RestTemplate to fix it. But I hope that another way should exist.
Could you help me with this?
Please check if you use HttpMethod.Delete for httpMethod and also try when you use delete to put as last parameter of exchange not String.class but Void.class.
I hope it help you.
I am executing a simple post test in rest-assured,
While exeuting post() method I am getting "java.net.SocketException: Operation timed out"
#Test
public void validateGetProductInfo_Prod() throws IOException {
String jsonBody = new String(Files.readAllBytes(Paths.get("src/test/resources/product.json")));
Headers basicHeaders = new Headers(asList(
new Header("X-ApplicationAuthorizationToken", "AAABB"),
new Header("Authorization", "Basic ABC"),
new Header("Content-Type", "application/json")));
String produPath = "http://api.abcd.com/product/v1/product/info";
given().headers(basicHeaders)
.body(jsonBody)
.when()
.post(produPath)
.then().using().defaultParser(Parser.JSON)
.statusCode(200);
}
I had validated that there is no error till the when is executed,
On executign the post() method facing the exception?
The original issue is the service end-point is expecting a user-agent header, the header was not sent in the request so the end-point was not responding.
Once the header was added the issues resolved.
Note:
Postman adds the default chrom user-agent if no header was provided
I read thousand of answers and try to a lot of way but doesn't work.
I really need to change response body when get "401". Because server response is different from other general response when unauthorized.
I'm using retrofit 2. To catch response i'm using Interceptor:
Request original = chain.request();
Request.Builder requestBuilder = original.newBuilder()
.header("authorization", getAccessToken(context));
Request request = requestBuilder.build();
Response response= chain.proceed(request);
if (response.code()==401) {
MediaType contentType = response.body().contentType();
ResponseBody body = ResponseBody.create(contentType, CommonFunctions.getUnAuthorizedJson(context).toString());
return response.newBuilder().body(body).build();
}else{
return response;
}
But still body doesn't change on client.enque method.
You can change body in this way, but Retrofit will eventually see 401 and throw HttpException with standart message, what can be misleading
check that you get your body right:
val errorConverter: Converter<ResponseBody, ErrorResponse> =
retrofit.responseBodyConverter(
ErrorResponse::class.java,
emptyArray()
)
val errorResponse = httpException
.response()
?.errorBody()
?.let (errorConverter::convert)