本手册正在编制中,目前尚未完善。
如果您想帮助改进它,我们希望您能这样做,请参见README

5 处理程序

本章介绍处理程序,它是 Ratpack 应用程序的基本组件。

1.5 什么是处理程序?

从概念上讲,处理程序 (Handler) 只是一个对处理上下文 (Context) 进行操作的函数。

“Hello world” 处理程序看起来像这样…

import ratpack.core.handling.Handler;
import ratpack.core.handling.Context;

public class Example implements Handler {
  public void handle(Context context) {
      context.getResponse().send("Hello world!");
  }
}

正如我们在上一章中看到的,强制性的启动配置属性之一是提供主处理程序的 HandlerFactory 实现。该工厂创建的处理程序实际上就是应用程序。

这似乎很有限,直到我们认识到处理程序不必是端点(即它可以做的事情不仅仅是生成 HTTP 响应)。处理程序还可以通过多种方式委托给其他处理程序,充当更多的路由功能。事实上,在路由步骤和端点之间没有框架级别(即类型)区别提供了很大的灵活性。这意味着可以通过组合处理程序来构建任何类型的自定义请求处理管道。这种组合方法是 Ratpack 作为工具包而非神奇框架的哲学的典型例子。

本章的其余部分讨论了处理程序的各个方面,这些方面超出了 HTTP 级别关注的问题(例如读取标头、发送响应等),这些问题将在HTTP 章中介绍。

2.5 处理程序委托

如果处理程序不生成响应,则必须委托给另一个处理程序。它可以插入一个或多个处理程序,或者只是推迟到下一个处理程序。

考虑一个根据请求路径路由到两个不同处理程序之一的处理程序。这可以实现为…

import ratpack.core.handling.Handler;
import ratpack.core.handling.Context;

public class FooHandler implements Handler {
  public void handle(Context context) {
    context.getResponse().send("foo");
  }
}

public class BarHandler implements Handler {
  public void handle(Context context) {
    context.getResponse().send("bar");
  }
}

public class Router implements Handler {
  private final Handler fooHandler = new FooHandler();
  private final Handler barHandler = new BarHandler();

  public void handle(Context context) {
    String path = context.getRequest().getPath();
    if (path.equals("foo")) {
      context.insert(fooHandler);
    } else if (path.equals("bar")) {
      context.insert(barHandler);
    } else {
      context.next();
    }
  }
}

委托的关键是context.insert()方法,该方法将控制权传递给一个或多个链接的处理程序。该context.next()方法将控制权传递给下一个链接的处理程序。

考虑以下内容…

import ratpack.core.handling.Handler;
import ratpack.core.handling.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PrintThenNextHandler implements Handler {
  private final String message;
  private final static Logger LOGGER = LoggerFactory.getLogger(PrintThenNextHandler.class);


  public PrintThenNextHandler(String message) {
    this.message = message;
  }

  public void handle(Context context) {
    LOGGER.info(message);
    context.next();
  }
}

public class Application implements Handler {
  public void handle(Context context) {
    context.insert(
      new PrintThenNextHandler("a"),
      new PrintThenNextHandler("b"),
      new PrintThenNextHandler("c")
    );
  }
}

鉴于Application是主处理程序(即启动配置的HandlerFactory返回的处理程序),当此应用程序接收请求时,以下内容将写入System.out

a
b
c

然后呢?当“c”处理程序委托给它的下一个处理程序时会发生什么?最后一个处理程序始终是一个内部处理程序,它通过context.clientError(404)(稍后讨论)发出 HTTP 404 客户端错误。

考虑到插入的处理程序本身可以插入更多处理程序…

import ratpack.core.handling.Handler;
import ratpack.core.handling.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PrintThenInsertOrNextHandler implements Handler {
  private final String message;
  private final Handler[] handlers;
  private final static Logger LOGGER = LoggerFactory.getLogger(PrintThenInsertOrNextHandler.class);

  public PrintThenInsertOrNextHandler(String message, Handler... handlers) {
    this.message = message;
    this.handlers = handlers;
  }

  public void handle(Context context) {
    LOGGER.info(message);
    if (handlers.length == 0) {
      context.next();
    } else {
      context.insert(handlers);
    }
  }
}

public class Application implements Handler {
  public void handle(Context context) {
    context.insert(
      new PrintThenInsertOrNextHandler("a",
        new PrintThenInsertOrNextHandler("a.1"),
        new PrintThenInsertOrNextHandler("a.2"),
      ),
      new PrintThenInsertOrNextHandler("b",
        new PrintThenInsertOrNextHandler("b.1",
          new PrintThenInsertOrNextHandler("b.1.1")
        ),
      ),
      new PrintThenInsertOrNextHandler("c")
    );
  }
}

这将把以下内容写入System.out

a
a.1
a.2
b
b.1
b.1.1
c

这演示了插入处理程序的下一个处理程序如何成为插入的最后一个处理程序的下一个处理程序。您可能需要多次阅读这句话。

您应该能够看到某种嵌套功能出现。这对于组合性很重要,对于在本章后面考虑注册表上下文时也很重要。

此时自然会想到,为典型的 Web 应用程序(即一个将匹配某些请求路径的请求分派到端点的应用程序)构建处理程序结构似乎有很多工作要做。继续阅读。

3.5 构建处理程序链

链 (Chain) 是用于组合(或链接)处理程序的构建器。链本身不响应请求,而是将请求传递给与其关联的处理程序。

再次考虑 Foo-Bar 路由器示例…

import ratpack.core.handling.Chain
import ratpack.core.handling.Handler;
import ratpack.core.handling.Context;
import ratpack.func.Action;

public class FooHandler implements Handler {
    public void handle(Context context) {
        context.getResponse().send("foo");
    }
}

public class BarHandler implements Handler {
    public void handle(Context context) {
        context.getResponse().send("bar");
    }
}

public class RouterChain implements Action<Chain> {
    private final Handler fooHandler = new FooHandler();
    private final Handler barHandler = new BarHandler();

    @Override
    void execute(Chain chain) throws Exception {
        chain.path("foo", fooHandler)
        chain.path("bar", barHandler)
    }
}

这次,我们不必手动检查路径并处理每个代码分支。但是,结果是一样的。这条链最终将被视为处理程序。此处理程序将被设置为从请求中读取路径,首先将其与“foo”进行比较,然后与“bar”进行比较。如果两者中的任何一个匹配,它将context.insert()给定的处理程序。否则,它将调用context.next()

与处理程序一样,上下文的目标不是成为一件魔法。相反,它是使用更灵活的工具(处理程序)构建的强大工具。

1.3.5 添加处理程序和链

因此,链最简单地可以被认为是处理程序列表。将处理程序添加到链列表中最基本的方式是使用all(Handler)方法。“all”一词表示到达链中这一点的所有请求都将流经给定的处理程序。

如果我们稍微伸展一下思路,将链视为处理程序(一个专门用于插入处理程序的处理程序),那么我们也可以将其他链添加到链中。事实上,我们可以,并且为了匹配all(Handler)方法,您可以使用insert(Action<Chain>)方法。同样,这会插入一条所有请求都将被路由的链。

现在,如果链只是处理处理程序列表,按顺序调用每个处理程序,那么它就不是很有用,因此还有一些方法可以执行处理程序和链的条件插入

2.3.5 注册表

待办事项(可以在Chain javadocs 上找到技术定义)

3.3.5 路径绑定

(即 /player/:id)

待办事项(可以在Chain javadocs 上找到技术定义)

4.3.5 路径和方法绑定

待办事项(可以在Chain javadocs 上找到技术定义)