Slide 1

Slide 1 text

HTTP with Java 8 Philipp Schirmacher

Slide 2

Slide 2 text

Building HTTP servers with Java...

Slide 3

Slide 3 text

...should be awesome!

Slide 4

Slide 4 text

But it’s not.

Slide 5

Slide 5 text

servlet api

Slide 6

Slide 6 text

servlet containers

Slide 7

Slide 7 text

annotations

Slide 8

Slide 8 text

@GET @Path("/my-path") public Response foo(@QueryParam("id") Integer id) { return Response.ok().build(); }

Slide 9

Slide 9 text

poor HTML integration

Slide 10

Slide 10 text

$ grep -ic controller RFC2616 > 0

Slide 11

Slide 11 text

$ curl -i playframework.com > HTTP/1.1 200 OK

Slide 12

Slide 12 text

$ curl -i -XDELETE playframework.com > HTTP/1.1 404 Not Found

Slide 13

Slide 13 text

content negotiation

Slide 14

Slide 14 text

conditional requests

Slide 15

Slide 15 text

$ curl myresource -i > HTTP/1.1 200 OK ETag: 2 $ curl myresource -i -H’If-None-Match: 2’ > HTTP/1.1 304 Not Modified $ curl myresource -i -H’If-Match: 1’ -XPOST --data foo=bar > HTTP/1.1 412 Precondition Failed

Slide 16

Slide 16 text

> async, no servlets, no containers > just plain Java > proper HTTP support

Slide 17

Slide 17 text

github.com/pschirmacher/webster

Slide 18

Slide 18 text

https://github.com/basho/webmachine/wiki/Diagram

Slide 19

Slide 19 text

https://github.com/basho/webmachine/wiki/Diagram

Slide 20

Slide 20 text

https://github.com/basho/webmachine/wiki/Diagram

Slide 21

Slide 21 text

https://github.com/basho/webmachine/wiki/Diagram

Slide 22

Slide 22 text

https://github.com/basho/webmachine/wiki/Diagram

Slide 23

Slide 23 text

graph + resource + request = response https://github.com/basho/webmachine/wiki/Diagram

Slide 24

Slide 24 text

public interface Node extends BiFunction> { }

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

3

Slide 27

Slide 27 text

3 .thenApply(inc)

Slide 28

Slide 28 text

3 .thenApply(inc) 4

Slide 29

Slide 29 text

3 .thenApply(incFuture)

Slide 30

Slide 30 text

3 .thenApply(incFuture) 3 4

Slide 31

Slide 31 text

3 .thenCompose(incFuture)

Slide 32

Slide 32 text

3 .thenCompose(incFuture) 4

Slide 33

Slide 33 text

public interface Node extends BiFunction> { }

Slide 34

Slide 34 text

public class Decision implements Node { private final BiFunction> decision; private final Node onTrue; private final Node onFalse; @Override public CompletableFuture apply(Resource resource, Request request) { return decision .apply(resource, request) .thenCompose(decisionResult -> decisionResult ? onTrue.apply(resource, request) : onFalse.apply(resource, request)); } }

Slide 35

Slide 35 text

public class Decision implements Node { private final BiFunction> decision; private final Node onTrue; private final Node onFalse; @Override public CompletableFuture apply(Resource resource, Request request) { return decision .apply(resource, request) .thenCompose(decisionResult -> decisionResult ? onTrue.apply(resource, request) : onFalse.apply(resource, request)); } public static interface Fn extends BiFunction> { } }

Slide 36

Slide 36 text

public class Completion implements Node { private final BiFunction> completion; @Override public CompletableFuture apply(Resource resource, Request request) { // logging return completion.apply(resource, request); } public static interface Fn extends BiFunction> { } }

Slide 37

Slide 37 text

public class Action implements Node { private final BiFunction> action; private final Node then; @Override public CompletableFuture apply(Resource resource, Request request) { return action.apply(resource, request) .thenCompose(ignored -> then.apply(resource, request)); } public static interface Fn extends BiFunction> { } }

Slide 38

Slide 38 text

public static Decision.Fn isMethodAllowed = (r, req) -> CompletableFuture.completedFuture( r.allowedMethods().contains(req.method()));

Slide 39

Slide 39 text

public static Completion.Fn methodNotAllowed = (r, req) -> { String allow = r.allowedMethods().stream().collect(Collectors.joining(", ")); Map headers = new HashMap<>(); headers.put("Allow", allow); return CompletableFuture.completedFuture(new Response(405, headers)); };

Slide 40

Slide 40 text

public static Action.Fn doPost = (r, req) -> r.onPost(req);

Slide 41

Slide 41 text

public static Node everythingFine = ...; public static Node decision = new Decision(isMethodAllowed, everythingFine, new Completion(methodNotAllowed));

Slide 42

Slide 42 text

public static Node decisionTree = decide(isMethodAllowed) .onTrue(...) .onFalse(complete(methodNotAllowed))

Slide 43

Slide 43 text

public static Node contentNegotiation = decide(acceptExists) .onTrue(decide(acceptableMediaTypeAvailable) .onTrue(existingResourceDecision) .onFalse(complete(notAcceptable))) .onFalse(existingResourceDecision);

Slide 44

Slide 44 text

public static Node handlePost = decide(isPostForbidden) .onTrue(complete(forbidden)) .onFalse(decide(isPostValid) .onTrue(act(doPost).andThen(decide(isRedirect) .onTrue(complete(seeOther)) .onFalse(decide(isNewResource) .onTrue(complete(created)) .onFalse(entityOrNoContent)))) .onFalse(complete(badRequest)));

Slide 45

Slide 45 text

HTTP logic app resources routing

Slide 46

Slide 46 text

public class Route implements Function> { private final Function> handler; private final String pattern; @Override public CompletableFuture apply(Request request) { Optional routeMatch = matchFor(request); if (!routeMatch.isPresent()) { throw new IllegalArgumentException("route doesn't match"); } else { return handler.apply(requestWithPathParams(request, routeMatch.get())); } } public boolean matches(Request request) { return matchFor(request).isPresent(); } } public Route createRoute(String pattern, Resource resource, Node decisionTree) { return new Route(pattern, request -> decisionTree.apply(resource, request)); }

Slide 47

Slide 47 text

public class RoutingTable implements Function> { private final List routes; public RoutingTable(List routes) { this.routes = routes; } @Override public CompletableFuture apply(Request request) { Optional route = routes.stream().filter(r -> r.matches(request)).findFirst(); return route.isPresent() ? route.get().apply(request) : CompletableFuture.completedFuture(new Response(404)); } }

Slide 48

Slide 48 text

public class App { public static void main(String[] args) throws Exception { RoutingTable routingTable = routingTable() .withRoute(from("/").toResource(new Home())) .withRoute(from("/computers").toResource(new Computers())) .withRoute(from("/computer_new").toResource(new ComputerCreate())) .withRoute(from("/computer/:id").toResource(new ComputerEdit())) .withRoute(from("/assets/*").toResource(new AssetsResource())) .build(); new Server().run(routingTable); } }

Slide 49

Slide 49 text

public interface Resource { default CompletableFuture> expires(Request request) { return CompletableFuture.completedFuture(Optional.empty()); } default CompletableFuture> etag(Request request) { return CompletableFuture.completedFuture(Optional.empty()); } default CompletableFuture onPost(Request request) { return CompletableFuture.completedFuture(null); } default Set supportedMediaTypes() { return Collections.singleton("text/plain"); } default Set allowedMethods() { return Collections.singleton("GET"); } ... CompletableFuture doesRequestedResourceExist(Request request); CompletableFuture entity(Request request); }

Slide 50

Slide 50 text

public class HelloWorld implements Resource { @Override public CompletableFuture doesRequestedResourceExist(Request request) { return CompletableFuture.completedFuture(true); } @Override public CompletableFuture entity(Request request) { return CompletableFuture.completedFuture("hello world"); } }

Slide 51

Slide 51 text

public class HelloWorld implements Resource { @Override public CompletableFuture doesRequestedResourceExist(Request request) { return CompletableFuture.completedFuture(true); } @Override public CompletableFuture entity(Request request) { return CompletableFuture.completedFuture("hello world"); } @Override public CompletableFuture> etag(Request request) { return CompletableFuture.completedFuture(Optional.of("1")); } } $ curl localhost:8080/helloworld -i -H’If-None-Match: 1’ > HTTP/1.1 304 Not Modified

Slide 52

Slide 52 text

public interface Parsable { I value(); default O parse(Function parser) { return parser.apply(value()); } } public class ValueSupplier implements Parsable { private final T value; public ValueSupplier(T value) { this.value = value; } @Override public T value() { return value; } }

Slide 53

Slide 53 text

public class Request { private final Map headers; private final Map> requestParams; public ValueSupplier> header(String name) { return new ValueSupplier<>(Optional.ofNullable(headers.get(name))); } public ValueSupplier> param(String name) { ... } } Optional id = request.param("id").parse(Parsers.asLong); int page = request.param("page").parse(Parsers.asInt).orElse(0); boolean hasIfMatch = request.header("If-Match").value().isPresent(); Map form = request.body().parse(Parsers.asForm); int i = request.param("id").parse(optionalString -> Integer.valueOf(optionalString.orElse("0")));

Slide 54

Slide 54 text

demo

Slide 55

Slide 55 text

> restrictive > proper HTTP support > async, no magic anywhere > Java 8 does make a difference

Slide 56

Slide 56 text

Thanks! Questions? github.com/pschirmacher/webster

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

No content

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

No content

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

No content