jersey-multipart-upload-core-feature
Introduction
Alternative Multipart implementation for Jax-RS Jersey 2.x. This implementation is largely inspired from the current Jersey 2.x implementation, with the following differences:
- SERVER SIDE
- Request parsing:
- A global request size limit can be set, and a
413 Entity too largeexception is thrown. - Use of Apache commons-fileupload for MIME parts parsing in requests
- MIME parts parsing is customisable, and you can provide your own implementation (see below)
- A global request size limit can be set, and a
- MIME Parts storing
- storing each MIME parts is customisable, and you can provide your own implementation (see below)
- By default, all form fields are stored in memory if their content size is below a thershold (10kB) or in a temporary file, and all attachments are stored in temporary files on the file system. Temporary files are kept for the whole duration of the request, and are deleted after the response is finished. You may change this behaviour (see below).
- Value resolvers
- This implementation deals more nicely with collection of parameters, and can use MessageBodyWorkers of single object for collection of objects.
- More control on the annotation
@FormDataParam: filter on the field: type, content-type, or to map a content-type to another.
- Request parsing:
- CLIENT SIDE
- Custom
ParamConvertersare taken into account to serialize the response.
- Custom
Quick-start
in your pom.xml:
<dependency>
<artifactId>jersey-multipart-upload-core-feature</artifactId>
<groupId>io.github.archytas-it</groupId>
<version>0.0.4</version>
</dependency>
It is also recommended to remove the Jersey multipart official implementation. There won't be any conflict. But, as some classes/annotations share the same names (but not located in same packages) it will be easier for the developer to import the right one.
Server side
In your resource config:
public class JerseyApplication extends ResourceConfig {
public JerseyApplication() {
register(MultipartFeature.class);
[...]
}
}
In your resource:
@Path("/upload")
public class MyResource {
@POST
@Consumes("multipart/*")
public void upload(
@FormDataParam("field") String field,
@FormDataParam("file") List<FormDataBodyPart> files) {
[...]
}
}
You can use collections to get several parts named identically. Supported collections are List, SortedSet and Set.
The typing of the parameter is important, and if a MessageBodyReader for the particular content-type, or an extractor (via a ParamConverter for instance) exists, the field will be instanciated as desired.
It is also possible to use the FormDataBodyPart or any sub-class (even your own implementation with its storage provider). If a sub-class is used, only the ones that are instances of the desired type will be returned.
Be careful if you use a type, or an invalid FormDataBodyPart sub-class, you will get some null values.
Client side
Client client = ClientBuilder.newClient(new ClientConfig().register(MultipartFeature.class));
MultiPart multiPart = MultiPart.formDataMultiPart()
.add(new FormDataEntityBodyPart("textfield", Entity.text("foo bar")))
.add(new FormDataEntityBodyPart("jsonfield", Entity.json(myJsonObject)))
.add(new FormDataFileBodyPart("file", new File("foo.txt")))
.add(new FormDataFileBodyPart("file", new File("bar.txt")));
Response response = client.target("http://myserver/upload").request().post(multiPart.entity());
You can fill parts of a MultiPart object with any objects that inherits from BodyPart, which includes:
FormDataBodyPartabstract base class for any form-data field.FormDataStringBodyPartfield as a StringFormDataEntityBodyPartcan be used to provide a JAXRS entity, to inject any objectFormDataStreamBodyPartbody part with free input stream source contentFormDataFileBodyPartfield with a file as source contentMultiPartto nest a multipart inside (multipart/mixed)- any subclass you choose to implement
When using FormDataEntityBodyPart, a MessageBodyWriter for this type and content-type, or ParamConverter must exists.
Global configuration
- Instanciate a
MultiPartConfigclass - Register in the resource configuration with a
ContextResolver<MultiPartConfig>
Available parameters:
| Parameter | Default value | Description |
|---|---|---|
| tempFileDirectory | value of sytem property java.io.tmpdir |
place where temporary files are created if needed. |
| tempFilePrefix | MULTIPART_ |
temporary files prefix |
| tempFileSuffix | null | temporary files prefix |
| memorySizeLimit | 10240 bytes (10k) | Used by default form field provider to indicate the threshold to switch between in memory or file storage. Ignored if < 0 |
| defaultCharset | ISO-8859-1 |
default content encoding of incoming parts without charset specification |
| requestSizeLimit | -1 | maximum accepted whole request size, will execute the action defined in the following parameter if encountered. Ignored if < 0 |
| requestSizeLimitAction | throw a 413 Entity too large exception |
Action to take if the request size exceeds the defined value |
| cleanResourceMode | ALWAYS |
Stored resources have a cleanup() method. This parameter tells how resources are cleans. ALWAYS: resources are cleaned after the end of the request, ON_ERROR: resources are only cleaned if the request did not respond a statuc in the 2xx range. NEVER: never cleaned. |
Annotation @FormDataParam
The annotation can be placed on a resource method parameter, or inside a @BeanParam
option formDataType()
Can filter a field type: BOTH, FORMFIELD or ATTACHMENT. A field is considered to be an attachement if it has a filename.
option mapContentTypeAs()
For complex data transformation, allows to map some content types to others, to use some specific MessageBodyReader For example, if you may want to consider every text/plain content type to be parsed as JSON, if you have a json media provider.
@FormDataParam(value = "field", mapContentTypeAs = {
@Map(to = MediaType.APPLICATION_JSON)
}) List<MyJsonContent> content
You can set a from= inside the @Map to restrict the mapping. By default from=*/*.
The only first declared that matches the content-type will be executed, they are not called recursively.
option filterContentType()
To be documented
Low-level customizations
It is possible to implement your own:
- request multipart parser class
- parts storage class
The parser is in charge to implement an "iterator" which provides data in an internal model StreamingPart containing all metadatas of the part, and an InputStream providing the part content.
The second one is in charge to consume the content of the StreamingPart and to provide an instance of FormDataBodyPart
You can set one or the other during the feature registration:
public class JerseyApplication extends ResourceConfig {
public JerseyApplication() {
register(new MultipartFeature()
.withRequestParser(MyRequestParser.inst())
.withBodyPartProvider(MyBodyPartProvider.inst()));
[...]
}
}
Request parser
Class to implement: IRequestParser
The default implementation uses Apache commons-fileupload
If you implement yours, pay attention to provide parts as they are sequentially read in a streaming way. This component SHOULD NOT store temporarly the whole part contents in memory or in the file system, but only stream parts as they are received in the request input stream. The way each parts are stored is the responsability of the parts storage implementation of IFormDataBodyPartProvider (see below).
To be more documented
Parts storage
Class to implement: IFormDataBodyPartProvider
The default implementation is:
- Form field part
- if size below value specified in the memorySizeLimit parameter (10kB by default), part stored in memory
- otherwise, part is stored in a temporary file.
- Attachment part is always stored in a temporary file.
Every implementation is in charge to provide an object that is inherited from FormDataBodyParam. The is currenctly 4 implementations of IFormDataBodyPartProvider:
FieldOrAttachementBodyPartProviderdelegates the job to two different ones, depending if the part is a form-field or not (attachment)MemoryLimitBodyPartProviderdelegates the job to two different ones, depending of the size of the stored contentFormDataFileBodyPartProviderstores the content in aStringin aFormDataStringBodyPartFormDataStringBodyPartProviderstores the content in a temporary file on the filesystem in aFormDataFileBodyPart
You can write your own FormDataBodyPart sub-class with your own IFormDataBodyPartProvider.
An
AmazonS3BodyPartand its provider could be implemented
To be more documented