Search code examples
restgwtrestlet

RESTlet on GWT unable to retrieve RESTful resource not backed by GWT enabled server


I am trying to get GWT+RESTlet to communicate with a RESTful service, which is not "GWT aware".

On the GWT client, I do something like

Reference ref = new Reference("http://localhost:8080/control/programs/");
ProgramListResourceProxy clientResource = GWT.create( ProgramListResourceProxy.class );
clientResource.setReference( proxyRef );
clientResource.setFollowingRedirects( true );
clientResource.accept( MediaType.APPLICATION_JSON  );
clientResource.accept( MediaType.APPLICATION_XML  );

ProgramListResourceProxy resource = RestClient.createProgramListResource();
resource.retrieve( new Result<ArrayList<ProgramRef>>()
{
    @Override
    public void onFailure( Throwable caught )
    {
        while( caught != null)
        {
            Window.alert( "Error retrieving programs.\n" + caught.getMessage() );
            caught = caught.getCause();
        }
    }

    @Override
    public void onSuccess( ArrayList<ProgramRef> result )
    {
        Window.alert( "Programs: " + result );
        programs = result;
        view.setRowData( toStringList( result ) );
    }
});

If I request the resource from the browser, I get

[{"name":"niclas","link":{"action":"GET","path":"/control/programs/niclas/"}}]

as expected.

But when doing the code above in GWT, I get the popup alert telling me that there is a problem, and in the nested exception is;

Error retrieving programs.
Can't parse the enclosed entity because of its media type. 
Expected <application/x-java-serialized-object+gwt> but was 
<application/json>. Make sure you have added the 
org.restlet.client.ext.gwt.jar file to your server.

The MediaTypes matches in the request/response and the traffic looks like this.

Request;

GET /control/programs/ HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Accept: application/json, application/xml
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.172 Safari/537.22
Referer: http://localhost:8080/connect/Connect.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

Response;

HTTP/1.1 200 OK
Date: Sat, 30 Mar 2013 15:46:04 GMT
Content-Type: application/json; charset=UTF-8
Date: Sat, 30 Mar 2013 15:46:04 GMT
Accept-Ranges: bytes
Server: Restlet-Framework/2.1.2
Vary: Accept-Charset, Accept-Encoding, Accept-Language, Accept
Transfer-Encoding: chunked

4E
[{"name":"niclas","link":{"method":"GET","path":"/control/programs/niclas/"}}]

Can anyone explain why Restlet is expecting "application/x-java-serialized-object+gwt" and not the set MediaType in the resource??

Is it related to using org.restlet.client.resource.ClientProxy? If so, is there another way to do these async requests with RESTlet??

I am using RESTlet 2.1.2 and GWT 2.2.0.

Thanks in Advance.


Solution

  • End of the day there were several problems wrapped up in this, and I thought I'd better share my findings;

    1. I didn't realize that I had a "same-origin" problem. This was solved by using a transparent proxy on the GWT serving port to my Rest Server. This also seems to correctly forward the authentication of the Rest server to the GWT application in the browser.

    2. Restlet's packaging in Editions seems like a really good idea. But it was really difficult to get the IntelliJ IDEA to be able to set up the GWT debug environment with these plus a shared DTO module. I think I had other problems regarding Restlet's way to figure out which content types being used and what not. End of the day, I convinced myself to not bother with a shared DTO library and instead;

      • Server side uses Restlet's Jackson extension, and pojo classes as DTOs.
      • On the GWT side, I ask Restlet to only deal with Representation type and use the AutoBean feature in GWT to do the serialization. (See below)

    The AutoBean is great;

    public interface Program
    {
        String getName();
    
        void setName( String name );
    
        List<Block> getBlocks();
        void setBlocks(List<Block> blocks);
    
        List<Connection> getConnections();
        void setConnections(List<Connection> connections );
    
        public static class Factory
        {
            public static Program make() {
                AutoBean<Program> ref = ModelFactory.instance.program();
                return ref.as();
            }
    
            public static String toJson(Program ref) {
                AutoBean<Program> bean = AutoBeanUtils.getAutoBean( ref );
                return AutoBeanCodex.encode( bean ).getPayload();
            }
    
            public static Program fromJson(String json) {
                AutoBean<Program> bean = AutoBeanCodex.decode( ModelFactory.instance, Program.class, json );
                return bean.as();
            }
        }
    }
    

    To make the sample complete, my ModelFactory has creation methods for these, which are also fully handled by GWT;

    public interface ModelFactory extends AutoBeanFactory
    {
        ModelFactory instance = GWT.create( ModelFactory.class );
        AutoBean<Program> program();
        AutoBean<ProgramRefList> programRefList();
        :
    }
    

    One issue that wasn't obvious was how to handle top-level JSON lists, since AutoBean matches JSON keys with fields in the interfaces. But I found a neat little trick, which can be seen in the following snippet of the same program;

    public interface ProgramRefList
    {
        List<ProgramRef> getList();
    
        public static class Factory
        {
            public static ProgramRefList make()
            {
                AutoBean<ProgramRefList> ref = ModelFactory.instance.programRefList();
                return ref.as();
            }
    
            public static String toJson( ProgramRefList ref )
            {
                AutoBean<ProgramRefList> bean = AutoBeanUtils.getAutoBean( ref );
                return AutoBeanCodex.encode( bean ).getPayload();
            }
    
            public static ProgramRefList fromJson( String json )
            {
                json = "{ \"list\": " + json + "}";
                AutoBean<ProgramRefList> bean = AutoBeanCodex.decode( ModelFactory.instance, ProgramRefList.class, json );
                return bean.as();
            }
        }
    }
    

    The top-level list is faked into another object with a single key ("list" in this case) which then is reachable from the getList() method.

    And this is then used very simply in the Rest client code;

        ClientResource resource = RestClient.createProgramList( new Uniform()
        {
            @Override
            public void handle( Request request, Response response )
            {
                Logger.trace( this, "handle(" + request + "," + response + ")" );
                try
                {
                    if( response.isEntityAvailable() )
                    {
                        String jsonText = response.getEntity().getText();
                        programs = ProgramRefList.Factory.fromJson( jsonText );
                        ArrayList<String> rowData = toStringList( programs );
                        view.setRowData( rowData );
                    }
                }
                catch( Exception e )
                {
                    Logger.handleException( this, "loadProgramsList()", e );
                }
            }
        } );
        resource.get();