Ratpack



Ratpack

0 6


presentation-ratpack-under-the-hood-gr8conf-us-2015


On Github ratpack / presentation-ratpack-under-the-hood-gr8conf-us-2015

Ratpack

Under the hood.

Luke Daley

Creator.

What?

  • Execution (non blocking)

  • Promise transformations

  • Handler composition

Blocking?

Any kind of waiting.

  • IO (e.g. InputStream)

  • Lock.lock()

  • JDBC

  • Common HTTP clients

  • most APIs

Why is blocking “bad”?

More threads than there are cores creates contention.

Blocking and threads

Each concurrent piece of work requires a separate thread.

100 concurrent requests =~ 100 threads

Performance?

Not the whole story.

Really… low & predictably scalable resource usage.

More…

Non blocking?

Relinquish the thread when you have to wait.

Get a thread back when you can do more work.

Use it or lose it.

Blocking

Render the contents of a file.

handlers {
  get {
    render file("foo.txt").text
  }
}

Bad!

Non blocking

Render the contents of a file.

import ratpack.exec.Blocking

handlers {
  get {
    Blocking.get {
      file("foo.txt").text
    } then {
      render it
    }
}

Good!

Non blocking (2)

Render the contents of a file.

import ratpack.exec.Blocking

handlers {
  get {
    render Blocking.get { file("foo.txt").text }
  }
}

Even better!

Non blocking (3)

Render the contents of a file.

import ratpack.exec.Promise

handlers {
  get {
    render Promise.of { down ->
      readFileAsync(file("foo.txt"), new Callback() {
        void onDone(String contents) {
          down.success(contents)
        }
      })
    }
  }
}

Better!

Rendering files

Use this API.

handlers {
  get {
    render file("foo.txt") // adhoc
  }
  files { it.dir("public") } // serve a directory
}

Implications?

Non blocking changes a lot.

  • try/catch doesn’t work.

  • ThreadLocal doesn’t work.

  • Most libs aren’t non blocking.

  • Debugging sucks (stack traces are segmented)

Execution

Kinda similar to Thread with the blocking model.

A logical piece of work (e.g. handling a request).

try/catch

Executions have an error handler.

import ratpack.exec.Execution
import ratpack.exec.Blocking

Execution.fork().onError {
  println it.message
}.start {
  Blocking.get { throw new Exception("!") }
}

try/catch (2)

Request executions use ServerErrorHandler as the error handler.

package ratpack.handling;

public interface ServerErrorHandler {
  void error(Context context, Throwable throwable) throws Exception;
}

Thread locals

Execution implements MutableRegistry

import ratpack.exec.Execution
import ratpack.exec.Blocking

Execution.fork().start {
  Execution.current().add("some value")
  Blocking.get { Execution.current().get(String) } then { render it }
}

ExecInterceptor

Interceptors let you wrap execution segments.

package ratpack.exec

public interface ExecInterceptor {
  enum ExecType {
    BLOCKING,
    COMPUTE
  }

  void intercept(Execution execution, ExecType execType, Block continuation) throws Exception;
}

ExecInterceptor

Let’s you integrate with libs using thread locals.

import org.slf4j.MDC;

public void intercept(Execution execution, ExecType type, Block continuation) throws Exception {
  Map map = execution.get(Map.class);
  MDC.setContextMap(map);
  try {
    continuation.execute();
  } finally {
    map.clear();
    map.putAll(MDC.getCopyOfContextMap());
    MDC.clear();
  }
}

Promise

A representation of a potential value.

Say things about the value without actually having the value.

Promise

package ratpack.exec;

public interface Promise<T> {
  static <T> Promise<T> of(Upstream<T> upstream) { }
}

public interface Upstream<T> {
  void connect(Downstream<T> downstream) throws Exception;
}

public interface Downstream<T> {
  void success(T value);
  void error(Throwable throwable);
  void complete();
}

Promise usage

Promise.of { down ->
  Thread.start { down.success("foo") }
} then {
  println it // prints 'foo'
}

Promise transformations

Promise.of { it.down("foo") }
  .map { it.toUpperCase() }
  .flatMap { foo ->
    Blocking.get {
      new File("foo.txt").text
    }.map {
      it + foo
    }
  }
  .cache()
  .then {
    println it
  }

Promise transformations

package ratpack.exec;

public interface Promise<T> {
  <O> Promise<O> transform(Function<Upstream<T>, Upstream<O>> function);
}

map()

<O> Promise<O> map(Function<T, O> function) {
  return transform(up ->
    down -> up.connect(new Downstream<T>() {

      void success(T value) {
        try {
          down.success(function.apply(value));
        } catch (Throwable e) {
          down.error(e);
        }
      }

      void error(Throwable t) { down.error(t) }
      void complete() { down.complete() }
    })
  );
}

Other transform impls…

Handler Composition

All request handling logic is expressed as a single handler.

Handler

package ratpack.handling;

public interface Handler {
  void handle(Context ctx) throws Exception;
}

Closures as handlers

handlers {
  get {
    render "Hello world!"
  }
}
handlers {
  get new Handler() {
    void handle(Context ctx) {
      ctx.render "Hello world!"
    }
  }
}

Handler delegation

Handlers either respond, or delegate.

  • Context.next()

  • Context.insert(Handler…​)

  • «handler».handle(context)

Chain basics

handlers {
  // delegate is ratpack.handling.Chain
  all {
    render "Hello world!"
  }
}
package ratpack.handling

interface Chain {
  Chain all(Handler handler)
}

Collects handlers to a list the inserts.

ChainHandler

package ratpack.handling.internal

public class ChainHandler implements Handler {
  private final Handler[] handlers;

  public ChainHandler(Handler... handlers) {
    this.handlers = handlers
  }

  public void handle(Context context) {
    context.insert(handlers);
  }
}

Chain.get(String, Handler)

import ratpack.handler.Handlers;

default Chain get(String path, Handler handler) {
  return all(
    Handlers.path(
      path,
      Handlers.chain(Handlers.get(), handler)
    )
  );
}

Handlers

Composable pieces, and the basis of Chain.

package ratpack.handling.Handlers;

public class Handlers {
  public static Handler path(String path, Handler handler) {
    return path(PathBinder.parse(path, true), handler);
  }
  public static Handler path(PathBinder pathBinder, Handler handler) {
    return new PathHandler(pathBinder, handler);
  }
  public static Handler chain(Handler... handlers) {
    return new ChainHandler(handlers);
  }
  public static Handler get() {
    return new MethodHandler("GET");
  }
}

PathHandler

package ratpack.handling.internal;

public class PathHandler implements Handler {
  private final PathBinder binder; private final Handler handler;

  public PathHandler(PathBinder binder, Handler handler) {
    this.binder = binder; this.handler = handler;
  }

  public void handle(Context ctx) {
    Optional<PathBinding> binding = binder.bind(ctx.get(PathBinding.class));
    if (binding.isPresent()) {
      Registry registry = Registry.single(PathBinding.class, binding.get());
      ctx.insert(registry, handler);
    } else {
      ctx.next();
    }
  }
}

MethodHandler

package ratpack.handling.internal;

public class MethodHandler implements Handler {
  private final String method;

  public MethodHandler(String method) {
    this.method = method;
  }

  public void handle(Context ctx) {
    if (ctx.getRequest().getMethod().name(method)) {
      ctx.next();
    } else {
      ctx.clientError(405);
    }
  }
}

Chain.get(String, Handler)

import ratpack.handler.Handlers;

default Chain get(String path, Handler handler) {
  return all(
    Handlers.path(
      path,
      Handlers.chain(Handlers.get(), handler)
    )
  );
}

Thanks!

0