4

I have some problems in my application, I send a POST request, but I cannot retrieve the JsonObject in my server, this is the code to send:

String quo = "{\"network\": {\"label\": \"new net 111\",\"cidr\": \"10.20.105.0/24\"}}";
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.create();
JsonParser json = new JsonParser();
JsonObject jo = (JsonObject)json.parse(quo);
ClientConfig config = new ClientConfig();

Client client = ClientBuilder.newClient(config);

WebTarget target = client.target("http://localhost:7999/jersey/rest/network/"+tenant_id);

Response oj = target.request().accept(MediaType.APPLICATION_JSON)
        .header("X-Auth-Token", token)
        .post(Entity.json(gson.toJson(jo)));

Trying to retrieve with:

@POST
@Produces(MediaType.APPLICATION_JSON)
@Path("/{tenant_id}")
public String createNetwork(@HeaderParam(value = "X-Auth-Token") String authToken, 
                        @PathParam(value = "tenant_id") String tenant_id, 
                        JsonObject network){

    Response response = client.target(NOVA_ENDPOINT+tenant_id)
                .request(MediaType.APPLICATION_JSON)
                .header("X-Auth-Token", authToken)
                .post(Entity.json(gson.toJson(network)));
    System.out.println("Hello"); 
    String responseJson = response.readEntity(String.class);

JsonObject network seems to be empty, in fact it doesn't execute the method ("Hello is not printed"), the error I get is "Invalid request body" (because the JsonObject is empty I think).. What's wrong with my code?

Ok, I understood that the problem is related to Json handling such as I'm using Gson. This is my improved code (simplified version) following users suggestion, but I still have problems..

Client Side:

package openstack;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.net.URI;


public class Post {

    public static HttpServer startServer() {
    final ResourceConfig resourceConfig = new ResourceConfig()
            .packages("openstack")
            .register(GsonMessageBodyHandler.class);
    return GrizzlyHttpServerFactory.createHttpServer(URI.create("http://localhost:7999/jersey/rest"), resourceConfig);
}  
    public static void main(String[] args) {

    String quo = "{\"keypair\": {\"name\": \"MyKey\"}}";
    HttpServer server = startServer();


    Client client = ClientBuilder.newClient();

    client.register(GsonMessageBodyHandler.class);

    GsonBuilder builder = new GsonBuilder();
    Gson gson = builder.create();
    JsonParser json = new JsonParser();
    JsonObject jo = (JsonObject)json.parse(quo);

    WebTarget target = client.target("http://localhost:7999/jersey/rest/test/prova");

    System.out.println(jo);
    Response oj = target.request().post(Entity.json(jo));

    String responseString = oj.readEntity(String.class);
    System.out.println(responseString);

    }
}

Server Side:

package openstack;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

@Path("/test")
public class Test {

    GsonBuilder builder = new GsonBuilder();
    Gson gson = builder.create();
    Parliament parliament = new Parliament();
    JsonParser json = new JsonParser();
    private final Client client;

    public Test() {
        client = ClientBuilder.newClient().register(GsonMessageBodyHandler.class);
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/prova")
    public Response mymethod(JsonObject keypairsob){

        return Response.ok(keypairsob).build();

    }
}

I created a GsonMessageBodyHandler.java in my package with the code suggested below by the user peeskillet. Added jersey-container-grizzly2-http.jar to my web-inf/lib (I don't know how to proper use Maven), but still doesn't work.. what am I missing?

rok
  • 2,574
  • 3
  • 23
  • 44
  • @peeskillet it's because I'm writing a proxy that act as a server for incoming request and as a client for Openstack. It's not the same resource and the problem occurs before, because the method createNetwork isn't executed at all. – rok Nov 28 '14 at 13:52
  • Did you add the [JSON-P module](https://jersey.java.net/documentation/latest/media.html#json.json-p)? – Paul Samsotha Nov 28 '14 at 13:59
  • Actually I didn't because I'm using Gson.. is there any problem related? – rok Nov 28 '14 at 14:11
  • Yes. When you send out the request, it is serialized JSON form. But who is going to deserialize it back to JsonObject? That is done behind the scenes by a `MessageBodyReader` implementation that supports JSON-P. That reader is provided in the JSON-P module. I'm Not familiar with Gson, so I don't know if that `JsonObject` is a Gson object or a JSON-P object. If it is Gson object, you can take a look at `MessageBodyReader/Writer` implemntation for Gson. You would need to register that provider both with client and server. If it's JSON-P, the JSON-P module will register itself – Paul Samsotha Nov 28 '14 at 14:17
  • Sorry, forgot the link for the implementation. [Here it is](http://eclipsesource.com/blogs/2012/11/02/integrating-gson-into-a-jax-rs-based-application/) – Paul Samsotha Nov 28 '14 at 14:21
  • Oh and like the answer below mentions, you should add a `@Consumes` annotation for application/json – Paul Samsotha Nov 28 '14 at 14:40
  • I tried with consumes, having the same problem.. the problem is related to Json handling, because changing all to strings works prefectly. But I'm not expert, I don't know even if I'm using JSON-P, Jackson to deserialize the Json ... where should I check? – rok Nov 28 '14 at 17:56
  • Looking through the Gson API, looks like you're using `com.google.gson.JsonObject`. So copy [this code](http://eclipsesource.com/blogs/2012/11/02/integrating-gson-into-a-jax-rs-based-application/). And you need to register this class with your application. If you need help with that, then you need to show your application configuration, whether it's in a `web.xml` or a subclass of `Application` or `ResourceConfig` – Paul Samsotha Nov 28 '14 at 18:13
  • If you want client support so you don't have to manual parse the `JsonObject` before you send it, then register it with the client also `client.register(GsonMessageBodyHandler.class)` – Paul Samsotha Nov 28 '14 at 18:14
  • In general, read more about [Entity Providers](https://jersey.java.net/documentation/latest/message-body-workers.html) – Paul Samsotha Nov 28 '14 at 18:15
  • Tried to follow your link, copied the code into a GsonMessageBodyHandler class in my package, but I get this error :"Exception Exception is not compatible with throws clause in MessageBodyReader.readFrom(Class, Type, Annotation[], MediaType, MultivaluedMap, InputStream)" in reafFrom method... – rok Nov 29 '14 at 11:48
  • I'll play around with it in a bit. I'll see if I can get you a tested answer. Are you using Maven btw? – Paul Samsotha Nov 29 '14 at 11:59
  • Thank you much! I'm using Eclipse with Glassfish 4.0, anyway I tried to implement a more simple code to better understand, I opened this question: http://stackoverflow.com/questions/27201993/error-with-org-glassfish-json-jaxrs-jsonstructurebodyreader-on-a-simply-rest-app I've problem in there too.. I'm a mess.. :D – rok Nov 29 '14 at 12:08
  • I'm guessing you have to make some changes to the class that you copied from that link, as it was giving compile errors? I fixed those errors and it works. I'll post answer in a bit – Paul Samsotha Nov 29 '14 at 12:31
  • Just post a tested solution. Let me know if it works for you. – Paul Samsotha Nov 29 '14 at 13:02
  • Where do you register it with the `GsonMessageBodyHandler` with the server. The server is the one that does the conversion before handing it off to you resource method. – Paul Samsotha Nov 29 '14 at 16:02
  • Edited, see if you can help.. – rok Nov 29 '14 at 18:05

3 Answers3

2

In order to convert JSON to a Java type, there is need to for a MessageBodyReader and a MessageBodyWriter implementation to do the conversion to and from. Since you are using JsonObject which is a GSON type, you can see this implementation. There is a problem with the implementation though, as the readFrom method doesn't compile with Jersey 2. Here is the a fixed version

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;


@Provider
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public final class GsonMessageBodyHandler implements MessageBodyWriter<Object>,
        MessageBodyReader<Object> {

    private static final String UTF_8 = "UTF-8";

    private Gson gson;

    private Gson getGson() {
        if (gson == null) {
            final GsonBuilder gsonBuilder = new GsonBuilder();
            gson = gsonBuilder.create();
        }
        return gson;
    }

    @Override
    public boolean isReadable(Class<?> type, Type genericType,
            java.lang.annotation.Annotation[] annotations, MediaType mediaType) {
        return true;
    }

    @Override
    public Object readFrom(Class<Object> type, Type type1, Annotation[] antns,
            MediaType mt, MultivaluedMap<String, String> mm, InputStream in) 
            throws IOException, WebApplicationException {
        InputStreamReader streamReader = new InputStreamReader(in, UTF_8);
        try {
            Type jsonType;
            if (type.equals(type1)) {
                jsonType = type;
            } else {
                jsonType = type1;
            }
            return getGson().fromJson(streamReader, jsonType);
        } finally {
            streamReader.close();
        }
    }

    @Override
    public boolean isWriteable(Class<?> type, Type genericType, 
            Annotation[] annotations, MediaType mediaType) {
        return true;
    }

    @Override
    public long getSize(Object object, Class<?> type, Type genericType, 
            Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

    @Override
    public void writeTo(Object object, Class<?> type, Type genericType, 
            Annotation[] annotations, MediaType mediaType, 
            MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) 
            throws IOException, WebApplicationException {
        OutputStreamWriter writer = new OutputStreamWriter(entityStream, UTF_8);
        try {
            Type jsonType;
            if (type.equals(genericType)) {
                jsonType = type;
            } else {
                jsonType = genericType;
            }
            getGson().toJson(object, jsonType, writer);
        } finally {
            writer.close();
        }
    }
}

Then we just need to register it with both the client and the application. I'm using a standalone test, where you can see the configuration here

final ResourceConfig resourceConfig = new ResourceConfig()
        .packages("jersey.stackoverflow.standalone")
        .register(GsonMessageBodyHandler.class);
...
Client c = ClientBuilder.newClient();
c.register(GsonMessageBodyHandler.class);

Here is the resource class I used for the test

import com.google.gson.JsonObject;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import jersey.stackoverflow.standalone.provider.GsonMessageBodyHandler;

@Path("/gson")
public class GsonResource {

    private final Client client;
    private static final String BASE_URI = "http://localhost:8080/api/gson";

    public GsonResource() {
        client = ClientBuilder.newClient().register(GsonMessageBodyHandler.class);
    }

    @POST
    @Path("/proxy")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response proxyPost(JsonObject json) {
        Response response = client.target(BASE_URI)
                .path("main-resource").request().post(Entity.json(json));
        JsonObject fromMainResource = response.readEntity(JsonObject.class);
        return Response.created(null /* should be a created URI */)
                .entity(fromMainResource).build();
    }

    @POST
    @Path("/main-resource")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response mainResource(JsonObject json) {
        return Response.ok(json).build();
    }  
}

Here's the complete test, which requires this maven dependency

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.net.URI;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import static jersey.stackoverflow.standalone.Main.BASE_URI;
import jersey.stackoverflow.standalone.provider.GsonMessageBodyHandler;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import org.junit.Test;

public class GsonProviderTest {

    public static HttpServer startServer() {
        final ResourceConfig resourceConfig = new ResourceConfig()
                .packages("jersey.stackoverflow.standalone")
                .register(GsonMessageBodyHandler.class);
        return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), resourceConfig);
    }

    public static Client getClient() {
        Client c = ClientBuilder.newClient();
        c.register(GsonMessageBodyHandler.class);
        return c;
    }

    @Test
    public void testGetIt() {
        HttpServer server = startServer();
        Client c = getClient();

        c.register(GsonMessageBodyHandler.class);

        String quo = "{\"network\": {\"label\": \"new net 111\",\"cidr\": \"10.20.105.0/24\"}}";

        GsonBuilder builder = new GsonBuilder();
        Gson gson = builder.create();
        JsonParser json = new JsonParser();
        JsonObject jo = (JsonObject) json.parse(quo);

        WebTarget target = c.target("http://localhost:8080/api/gson/proxy");
        Response response = target.request().post(Entity.json(jo));
        String responseString = response.readEntity(String.class);
        System.out.println(responseString);
        response.close();

        c.close();
        server.stop();
    }
}

All the test does is send the JsonObject. Though there isn't any visible conversion to JSON, in any of my code, it is happening behind the scenes by the GsonMessageBodyHandler. If you look at the GsonResource class, you can see the methods don't do anything but send out the JsonObject. In the client test, I read the response as a String, and you can see the result the same as what sent out in the initial request.

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • I still have some problems, this is what I did: created GsonMessageBodyHandler.java in my package copying your above code, then put in my code 'client = ClientBuilder.newClient().register(GsonMessageBodyHandler.class);' in both my client and server classes. The server method is executed (I put a debugging output there), but the json is empty, in fact in the client console I see {"type":"jsonObject"} insead of the one I send. Btw I'm going to edit my question adding the new code I have, to be more clear. – rok Nov 29 '14 at 15:46
2

There's a simple way to get the JsonObject in com.google.gson.JsonObject type using a post request.

I am assuming that all the dependencies for com.google.gson , jersey and jax-rs are already added.

On the server side you need to have code similar to below :

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;

@Path("/api")
public class JersyAPI {


    private JsonParser parser= new JsonParser();

    @POST
    @Path("/pudding")
    @Consumes("application/json")
    public Response postTest(String requestBody){
        Response re = Response.status(200).build();
        try{
            JsonObject inputObjectJson = parser.parse(requestBody).getAsJsonObject();

The code above has a rest endpoint defined with path /api/pudding and it is accepting the Request Body as String. Once you receive the Json as string on server side, com.google.gson.JsonParser can be used to convert it into the com.google.gson.JsonObject directly and this can be used in your program.

To make a request on server side you post request should look like this :

POST /rest/api/pudding HTTP/1.1
Host: localhost:8082
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: c2b087d9-4830-c8a8-2a19-78273a73898c

{
  "id": 1312312,
  "name": "Test",
  "data": { 
            "test" : "data"
        },
}
0

Have you had any JSON requests successfully parsed? It could be that you need to enable JSON support in Jersey:

https://jersey.java.net/documentation/1.18/json.html

Otherwise, it may simply be failing on your request to turn the message body into a JsonObject here:

public String createNetwork(
    @HeaderParam(value = "X-Auth-Token") String authToken, 
    @PathParam(value = "tenant_id") String tenant_id, 
    JsonObject network)
user384842
  • 1,946
  • 3
  • 17
  • 24
  • Yes, Json works because I have no problems with GET requests (I'm using Jersey 2.13). I think you are right, it fails somehow to turn the message body in the JsonObject, but why? – rok Nov 28 '14 at 13:56
  • Is it possible it's calling one of your other @Path methods instead? Sometimes the resolution of paths can be a little counter-intuitive.. – user384842 Nov 28 '14 at 14:12
  • Calling my other path produce the same – rok Nov 28 '14 at 14:19
  • 1
    Do you have an @Consumes on the class that is inconsistent with json? Perhaps you could post up the whole class? – user384842 Nov 28 '14 at 14:27
  • it doesn't work with consumes too.. I think is something related to Json management, because using String instead works perfectly.. I'll post the entire class soon anyway.. – rok Nov 28 '14 at 17:53