On Github ratpack / presentation-ratpack-under-the-hood-gr8conf-us-2015
Under the hood.
Creator.
Execution (non blocking)
Promise transformations
Handler composition
Any kind of waiting.
IO (e.g. InputStream)
Lock.lock()
JDBC
Common HTTP clients
most APIs
More threads than there are cores creates contention.
Each concurrent piece of work requires a separate thread.
100 concurrent requests =~ 100 threads
Not the whole story.
Really… low & predictably scalable resource usage.
Relinquish the thread when you have to wait.
Get a thread back when you can do more work.
Use it or lose it.
Render the contents of a file.
handlers { get { render file("foo.txt").text } }
Bad!
Render the contents of a file.
import ratpack.exec.Blocking handlers { get { Blocking.get { file("foo.txt").text } then { render it } }
Good!
Render the contents of a file.
import ratpack.exec.Blocking handlers { get { render Blocking.get { file("foo.txt").text } } }
Even better!
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!
Use this API.
handlers { get { render file("foo.txt") // adhoc } files { it.dir("public") } // serve a directory }
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)
Kinda similar to Thread with the blocking model.
A logical piece of work (e.g. handling a request).
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("!") } }
Request executions use ServerErrorHandler as the error handler.
package ratpack.handling; public interface ServerErrorHandler { void error(Context context, Throwable throwable) throws Exception; }
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 } }
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; }
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(); } }
A representation of a potential value.
Say things about the value without actually having the value.
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.of { down -> Thread.start { down.success("foo") } } then { println it // prints 'foo' }
Promise.of { it.down("foo") } .map { it.toUpperCase() } .flatMap { foo -> Blocking.get { new File("foo.txt").text }.map { it + foo } } .cache() .then { println it }
package ratpack.exec; public interface Promise<T> { <O> Promise<O> transform(Function<Upstream<T>, Upstream<O>> function); }
<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() } }) ); }
Read the source of ratpack.exec.Promise.
All request handling logic is expressed as a single handler.
package ratpack.handling; public interface Handler { void handle(Context ctx) throws Exception; }
handlers { get { render "Hello world!" } }
handlers { get new Handler() { void handle(Context ctx) { ctx.render "Hello world!" } } }
Handlers either respond, or delegate.
Context.next()
Context.insert(Handler…)
«handler».handle(context)
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.
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); } }
import ratpack.handler.Handlers; default Chain get(String path, Handler handler) { return all( Handlers.path( path, Handlers.chain(Handlers.get(), handler) ) ); }
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"); } }
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(); } } }
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); } } }
import ratpack.handler.Handlers; default Chain get(String path, Handler handler) { return all( Handlers.path( path, Handlers.chain(Handlers.get(), handler) ) ); }
Join the Slack channel and talk to us!
Buy Dan Woods' book!