Restlet is returning HTTP 415 for every POST request I make

1.2k views Asked by At

I am getting this error every time I try to post data to my server:

enter image description here

Server logs:

Starting the internal [HTTP/1.1] server on port 9192
Starting facilitymanager.api.rest.FacilityManagerAPIRestWrapper application
2015-06-22  13:18:11    127.0.0.1   -   -   9192    POST    /devices/rename -   415 554 45  64  http://localhost:9192   Java/1.7.0_79   -
Stopping the internal server

However In the service handler I am stating that I will handle JSON messages as you can see here:

public static final class RenameDevice extends ServerResource {

    @Post("application/json")
    public String doPost() throws InterruptedException, ConstraintViolationException, InvalidChoiceException, JSONException {
        configureRestForm(this);
        final String deviceId = getRequest().getAttributes().get("device_id").toString();
        final String newName = getRequest().getAttributes().get("new_name").toString();
        return renameDevice(deviceId, newName).toString(4);
    }
}

/**
     * Enables incoming connections from different servers.
     * 
     * @param serverResource
     * @return
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private static Series<Header> configureRestForm(ServerResource serverResource) {
        Series<Header> responseHeaders = (Series<Header>) serverResource.getResponse().getAttributes()
                .get("org.restlet.http.headers");
        if (responseHeaders == null) {
            responseHeaders = new Series(Header.class);
            serverResource.getResponse().getAttributes().put("org.restlet.http.headers", responseHeaders);
        }
        responseHeaders.add("Access-Control-Allow-Origin", "*");
        responseHeaders.add("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS");
        responseHeaders.add("Access-Control-Allow-Headers", "Content-Type");
        responseHeaders.add("Access-Control-Allow-Credentials", "false");
        responseHeaders.add("Access-Control-Max-Age", "60");
        return responseHeaders;
    }

What am I missing here?

Thanks!


Edit: This is the full log concerning the request:

Processing request to: "http://localhost:9192/devices/rename"
Call score for the "org.restlet.routing.VirtualHost@54594d1d" host: 1.0
Default virtual host selected
Base URI: "http://localhost:9192". Remaining part: "/devices/rename"
Call score for the "" URI pattern: 0.5
Selected route: "" -> facilitymanager.api.rest.FacilityManagerAPIRestWrapper@d75d3d7
Starting facilitymanager.api.rest.FacilityManagerAPIRestWrapper application
No characters were matched
Call score for the "/devices/list" URI pattern: 0.0
Call score for the "/groups/rename" URI pattern: 0.0
Call score for the "/devices/rename" URI pattern: 1.0
Selected route: "/devices/rename" -> Finder for RenameDevice
15 characters were matched
New base URI: "http://localhost:9192/devices/rename". No remaining part to match
Delegating the call to the target Restlet
Total score of variant "[text/html]"= 0.25
Total score of variant "[application/xhtml+xml]"= 5.0E-4
Converter selected for StatusInfo: StatusInfoHtmlConverter
2015-06-22  13:28:31    127.0.0.1   -   -   9192    POST    /devices/rename -   415 554 45  67  http://localhost:9192   Java/1.7.0_79   -
POST /devices/rename HTTP/1.1 [415  Unsupported Media Type] ()
KeepAlive stream used: http://localhost:9192/devices/rename
sun.net.www.MessageHeader@2bf4dee76 pairs: {null: HTTP/1.1 415 Unsupported Media Type}{Content-type: text/html; charset=UTF-8}{Content-length: 554}{Server: Restlet-Framework/3.0m1}{Accept-ranges: bytes}{Date: Mon, 22 Jun 2015 12:28:31 GMT}

To obtain a full log one must invoke this line of code anywhere before opening the restlet/component server:

// Create a new Component.
component = new Component();
// Add a new HTTP server listening on default port.
component.getServers().add(Protocol.HTTP, SERVER_PORT);
Engine.setLogLevel(Level.ALL); /// <----- HERE
component.start();
2

There are 2 answers

0
PedroD On BEST ANSWER

I've found the problem! The thing is that a tagged @Post method must receive an argument.

So the method should be like this:

@Post("application/json")
        public String doPost(Representation entity) throws InterruptedException, ConstraintViolationException,
                InvalidChoiceException, JSONException, IOException {
            configureRestForm(this);
            final Reader r = entity.getReader();
            StringBuffer sb = new StringBuffer();
            int c;
            // Reads the JSON from the input stream
            while ((c = r.read()) != -1) {
                sb.append((char) c);
            }
            System.out.println(sb.toString()); // Shows the JSON received
        }
    }

The Representation entity argument brings you the means to detect the media type you are receiving. But since I have my tag like @Post("application/json") I do not need to verify this again.

Imagine that I use just "@Post" instead of "@Post("application/json")", I would have to validate the media type (or types) this way:

@Post
public Representation doPost(Representation entity)
        throws ResourceException {
    if (entity.getMediaType().isCompatible(MediaType.APPLICATION_JSON)) {
       // ...
    }
    // ...
}
0
Thierry Boileau On

A method with an @Post annotation is not required to receive an argument, unless you intend to receive a payload from your request.

If you want to filter on the media type of the incoming representation, use the "json" shortcut, as follow

@Post("json")

This will prevent you to test the media type of the representation.

The list of all available shortcut is available here. Most of them are quite simple to remember. The main reason to use shortcuts (or "extension" such as file extension) is that "xml" is related to several media types (application/xml, text/xml).

If you want to get the full content of the representation, simply call the "getText()" method, instead of using the getReader() and consume it.

If you want to support CORS, I suggest you to use the CorsService (available in the 2.3 version of the Restlet Framework.

Notice there exists a shortcut for getting the headers from a Request or a Response, just call the "getHeaders()" method.

Notice there exists a shortcut for getting the attributes taken from the URL, just call the "getAttribute(String) method.

Here is an updated version of your source code:

public class TestApplication extends Application {

    public final static class TestPostResource extends ServerResource {
    @Post
    public String doPost(Representation entity) throws Exception {
        final String deviceId = getAttribute("device_id");
        final String newName = getAttribute("new_name");

        System.out.println(entity.getText());
        System.out.println(getRequest().getHeaders());
        System.out.println(getResponse().getHeaders());

        return deviceId + "/" + newName;
    }
    }

    public static void main(String[] args) throws Exception {
    Component c = new Component();
    c.getServers().add(Protocol.HTTP, 8183);
    c.getDefaultHost().attach(new TestApplication());
    CorsService corsService = new CorsService();
    corsService.setAllowedOrigins(new HashSet<String>(Arrays.asList("*")));
    corsService.setAllowedCredentials(true);
    corsService.setSkippingResourceForCorsOptions(true);

    c.getServices().add(corsService);
    c.start();
    }

    @Override
    public Restlet createInboundRoot() {
    Router router = new Router(getContext());
    router.attach("/testpost/{device_id}/{new_name}", TestPostResource.class);
    return router;
    }

}