Upgrade to Pro — share decks privately, control downloads, hide ads and more …

HTTP with Java 8

A0335428a9bc84ac981640853ca3a7eb?s=47 pschirmacher
June 27, 2014
99

HTTP with Java 8

A0335428a9bc84ac981640853ca3a7eb?s=128

pschirmacher

June 27, 2014
Tweet

Transcript

  1. HTTP with Java 8 Philipp Schirmacher

  2. Building HTTP servers with Java...

  3. ...should be awesome!

  4. But it’s not.

  5. servlet api

  6. servlet containers

  7. annotations

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

    }
  9. poor HTML integration

  10. $ grep -ic controller RFC2616 > 0

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

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

  13. content negotiation

  14. conditional requests

  15. $ 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
  16. > async, no servlets, no containers > just plain Java

    > proper HTTP support
  17. github.com/pschirmacher/webster

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

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

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

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

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

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

  24. public interface Node extends BiFunction<Resource, Request, CompletableFuture<Response>> { }

  25. None
  26. 3

  27. 3 .thenApply(inc)

  28. 3 .thenApply(inc) 4

  29. 3 .thenApply(incFuture)

  30. 3 .thenApply(incFuture) 3 4

  31. 3 .thenCompose(incFuture)

  32. 3 .thenCompose(incFuture) 4

  33. public interface Node extends BiFunction<Resource, Request, CompletableFuture<Response>> { }

  34. public class Decision implements Node { private final BiFunction<Resource, Request,

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

    CompletableFuture<Boolean>> decision; private final Node onTrue; private final Node onFalse; @Override public CompletableFuture<Response> 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<Resource, Request,CompletableFuture<Boolean>> { } }
  36. public class Completion implements Node { private final BiFunction<Resource, Request,

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

    CompletableFuture<Void>> action; private final Node then; @Override public CompletableFuture<Response> apply(Resource resource, Request request) { return action.apply(resource, request) .thenCompose(ignored -> then.apply(resource, request)); } public static interface Fn extends BiFunction<Resource, Request, CompletableFuture<Void>> { } }
  38. public static Decision.Fn isMethodAllowed = (r, req) -> CompletableFuture.completedFuture( r.allowedMethods().contains(req.method()));

  39. public static Completion.Fn methodNotAllowed = (r, req) -> { String

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

  41. public static Node everythingFine = ...; public static Node decision

    = new Decision(isMethodAllowed, everythingFine, new Completion(methodNotAllowed));
  42. public static Node decisionTree = decide(isMethodAllowed) .onTrue(...) .onFalse(complete(methodNotAllowed))

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

  44. 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)));
  45. HTTP logic app resources routing

  46. public class Route implements Function<Request, CompletableFuture<Response>> { private final Function<Request,

    CompletableFuture<Response>> handler; private final String pattern; @Override public CompletableFuture<Response> apply(Request request) { Optional<RouteMatch> 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)); }
  47. public class RoutingTable implements Function<Request, CompletableFuture<Response>> { private final List<Route>

    routes; public RoutingTable(List<Route> routes) { this.routes = routes; } @Override public CompletableFuture<Response> apply(Request request) { Optional<Route> route = routes.stream().filter(r -> r.matches(request)).findFirst(); return route.isPresent() ? route.get().apply(request) : CompletableFuture.completedFuture(new Response(404)); } }
  48. 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); } }
  49. public interface Resource { default CompletableFuture<Optional<Instant>> expires(Request request) { return

    CompletableFuture.completedFuture(Optional.empty()); } default CompletableFuture<Optional<String>> etag(Request request) { return CompletableFuture.completedFuture(Optional.empty()); } default CompletableFuture<Void> onPost(Request request) { return CompletableFuture.completedFuture(null); } default Set<String> supportedMediaTypes() { return Collections.singleton("text/plain"); } default Set<String> allowedMethods() { return Collections.singleton("GET"); } ... CompletableFuture<Boolean> doesRequestedResourceExist(Request request); CompletableFuture<Object> entity(Request request); }
  50. public class HelloWorld implements Resource { @Override public CompletableFuture<Boolean> doesRequestedResourceExist(Request

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

    request) { return CompletableFuture.completedFuture(true); } @Override public CompletableFuture<Object> entity(Request request) { return CompletableFuture.completedFuture("hello world"); } @Override public CompletableFuture<Optional<String>> 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
  52. public interface Parsable<I> { I value(); default <O> O parse(Function<I,

    O> parser) { return parser.apply(value()); } } public class ValueSupplier<T> implements Parsable<T> { private final T value; public ValueSupplier(T value) { this.value = value; } @Override public T value() { return value; } }
  53. public class Request { private final Map<String, String> headers; private

    final Map<String, List<String>> requestParams; public ValueSupplier<Optional<String>> header(String name) { return new ValueSupplier<>(Optional.ofNullable(headers.get(name))); } public ValueSupplier<Optional<String>> param(String name) { ... } } Optional<Long> 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<String, String> form = request.body().parse(Parsers.asForm); int i = request.param("id").parse(optionalString -> Integer.valueOf(optionalString.orElse("0")));
  54. demo

  55. > restrictive > proper HTTP support > async, no magic

    anywhere > Java 8 does make a difference
  56. Thanks! Questions? github.com/pschirmacher/webster

  57. None
  58. None
  59. None
  60. None
  61. None
  62. None
  63. None