Handler 的请求方法与错误响应
使用方法路由约束 HTTP 方法,Handler 负责读取参数并生成业务响应。响应体可以使用 Map,推荐使用 RespBodyVo 统一构造成功与失败响应。下面示例兼容 Java 8,业务数据仍可使用 Map,不执行数据库写入。
package example;
import java.util.Collections;
import nexus.io.model.body.RespBodyVo;
import nexus.io.context.BootConfiguration;
import nexus.io.tio.boot.TioApplication;
import nexus.io.tio.boot.server.TioBootServer;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.server.util.Resps;
public class HttpContractExample {
public static void main(String[] args) {
BootConfiguration configuration = () -> {
TioBootServer.me().getRequestRouter().get("/api/items", HttpContractExample::items);
};
TioApplication.run(configuration, args);
}
public static HttpResponse items(HttpRequest request) {
String raw = request.getParam("pageSize");
int size;
try {
size = raw == null ? 20 : Integer.parseInt(raw);
} catch (NumberFormatException e) {
return json(request, 400, RespBodyVo.fail("Invalid pageSize"));
}
if (size < 1 || size > 100) {
return json(request, 400, RespBodyVo.fail("pageSize must be 1..100"));
}
return json(request, 200, RespBodyVo.ok(Collections.singletonMap("pageSize", size)));
}
private static HttpResponse json(HttpRequest request, int status, Object body) {
HttpResponse response = Resps.json(request, body);
response.setStatus(status);
return response;
}
}
启动参数可使用 --server.port=8100 --server.context-path=/admin。示例外部路径为 /admin/api/items,验证正常查询、非法数字、越界页大小及 POST 请求分别返回 200、400、400、405。405 与 Allow 由框架生成,该响应不含示例的业务 JSON 包装。
按请求方法注册路由
HttpRequestRouter 为常用 HTTP 方法提供了对应的注册方法:get、post、put、delete、patch、options、head,也可以用 add(HttpMethod, path, handler) 指定任意方法。同一路径可以按方法注册不同的 Handler,RESTful 接口写起来非常直观。
package example;
import java.util.Collections;
import nexus.io.context.BootConfiguration;
import nexus.io.model.body.RespBodyVo;
import nexus.io.tio.boot.TioApplication;
import nexus.io.tio.boot.server.TioBootServer;
import nexus.io.tio.http.common.HttpMethod;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.server.router.HttpRequestRouter;
import nexus.io.tio.http.server.util.Resps;
public class RestRouteExample {
public static void main(String[] args) {
BootConfiguration configuration = () -> {
HttpRequestRouter r = TioBootServer.me().getRequestRouter();
r.get("/api/items", RestRouteExample::list);
r.post("/api/items", RestRouteExample::create);
r.get("/api/items/{id:[0-9]+}", RestRouteExample::detail);
r.put("/api/items/{id:[0-9]+}", RestRouteExample::update);
r.patch("/api/items/{id:[0-9]+}", RestRouteExample::update);
r.delete("/api/items/{id:[0-9]+}", RestRouteExample::remove);
r.add(HttpMethod.POST, "/api/items/{id:[0-9]+}/copy", RestRouteExample::copy);
};
TioApplication.run(configuration, args);
}
public static HttpResponse list(HttpRequest request) {
return Resps.json(request, RespBodyVo.ok(Collections.emptyList()));
}
public static HttpResponse create(HttpRequest request) {
return Resps.json(request, RespBodyVo.ok(request.getBodyString()));
}
public static HttpResponse detail(HttpRequest request) {
return Resps.json(request, RespBodyVo.ok(Collections.singletonMap("id", request.getLong("id"))));
}
public static HttpResponse update(HttpRequest request) {
return Resps.json(request, RespBodyVo.ok(Collections.singletonMap("updated", request.getLong("id"))));
}
public static HttpResponse remove(HttpRequest request) {
return Resps.json(request, RespBodyVo.ok(Collections.singletonMap("deleted", request.getLong("id"))));
}
public static HttpResponse copy(HttpRequest request) {
return Resps.json(request, RespBodyVo.ok(Collections.singletonMap("copied", request.getLong("id"))));
}
}
使用 --server.port=8100 启动后验证:
curl http://localhost:8100/api/items
curl -X PUT http://localhost:8100/api/items/1
curl -X DELETE http://localhost:8100/api/items/1
curl -X POST http://localhost:8100/api/items/1/copy
路径模板 {id:[0-9]+} 只匹配数字,/api/items/detail 这类固定路径可以和它并存;精确路径优先于模板。同一路径未注册的方法会自动返回 405,并在 Allow 头中列出可用方法。匹配规则详见 请求分发。
JSON 请求体
使用 request.getBodyString() 读取文本后,由项目选定的 JSON 库解析。校验根节点是否为对象、字段类型及允许字段,解析失败返回明确的 400。不要让客户端提交的用户 ID 直接覆盖认证结果,也不要把未校验字段拼接到 SQL 中。
服务端应限制请求体字节大小;读取后再检查字符串长度只能补充业务限制,不能替代接收层限制。文件上传与普通 JSON 请求的大小配置分别按 上传 和 配置参数 核对。
错误与空结果
- 推荐使用
RespBodyVo.ok(data)/RespBodyVo.fail(msg),默认业务码分别为 1 / 0;也可以使用 Map 自定义响应结构。Resps.json负责序列化,不会自动添加统一包装。 - 业务码与 HTTP 状态独立,
RespBodyVo.fail(...)不会自动把 HTTP 状态设置为 400,仍应显式设置。详见 RespBodyVo。 - 没有记录可返回约定的空列表;参数非法返回 400;身份与权限失败分别返回 401 / 403。
- 未预期异常应由统一异常处理记录并返回通用错误,不将堆栈和数据库连接信息回传给客户端。
- 不通过返回 null 表示业务失败,分发器可能继续其他处理分支。
