Spring Rest Controller API with HttpEntity getting null body when testing with Open API - spring-boot

I'm using Spring Boot 2.6.7 and Using Open API springdoc-openapi-ui 1.6.4. I have 2 services. From first service I'm using rest template to connect to second service.
In the first service, in rest controller api, I have used HttpEntity to get request object. The same is passed to rest template. The reason is with HttpEntity, I'm passing the request body as well as some other headers as well.
My controller method is as follows.
#PostMapping(value = "/submit", produces = MediaType.APPLICATION_JSON_VALUE)
#Operation(summary = "API for submit", description = "Submit data")
#ApiResponses(value = { #ApiResponse(responseCode = "200", description = "OK"),
#ApiResponse(responseCode = "400", description = "Bad request", content = #Content(schema = #Schema(implementation = Failure.class))),
#ApiResponse(responseCode = "500", description = "Error", content = #Content(schema = #Schema(implementation = Failure.class))), })
public ResponseEntity<Success<SubmitOpr>> submit(HttpEntity<OperationReq> httpEntity) throws Exception {
log.info("Request Entity is {}", httpEntity);
log.info("Request Body is {}", httpEntity.getBody());
SuccessResponse<SubmitOpr> response = null;
try {
response = oprService.submit(httpEntity);
} catch (Exception e) {
log.error("Failure: {}", e.getMessage());
throw e;
}
return ResponseEntity.ok().body(response);
}
My application works fine with this. And with postman client also it works fine.
But when I use swagger UI to test, I did not get expected result. And when I debug, httpEntity.getBody() is null
If I change from HttpEntity<OperationReq> httpEntity to OperationReq httpEntity and then accordingly change subsequent service layer methods, the api works fine in swagger.
But I don't want to change that. Because I want to pass HttpEntity and another thing is there are so many similar APIs and it would be very difficult to change everywhere.
Is there a better solution to this?

I think there is no direct solution to this. If you are looking to get headers along with request body, what you can do is, you can get RequestBody and get Headers in controller and create an HttpEntity object. And that you can pass on to your service layer methods.
Change will be only in controller side and no change will be required from service layers.
#PostMapping(value = "/submit", produces = MediaType.APPLICATION_JSON_VALUE)
#Operation(summary = "API for submit", description = "Submit data")
#ApiResponses(value = { #ApiResponse(responseCode = "200", description = "OK"),
#ApiResponse(responseCode = "400", description = "Bad request", content = #Content(schema = #Schema(implementation = Failure.class))),
#ApiResponse(responseCode = "500", description = "Error", content = #Content(schema = #Schema(implementation = Failure.class))), })
public ResponseEntity<Success<SubmitOpr>> submit(#RequestBody OperationReq operationReq, #RequestHeader MultiValueMap<String, String> headers) throws Exception {
HttpEntity<OperationReq> httpEntity = new HttpEntity<>(operationReq, headers);
log.info("Request Entity is {}", httpEntity);
log.info("Request Body is {}", httpEntity.getBody());
SuccessResponse<SubmitOpr> response = null;
try {
response = oprService.submit(httpEntity);
} catch (Exception e) {
log.error("Failure: {}", e.getMessage());
throw e;
}
return ResponseEntity.ok().body(response);
}

I had the same behavior of Swagger for HttpEntity controller method parameter. In my case the problem was that Swagger sent httpEntity as a query parameter instead body.
I have added a #io.swagger.v3.oas.annotations.parameters.RequestBody annotation and it solved the problem:
#io.swagger.v3.oas.annotations.parameters.RequestBody(
required = true,
description = "Put here your feature configuration DTO in a JSON format",
content = #Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
examples = #ExampleObject(value = "{}")
)
)
HttpEntity<String> httpEntity

Related

Why sending a request to controller using Postman runs fine but using RestTemplate throws 500 Internal Server Error?

Context
I have two controllers: /testParams and /callTestParams
Controller /testParams receives an object of type Example and I can call this controller from Postman without any problem.
Controller /callTestParams calls /testParams internally using RestTemplate but the response is a 500 Internal Server Error. I supose that the implementation of /callTestParams is equivalent to the call maded by Postman.
Here is the code:
#RequestMapping(value = "/testParams",
method = RequestMethod.POST, produces = "application/json", consumes = "application/json")
public ResponseEntity<Object> testParams(
#RequestBody Example credentials
) {
JSONObject params = new JSONObject( credentials );
System.out.println( params.get("clientId") + " from JSONObject");
System.out.println( credentials.getClientId() + " from GraphCredentials");
return new ResponseEntity<>(credentials,HttpStatus.OK);
}
#RequestMapping(value = "/callTestParams",
method = RequestMethod.POST, produces = "application/json", consumes = "application/json")
public ResponseEntity<Object> callTestParams() {
String url = "http://localhost:8080/GraphClient/testParams";
HttpHeaders headers = new HttpHeaders();
headers.set( HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE );
JSONObject params = new JSONObject();
params.put("clientId", "value1" );
RestTemplate restTemplate = new RestTemplate();
HttpEntity<?> entity = new HttpEntity<>(params,headers);
HttpEntity<Object> response = restTemplate.exchange(
url,
HttpMethod.POST,
entity,
Object.class
);
return new ResponseEntity<>(response.getBody(), HttpStatus.OK);
}
This is the response from Postman for /testParams
Headers:
(Content-Type,application/json)
Request Body:
JSON (appplication/json)
{"clientId":"value1"}
Response:
{
"clientId": "value1",
"clientSecret": null,
"tenantId": null,
"scope": null,
"grantType": null,
"microsoftLoginBaseURL": "https://login.microsoftonline.com/"
}
This is the response from Postman for /callTestParams
{
"timestamp": "2022-01-09T03:39:06.878+0000",
"status": 500,
"error": "Internal Server Error",
"message": "500 Internal Server Error",
"path": "/GraphClient/callTestParams"
}
This is the error in the console>
Forwarding to error page from request [/testParams] due to exception [JSONObject["clientId"] not found.]: org.json.JSONException: JSONObject["clientId"] not found.
In the parameter of the body of the HttpEntity constructor you need to pass params as String
HttpEntity<?> entity = new HttpEntity<>(params.toString(),headers);

spring boot rest client connection Exception:: org.springframework.web.client.HttpClientErrorException: 400 null

while i am executing below code i am getting error like
"org.springframework.web.client.HttpClientErrorException: 400 null".
but when i use postman to call this "http://localhost:2018/test" it is working.
static final String URL_EMPLOYEES = "http://localhost:2018/test";
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(new MediaType[] {
MediaType.APPLICATION_JSON}));
// Request to return XML format
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("replyMsg", "str");
// HttpEntity<Employee[]>: To get result as Employee[].
HttpEntity<String> entity = new HttpEntity<String>(headers);
// RestTemplate
RestTemplate restTemplate = new RestTemplate();
// Send request with GET method, and Headers.
ResponseEntity<String> response =
restTemplate.exchange(URL_EMPLOYEES,
HttpMethod.POST, entity,String.class);
HttpStatus statusCode = response.getStatusCode();
// Status Code: 200
if (statusCode == HttpStatus.OK) {
// Response Body Data
msg=response.getBody();
if (msg != null) {
System.out.println(msg);
}
}
//my clint controller class
#RestController
public class TextController {
#RequestMapping(value="/test",method = RequestMethod.POST)
public String myData2(#RequestBody String payload) {
return "done";
}
}
any suggetions?
If you're using Jackson as your JSON parser, you can simply declare your parameter with the type TextNode. This is the Jackson type representing JSON strings.
public String updateName(#PathVariable(MY_ID) String myId, #RequestBody TextNode name) {
You can then use its asText method to retrieve its text value.
Here you are setting headers Content-Type with type JSON and passing the body of type text/String.
headers.setContentType(MediaType.APPLICATION_JSON); //setting your Content type as JSON.
So, First you need to change this to
headers.setContentType(MediaType.TEXT_PLAIN); //setting your Content type as Pure Text String.
and add some code after this line
// HttpEntity<Employee[]>: To get result as Employee[].
HttpEntity<String> entity = new HttpEntity<String>(headers);
add this code
// HttpEntity<Employee[]>: To get result as Employee[].
HttpEntity<String> entity = new HttpEntity<String>(headers);
// RestTemplate
RestTemplate restTemplate = new RestTemplate();
// Send request with GET method, and Headers.
String entity_Str = new ObjectMapper().writeValueAsString(entity);
ResponseEntity<String> response =
restTemplate.exchange(URL_EMPLOYEES,
HttpMethod.POST, entity_Str, String.class);
This might work for you.. Thanks :)

swagger doesn't recognize api description

I instatiate docket like this
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.config.internal"))
.paths(Predicates.or(PathSelectors.ant("/api**/**")))
.build();
}
I created a set of stub endpoints that imitate the real one for /login or /oauth.
#Api("Authentication")
#RequestMapping("/api")
public interface LoginEndpointApi {
#ApiOperation(value = "Github SSO endpoint", notes = "Endpoint for Github SSO authentication")
#ApiResponses({
#ApiResponse(code = 200, message = "HTML page of main application")
})
#GetMapping("/oauth/github")
default void oauthGithub() {
throw new UnsupportedOperationException();
}
#ApiOperation(value = "Get CSRF token", notes = "Returns current CSRF token")
#ApiResponses({
#ApiResponse(code = 200, message = "CSRF token response", response = String.class,
examples = #Example({#ExampleProperty(value = "015275eb-293d-4ce9-ba07-ff5e1c348092")}))
})
#GetMapping("/csrf-token")
default void csrfToken() {
throw new UnsupportedOperationException();
}
#ApiOperation(value = "Login endpoint", notes = "Login endpoint for authorization")
#ApiResponses({
#ApiResponse(code = 200, message = "Successful authentication")
})
#PostMapping("/login")
default void login(
#ApiParam(required = true, name = "login", value = "login body")
#RequestBody LoginRequest loginRequest) {
throw new UnsupportedOperationException();
}
}
But it doesn't recognize it. It is located in the same com.config.internal package as I described.
But the page swagger ui is empty and shows that No operations defined in spec!
What is the problem?
If you want to provide swagger documentation for your request mappings specified above you could simply describe it with .paths(Predicates.or(PathSelectors.ant("/api/**"))) path matchers. But if your path includes something more complicated like api + text without backslash separator then you should get known with
https://docs.spring.io/spring/docs/3.1.x/javadoc-api/org/springframework/util/AntPathMatcher.html

How to set the produces value dynamically in spring rest controller?

I need to implement API which can either send response or download a file:
#GetMapping(value = "/download")
public ResponseEntity<?> downloadFile(
#RequestParam(value = "apiResponseType", required = true) String apiResponseType) throws IOException {
ValidationResponse response = null;
if (apiResponseType.equals("FILE")) {
String FILE_HEADER = "id,firstName,lastName,gender,age";
byte[] json = FILE_HEADER.getBytes();
Resource resource = new ByteArrayResource(json);
HttpHeaders headers = new HttpHeaders();
headers.setContentLength(resource.contentLength());
headers.setContentDispositionFormData("attachment", "test.csv");
return ResponseEntity.ok().headers(headers).contentType(MediaType.APPLICATION_OCTET_STREAM).body(resource);
} else {
response = new ValidationResponse();
response.setSuccess(true);
response.setMessage("TESTING");
return ResponseEntity.ok(response);
}
}
Above code is working for "ELSE" case. i.e., can able to send response.
But if I add "produces" to #GetMapping like below, I am able to download the file but not working for response (else case in above code) (Got status: 406 Not Acceptable):
#GetMapping(value = "/downloadFile", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
Can anyone help me with this ?
Did you try return ResponseEntity.ok(response).contentType(MediaType.TEXT_PLAIN); on the else branch (and remove the produces)?
Your question has invalid assumption 'which can either send response or download a file'. Download file is nothing else that sending response! For API client it is no difference. For browser it is just implementation details if browser proposes to save the response as file or shows response in browser window.
Setting the MediaType via a call to contentType on the ResponeEntitiy no longer works. By now (SpringBoot 2.7.1) you have to set the MediaType to the headers.
I wrote this method to dynamically create headers with a given MediaType provided as a string.
final HttpHeaders httpHeaders = new HttpHeaders();
final MediaType mediaType;
switch (responseType) {
case "json":
mediaType = MediaType.APPLICATION_JSON;
break;
case "plain":
case "text":
mediaType = MediaType.TEXT_PLAIN;
break;
default:
final var parts = responseType.split("/");
if (parts.length < 2)
throw new IllegalArgumentException(String.format("Unrecognizable MediaType '%s'", responseType));
mediaType = new MediaType(parts[0], parts[1]);
break;
}
LOGGER.debug("Using mediaType {}", mediaType);
httpHeaders.setContentType(mediaType);

Spring boot - Request method 'POST' not supported.

please i have this error when trying to create a customer. Can some one help me? May be i am missing some thing. I have even try to change the #PostMapping to #RequestMapping till yet. Thks
My Controller code
`#PostMapping("CREATE_CUSTOMER_ENDPOINT")
#ResponseStatus(value = HttpStatus.OK)
#ApiResponses(value = {
#ApiResponse(code = 201, message = "The Customer was Created", response = CustomerDto.class),
#ApiResponse(code = 400, message = "Bad Request", response = ResponseError.class),
#ApiResponse(code = 500, message = "Unexpected error")
})
public ResponseEntity createCustomer(final HttpServletRequest request, #RequestBody CustomerDto customerDto)
{
if (log.isDebugEnabled()){
log.debug("[CustomerResource] POST {} : Creating customer ", CREATE_CUSTOMER_ENDPOINT);
}
if(customerDto.getUidpk()!=null) {
ResponseError error = new ResponseError(HttpStatus.BAD_REQUEST.getReasonPhrase(), "A customer Already exist with an Uidpk");
log.error("[CustomerResource] The customer Already exist ({}) with an Uidpk", customerDto.getUidpk());
return new ResponseEntity<>(error, null, HttpStatus.BAD_REQUEST);
}
CustomerDto result = customerService.createCustomer(customerDto);
log.debug("[CustomerResource] Customer created ({})", result.getUidpk());
return new ResponseEntity<>(result, HeaderUtil.putLocationHeader(request.getRequestURL().toString() + "/" + result.getUidpk()), HttpStatus.CREATED);
} `
My endpoints
private static final String CUSTOMER_SEARCH_USER_ID_ENDPOINT = "/customers/{userId:.+}";
private static final String CREATE_CUSTOMER_ENDPOINT= "/customer";
private static final String UPDATE_CUSTOMER_ENDPOINT= "/customer";
private static final String DELETE_CUSTOMER_ENDPOINT = CREATE_CUSTOMER_ENDPOINT + "/{uidpk}";
This is the response of Postman
Postman sample
When you send JSON payloads in HTTP request, you need to specify Content-Type HTTP header with value application/json.

Resources