Skip to content

Handler 的请求方法与错误响应 ​

使用方法路由约束 HTTP 方法,Handler 负责读取参数并生成业务响应。响应体可以使用 Map,推荐使用 RespBodyVo 统一构造成功与失败响应。下面示例兼容 Java 8,业务数据仍可使用 Map,不执行数据库写入。

java
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 接口写起来非常直观。

java
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 启动后验证:

shell
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 表示业务失败,分发器可能继续其他处理分支。

框架分发顺序见 请求分发,真实服务验证见 HTTP 集成测试。

MIT Licensed