I want to add a #ApiImplicitParam Springfox annotation to document on Swagger UI a cookie, required by the service
#GetMapping(value = "cookies")
public void methodA(
#RequestHeader HttpHeaders headers) {
service.checkCookie(headers);
}
I'll try doing
#GetMapping(value = "cookies")
public void methodA(
#ApiImplicitParam(name="cookie", paramType = "header", type = "string") #RequestHeader HttpHeaders headers) {
service.checkCookie(headers);
}
But in Swagger UI don't sent any cookie when I push on Execute button.
Cookie isn't sent, and return a Bad Request 400 code. But If I execute the curl printed, I obtain an OK status.
curl -k -i -X GET "https://localhost:8080/cookies" -H "cookie: KEY=VALUE"
HTTP/1.1 200
Also try with paramType annotation value cookie like this:
#GetMapping(value = "cookies")
public void methodA(
#ApiImplicitParam(name="cookie", paramType = "cookie", type = "string") #RequestHeader HttpHeaders headers) {
service.checkCookie(headers);
}
With the same 400 error
Related
I need to receive this request using Spring:
POST /test HTTP/1.1
user-agent: Dart/2.8 (dart:io)
content-type: multipart/form-data; boundary=--dio-boundary-3791459749
accept-encoding: gzip
content-length: 151
host: 192.168.0.107:8443
----dio-boundary-3791459749
content-disposition: form-data; name="MyModel"
{"testString":"hello world"}
----dio-boundary-3791459749--
But unfortunately this Spring endpoint:
#PostMapping(value = "/test", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void test(#Valid #RequestPart(value = "MyModel") MyModel myModel) {
String testString = myModel.getTestString();
}
returns 415 error:
Content type 'multipart/form-data;boundary=--dio-boundary-2534440849' not supported
to the client.
And this(same endpoint but with the consumes = MULTIPART_FORM_DATA_VALUE):
#PostMapping(value = "/test", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void test(#Valid #RequestPart(value = "MyModel") MyModel myModel) {
String testString = myModel.getTestString();
}
again returns 415 but, with this message:
Content type 'application/octet-stream' not supported
I already successfully used this endpoint(even without consumes) with this old request:
POST /test HTTP/1.1
Content-Type: multipart/form-data; boundary=62b81b81-05b1-4287-971b-c32ffa990559
Content-Length: 275
Host: 192.168.0.107:8443
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.8.0
--62b81b81-05b1-4287-971b-c32ffa990559
Content-Disposition: form-data; name="MyModel"
Content-Transfer-Encoding: binary
Content-Type: application/json; charset=UTF-8
Content-Length: 35
{"testString":"hello world"}
--62b81b81-05b1-4287-971b-c32ffa990559--
But unfortunately now I need to use the first described request and I can't add additional fields to it.
So, I need to change the Spring endpoint, but how?
You need to have your controller method consume MediaType.MULTIPART_FORM_DATA_VALUE,
#PostMapping(value = "/test", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
......
You also need to add a MappingJackson2HttpMessageConverter support application/octet-stream. In this answer,
I configure it by using WebMvcConfigurer#extendMessageConverters so that I can keep the default configuration of the other converters.(Spring MVC is configured with Spring Boot’s converters).
I create the converter from the ObjectMapper instance used by Spring.
[For more information]
Spring Boot Reference Documentation - Spring MVC Auto-configuration
How do I obtain the Jackson ObjectMapper in use by Spring 4.1?
Why does Spring Boot change the format of a JSON response even when a custom converter which never handles JSON is configured?
#Configuration
public class MyConfigurer implements WebMvcConfigurer {
#Autowired
private ObjectMapper objectMapper;
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
ReadOnlyMultipartFormDataEndpointConverter converter = new ReadOnlyMultipartFormDataEndpointConverter(
objectMapper);
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.addAll(converter.getSupportedMediaTypes());
supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
converter.setSupportedMediaTypes(supportedMediaTypes);
converters.add(converter);
}
}
[NOTE]
Also you can modify the behavior of your converter by extending it.
In this answer, I extends MappingJackson2HttpMessageConverter so that
it reads data only when the mapped controller method consumes just MediaType.MULTIPART_FORM_DATA_VALUE
it doesn't write any response(another converter do that).
public class ReadOnlyMultipartFormDataEndpointConverter extends MappingJackson2HttpMessageConverter {
public ReadOnlyMultipartFormDataEndpointConverter(ObjectMapper objectMapper) {
super(objectMapper);
}
#Override
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
// When a rest client(e.g. RestTemplate#getForObject) reads a request, 'RequestAttributes' can be null.
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
return false;
}
HandlerMethod handlerMethod = (HandlerMethod) requestAttributes
.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
if (handlerMethod == null) {
return false;
}
RequestMapping requestMapping = handlerMethod.getMethodAnnotation(RequestMapping.class);
if (requestMapping == null) {
return false;
}
// This converter reads data only when the mapped controller method consumes just 'MediaType.MULTIPART_FORM_DATA_VALUE'.
if (requestMapping.consumes().length != 1
|| !MediaType.MULTIPART_FORM_DATA_VALUE.equals(requestMapping.consumes()[0])) {
return false;
}
return super.canRead(type, contextClass, mediaType);
}
// If you want to decide whether this converter can reads data depending on end point classes (i.e. classes with '#RestController'/'#Controller'),
// you have to compare 'contextClass' to the type(s) of your end point class(es).
// Use this 'canRead' method instead.
// #Override
// public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
// return YourEndpointController.class == contextClass && super.canRead(type, contextClass, mediaType);
// }
#Override
protected boolean canWrite(MediaType mediaType) {
// This converter is only be used for requests.
return false;
}
}
The causes of 415 errors
When your controller method consumes MediaType.APPLICATION_OCTET_STREAM_VALUE, it doesn't handle a request with Content-Type: multipart/form-data;. Therefore you get 415.
On the other hand, when your controller method consumes MediaType.MULTIPART_FORM_DATA_VALUE, it can handle a request with Content-Type: multipart/form-data;. However JSON without Content-Type is not handled depending on your configuration.
When you annotate a method argument with #RequestPart annotation,
RequestPartMethodArgumentResolver parses a request.
RequestPartMethodArgumentResolver recognizes content-type as application/octet-stream when it is not specified.
RequestPartMethodArgumentResolver uses a MappingJackson2HttpMessageConverter to parse a reuqest body and get JSON.
By default configuration MappingJackson2HttpMessageConverter supports application/json and application/*+json only.
(As far as I read your question) Your MappingJackson2HttpMessageConverters don't seem to support application/octet-stream.(Therefore you get 415.)
Conclusion
Therefore I think you can successfully handle a request by letting MappingJackson2HttpMessageConverter(an implementation of HttpMessageConverter) to support application/octet-stream like above.
[UPDATE 1]
If you don't need to validate MyModel with #Valid annotation and simply want to convert the JSON body to MyModel, #RequestParam can be useful.
If you choose this solution, you do NOT have to configure MappingJackson2HttpMessageConverter to support application/octet-stream.
You can handle not only JSON data but also file data using this solution.
#PostMapping(value = "/test", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void test(#RequestParam(value = "MyModel") Part part) throws IOException {
// 'part' is an instance of 'javax.servlet.http.Part'.
// According to javadoc of 'javax.servlet.http.Part',
// 'The part may represent either an uploaded file or form data'
try (InputStream is = part.getInputStream()) {
ObjectMapper objectMapper = new ObjectMapper();
MyModel myModel = objectMapper.readValue(part.getInputStream(), MyModel.class);
.....
}
.....
}
See Also
Javadoc of RequestPartMethodArgumentResolver
Javadoc of MappingJackson2HttpMessageConverter
Content type blank is not supported (Related question)
Spring Web MVC - Multipart
I have to make a HTTP POST request with form data to an external API. Currently, I am invoking the API using the below curl command
curl https://books.zoho.com/api/v3/contacts?organization_id=10234695
-X POST
-H "Authorization: Zoho-oauthtoken 216793f385b9dd6e125f"
-H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8"
-F 'JSONString="{
"contact_name": "Bowman and Co",
"company_name": "Bowman and Co",
"website": "www.bowmanfurniture.com",
"contact_type": "customer"
}
I have implemented ClientHttpRequestInterceptor interface and registered the interceptor with the RestTemplate. I am not sure, how to post the HTTP request with form data. Kindly let me know, how to submit the form data or point me to any reference.
Inside my Controller class:
#RestController
#RequestMapping("/api/v1/contacts")
public class ContactController {
#Autowired
private RestTemplate restTemplate;
#PostMapping("/")
public Invoice createContact(#RequestBody Contact contact){
//TODO
return null;
}
}
You could use a rest template to make a post request with form data.
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
map.add("data", "data");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<String> response = restTemplate.postForEntity( url, request , String.class );
Spring will automatically take care of the message converters used in this case.(The message converter used will be FormHttpMessageConverter).
Read Doc
While Executing the following post request in postman:
http://localhost:8080/FinalSmsApi/rest/requestSms/hello
with parameter username,password and phone .
I am getting the following error :
HTTP Status 415: The server refused this request because the request entity is in a format not supported by the requested resource for the requested method
This is the controller:
#RestController
public class MainController1 {
#RequestMapping(value = "/hello", method = POST, consumes = "application/json")
public void Register(#RequestParam(value = "username") String username,
#RequestParam(value = "password") String password,
#RequestParam(value = "phone") String phone) {...}
}
Using Spring 4 version.
HTTP Status 415: The server refused this request...
This means that your endpoint is not able to process the passed Request Body. This error have two main reasons: either you did not specify what is the type of your request body or you passed an invalid data.
By Adding Content-Type header to your request headers, this problem would be solved:
Content-Type: application/json
And also, you're not capturing request body in your public void Register(..) method. If you're planning to go this way, it's better to drop the consumes attribute and pass all the parameters with Query Parameters, as you did.
The other approach is to define a resource class like:
public class User {
private String username;
private String password;
private String phone;
// getters and setters
}
Then change your controller to capture the request body, like following:
#RequestMapping(value = "/hello", method = POST, consumes = "application/json")
public void Register(#RequestBody User user) {...}
And finally, send a JSON representation along with your request:
curl -XPOST -H'Content-Type: application/json' --data '{"username": "", "password": "", "phone": ""}' http://localhost:8080/hello
Have an external Restful Web Service which takes in JSON payloads if its is more than one input but if its a single input it just needs the value.
For example, for multiple inputs, this works:
curl -H "Content-Type: application/json" -X POST -d '{ "amount": 10000, "interestRate": ".28", "term": "12", "state": "Georgia"}' http://localhost:8080/webservices/REST/sample/loan
Returns:
Approved
For single inputs:
curl -H "Content-Type: application/json" -X POST -d "18" http://localhost:8080/webservices/REST/sample/age
Returns:
Approved
Using Spring Boot, tried to create a JUnit test, in order, to see if I can post to this external service using Spring's RestTemplate API.
public void RestWebServiceTest {
private RestTemplate restTemplate;
private HttpHeaders headers;
#Before
public void setup() {
restTemplate = new RestTemplate();
headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
}
#Test
public void validLoan() {
final String uri = "http://localhost:8080/webservices/REST/sample/Loan";
Map<String, String> input = new HashMap<>();
input.put("amount", "10000");
input.put("interestRate", ".28");
input.put("term", "12");
input.put("state", "Georgia");
String result = restTemplate.postForObject(uri, input, String.class);
assertEquals("Approved", result);
}
#Test
public void validAge() {
final String uri = "http://localhost:8080/webservices/REST/sample/age";
Integer input = 18;
String result = restTemplate.postForObject(uri, input, String.class);
assertEquals("Approved", result);
}
#Test
public void validCountry() {
final String uri = "http://localhost:8080/webservices/REST/sample/country
String input = "US";
String result = restTemplate.postForObject(uri, input, String.class);
assertEquals("Approved", result);
}
}
All of these work except for the validCountry() test method:
org.springframework.web.client.HttpClientErrorException: 415 Unsupported Media Type
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:641)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:597)
This is strange because this curl command works for the same call:
curl -H "Content-Type: application/json" -X POST -d 'US' http://localhost:8080/webservices/REST/sample/country
Returns:
Approved
Question(s):
How can one mimic the rest call for the country (see above curl command) inside the validCountry() test method?
Do I need to add or change a different value for HTTP Headers (inside setup() method)?
Don't understand that validAge works by using the Integer wrapper class but the String doesn't?
Is there a better way to do this using Spring's RestTemplate API?
Thank you for taking the time to read this...
You need to set the Content-Type to application/json. Content-Type has to be set in request. Below is the modified code to set the Content-Type
#Test
public void validCountry() {
final String uri = "http://localhost:8080/webservices/REST/sample/country";
String input = "US";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> request = new HttpEntity<String>(input, headers);
String result = restTemplate.postForObject(uri, request, String.class);
assertEquals("Approved", result);
}
Here, HttpEntity is constructed with your input i.e "US" and with headers.
I think this answers you questions 1,2 and 4. But not 3.
I am using Spring MVC 4, and I have a controller with the below mapping/method:
#RequestMapping(value = "/me/bio", method = RequestMethod.POST, consumes = { "multipart/form-data" })
#ResponseBody
public JsonResponse<Boolean> saveProfileBio1(Account account, #RequestPart("file") MultipartFile file, #RequestPart("profile") #Valid ProfileBio profileBio) throws ValidationException, IOException {
...//code here
}
When I submit a multipart form data request it fails with HTTP 400 Bad request with the error " org.springframework.web.multipart.support.MissingS ervletRequestPartException: Required request part 'profile' is not present"
Below is the raw request:
------WebKitFormBoundarynU961NKt3K534rCg
Content-Disposition: form-data; name="profile"
{"profileName":"Zack Smith","profileDescription":"xxx","profileWebLink" :"www.abc","profilePictureUrl":"https://s3.amazonaws.com/xxx-images/default.png","profileTitle":"CTO1"}
------WebKitFormBoundarynU961NKt3K534rCg
Content-Disposition: form-data; name="file"; filename="2013-11-16 21.19.59.jpg"
Content-Type: image/jpeg
As you can see the request clearly has the "profile" part. From my debugging, the issue is that the "profile" request part does not have the "Content-type" set, and DefaultMultipartHttpServletRequest has the below method that requires it to be set and if it returns null the entire request fails with the above error.
#Override
public HttpHeaders getMultipartHeaders(String paramOrFileName) {
String contentType = getMultipartContentType(paramOrFileName);
if (contentType != null) {
HttpHeaders headers = new HttpHeaders();
headers.add(CONTENT_TYPE, contentType);
return headers;
}
else {
return null;
}
}
Trouble is is that I can't seem to find a way to set the content-type on a FormData submit in the browser for each part and seems to be something I can't set, and Spring seems to require it.
Any tips on how to fix this or if this is a bug?
Thanks
I see two options to solve the issue:
On the client: Add the JSON as Blob to FormData, as mentioned here. Background: Blob allows setting the content type (example with angular js):
var formData = new FormData();
formData.append('profile', new Blob([angular.toJson(profile)], {
type: "application/json"}
));
Alternativly on the server (not recommended): overwrite the getMultipartHeaders method of DefaultMultipartHttpServletRequest and configure this in spring. If you are using CommonsMultipartResolver you need to overwrite it as well (due to missing dependency injection point):
new DefaultMultipartHttpServletRequest() {
#Override
public HttpHeaders getMultipartHeaders(String paramOrFileName) {
// your code here
}
}
I was just battling this issue and my solution was to stop using #RequestPart and use #RequestParam instead. If I'm understanding the doc for #RequestPart correctly, it only works out of the box for a few types (such as MultipartFile) but others require an HttpMessageConverter. Also make sure you have a MultipartResolver bean declared. Recommend that it return a CommonsMultipartResolver.