finch icon indicating copy to clipboard operation
finch copied to clipboard

Creating your own Services

Open olib963 opened this issue 5 years ago • 2 comments

I cannot find in the docs a way to create a Service[NotRequest, NotResponse], perhaps I am missing something obvious but I am wondering if you need to recreate the whole stack.

Expected behavior

I would like to be able to extend the Request and Response types to create a Service[AuthorisedRequest, MonitoredResponse].

Actual behavior

I can implement a Filter[Reqest, Response, AuthorisedRequest, MonitoredResponse] but I don't know how to create endpoints etc. that can be integrated with these types. Ideally I would like something like this as a trivial case:

// Healthchecks
val ping = get("ping"){_ => Ok("pong")}
val healthService = Bootstrap.serve[Plaintext](ping).toService

// Authorised endpoints
val hello = withRole("foo")(get("/hello"){_ =>
 Ok(Timed(Duration.ofMillis(10), value = "world"))
})

val otherEndpoint = // more of the same

val authedService = (hello :+: otherEndpoint) // Somehow turn into Service[AuthorisedRequest, MonitoredResponse]

// Server to run
val server = healthService andThen (MyFilter andThen authedService)

I can see that you have the com.twitter.finagle.http.SpnegoAuthenticator that does a similar transformation on the request but I am unsure how to use it. Perhaps someone could point me in the direction of documentation for using a Filter?

olib963 avatar May 20 '20 16:05 olib963

Hi @olib963

Sorry for the late response. Finch can't generate a Service for anything that is not Request/Response

What I could suggest (and what we use in production) is to use a more complex monad instead, such as Kleisli or StateT, and store/update the information on the request as a part of the monad's algebra.

Sadly, I don't have any examples to demonstrate what I mean right away, but we're going to improve this part soon.

sergeykolbasov avatar Jun 10 '20 07:06 sergeykolbasov

Hey, thank you for your response. That makes sense, I couldn't find a way to do so in the API.

Currently the process is to

  • use a headerOption Endpoint to do RBAC on our endpoints. This works but leaves us in the position where you can send an aunauthenitcated request and we will parse the body anyway
  • use another Endpoint type for monitoring

For example:

val foo = post("foo" :: jsonbody[Foo] :: withRole("create-foo") :: monitor[Future]("create-foo")){
   (foo: Foo, monitoringStuff: Metrics[Future, Unit]) =>
      val endpointAction = monitoringStuff.flatMap(_ => /* do stuff */)
      val result: Future[Id] = metricsService.eval(endpointAction)
      result.map(_ => Created())
}

such that withRole will return 403, 401 or 200 but jsonBody is always evaluated. It works OK I guess, I had just hoped given the types in Service we would be able to extend it.

Examples would be fantastic thanks!

olib963 avatar Jun 13 '20 11:06 olib963