Skip to content

Instantly share code, notes, and snippets.

@jayhutfles
Created August 24, 2016 14:07
Show Gist options
  • Save jayhutfles/b09e8d2d71aab08ac516029efd2c487d to your computer and use it in GitHub Desktop.
Save jayhutfles/b09e8d2d71aab08ac516029efd2c487d to your computer and use it in GitHub Desktop.
// Akka HTTP and Streams notes:
// Working with akka.http.scaladsl.Http:
Http().bind(
interface: String,
port: Int
): Source[Http.IncomingConnection, Future[ServerBinding]]
//- creates an akka.stream.scaladsl.Source[Http.IncomingConnection, Future[ServerBinding]]
//- DOESN'T ACTUALLY START HANDLING REQUESTS. You have to compose the Source in a Flow to a Sink and RUN IT.
/** NOTES:
*
* Http.IncomingConnection has the following methods which run the flows immediately upon calling:
* def handleWith[Mat](handler: Flow[HttpRequest, HttpResponse, Mat]): Mat
* def handleWithSyncHandler(handler: HttpRequest => HttpResponse): Unit
* def handleWithAsyncHandler(handler: HttpRequest => Future[HttpResponse], parallelism: Int): Unit
*
* Http.IncomingConnection is also constructed with a flow: Flow[HttpResponse, HttpRequest, NotUsed]
* this is the flow that feeds responses back to the associated requests
* flow.joinMat(handler) will join them together so things flow from end-to-end
*/
Http().bindAndHandle(
handler: Flow[HttpRequest, HttpResponse, Any],
interface: String,
port: Int
): Future[ServerBinding]
//- calls bind() to get a Source[Http.IncomingConnection, Future[ServerBidning]]
//- composes binding with handler and IncomingConnection's .flow value to create RunnableGraph
//- then runs it
//- STARTS HANDLING REQUESTS
Http().bindAndHandleSync(
handler: HttpRequest ⇒ HttpResponse,
interface: String,
port: Int
): Future[ServerBinding]
//- creates binding to interface/port
//- creates flow using handler function like this:
Flow[HttpRequest].map(handler)
//- calls bindAndHandle with that flow
//- WHICH STARTS HANDLING REQUESTS
Http().bindAndHandleAsync(
handler: HttpRequest ⇒ Future[HttpResponse],
interface: String,
port: Int,
parallelism: Int
): Future[ServerBinding]
//- creates binding
//- creates flow using handler function like this:
Flow[HttpRequest].mapAsync(parallelism)(handler)
//- calls bindAndHandle with that flow
//- WHICH STARTS HANDLING REQUESTS
/** So... How does it work that these take akka.http.scaladsl.server.Route as arguments?
*
* HOW ARE ROUTES IMPLICITLY CONVERTED TO FLOWS? */
// TL;DR: a magic combination of these
import akka.http.scaladsl.server.Directives._
implicit val materializer = ActorMaterializer()
// LONG VERSION:
// Per akka-http\src\main\scala\akka\http\scaladsl\server\package.scala:
type Route = RequestContext ⇒ Future[RouteResult]
// But the Route companion object has this gem:
/**
* Turns a `Route` into a server flow.
*
* This conversion is also implicitly available through [[RouteResult#route2HandlerFlow]].
*/
def handlerFlow(route: Route)(implicits...): Flow[HttpRequest, HttpResponse, NotUsed] =
Flow[HttpRequest].mapAsync(1)(asyncHandler(route))
// So, tracking where the transitive imports end up lead me here:
import akka.http.scaladsl.server.Directives._ // which includes
akka.http.scaladsl.server.CodingDirectives // which calls
import akka.http.scaladsl.server.MiscDirectives // whose companion object includes
import RouteResult._
// But that still didn't explain all of the steps, because if
type Route = RequestContext => Future[RouteResult]
// then Flow[HttpRequest].mapAsync should take a
HttpRequest => Future[HttpResponse],
// not a
RequestContext => Future[RouteResult]
// which means that the Route companion object's asyncHandler method does it. Sure enough, it's right there:
request ⇒
sealedRoute(new RequestContextImpl(request, routingLog.requestLog(request), routingSettings, effectiveParserSettings)).fast
.map {
case RouteResult.Complete(response) ⇒ response
case RouteResult.Rejected(rejected) ⇒ throw new IllegalStateException(s"Unhandled rejections '$rejected', unsealed RejectionHandler?!")
}
// that fun stuff says given an HttpRequest:
//- it lifts the request into a RequestContext
//- runs the RequestContext through the route (remember, it's a RequestContext => Future[RouteResult] ?)
//- and then pattern matches to extract the completed or rejected HttpResponse.
// CONCLUSIONS:
// - The Route DSL is really just a bunch of convenience for Flow.mapAsync calls.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment