Releases: finagle/finch
Finch 0.26
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 anpathEmpty
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 matchedTrace
(see #1017)- We refined our Todo App example with the new shiny UI and tests (see #1020)
Finch 0.25
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
Finch 0.24
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
- Fixed a bug when a dispatching multiple decoders (see #980)
- finch-sprayjson is extracted into its own project (see #982 and https://github.com/finch/finch-sprayjson)
Finch 0.23
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 Rerunnable
s) 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
- New cookbook recipe on how to use Finagle clients (see #962, thanks @erikwj)
- Bug fix for
build.sbt
so finch-refined is actually published now (see #969, thanks @rider-yi) - The user guide has been updated with examples on how to encode accumulated errors from Circe (see #970, thanks @rpless)
- finch-json4s was extracted into its own project (see #971 and https://github.com/finch/finch-json4s).
Finch 0.22
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 Trace
s 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, aTrace
will always be captured within aTrace.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 theservice(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
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
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
andPOST /users
). - Charset wasn't properly set on streaming responses (see #938, thanks @gregor-rayman).
Finch 0.19
This release just bumps Finagle/Util to 18.4 (thanks to @travisbrown).
Finch 0.18.1
Finch 0.18
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
- circe-jackson dependency was removed, use standard Circe's printers instead (see #916)
- finch-jackson is extracted into its own project, https://github.com/finch/finch-jackson (see #920)
- path extracting endpoints now apply URL-decoding (see #917, thanks @sergeykolbasov)
- there are new content types defined out of the box (see #918, thanks @muuki88)