🌐 Vostok.Web
Web 服务器模块
基于 Java NIO Reactor 模式的高性能 HTTP 服务器,支持函数式路由、@VKApi 注解控制器参数绑定、统一 VKWebResult 返回、中间件、WebSocket、SSE、静态资源、GZIP 压缩、CORS、TLS/HTTPS、限流,以及根据实体类自动生成 REST CRUD API;同时支持通过 VKWebConfig.serverFactory(...) 注入自定义 Server Engine。
初始化与启动
Vostok.Web.init() 是静态方法,返回 VostokWeb 实例,路由、中间件等注册方法均为链式实例方法。start() / stop() 是静态方法。
// 方式一:独立初始化(推荐)
Vostok.Web.init(new VKWebConfig()
.port(8080)
.ioThreads(2)
.workerThreads(16)
)
.get("/hello", (req, res) -> res.text("Hello!"))
.post("/users", (req, res) -> {
String body = req.bodyText();
res.status(201).json("{\"created\":true}");
})
.gzip()
.cors();
Vostok.Web.start();
// 方式二:通过 Vostok.init() 统一初始化
Vostok.init(cfg -> cfg
.webConfig(new VKWebConfig().port(8080))
.webSetup(web -> web
.get("/hello", (req, res) -> res.text("Hello!"))
.gzip()
.cors()
)
.webStart(true)
);
自定义 Server Engine
Vostok 核心当前默认内置的是 BUILTIN NIO 引擎,但 Web 语义层已经抽成可复用的 SPI。业务项目如果需要接入 Netty、Undertow 或其他实现,可以通过 serverFactory(...) 注入自定义引擎,而不需要改动 Vostok.Web 的公共 API。
import yueyang.vostok.web.spi.*;
class MyServerEngine implements VKWebServerEngine {
private final VKWebRuntimeSupport runtime;
private boolean started;
MyServerEngine(VKWebRuntimeSupport runtime) {
this.runtime = runtime;
}
@Override
public void start() {
// 1. 业务项目自己负责 socket accept / event loop / HTTP 编解码 / TLS
// 2. 普通 HTTP 请求复用 Vostok 语义:
// VKWebDispatchResult result = runtime.dispatchHttp(req);
// 3. WebSocket 元数据复用:
// runtime.findWebSocket(path) / runtime.wsRegistry()
started = true;
}
@Override
public void stop() { started = false; }
@Override
public boolean isStarted() { return started; }
@Override
public int port() { return 8080; }
}
Vostok.Web.init(new VKWebConfig()
.port(8080)
.serverFactory((cfg, runtime) -> new MyServerEngine(runtime))
)
.get("/ping", (req, res) -> res.text("pong"));
自定义引擎的职责边界
业务自定义引擎需要自己负责:socket accept / event loop / HTTP 编解码 / TLS / WebSocket / SSE / 文件传输与背压。可以直接复用 Vostok 的:
router、middleware、rateLimit、@VKApi MVC、VKWebResult、metrics、VKWsRegistry。
路由
HTTP 方法路由
VostokWeb web = Vostok.Web.init(new VKWebConfig().port(8080));
// 快捷方法:GET / POST
web.get("/items", handler);
web.post("/items", handler);
// 通用方法:任意 HTTP 方法
web.route("PUT", "/items/{id}", handler);
web.route("DELETE", "/items/{id}", handler);
web.route("PATCH", "/items/{id}", handler);
注解控制器(@VKApi)
除了函数式 VKHandler,也可以用注解方式定义 API。类上使用 @VKApi,方法上使用 @VKGet/@VKPost/@VKPut/@VKDelete/@VKPatch。
import yueyang.vostok.Vostok;
import yueyang.vostok.web.http.VKResponse;
import yueyang.vostok.web.mvc.VKMvcConfig;
import yueyang.vostok.web.mvc.VKWebResult;
import yueyang.vostok.web.mvc.annotation.*;
class CreateUserReq {
public String name;
}
@VKApi("/api/users")
class UserApi {
@VKGet("/detail/{id}")
public java.util.Map<String, Object> detail(
@VKPath("id") long id,
@VKQuery(value = "page", required = false, defaultValue = "1") int page) {
return java.util.Map.of("id", id, "page", page);
}
@VKPost("/create")
public VKWebResult<String> create(@VKBody CreateUserReq req) {
return VKWebResult.of(201, null, "created-" + req.name);
}
@VKGet("/manual")
public void manual(VKResponse res) {
// void 返回代表手动响应模式,不自动包装 VKWebResult
res.status(202).text("manual");
}
}
Vostok.Web.init(8080)
.mvcConfig(VKMvcConfig.defaults().exposeExceptionMessage(false))
.controller(new UserApi())
.controllers("com.example.api");
路径拼接规则
@VKApi("/api/users") + @VKGet("/list") => /api/users/list@VKApi 不写前缀时,方法注解路径应写完整路径(如 /api/users/list)。
参数绑定注解
| 注解 | 来源 | 示例 |
|---|---|---|
@VKPath("id") | 路径参数 | /users/{id} |
@VKQuery("page") | Query 参数(支持 defaultValue) | ?page=1 |
@VKHeader("X-Token") | 请求头 | X-Token: abc |
@VKCookie("sid") | Cookie | Cookie: sid=s-001 |
@VKBody | JSON 请求体 | 绑定到 POJO |
@VKForm("name") | 表单字段 | multipart/form-data 或 form |
@VKFile("file") | 单文件上传 | VKUploadedFile |
@VKFiles("files") | 多文件上传 | List<VKUploadedFile> |
VKRequest / VKResponse | 原生对象注入 | 参数可直接声明类型,无需注解 |
自动返回与 VKWebResult<T>
@VKApi 路由默认启用自动返回包装。普通返回值会自动包装成 VKWebResult.ok(data),并补齐链路字段。
| 字段 | 类型 | 说明 |
|---|---|---|
statusCode | int | 业务状态码,同时作为 HTTP 状态码输出 |
errorMessage | String | 错误信息;成功时一般为 null |
data | T | 业务返回数据 |
requestCostMs | long | 请求耗时(毫秒) |
traceId | String | 链路追踪 ID(来自请求) |
requestTime | long | 请求进入处理器时间(epochMillis) |
responseTime | long | 响应写出前时间(epochMillis) |
// 返回普通对象:自动包装为 VKWebResult.ok(...)
{
"statusCode": 200,
"errorMessage": null,
"data": {"id": 9, "page": 1},
"requestCostMs": 3,
"traceId": "trace-abc",
"requestTime": 1741075200000,
"responseTime": 1741075200003
}
路径参数 & 查询参数
web.get("/users/{id}", (req, res) -> {
String id = req.param("id"); // 路径参数
res.json("{\"id\":" + id + "}");
});
web.get("/search", (req, res) -> {
String q = req.queryParam("q"); // 查询参数(String)
String page = req.queryParam("page"); // 需自行转型
int pageNum = page != null ? Integer.parseInt(page) : 1;
res.text("q=" + q + " page=" + pageNum);
});
// 通配路径:匹配 /files/a/b/c
web.get("/files/{*path}", (req, res) -> {
String path = req.param("path");
res.text("path=" + path);
});
VKHandler 接口
// 函数式接口,可用 lambda 实现
@FunctionalInterface
public interface VKHandler {
void handle(VKRequest req, VKResponse res);
}
VKRequest — 请求对象
| 方法 | 返回值 | 说明 |
|---|---|---|
method() | String | HTTP 方法,如 "GET"、"POST" |
path() | String | 请求路径,如 "/users/1" |
query() | String | 原始查询字符串,如 "page=1&size=10" |
version() | String | HTTP 版本,如 "HTTP/1.1" |
header(name) | String | 请求头(大小写不敏感) |
headers() | Map<String,String> | 所有请求头(只读) |
body() | byte[] | 原始请求体字节 |
bodyText() | String | 请求体 UTF-8 字符串 |
param(name) | String | 路径参数,如 {id} 对应 param("id") |
queryParam(name) | String | 查询参数(URL 解码后) |
queryParams() | Map<String,String> | 所有查询参数 |
cookie(name) | String | Cookie 值 |
cookies() | Map<String,String> | 所有 Cookie |
traceId() | String | 请求追踪 ID |
remoteAddress() | InetSocketAddress | 客户端地址(含 IP 和端口) |
keepAlive() | boolean | 是否为 Keep-Alive 连接 |
isMultipart() | boolean | 是否为 multipart/form-data 请求 |
formField(name) | String | 表单字段值 |
formFields() | Map<String,String> | 所有表单字段 |
file(name) | VKUploadedFile | 获取第一个上传文件 |
files(name) | List<VKUploadedFile> | 获取同名所有上传文件 |
allFiles() | Collection<VKUploadedFile> | 所有上传文件 |
VKResponse — 响应对象
| 方法 | 返回值 | 说明 |
|---|---|---|
status(int) | VKResponse | 设置 HTTP 状态码(链式) |
status() | int | 获取当前状态码(默认 200) |
header(name, value) | VKResponse | 设置响应头(链式) |
headers() | Map<String,String> | 获取所有响应头 |
text(String) | VKResponse | 设置纯文本响应体(Content-Type: text/plain; charset=utf-8) |
json(String) | VKResponse | 设置 JSON 字符串响应体(Content-Type: application/json; charset=utf-8) |
body(byte[]) | VKResponse | 设置原始字节响应体 |
body() | byte[] | 获取响应体字节 |
cookie(name, value) | VKResponse | 设置简单 Cookie(链式) |
cookie(VKCookie) | VKResponse | 设置带完整属性的 Cookie(链式) |
deleteCookie(name) | VKResponse | 删除 Cookie(设置 Max-Age=0,链式) |
file(Path, long) | VKResponse | 以文件方式响应(零拷贝传输) |
sseResponse(Consumer<VKSseEmitter>) | VKResponse | 切换为 SSE 模式(框架内部使用) |
常用响应示例
// 200 纯文本
res.text("Hello World");
// 200 JSON 字符串
res.json("{\"code\":0,\"msg\":\"ok\"}");
// 自定义状态码 + JSON
res.status(201).json("{\"created\":true}");
// 返回二进制内容(图片等)
res.status(200)
.header("Content-Type", "image/png")
.body(imageBytes);
// 404
res.status(404).text("Not Found");
// 302 重定向
res.status(302).header("Location", "/login").text("");
Cookie 操作
// 简单 Cookie
res.cookie("token", "abc123");
// 带完整属性的 Cookie
res.cookie(new VKCookie("session", "xyz")
.httpOnly(true)
.secure(true)
.maxAge(3600)
.path("/")
.sameSite(VKCookie.SameSite.LAX)
);
// 删除 Cookie
res.deleteCookie("session");
文件上传
web.post("/upload", (req, res) -> {
if (!req.isMultipart()) {
res.status(400).text("Not a multipart request");
return;
}
// 获取单个文件
VKUploadedFile file = req.file("avatar");
if (file != null) {
System.out.println("文件名: " + file.fileName());
System.out.println("类型: " + file.contentType());
System.out.println("大小: " + file.size());
System.out.println("内存: " + file.inMemory());
// 读取内容
byte[] bytes = file.bytes();
// 保存到磁盘
file.transferTo(Path.of("/uploads/" + file.fileName()));
}
// 同名多文件
List<VKUploadedFile> images = req.files("images");
// 表单字段
String desc = req.formField("description");
res.json("{\"uploaded\":" + (file != null) + "}");
});
中间件
// 全局中间件(按注册顺序执行)
web.use((req, res, chain) -> {
String token = req.header("Authorization");
if (token == null) {
res.status(401).json("{\"error\":\"unauthorized\"}");
return; // 不调用 chain.next() 即拦截请求
}
chain.next(req, res); // 放行到下一个中间件或处理器
});
// 请求日志中间件示例
web.use((req, res, chain) -> {
long start = System.currentTimeMillis();
chain.next(req, res);
System.out.printf("%s %s → %d (%dms)%n",
req.method(), req.path(), res.status(),
System.currentTimeMillis() - start);
});
说明
VKMiddleware 为函数式接口:void handle(VKRequest req, VKResponse res, VKChain chain)。调用
chain.next(req, res) 将请求传递给下一个处理器;不调用则直接截断请求。
CORS
// 快捷开启(允许所有来源,默认方法:GET/POST/PUT/DELETE/OPTIONS/PATCH)
web.cors();
// 自定义 CORS 配置
web.cors(new VKCorsConfig()
.allowOrigins("https://example.com", "https://app.example.com")
.allowMethods("GET, POST, PUT, DELETE")
.allowHeaders("Content-Type, Authorization")
.allowCredentials(true)
.maxAge(3600) // Preflight 缓存秒数
.exposeHeaders("X-Request-Id")
);
GZIP 压缩
// 默认配置(响应 >= 256 字节,且 Content-Type 为 text/* / application/json / application/xml 时压缩)
web.gzip();
// 自定义配置
web.gzip(new VKGzipConfig()
.minBytes(512) // 触发压缩的最小字节数
.compressibleTypes(List.of("text/", "application/json"))
);
限流(令牌桶)
// 全局限流:每秒最多 1000 个请求(按客户端 IP 区分)
web.rateLimit(new VKRateLimitConfig()
.capacity(1000) // 令牌桶容量
.refillTokens(1000) // 每次补充令牌数
.refillPeriodMs(1000) // 补充周期(ms)
.keyStrategy(VKRateLimitKeyStrategy.IP) // 限流 key 策略
.rejectStatus(429)
.rejectBody("Too Many Requests")
);
// 针对指定路由限流
web.rateLimit(VKHttpMethod.POST, "/login",
new VKRateLimitConfig()
.capacity(10)
.refillTokens(10)
.refillPeriodMs(60000) // 每分钟 10 次
.rejectStatus(429)
);
// 按自定义 key 限流(如用户 ID)
web.rateLimit(new VKRateLimitConfig()
.capacity(100)
.refillTokens(100)
.refillPeriodMs(1000)
.keyStrategy(VKRateLimitKeyStrategy.CUSTOM)
.customKeyResolver(req -> req.header("X-User-Id"))
);
VKRateLimitKeyStrategy 枚举
| 枚举值 | 说明 |
|---|---|
IP | 按客户端 IP 限流(默认) |
TRACE_ID | 按请求追踪 ID 限流 |
HEADER | 按指定请求头值限流,配合 headerName() 使用 |
CUSTOM | 自定义 key,配合 customKeyResolver() 使用 |
静态资源
// 将 URL /static/** 映射到本地 ./public 目录
web.staticDir("/static", "./public");
// 示例:访问 /static/js/app.js → 读取 ./public/js/app.js
自动 CRUD API
扫描 @VKEntity 实体类,自动为每个实体生成标准 REST 接口(依赖 Data 模块已初始化)。
// 使用 Data 模块扫描的默认包(无需再传包名)
web.autoCrudApi();
// 指定包 + RESTful 风格(默认)
web.autoCrudApi(VKCrudStyle.RESTFUL, "com.example.entity");
// 传统风格(/entity/list、/entity/add 等)
web.autoCrudApi(VKCrudStyle.TRADITIONAL, "com.example.entity");
以 User 实体(表名 users)为例,RESTFUL 模式自动生成:
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /users | 查询全部 |
GET | /users/{id} | 按主键查询 |
POST | /users | 新增 |
PUT | /users/{id} | 按主键更新 |
DELETE | /users/{id} | 按主键删除 |
WebSocket
基本用法
web.websocket("/ws/chat", new VKWebSocketHandler() {
@Override
public void onOpen(VKWebSocketSession session) {
System.out.println("connected: " + session.id());
session.sendText("欢迎加入!");
}
@Override
public void onText(VKWebSocketSession session, String text) {
// 向当前路径所有连接广播
Vostok.Web.websocketBroadcast("/ws/chat", text);
}
@Override
public void onBinary(VKWebSocketSession session, byte[] data) {
session.sendBinary(data);
}
@Override
public void onClose(VKWebSocketSession session, int code, String reason) {
System.out.println("closed: " + session.id() + " code=" + code);
}
@Override
public void onError(VKWebSocketSession session, Throwable error) {
System.err.println("error: " + error.getMessage());
}
});
房间与分组广播
// 连接加入房间 / 分组(在 onOpen 或消息处理中调用)
session.joinRoom("room-1");
session.joinGroup("vip");
// 会话自行广播(返回发送成功的连接数)
session.broadcastRoom("room-1", "hello room");
session.broadcastGroup("vip", "vip msg");
session.broadcastRoomAndGroup("room-1", "vip", "msg");
// 全局静态广播(可在任意线程调用)
Vostok.Web.websocketBroadcast("/ws/chat", "全员通知");
Vostok.Web.websocketBroadcastRoom("/ws/chat", "room-1", "房间通知");
Vostok.Web.websocketBroadcastGroup("/ws/chat", "vip", "VIP 通知");
Vostok.Web.websocketBroadcastRoomAndGroup("/ws/chat", "room-1", "vip", "msg");
// 二进制广播
Vostok.Web.websocketBroadcastBinary("/ws/chat", data);
Vostok.Web.websocketBroadcastRoomBinary("/ws/chat", "room-1", data);
Vostok.Web.websocketBroadcastGroupBinary("/ws/chat", "vip", data);
WebSocket 握手鉴权
web.websocket("/ws/secure", new VKWebSocketConfig()
.handshakeAuthenticator(ctx -> {
String token = ctx.queryParam("token");
if (token == null || !isValid(token)) {
return VKWsAuthResult.reject(401, "Unauthorized");
}
// 通过后,attributes 会写入 session,可用 session.getAttribute() 获取
return VKWsAuthResult.allow(Map.of("userId", parseUserId(token)));
})
.pingIntervalMs(30000)
.idleTimeoutMs(120000),
handler
);
VKWebSocketSession 方法
| 方法 | 说明 |
|---|---|
id() | 会话唯一 ID |
path() | WebSocket 端点路径 |
traceId() | 请求追踪 ID |
remoteAddress() | 客户端地址(InetSocketAddress) |
isOpen() | 连接是否仍开放 |
sendText(String) | 向此会话发送文本帧 |
sendBinary(byte[]) | 向此会话发送二进制帧 |
ping(byte[]) | 发送 Ping 帧 |
close() | 正常关闭(code=1000) |
close(int code, String reason) | 以指定 code 关闭连接 |
joinRoom(String) | 加入指定房间,返回是否成功 |
leaveRoom(String) | 离开指定房间 |
joinGroup(String) | 加入指定分组 |
leaveGroup(String) | 离开指定分组 |
broadcastRoom(room, msg) | 广播文本到指定房间,返回发送数 |
broadcastGroup(group, msg) | 广播文本到指定分组,返回发送数 |
broadcastRoomAndGroup(room, group, msg) | 广播到房间且属于分组的连接 |
setAttribute(key, value) | 设置会话属性(链式) |
getAttribute(key) | 获取会话属性 |
getAttribute(key, Class<T>) | 获取会话属性并转型 |
removeAttribute(key) | 移除会话属性 |
hasAttribute(key) | 判断属性是否存在 |
attributes() | 获取所有属性(只读 Map) |
Server-Sent Events (SSE)
// 注册 SSE 端点:handler 在建立连接时调用,emitter 可保存到集合用于后续推送
web.sse("/events", (req, emitter) -> {
// 发送仅含 data 的事件
emitter.send("connected");
// 发送带 event 类型的事件
emitter.send("update", "{\"count\":1}");
// 发送完整事件(event + data + id)
emitter.send("update", "{\"count\":2}", "evt-001");
// 检查连接状态
if (emitter.isOpen()) {
emitter.send("alive", "ping");
}
// 主动关闭(幂等,多次调用安全)
emitter.close();
});
SSE 事件格式说明
框架自动处理 SSE 协议格式,调用 emitter.send(data) 时不需要手动拼 "data: ...\n\n",框架会按 RFC 8895 格式化。多行 data 会自动按行分割。
HTTPS / TLS
Vostok.Web.init(new VKWebConfig()
.port(8443)
.tls(new VKTlsConfig()
.keyStorePath("/path/to/server.p12") // KeyStore 文件路径(必填)
.keyStorePassword("changeit") // KeyStore 密码(必填)
.keyStoreType("PKCS12") // 默认 PKCS12,也支持 JKS
.sslProtocol("TLS") // 默认 TLS
.enabledProtocols("TLSv1.2", "TLSv1.3") // 指定启用的协议版本
.clientAuth(false) // 是否要求客户端证书
)
);
健康检查 & Metrics
// 内置健康检查端点:GET /actuator/health
// 返回:{"status":"UP","connections":42}
web.health();
// 自定义路径
web.health("/_ping");
// 内置 Metrics 端点:GET /actuator/metrics
// 返回:{"requests":1000,"errors":2,"activeConnections":42,"avgResponseMs":5.2}
web.metrics();
// 自定义路径
web.metrics("/admin/metrics");
错误处理
// VKErrorHandler 签名:handle(Throwable error, VKRequest req, VKResponse res)
web.error((error, req, res) -> {
System.err.println("Error on " + req.path() + ": " + error.getMessage());
res.status(500).json(
"{\"error\":\"" + error.getMessage() + "\"}"
);
});
@VKApi 路由异常处理说明
@VKApi 路由内部会把绑定错误统一包装为 400、执行异常统一包装为 500 的 VKWebResult。传统
get/post/route 注册的函数式路由仍按原逻辑走全局 web.error(...)。
生命周期
// 启动服务器(阻塞直到服务器绑定端口完成)
Vostok.Web.start();
// 检查是否已启动
boolean running = Vostok.Web.started();
// 获取实际监听端口(配置 port=0 时由 OS 分配)
int port = Vostok.Web.port();
// 停止服务器
Vostok.Web.stop();
配置参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| 基础 | |||
| port | int | 8080 | 监听端口(0 表示随机分配) |
| ioThreads | int | 1 | NIO Selector 线程数 |
| workerThreads | int | CPU核数×2(最小2) | 业务处理线程池大小 |
| workerQueueSize | int | 10000 | Worker 任务队列容量 |
| backlog | int | 1024 | TCP 连接队列长度 |
| maxConnections | int | 10000 | 最大并发连接数 |
| serverFactory(VKWebServerFactory) | VKWebServerFactory | null | 自定义 Server Engine 工厂;为空时使用内建 NIO 引擎 |
| HTTP | |||
| readBufferSize | int | 16384 | 读缓冲区大小(字节) |
| maxHeaderBytes | int | 32768 | 请求头最大字节数 |
| maxBodyBytes | int | 4194304 (4MB) | 请求体最大字节数 |
| keepAliveTimeoutMs | int | 30000 | Keep-Alive 连接超时(ms) |
| readTimeoutMs | int | 15000 | 读取超时(ms) |
| accessLogEnabled | boolean | true | 是否开启访问日志 |
| accessLogQueueSize | int | 8192 | 访问日志异步队列大小 |
| 文件上传(Multipart) | |||
| multipartEnabled | boolean | true | 是否允许 multipart 文件上传 |
| multipartTempDir | String | 系统临时目录/vostok-upload | 超内存阈值的文件临时目录 |
| multipartInMemoryThresholdBytes | int | 65536 (64KB) | 小于此值的文件保留在内存 |
| multipartMaxParts | int | 128 | 单请求最大 multipart 部分数 |
| multipartMaxFileSizeBytes | long | 16777216 (16MB) | 单个文件最大大小 |
| multipartMaxTotalBytes | long | 33554432 (32MB) | 所有文件总大小上限 |
| 限流 | |||
| rateLimitLogEnabled | boolean | true | 是否记录限流日志 |
| rateLimitCleanupIntervalMs | int | 60000 | 限流 key 定期清理间隔(ms) |
| WebSocket | |||
| websocketEnabled | boolean | true | 是否允许 WebSocket 升级 |
| websocketMaxFramePayloadBytes | int | 1048576 (1MB) | 单帧最大负载字节数 |
| websocketMaxMessageBytes | int | 4194304 (4MB) | 组合消息最大字节数 |
| websocketMaxPendingFrames | int | 1024 | 发送队列最大帧数 |
| websocketMaxPendingBytes | int | 8388608 (8MB) | 发送队列最大字节数 |
| websocketPingIntervalMs | int | 30000 | Ping 心跳间隔(ms) |
| websocketPongTimeoutMs | int | 10000 | Pong 超时(ms) |
| websocketIdleTimeoutMs | int | 120000 | 空闲连接超时(ms) |
| TLS | |||
| tls(VKTlsConfig) | VKTlsConfig | null | TLS 配置,null 表示明文 HTTP |
API 速查
初始化与生命周期(静态方法)
| 方法 | 返回值 | 说明 |
|---|---|---|
Vostok.Web.init(int port) | VostokWeb | 快速初始化,返回链式实例 |
Vostok.Web.init(VKWebConfig) | VostokWeb | 完整初始化,返回链式实例 |
Vostok.Web.start() | void | 启动服务器 |
Vostok.Web.stop() | void | 停止服务器 |
Vostok.Web.started() | boolean | 是否已启动 |
Vostok.Web.port() | int | 实际监听端口 |
路由与中间件(实例方法,链式)
| 方法 | 说明 |
|---|---|
get(path, VKHandler) | 注册 GET 路由 |
post(path, VKHandler) | 注册 POST 路由 |
route(method, path, VKHandler) | 注册任意 HTTP 方法路由 |
controller(Object) | 注册单个 @VKApi 控制器实例 |
controllers(String... basePackages) | 扫描包并注册 @VKApi 控制器 |
mvcConfig(VKMvcConfig) | 设置注解路由行为(自动包装/异常暴露等) |
use(VKMiddleware) | 注册全局中间件 |
error(VKErrorHandler) | 注册全局错误处理器 |
cors() | 启用默认 CORS 中间件 |
cors(VKCorsConfig) | 启用自定义 CORS 中间件 |
gzip() | 启用默认 GZIP 压缩 |
gzip(VKGzipConfig) | 启用自定义 GZIP 压缩 |
rateLimit(VKRateLimitConfig) | 设置全局限流 |
rateLimit(VKHttpMethod, path, VKRateLimitConfig) | 设置单路由限流 |
staticDir(urlPrefix, directory) | 映射静态资源目录 |
autoCrudApi() | 自动生成 CRUD API(默认包) |
autoCrudApi(String... packages) | 自动生成 CRUD API(指定包,RESTFUL) |
autoCrudApi(VKCrudStyle, String... packages) | 自动生成 CRUD API(指定风格与包) |
websocket(path, VKWebSocketHandler) | 注册 WebSocket 端点(默认配置) |
websocket(path, VKWebSocketConfig, VKWebSocketHandler) | 注册 WebSocket 端点(自定义配置) |
sse(path, VKSseHandler) | 注册 SSE 端点 |
health() | 注册 GET /actuator/health 端点 |
health(String path) | 注册自定义路径健康检查端点 |
metrics() | 注册 GET /actuator/metrics 端点 |
metrics(String path) | 注册自定义路径 Metrics 端点 |
MVC 配置(VKMvcConfig)
| 配置项 | 默认值 | 说明 |
|---|---|---|
autoWrapEnabled | true | 是否对非 VKWebResult 返回值自动包装 |
internalErrorMessage | "Internal Server Error" | 控制器异常的默认错误文案 |
exposeExceptionMessage | false | 是否对外暴露异常原始 message(建议生产关闭) |
failFastOnControllerLoad | true | 控制器扫描/注册失败时是否立即抛错 |
bindErrorStatus | 400 | 参数绑定失败的 HTTP 状态码 |
internalErrorStatus | 500 | 控制器执行异常的 HTTP 状态码 |
WebSocket 全局广播(静态方法)
| 方法 | 说明 |
|---|---|
websocketBroadcast(path, text) | 向路径所有连接广播文本 |
websocketBroadcastRoom(path, room, text) | 向指定房间广播文本 |
websocketBroadcastGroup(path, group, text) | 向指定分组广播文本 |
websocketBroadcastRoomAndGroup(path, room, group, text) | 向房间且属于分组的连接广播文本 |
websocketBroadcastBinary(path, data) | 向路径所有连接广播二进制 |
websocketBroadcastRoomBinary(path, room, data) | 向指定房间广播二进制 |
websocketBroadcastGroupBinary(path, group, data) | 向指定分组广播二进制 |
websocketBroadcastRoomAndGroupBinary(path, room, group, data) | 向房间且属于分组的连接广播二进制 |