tune up request headers to be accepted by spring boot resource - java

my Spring Boot Resource is not receiving my requests.
this is the resource definition:
#PostMapping(name = "sign_one_doc", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<InputStreamResource> signDocument(
#RequestParam("file") MultipartFile data,
#RequestPart("isTimestamping") boolean isTimestamping,
#RequestPart("isMakeCheck") boolean isMakeCheck,
#RequestPart("signMode") int signMode,
#RequestPart("certClientID") int certClientID,
#RequestPart("isCertLocal") boolean isCertLocal
) throws IOException, DocSignException, InputStreamReadException {
RequestData requestData
= signCtrl.signFile(data.getInputStream(), data.getOriginalFilename(), signMode, certClientID, isTimestamping, isMakeCheck, isCertLocal);
HttpHeaders headers = new HttpHeaders();
InputStreamResource inputStreamResource = new InputStreamResource(requestData.currentCtxFile());
headers.setContentLength(requestData.currentCtxFileBA().length);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
return new ResponseEntity<InputStreamResource>(inputStreamResource, headers, HttpStatus.OK);
}
request that I'm sending (via Insomnia) has content-type: multipart/form-data and accept: application/octet-stream set
but when I send this requeste spring just says:
Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported]
Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation]
what am I setting wrong and why? also, would you be so kind and provide curl request for this resource?
I have tried:
curl -v --include -F isTimestamping=false -F file=#test.pdf -H 'Content-Type: multipart/form-data' http://localhost:8080/sign_one_doc
but spring fails with:
java.io.EOFException: Unexpected EOF read on the socket
man, it's hard to sent a request, everything that's possible just fails :D

I need to change #RequestPart to #RequestParam.
curl issue still unresolved

Related

Can spring boot controller receive plain/text?

I am trying to process a POST request with body of plain text (utf-8) but it seems that spring does not like the plain text nature of the call. Could it be that it is not supported - or otherwise, am I coding it wrong?
#RestController
#RequestMapping(path = "/abc", method = RequestMethod.POST)
public class NlpController {
#PostMapping(path= "/def", consumes = "text/plain; charset: utf-8", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> doSomething(#RequestBody String bodyText)
{
...
return ResponseEntity.ok().body(responseObject);
}
}
Respond is:
Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded' not supported]
I tested with curl command:
curl -s -X POST -H 'Content-Type: text/plain; charset: utf-8' --data-binary #text.txt localhost:8080/abc/def
The text.txt contains plain text (UTF-8 in Hebrew).
I would like to throw some light on the other part of the question of whether spring supports text/plain?
According to spring docs: what is "consumes" or Consumable Media Types in the #RequestMapping annotation
Definition :
Consumable Media Types
"You can narrow the primary mapping by specifying a list of consumable media types. The request will be matched only if the Content-Type request header matches the specified media type. For example:"
#Controller
#RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json")
public void addPet(#RequestBody Pet pet, Model model) {
// implementation omitted
}
Consumable media type expressions can also be negated as in !text/plain to match to all requests other than those with Content-Type of text/plain.
How spring does the media type matching internally?
Spring Request Driven Design is centred around a servlet called the dispatcher Servlet which uses special beans to process requests one of the bean is RequestMappingHandlerMapping which checks for the media type using
getMatchingCondition method of ConsumesRequestCondition class as shown below.
#Override
public ConsumesRequestCondition getMatchingCondition(ServerWebExchange exchange) {
if (CorsUtils.isPreFlightRequest(exchange.getRequest())) {
return PRE_FLIGHT_MATCH;
}
if (isEmpty()) {
return this;
}
Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<>(expressions);
result.removeIf(expression -> !expression.match(exchange));
return (!result.isEmpty() ? new ConsumesRequestCondition(result) : null);
}
the get matching condition class uses static inner class ConsumesMediaType Expression which actually makes the check
#Override
protected boolean matchMediaType(ServerWebExchange exchange) throws UnsupportedMediaTypeStatusException {
try {
MediaType contentType = exchange.getRequest().getHeaders().getContentType();
contentType = (contentType != null ? contentType : MediaType.APPLICATION_OCTET_STREAM);
return getMediaType().includes(contentType);
}
catch (InvalidMediaTypeException ex) {
throw new UnsupportedMediaTypeStatusException("Can't parse Content-Type [" +
exchange.getRequest().getHeaders().getFirst("Content-Type") +
"]: " + ex.getMessage());
}}
This method returns false once the media type does not match and getMatchingCondition returns null which results in handleNoMatch method of RequestMappingInfoHandlerMapping being called and using PartialMatchHelper class we check for the what type of mismatch it has as shown below and spring throws HttpMediaTypeNotSupportedException error once its see consumes mismatch
if (helper.hasConsumesMismatch()) {
Set<MediaType> mediaTypes = helper.getConsumableMediaTypes();
MediaType contentType = null;
if (StringUtils.hasLength(request.getContentType())) {
try {
contentType = MediaType.parseMediaType(request.getContentType());
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
}
throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<>(mediaTypes));
}
Spring supports all media types as per the IANA https://www.iana.org/assignments/media-types/media-types.xhtml the problem lies only with the curl command as quoted by others.
#rumbz
Please refer to the below link it might solve your issue
Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported for #RequestBody MultiValueMap
1 Using annotation
#RequestMapping(value = "/some-path", produces =
org.springframework.http.MediaType.TEXT_PLAIN)
public String plainTextAnnotation() {
return "<response body>";
}
where replace /some-path with whatever you'd like to use.
2 Setting content type in the response entity's HTTP headers:
public String plainTextResponseEntity() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(org.springframework.http.MediaType.TEXT_PLAIN);
return new ResponseEntity("<response body>", httpHeaders, HttpStatus.OK);
}
Per #m-deinum 's comment: The problem is not in the spring framework - but in the fact that curl adds "application/x-www-form-urlencoded" to the request ...
And just to make this question complete:
curl -s -X POST -H "Content-Type:" -H "Content-Type: text/plain; charset: utf-8" --data-binary #file.txt localhost:8080/abc/def

Spring Boot RestTemplate exchange 400 bad request

I have a problem with Spring Boot RestTemplate exchange.
I have the following code:
#RequestMapping(path = "/add")
public #ResponseBody String addFromTo () {
String apikey = "";
String baseurl = "http://demowebshop.webshop8.dk/admin/WEBAPI/v2/orders?start=2018-10-05T20%3A49%3A41.745Z&end=2018-10-15T20%3A49%3A41.745Z&api_key=" + apikey;
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setBasicAuth("", apikey);
HttpEntity<String> request = new HttpEntity<String>(" ", headers);
ResponseEntity<OrderResponse> response = restTemplate.exchange(baseurl, HttpMethod.GET, request, OrderResponse.class);
return "Some text.";
}
What I want is the equivalent of:
curl -X GET --header 'Accept: application/json' --header 'Authorization: Basic {encodedapikey}' 'http://demowebshop.webshop8.dk/admin/WEBAPI/v2/orders?start=2018-10-06T06%3A43%3A40.926Z&end=2018-10-16T06%3A43%3A40.926Z&api_key={apikey}'
I've tried using Postman with the exact same URL, and adding Basic Auth with the apikey, and an 'Accept: application/json' header, and that works fine, but when I run this code, I get the error message:
There was an unexpected error (type=Internal Server Error, status=500).
400 Bad Request
EDIT:
Pastebin link to the exception thrown by the program:
https://pastebin.com/jdYJ2nv7
In your curl request you are using an apikey and encodedapikey. Whereas in your Java code you don't. Next to that you are also passing an encoded URL as the URL to use. This will result in encoding the encoded URL again. So don't do that. Instead use a URL with placeholders and supply values for them.
#RequestMapping(path = "/add")
public #ResponseBody String addFromTo () {
String apikey = "";
String baseurl = "http://demowebshop.webshop8.dk/admin/WEBAPI/v2/orders?start={start}&end={end}&api_key={apikey}";
Map<String, Object> parameters = new HashMap<>();
parameters.put("start", "2018-10-05T20:49:41.745Z");
parameters.put("end", "2018-10-16T06:43:40.926Z");
parameters.put("apikey", apikey);
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setBasicAuth("", apikey);
ResponseEntity<OrderResponse> response = restTemplate.getForEntity(baseurl, OrderResponse.class, parameters);
return "Some text.";
}
The code above uses a proper parameterized URL together with a map containing values for the placeholders. Notice that those aren't encoded, as that will be handled by Spring!. Finally you can simply use the getForEntity method to get the result instead of the exchange method.
A final suggestion, Spring Boot already configures a RestTemplate which you can (re)use. You don't need to create a RestTemplate each time you need one (it is quite a heavy object to create, and after creation it is thread safe so it is enough to have a single instance).
public YourClassCOnstructor(RestTemplateBuilder builder) {
this.restTemplate = builder.basicAuthorization("", apikey).build();
}
Ofcourse you can also put this in an #Bean method and inject the specific RestTemplate into your class.
HttpHeaders.setBasicAuth(String, String) is used for username and password only, not for basic token.
If you want to use basic token try something like headers.add("Authorization", "Basic " + apiKey) instead of headers.setBasicAuth(...)

MethodArgumentTypeMismatchException calling a Rest method with curl

I have a basic SpringBoot 2.0.6.RELEASE app. Using Spring Initializer, JPA, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR with a restful architecture
#PutMapping(path = "/users/alarms2", consumes = "application/json", produces = "application/json")
public void setAlerts2(#RequestHeader(value = "Authorization") String authHeader,
#RequestBody AlertConfiguration alertConfiguration)
throws DataAccessException, ClientProtocolException, SQLException, IOException {
..
}
but when I call this method from curl:
curl -X PUT -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJyaWNhcmQub2SAsZUBnbWFpbC5jb20iLCJleHAiOjE2MDAxODMzNDAsImlhdCI6MTUzOTcwMzM0MH0.2gbyyGnkcoHjOw7HbUBBQgb59Bw8iAyFbqTe2DPUlOA-V5UwrW3KXWHZlXssZni8oRJ_o1QRzAFtAWMfz7f0Yw" -d ‘{“symbol": “MENU”, "alarmKey”:”VEGAN” , "enabled": "true"}' "http://95.90.225.68:1133/restos/api/v1/users/alarms2"
I got this error in the server:
2018-10-17 17:16 [http-nio-1133-exec-9] WARN o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver.resolveException(140) - Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'long'; nested exception is java.lang.NumberFormatException: For input string: "alarms2"]
You probably have another method with a more general signature that overlaps given one. Something like:
#PutMapping(path = "/users/{userId}", ...)
public void someMethod(#PathVariable(value = "userId") Long userId) {
...
}
So when you consume /api/v1/users/alarms2 Spring first tries to convert "alarms2" (which is obviously not a valid Long) to userId (which is Long)

Spring Boot optional multipart POST request

I have a service where I want to be able to optionally upload a file (including a file will run a separate function) with a POST request.
A simplified version of what my ReqestMapping looks like is this:
#ApiOperation(value = "Data", nickname = "Create a new data object")
#RequestMapping(value = "/add/{user_id}", produces = "application/json", method = RequestMethod.POST)
public ResponseEntity<Data> addData(#RequestParam("note") String body,
#RequestParam("location") String location,
#RequestParam(value = "file", required = false) List<MultipartFile> file,
#PathVariable String user_id){
if (file != null) {
doSomething(file);
}
doRegularStuff(body, location, user_id);
return new ResponseEntity(HttpStatus.OK);
}
As can be seen, I have the required = false option for my List of multipart files. However, when I attempt to curl the endpoint without any files and while stating that my content type is Content-Type: application/json, I get the error that my request isn't a multipart request.
Fine. So I change to Content-Type: multipart/form-data and without any files, I get the request was rejected because no multipart boundary was found (obviously, since I don't have a file).
This leads me to wonder how I can have a optional multipart parameter in my Spring endpoints? I would like to avoid having to add additional parameters to my request, such as "File Attached: True/False" as that can become cumbersome and unnecessary when the server can just check for existence.
Thanks!
There is no problem in your code, but the problem in client request, because Content-Type should be like below if you want to upload image,
multipart/form-data; boundary="123123"
try to remove the Content-Type header and test, i will put one example for server code and client request
Server code:
#RequestMapping(method = RequestMethod.POST, value = "/users/profile")
public ResponseEntity<?> handleFileUpload(#RequestParam("name") String name,
#RequestParam(name="file", required=false) MultipartFile file) {
log.info(" name : {}", name);
if(file!=null)
{
log.info("image : {}", file.getOriginalFilename());
log.info("image content type : {}", file.getContentType());
}
return new ResponseEntity<String>("Uploaded",HttpStatus.OK);
}
Client Request using Postman
with image
without image
Curl example:
without image, with Content-Type
curl -X POST -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" -F "name=test" "http://localhost:8080/api/users/profile"
without image, without Content-Type
curl -X POST -F "name=test" "http://localhost:8080/api/users/profile"

Spring ModelAndView set request header values

Is there any way to set some header values for spring ModelAndView. The exact issue is the following.
final String confirmationUrl = details.getConfirmationUrl() + details.getOrderAttemptUuid();
final ModelAndView modelAndView = new ModelAndView(new RedirectView(confirmationUrl));
I am creating a new model and view, but would like to set the referer header to some specific value. Is there any way of doing this?
The reason is that when i am comming from https pages and redirecting to https the referer is kept in the request, but when I am comming from https but redirecting to http I lost the referer as per
http://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3
Is there any way to keep the referer in the request or set it back to the ModelAndView?
Consider maybe returning a ResponseEntity from your request mapping.
http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html
Then you can set response headers using like this:
#RequestMapping("/handle") public ResponseEntity<String> handle() {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("MyResponseHeader", "MyValue");
return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}
Could you use the addObject(name, value) method on ModelAndView to store?
http://docs.spring.io/spring/docs/3.2.x/javadoc-api/org/springframework/web/servlet/ModelAndView.html

Categories

Resources