Dropwizard Multipart Form Quickstart

The other day I had the requirement where I had to code an endpoint such that a client could submit two bodies, one containing JSON, and the other one containing XML. One could get clever and embed the XML in JSON, vice versa, or even make two requests, but the HTTP spec already has this issue solved: multipart messages. If you took a peek at the Wikipedia page or the stackoverflow question, “What is [a] HTTP multipart request”, you might be confused because there is a lot of talk about submitting forms, files, and email. These are the typical use cases, but not the only use cases. I’m here to talk about (document) the other use case: multiple bodies.

I’ll be using Dropwizard for the examples but multipart messages should work across frameworks and languages.

Step 1: Add the bundle

First we add the dependency to our pom.xml

<dependency>
    <groupId>io.dropwizard</groupId>
    <artifactId>dropwizard-forms</artifactId>
</dependency>

And then register the bundle:

@Override
public void initialize(Bootstrap<ExampleConfiguration> bootstrap) {
    bootstrap.addBundle(new MultiPartBundle());
}

Step 2: Create resource endpoint

Our resource endpoint will consume JSON content and XML content. Both will be representing the same not-null and valid Person class. We’ll return the difference between the ages of the JSON person and the XML person.

@POST
@Timed
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Path("/form-quickstart")
public int ageDifference(
    @Valid @NotNull @FormDataParam("json-body") Person jsonPerson,
    @Valid @NotNull @FormDataParam("xml-body") Person xmlPerson
) {
    return jsonPerson.age() - xmlPerson.age();
}

Couple of notes:

Step 3: Create Test

Assuming you added dropwizard-testing to your pom:

@ClassRule
public static final ResourceTestRule RULE = ResourceTestRule.builder()
        .addResource(new PersonResource())
        .addProvider(MultiPartFeature.class)
        .build();

@Test
public void testResource() {
    final MultiPart multiPartEntity = new FormDataMultiPart()
        .field("json-body", Person.create("Nick", 24), APPLICATION_JSON_TYPE)
        .field("xml-body", Person.create("Papa", 55), APPLICATION_XML_TYPE);

    assertThat(RULE.client().target("/form-quickstart")
        .register(MultiPartFeature.class).request()
        .post(Entity.entity(multiPartEntity, multiPartEntity.getMediaType()))
        .readEntity(String.class))
        .isEqualTo("-31");
}

Here we have to explicitly register the Jersey MultiPartFeature, which is what the bundle did in step 1. It’s a tad inconvenient.

If you have a healthy dose of skepticism whether XML or JSON is really being used you can add another test where you use JSON and XML directly.

final MultiPart multiPartEntity = new FormDataMultiPart()
    .field("json-body", "{ \"age\": 24, \"name\": \"Nick\" }", APPLICATION_JSON_TYPE)
    .field("xml-body", "<Person><age>55</age><name>Papa</name></Person>", APPLICATION_XML_TYPE);

Step 4: Curl Example

Oftentimes across companies and teams, Dropwizard may not be persasive, so handing them the test code snippet as a way to call the API may be inappropriate. That’s why it’s good to include a curl example as an unbiased mechanism for calling the API. It is normally easy enough to work backwards from a curl example to the language or framework of choice. For the API shown earlier a request from curl would look like:

cat > /tmp/tmp-xml-body.xml <<EOF
<root>
  <age>55</age>
  <name>Papa</name>
</root>
EOF

curl 'http://localhost:8080/form-quickstart' \
    -F 'json-body={ "age": 24, "name": "Nick" };type=application/json' \
    -F 'xml-body=</tmp/tmp-xml-body.xml;type=application/xml'

Of course, it came out a little more complicated than I wanted due to my fruitless search in how to escape < in the -F flag. I initially tried embedding the XML data like the JSON data, but curl parses the -F flag and < is a special character signalling to embed the file’s contents.

Specifying type=application/json and type=application/xml is critical as curl interprets the snippets and adds the appropriate Content-Type to each part. This content type is interpreted by Dropwizard, so that it can be deserialized correctly.

This curl statement is translated into the following request:

POST /form-quickstart HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.47.0
Accept: */*
Content-Length: 384
Expect: 100-continue
Content-Type: multipart/form-data; boundary=------------------------9193de6aa3698f01

--------------------------9193de6aa3698f01
Content-Disposition: form-data; name="json-body"
Content-Type: application/json

{ "age": 24, "name": "Nick" }
--------------------------9193de6aa3698f01
Content-Disposition: form-data; name="xml-body"
Content-Type: application/xml

<root>
  <age>55</age>
  <name>Papa</name>
</root>

--------------------------9193de6aa3698f01--

As an aside, I lament the documentation for the -F flag:

lets curl emulate a filled-in form in which a user has pressed the submit button

The documentation makes it appear as if the only use case is someone filling in a form. The sheer amount of documentation missing the use case of having multiple bodies can be misleading. Many times while writing this post I was left scratching my head wondering if what I was doing was against some kind of unspoken law. If it is, feels good.

Step 5: The missing steps

There are some steps that were omitted earlier as they distracted from the main part of using multipart forms:

Since Dropwizard is a JSON first framework, one needs to add in basic XML support:

<dependency>
    <groupId>com.fasterxml.jackson.jaxrs</groupId>
    <artifactId>jackson-jaxrs-xml-provider</artifactId>
    <version>2.7.6</version>
</dependency>

The Jackson package (for Dropwizard 1.0.2) automatically registers an XML provider on the client and server sides, so you don’t have to do anything.

The Person class is described below.

@AutoValue
public abstract class Person {
    @JsonProperty
    public abstract String name();

    @JsonProperty
    public abstract int age();

    @JsonCreator
    public static Person create(
            @JsonProperty("name") String name,
            @JsonProperty("age") int age
    ) {
        return new AutoValue_Person(name, age);
    }
}

I can’t help it, but I really enjoy AutoValue and want its usage to spread

Comments

If you'd like to leave a comment, please email [email protected]