-
Notifications
You must be signed in to change notification settings - Fork 12
RESTful services
This page refers to las2peer 0.6.1 and up.
The las2peer WebConnector and REST Mapper allow easy implementation of las2peer services with a RESTful interface. The REST Mapper is the library that services use for implementing a REST interface. The WebConnector communicates with the REST Mapper using las2peer's RMI calls.
Unlike as previous versions, REST calls are now processed by Jersey, the reference implementation of the JAX-RS standard for defining RESTful services in Java. Additionally, Swagger is used for generating documentations automatically.
For examples of RESTful services, please refer to the las2peer Examples on GitHub. The las2peer Template Project implements all neccessary methods for a simple RESTful service.
Currently, these libraries are used:
- JAX-RS 2.0.1
- Jersey 2.23.2
- Swagger 2.0 (swagger-jersey2-jaxrs 1.5.10)
Note that this section refers to las2peer 0.7. Prior to this version, the implementation of the initResources()
method was enforced.
The service should extend the abstract RESTService
class. Starting with las2peer 0.7, the service class itself is registered as RESTful resource at the root path /
of the service. However, this behaviour can be customized by overriding the initResources()
method.
The initResources()
method should be used for setting up JAX-RS Resources. The resource configuration cannot be updated at any later point. For more details see below.
For a complete guide please refer to the Jersey documentation, especially to this chapter.
A service consists of resources that need to be defined and then registered in the service.
A resource is a plain Java object and may be look like this:
@Path("/resource")
public class Resource {
@GET
@Path("/hello")
public String getOk() {
return "Hello";
}
@GET
@Path("/create/{id}")
public Response createSomething(@PathParam("id") String id) {
if (id.equals("favouriteNumber")) {
return Response.status(Response.Status.NOT_FOUND).entity("Not found!").build();
}
String json = "{number: 7}";
return Response.ok(json, MediaType.APPLICATION_JSON).build();
}
}
Resources can also be defined as member classes, but they need to be static:
@ServicePath("service")
public class ExampleService extends RESTService {
@Override
protected void initResources() {
getResourceConfig().register(Resource.class);
}
@Path("/resource")
public static class Resource {
@GET
@Path("/hello")
public String getOk() {
return "Hello";
}
}
}
Or the service itself can be used as resource (default):
@ServicePath("service")
public class ExampleService extends RESTService {
@GET
@Path("/hello")
public String getOk() {
return "Hello";
}
}
It is also possible to get the plain request body:
@POST
@Path("/body")
@Consumes("text/plain")
public String getBody(String body) {
return body;
}
However, Jersey has more advanced features for deserializing request bodys.
By default, the service class itself is registered as a RESTful resource at the root path /
relative to the service path (see below). Internally, it is a singleton resource, which means that the lifecycle is not handled by Jersey.
This behaviour can be overridden using initResources()
.
@Override
protected void initResources() {
getResourceConfig().register(Resource.class);
}
getResourceConfig()
returns a ResourceConfig
from Jersey and should be used as shown above.
It has some extended features such as automatically detecting resources in a package. The following example reads all resources found in the ExampleService
's package:
getResourceConfig().packages("i5.las2peer.services.exampleService");
The REST Mapper sets up the ResourceConfig to use the current service's class loader.
If you don't want to use the ResourceConfig object, you can set up a custom JAX-RS Application:
@Override
protected void initResources() {
setApplication(new Application() {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<>();
classes.add(Resource.class);
return classes;
}
});
}
Please note that you only can use either setApplication(Application)
or getResourceConfig()
. Also, one of these methods must be used to set up the resources.
The REST Mapper has a simple integration of the JAX-RS SecurityContext
with las2peer's security mechanisms:
-
Invocations done by the
anonymous
agent are treated as unauthenticated, this means thatgetUserPrincipal()
returns null. -
Authenticated agents have the role
authenticated
. Users have theauthenticated_user
role, all other agents have theauthenticated_bot
role.
To see how to use this, have a look on this example:
@ServicePath("service")
public class TestService extends RESTService {
@Override
protected void initResources() {
getResourceConfig().register(Resource.class);
// needed for @PermitAll and @RolesAllowed annotations:
getResourceConfig().register(RolesAllowedDynamicFeature.class);
}
@Path("/")
@PermitAll
public static class Resource {
@GET
@Path("/everyone")
@Produces(MediaType.TEXT_PLAIN)
public String getEveryone(@Context SecurityContext securityContext) {
// available to all users (thanks to @PermitAll)
}
@RolesAllowed("authenticated")
@GET
@Path("/authenticated")
public String getAuthenticated() {
// only available to authenticated users
}
}
}
Once you have created your RESTful las2peer service, you should provide good documentation for external developers. las2peer uses Swagger for this purpose. With a couple of additional annotations to your service, las2peer auto-generates Swagger documents and hosts them along with your service.
In addition to the JAX-RS annotations, Swagger provides some own annotations for documentation. Please refer to our examples and to Swagger's documentation for more information.
Please notice that only resources annotated with @Api
are listed in the output.
Note: In las2peer 0.6.0/0.6.1, the service path must not contain /
. This section refers to las2peer 0.6.2.
The last step is to specify where your service should be accessible through the WebConnector.
Services can define a base path where the service is deployed. For example, if the service is deployed at my/service/
, the service methods will be available at https://my.web.connector/my/service/
.
The service is determined by the service's base path, the rest of the request URI is processed by Jersey. All paths in the service are relative to this path.
The alias can be set by annotating the RESTService
class with @ServicePath(String)
:
@ServicePath("path/to/my/service")
public class ExampleService extends RESTService {
@Override
protected void initResources() {
...
}
...
}
But there are some restrictions on the base paths:
- The alias must not contain any character not allowed in URIs.
- No
PathParam
s are possible in the service's base path. - A service path cannot be a prefix of another service path. For example, services at
service
andservice/sub
are invalid. In this case, only the first service registered would be available via the WebConnector.
All service methods are available at paths beginning with /service/path/...
. In this case, any service version could be choosen by the WebConnector (probably a newer one). You can also specify a version using /service/path/v[version]/...
, where [version]
is replaced by a version as described here.
The version is determined using the same algorithm used for remote method invocations, it is not guaranteed that the called service version is always the same. You should specify the version to make sure to get a consistent API. For this purpose, we encourage every service to use semantic versioning.
The swagger definitions are available at /service/path/swagger.json
respecively /service/path/v[version]/swagger.json
. For example: http://localhost:8080/service/swagger.json
You can use this definition in Swagger UI.
The WebConnector sets up the default OIDC provider (https://api.learning-layers.eu/o/oauth2
) as security definition and sets up Swagger to provide authentication for each method.
To use OpenID Connect in Swagger UI, you need to register a client at the default provider (see https://github.com/learning-layers/openid-connect-button for instructions). Then download Swagger UI (version 2.2.x
, 3.0.0
does not support OIDC anymore), open dist/index.html
, find these lines in the code and fill in clientId
, clientSecret
and appName
:
if(typeof initOAuth == "function") {
initOAuth({
clientId: "your-client-id",
clientSecret: "your-client-secret-if-required",
realm: "your-realms",
appName: "your-app-name",
scopeSeparator: ",",
additionalQueryStringParams: {}
});
}
Now you can test authenticated requests directly from Swagger UI!