/src/main/java/com/lightbend/akkasample/sample4/WebServer.java

https://github.com/johanandren/akka-actor-java8-webinar · Java · 128 lines · 98 code · 24 blank · 6 comment · 4 complexity · b8558c1d194853ab4a6e03976de845f7 MD5 · raw file

  1. /*
  2. * Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com>
  3. */
  4. package com.lightbend.akkasample.sample4;
  5. import akka.actor.*;
  6. import akka.event.Logging;
  7. import akka.http.javadsl.ConnectHttp;
  8. import akka.http.javadsl.Http;
  9. import akka.http.javadsl.ServerBinding;
  10. import akka.http.javadsl.marshallers.jackson.Jackson;
  11. import akka.http.javadsl.marshalling.Marshaller;
  12. import akka.http.javadsl.model.RequestEntity;
  13. import akka.http.javadsl.model.StatusCodes;
  14. import akka.http.javadsl.server.Route;
  15. import akka.japi.pf.ReceiveBuilder;
  16. import akka.stream.ActorMaterializer;
  17. import akka.stream.Materializer;
  18. import scala.util.Try;
  19. import java.net.InetSocketAddress;
  20. import java.util.concurrent.CompletionStage;
  21. import static com.lightbend.akkasample.sample4.CompletionStageUtils.withRetries;
  22. import static akka.pattern.PatternsCS.*;
  23. import static akka.http.javadsl.server.PathMatchers.*;
  24. import static akka.http.javadsl.server.Directives.*;
  25. public class WebServer extends AbstractLoggingActor {
  26. private final static Marshaller<Product, RequestEntity> productMarshaller = Jackson.<Product>marshaller();
  27. // internal protocol
  28. private static class ServerStarted {
  29. final String host;
  30. final int port;
  31. public ServerStarted(String host, int port) {
  32. this.host = host;
  33. this.port = port;
  34. }
  35. }
  36. private static class ServerFailed {
  37. final Exception cause;
  38. public ServerFailed(Exception cause) {
  39. this.cause = cause;
  40. }
  41. }
  42. public static Props props(ActorRef database, String host, int port) {
  43. return Props.create(WebServer.class, database, host, port);
  44. }
  45. private final ActorRef database;
  46. private final CompletionStage<ServerBinding> bindingCompletionStage;
  47. public WebServer(ActorRef database, String host, int port) {
  48. this.database = database;
  49. final Route route =
  50. logRequest("request", Logging.InfoLevel(), () ->
  51. path(segment("products").slash(longSegment()), (productId) ->
  52. get(() ->
  53. onComplete(lookupProduct(productId), (Try<DbActor.ProductResult> result) -> {
  54. if (result.isFailure()) {
  55. return complete(StatusCodes.SERVICE_UNAVAILABLE);
  56. } else {
  57. final DbActor.ProductResult productResult = result.get();
  58. if (productResult.product.isPresent()) {
  59. return completeOK(productResult.product.get(), productMarshaller);
  60. } else {
  61. return complete(StatusCodes.NOT_FOUND);
  62. }
  63. }
  64. })
  65. )
  66. )
  67. );
  68. Materializer materializer = ActorMaterializer.create(context());
  69. bindingCompletionStage = Http.get(context().system())
  70. .bindAndHandle(
  71. route.flow(context().system(), materializer),
  72. ConnectHttp.toHost(host, port),
  73. materializer);
  74. // starting the http server is async, inform us when it completes, or fails
  75. pipe(bindingCompletionStage, context().dispatcher()).to(self());
  76. receive(ReceiveBuilder
  77. .match(Status.Failure.class, failure -> onFailure(failure.cause()))
  78. .match(ServerBinding.class, this::onStarted)
  79. .build());
  80. }
  81. private void onStarted(ServerBinding binding) {
  82. final InetSocketAddress address = binding.localAddress();
  83. log().info("Server started at {}:{}", address.getHostString(), address.getPort());
  84. }
  85. private void onFailure(Throwable cause) {
  86. log().error(cause, "Failed to start webserver");
  87. throw new RuntimeException(cause);
  88. }
  89. private CompletionStage<DbActor.ProductResult> lookupProduct(long productId) {
  90. return withRetries(
  91. () -> ask(
  92. database, // actor to ask
  93. new DbActor.GetProduct(productId), // message
  94. 500) // max time in ms to wait before failing
  95. .thenApply(object -> ((DbActor.ProductResult) object)),
  96. 2 // nr of retries - this means max time a user will have to wait is 2s after which it will always fail
  97. );
  98. }
  99. @Override
  100. public void postStop() {
  101. // make sure we stop the http server when actor stops
  102. bindingCompletionStage.thenAccept(ServerBinding::unbind);
  103. }
  104. }