JAX-RS HTTP multipart request - java

How to implement multipart/form-data request (file upload) handler with JAX-RS without vendor specific libraries? So far I haven't found other way than to inject the HttpServletRequest and use the Servlet API to access the form data.
Yet HttpServletRequest#getParts() returns an empty list even the request is well formed (confirmed with Wireshark). I read I have to enable multipart configuration for the Jersey Servlet in the web.xml. However, I'm using #ApplicationPath annotation to automatically configure JAX-RS. So what is the correct way to handle multipart requests?

This code may inspire you
1) JAXRS App setup
import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
#ApplicationPath("demo")
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
packages("com.mycompany.demo").register(MultiPartFeature.class); // <= here!
}
}
2) JAXRS service
#POST
#Path("/upload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadImage(
#FormDataParam("file") InputStream data,
#FormDataParam("file") FormDataContentDisposition fileInfo) {
...
}

Related

Create a REST API to upload Multipart file data in spring boot

I have created a rest API to accept MULTIPART_FORM_DATA as below. But once I hit the service using Postman, I am getting HTTP Status 415 – Unsupported Media Type exception
#POST
#Path("/fileupload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.APPLICATION_JSON)
public String uploadfile(#RequestParam(value = "file") MultipartFile file) {
System.out.println(file.getName());
return "Success String";
}
What is wrong here? To consume MediaType.MULTIPART_FORM_DATA, do I need to make any modifications?
In Postman I have attached a text file in the BODY and hit the endpoint. The content type is set as "multipart/form-data"
Seems like you are confused with Spring rest API with Rest easy implementation.
In Resteasy,
Normal way to handle uploaded file is via
MultipartFormDataInput or Map uploaded file to a
POJO class via #MultipartForm
https://www.mkyong.com/webservices/jax-rs/file-upload-example-in-resteasy/
How to POST a multipart/form data with files programatically in a REST API
If you want to use spring rest approach, refer here
Multipart File upload Spring Boot
Have a look on below tutorial on uploading file in spring boot
https://devkonline.com/tutorials/content/ANGULAR-8-SPRING-BOOT-FILE-UPLOAD
You have probably imported different annotations.
Try it this way
import org.springframework.web.bind.annotation.*;
import static org.springframework.http.MediaType.*;
#PostMapping(value = "/fileupload", consumes = MULTIPART_FORM_DATA_VALUE, produces = APPLICATION_JSON_VALUE)
public String uploadfile(#RequestParam(value = "file") MultipartFile file) {
System.out.println(file.getName());
return "Success String";
}

How to use Jersey's internal routing mechanism to extract a class/method reference?

I have a Jersey 1.8 application running. Jersey is running as a Servlet.
I need to write a servlet filter that given a plain request/response, is able to figure out which REST resource/method will respond to the request and extract values from annotations.
For example, imagine I have the following resource:
#Path("/foo")
#MyAnnotation("hello")
public class FooResource {
#GET
#Path("/bar")
#MyOtherAnnotation("world")
public Response bar(){
...
}
}
When a request GET /foo/bar comes in, I need my servlet filter to be able to extract the values "hello" and "world" from MyAnnotation and MyOtherAnnotation before Jersey's own servlet processes the request.
This filter logic should be able to work for all requests and all resources registered.
Is there a way to access Jersey's internal routing mechanism to obtain a class/method reference where Jersey will dispatch the request?
I'm open to other suggestions as well, but ideally nothing like trying to hack my own routing mechanism by reading the #Path annotations myself.
#Provider
#Priority(Priorities.AUTHORIZATION)
public class MyFilter implements ContainerRequestFilter
#Context // request scoped proxy
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
if (resourceInfo.getResourceClass().isAnnotationPresent(MyAnnotationion.class) ||
resourceInfo.getResourceMethod().isAnnotationPresent(MyOtherAnnotation.class)) {
to register the filter use
bind(AuthFilter.class).to(ContainerRequestFilter.class).in(Singleton.class);

How to create APIs that accept file in the request object using jersey?

I'm creating apis that needs to accept a file and other informations which will be sent in a createAppRequest. What should I need to do to my apis to be able to let the user upload a file through the apis.
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response createApp(CreateAppRequest){
// save app to db
}
Request class:
public class CreateAppRequest{
// Other fields like name, createDate
#JsonProperty("file")
#Property("file")
private byte [] file;
public byte[] getFile() {
return file;
}
public void setFile(byte[] file) {
this.file = file;
}
}
I'll assume you're using the latest jersey release (2.7).
First you need to enable the MultiPart support in Jersey by adding the following to your pom.xml (if you are using maven, if not add the dependency to your project the same way you have added jersey):
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>2.7</version>
</dependency>
MultiPart is a Jersey Feature (such as the Jackson feature for example) and this means you will have to register it with both your client (if you have one) and your server apps.
Client side example (optional):
final Client client = ClientBuilder.newBuilder()
.register(MultiPartFeature.class)
.build();
Server side example:
final Application application = new ResourceConfig()
.packages("your.root.package.here")
.register(MultiPartFeature.class)
Once you've done all of the above you can define your post method like:
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA_TYPE)
public Response createApp(
#DefaultValue("true") #FormDataParam("enabled") boolean enabled,
#FormDataParam("data") FileData bean,
#FormDataParam("file") InputStream file,
#FormDataParam("file") FormDataContentDisposition fileDisposition) {
// your code here
}
For more information and examples take a look at the official jersey docs - https://jersey.java.net/documentation/latest/user-guide.html#multipart
However if you find this whole procedure too complicated you can always put your file in the request body as application/octet-stream and then read it in your post method with a MessageBodyReader<T>. If you are not sure what all these mean, or how to use them, again, check the jersey docs :)

Injecting contextual data in Resteasy tests with Netty

I'm trying to test a resource with Resteasy using an embedded Netty instance as described in the Resteasy Docs.
Injecting path parameters and query parameters works like a charm but then I tried to test a resource that injects HttpServletRequest and HttpServletResponse from the context like this:
#GET
#Path("/")
public void example(#Context HttpServletResponse response,
#Context HttpServletRequest request) { ... }
Resteasy cannot find HttpServletRequestin the context and throws the following exception:
5105 [r #1] DEB o.j.resteasy.core.SynchronousDispatcher - PathInfo: /auth
5201 [r #1] ERR c.s.f.v.s.r.e.ApplicationExceptionMapper - Unhandled application exception: Unable to find contextual data of type: javax.servlet.http.HttpServletRequest
org.jboss.resteasy.spi.LoggableFailure: Unable to find contextual data of type: javax.servlet.http.HttpServletRequest
I tried putting mock versions of request and response in the context as suggested in RESTEasy Mock vs. Exception Mapper vs. Context but it does not work either as the contextual data is a ThreadLocal and Netty spawns a new thread for each request.
Any ideas on how to solve this?
What worked in my case was injecting a org.jboss.seam.mock.HttpServletRequest, since I am using seam in my application. You should try some mock framework like spring.test or mockito.
Here is how my code looks like:
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockDispatcherFactory;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.jboss.resteasy.plugins.server.resourcefactory.POJOResourceFactory;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.seam.mock.MockHttpServletRequest;
import org.jboss.seam.mock.DBUnitSeamTest;
public class Test extends DBUnitSeamTest{
#Test
public void test() throws Exception {
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
POJOResourceFactory noDefaults = new POJOResourceFactory(ClasstoBeTested.class); dispatcher.getRegistry().addResourceFactory(noDefaults);
MockHttpRequest request = MockHttpRequest.get("/serviceToBeTested/1961");
MockHttpResponse response = new MockHttpResponse();
HttpServletRequest servletRequest = new MockHttpServletRequest(getSession());
ResteasyProviderFactory.getContextDataMap().put(HttpServletRequest.class, servletRequest);
dispatcher.invoke(request, response);
Assert.assertEquals(HttpServletResponse.SC_OK, response.getStatus());
Assert.assertTrue(response.getContentAsString().contains("1961"));
}
}
I just got hit by this again on another project and decided to investigate once more.
The issue is that in a mock request with Netty, there is no HttpServletRequest available. If you look into the sources of NettyJaxrsServerand related classes, Reasteasy uses its own abstraction for http requests that do not implement HttpServletRequest.
If I change my implementation to use these abstractions, I can access request and response in my resource.
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse;
#GET
#Path("/")
public void example(#Context HttpResponse response,
#Context HttpRequest request) { ... }
This is not perfect, because it makes my resources depend on Resteasy interfaces but I decided to go with it for now to support mock testing of multipart data.

How to set the charset with JAX-RS?

How can I set the charset with JAX-RS? I've tried #Produces("text/html; charset=UTF-8") but that was ignored and only text/html was send with the HTTP header. I want to set the charset within a MessageBodyWriter, but don't want to extract the media type by analysing the #Produces annotation via reflection by myself.
As Daemon pointed out in a comment, the latest versions of JAX-RS (including the stable version as of September 2012) now do support the #Produces syntax. So you can just use:
#Produces("text/html; charset=UTF-8")
Just to keep it up to date. Not sure whether this was supported in older versions of Jersey, but definitely if you decide to use ResponseBuilder.header(...) method you can use MediaType method withCharset(). Like this:
return Response.status(Status.OK)
.entity(result)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_TYPE.withCharset("utf-8"))
.build());
It is also possible to use ResponseBuilder.header(...) method to set the content type with the charset. See below for a code sample (using JAX-RS 1.1.1, CXF 2.3.1).
final Response myResponse = Response.status(Response.Status.BAD_REQUEST)
.entity("La requête n'est pas correcte.\n ...")
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN+"; charset=ISO-8859-15" )
.build();
If you want to do this in a JAX-RS implementation neutral way, you may be able to reset the Content-Type in the MessageBodyWriter. Something like:
public void writeTo(Object obj,
Class<?> cls,
Type type,
Annotation[] annotations,
MediaType mt,
MultivaluedMap<String, Object> responseHttpHeaders,
OutputStream stream) throws IOException {
responseHttpHeaders.putSingle(javax.ws.rs.core.HttpHeaders.CONTENT_TYPE, mt.toString() + ";charset=UTF-8");
}
If you have different character sets besides UTF-8 per resource method, you may want to create a custom annotation and add it to each resource method. Then, try to use the annotations parameter in the writeTo() method.
Just FYI, Apache Wink supports the usage of charset and other attributes on media types. I hope that future JAX-RS spec revisions makes this easier.
First setup #Produces annotation on your resource class methods.
Then in MessageBodyWriter of your returned type, you can do this in writeTo() method:
response.setContentType(mediaType.toString);
Remark: You can inject response in your writer by:
#Context
protected HttpServletResponse response;
What I do is to get an instance of the servlet response object:
protected #Context HttpServletResponse response;
And then set the character encoding to utf-8:
response.setCharacterEncoding("utf-8");
That works for me.
If using RESTEasy you can register an Inteceptor:
import org.jboss.resteasy.annotations.interception.ServerInterceptor;
import org.jboss.resteasy.core.ResourceMethodInvoker;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.interception.PreProcessInterceptor;
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.Provider;
#Provider
#ServerInterceptor
public class ContentTypeSetter implements PreProcessInterceptor {
#Override
public ServerResponse preProcess(HttpRequest request, ResourceMethodInvoker resourceMethodInvoker) throws Failure, WebApplicationException {
request.setAttribute(InputPart.DEFAULT_CONTENT_TYPE_PROPERTY, "*/*; charset=UTF-8");
return null;
}
}
Note: If you manually set a #Produces it overrides the ContentType set by this interceptor. If you do that, set the charset in #Produces

Categories