implementation("io.micronaut.session:micronaut-session")
Micronaut session
HTTP Session support for Micronaut
Version: 4.6.0-SNAPSHOT
1 Introduction
By default, Micronaut is a stateless HTTP server, however depending on your application requirements you may need the notion of HTTP sessions.
2 Http Sessions
Micronaut provides this module inspired by Spring Session that enables this which currently has two implementations:
-
In-Memory sessions - which you should combine with a sticky session proxy if you plan to run multiple instances.
-
Redis sessions - In this case Redis stores sessions, and non-blocking I/O is used to read/write sessions to Redis.
2.1 Enabling Sessions
To enable support for in-memory sessions you just need the micronaut-session
dependency:
<dependency>
<groupId>io.micronaut.session</groupId>
<artifactId>micronaut-session</artifactId>
</dependency>
2.2 Redis Sessions
To store Session instances in Redis, use the Micronaut Redis module which includes detailed instructions.
To quickly get up and running with Redis sessions you must also have the redis-lettuce
dependency in your build:
compile "io.micronaut-session:micronaut-session"
compile "io.micronaut.redis:micronaut-redis-lettuce"
And enable Redis sessions via configuration in the application configuration file:
redis.uri=redis://localhost:6379
micronaut.session.http.redis.enabled=true
redis:
uri: redis://localhost:6379
micronaut:
session:
http:
redis:
enabled: true
[redis]
uri="redis://localhost:6379"
[micronaut]
[micronaut.session]
[micronaut.session.http]
[micronaut.session.http.redis]
enabled=true
redis {
uri = "redis://localhost:6379"
}
micronaut {
session {
http {
redis {
enabled = true
}
}
}
}
{
redis {
uri = "redis://localhost:6379"
}
micronaut {
session {
http {
redis {
enabled = true
}
}
}
}
}
{
"redis": {
"uri": "redis://localhost:6379"
},
"micronaut": {
"session": {
"http": {
"redis": {
"enabled": true
}
}
}
}
}
2.3 Configuring Session Resolution
Session resolution can be configured with HttpSessionConfiguration.
By default, sessions are resolved using an HttpSessionFilter that looks for session identifiers via either an HTTP header (using the Authorization-Info
or X-Auth-Token
headers) or via a Cookie named SESSION
.
You can disable either header resolution or cookie resolution via configuration in the application configuration file:
micronaut.session.http.cookie=false
micronaut.session.http.header=true
micronaut:
session:
http:
cookie: false
header: true
[micronaut]
[micronaut.session]
[micronaut.session.http]
cookie=false
header=true
micronaut {
session {
http {
cookie = false
header = true
}
}
}
{
micronaut {
session {
http {
cookie = false
header = true
}
}
}
}
{
"micronaut": {
"session": {
"http": {
"cookie": false,
"header": true
}
}
}
}
The above configuration enables header resolution, but disables cookie resolution. You can also configure header and cookie names.
2.4 Working with Sessions
A Session can be retrieved with a parameter of type Session in a controller method. For example consider the following controller:
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.micronaut.session.Session;
import io.micronaut.session.annotation.SessionValue;
import io.micronaut.core.annotation.Nullable;
@Controller("/shopping")
public class ShoppingController {
private static final String ATTR_CART = "cart"; // (1)
@Post("/cart/{name}")
Cart addItem(Session session, String name) { // (2)
Cart cart = session.get(ATTR_CART, Cart.class).orElseGet(() -> { // (3)
Cart newCart = new Cart();
session.put(ATTR_CART, newCart); // (4)
return newCart;
});
cart.getItems().add(name);
return cart;
}
}
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Post
import io.micronaut.session.Session
import io.micronaut.session.annotation.SessionValue
import jakarta.annotation.Nullable
@Controller("/shopping")
class ShoppingController {
private static final String ATTR_CART = "cart" // (1)
@Post("/cart/{name}")
Cart addItem(Session session, String name) { // (2)
Cart cart = session.get(ATTR_CART, Cart).orElseGet({ -> // (3)
Cart newCart = new Cart()
session.put(ATTR_CART, newCart) // (4)
newCart
})
cart.items << name
cart
}
}
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Post
import io.micronaut.session.Session
import io.micronaut.session.annotation.SessionValue
@Controller("/shopping")
class ShoppingController {
companion object {
private const val ATTR_CART = "cart" // (1)
}
@Post("/cart/{name}")
internal fun addItem(session: Session, name: String): Cart { // (2)
require(name.isNotBlank()) { "Name cannot be blank" }
val cart = session.get(ATTR_CART, Cart::class.java).orElseGet { // (3)
val newCart = Cart()
session.put(ATTR_CART, newCart) // (4)
newCart
}
cart.items.add(name)
return cart
}
}
1 | ShoppingController declares a Session attribute named cart |
2 | The Session is declared as a method parameter |
3 | The cart attribute is retrieved |
4 | Otherwise a new Cart instance is created and stored in the session |
Note that because the Session is declared as a required parameter, to execute the controller action a Session will be created and saved to the SessionStore.
If you don’t want to create unnecessary sessions, declare the Session as @Nullable
in which case a session will not be created and saved unnecessarily. For example:
@Post("/cart/clear")
void clearCart(@Nullable Session session) {
if (session != null) {
session.remove(ATTR_CART);
}
}
@Post("/cart/clear")
void clearCart(@Nullable Session session) {
session?.remove(ATTR_CART)
}
@Post("/cart/clear")
internal fun clearCart(session: Session?) {
session?.remove(ATTR_CART)
}
The above method only injects a new Session if one already exists.
2.5 Session Clients
If the client is a web browser, sessions should work if cookies are enabled. However, for programmatic HTTP clients you need to propagate the session ID between HTTP calls.
For example, when invoking the viewCart
method of the StoreController
in the previous example, the HTTP client receives by default a AUTHORIZATION_INFO
header. The following example demonstrates this:
when: "The shopping cart is retrieved"
HttpResponse<Cart> response = client.exchange(HttpRequest.GET('/shopping/cart'), Cart) // (1)
.blockFirst()
Cart cart = response.body()
then: "The shopping cart is present as well as a session id header"
response.header(HttpHeaders.AUTHORIZATION_INFO) != null // (2)
cart != null
cart.items.isEmpty()
1 | A request is made to /shopping/cart |
2 | The AUTHORIZATION_INFO header is present in the response |
You can then pass this AUTHORIZATION_INFO
in subsequent requests to reuse the existing Session:
String sessionId = response.header(HttpHeaders.AUTHORIZATION_INFO) // (1)
response = client.exchange(HttpRequest.POST('/shopping/cart/Apple', "")
.header(HttpHeaders.AUTHORIZATION_INFO, sessionId), Cart) // (2)
.blockFirst()
cart = response.body()
1 | The AUTHORIZATION_INFO is retrieved from the response |
2 | And then sent as a header in the subsequent request |
2.6 Using @SessionValue
Rather than explicitly injecting the Session into a controller method, you can instead use @SessionValue. For example:
@Get("/cart")
@SessionValue(ATTR_CART) // (1)
Cart viewCart(@SessionValue @Nullable Cart cart) { // (2)
if (cart == null) {
cart = new Cart();
}
return cart;
}
@Get("/cart")
@SessionValue("cart") // (1)
Cart viewCart(@SessionValue @Nullable Cart cart) { // (2)
cart ?: new Cart()
}
@Get("/cart")
@SessionValue(ATTR_CART) // (1)
internal fun viewCart(@SessionValue cart: Cart?): Cart { // (2)
return cart ?: Cart()
}
1 | @SessionValue is declared on the method resulting in the return value being stored in the Session. Note that you must specify the attribute name when used on a return value |
2 | @SessionValue is used on a @Nullable parameter which results in looking up the value from the Session in a non-blocking way and supplying it if present. In the case a value is not specified to @SessionValue resulting in the parameter name being used to lookup the attribute. |
2.7 Session Events
You can register ApplicationEventListener beans to listen for Session related events located in the io.micronaut.session.event package.
The following table summarizes the events:
Type | Description |
---|---|
Fired when a Session is created |
|
Fired when a Session is deleted |
|
Fired when a Session expires |
|
Parent of both |
3 Release History
For this project, you can find a list of releases (with release notes) here:
4 Repository
You can find the source code of this project in this repository: