Micronaut HTTP Sessions

2023-03-07 14:32 更新

默认情况下,Micronaut 是一个无状态的 HTTP 服务器,但是根据您的应用程序要求,您可能需要 HTTP 会话的概念。

Micronaut 包含一个受 Spring Session 启发的会话模块,该模块目前有两个实现:

  • 内存中会话——如果您计划运行多个实例,则应将其与粘性会话代理结合使用。

  • Redis 会话 - 在这种情况下,Redis 存储会话,非阻塞 I/O 用于将会话读/写到 Redis。

启用会话

要启用对内存中会话的支持,您只需要会话依赖项:

 Gradle Maven 
implementation("io.micronaut:micronaut-session")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-session</artifactId>
</dependency>

Redis 会话

要在 Redis 中存储 Session 实例,请使用包含详细说明的 Micronaut Redis 模块。

要快速启动并运行 Redis 会话,您还必须在构建中具有 redis-lettuce 依赖项:

build.gradle

compile "io.micronaut:micronaut-session"
compile "io.micronaut.redis:micronaut-redis-lettuce"

并通过配置文件中的配置启用 Redis 会话(例如 application.yml):

启用 Redis 会话

 Properties Yaml  Toml Groovy  Hocon  JSON 
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
        }
      }
    }
  }
}

配置会话解析

可以使用 HttpSessionConfiguration 配置会话解析。

默认情况下,使用 HttpSessionFilter 解析会话,该过滤器通过 HTTP 标头(使用 Authorization-Info 或 X-Auth-Token 标头)或通过名为 SESSION 的 Cookie 查找会话标识符。

您可以通过配置文件(例如 application.yml)中的配置禁用标头解析或 cookie 解析:

禁用 Cookie 解析

 Properties Yaml  Toml  Groovy  Hocon  JSON 
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
      }
    }
  }
}

上面的配置启用了 header 解析,但禁用了 cookie 解析。您还可以配置标头和 cookie 名称。

使用会话

可以在控制器方法中使用 Session 类型的参数来检索 Session。例如考虑以下控制器:

 Java Groovy  Kotlin 
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;

import javax.validation.constraints.NotBlank;

@Controller("/shopping")
public class ShoppingController {
    private static final String ATTR_CART = "cart"; // (1)

@Post("/cart/{name}")
Cart addItem(Session session, @NotBlank 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 javax.annotation.Nullable
import javax.validation.constraints.NotBlank

@Controller("/shopping")
class ShoppingController {
    private static final String ATTR_CART = "cart" // (1)

@Post("/cart/{name}")
Cart addItem(Session session, @NotBlank 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 声明了一个名为 cart 的 Session 属性

  2. Session 声明为方法参数

  3. 检索购物车属性

  4. 否则会创建一个新的 Cart 实例并将其存储在会话中

请注意,由于 Session 被声明为必需参数,因此要执行控制器操作,将创建一个 Session 并将其保存到 SessionStore。

如果您不想创建不必要的会话,请将 Session 声明为@Nullable,在这种情况下不会创建和保存不必要的会话。例如:

将@Nullable 与会话一起使用

 Java Groovy  Kotlin 
@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)
}

如果会话已经存在,上述方法只会注入一个新会话。

会话客户端

如果客户端是 Web 浏览器,则在启用 cookie 的情况下会话应该可以正常工作。但是,对于编程式 HTTP 客户端,您需要在 HTTP 调用之间传播会话 ID。

例如,在前面的示例中调用 StoreController 的 viewCart 方法时,HTTP 客户端默认接收一个 AUTHORIZATION_INFO 标头。以下示例使用 Spock 测试演示了这一点:

检索 AUTHORIZATION_INFO 标头

 Java Groovy  Kotlin 
HttpResponse<Cart> response = Flux.from(client.exchange(HttpRequest.GET("/shopping/cart"), Cart.class)) // (1)
                                    .blockFirst();
Cart cart = response.body();

assertNotNull(response.header(HttpHeaders.AUTHORIZATION_INFO)); // (2)
assertNotNull(cart);
assertTrue(cart.getItems().isEmpty());
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()
var response = Flux.from(client.exchange(HttpRequest.GET<Cart>("/shopping/cart"), Cart::class.java)) // (1)
                     .blockFirst()
var cart = response.body()

assertNotNull(response.header(HttpHeaders.AUTHORIZATION_INFO)) // (2)
assertNotNull(cart)
cart.items.isEmpty()
  1. 向 /shopping/cart 提出请求

  2. AUTHORIZATION_INFO 标头存在于响应中

然后,您可以在后续请求中传递此 AUTHORIZATION_INFO 以重用现有会话:

发送 AUTHORIZATION_INFO 标头

Java  Groovy  Kotlin 
String sessionId = response.header(HttpHeaders.AUTHORIZATION_INFO); // (1)

response = Flux.from(client.exchange(HttpRequest.POST("/shopping/cart/Apple", "")
                 .header(HttpHeaders.AUTHORIZATION_INFO, sessionId), Cart.class)) // (2)
                 .blockFirst();
cart = response.body();
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()
val sessionId = response.header(HttpHeaders.AUTHORIZATION_INFO) // (1)

response = Flux.from(client.exchange(HttpRequest.POST("/shopping/cart/Apple", "")
                 .header(HttpHeaders.AUTHORIZATION_INFO, sessionId), Cart::class.java)) // (2)
                 .blockFirst()
cart = response.body()
  1. 从响应中检索 AUTHORIZATION_INFO

  2. 然后在后续请求中作为header发送

使用@SessionValue

您可以使用@SessionValue,而不是显式地将 Session 注入到控制器方法中。例如:

使用@SessionValue

 Java Groovy  Kotlin 
@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 在方法上声明,导致返回值存储在 Session 中。请注意,在返回值上使用时必须指定属性名称

  2. @SessionValue 用于 @Nullable 参数,这导致以非阻塞方式从 Session 中查找值并在存在时提供它。在没有为 @SessionValue 指定值的情况下,会导致使用参数名称来查找属性。

会话事件

您可以注册 ApplicationEventListener beans 以侦听位于 io.micronaut.session.event 包中的会话相关事件。

下表总结了事件:

表 1. 会话事件
类型 描述

SessionCreatedEvent

创建 Session 时触发

SessionDeletedEvent

删除会话时触发

SessionExpiredEvent

Session 过期时触发

SessionDestroyedEvent

SessionDeletedEvent 和 SessionExpiredEvent 的父级


以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号