Request Handling

http://helenaedelson.com/?p=879

This

RoutesCreator (Actor):

Matches the request to a Handler via the requests URI, applying a series of common Directives (to deal with authentication, content negotiation etc). If no route is created matching a specific URI, skysail will return the usual 404 response.

Otherwise, if a match is found, a new ProcessComand message is sent to the associated application actor:

val applicationActor = ... // get the application actor for the current Application
val processCommand = ProcessCommand(requestContext, resourceClass, urlParameter, unmatchedPath)
(applicationActor ? processCommand).mapTo[ListResponseEvent[_]]
...

The ProcessCommand is created using

  • the current akka.http.scaladsl.server.RequestContext
  • the resource class (extending io.skysail.core.resources.Resource), which defines the business logic to be executed for this request
  • to be done
  • the "unmatched path", which is the optional suffix of the URI which was not matched completely.

ApplicationActor

The ApplicationActor handles the ProcessCommand created by the RoutesCreator:

case cmd: ProcessCommand => {
  ...
  val theClass = cmd.resourceClass.newInstance()
  val controllerActor = ... // create new ControllerActor as Child
  (controllerActor ? SkysailContext(cmd, appModel, theClass, bundleContext)).mapTo[ListResponseEvent[_]]
  ...
}

It uses the resource class from the ProcessCommand to create a new instance of this class (which will be used to actually execute the business logic associated with the request).

There is only one ApplicationActor per Application, but each request creates a new ControllerActor as a child actor to the application actor.

A new SkysailContext object is sent to the newly created ControllerActor using

  • the original ProcessCommand
  • the applicationModel of the current Application
  • the new resource instance and
  • the OSGi bundle context.

ControllerActor

Now its the newly created ControllerActors responsibility to match the request method and call the appropriate method on the resource class:

cmd.ctx.request.method match {
  case HttpMethods.GET => resource.get(RequestEvent(cmd, self))
  case HttpMethods.PUT =>
  case HttpMethods.DELETE => 
  case HttpMethods.POST => resource.asInstanceOf[PostSupport].post(RequestEvent(cmd, self))
  case e: Any => resource.get(RequestEvent(cmd, self))
}

Resource

A Resource instance is not an actor. It is called by a ControllerActor to execute the business logic for this request and will send back a ResponseEvent to its original ControllerActor:

def get(requestEvent: RequestEvent): Unit = {
    val r = ... // async business logic
    r onComplete {
      case Success(success) => requestEvent.controllerActor ! ListResponseEvent(requestEvent, apply(success))
      case Failure(failure) => ...
    }
  }
}

Now the execution returns back in the actor chain, as the original ControllerActor is sent back a ResponseEvent or ListResponseEvent:

ControllerActor

Depending on the request's Accept-Header, the MediaTypeNegotiator is used to determine how to actually render the retrieved Response:

case response: ListResponseEvent[T] =>
  val negotiator = new MediaTypeNegotiator(response.req.cmd.ctx.request.headers)
  val acceptedMediaRanges = negotiator.acceptedMediaRanges
  ...
  val m = Marshal(response.resource.asInstanceOf[List[_]]).to[RequestEntity]

  if (negotiator.isAccepted(MediaTypes.`text/html`)) {
    handleHtmlWithFallback(response, m)
  } else if (negotiator.isAccepted(MediaTypes.`application/json`)) {
    handleJson(m, response)
  }
case response: ResponseEvent[T] =>
  ... // similar to ListResponseEvent
Somewhere in handleXXX:

val answer = HttpEntity(ContentTypes.`text/html(UTF-8)`, <body>)
applicationActor ! response.copy(resource = response.resource, httpResponse = response.httpResponse.copy(entity = answer))

So, the ResponseEvent is sent back up in the chain to the ApplicationActor:

ApplicationActor

val r = ... // the async result from calling the controller actor
r onComplete {
  case Success(value) => routesCreator ! value
  case Failure(failure) => ...
}

Finally, the RoutesCreator gets the response:

RoutesCreator

val t = ... // the async result from calling the application actor
onComplete(t) {
  case Success(result) => complete(result.httpResponse)
  case Failure(failure) => ...; complete(StatusCodes.BadRequest, failure)
}

results matching ""

    No results matching ""