Micronaut HTTP Sessions

2023-03-07 14:32 更新

默認(rèn)情況下,Micronaut 是一個(gè)無狀態(tài)的 HTTP 服務(wù)器,但是根據(jù)您的應(yīng)用程序要求,您可能需要 HTTP 會話的概念。

Micronaut 包含一個(gè)受 Spring Session 啟發(fā)的會話模塊,該模塊目前有兩個(gè)實(shí)現(xiàn):

  • 內(nèi)存中會話——如果您計(jì)劃運(yùn)行多個(gè)實(shí)例,則應(yīng)將其與粘性會話代理結(jié)合使用。

  • Redis 會話 - 在這種情況下,Redis 存儲會話,非阻塞 I/O 用于將會話讀/寫到 Redis。

啟用會話

要啟用對內(nèi)存中會話的支持,您只需要會話依賴項(xiàng):

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

Redis 會話

要在 Redis 中存儲 Session 實(shí)例,請使用包含詳細(xì)說明的 Micronaut Redis 模塊。

要快速啟動(dòng)并運(yùn)行 Redis 會話,您還必須在構(gòu)建中具有 redis-lettuce 依賴項(xiàng):

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 配置會話解析。

默認(rèn)情況下,使用 HttpSessionFilter 解析會話,該過濾器通過 HTTP 標(biāo)頭(使用 Authorization-Info 或 X-Auth-Token 標(biāo)頭)或通過名為 SESSION 的 Cookie 查找會話標(biāo)識符。

您可以通過配置文件(例如 application.yml)中的配置禁用標(biāo)頭解析或 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 解析。您還可以配置標(biāo)頭和 cookie 名稱。

使用會話

可以在控制器方法中使用 Session 類型的參數(shù)來檢索 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 聲明了一個(gè)名為 cart 的 Session 屬性

  2. Session 聲明為方法參數(shù)

  3. 檢索購物車屬性

  4. 否則會創(chuàng)建一個(gè)新的 Cart 實(shí)例并將其存儲在會話中

請注意,由于 Session 被聲明為必需參數(shù),因此要執(zhí)行控制器操作,將創(chuàng)建一個(gè) Session 并將其保存到 SessionStore。

如果您不想創(chuàng)建不必要的會話,請將 Session 聲明為@Nullable,在這種情況下不會創(chuàng)建和保存不必要的會話。例如:

將@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)
}

如果會話已經(jīng)存在,上述方法只會注入一個(gè)新會話。

會話客戶端

如果客戶端是 Web 瀏覽器,則在啟用 cookie 的情況下會話應(yīng)該可以正常工作。但是,對于編程式 HTTP 客戶端,您需要在 HTTP 調(diào)用之間傳播會話 ID。

例如,在前面的示例中調(diào)用 StoreController 的 viewCart 方法時(shí),HTTP 客戶端默認(rèn)接收一個(gè) AUTHORIZATION_INFO 標(biāo)頭。以下示例使用 Spock 測試演示了這一點(diǎn):

檢索 AUTHORIZATION_INFO 標(biāo)頭

 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 標(biāo)頭存在于響應(yīng)中

然后,您可以在后續(xù)請求中傳遞此 AUTHORIZATION_INFO 以重用現(xiàn)有會話:

發(fā)送 AUTHORIZATION_INFO 標(biāo)頭

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. 從響應(yīng)中檢索 AUTHORIZATION_INFO

  2. 然后在后續(xù)請求中作為header發(fā)送

使用@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 在方法上聲明,導(dǎo)致返回值存儲在 Session 中。請注意,在返回值上使用時(shí)必須指定屬性名稱

  2. @SessionValue 用于 @Nullable 參數(shù),這導(dǎo)致以非阻塞方式從 Session 中查找值并在存在時(shí)提供它。在沒有為 @SessionValue 指定值的情況下,會導(dǎo)致使用參數(shù)名稱來查找屬性。

會話事件

您可以注冊 ApplicationEventListener beans 以偵聽位于 io.micronaut.session.event 包中的會話相關(guān)事件。

下表總結(jié)了事件:

表 1. 會話事件
類型 描述

SessionCreatedEvent

創(chuàng)建 Session 時(shí)觸發(fā)

SessionDeletedEvent

刪除會話時(shí)觸發(fā)

SessionExpiredEvent

Session 過期時(shí)觸發(fā)

SessionDestroyedEvent

SessionDeletedEvent 和 SessionExpiredEvent 的父級


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號