Date post: | 16-Apr-2017 |
Category: |
Engineering |
Upload: | seiya-mizuno |
View: | 153 times |
Download: | 0 times |
Introduction to FinchEnglish edition
GitHub @ Saint1991
What is Finch?A combinator library to build up Finagle’s
Service in a functional fashion.
Service
HTTP Server
HTTP ClientFinch
Build using CombinatorsFilter
finagle
≒Request => Future[Response]
Why Finch?Unlike Finagle, Finch is specialized to HTTP
Easy routing Rich Utility for HTTP Supporting modern JSON libararies like Circe and Argonaut. Finagle’s strong functionalities like Filter and Server are stil available
Differences in Code
GET /div/op1/(int1)/op2/(int2)
{ "result": (int)}=>
An endpoint that retruns the division of int1 by int2
Finagle
import io.circe.generic.auto._import io.circe.syntax._case class Res(result: Int)
val service: Service[Request, Response] = RoutingService.byMethodAndPathObject[Request] { case (Get, Root / "div" / "op1" / op1 / "op2" / op2) => new Service[Request, Response] { def apply(request: Request): Future[Response] = Future.value( allCatch withTry { Response(request.version, Status.Ok, Reader.fromBuf( Buf.Utf8( Res(op1.toInt / op2.toInt).asJson.noSpaces ) )) } getOrElse Response(request.version, Status.BadRequest, Reader.fromBuf( Buf.Utf8("Invalid params") )) ) }}
1. RoutingService and Pattern matching are required for routing
1
2. Error handling coexists in a main logic…
2
23
3. JSON serialization is on your own
Finch
case class Res(result: Int)
val getDiv: Endpoint[Res] = get( "div" :: "op1" :: int :: "op2" :: int.shouldNot("be 0") { _ == 0 }) { (op1: Int, op2: Int) => Ok(Res(op1 / op2))}
import io.finch.circe._import io.circe.generic.auto._val service: Service[Request, Response] = getDiv.toService
1. No redundant components is required for routing2. Easy to write error handling (It’s separated from main logic)3. Finch internally treats JSON serialization!
What is Endpoint in Finch?Implementing on Finch≒ Implementing
Endpoint[A]
Endpoint[A]
val getDiv: Endpoint[Res] = get( "div" :: "op1" :: int :: "op2" :: int.shouldNot("be 0") { _ == 0 }) { (op1: Int, op2: Int) => Ok(Res(op1 / op2))}
Request => Option [ Future[ Output[A] ] ]HTTP Response
Aynchronous procedure(May be in backend)(50x occurs here)
Relation to Finagle ServiceFinagle’s service is composed of a combination
of Endpoints serially or parallely
Endpoint 1
Endpoint 3
::
Endpoint 4
:+:
:+:Finagle Service
Endpoint 2
toService
How to compose Endpoints? Routing JSON serialization (Circe) Validation for data user input Error Handling
How to compose Endpoints? Routing JSON serialization (Circe) Validation for data user input Error Handling
RoutingMethod
Utility functions according to each HTTP method are provided
PathUse :: to compose as an Endpoint
Use :+: to compose as different Endpoints
get("div" :: "op1" :: int :: "op2" :: int)
GET /div/op1/(int)/op2/(int)
get("hello" :: string) :+: post("echo" :: string)
GET /hello/(string) POST /echo/(string)
Routing - extracting user inputsBuild-in utilities are provided for premitives
Path params• string, long, int, boolean, uuid
Query params• param, paramOption, params, paramsNel
Body• body, bodyOption, binaryBody, binaryBodyOption,
asyncBody
get("div" :: "op1" :: int :: "op2" :: int :: paramOption("pretty").as[Boolean])
GET /div/op1/(int)/op2/(int)[?pretty={true|false}]
etc…
Routing - extracting user inputsExtracted parameters are passed to
Endpoint#applyProcess these here to convert to desired
output as a response.get( "div" :: "op1" :: int :: "op2" :: int :: paramOption("pretty").as[Boolean]){ (op1: Int, op2: Int, isPretty: Boolean) => ??? }
Column about Routing (1)An example that serially compose two
existing endpoints
val getSumOfProdAndDiv: Endpoint[Int] = get(getProd :: getDiv) { (product: Int, division: Int) => Ok(product.result + division.result)}
val getDiv: Endpoint[Int] = get("div" :: "op1" :: int :: "op2" :: int) { (op1: Int, op2: Int) => Ok(op1 / op2)}
val getProd: Endpoint[Int] = get("prod" :: "op1" :: int :: "op2" :: int) { (op1: Int, op2: Int) => Ok(op1 * op2)}
GET /prod/op1/(int)/op2/(int)/div/op1/(int)/op2/(int)
Don’t combine endpoints of different HTTP method.It does not match any requests!
Column about Routing (2)Combination by :+: is ordered
val hello: Endpoint[String] = get("hello" :: string) { (str: String) => Ok(s"Hello $str!!!") }
val helloBar: Endpoint[String] = get("hello" :: "bar") { Ok("bar")}
hello :+: helloBarGiven
GET /hello/bar => “Hello bar!!!”(1)
helloBar :+: hello
GET /hello/bar => “bar”(2)
Match is checked from head to tail in a combination order
Given
How to compose Endpoints? Routing JSON serialization (Circe) Validation for data user input Error Handling
JSON Serialization (Circe)What you have to do are only
Define implicit Encoder[A] within the scope of toService
Import io.finch.circe._ within the scope of toServicecase class Res(result: Int)
object CalcService { val getDiv: Endpoint[Res] = get("div" :: "op1" :: int :: "op2" :: int) { (op1: Int, op2: Int) => Ok(Res(op1 / op2)) }}
object ServerApp extends TwitterServer { import io.finch.circe._ import io.circe.generic.auto._ val endpoints = CalcService.getDiv.toService . . .}
Not here…
IT’S HERE!!!
※ With Circe, importing io.circe.generic.auto._ This generates Encoder of any case classes !!!
Not JSON ResponseAll responses are serialized as
application/json. It’s sometimes not convenient…val hello: Endpoint[String] = get("hello" :: string) {
who: String => Ok(s"Hello $who")}
GET /hello/bar => ”Hello bar”Response value is wrapped with ”” …
val helloPlain: Endpoint[Response] = get("helloPlain" :: string) { who: String => val res = Response() res.setContentType("text/plain") res.setContentString(s"Hello $who") Ok(res)}
GET /helloPlain/bar => Hello barTo avoid it, turn the response type of Endpoint
to Buf or Reponse and set Content-Type
How to compose Endpoints? Routing JSON serialization (Circe) Validation for data user input Error Handling
Validation for User InputsIt can be put with any extractors .
get("div" :: "op1" :: int :: "op2" :: int.shouldNot("be 0") {_ == 0})
=> NotValid will be thrown when provided 0 to the second param
Validation codes can be separated from main logic!!!
※Finch responds with BadRequest when NotValid is thrown ,
How to compose Endpoints? Routing JSON serialization (Circe) Validation for data user input Error Handling
Error HandlingAny not handled exceptions can be caught in
a “handle” clause When some exceptions are not handle, Finch
responds with InternalServerError with an empty payload…
val getDiv: Endpoint[Res] = get( "div" :: "op1" :: int :: "op2" :: int) { (op1: Int, op2: Int) => Ok(Res(op1 / op2))} handle { case ae: ArithmeticException => BadRequest(ae)}
Error HandlingWhen you want to responds with your custom
Exception , you have to override Encoder[Exception] that is defined in Finch as a defaultcase class ErrorRes(errorCode: Int, message: String) extends Exception
import io.circe.syntax._ import io.finch.circe._import io.circe.generic.auto._ implicit val errorEncoder: Encoder[Exception] = Encoder.instance { case er: ErrorRes => er.asJson}
val endpoints = CalcService.getDiv.toService
※ It’s override. If you don’t specify the case of Finch build-in exceptions like NotValid, NotPresent, NotParsed, etc…) are not handled. In practice you had better care those cases.
!
Entire code (some imports are omitted)
case class Res(result: Int)case class ErrorRes(errorCode: Int, message: String) extends Exception
object CalcService { val getDiv: Endpoint[Res] = get("div" :: "op1" :: int :: "op2" :: int) { (op1: Int, op2: Int) => Ok(Res(op1 / op2)) } handle { case ae: ArithmeticException => BadRequest(ErrorRes(400, ae.getMessage)) }}
object ServerApp extends TwitterServer {
import io.finch.circe._ import io.circe.generic.auto._ import io.circe.syntax._
implicit val errorEncoder: Encoder[Exception] = Encoder.instance { case er: ErrorRes => er.asJson } val endpoints = CalcService.getDiv.toService
val server: ListeningServer = Http.server.serve(":8000", endpoints) onExit(Await.ready(server.close(30 seconds))) Await.ready(server)}
Pros. and Cons.Pros.
Easy routingExcellent JSON support.Maybe easy to test
Cons.10% overheadHave to pay attension to the scope of JSON
EncodersIntelliJ…