JBoss.orgCommunity Documentation
RESTEasy has rich support for the "multipart/*" and "multipart/form-data" mime types. The multipart mime format is used to pass lists of content bodies. Multiple content bodies are embedded in one message. "multipart/form-data" is often found in web application HTML Form documents and is generally used to upload files. The form-data format is the same as other multipart formats, except that each inlined piece of content has a name associated with it.
RESTEasy provides a custom API for reading and writing multipart types as well as marshalling arbitrary List (for any multipart type) and Map (multipart/form-data only) objects
Classes MultipartInput
and MultipartOutput
provides read and write
support for mime type "multipart/mixed" messages respectively. They provide for
multiple part messages, in which one or more different sets of data are combined in a single body.
MultipartRelatedInput
and MultipartRelatedOutput
classes
provide read and write
support for mime type "multipart/related" messages. These are messages that contain
multiple body parts that are inter-related.
MultipartFormDataInput
and MultipartFormDataOutput
classes
provide read and write
support for mine type "multipart/form-data". This type is used when returning
a set of values as the the result of a user filling out a form or for uploading files.
MultipartOutput
provides a set of addPart methods for
registering message content
and specifying special marshalling requirements. In all cases the addPart
methods require
an input parameter, Object and a MediaType that declares the mime type of the object.
Sometimes you may have an object in which marshalling is sensitive to generic type metadata.
In such cases, use an addPart method in which you declare the GenericType of the entity Object.
Perhaps a file will be passed as content and it will require UTF-8 encoding.
Setting input parameter, utf8Encode to true
will indicate to RESTEasy to process the
filename according to the character set and language encoding rules of rfc5987
.
This flag is only processed when mime type "multipart/form-data" is specified.
MultipartOutput
automatically generates a unique message boundary identifier
when it is created. Method setBoundary is provided in case you wish to declare
a different identifier.
public class MultipartOutput { public OutputPart addPart(Object entity, MediaType mediaType); public OutputPart addPart(Object entity, MediaType mediaType, String filename); public OutputPart addPart(Object entity, MediaType mediaType, String filename, boolean utf8Encode); public OutputPart addPart(Object entity, GenericType<?> type, MediaType mediaType); public OutputPart addPart(Object entity, GenericType<?> type, MediaType mediaType, String filename); public OutputPart addPart(Object entity, GenericType<?> type, MediaType mediaType, String filename, boolean utf8Encode); public OutputPart addPart(Object entity, Class<?> type, Type genericType, MediaType mediaType); public OutputPart addPart(Object entity, Class<?> type, Type genericType, MediaType mediaType, String filename); public OutputPart addPart(Object entity, Class<?> type, Type genericType, MediaType mediaType, String filename, boolean utf8Encode); public List<OutputPart> getParts(); public String getBoundary(); public void setBoundary(String boundary); }
Each message part registered with MultipartOutput
is represented by an
OutputPart
object. Class MultipartOutput
generates an
OutputPart
object for each addPart method call.
public class OutputPart { public OutputPart(final Object entity, final Class<?> type, final Type genericType, final MediaType mediaType); public OutputPart(final Object entity, final Class<?> type, final Type genericType, final MediaType mediaType, final String filename); public OutputPart(final Object entity, final Class<?> type, final Type genericType, final MediaType mediaType, final String filename, final boolean utf8Encode); public MultivaluedMap<String, Object> getHeaders(); public Object getEntity(); public Class<?> getType(); public Type getGenericType(); public MediaType getMediaType(); public String getFilename(); public boolean isUtf8Encode(); }
MultipartInput
and InputPart
are interface
classes that provide
access to multipart/mixed message data. RESTEasy provides an implementation
of these classes. They perform the work to retrieve message data.
package org.jboss.resteasy.plugins.providers.multipart; import java.util.List; public interface MultipartInput { List<InputPart> getParts(); String getPreamble(); /** * Call this method to delete any temporary files created from unmarshalling * this multipart message * Otherwise they will be deleted on Garbage Collection or JVM exit. */ void close(); }
package org.jboss.resteasy.plugins.providers.multipart; import javax.ws.rs.core.GenericType; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import java.io.IOException; import java.lang.reflect.Type; /** * Represents one part of a multipart message. */ public interface InputPart { /** * If no content-type header is sent in a multipart message part * "text/plain; charset=ISO-8859-1" is assumed. * * This can be overwritten by setting a different String value in * {@link org.jboss.resteasy.spi.HttpRequest#setAttribute(String, Object)} * with this ("resteasy.provider.multipart.inputpart.defaultContentType") * String as key. It should be done in a * {@link javax.ws.rs.container.ContainerRequestFilter}. */ String DEFAULT_CONTENT_TYPE_PROPERTY = "resteasy.provider.multipart.inputpart.defaultContentType"; /** * If there is a content-type header without a charset parameter, * charset=US-ASCII is assumed. * * This can be overwritten by setting a different String value in * {@link org.jboss.resteasy.spi.HttpRequest#setAttribute(String, Object)} * with this ("resteasy.provider.multipart.inputpart.defaultCharset") * String as key. It should be done in a * {@link javax.ws.rs.container.ContainerRequestFilter}. */ String DEFAULT_CHARSET_PROPERTY = "resteasy.provider.multipart.inputpart.defaultCharset"; /** * @return headers of this part */ MultivaluedMap<String, String> getHeaders(); String getBodyAsString() throws IOException; <T> T getBody(Class<T> type, Type genericType) throws IOException; <T> T getBody(GenericType<T> type) throws IOException; /** * @return "Content-Type" of this part */ MediaType getMediaType(); /** * @return true if the Content-Type was resolved from the message, false if * it was resolved from the server default */ boolean isContentTypeFromMessage(); /** * Change the media type of the body part before you extract it. * Useful for specifying a charset. * @param mediaType media type */ void setMediaType(MediaType mediaType); }}
The following example shows how to read and write a simple multipart/mixed message.
The data to be transfered is a very simple class, Soup.
package org.jboss.resteasy.test.providers.multipart.resource; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlElement; @XmlRootElement(name = "soup") @XmlAccessorType(XmlAccessType.FIELD) public class Soup { @XmlElement private String id; public Soup(){} public Soup(final String id){this.id = id;} public String getId(){return id;} }
This code fragment creates a multipart/mixed message passing
Soup information using class, MultipartOutput
.
MultipartOutput multipartOutput = new MultipartOutput(); multipartOutput.addPart(new Soup("Chicken Noodle"), MediaType.APPLICATION_XML_TYPE); multipartOutput.addPart(new Soup("Vegetable"), MediaType.APPLICATION_XML_TYPE); multipartOutput.addPart("Granny's Soups", MediaType.TEXT_PLAIN_TYPE);
This code fragment uses class MultipartInput
to extract the Soup information provided by multipartOutput
above.
// MultipartInput multipartInput, the entity returned in the client in a // Response object or the input value of an endpoint method parameter. for (InputPart inputPart : multipartInput.getParts()) { if (MediaType.APPLICATION_XML_TYPE.equals(inputPart.getMediaType())) { Soup c = inputPart.getBody(Soup.class, null); String name = c.getId(); } else { String s = inputPart.getBody(String.class, null); } }
Returning a multipart/mixed message from an endpoint can be done
in two ways. MultipartOutput
can be returned as the method's
return object or as an entity in a Response
object.
@GET @Path("soups/obj") @Produces("multipart/mixed") public MultipartOutput soupsObj() { return multipartOutput; } @GET @Path("soups/resp") @Produces("multipart/mixed") public Response soupsResp() { return Response.ok(multipartOutput, MediaType.valueOf("multipart/mixed")) .build(); }
There is no difference in the way a client retrieves the message from the endpoint. It is done as follows.
ResteasyClient client = (ResteasyClient)ClientBuilder.newClient(); ResteasyWebTarget target = client.target(THE_URL); Response response = target.request().get(); MultipartInput multipartInput = response.readEntity(MultipartInput.class); for (InputPart inputPart : multipartInput.getParts()) { if (MediaType.APPLICATION_XML_TYPE.equals(inputPart.getMediaType())) { Soup c = inputPart.getBody(Soup.class, null); String name = c.getId(); } else { String s = inputPart.getBody(String.class, null); } } client.close();
A client sends the message, multipartOutput, to an endpoint as an entity object in an HTTP method call in this code fragment.
ResteasyClient client = (ResteasyClient)ClientBuilder.newClient(); ResteasyWebTarget target = client.target(SOME_URL + "/register/soups"); Entity<MultipartOutput> entity = Entity.entity(multipartOutput, new MediaType("multipart", "mixed")); Response response = target.request().post(entity);
Here is the endpoint receiving the message and extracting the contents.
@POST @Consumes("multipart/mixed") @Path("register/soups") public void registerSoups(MultipartInput multipartInput) throws IOException { for (InputPart inputPart : multipartInput.getParts()) { if (MediaType.APPLICATION_XML_TYPE.equals(inputPart.getMediaType())) { Soup c = inputPart.getBody(Soup.class, null); String name = c.getId(); } else { String s = inputPart.getBody(String.class, null); } } }
This example shows how to read and write a multipart/mixed message
whose content consists of a generic type, in this case a List<Soup>.
The MultipartOutput
and MultipartIntput
methods
that use GenericType
parameters are used.
The multipart/mixed message is created using MultipartOutput
as follows.
MultipartOutput multipartOutput = new MultipartOutput(); List<Soup> soupList = new ArrayList<Soup>(); soupList.add(new Soup("Chicken Noodle")); soupList.add(new Soup("Vegetable")); multipartOutput.addPart(soupList, new GenericType<List<Soup>>(){}, MediaType.APPLICATION_XML_TYPE ); multipartOutput.addPart("Granny's Soups", MediaType.TEXT_PLAIN_TYPE);
The message data is extracted with MultipartInput
.
Note there are two MultipartInput
getBody methods that
can be used to retrieve data specifying GenericType
.
This code fragment uses the second one but shows the first one in comments.
<T> T getBody(Class<T> type, Type genericType) throws IOException; <T> T getBody(GenericType<T> type) throws IOException;
// MultipartInput multipartInput, the entity returned in the client in a // Response object or the input value of an endpoint method parameter. GenericType<List<Soup>> gType = new GenericType<List<Soup>>(){}; for (InputPart inputPart : multipartInput.getParts()) { if (MediaType.APPLICATION_XML_TYPE.equals(inputPart.getMediaType())) { List<Soup> c = inputPart.getBody(gType); // List<Soup> c = inputPart.getBody(gType.getRawType(), gType.getType()); } else { String s = inputPart.getBody(String.class, null);; } }
When a set of message parts are uniform they do not need to be
written using MultipartOutput
or read with
MultipartInput
. They can be sent and received as a
List
.
RESTEasy performs the necessary work to read and write the message data.
For this example the data to be transmitted is class,
ContextProvidersCustomer
package org.jboss.resteasy.test.providers.multipart.resource; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name = "customer") @XmlAccessorType(XmlAccessType.FIELD) public class ContextProvidersCustomer { @XmlElement private String name; public ContextProvidersCustomer() { } public ContextProvidersCustomer(final String name) { this.name = name; } public String getName() { return name;} }
In this code fragment the client creates and sends of list
ContextProvidersCustomers
.
List<ContextProvidersCustomer> customers = new ArrayList<ContextProvidersCustomer>(); customers.add(new ContextProvidersCustomer("Bill")); customers.add(new ContextProvidersCustomer("Bob")); Entity<ContextProvidersCustomer> entity = Entity.entity(customers, new MediaType("multipart", "mixed")); Client client = ClientBuilder.newClient(); WebTarget target = client.target(SOME_URL); Response response = target.request().post(entity);
The endpoint receives the list, alters the contents and returns a new list.
@POST @Consumes("multipart/mixed") @Produces(MediaType.APPLICATION_XML) @Path("post/list") public List<ContextProvidersName> postList( List<ContextProvidersCustomer> customers) throws IOException { List<ContextProvidersName> names = new ArrayList<ContextProvidersName>(); for (ContextProvidersCustomer customer : customers) { names.add(new ContextProvidersName("Hello " + customer.getName())); } return names; }
The client receives the altered message data and processes it.
Response response = target.request().post(entity); List<ContextProvidersCustomer> rtnList = response.readEntity(new GenericType<List<ContextProvidersCustomer>>(){}); : :
The Multipart/Related mime type is intended for compound objects consisting of several inter-related body parts, (RFC2387). There is a root or start part. All other parts are referenced from the root part. Each part has a unique id. The type and the id of the start part is presented in parameters in the message content-type header.
RESTEasy provides class MultipartRelatedOutput
to assist
the user in specifying the required information and generating a properly
formatted message. MultipartRelatedOutput
is a subclass of
MultipartOutput
.
package org.jboss.resteasy.plugins.providers.multipart; import javax.ws.rs.core.MediaType; public class MultipartRelatedOutput extends MultipartOutput { private String startInfo; /** * The part used as the root. */ public OutputPart getRootPart(); /** * entity object representing the part's body * mediaType Content-Type of the part * contentId Content-ID to be used as identification for the current * part, optional, if null one will be generated * contentTransferEncoding * value used for the Content-Transfer-Encoding header * field of the part. It's optional, if you don't want to set * this pass null. Example values are: "7bit", * "quoted-printable", "base64", "8bit", "binary" */ public OutputPart addPart(Object entity, MediaType mediaType, String contentId, String contentTransferEncoding); /** * start-info parameter of the Content-Type. An optional parameter. * As described in RFC2387, section 3.3. The Start-Info Parameter */ public String getStartInfo(); }
MultipartRelatedInput
is an interface class that provides
access to multipart/related message data. It is a subclass of
MultipartInput
. RESTEasy provides an implementation of
this class. It performs the work to retrieve message data.
package org.jboss.resteasy.plugins.providers.multipart; import javax.ws.rs.core.MediaType; public class MultipartRelatedOutput extends MultipartOutput { private String startInfo; /** * The part used as the root. */ public OutputPart getRootPart(); /** * entity object representing the part's body * mediaType Content-Type of the part * contentId Content-ID to be used as identification for the current * part, optional, if null one will be generated * contentTransferEncoding * value used for the Content-Transfer-Encoding header * field of the part. It's optional, if you don't want to set * this pass null. Example values are: "7bit", * "quoted-printable", "base64", "8bit", "binary" */ public OutputPart addPart(Object entity, MediaType mediaType, String contentId, String contentTransferEncoding); /** * start-info parameter of the Content-Type. An optional parameter. * As described in RFC2387, section 3.3. The Start-Info Parameter */ public String getStartInfo(); }
The client in this example creates a multipart/related message, POSTs it to the endpoint and processes the multipart/related message returned by the endpoint.
MultipartRelatedOutput mRelatedOutput = new MultipartRelatedOutput(); mRelatedOutput.setStartInfo("text/html"); mRelatedOutput.addPart("Bill", new MediaType("image", "png"), "bill", "binary"); mRelatedOutput.addPart("Bob", new MediaType("image", "png"), "bob", "binary"); Entity<MultipartRelatedOutput> entity = Entity.entity(mRelatedOutput, new MediaType("multipart", "related")); Client client = ClientBuilder.newClient(); WebTarget target = client.target(SOME_URL); Response response = target.request().post(entity); MultipartRelatedInput result = response.readEntity( MultipartRelatedInput.class); Map<String, InputPart> map = result.getRelatedMap(); Set<String> keys = map.keySet(); boolean a = keys.contains("Bill"); boolean b = keys.contains("Bob"); for (InputPart inputPart : map.values()) { String alterName = inputPart.getBody(String.class, null); }
Here is the endpoint the client above is calling.
@POST @Consumes("multipart/related") @Produces("multipart/related") @Path("post/related") public MultipartRelatedOutput postRelated(MultipartRelatedInput input) throws IOException { MultipartRelatedOutput rtnMRelatedOutput = new MultipartRelatedOutput(); rtnMRelatedOutput.setStartInfo("text/html"); for (Iterator<InputPart> it = input.getParts().iterator(); it.hasNext(); ) { InputPart part = it.next(); String name = part.getBody(String.class, null); rtnMRelatedOutput.addPart("Hello " + name, new MediaType("image", "png"), name, null); } return rtnMRelatedOutput; }
RESTEasy supports XOP messages packaged as multipart/related messages (http://www.w3.org/TR/xop10/). A JAXB annotated POJO that also holds binary content can be transmitted using XOP. XOP allows the binary data to skip going through the XML serializer because binary data can be serialized differently from text and this can result in faster transport time.
RESTEasy requires annotation @XopWithMultipartRelated to be placed on any endpoint method that returns an object that is to be to be processed with XOP and on any endpoint input parameter that is to be processed by XOP.
RESTEasy highly recommends, if you know the exact mime type of the POJO's binary data, tag the field with annotation @XmlMimeType. This annotation tells JAXB the mime type of the binary content, however this is not required in order to do XOP packaging.
The data to be transmitted is class, ContextProvidersXop
.
Note that field bytes
is identified as an application/octet-stream
mime type using annotation @XmlMimeType
package org.jboss.resteasy.test.providers.multipart.resource; import javax.ws.rs.core.MediaType; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlMimeType; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class ContextProvidersXop { @XmlMimeType(MediaType.APPLICATION_OCTET_STREAM) private byte[] bytes; public ContextProvidersXop(final byte[] bytes) { this.bytes = bytes; } public ContextProvidersXop() {} public byte[] getBytes() {return bytes;} public void setBytes(byte[] bytes) {this.bytes = bytes;} }
The endpoint returns an instance of ContextProvidersXop
.
Note annotation @XopWithMultipartRelated declared on the method because
we want the return object to use XOP packaging.
@GET @Path("get/xop") @Produces("multipart/related") @XopWithMultipartRelated public ContextProvidersXop getXop() { return new ContextProvidersXop("goodbye world".getBytes()); }
The client retreives the data as follows
Client client = ClientBuilder.newClient(); WebTarget target = client.target(SOME_URL); Response response = target.request().get(); ContextProvidersXo entity = response.readEntity(ContextProvidersXop.class); client.close();
Here is an endpoint that has an input parameter that is transmitted as an XOP package. Note the @XopWithMultipartRelated annotation on input parameter xop.
@POST @Path("post/xop") @Consumes("multipart/related") public String postXop(@XopWithMultipartRelated ContextProvidersXop xop) { return new String(xop.getBytes()); }
This client is sending the data to the endpoint above.
ContextProvidersXop xop = new ContextProvidersXop("hello world".getBytes()); Entity<ContextProvidersXop> entity = Entity.entity(xop, new MediaType("multipart", "related")); Client client = ClientBuilder.newClient(); WebTarget target = client.target(SOME_URL); Response response = target.request().post(entity);
The MultiPart/Form-Data mime type is used in sending form data (rfc2388). It can include data generated by user input, information that is typed, or included from files that the user has selected. "multipart/form-data" is often found in web application HTML Form documents and is generally used to upload files. The form-data format is the same as other multi-part formats, except that each inlined piece of content has a name associated with it.
Form data consists of key/value pairs. RESTEasy provides class
MultipartFormDataOutput
to assist the user in specifying
the required information and generating a properly formatted message.
It is a subclass of MultipartOutput
. And as with
multipart/mixed data sometimes there may be marshalling which is
sensitive to generic type metadata, in those cases use the methods
containing input parameter GenericType.
package org.jboss.resteasy.plugins.providers.multipart; public class MultipartFormDataOutput extends MultipartOutput { public OutputPart addFormData(String key, Object entity, MediaType mediaType) public OutputPart addFormData(String key, Object entity, GenericType type, MediaType mediaType) public OutputPart addFormData(String key, Object entity, Class type, Type genericType, MediaType mediaType) public Map<String, OutputPart> getFormData() public Map<String, List<OutputPart>> getFormDataMap() }
MultipartFormDataInput
is an interface class that
provides access to multipart/form-data message data. It is a subclass
of MultipartInput
. RESTEasy provides an implementation
of this class. It performs the work to retrieve message data.
package org.jboss.resteasy.plugins.providers.multipart; import java.io.IOException; import java.lang.reflect.Type; import java.util.List; import java.util.Map; import javax.ws.rs.core.GenericType; public interface MultipartFormDataInput extends MultipartInput { /** * @return A parameter map containing a list of values per name. */ Map<String, List<InputPart>> getFormDataMap(); <T> T getFormDataPart(String key, Class<T> rawType, Type genericType) throws IOException; <T> T getFormDataPart(String key, GenericType<T> type) throws IOException; }
The following example show how to read and write a simple multipart/form-data message.
The multipart/mixed message is created on the clientside using the
MultipartFormDataOutput
object. One piece of form data
to be transfered is a very simple class, ContextProvidersName
.
package org.jboss.resteasy.test.providers.multipart.resource; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name = "name") @XmlAccessorType(XmlAccessType.FIELD) public class ContextProvidersName { @XmlElement private String name; public ContextProvidersName() {} public ContextProvidersName(final String name) {this.name = name;} public String getName() {return name;} }
The client creates and sends the message as follows:
MultipartFormDataOutput output = new MultipartFormDataOutput(); output.addFormData("bill", new ContextProvidersCustomer("Bill"), MediaType.APPLICATION_XML_TYPE); output.addFormData("bob", "Bob", MediaType.TEXT_PLAIN_TYPE); Entity<MultipartFormDataOutput> entity = Entity.entity(output, new MediaType("multipart", "related")); Client client = ClientBuilder.newClient(); WebTarget target = client.target(SOME_URL); Response response = target.request().post(entity);
The endpoint receives the message and processes it.
@POST @Consumes("multipart/form-data") @Produces(MediaType.APPLICATION_XML) @Path("post/form") public Response postForm(MultipartFormDataInput input) throws IOException { Map<String, List<InputPart>> map = input.getFormDataMap(); List<ContextProvidersName> names = new ArrayList<ContextProvidersName>(); for (Iterator<String> it = map.keySet().iterator(); it.hasNext(); ) { String key = it.next(); InputPart inputPart = map.get(key).iterator().next(); if (MediaType.APPLICATION_XML_TYPE.equals(inputPart.getMediaType())) { names.add(new ContextProvidersName(inputPart.getBody( ContextProvidersCustomer.class, null).getName())); } else { names.add(new ContextProvidersName(inputPart.getBody( String.class, null))); } } return Response.ok().build(); }
When the data of a multipart/form-data message is uniform it
does not need to be written in a MultipartFormDataOutput
object. It can be sent and received as a java.util.Map
object. RESTEasy performs the necessary work to read and write the
message data, however the Map object must declare the type it
is unmarshalling via the generic parameters in the Map type declaration.
Here is an example of a client creating and sending a multipart/form-data message.
Map<String, ContextProvidersCustomer> customers = new HashMap<String, ContextProvidersCustomer>(); customers.put("bill", new ContextProvidersCustomer("Bill")); customers.put("bob", new ContextProvidersCustomer("Bob")); Entity<Map<String, ContextProvidersCustomer>> entity = Entity.entity(customers, new MediaType("multipart", "form-data")); Client client = ClientBuilder.newClient(); WebTarget target = client.target(SOME_URL); Response response = target.request().post(entity)
This is the endpoint the client above is calling. It receives the message and processes it.
@POST @Consumes("multipart/form-data") @Produces(MediaType.APPLICATION_XML) @Path("post/map") public Response postMap(Map<String, ContextProvidersCustomer> customers) throws IOException { List<ContextProvidersName> names = new ArrayList<ContextProvidersName>(); for (Iterator<String> it = customers.keySet().iterator(); it.hasNext(); ) { String key = it.next(); ContextProvidersCustomer customer = customers.get(key); names.add(new ContextProvidersName(key + ":" + customer.getName())); } return Response.ok().build(); }
A java.util.Map
object representing a multipart/form-data
message can be returned from an endpoint as long as the message data
is uniform, however the endpoint method MUST be annotated with
@PartType which declares the media type of the Map entries and the
Map object must declare the type it is unmarshalling via the generic
parameters in the Map type declaration. RESTEasy requires this
information so it can generate the message properly.
Here is an example of an endpoint returning a Map of
ContextProvidersCustomer
to the client.
@GET @Produces("multipart/form-data") @PartType("application/xml") @Path("get/map") public Map<String, ContextProvidersCustomer> getMap() { Map<String, ContextProvidersCustomer> map = new HashMap<String, ContextProvidersCustomer>(); map.put("bill", new ContextProvidersCustomer("Bill")); map.put("bob", new ContextProvidersCustomer("Bob")); return map; }
The client would retrieve the data as follows.
Client client = ClientBuilder.newClient(); WebTarget target = client.target(SOME_URL); Response response = target.request().get(); MultipartFormDataInput entity = response.readEntity( MultipartFormDataInput.class); client.close(); ContextProvidersCustomer bill = entity.getFormDataPart("bill", ContextProvidersCustomer.class, null); ContextProvidersCustomer bob = entity.getFormDataPart("bob", ContextProvidersCustomer.class, null);
If you have an exact knowledge of your multipart/form-data packets,
you can map them to and from a POJO class using the annotation
@org.jboss.resteasy.annotations.providers.multipart.MultipartForm
and the JAX-RS @FormParam
annotation. Simply define a POJO with
at least a default constructor and annotate its fields and/or properties
with @FormParams
. These @FormParams
must
also be annotated with
@org.jboss.resteasy.annotations.providers.multipart.PartType
if you are doing output. For example:
public class CustomerProblemForm { @FormParam("customer") @PartType("application/xml") private Customer customer; @FormParam("problem") @PartType("text/plain") private String problem; public Customer getCustomer() { return customer; } public void setCustomer(Customer cust) { this.customer = cust; } public String getProblem() { return problem; } public void setProblem(String problem) { this.problem = problem; } }
After defining the POJO class you can use it to represent multipart/form-data. Here's
an example of sending a CustomerProblemForm
using the
RESTEasy client framework:
@Path("portal") public interface CustomerPortal { @Path("issues/{id}") @Consumes("multipart/form-data") @PUT public void putProblem(@MultipartForm CustomerProblemForm, @PathParam("id") int id) { CustomerPortal portal = ProxyFactory.create( CustomerPortal.class, "http://example.com"); CustomerProblemForm form = new CustomerProblemForm(); form.setCustomer(...); form.setProblem(...); portal.putProblem(form, 333); } }
Note that the @MultipartForm
annotation was used to tell RESTEasy
that the object has a @FormParam
and that it should be marshalled from that. You can also use the same
object to receive multipart data.
Here is an example of the server side counterpart of our customer portal.
@Path("portal") public class CustomerPortalServer { @Path("issues/{id}) @Consumes("multipart/form-data") @PUT public void putIssue(@MultipartForm CustoemrProblemForm, @PathParam("id") int id) { ... write to database... } }
In addition to the XML data format, JSON formatted data can be used to represent POJO classes. To achieve this goal, plug in a JSON provider into your project. For example, add the RESTEasy Jackson2 Provider into your project's dependency scope:
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jackson2-provider</artifactId> <version>${resteasy.ver}</version> </dependency>
Now you can write an ordinary POJO class, which Jackson2 will automatically serialize/deserialize into JSON format:
public class JsonUser { private String name; public JsonUser() {} public JsonUser(final String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
The resource class can be written like this:
import org.jboss.resteasy.annotations.providers.multipart.MultipartForm; import org.jboss.resteasy.annotations.providers.multipart.PartType; import javax.ws.rs.Consumes; import javax.ws.rs.FormParam; import javax.ws.rs.PUT; import javax.ws.rs.Path; @Path("/") public class JsonFormResource { public JsonFormResource() { } public static class Form { @FormParam("user") @PartType("application/json") private JsonUser user; public Form() { } public Form(final JsonUser user) { this.user = user; } public JsonUser getUser() { return user; } } @PUT @Path("form/class") @Consumes("multipart/form-data") public String putMultipartForm(@MultipartForm Form form) { return form.getUser().getName(); } }
As the code shown above, you can see the PartType of JsonUser is marked as "application/json", and it's included in the "@MultipartForm Form" class instance.
To send the request to the resource method, you need to send JSON formatted data that is corresponding with the JsonUser class. The easiest way to do this is to use a proxy class that has the same definition of the resource class. Here is the sample code of the proxy class that is corresponding with the JsonFormResource class:
import org.jboss.resteasy.annotations.providers.multipart.MultipartForm; import javax.ws.rs.Consumes; import javax.ws.rs.PUT; import javax.ws.rs.Path; @Path("/") public interface JsonForm { @PUT @Path("form/class") @Consumes("multipart/form-data") String putMultipartForm(@MultipartForm JsonFormResource.Form form); }
And then use the proxy class above to send the request to the resource method correctly. Here is the sample code:
ResteasyClient client = (ResteasyClient)ClientBuilder.newClient(); ... JsonForm proxy = client.target("your_request_url_address") .proxy(JsonForm.class); String name = proxy.putMultipartForm(new JsonFormResource .Form(new JsonUser("bill"))); ...
If your client side has the Jackson2 provider included, the request will be marshaled correctly. The JsonUser data will be converted into JSON format and sent to the server side. You can also use hand-crafted JSON data as your request and send it to server side, but you have to make sure the request data is in the correct form.
There are a lot of frameworks doing multipart parsing automatically
with the help of filters and interceptors, like
org.jboss.seam.web.MultipartFilter
in Seam and
org.springframework.web.multipart.MultipartResolver
in Spring,
however these incoming multipart request stream can be parsed only once.
RESTEasy users working with multipart should
make sure that nothing parses the stream before RESTEasy gets it.
By default if no Content-Type header is present in a part,
"text/plain; charset=us-ascii"
is used as the fallback.
This is the value defined by the MIME RFC. However some web clients,
like most, if not all, web browsers,
do not send Content-Type headers for all fields in a multipart/form-data
request. They send them only for the file parts. This can cause
character encoding and unmarshalling errors on the server side. To correct
this there is an option to define an other, non-rfc compliant fallback value.
This can be done dynamically per request with the PreProcessInterceptor
infrastructure of RESTEasy. In the following example we will set
"*/*; charset=UTF-8"
as the new default fallback:
import org.jboss.resteasy.plugins.providers.multipart.InputPart; @Provider @ServerInterceptor public class ContentTypeSetterPreProcessorInterceptor implements PreProcessInterceptor { public ServerResponse preProcess(HttpRequest request, ResourceMethod method) throws Failure, WebApplicationException { request.setAttribute(InputPart.DEFAULT_CONTENT_TYPE_PROPERTY, "*/*; charset=UTF-8"); return null; } }
Using attribute, InputPart.DEFAULT_CONTENT_TYPE_PROPERTY
and an interceptor enables the setting of a default Content-Type,
It is also possible to override the Content-Type by setting a
different media type with method InputPart.setMediaType()
.
Here is an example:
@POST @Path("query") @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.TEXT_PLAIN) public Response setMediaType(MultipartInput input) throws IOException { List<InputPart> parts = input.getParts(); InputPart part = parts.get(0); part.setMediaType(MediaType.valueOf("application/foo+xml")); String s = part.getBody(String.class, null); ... }
Sometimes, a part may have a Content-Type header with no charset parameter. If the
InputPart.DEFAULT_CONTENT_TYPE_PROPERTY
property is set and the value has a charset parameter,
that value will be appended to an existing Content-Type header that has no charset parameter.
It is also possible to specify a default charset using the constant
InputPart.DEFAULT_CHARSET_PROPERTY
(actual value "resteasy.provider.multipart.inputpart.defaultCharset"):
import org.jboss.resteasy.plugins.providers.multipart.InputPart; @Provider @ServerInterceptor public class ContentTypeSetterPreProcessorInterceptor implements PreProcessInterceptor { public ServerResponse preProcess(HttpRequest request, ResourceMethod method) throws Failure, WebApplicationException { request.setAttribute(InputPart.DEFAULT_CHARSET_PROPERTY, "UTF-8"); return null; } }
If both InputPart.DEFAULT_CONTENT_TYPE_PROPERTY
and
InputPart.DEFAULT_CHARSET_PROPERTY
are set, then the value of
InputPart.DEFAULT_CHARSET_PROPERTY
will override any charset in the value of
InputPart.DEFAULT_CONTENT_TYPE_PROPERTY
.