1
votes

I want to have an actor based service which is accessible via http.
I'm new to akka, but after researching I came to the conclusion that using akka http will be the best approach for me.

I'm implementing a server that uses the akka http like so:

class MyServer extends AllDirectives {
    private final Http http;
    private final Materializer materializer;
    private final Flow<HttpRequest, HttpResponse, NotUsed> routes;

    private ServerBinding binding;

    MyServer(final ActorSystem system) {
        this.http = Http.get(system);
        this.materializer = ActorMaterializer.create(system);
        this.routes = this.createRoutes().flow(system, materializer);
    }

    CompletionStage<ServerBinding> start() {
        return this.http
                .bindAndHandle(this.routes, ConnectHttp.toHost("localhost", DEFAULT_PORT), this.materializer)
                .thenApplyAsync(serverBinding -> {
                    this.binding = serverBinding;
                    return serverBinding;
                });
    }

    CompletionStage<BoxedUnit> stop() {
        return this.binding.unbind();
    }

    private Route createRoutes() {
        // create routes 
    }
}

In the createRoutes method I want to create an hierarchical route tree (or forest) using directives, something like:

private Route createRoutes() {
    return pathPrefix("typeOne", () ->
                path(PathMatchers.segment(), id ->
                    get(() -> handleTypeOneGetRequest(id))
                    .orElse(post(() -> handleTypeOnePostRequest(id, requestData)))
                )
            ).orElse(pathPrefix("typeTwo", () ->
                path(PathMatchers.segment(), id ->
                        get(() -> handleTypeTwoGetRequest(id))
                        .orElse(post(() -> handleTypeTwoPostRequest(id, requestData)))
                )
    ));
}

When handling requests (handleTypeOneGetRequest for example) I'd like to pass a message to the appropriate actor (TypeOne for example) and return an async response that will be fulfilled when a response message returns from the actor.

My problem is that I can't figure out how to access the RequestContext when handling the directives.

My questions are:

  • How to I obtain the RequestContext in the directive?
  • Am I going about this the right way? Is there a better approach?
1

1 Answers

2
votes

Not quite sure if I got your question right, but as far as I got you, you want to basically let an actor handle your request and then return a result. I usually use Scala so please don't mind my possible Syntax Errors in your Java8 code ;)

I usually use the akka ask pattern to retrieve a future of the response object sent by the actor and then use the onSuccess directive to extract the actual value out of the future. Since actor responses are by default untyped you need to check if it actual is what you expected and then you can complete your request with the value. (You probably also need to cast the object, so your response marshallers work).

Timeout timeout = new Timeout(Duration.create(5, "seconds"));    

private Route createRoutes() {
    return pathPrefix("typeOne", () ->
                path(PathMatchers.segment(), id ->
                    get(() -> 
                            onSuccess(() -> Patterns.ask(actor, id, timeout),
                                      extraction -> if (extraction instanceof WhatEverYouExpect) complete(extraction)
                                     )
                        )
    ));
}

Ask Pattern: http://doc.akka.io/docs/akka/snapshot/java/futures.html

onSuccess Directive: http://doc.akka.io/docs/akka/snapshot/java/http/routing-dsl/directives/future-directives/onSuccess.html

EDIT

You should have a look at the extract* directives: http://doc.akka.io/docs/akka/2.4.7/java/http/routing-dsl/directives/alphabetically.html

Specifically extract: http://doc.akka.io/docs/akka/2.4.7/java/http/routing-dsl/directives/basic-directives/extract.html#extract-java

final Route route = extract(
  ctx -> ctx.getRequest().getUri().toString().length() // extract anything you need and pack into your object
  len -> //use your object
);

I suppose that's what you need.


Edit (by the OP)

Here's a complete solution to the problem, including the HttpContext, the usage of an Actor for an async response and how to make it work in java:

pathPrefix("typeOne", () ->
    path(PathMatchers.segment(), segment ->
        get(() ->
            extract(
                context -> context,
                context -> onSuccess(() -> 
                    FutureConverters.toJava(Patterns.ask(ACTOR_REF, MESSAGE, TIMEOUT)),
                    extraction -> complete(DO_SOMETHING_WITH(segment, context, extraction))
                ).orElse(...)
            )
        )
    )
)