Since some days ago, my app started to have a very strange behavior on a spring boot #PostMapping request.
The code is as follows:
#PostMapping("/uploadFiles")
public ResponseEntity<Map<String,Object>> uploadFiles(MultipartHttpServletRequest request) throws IOException{
System.out.println(request.getParameterMap().keySet());
}
Pretty straigthfoward, nothing special here.
The problem is that the 'request' parameter only have parameters on the fist rest request the application receives. If other request is called before this, the request parameters will always be null. If the same request is sent twice to this endpoint, the fist one have all parameters / attached files, but the second is always empty.
Debugging the request parameter to identify what is going on, I identified the 'problem' occurs on 'MultipartStream.skipPreamble() (line 607 on 'tomcat-embed-core-9.0.27.jar'). When the method 'discardBodyData()' is called it is throwing the following exception, which make it never read the data from the stream:
org.apache.tomcat.util.http.fileupload.MultipartStream$MalformedStreamException: Stream ended unexpectedly
I have no idea what might be causing this. The toncat version wasn't updated (but its pretty old, I know), a new black project with this endpoint receive the parameter as many times as I pass it, other 'non-file' endpoints also receives the parameters and another colleague in the same project had the same problem (not a problem on my machine, it seems)
Any idea what might be causing this issue?
Related
I know sending a body with a GET request isn't the best idea but I'm trying to consume an existing API which requires it.
Sending a body with POST is straight-forward:
webClient.post()
.uri("/employees")
.body(Mono.just(empl), Employee.class)
.retrieve()
.bodyToMono(Employee.class);
It won't work with webClient.get() though, because while the post() method returns a WebClient.RequestBodyUriSpec, the get() method returns WebClient.RequestHeadersUriSpec<?>, which doesn't seem to allow any body definitions.
I've found a workaround for Spring RestTemplate here: RestTemplate get with body,
but had no luck finding any for the new WebClient.
While the other responses are correct that you shouldn't use a body with a GET request, that is not helpful when you do not own, or cannot change the already existing method you are calling.
The problems is WebClient#get returns a WebClient.RequestHeadersUriSpec which does not provide a way for us to set the body.
WebClient#post returns a WebClient.RequestBodyUriSpec which does provide us a way to set the body but will cause us to use the wrong HTTP method, POST instead of GET.
Thankfully for us stuck in this situation there is WebClient#method which returns a WebClient.RequestBodyUriSpec and allows us to set the HTTP method.
webClient.method(HttpMethod.GET)
.uri("/employees")
.body(Mono.just(empl), Employee.class)
.retrieve()
.bodyToMono(Employee.class);
You may still run into issues in your testing libraries though...
A GET reques has no body. It is forbidden (well, not forbidden, but not used at all) by the HTTP specification. You have two approaches here:
Do a POST. It is there just for that.
Use a query string and pass the data in that part of the URL.
Of course, you can attach the needed fields and pass a payload to the GET request, but it will probably be ignored, or worse, identified as an error and rejected by the server, before your served code has access to it. But if you are passing data to the server to do some processing with it, then POST is what you need to use.
Extracted from RFC-7231. HTTP 1.1. Semantics and code:
A payload within a GET request message has no defined semantics;
sending a payload body on a GET request might cause some existing
implementations to reject the request.
(markup is mine)
Reasons for this are, mainly, that a GET method must be idempotent, producing the same output for the same URL, if repeated. POST doesn't have these requirements, so POST is your friend.
I am building the restful web service. For the put request, I first find the testBean with the id in the pathvariable. If it does not exist, then I create a new one. I am wondering if it is right to create a new one here, or I should throw the exception. Because id is auto increment, if I create a new TestBean, the id saved in the db is different from the one from the url path parameter.
#PutMapping("/Test/{id}")
public TestBean updateTestBean(#PathVariable long id, #RequestBody TestBean newTestBean) {
return testBeanService.getTestById(id)
.map(testBean -> {
testBean.setBRR(newTestBean.getBRR());
testBean.setModifiedDate(newTestBean.getModifiedDate());
return crewsBeanService.saveTestBean(testBean);
})
.orElseGet(() -> {
newTestBean.setId(id);
return testBeanService.saveTestBean(newTestBean);
});
}
I'd always prefer to keep PUT method idempotent. Idempotency can be explained as how many times you apply a certain "operation", the result will be the same as the first time. Since REST is just a style, it's up to you, but I will always question to me if it makes sense to keep the operation as PUT or POST.
What if the client of your service is impatient and access your PUT service multiple times while the first request is being served?. You may end up creating two users. So throwing an exception is meaningful if the ID doesn't exist.
It can be 400 or 404, I don't prefer 404 but prefer 400 because of the following reasons,
1) It confuses the client of your APIs if the resource is wrong or the ID they are using is wrong.
(You can always differentiate in your error response and provide meaningful information, but still, I don't prefer!)
2) By using 404,
you're telling the user the problem could be permanent or temporary
,for instance, say your service is not properly registered with discovery server(eureka) or is crashed, the discovery server will send 404 until you fix the problem.
By using 400,
you're asking the user to try with different input, in this case, with a different ID. This is permanent...
as you said id is auto-increment and the client cannot decide the value, so until the user fixes the problem by going back and request your POST service for a new ID, the request is "BAD" and cannot be processed.
Based on Single Responsibility Principle, you should have methods which are doing only one thing. So for your question, you need 2 methods for each request:
GET - asking the server for an object, in your case TestBean.
POST - save new objects (you don't need an id for these).
And in your front end application you could use the GET to ask the server if it have the requested object, and if not, maybe you can add a form which on submit will make the POST request with the data provided in the form fields.
PUT should only be responsible for updating a record. If the id of your bean doesn't exist, you will have an exception on your persistence layer. You can catch that exception on your API and return one of the 400's response code, such as BAD REQUEST.
For creation you should use POST, an id should not be provided in that case
This would be the RESTful way of doing this.
404 is the correct return code for a PUT to a non-existent resource, because the URL used does not address an extant resource.
If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI.
If the server desires that the request be applied to a different URI, it MUST send a 301 (Moved Permanently) response; the user agent MAY then make its own decision regarding whether or not to redirect the request.
https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
I'm trying to communicate information between two micro services in Spring. One is sending a ResponseEntity with an Object in the body, but on the other side I can't seem to get the correct response. All the fields are null.
this is the controller of my first microservice
this is what is returned when called directly
this is the code inspection of the response before it's sent
Then I try to recuperate this response in another micro service.
This is the client
This is the call to the first API
This is the response I get
So I'm stuck there, not getting why my client can't access the data. I have set breakpoints in both of the application and the first one is correctly called.
You can find my code there : https://github.com/Shikatamo/B3Examples
I have tried for 3-4h, and I'm really stuck there. It looks like something really stupid from my part but I can't seem to put my finger on it. All help is greatly appreciated at this point.
Try to get rid of ResponseEntity in your client:
#Component
#FeignClient("CourseStudent")
public interface ICourseStudentClient {
#RequestLine("GET /{id}")
CourseStudents getOneById(#Param("id") Long id);
}
I'm having an issue with consuming a REST service in Spring 5. The scenario: user updates a field or two on a screen and clicks a button. That calls a Spring controller which in turn makes two REST calls in series to gather information that is then returned to the browser. Our current production version uses Spring 4 and works great. But after upgrading to 5, the service invocations fail unless I put the server into debug mode and debug through the part of the code that does the two calls. If I debug, it works as expected. If I don't debug, I get a NullPointerException because the code uses information retrieved by the REST call that isn't there because it closed the connection before the REST service could return the information. Maybe there's a new dependency I missed or something I've overlooked when I upgraded to Spring 5 from 4, or something, but I've never seen anything like this.
Edit: Here's the code responsible for the REST service call:
ResponseEntity<String> entity = restTemplate.exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference<String>(){});
restTemplate is an autowired instance of org.springframework.web.client.RestTemplate. Whether the call completes or not, I always get a ResponseEntity. But when the call terminates early, the body data is null, which is incorrect. There will always be body data returned by call, and Spring 4 always captures it correctly. Spring 5 only captures it correctly when I debug through the code, but fails during normal execution - I see this when I examine captured data.
Discovered a solution - the problem likely resides in the JDK version I have. I switched the request factory to HttpComponentsClientHttpRequestFactory, and voila, no more problem.
I have something like the following in a Spring project:
#RequestMapping(value = "/someRestUrl", method = RequestMethod.POST)
public SomeSo doSomeWork(#Validated #RequestBody SomeSo someSo) {
...
}
I recently changed SomeSo to have an additional parameter that was required:
#NotNull
private String someParameterThatNeedsToBeProvided;
Then I promptly left for the evening and when I got back to work the next day, my requests that were working before I made this change were no longer working. It took me forever to figure out why because I can't remember what I did less than 24 hours ago and, more importantly, because the error message sent to the client was only the following with no further details:
How can I get more information on what the issue is by either logging or sending this information back to the client? Is there some configuration I can do to get either more detailed logging for errors like this (I'd like to NOT include all of Spring's logging at DEBUG level, though) or provide this information to the client?
Note: I'd like to clarify that the request was parseable but was just missing the new parameter. It wasn't poorly formatted JSON.
You would want to check out the Errors class or the BindingResult class. Those give you details on what problems occurred due to #Validated. You can include them in the method parameters to interact with them. They are also available to templates. You would want to make a custom error page to output the details of the errors if that is something you want to expose.
I have faced this same error, and it was due to the incoming JSON not matching the object it is being mapped to.
Most probable cause is an empty collection, a single item mapped to a collection or some incorrect data type conversion.
You should enable DEBUG for Spring to detect the failure. Usually this errors are caused by inner exceptions from Jackson Mapper... So take a look at the log to find it, and you'll get an idea of what is the cause for your particular error.