Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Introduction of Finch

Introduction of Finch

Brief introduction one of the remarkable scala HTTP framework Finch.

Saint1991

March 08, 2017
Tweet

More Decks by Saint1991

Other Decks in Technology

Transcript

  1. What is Finch? pA combinator library to build up Finagleʼs

    Service in a functional fashion. Service HTTP Server HTTP Client 'JODI #VJMEVTJOH$PNCJOBUPST Filter finagle Request => Future[Response]
  2. Why Finch? pUnlike 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
  3. Differences in Code GET /div/op1/(int1)/op2/(int2) { "result": (int) } =>

    An endpoint that retruns the division of int1 by int2
  4. 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 2 3 3. JSON serialization is on your own
  5. 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 routing 2. Easy to write error handling (Itʼs separated from main logic) 3. Finch internally treats JSON serialization!
  6. What is Endpoint in Finch? pImplementing on Finch≒ Implementing Endpoint[A]

    pEndpoint[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)
  7. Relation to Finagle Service pFinagleʼs service is composed of a

    combination of Endpoints serially or parallely Endpoint 1 Endpoint 3 :: Endpoint 4 :+: :+: Finagle Service Endpoint 2 UP4FSWJDF
  8. How to compose Endpoints? p Routing p JSON serialization (Circe)

    p Validation for data user input p Error Handling
  9. How to compose Endpoints? p Routing p JSON serialization (Circe)

    p Validation for data user input p Error Handling
  10. Routing pMethod uUtility functions according to each HTTP method are

    provided pPath uUse :: to compose as an Endpoint uUse :+: 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)
  11. Routing - extracting user inputs pBuild-in utilities are provided for

    premitives uPath params • string, long, int, boolean, uuid uQuery params • param, paramOption, params, paramsNel uBody • 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…
  12. Routing - extracting user inputs pExtracted parameters are passed to

    Endpoint#apply pProcess 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) => ??? }
  13. Column about Routing (1) pAn 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 !
  14. Column about Routing (2) pCombination 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 :+: helloBar Given 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
  15. How to compose Endpoints? p Routing p JSON serialization (Circe)

    p Validation for data user input p Error Handling
  16. JSON Serialization (Circe) pWhat you have to do are only

    uDefine implicit Encoder[A] within the scope of toService uImport io.finch.circe._ within the scope of toService case 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 !!!
  17. Not JSON Response pAll 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 bar To avoid it, turn the response type of Endpoint to Buf or Reponse and set Content-Type
  18. How to compose Endpoints? p Routing p JSON serialization (Circe)

    p Validation for data user input p Error Handling
  19. Validation for User Inputs pIt 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,
  20. How to compose Endpoints? p Routing p JSON serialization (Circe)

    p Validation for data user input p Error Handling
  21. Error Handling pAny not handled exceptions can be caught in

    a “handle” clause uWhen 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) }
  22. Error Handling pWhen you want to responds with your custom

    Exception,you have to override Encoder[Exception] that is defined in Finch as a default case 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. !
  23. 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) }
  24. Pros. and Cons. pPros. uEasy routing uExcellent JSON support. uMaybe

    easy to test pCons. u10% overhead uHave to pay attension to the scope of JSON Encoders uIntelliJ…