跳到主要内容

使用HTTP解码器进行表单验证

在这个例子中,我们将创建一个异步Servlet,将联系人添加到列表中,解析请求并在 Decoder的帮助下处理表单 验证。 把这个例子看作是MVC模式的一个简明代表。

  • 为了建立一个 Contact 的模型,我们将创建一个带有字段(姓名、年龄、地址)、构造器和字段访问器的普通Java类。
  • 为了简化例子,我们将使用一个 ArrayList 来存储 Contact 对象。 ContactDAO 接口和它的实现被用于此目的。
  • 为了建立一个视图,我们将使用一个单一的HTML文件,在Mustache模板引擎的帮助下进行编译。
  • 一个 AsyncServlet 将被用作控制器。 我们还将添加 RoutingServlet ,用于将一个具体的请求路由到一个特定的端点。
  • Decoder 为你提供解析请求的工具。
See full example on GitHub

这里我们将只考虑 HttpDecoderExample 类与 AsyncServlet ,因为它包含了ActiveJ的特定功能。

创建 HttpDecoderExample

让我们创建 HttpDecoderExample 类,它扩展了 HttpServerLauncher。 通过扩展 HttpServerLauncher ,我们将照顾到 服务器的生命周期和服务管理。 接下来,我们提供两个基于HTTP的自定义解析器 解码器- ADDRESS_DECODERCONTACT_DECODER - 这将被用于验证。

public final class HttpDecoderExample extends HttpServerLauncher {  private static final String SEPARATOR = "-";
  private static final Decoder<Address> ADDRESS_DECODER = Decoder.    of(Address::new,      ofPost("title", "")        .validate(param -> !param.isEmpty(), "Title cannot be empty")    );
  private static final Decoder<Contact> CONTACT_DECODER = Decoder.of(Contact::new,    ofPost("name")      .validate(name -> !name.isEmpty(), "Name cannot be empty"),    ofPost("age")      .map(Mapper.ofEx(Integer::valueOf, "Cannot decode age"))      .validate(age -> age >= 18, "Age must not be less than 18"),    ADDRESS_DECODER.withId("contact-address")  );
note

如果你想了解更多关于模板引擎集成的信息,请查看 这个例子

另外,我们需要创建 applyTemplate(Mustache mustache, Map<String, Object> scopes) 方法,将提供的 Mustache模板填入给定的数据。

private static ByteBuf applyTemplate(Mustache mustache, Map<String, Object> scopes) {  ByteBufWriter writer = new ByteBufWriter();  mustache.execute(writer, scopes);  return writer.getBuf();}

接下来,让我们提供一个 ContactDAOImpl 工厂方法。

@ProvidesContactDAO dao() {  return new ContactDAOImpl();}

现在我们有了创建 AsyncServlet 来处理请求所需的一切。

@ProvidesAsyncServlet mainServlet(Reactor reactor, ContactDAO contactDAO) {  Mustache contactListView = new DefaultMustacheFactory().compile("static/contactList.html");  return RoutingServlet.builder(reactor)    .with("/", request -> HttpResponse.ok200()      .withBody(applyTemplate(contactListView, Map.of("contacts", contactDAO.list())))      .toPromise())    .with(POST, "/add", request -> request.loadBody()      .then($ -> {        //[START REGION_3]        Either<Contact, DecodeErrors> decodedUser = CONTACT_DECODER.decode(request);        //[END REGION_3]        if (decodedUser.isLeft()) {          contactDAO.add(decodedUser.getLeft());        }        Map<String, Object> scopes = new HashMap<>();        scopes.put("contacts", contactDAO.list());        if (decodedUser.isRight()) {          //noinspection ConstantConditions - is 'right', hence not 'null'          scopes.put("errors", decodedUser.getRight().toMap(SEPARATOR));        }        return HttpResponse.ok200()          .withBody(applyTemplate(contactListView, scopes))          .toPromise();      }))    .build();}
  • 这里我们提供了一个 AsyncServlet,它从客户端接收 HttpRequests ,根据路由路径创建 HttpResponses ,并发送。
  • RoutingServlet 内,定义了两条路由路径。 第一个匹配到根路由的请求 "/" - 它只是显示一个联系人列表。 第二个, "/add" - 是一个HTTP POST 方法,用于添加或拒绝添加新用户。 我们将在上述HTTP 解码器 的帮助下,通过使用 decode(request) 方法处理这个请求解析。
@ProvidesAsyncServlet mainServlet(Reactor reactor, ContactDAO contactDAO) {  Mustache contactListView = new DefaultMustacheFactory().compile("static/contactList.html");  return RoutingServlet.builder(reactor)    .with("/", request -> HttpResponse.ok200()      .withBody(applyTemplate(contactListView, Map.of("contacts", contactDAO.list())))      .toPromise())    .with(POST, "/add", request -> request.loadBody()      .then($ -> {        //[START REGION_3]        Either<Contact, DecodeErrors> decodedUser = CONTACT_DECODER.decode(request);        //[END REGION_3]        if (decodedUser.isLeft()) {          contactDAO.add(decodedUser.getLeft());        }        Map<String, Object> scopes = new HashMap<>();        scopes.put("contacts", contactDAO.list());        if (decodedUser.isRight()) {          //noinspection ConstantConditions - is 'right', hence not 'null'          scopes.put("errors", decodedUser.getRight().toMap(SEPARATOR));        }        return HttpResponse.ok200()          .withBody(applyTemplate(contactListView, scopes))          .toPromise();      }))    .build();}
  • 无论是 代表两个可能的数据类型的值(联系, DecodeErrors)。 ** 要么是 (联系) 要么是 (DecodeErrors**) 。 为了确定一个解析是否成功,我们通过使用 isLeft()isRight() 方法来检查它的值。 最后,写下 main() 方法,它将启动我们的应用程序。
public static void main(String[] args) throws Exception {  Launcher launcher = new HttpDecoderExample();  launcher.launch(args);}

运行应用程序

如果你想运行这个例子,你需要从GitHub上 克隆ActiveJ ,然后 将其导入Maven项目。 查阅分支机构 v6.0-beta2。 在运行这个例子之前,构建项目(Ctrl F9 for IntelliJ IDEA)。

然后打开 HttpDecoderExample 类,该类位于 activej/examples/tutorials/decoder 并运行其 main() 方法。 打开你喜欢的浏览器,进入 localhost:8080