JBoss.orgCommunity Documentation

第 26 章 Multipart Providers

26.1. Multipart/mixed
26.1.1. Writing multipart/mixed messages
26.1.2. Reading multipart/mixed messages
26.1.3. Simple multipart/mixed message example
26.1.4. Multipart/mixed message with GenericType example
26.1.5. java.util.List with multipart/mixed data example
26.2. Multipart/related
26.2.1. Writing multipart/related messages
26.2.2. Reading multipart/related messages
26.2.3. Multipart/related message example
26.2.4. XML-binary Optimized Packaging (XOP)
26.2.5. @XopWithMultipartRelated return object example
26.2.6. @XopWithMultipartRelated input parameter example
26.3. Multipart/form-data
26.3.1. Writing multipart/form-data messages
26.3.2. Reading multipart/form-data messages
26.3.3. Simple multipart/form-data message example
26.3.4. java.util.Map with multipart/form-data
26.3.5. Multipart/form-data java.util.Map as method return type
26.3.6. @MultipartForm and POJOs
26.4. Note about multipart parsing and working with other frameworks
26.5. Overwriting the default fallback content type for multipart messages
26.6. Overwriting the content type for multipart messages
26.7. Overwriting the default fallback charset for multipart messages

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.

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 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.

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();
 }
 

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 filter facility of JAX-RS 3.0. In the following example we will set "*/*; charset=UTF-8" as the new default fallback:


import org.jboss.resteasy.plugins.providers.multipart.InputPart;

@Provider
public class InputPartDefaultCharsetOverwriteContentTypeCharsetUTF8
   implements ContainerRequestFilter {

   @Override
   public void filter(ContainerRequestContext requestContext) throws IOException
   {
      requestContext.setProperty(InputPart.DEFAULT_CONTENT_TYPE_PROPERTY, "*/*; charset=UTF-8");
   }
}

Using attribute, InputPart.DEFAULT_CONTENT_TYPE_PROPERTY and a filter 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
public class InputPartDefaultCharsetOverwriteContentTypeCharsetUTF8
   implements ContainerRequestFilter {

   @Override
   public void filter(ContainerRequestContext requestContext) throws IOException
   {
      requestContext.setProperty(InputPart.DEFAULT_CHARSET_PROPERTY, "UTF-8");
   }
}

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.