I'm trying to do a proof of concept that involves a .Net system posting a file to a Rest endpoint on a Java Spring Boot application. I keep getting the "Required Parameter is not present" error. There are a lot of SO questions with this error, and I've attempted the solutions presented in those with no luck. Can anyone see what I'm doing wrong?
Here's my C# code:
private async Task<string> PostFileAsync(string uri, System.IO.FileStream fileStream)
{
using (var client = _httpClientFactory())
{
using (var content = new MultipartFormDataContent())
{
content.Add(new StreamContent(fileStream), "assetFile");
using (var message = await client.PostAsync(uri, content))
{
return await message.Content.ReadAsStringAsync();
}
}
}
}
Here's the request as Fiddler sees it:
POST http://10.0.2.2:8083/asset/1000/1001 HTTP/1.1
Content-Type: multipart/form-data; boundary="bac9aebd-d9ff-40ef-bcf3-4fffdd1b2c00"
Host: 10.0.2.2:8083
Content-Length: 158
Expect: 100-continue
Connection: Keep-Alive
--bac9aebd-d9ff-40ef-bcf3-4fffdd1b2c00
Content-Disposition: form-data; name=assetFile
foo,bar,10
foo2,bar2,12
--bac9aebd-d9ff-40ef-bcf3-4fffdd1b2c00--
Here's my Controller:
#RestController
#RequestMapping("/asset/")
public class AssetController {
#RequestMapping(path="{merchantId}/{assetId}", method=RequestMethod.POST)
public String getAsset(
HttpServletRequest request,
#RequestParam("assetFile") MultipartFile file,
#PathVariable("merchantId") long merchantId,
#PathVariable("assetId") long assetId) throws IOException
{
return "It worked!";
}
}
Here's my config:
#SpringBootApplication(exclude={MultipartAutoConfiguration.class})
public class MySpringApplication {
public static void main(String[] args) {
SpringApplication.run(MySpringApplication.class, args);
}
#Bean(name = "multipartResolver")
public CommonsMultipartResolver multipartResolver() {
System.out.println("multipartResolver()");
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
return multipartResolver;
}
}
And here's the response:
HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 25 Mar 2016 19:34:55 GMT
Connection: close
f3
{"timestamp":1458934495566,"status":400,"error":"Bad Request","exception":"org.springframework.web.bind.MissingServletRequestParameterException","message":"Required MultipartFile parameter 'assetFile' is not present","path":"/asset/1000/1001"}
0
Edited because I posted the wrong C# code
Ok, maybe I hadn't tried ALL of the solutions I saw on SO.
This question had a solution for me.
I had to use #ModelAttribute rather than #RequestParam.
Related
This question already has answers here:
What does "consume an API" mean?
(6 answers)
Closed last month.
I am aware what an API is but I do not actually understand what it means when we say consumes or produces application/json in the context of a REST API. I have found several sources explaining how to do it but not what it actually is. I am using Java with SpringBoot in IntelliJ, so any examples relevant to that would be greatly appreciated.
I'd like to explain what consume means, it means if you send a POST request with JSON as the content type, your service should be able to accept it and doesn't reject it.
The service sample code is here:
import com.fishpro.restcontroller.domain.UserDO;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
#RestController
#RequestMapping("user2")
public class UserRestController {
#PostMapping("/update")
#ResponseBody
public Object update(#RequestBody User user){
Map<String,Object> map=new HashMap<>();
if(null==user){
map.put("status",3);
map.put("message","Empty Content");
return map;
}
//更新逻辑
map.put("status",0);
return map;
}
}
// The entity class
public class User {
private Integer userId;
private String userName;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
Let's take look at an example(you can use IDEA HTTP Client to execute it https://www.jetbrains.com/help/idea/2022.3/http-client-in-product-code-editor.html#open-requests-collection ) :
Do post to a Restful Service:
POST http://localhost:8087/user2/update, the request:
POST /user2/update HTTP/1.1
Content-Type: application/json
Host: localhost:8087
{
"userId": 1,
"userName": "userName_txxhs"
}
And the response:
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 13 Jan 2023 04:09:57 GMT
{"status":0}
It works because the HTTP client sends the correct content type and the server accepts it and produces the right one.
So in case we change the service annotation and force the client to use another Content-Type: application/text:
// ...
#PostMapping(value="/update", consumes = {"application/text"}, produces = {"application/json"})
#ResponseBody
public Object update(#ModelAttribute User user){
// ...
Then the response will say that it can't consume (support) this content type:
HTTP/1.1 415
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 13 Jan 2023 04:41:46 GMT
Connection: close
{"timestamp":"2023-01-13T04:41:46.278+0000","status":415,"error":"Unsupported Media Type","message":"Content type 'application/json' not supported","path":"/user2/update"}
So the request has to change the content type and then the response will work:
POST /user2/update HTTP/1.1
Content-Type: application/text
Host: localhost:8087
Connection: close
User-Agent: RapidAPI/4.1.1 (Macintosh; OS X/13.1.0) GCDHTTPRequest
Content-Length: 50
{
"userId": 1,
"userName": "userName_txxhs"
}
It's much simple for the annotation produces, just simply change the Content-Type header of the response, and yes that your Spring boot application should provide logic to convert any object to that content type.
I hope this will help you to understand it, and you may find the HTTP resources and specifications helpful:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Resources_and_specifications
I am working on a frontend application in React that connects to a middleware service written in Spring boot.
I am attempting to call an endpoint from the front end as follows:
return axios.post('http://localhost:8085/workshop/client/createClient', {username})
.then((response) => {
console.log('Success')
})
.catch((error) => console.log(error));
Whenever I make the request in my browser I get the following errors:
OPTIONS http://localhost:8085/workshop/client/createClient 401 ()
Failed to load http://localhost:8085/workshop/client/createClient: Response for preflight has invalid HTTP status code 401.
As far as I understand, this is because the preflight request is being blocked by my middleware application.
Having read a bit online about enabling this, I have added a CorsFilter to my spring boot application:
#Slf4j
public class CORSFilter implements Filter {
private static final String ONE_HOUR = "3600";
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", ONE_HOUR);
response.setHeader("Access-Control-Allow-Headers", "X-Requested-With,Origin,Content-Type, Accept, x-device-user-agent, Content-Type");
if (req instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest) req;
if (httpServletRequest.getHeader(HttpHeaders.ORIGIN) != null
&& httpServletRequest.getMethod().equals(HttpMethod.OPTIONS.name())
&& httpServletRequest.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD) != null) {
log.debug("Received an OPTIONS pre-flight request.");
return;
}
}
chain.doFilter(req, res);
}
#Override
public void destroy() {
}
}
And
#Configuration
public class MvcConfiguration {
#Bean
public FilterRegistrationBean corsFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new CORSFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setOrder(0);
return filterRegistrationBean;
}
}
Here is an example of the endpoint:
#PostMapping("/createClient")
public ResponseEntity<?> createClient(#RequestBody CreateClientDto clientDto) {
try {
...
return new ResponseEntity<>(responseBody, OK);
} catch (Exception e ...) {}
}
Any help would be greatly appreciated :)
Update: I had the url slightly wrong for the request (hence the 404). I have updated the error message. I still seem to be having CORS issues.
These are the response headers that I can see in the dev tools:
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:access-control-allow-credentials, access-control-allow-methods, access-control-allow-origin, allow, content-type
Access-Control-Allow-Methods:POST
Access-Control-Allow-Origin:http://localhost:3000
Allow:GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
Cache-Control:no-cache, no-store, max-age=0, must-revalidate
Content-Length:0
Date:Thu, 01 Mar 2018 14:06:38 GMT
Expires:0
Pragma:no-cache
Strict-Transport-Security:max-age=31536000 ; includeSubDomains
Vary:Origin
WWW-Authenticate:Basic realm="Spring"
X-Content-Type-Options:nosniff
X-Frame-Options:DENY
X-XSS-Protection:1; mode=block
And the request headers:
OPTIONS /workshop/client/createClient HTTP/1.1
Host: localhost:8085
Connection: keep-alive
Access-Control-Request-Method: POST
Origin: http://localhost:3000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
Access-Control-Request-Headers: access-control-allow-credentials,access-control-allow-methods,access-control-allow-origin,allow,content-type
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en;q=0.9,en-US;q=0.8,da;q=0.7
I also had this issue, but allow me to give a straight forward answer in a simpler communication manner.
First and foremost, you have to tell your server (Spring boot java), about the client (Reactjs ) URL.
For example, most of the times spring boot uses http://localhost:8080 and Reactjs uses http://localhost:3000.
So you have to navigate to the controller in Spring boot java and allow the URL of Reactjs to be given the permission for accessibility in the server (Spring boot). This will help you get rid of the CORS issue.
How do we do this in Spring boot? simply we add the #CrossOrigin annotation specifying the Reactjs URL link as below:
For example :
#GetMapping("/orphans")
#CrossOrigin(origins = "http://localhost:3000")
Iterable<Student> read() {
return studentService.findAll();
}
The method above is to list all the orphans, so I gave Reactjs URL link permission then I got rid of the CORS issue.
Happy coding.
Controller.java
Refer to the below sample code to resolve this issue.
#CrossOrigin(origins = "http://localhost:3000")
#RequestMapping(value="/sample", method = RequestMethod.GET, produces = "application/json")
public Student getStudent(){
}
add below line of code in fetch
export function getData(endpoint = '/', request = {}) {
let url = endpoint;
let req = Object.assign(
{
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
},
request
);
console.log('url,req', url, req);
return fetch(url, req).then(handleResponse).catch(handleErrors);
}
And Add below line below Rest Controller class
#RestController
#CrossOrigin(origins = "*", allowedHeaders = "*")
class TestController{}
hope this would helpful :)
This is a standard CORS issue, this basically means that user agent i.e. http://localhost:3000 doesn't have permissions to access resources at http://localhost:8085. You can learn more about it at https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS.
Your request headers should exactly map to server rules. Keeping any parameter as * won't work.
For e.g., if your request header has following:
Access-Control-Request-Method: POST
Access-Control-Request-Headers: <your custom headers>
Then, server rules show map your request header:
Access-Control-Request-Method: GET, PUT, DELETE, POST
Access-Control-Request-Headers: <your custom headers>
Let me know if it helps!
The issue seems to be resolved. I was missing the Accept request header (thanks #Anadi Sharma), and I also had the spring-boot-starter-security as a dependency, which seemed to be returning unauthorised. I also didn't need the CorsFilter, in the end I just used the #CrossOrigin annotation on the endpoint.
I have a web application built using Spring Boot with Apache Camel and I'm implementing a REST interface.
Currently, using either Camel default Servlet or Restlet component, I'm not getting the HTTP Status code reason in the response.
Here is an example response I'm getting while setting the HTTP Status code to 403:
< HTTP/1.1 403
< Date: Mon, 19 Feb 2018 10:01:21 GMT
< Server: Restlet-Framework/2.4.0
< Content-Type: application/json
< Content-Length: 75
How it should be:
< HTTP/1.1 403 Forbidden
< Date: Mon, 19 Feb 2018 10:01:21 GMT
< Server: Restlet-Framework/2.4.0
< Content-Type: application/json
< Content-Length: 75
How can I configure Camel/Restlet/Servlet to include the reason on the HTTP Status code?
My current configuration:
Application.java
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
private static final Logger appLogger = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
appLogger.info("--Application Started--");
}
#Bean
public ServletRegistrationBean servletRegistrationBean() {
SpringServerServlet serverServlet = new SpringServerServlet();
ServletRegistrationBean regBean = new ServletRegistrationBean(serverServlet, "/*");
Map<String,String> params = new HashMap<>();
params.put("org.restlet.component", "restletComponent");
regBean.setInitParameters(params);
return regBean;
}
#Bean
public Component restletComponent() {
return new Component();
}
#Bean
public RestletComponent restletComponentService() {
return new RestletComponent(restletComponent());
}
}
Route Configuration:
#Component
public class RestRouteBuilder extends RouteBuilder {
private static final Logger appLogger = LoggerFactory.getLogger(RestRouteBuilder.class);
private Predicate isAuthorizedRequest = header(HttpHeaders.AUTHORIZATION).isNotNull();
#Override
public void configure() throws Exception {
restConfiguration().component("restlet")
.contextPath("/overlay")
.bindingMode(RestBindingMode.json)
.skipBindingOnErrorCode(false)
.dataFormatProperty("prettyPrint", "true");
rest("/")
.get()
.route()
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(403))
.setBody(constant("Forbidden"))
.endRest();
}
}
I also tried adding .setHeader(Exchange.HTTP_RESPONSE_TEXT, constant("Forbidden")) but the result was the same.
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(403))
.setHeader(Exchange.CONTENT_TYPE, constant("text/plain"))
.setBody(constant("Forbidden"))
How can I configure Camel/Restlet/Servlet to include the reason on the HTTP Status code?
Without a custom core, I believe you can't:
The response is being sent at org.restlet.engine.adapter.ServerCall.sendResponse(), where the response head and body are written to the OutputStream:
writeResponseHead(response); // <--
if (responseEntity != null) {
responseEntityStream = getResponseEntityStream();
writeResponseBody(responseEntity, responseEntityStream);
}
... and writeResponseHead(response) does nothing by default, check it:
protected void writeResponseHead(Response response) throws IOException {
// Do nothing by default
}
Update: ... the HttpStatus(value, reasonPhrase) has the reasonPhrase, but isn't used to stringify:
HttpStatus(int value, String reasonPhrase) {
this.value = value;
this.reasonPhrase = reasonPhrase;
}
...
#Override
public String toString() {
return Integer.toString(this.value);
}
Update 2: ... the DefaultRestletBinding.populateRestletResponseFromExchange does the following:
// get response code
Integer responseCode = out.getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class);
if (responseCode != null) {
response.setStatus(Status.valueOf(responseCode));
}
... it only uses the Status.valueOf.
Although there is a Status.reasonPhrase, it isn't accessible.
Answer:
Without custom core, (I believe) you can't!
... what isn't inappropriate, given that:
RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1:
6.1.1 Status Code and Reason Phrase
(...) The client is not required to examine or display the Reason-Phrase.
(...) The reason phrases (...) MAY be replaced by local equivalents without affecting the protocol.
RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing:
3.1.2. Status Line
(...) A client SHOULD ignore the reason-phrase content.
RFC 7540 - Hypertext Transfer Protocol Version 2 (HTTP/2):
8.1.2.4. Response Pseudo-Header Fields
(...) HTTP/2 does not define a way to carry the version or reason phrase that is included in an HTTP/1.1 status line.
Need to know the meaning of a status code?
See the complete list of status codes maintained by IANA.
TLDR - use .setHeader(Exchange.HTTP_RESPONSE_CODE, 403)
I found out that
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(403)) does not work but
.setHeader(Exchange.HTTP_RESPONSE_CODE, 403) DOES.
Mind the constant keyword difference. You need to use constant when setting values directly in the route, but e.g. in Processor you do not. Therefore you can set response status code whenever exception occurs in code, not in the route builder config.
I have the following REST API to parse the given JSON:
POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.TEXT_PLAIN)
#Path("/test")
public String getText(#FormDataParam("file") InputStream fileInputStream, #FormDataParam("file") FormDataContentDisposition fileDetail) throws Exception {
when I test it using the chrome extension postman, the filedetail.getName() is working however the input stream received is null. here the post request I sent :
POST /parse/test HTTP/1.1
Host: localhost:8080
Cache-Control: no-cache
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="file"; filename="1.json"
Content-Type:
----WebKitFormBoundaryE19zNvXGzXaLvS5C
The inputstream received is null .
Note: if I set the content type to "multipart/form-data" I got an exception :
java.lang.NullPointerException
com.sun.jersey.multipart.impl.MultiPartReaderClientSide.unquoteMediaTypeParameters(MultiPartReaderClientSide.java:245)
com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readMultiPart(MultiPartReaderClientSide.java:172)
com.sun.jersey.multipart.impl.MultiPartReaderServerSide.readMultiPart(MultiPartReaderServerSide.java:80)
com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readFrom(MultiPartReaderClientSide.java:158)
com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readFrom(MultiPartReaderClientSide.java:85)
com.sun.jersey.spi.container.ContainerRequest.getEntity(ContainerRequest.java:490)
com.sun.jersey.spi.container.ContainerRequest.getEntity(ContainerRequest.java:555)
com.sun.jersey.multipart.impl.FormDataMultiPartDispatchProvider$FormDataInjectableValuesProvider.getInjectableValues(FormDataMultiPartDispatchProvider.java:122)
com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider$EntityParamInInvoker.getParams(AbstractResourceMethodDispatchProvider.java:153)
com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider$TypeOutInvoker._dispatch(AbstractResourceMethodDispatchProvider.java:183)
so I send it without any header, how can I read the file I sent from the postman, is there anything wrong with my REST API ?
do you use org.glassfish.jersey.bundle (jaxrs-ri) ?
if you do, you have to add MultiPartFeature.class to your ApplicationConfigure.java (which contains the Override of getClasses())
if you use grizzly so you have to put and register that class in ResourceConfig.
here an example for both
first grizzly
public static HttpServer startServer() {
final ResourceConfig rc = new ResourceConfig().packages(true, "your.controllers.package");
rc.register(MultiPartFeature.class);
return GrizzlyHttpServerFactory.createHttpServer(URI.create("http://localhost:8080/"),rc);
}
now the jersey
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new HashSet<>();
addResources(resources);
return resources;
}
private void addResources(Set<Class<?>> resources) {
resources.add(MultiPartFeature.class);
}
I also remove Consumes annotation from my method (I believe it detect it as multipart/form-data by default) and also remove content-type from client request because in this case, it cause error 400
I am using Spring 4 with mapped methods as follows
#RestController
#RequestMapping("/v3/users")
public class UserController {
...
#RequestMapping(value = "/{userId}/reset_password", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<Void> resetPassword(
#PathVariable("userId") Long userId, #RequestBody UserPasswordResetRequestDTO data) {
// Logic here
return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
}
}
public class UserPasswordResetRequestDTO {
private Long userId;
private String oldPassword;
private String newPassword;
// Getters and setters
}
then, i do this request:
POST /v3/users/6/reset_password HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 8afe6ef8-a4cd-fc9d-a6cc-b92766a56bd6
{"oldPassword":"actualPassword", "newPassword":"newOne"}
And all UserPasswordResetRequestDTO attributes are coming null
I've been searching and i find some related problems with the difference that those were PUT methods (then since Spring 3.1 can be done with HttpPutFormContentFilter), but this is a post and i couldn't find any problem... what i am doing wrong?
EDIT
I've tried changing #RequestBody with #ModelAttribute and it just mapped "userId" attribute (coming from url), leaving null the rest of attributes. Just the same as #RequestBody did with the difference of userId
I am really really disconcerted
Finally, i discovered what was going on. I had this jackson config
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.indentOutput(true).dateFormat(new SimpleDateFormat("yyyy-MM-dd"));
builder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
builder.serializationInclusion(Include.NON_NULL);
return builder;
}
which was causing the conversion from camel case to underscores on serialization and deserealization. So there was nothing wrong with code, just an invalid request, which in this case had to be this one:
POST /v3/users/6/reset_password HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 8afe6ef8-a4cd-fc9d-a6cc-b92766a56bd6
{"old_password":"actualPassword", "new_password":"newOne"}