How to get post body data from java jax-rs server

Viewed 1378

I followed a simple java server setup guide.

So i have something like this:

MyApp.java

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;

@ApplicationPath("/")
public class MyApplication extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> h = new HashSet<>();
        h.add( HelloWorld.class );
        return h;
    }
}

and HelloWord.java

import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Path("/helloworld")
public class HelloWorld {
    @Context
    private HttpServletRequest httpRequest;

    @GET
    @Produces("text/plain")
    public String getClichedMessage() {
        return "Hello, World!";
    }

    @POST
    @Consumes()
    @Produces("text/plain")
    public String doThis(String x) {
        System.out.println(x);
        //....
    }
}

The incoming request is of type

content-type = multipart/form-data; boundary=somerandstuff

and looks like :

--somerandstuff
Content-Disposition: form-data; name="somedata1"

data text 1here
--somerandstuff
Content-Disposition: form-data; name="somedata2"

more data text here

--somerandstuff
Content-Disposition: form-data; name="numberoffiles"

3
--somerandstuff
Content-Disposition: form-data; name="file1"; filename="firstfile"
Content-Type: application/octet-stream

{unreadable symbols thing here}

.
.
. 2 more files like above

How do I go about actually reading the data? I've tried many things and can't get it to work. I tried using httpRequest.getParameterMap() and httpRequest.getParameterNames() and printing everything but nothing prints. I can't seem to even access the post body data. The only things I can access are the headers using httpRequest.getHeaderNames().

What I want to do is store each file in a File object (or something similar) and then download it. How would i go about doing that? I've looked at a few posts on this topic but when I try implementing the solutions, they don't seem to work.

Edit: here is my pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.serverproject</groupId>
    <artifactId>serverProject</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>serverProject</name>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
        <junit.version>5.6.2</junit.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>8.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-servlet</artifactId>
            <version>2.31</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-jackson</artifactId>
            <version>2.31</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
            <version>2.31</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-client</artifactId>
            <version>2.31</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.0</version>
            </plugin>
        </plugins>
    </build>
</project>
1 Answers

Ok so what you have is multipart data, which is most commonly used to send multiple files and metadata (or other data) along with those files. There is no standard JAX-RS support for multipart, but you are using Jersey (a JAX-RS implementation) and Jersey does have support for multipart. The first thing you need to do is add a new dependency to your pom.xml file

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-multipart</artifactId>
    <version>2.31</version>
</dependency>

The next thing you need to do is register this feature with your application. Since you are using an Application subclass, you would add the MultiPartFeature class to the classes in getClasses()

@ApplicationPath("/")
public class MyApplication extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> h = new HashSet<>();
        h.add(HelloWorld.class);
        h.add(MultiPartFeature.class);
        return h;
    }
}

Now we can use this feature. In your resource method, what we will do is declare each part using Jersey's @FormDataParam annotation. As you can see in the multipart entity, each part has a name

Content-Disposition: form-data; name="somedata1"

Here the name of this part is somedata1. So we will add a parameter for this part. Also notice I will add @Consumes(MediaType.MULTIPART_FORM_DATA) as that is the content-type our method will be consuming.

@POST
@Path("upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response fileUpload(@FormDataParam("somedata1") String someData1) {
    ...
}

Based on what type of data is expected, that's how you will determine the parameter type. The type of somedata1 is just plain text, that's why I used a String. But the numeroffiles part is a number, so I can use an int for that part

public Response fileUpload(@FormDataParam("somedata1") String someData1,
                           @FormDataParam("numberoffiles") int numberOfFiles) {
    ...
}

The firtfile part is going to be a binary file. So what we can use an InputStream, File, or byte[] parameter. We can also add an additional parameter of type FormDataContentDisposition which will give us some info about this file. For this example, I will use an InputStream. Just google "how to save an InputStream to a file in Java"

public Response fileUpload(@FormDataParam("somedata1") String someData1,
                           @FormDataParam("numberoffiles") int numberOfFiles,
                           @FormDataParam("file1") InputStream firstFile,
                           @FormDataParam("file1") FormDataContentDisposition fdcd) {
    ...
    String fileName = fdcd.getFileName();
}

UPDATE

If the number of files is unknown, instead of decalring all these separate parts, what you can do is just declare one parameter of type FormDataMultiPart and programmatically extract each part

public Response fileUpload(FormDataMultiPart multipart) {
    Map<String,List<FormDataBodyPart>> bodyParts = multipart.getFields();
    FormDataBodyPart someDataPart = multipart.getField("somedata1");
    String someData = someDataPart.getValueAs(String.class);
}

You will need to traverse all the FormDataBodyParts and you use the bodyPart.getValueAs(Class) method to extract the data of the body part. For the files, you would use getValueAs(InputStream.class).

And that's pretty much it. That's how you work with multipart in Jersey. For more info, you can read the complete docs

Related