Ratpack|高性能异步HTTP服务器

Ratpack是一套Java HTTP应用程序类库,可以快速实现高效的HTTP应用程序,Ratpack基于高性能事件驱动的Netty引擎构建。

Ratpack API构建于Java 8之上,虽然Ratpack项目通过插件提供Gradle支持,但构建Ratpack应用程序,可以使用任何基于JVM的构建工具。ratpack-core包是唯一必需的类库,而ratpack-groovy、ratpack-guice、ratpack-jackson、ratpack-test类库是可选的。

Ratpack “Hello World”示例

--java--
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())
   );
 }
}

Ratpack服务器(RatpackServer)

RatpackServer是Ratpack切入点,用来启动应用程序。

--java--
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") + "!"))     
     )
   );
 }
}

RatpackServer定义了两个接口,of()和start()静态方法,该方法接受一个RatpackServerSpec对象,用于配置Ratpack应用程序的三个基本方面(服务器配置/server config、基础注册表/base registry、根处理程序/root handler)。

Ratpack服务器配置

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

Ratpack基础目录

Ratpack服务器配置的一个重要方面是基础目录。基础目录实际上是应用程序文件系统的根,方便移植文件系统。在运行时期间根据基础目录,解析应用程序中的所有相对路径。如:静态资源(图像、脚本)的相对路径。

baseDir(Path)方法能够设置基础目录,但使用BaseDir.find()更常见,它支持在类路径上查找基础目录,从而提供更好的跨平台移植性。这个方法会在类路径上的“/.ratpack”目录下搜索资源。

--java--
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应用程序。

Ratpack服务器端口

ServerConfigBuilder port(int port) 方法能够设置服务器的端口。如果没有配置,Ratpack默认端口为5050。

Ratpack Registry

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

--java--
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 (处理程序)

Ratpack Handler是HTTP请求处理程序,处理程序是可组合的,很少有应用程序只包含一个处理程序。大多数应用程序服务器都是复合处理程序,通常使用handlers(Action)方法创建,使用Chain DSL创建复合处理程序。

Ratpack服务器启动和停止

Ratpack应用程序启动时,将通知服务器注册表中的所有服务。同样,当应用程序停止时,Ratpack将通知所有服务并让它们执行清理程序。

--java--
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());
  }
}

Ratpack 单元测试

ExecHarness测试程序,ExecHarness是Ratpack测试支持的一部分。它通常使用Ratpack Promise异步结构代码进行单元测试。Ratpack Promise主要用来避免异步调用时的地狱回调写法。

--java--
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);
 }
}

Ratpack的理念和目标

Ratpack的目标

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

Ratpacks不可及的方面

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