Skip to content

Releases: finagle/finch

Finch 0.26

15 Nov 02:31
Compare
Choose a tag to compare

finch-* Release (Twitter Futures)

  • We have updated to Twitter Utils and Finagle 18.11.0

finchx-* Release (Cats Effect)

Backwards Compatibility Updates

One of the biggest concerns with moving to cats-effect in 0.25 was the lack of backwards compatibility with existing Future-based endpoints (both Scala and Twitter implementations). This version eases that transition by adding support for both Scala and Twitter futures in the Mapper. See #1016 for more details.

scala> import io.finch._, io.finch.catsEffect._
scala> import com.twitter.util.Future

scala> val foo = get(pathEmpty) { Future.value(Ok("foo")) }
<console>:18: warning: method mapperFromFutureOutputValue in trait HighPriorityMapperConversions is deprecated (since 0.25.0): com.twitter.util.Future use is deprecated in Endpoints. Consider to use cats-effect compatible effect
       val foo = get(pathEmpty) { Future.value(Ok("foo")) }
                                              ^
foo: io.finch.Endpoint[cats.effect.IO,String] = GET /

NOTE: The foo endpoint is still structured as Endpoint[cats.effect.IO, String]. Both Scala and Twitter Futures are converted to IO when endpoints are mapped.

Stewarding a better Environment

In the past, we've gotten a fair number of Github issues and Gitter messages about getting dependencies up to date. With this release we have enabled Scala Steward which makes PRs to update versions of dependencies. A huge thanks to @fthomas for creating this tool and to @travisbrown for adding this to our main repository. We are looking into adding it into our other repositories from the Finch Org.

Serving Static Assets

We're not about telling you not to do something with Finch, only that you can do things with Finch. In this vein, you probably shouldn't serve static assets from the classpath or a file from Finch (or really any HTTP library that isn't designed for serving static assets). But we've added Endpoint.classpathAsset and Endpoint.filesystemAssert that let you do just that. Please, try not to use this in a production environment as it will never match the throughput of what static server. See #1017 for more details.

scala> import io.finch._, io.finch.catsEffect,
scala> import cats.effect.{IO, ContextShift}
scala> import scala.concurrent.ExecutionContext

scala> implicit val S: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
S: cats.effect.ContextShift[cats.effect.IO] = cats.effect.internals.IOContextShift@641d5285

scala> val index = classpathAsset("/index.html")
index: io.finch.Endpoint[cats.effect.IO,com.twitter.io.Buf] = GET /index.html :: io.finch.endpoint.package$FromInputStream@63905eda

scala> index.toServiceAs[Text.Html]
res3: com.twitter.finagle.Service[com.twitter.finagle.http.Request,com.twitter.finagle.http.Response] = io.finch.ToService$$anon$4$$anon$2

Gitter8

We now have a basic gitter8 template to get new projects started fast (thanks to @vkostyukov). Just run sbt new finch/finch.g8 to get started.

Other Changes

  • Endpoint now has an pathEmpty method which matches an empty path string (see #1015)
  • We have updated to Twitter Utils and Finagle 18.11.0 (see #1033)
  • Our version badge now points at Scala Index (see #1010)
  • Endpoint.pathAny now properly captures the matched Trace (see #1017)
  • We refined our Todo App example with the new shiny UI and tests (see #1020)

Finch 0.25

21 Oct 16:04
Compare
Choose a tag to compare

Our anniversary 50th release brings a lot of exciting changes. The biggest news is starting with 0.25 (this release) Finch is now completely agnostic to the underlying effect type (was Twitter Future before).

Polymorphic Endpoints

This is, perhaps, the biggest change in Finch since introduction of Endpoint couple of years back. Locking our core APIs to a particular effect type (Twitter Future) never felt right yet, surely, was a reasonable thing to do at first. As Finch's API matured, we figured the major source of complains from our users was about this hard dependency to a Twitter's stack (think Twitter Util's).

With the 1.0 release of cats-effect, it became clear that Finch's path towards better interoperability with the rest of typelevel libraries (think Cats-based libraries) was adopting Effect type classes in the Endpoint API.

Long story short Endpoint[A] now becomes Endpoint[F[_], A] where F[_] is anything that has an cats.effect.Effect instance (think ZIO, Monix Task, Cats IO, Catbird Rerunnable, etc). This heroic effort was driven by @sergeykolbasov in #979.

This sounds scary at least - a LOT of API changes. To mitigate the impact we keep releasing normal Finch artifacts (Twitter Futures) under a previous name: finch-*. This means you don't really have to migrate to a new endpoint API just now. The current plan is to keep supporting a previous version of Finch API for some time (at least 6 months) but keep its branch under feature freeze (commit only bug-fixes and dependency bumps).

If you're feeling adventurous, you can jump into polymorphic endpoints now by depending on finchx-* artifacts instead.

libraryDependencies ++= Seq(
  "com.github.finagle" %% "finchx-core" % "0.25.0",
  "com.github.finagle" %% "finchx-circe" % "0.25.0",
)

There several ways how to instantiate polymorphic endpoints (see #988). Perhaps, the most standard way is via importing io.finch.catsEffect._ EndpointModule (in case if you prefer Cats IO as your effect type).

import io.finch._
import io.finch.catsEffect._


val p: Endpoint[IO, Int] = param[Int]("foo")

You can also specify the F[_] type constructor explicitly should you prefer other effect implementation.

import io.finch._

val p: Endpoint[MyIO, Int] = Endpoint[MyIO].param[Int]("foo")

Or via instantiating an EndpointModule for your effect instance.

import io.finch._

object myIO extends Endpoint.Module[MyIO]
import myIO._

val p: Endpoint[MyIO, Int] = param[Int]("foo")

Notable Changes Accommodating cats-effect Integration

  • Syntax Package became obsolete (do not need to import it) as we can now derive syntax based on Effect instance.
  • As we're no longer depending on Twitter Futures, other Twitter APIs (Duration, Try) have been migrated to their Scala's counterparts.

Performance

We did some initial testing of new endpoints and the outcome looks optimistic. Essentially, we're observing similar performance gains as we did with Trane Arrows couple of releases back.

Auto Releases

Inspired by how Trane Arrows's build is setup, @arron-green kicked in a new releasing machinery for Finch. Now we can cut releases via a single commit that changes the version.sbt file (see #995 and #1000 for more details). Everything is automated and requires no human supervision or interaction. This very release was performed this way.

This is quite an exciting and very necessary improvement as now any Finch maintainer (a person with a push access to a repository) can cut releases.

Other Changes

  • Finagle is bumped to 18.10 (see #1007)
  • Circe is bumped to 0.10 (see #991)

Finch 0.24

18 Sep 03:58
Compare
Choose a tag to compare

This is matching release for Finagle 18.9 (September 2018). Additional this bring a couple of improvements and fixes.

Support to 415 UnsupportedMediaType

It's now possible to configure Bootstrap to respond 415 UnsupportedMediaType from a derived service should the incoming request has a wrong media type. The default behavior kept unchanged and 400 BadRequest is returned. See #980 for more details.

As a side bonus this changes the way we apply decoders to HTTP payloads. Starting this release a decoder won't be applied until its content-type matches requests' (it fails-fast when not matched).

scala> case class Foo(s: String)
defined class Foo

scala> val b = jsonBody[Foo]
b: io.finch.Endpoint[Foo] = body

scala> val s = Bootstrap
  .configure(enableUnsupportedMediaType = true)
  .serve[Application.Json](b)
  .toService

s: com.twitter.finagle.Service[com.twitter.finagle.http.Request,com.twitter.finagle.http.Response] = io.finch.ToService$$anon$4$$anon$2

scala> Http.server.serve(:8081, s)

And then:

$ http :8081 s=bar
HTTP/1.1 200 OK
Content-Type: application/json
Date: Tue, 18 Sep 2018 03:29:58 GMT
Server: Finch
content-encoding: gzip
content-length: 37

{
    "s": "bar"
}

$ http :8081 Content-Type:text/plain s=bar
HTTP/1.1 415 Unsupported Media Type
Content-Length: 0
Date: Tue, 18 Sep 2018 03:30:24 GMT
Server: Finch

Finch with Arrows

We're still publishing Finch artifacts with Trane Arrows as they seem to provide a better throughput. This is, however, going to be our last release with Trane Arrows. The current plan for 0.25 is make Endpoint polymorphic in terms of its effect type (see #979).

Other Changes

Finch 0.23

09 Aug 23:21
00d0ca2
Compare
Choose a tag to compare

This new Finch release, while matching Finagle 18.8, comes with several quite exciting improvements.

Multiple Decoders for Bodies

We've been asked if it's possible to specify multiple decoders (in a sense of or application) to a single body endpoint. Similar how users can serve multiple content-types based on the Accept header value, they now can resolve multiple decoders at compile-time and dispatch to an appropriate decoder at runtime when a request with the Content-Type header arrives (see #966 for implementation details; thanks @sergeykolbasov).

scala> import io.finch._, com.twitter.util._, com.twitter.io.Buf, shapeless._

scala> :paste
// Entering paste mode (ctrl-D to finish)

case class Foo(s: String)
object Foo {
  implicit val d1 = Decode.instance[Foo, Text.Plain]((_, _) => Return(Foo("text")))
  implicit val d2 = Decode.instance[Foo, Text.Html]((_, _) => Return(Foo("html")))
}

^D

scala> val foo = body[Foo, Text.Plain :+: Text.Html :+: CNil]
foo: io.finch.Endpoint[Foo] = body

scala> foo(Input.post("/").withBody[Text.Plain](Buf.Utf8("foo"))).awaitValueUnsafe()
res5: Option[Foo] = Some(Foo(text))

scala> foo(Input.post("/").withBody[Text.Html](Buf.Utf8("foo"))).awaitValueUnsafe()
res6: Option[Foo] = Some(Foo(html))

Arrows instead of Rerunnables

We've been experimenting with Trane Arrows as a drop-in replacement for Finch's underlying effect, Rerunnable. The results are quite uplifting considering the performance improvements gained without visible API changes.

Here is a sneak peek into the performance improvements (see #964 for more details; thanks @erikwj).

BEFORE (Rerunnable)

[info] Benchmark                                                   Mode  Cnt     Score     Error   Units
[info] ProductBenchmark.bothMatched                                avgt   10   984.884 ±  35.849   ns/op
[info] ProductBenchmark.bothMatched:·gc.alloc.rate.norm            avgt   10  1472.000 ±   0.001    B/op

AFTER (Arrow/Task)

[info] ProductBenchmark.bothMatched                                avgt   10   361.867 ±   8.955   ns/op
[info] ProductBenchmark.bothMatched:·gc.alloc.rate.norm            avgt   10   712.000 ±  12.749    B/op

We published Finch depending on Arrows (instead of Rerunnables) under its own artifact for now for the sake of keeping the standard version unchanged. We are aiming to collect more feedback from users before we decide to adopt/not adopt. Use the following dependency (finch-arrows-x instead of finch-x) to experiment with Arrows (no source changes required) and let us know what do you think in #968.

libraryDependencies ++= Seq(
  "com.github.finagle" %% "finch-arrows-core" % "0.23.0",
  "com.github.finagle" %% "finch-arrows-circe" % "0.23.0",
)

Other Changes

Finch 0.22

12 Jul 17:41
Compare
Choose a tag to compare

This release bumps Finagle to 18.7 and introduces "tracing" allowing users to identify what endpoint (distinguished by path) was matched.

Tracing for Endpoints

It's been a known problem for quite a while: there is no way to tell what endpoint (out of the coproduct of endpoints) was matched. This has been making it non-trivial to add telemetry and instrumentation into Finch applications.

In #957, we introduced a new data type, io.finch.Trace, that models a path over which an endpoint is matched and is returned as part of EndpointResult. For example:

scala> import io.finch._, io.finch.syntax._
import io.finch._
import io.finch.syntax._

scala> val foo = get("foo" :: "bar" :: path[String]) { s: String => Ok(s) }
foo: io.finch.Endpoint[String] = GET /foo :: bar :: :string

scala> val bar = get("bar" :: "foo" :: path[Int]) { i: Int => Ok(i) }
bar: io.finch.Endpoint[Int] = GET /bar :: foo :: :int

scala> val fooBar = foo :+: bar
fooBar: io.finch.Endpoint[String :+: Int :+: shapeless.CNil] = (GET /foo :: bar :: :string :+: GET /bar :: foo :: :int)

scala> fooBar(Input.get("/foo/bar/baz")).trace
res0: Option[io.finch.Trace] = Some(/foo/bar/:string)

scala> fooBar(Input.get("/bar/foo/10")).trace
res1: Option[io.finch.Trace] = Some(/bar/foo/:int)

In #960, we allowed users to capture such Traces on their side (presumably in Finagle filters) using Twitter Future Locals. The usage API looks as follows.

scala> val foo = get("foo" :: path[String]) { s: String => Ok(s) }
foo: io.finch.Endpoint[String] = GET /foo :: :string

scala> import com.twitter.finagle.http.Request
import com.twitter.finagle.http.Request

scala> val s = foo.toServiceAs[Text.Plain]
s: com.twitter.finagle.Service[com.twitter.finagle.http.Request,com.twitter.finagle.http.Response] = io.finch.ToService$$anon$4$$anon$2

scala> Trace.capture { s(Request("/foo/bar")).map(_ => Trace.captured) }
res0: com.twitter.util.Future[io.finch.Trace] = Promise@406161512(state=Done(Return(/foo/:string)))

A couple of things worth noting with regards to the new machinery:

  • There is no need to explicitly enable it in Bootstrap options, a Trace will always be captured within a Trace.capture context.
  • There is no need to wait for a service's future to resolve before retrieving a captured Trace as it's immediately available once endpoint is matched (i.e., after the service(req) call).
  • Obviously materializing a new structure on each request comes at the cost of allocations/running time. We, however, haven't observed any significant overhead in Finch's benchmarks.

Finch 0.21

16 Jun 05:40
Compare
Choose a tag to compare

This release bumps Finagle to 18.6 as well as introduces the following improvements.

  • New finch-refined module (see #944, thanks @sergeykolbasov)
  • Performance improvements for Input.fromRequest (see #943)
  • Faster charset parsing (see #946)

Additionally, headerExist and paramExist are now deprecated. Use paramOption and headerOption to retrive optional entities.

Finch 0.20

28 May 15:47
Compare
Choose a tag to compare

This release bumps the Finagle/Util versions to 18.5 and fixes a couple of embarrassing bugs:

  • Body endpoints were evaluating (decoding) while being matched (see #939, thanks @manuelcueto for reporting). This could have resulted in decoding the same request twice if an app served two endpoints that only differ by a verb (eg: PUT /users and POST /users).
  • Charset wasn't properly set on streaming responses (see #938, thanks @gregor-rayman).

Finch 0.19

13 Apr 16:55
Compare
Choose a tag to compare

This release just bumps Finagle/Util to 18.4 (thanks to @travisbrown).

Finch 0.18.1

23 Mar 22:13
Compare
Choose a tag to compare

This is a patch release that fixes #929 (header not parsed was reported as param not parsed). Thanks to @jsancio for fixing it (see #930)!

Finch 0.18

18 Mar 21:16
Compare
Choose a tag to compare

A new Finch release against Finagle 18.3.

Improved compile times

Georgi Krastev (@joroKr21) in #924 quite dramatically improved compilation time of coproduct endpoints. He and @rpless reported improvements 4m -> 6s and 140s -> 70s respectively. These are quite outstanding improvements given how local and minimal the change is.

MethodNotAllowed support

More than a year ago we started talking about automatically supporting 405 MethodNotAllowed for the cases when an endpoint matches but for a different method. Sergey Kolbasov (@sergeykolbasov) gave it a shot in #883 and later myself iterated on it in #927 such that we also compliant with RFC 2016, Section 10.

The new behavior is disabled by default but you can easily opt-in for 405 support via a new Bootstrap option.

scala> val foo = get("foo") { Ok("foo") }
foo: io.finch.Endpoint[String] = GET /foo

scala> val s = Bootstrap
  .configure(enableMethodNotAllowed = true)
  .serve[Text.Plain](foo)
  .toService
s: com.twitter.finagle.Service[com.twitter.finagle.http.Request,com.twitter.finagle.http.Response] = <function1>

scala> Http.server.serve(":8081", s)

And then (note the Allow header derived automatically):

$ http GET :8081/foo
HTTP/1.1 200 OK
Content-Type: text/plain
Date: Sun, 18 Mar 2018 21:09:02 GMT
Server: Finch
content-encoding: gzip
content-length: 29

foo

$ http PUT :8081/foo
HTTP/1.1 405 Method Not Allowed
Allow: GET
Content-Length: 0
Date: Sun, 18 Mar 2018 21:09:05 GMT
Server: Finch

Other changes