restlet / restlet-framework-java

The first REST API framework for Java
https://restlet.talend.com
646 stars 284 forks source link

favicon.ico #1392

Closed nzappite closed 2 years ago

nzappite commented 2 years ago

Hi,

sorry to create an issue for this, but I cannot find a solution (Maybe this will help other people)

I'm using restlet (ext.jaxrs module)

I know the purpose of REST api is not to call them via a browser but for debugging purpose it is easy to query from them ... When I call a service I got an error due to the GET /favicon.ico

java.lang.AbstractMethodError: javax.ws.rs.core.Response.getStatusInfo()Ljavax/ws/rs/core/Response$StatusType;
    at javax.ws.rs.WebApplicationException.computeExceptionMessage(WebApplicationException.java:205)
    at javax.ws.rs.WebApplicationException.<init>(WebApplicationException.java:179)
    at javax.ws.rs.WebApplicationException.<init>(WebApplicationException.java:244)

2022-01-01  19:26:42    <MY_IP> -   -   8081    **GET   /favicon.ico**  -   500 -   0   327542

I would like to know if there is a way to skip this kind of request (made by the browser) ?

Thanks

===========================

Below my current code:

RestServer.java

import org.restlet.Component;
import org.restlet.Server;
import org.restlet.data.Protocol;
import org.restlet.ext.jaxrs.JaxRsApplication;

public class RestServer {

    public static void main(String[] args) throws Exception {
        Component server = new Component();
        Server s = server.getServers().add(Protocol.HTTP,8081);
        JaxRsApplication app = new JaxRsApplication(server.getContext().createChildContext());
        app.add(new RestApplication());
        server.getDefaultHost().attach(app);
        server.start();
    }

}

MyService.java

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;

@Path("/test")

public class MyService {

    @GET
    @Path("/search/{value}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response search(@PathParam("value") String value) {
        // my code
    }

}

RestApplication.java

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

public class RestApplication extends Application {

    public Set<Class<?>> getClasses() {
        Set<Class<?>> rrcs = new HashSet<Class<?>>();
        rrcs.add(MyService.class);
        return rrcs;
    }

}
Tembrel commented 2 years ago

Why not just handle the /favicon.ico request, if only by returning a 404 response? Then you won't have to configure each browser that you use (or someone else uses) to test your API.

nzappite commented 2 years ago

Hi Tim,

thanks for the reply, I tried but I was not able to handle the request... I don't have enough knowledge to do it...

Tembrel commented 2 years ago

Unfortunately, I don't know enough about the jaxrs module to help you. Can anyone else chime in? Should be trivial.

jlouvel commented 2 years ago

Hi @nzappite If you really prefer to use the JAX-RS API versus the Restlet API, I would recommend that you use a framework like Jersey or RESTeasy as they focus on this API. The JAX-RS module in Restlet Framework has been deprecated, even though handling the /favicon.ico path should indeed work.

nzappite commented 2 years ago

Hi @jlouvel , in fact I would prefer to use the full RESTLET api but I would prefer to have a singleClass managing any request for a specific business object (in the JAX-RS I can use the concept of "@Path" to handle all my cases)

I mean based on what I understood, if I want to manage a customer object in RESTLET I can only have a single "@GET" so I need to have multiple java object to handle all my cases

But again maybe I misunderstood the concept

Thanks

Tembrel commented 2 years ago

Restlet encourages decoupling the request routing from its actual handling. Here's a sketch — compiled but completely untested, because incomplete — of how your service might be re-cast using Restlet without JAX-RS. Note that the mapping from path to resource handler is specified at the application level. (Using dummy class SearchResults to stand in for some JSON-convertible type that holds the results.)

// RestServer.java
public class RestServer {
    public static void main(String[] args) throws Exception {
        Component server = new Component();
        server.getServers().add(Protocol.HTTP, 8081);
        server.getDefaultHost().attach(new RestApplication());
        server.start();
    }
}

// RestApplication.java
public class RestApplication extends org.restlet.Application {
    @Override public Restlet createInboundRoot() {
        Router router = new Router(getContext());
        router.attach("/search/{value}", SearchResource.class);
        router.attach("/favicon.ico", FaviconResource.class);
        return router;
    }
}

// SearchResource.java
public class SearchResource extends ServerResource {
    @Get("json") public SearchResults search() {
        String value = getAttribute("value");
        // ... real work here involving `value` ...
        return new SearchResults(/* ... */);
    }
}

// FaviconResource.java
public class FaviconResource extends ServerResource {
    @Get public Representation faviconNotFound() {
        // XXX Or could actually serve an ICO, but not necessary
        throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND, "no favicon");
    }
}
nzappite commented 2 years ago

Hi @Tembrel,

Thank you very much for this sample. It was the missing piece to fully understand Restlet. For sure I will move fully to it.

Last question: how do you map 1 route to a specific method ?

I mean if for a specific case I need to map example:

/search and /search1 on th same SearchResource, I guess I should use 2 classes but is there a way to reduce the code and map 2 methods in the same Resource class ?

in summary, is there a "@Path" equivalent ?

Tembrel commented 2 years ago

The Router code in Application.createInboundRoute — which you must override, incidentally, if you want your application to do anything — is the closest thing to JAX-RS @Path.

You can dispatch multiple paths to the same resource handler. For example (leaving out favicon concerns for the sake of illustration):

    @Override public Restlet createInboundRoot() {
        Router router = new Router(getContext());
        router.attach("/search/{value}", SearchResource.class);
        router.attach("/search1/{value}", SearchResource.class);
        return router;
    }

This will route both /search/xxx and /search1/xxx to SearchResource.

It's important to understand that an instance of SearchResource will be created for each request to be handled. That's because of the class object passed as the second argument to Router.attach. You can also pass an instance of Restlet (or one of its subclasses) instead of a ServerResource class object, in which case that instance will be reused for every request:

    router.attach("/special", new MySpecialRestlet());

Superficially, the latter approach seems simpler, because there's only one instance, but it carries serious risks: You have to ensure that all mutable state on your Restlet instance is safe for concurrent access. The Restlet classes defined by the framework are themselves thread-safe "out of the box", but it is notoriously difficult to preserve thread-safety in subclasses. If you choose to go this route, I suggest reading about Java concurrency best practices. (Not coincidentally, I am one of the authors of Java Concurrency in Practice, which would be good starting point for such reading.)

It's much better to create an instance of a ServerResource subtype to handle each request, because then there is no need to have instances be safe for concurrent access. It also helps keep your resource handling stateless: You can appeal to stateful objects within the resource handler, but the handler instance itself will disappear after the request is handled. By default, the resource handler instance is created using a public no-arg constructor, but there are ways to install more specialized instantiation using the notion of a Finder.

If you use a dependency injection framework, you can inject your Restlets (Components, Applications, Filters, Redirectors, etc.) using that framework, but it's not as easy to inject your ServerResource instances. To help you out in that case, the misleadingly-named Restlet Guice extension has tools for getting those instances dependency-injected. (Full disclosure: I was the primary author of this extension. It applies to all javax.inject-compatible frameworks, not just Guice.)

Finally, you might want to check out Restlet in Action, a book that goes into depth about using Restlet, written by the creators of the framework. (More disclosure: I was an advisor to the authors during the book's creation.)

I think we can close this comment, since it's getting a bit off-topic.

nzappite commented 2 years ago

Indeed you can close the issue and again thank you very much for your help and your advices. I will have some reading for the cold winter evening 😄