Trois minutes pour servir une ressource @Path en HTTP. Cassini se déploie via la SeBootstrap standard de la spec REST 4.0 — n’importe quelle classe Application est démarrée par un RuntimeDelegate fourni par le transport actif sur le classpath.
Prérequis
-
Java 25 + Maven 3.9.16 — fichier
.sdkmanrcfourni à la racine du repo (sdk envpour aligner). -
Le TCK officiel Jakarta REST 4.0 n’est requis que pour la conformance — pas pour développer une application.
Coordonnées Maven
<dependencies>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>io.vidocq.cassini</groupId>
<artifactId>cassini-core</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<!-- transport de référence, embarque Chappe -->
<dependency>
<groupId>io.vidocq.cassini</groupId>
<artifactId>cassini-chappe</artifactId>
<version>0.1.0-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
</dependencies>
Pour un déploiement zéro dépendance externe, utilisez cassini-jdk-http à la place de cassini-chappe : il s’appuie uniquement sur com.sun.net.httpserver du JDK.
|
Première ressource
@Path("/hello")
public class HelloResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello(@QueryParam("name") @DefaultValue("world") String name) {
return "Hello, " + name + "!";
}
@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Item getItem(@PathParam("id") long id) {
return new Item(id, "Article #" + id);
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response createItem(Item item) {
return Response.status(Response.Status.CREATED).entity(item).build();
}
public record Item(long id, String label) {}
}
Démarrage SE-Bootstrap (recommandé)
ChappeRuntimeDelegate (ou JdkHttpRuntimeDelegate) est découverte via ServiceLoader JPMS — le code applicatif ne référence aucun symbole transport.
import jakarta.ws.rs.SeBootstrap;
import jakarta.ws.rs.core.Application;
public class Main {
public static void main(String[] args) throws Exception {
var config = SeBootstrap.Configuration.builder()
.host("0.0.0.0")
.port(8080)
.rootPath("/")
.build();
SeBootstrap.start(MyApplication.class, config)
.thenAccept(instance -> System.out.println(
"Cassini démarré sur http://localhost:" + instance.configuration().port()))
.toCompletableFuture()
.join();
Thread.currentThread().join();
}
}
class MyApplication extends Application {
@Override public Set<Class<?>> getClasses() {
return Set.of(HelloResource.class);
}
}
Bootstrap manuel via CassiniStack
Pour un contrôle fin (embarquer Cassini dans un serveur Chappe existant, ou tester unitairement) :
try (var stack = CassiniStack.builder()
.application(new MyApplication())
.port(8080)
.build()) {
stack.start();
Thread.currentThread().join();
}
Avec CDI (Vauban)
Ajoutez cassini-cdi-vauban au classpath. Le BeanProvider Vauban est découvert via ServiceLoader ; les classes @Path / @Provider gérées par le container sont scannées automatiquement.
@Path("/items")
@ApplicationScoped // ou @RequestScoped automatique via CassiniScopeExtension
public class ItemResource {
@Inject ItemService service;
@GET @Produces(MediaType.APPLICATION_JSON)
public List<Item> list() { return service.findAll(); }
}
var container = VaubanContainer.builder()
.addBeanClass(ItemService.class)
.addBeanClass(ItemResource.class)
.build();
SeBootstrap.start(new Application() {}, config); // Application vide : Vauban remplit
|
Le |
Build et lancement
cd cassini
sdk env
./mvnw -ntp install -DskipTests
./mvnw -pl cassini-examples/cassini-examples-chappe -am exec:exec
Étapes suivantes : cas d’usage avancés (sub-resources, providers, async, SSE), concepts, état TCK.