Server as a Function. In Kotlin. _________________.
David Denton & Ivan Sanchez KotlinConf - October 4th 2018
Server as a Function. In Kotlin. _________________. KotlinConf - - - PowerPoint PPT Presentation
Server as a Function. In Kotlin. _________________. KotlinConf - October 4th 2018 David Denton & Ivan Sanchez The Oscar Platform Strategic journal delivery platform for a global academic publisher Top 1000 site globally, delivers
David Denton & Ivan Sanchez KotlinConf - October 4th 2018
academic publisher
CD build & deploy by Deployed to Monorepo in
Issues:
Action: Let’s try something in pure Kotlin
See it @ http://bit.ly/BarelyMagical
function:
I/O transforms
Finagle library
“It turns an HTTP Request into an HTTP Response”
HttpHandler: (Request) -> Response
val echo: HttpHandler = { req: Request -> Response(OK).body(req.body) } val resp: Response = echo(Request(POST, “/ echo“).body(“hello”))
“Provides pre and post processing on an HTTP operation”
Filter: (HttpHandler) -> HttpHandler
val twitterFilter = Filter { next: HttpHandler -> { req: Request -> val tweet: Request = req.body(req.bodyString().take(140)) next(tweet) } } val tweet: HttpHandler = twitterFilter.then(echo)
Router: (Request) -> HttpHandler?
“Matches an HttpHandler against a Request”
val routes: HttpHandler = routes( "/echo" bind POST to echo, "/twitter" bind routes( “/tweet” bind POST to tweet ) )
Can compose multiple routers to make an HttpHandler http4k does a depth-first search on the tree, then falls back to 404
val echo: HttpHandler = { r: Request -> Response(OK).body(r.body) } val server: Http4kServer = echo.asServer(Undertow(8000)).start()
We can attach an HttpHandler to a running container
HttpHandler: (Request) -> Response
Can reuse the symmetric HttpHandler API:
val client: HttpHandler = ApacheClient() val response: Response = client( Request(GET, "https://www.http4k.org/search") .query("term", “http4k is cool") )
Testing http4k apps is trivial because:
val echo: HttpHandler = { r: Request -> Response(OK).body(r.body) } class EchoTest { @Test fun `handler echoes input`() { val input: Request = Request(POST, "/echo").body("hello") val expected: Response = Response(OK).body("hello") assertThat(echo(input), equalTo(expected)) } }
Testing http4k apps is trivial because:
val echo: HttpHandler = SetHostFrom(Uri.of("http://myserver:80")) .then(ApacheClient()) class EchoTest { @Test fun `handler echoes input`() { val input: Request = Request(POST, "/echo").body("hello") val expected: Response = Response(OK).body("hello") assertThat(echo(input), equalTo(expected)) } }
val miner: HttpHandler = routes( "/mine/{btc}" bind POST to { r: Request -> val newTotal: Int = r.path("btc")!!.toInt() + 1 Response(OK).body("""{"value":$newTotal}""") } )
“A Lens targets a specific part of a complex object to either GET or SET a value”
Extract: (HttpMessage) -> X Inject: (X, HttpMessage) -> HttpMessage
invoke() functions
val miner: HttpHandler = routes( "/mine/{btc}" bind POST to { r: Request -> val newTotal: Int = r.path("btc")!!.toInt() + 1 Response(OK).body("""{"value":$newTotal}""") } ) data class BTC(val value: Int) {
}
val btcPath: PathLens<BTC> = Path.int().map(::BTC).of("btc") val btcBody: BiDiBodyLens<BTC> = Body.auto<BTC>().toLens()
produce a BadRequest (400)
val btcPath: PathLens<BTC> = Path.int().map(::BTC).of("btc") val btcBody: BiDiBodyLens<BTC> = Body.auto<BTC>().toLens() val miner: HttpHandler = CatchLensFailure.then( routes( "/mine/{btc}" bind POST to { r: Request -> val newTotal: BTC = btcPath(r) + BTC(1) btcBody(newTotal, Response(OK)) } ) )
Business Abstraction Remote Client Launcher
Load Environment Configuration Embedded Server Launch
Application Stack
Logging Metrics Remote Clients
Application
Business Abstraction Business Abstraction
Route Route Route Route
fun serverStack(systemName: String, app: HttpHandler): HttpHandler = logTransactionFilter(“IN”, systemName) .then(recordMetricsFilter(systemName)) .then(handleErrorsFilter()) .then(app) fun clientStack(systemName: String): HttpHandler = logTransactionFilter(“OUT”, systemName) .then(recordMetricsFilter(systemName)) .then(handleErrorsFilter()) .then(ApacheClient())
Filter.then(that: Filter) -> Filter
By utilising the ability to “stack” Filters, we can build reusable units of behaviour
Fake HTTP Service State
Test Environment Configuration
into each other in order to build an offline environment
failure to test particular scenarios
applications and clients are HttpHandlers
Abstract Test Contract Success scenarios Business Abstraction Fake Dependency Test Fake System (HttpHandler) State Failure scenarios Real Dependency Test Environment Configuration Remote Client (HttpHandler)
Full implementation @ http://bit.ly/techempower
http://bit.ly/http4kbox https://http4kbox.http4k.org Auth: http4kbox:http4kbox
CORE
AWS API Gateway AWS Lambda
single interface
start time**
** http://bit.ly/coldstartwar
CORE
HamKrest
“5 useful mini-apps which all fit in a tweet!”
{ ws: Websocket -> while (true) { ws.send(WsMessage( Instant.now().toString()) ) Thread.sleep(1000) } }.asServer(Jetty()).start() ProxyHost(Https) .then(JavaHttpClient()) .withChaosControls(Latency() .appliedWhen(Always)) .asServer(SunHttp()) .start() static(Directory() .asServer(SunHttp()) .start() ProxyHost(Https) .then(RecordTo(Disk("store"))) .then(JavaHttpClient()) .asServer(SunHttp()) .start() JavaHttpClient().let { client -> Disk("store").requests() .forEach { println(it) client(it) } }
http://bit.ly/http4knanoservices
quickstart: start.http4k.org web: www.http4k.org slack: #http @ kotlinlang @http4k @daviddenton david@http4k.org @s4nchez ivan@http4k.org