高性能异步HTTP服务器 - Ratpack

Ratpack是一套java应用程序类库,能快速构建高效的HTTP应用,基于高性能的netty引擎。

Ratpack API构建在Java 8上,提供Gradle支持,也可使用任何基于JVM的构建工具。

包是唯一必需的类库:

ratpack-core

可选类库:

ratpack-groovy、ratpack-guice、ratpack-jackson、ratpack-test

示例

import ratpack.test.embed.EmbeddedApp;
import static org.junit.Assert.assertEquals;

public class Example {
 public static void main(String... args) throws Exception {
   EmbeddedApp.fromHandler(ctx -> 
     ctx.render("Hello World!")
   ).test(httpClient -> 
     assertEquals("Hello World!", httpClient.getText())
   );
 }
}

服务器(RatpackServer)

RatpackServer用于启动应用。

import ratpack.server.RatpackServer;
import ratpack.server.ServerConfig;
import java.net.URI;

public class Main {
 public static void main(String... args) throws Exception {
   RatpackServer.start(server -> server
     .serverConfig(ServerConfig.embedded().publicAddress(new URI("http://cs.xieyonghui.com")))
     .registryOf(registry -> registry.add("World!"))
     .handlers(chain -> chain
       .get(ctx -> ctx.render("Hello " + ctx.get(String.class)))
       .get(":name", ctx -> ctx.render("Hello " + ctx.getPathTokens().get("name") + "!"))     
     )
   );
 }
}

定义了两个接口:of()、start(),方法接收一个RatpackServerSpec对象,用于配置Ratpack应用的三个基本面:

服务器配置/server config、基础注册表/base registry、根处理程序/root handler。

服务器配置

ServerConfig定义是必需的,用于设置服务器启动项,使用ServerConfig静态方法创建实例。

基础目录

Ratpack服务器配置的一个重点是基础目录。

基础目录实际上是应用程序文件系统的根,便于移植文件系统。

运行时期间根据基础目录,解析应用程序中所有相对路径,如:静态资源(图像、脚本)的相对路径。

baseDir(Path)方法能设置基础目录,但BaseDir.find()更常见,支持在类路径上查找基础目录,提供更好的跨平台移植性。

此方法会在类路径上的“/.ratpack”目录下搜索资源。

import ratpack.server.ServerConfig;
import ratpack.test.embed.EphemeralBaseDir;
import ratpack.test.embed.EmbeddedApp;

import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;

import static org.junit.Assert.assertEquals;

public class Example {
 public static void main(String... args) throws Exception {
   EphemeralBaseDir.tmpDir().use(baseDir -> {
     baseDir.write("mydir/.ratpack", "");
     baseDir.write("mydir/assets/message.txt", "Hello Ratpack!");
     Path mydir = baseDir.getRoot().resolve("mydir");

     ClassLoader classLoader = new URLClassLoader(new URL[]{mydir.toUri().toURL()});
     Thread.currentThread().setContextClassLoader(classLoader);

     EmbeddedApp.of(serverSpec -> serverSpec
       .serverConfig(c -> c.baseDir(mydir))
       .handlers(chain ->
         chain.files(f -> f.dir("assets"))
       )
     ).test(httpClient -> {
       String message = httpClient.getText("message.txt");
       assertEquals("Hello Ratpack!", message);
     });
   });
 }
}

示例中使用EphemeralBaseDir构造新的类加载器上下文,实际应用时主方法只需调用BaseDir.find(),使用适当的类路径启动Ratpack应用程序。

端口

ServerConfigBuilder port(int port) 设置服务器的端口。默认端口5050。

Ratpack Registry

一个registry是一个按类型存储的对象,应用程序中可能有许多不同的注册表。

import ratpack.handling.Handler;
import ratpack.handling.Context;
import ratpack.registry.Registry;

import static org.junit.Assert.assertTrue;

public class Thing {
  private final String name
  public Thing(String name) { this.name = name; }
  public String getName() { return name; }
}

public class UpstreamHandler implements Handler {
  public void handle(Context context) {
    context.next(Registry.single(new Thing("foo")));
  }
}

public class DownstreamHandler implements Handler {
  public void handle(Context context) {
    assertTrue(context instanceof Registry);
    Thing thing = context.get(Thing.class);
    context.render(thing.getName());
  }
}

import ratpack.test.handling.HandlingResult;
import ratpack.test.handling.RequestFixture;

import static ratpack.handling.Handlers.chain;
import static ratpack.func.Action.noop;

import static org.junit.Assert.assertEquals;

Handler chain = chain(new UpstreamHandler(), new DownstreamHandler());
HandlingResult result = RequestFixture.handle(chain, noop());

assertEquals("foo", result.rendered(String.class));

处理程序

Ratpack Handler是HTTP请求的处理程序,处理程序是可组合的,很少有应用程序只包含一个处理程序。

大多数应用服务器都是复合处理程序,常用handlers(Action)方法创建,使用Chain DSL创建复合处理程序。

启动/停止

应用程序启动时会通知服务器注册表中的所有服务。

应用程序停止时将通知所有服务执行清理程序。

import ratpack.server.RatpackServer;
import ratpack.server.ServerConfig;
import ratpack.service.Service;
import ratpack.service.StartEvent;
import ratpack.service.StopEvent;

import java.util.List;
import java.util.LinkedList;
import static org.junit.Assert.*;

public class Example {

  static class RecordingService implements Service {
    public final List<String> events = new LinkedList<>();

    public void onStart(StartEvent event) {
      events.add("start");
    }

    public void onStop(StopEvent event) {
      events.add("stop");
    }
  }

  public static void main(String... args) throws Exception {
    RecordingService service = new RecordingService();

    RatpackServer server = RatpackServer.of(s -> s
      .serverConfig(ServerConfig.embedded())
      .registryOf(r -> r.add(service))
      .handler(r -> ctx -> ctx.render("ok"))
    );

    assertEquals("[]", service.events.toString());
    server.start();
    assertEquals("[start]", service.events.toString());
    server.reload();
    assertEquals("[start, stop, start]", service.events.toString());
    server.stop();
    assertEquals("[start, stop, start, stop]", service.events.toString());
  }
}

单元测试

ExecHarness测试程序是Ratpack测试支持的一部分。

常用Ratpack Promise异步结构代码执行单元测试。

Ratpack Promise用来避免异步调用时的地狱回调

import com.google.common.io.Files;
import ratpack.test.exec.ExecHarness;
import ratpack.exec.Blocking;

import java.io.File;
import java.nio.charset.StandardCharsets;

import static org.junit.Assert.assertEquals;

public class Example {
 public static void main(String... args) throws Exception {
   File tmpFile = File.createTempFile("ratpack", "test");
   Files.write("Hello World!", tmpFile, StandardCharsets.UTF_8);
   tmpFile.deleteOnExit();

   String content = ExecHarness.yieldSingle(e ->
       Blocking.get(() -> Files.toString(tmpFile, StandardCharsets.UTF_8))
   ).getValueOrThrow();

   assertEquals("Hello World!", content);
 }
}

目标和理念

目标

快速、高效、可扩展,允许应用在不妥协的前提下在进行复杂性进化,利用非阻塞编程降低成本。 在集成其他工具和库时的灵活性,允许应用程序轻松、彻底地进行测试。

不可及的方面

成为一个完全的“全栈”解决方案;在一个简洁的框架中提供所有的功能;为“业务逻辑”而生的框架。