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

HTTP with Java 8

pschirmacher
June 27, 2014
130

HTTP with Java 8

pschirmacher

June 27, 2014
Tweet

Transcript

  1. $ 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
  2. 3

  3. 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)); } }
  4. 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>> { } }
  5. 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>> { } }
  6. 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>> { } }
  7. 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)); };
  8. public static Node everythingFine = ...; public static Node decision

    = new Decision(isMethodAllowed, everythingFine, new Completion(methodNotAllowed));
  9. 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)); }
  10. 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)); } }
  11. 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); } }
  12. 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); }
  13. 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"); } }
  14. 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
  15. 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; } }
  16. 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")));
  17. > restrictive > proper HTTP support > async, no magic

    anywhere > Java 8 does make a difference