Releases: finagle/finch
Finch 0.15
Netty 4 is a default now
Finagle 6.45 brings HTTP implementation based on Netty 4. It's now a default transport for both servers and clients created with Finagle. At this point, it's known that finagle-http running Netty 4 might be performing slightly worse than Netty 3 given the overhead the outdated Netty 3-based HTTP model involves. The Finagle team is aware of that and is working hard to remove the Netty 3 dependency from finagle-http hence make it faster with Netty 4.
If you're cautious about the performance degradations (roughly 15%-20% slower on our "Hello, World!" benchmark) during the upgrade, stick to Netty 3 for now (as shown below):
import com.twitter.finagle.Http
val server = Http.server.configured(Http.Netty3Impl)
Default exception encoders
We decided to stop supplying the default instances of exception JSON encoders (i.e., Encode.Json[Exception]
) given the amount of confusion and ambiguity they bring on the table (examples: #765, #737). The default behavior now (unless an instance of Encode
is provided) is to encode exceptions to just empty payloads.
New syntax package
We're starting to decouple Endpoint
s from their syntax (i.e., Mapper
). The fact they were coupled together caused lots of surprising compile errors in the past (especially in the Scala 2.10 times) and we're quite happy to start drawing a line between a concept and its syntax. As of this release, there are no breaking changes such that the Endpoint.apply(Mapper)
is still supported (although not directly) but we're planning to start hiding things behind the io.finch.syntax
namespace in the next release. See #779 for more details.
New API for path matching
We're promoting a new and more explicit API for path matching. Now there is a path
method that acts as following (see #771 for more details):
path("foo")
- matches a path "foo" (was"foo": Endpoint[HNil]
before)path[String]
- extracts a string path (wasstring
before)
Note that the previous API (i.e., int
, string
, etc) is still supported and we're pending its depreciation given the successful adoption of the new API.
While the new way is definitely more verbose (see example bellow) we think it is much more explicit and clear for the reader. At some point (when we merged request readers with endpoints), symbols as int
, string
, etc become ambiguous (i.e., does int
means "path as int" or "param as int") and are not something we'd like to see as part of 1.0 API (stable API).
val a = get("add" :: path[Int] :: path[Int]) { (a: Int, b: Int) => Ok(a + b) } // new
val b = get("bar" :: int :: int) { (a: Int, b: Int) => Ok(a + b) } // old
Other changes
finch-core/finch-circe 0.14.1
This release only publishes finch-core & finch-circe artifacts and rolls back string-less encoding for Circe. While it was known that byte-targeted printing might be a little slower for small/tiny input (see circe/circe#542), we didn't realize it might hit Finch really hard in the TechEmpower benchmark (-30%).
We're going to put string-less encoding back when we know more about the issue.
Finch 0.14
HTTP/2 Support
This release brings experimental HTTP/2 support via Finagle 6.43.
As usual, there are two ways enabling it in Finch's applications: via a toggle override or programmatically. Supply the following CLI flag to globally switch each HTTP client/server running in the same process:
-com.twitter.finagle.toggle.flag.overrides=com.twitter.finagle.http.UseHttp2=1.0
Or control the HTTP version on a per-client/per-server basis:
import io.finch._
import com.twitter.finagle.Http
import com.twitter.util.Await
Await.ready(
Http.server
.configuredParams(Http.Http2)
.serve(":8081", Endpoint.lift("foo").toServiceAs[Text.Plain])
)
New Site
Finch's user guide, best practices, and the cookbook are now published within the microsite with all the source code snippets type-checked by tut (thanks @erikwj, see #751).
Other changes
- HTTP
Date
header returned from a Finch service is now conforms the spec format (thanks @rpless, see #752) - New Finch module
finch-generic
now provides some basic machinery allowing for generic derivation of endpoints (thanks @ilya-murzinov, see #716).
Finch 0.13.1
Finch 0.13
This release is intended to accommodate Finagle 6.42 with its brand-new Netty 4 support. As of Finch 0.13, you may now (and should!) switch the underlying transport implementation over to Netty 4.
Netty 4 support
Historically, Finch was using some Netty 3-specific optimizations (i.e., hacks) to perform zero-copy retrieving of the HTTP payloads. In this release, we made everything Netty-agnostic and made sure it's still as efficient as it was before. This means you may now consider switching the underlying transport over to Netty 4 not worrying about potential performance penalties.
Switching to Netty 4 right now means staying aligned with future resiliency and performance improvements in finagle-http. At this point, there is no known difference in the throughput between finagle-http servers running Netty 3 and Netty 4. However, several major improvements for Netty 4 HTTP transport are planned to be shipped in the future Finagle releases. To mention a few: memory pooling (fewer allocations) and HTTP/2 support.
To jump into the Netty 4 land, supply the following CLI flag to your Finch application:
-Dcom.twitter.finagle.toggle.flag.overrides=com.twitter.http.UseNetty4=1.0
Or specify Netty 4 implementation programmatically on your Http.Server
instance:
import io.finch._
import com.twitter.finagle.Http
import com.twitter.util.Await
Await.ready(
Http.server
.configured(Http.Netty4Impl)
.serve(":8081", Endpoint.lift("foo").toServiceAs[Text.Plain])
)
Other changes
- There is now a
Date
header severed along with any Finch response (as it's required by spec). See #730 (thanks @n4to4) - 10 seconds startup delay and Netty warning on Scala 2.12 are now fixed in Finagle 6.42
Finch 0.12
Scala 2.12
This release finally brings Scala 2.12 support (see #668, thanks @ilya-murzinov, @travisbrown, @clhodapp). As for 0.12, Finch is cross-published for both 2.11 and 2.12.
String-less Encoding with Circe/Jackson
Since 0.11, Finch parses JSON directly from bytes (not strings), which improved its throughput quite dramatically (see #671). This time we're moving towards "string-less encoding" (see #676) with Circe and Jackson by printing directly into a byte buffer and returning that as an HTTP payload (see #717 and #714, thanks @imliar, @travisbrown).
The benchmark numbers look inspiring and demonstrate quite well how the initial idea of hiding the serialization/deserialization logic behind functional abstractions is starting paying back. Being able to control this part of request lifecycle allows for optimizations specific to a concrete JSON library.
Overall, the numbers confirm that printing directly to bytes cuts allocations in half (and improves the throughput).
circe-core (string)
[info] Benchmark Mode Cnt Score Error Units
[info] ToServiceBenchmark.foos thrpt 20 463.981 ± 24.913 ops/s
[info] ToServiceBenchmark.foos:·gc.alloc.rate.norm thrpt 20 5002135.084 ± 3252.704 B/op
[info] ToServiceBenchmark.ints thrpt 20 2118.919 ± 49.846 ops/s
[info] ToServiceBenchmark.ints:·gc.alloc.rate.norm thrpt 20 873272.466 ± 106.930 B/op
circe-core (bytes)
[info] Benchmark Mode Cnt Score Error Units
[info] ToServiceBenchmark.foos thrpt 20 523.730 ± 21.871 ops/s
[info] ToServiceBenchmark.foos:·gc.alloc.rate.norm thrpt 20 2602833.269 ± 76.806 B/op
[info] ToServiceBenchmark.ints thrpt 20 2234.631 ± 111.156 ops/s
[info] ToServiceBenchmark.ints:·gc.alloc.rate.norm thrpt 20 545837.968 ± 43806.910 B/op
EndpointResult
Previously, Endpoint
result was modeled as Option[Tuple2[Input, Rerunnable[Output[_]]]]
indicating that result could be either skipped (i.e., None
) or matched and both remainder and the output is returned. This release introduces a new type EndpointResult
(see #707) that encodes this very behavior as an ADT with two cases Matched
and Skipped
.
A standalone abstraction not only simplifies the reasoning about endpoints but also improves their performance. Technically, EndpointResult
is a flattened version of Option[Tuple2[_, _]]
so instead of two nested objects we only need one to carry the result. This simple idea impacts nearly all endpoint operations slightly reducing allocations and improving throughput (up to 5% better on some benchmarks).
Here are the numbers from map*
variants (before and after):
[info] MapBenchmark.mapAsync avgt 6 429.113 ± 43.297 ns/op
[info] MapBenchmark.mapAsync:·gc.alloc.rate.norm avgt 6 776.000 ± 0.001 B/op
[info] MapBenchmark.mapAsync avgt 6 407.126 ± 12.807 ns/op
[info] MapBenchmark.mapAsync:·gc.alloc.rate.norm avgt 6 720.000 ± 0.001 B/op
[info] MapBenchmark.mapOutputAsync avgt 6 821.786 ± 52.045 ns/op
[info] MapBenchmark.mapOutputAsync:·gc.alloc.rate.norm avgt 6 1376.001 ± 0.001 B/op
[info] MapBenchmark.mapOutputAsync avgt 6 777.654 ± 26.444 ns/op
[info] MapBenchmark.mapOutputAsync:·gc.alloc.rate.norm avgt 6 1320.001 ± 0.001 B/op
Better Testing APIs
Methods for querying a rerunnable Output
returned from an Endpoint
have been renamed to be explicitly alerting about their blocking nature. Now they all prefixed with await
and take an optional Duration
indicating the upper bound of await time (previously 10 seconds).
Deprecation schema:
value
->awaitValueUnsafe()
tryValue
->awaitValue()
output
->awaitOutputUnsafe()
tryOutput
->awaitOutput()
Basic HTTP Auth
Finch's implementation of Basic HTTP Auth has been moved to its own project finagle-http-auth and promoted to Finagle filter so it could be used with bare metal finagle-http.
To enable the Basic HTTP Auth on Finch's endpoint, apply the BasicAuth.Server
filter to the service yield from toService
call.
scala> import com.twitter.finagle.http.BasicAuth, io.finch._
scala> val ba = BasicAuth.serverFromCredentials("admin", "12345")
ba: com.twitter.finagle.http.BasicAuth.Server = <function2>
scala> val s = ba.andThen(Endpoint.const("foo").toServiceAs[Text.Plain])
s: com.twitter.finagle.Service[Request, Response]
Other Changes
- New method
Endpoint.liftToTry
that works in a similar fashion toFuture.liftToTry
(see #710) - New method
Endpoint.productWith
that also accepts a function(A, B) => C
(see #692, thanks @imliar) - Jackson's
ObjectMapper
is now embedded inio.finch.jackson
. No need for an implicit instance anymore (see #714)
Dependencies
- Finagle 6.41
- Circe 0.7
- Cats 0.9
- Shapeless 2.3.2
Finch 0.11.1
A patch release with couple of bug-fixes (and updated Jackson dependency)
- Handle
io.finch.Errors
(thanks @crispywalrus, #700) - JSON Encoding of
Exception
withnull
messages (thanks @akozhemiakin, #699) - Update Jackson to 2.8.5 (to be aligned with Finagle) (#702)
Finch 0.11
NOTE: If you're upgrading from 0.10, see changes in the milestone releases as well:
Goodbye Scala 2.10 and Java 7
We've finally decided to drop 2.10 and Java 7 support and align with the most recent version of Finagle (6.40 at this point). See #686. For the reference, Finagle and Co dropped 2.10 support in 6.35 (five releases ago).
Server Sent Events
Finch now provides very basic support to SSE. See #655 and the new cookbook example (thanks @rpless).
Long story short, serving AsyncStream[io.finch.sse.ServerSentEvent[A]]
, for which, cats.Show[A]
is defined will stream generated events back to the client until the connection is closed or the stream is empty.
New Decoding Machinery
In 0.11-M1 we've made a decent progress on making encoding in Finch less magical by introducing a type-level content type. This removed a lot of ambiguity when more than one implicit encoder present in the scope.
This time, we did a similar trick for Decode
type class. Now it embeds a type-level string indicating a content-type this decoder can decode. This highlighted an obvious difference between decoding query-string param as Int
and HTTP body as JSON. These are clearly two different use case that should be handled separately. Thus there is a new type class for decoding HTTP entities (not bodies) in Finch: DecodeEntity
that (1) doesn't embed a content-type and (2) decodes from String
. Decode
, on the other hand, know about content-type and (now) decodes from Buf
(as it should). See #663 for more details.
This work not only made the decoding story in Finch more explicit (clear separation of concerns, decoders for HTTP bodies are now resolved w.r.t. their content-types) but also allowed a performance optimization for body*
endpoints. Given that we now can decode directly from Buf
, we can eliminate one extra copy of going from Buf
to String
(to satisfy the DecodeEntity
input type). At this point, we managed to improve the throughput on out end-to-end test by 12.5%. See #671 (thanks @rpless) for more details.
All these benefits came with the cost of breaking API. See API changes for more details.
Decoding with Content-Type
Now that we have type-level content-type in place, we can enforce the implicit resolution to pick the right decoder for an expected request. We introduce a new API for body*
endpoints that also accept the desired content-type. See #695 for more details.
NOTE: body.as[User]
is still supported and defaults to body[User, Application.Json]
.
// before (deprecated in this version)
body.as[User]
// after
body[User, Application.Json]
// or using an alias method
jsonBody[User]
Previously, it was also possible to run as[User]
to decode a User
from JSON sent as a query string param or header. This indeed really powerful and allows some questionable design patterns, which are not necessary useful. Instead of being super generic here, we're trying to reduce the number of ways things could be built. This not only makes them easy to reason about but also quite efficient (b/c you now, specialization).
That said, we're promoting a new way of decoding HTTP payloads. Instead of using .as[A]
, we make it less powerful and more explicit. By limiting the responsibility of the Decode
type-class, we tight it directly with HTTP payloads. This means constructing body*
endpoints could be done in a single step, instead of producing 3-nested structures hence reduce allocations.
Quick experiments showed that we could save 15% of running time and 20% of allocations by just beeing explicit (json2
is the new body[A, Application.Json]
, json
is body.as[A]
).
[info] BodyBenchmark.json avgt 6 4824.580 ± 1205.444 ns/op
[info] BodyBenchmark.json:·gc.alloc.rate.norm avgt 6 5896.004 ± 147.449 B/op
[info] BodyBenchmark.json2 avgt 6 4179.209 ± 673.098 ns/op
[info] BodyBenchmark.json2:·gc.alloc.rate.norm avgt 6 4936.004 ± 73.723 B/op
[info] BodyBenchmark.jsonOption avgt 6 4335.755 ± 150.928 ns/op
[info] BodyBenchmark.jsonOption:·gc.alloc.rate.norm avgt 6 5712.004 ± 0.001 B/op
[info] BodyBenchmark.jsonOption2 avgt 6 4050.681 ± 685.263 ns/op
[info] BodyBenchmark.jsonOption2:·gc.alloc.rate.norm avgt 6 4940.004 ± 36.862 B/op
Fixing Mistakes in Errors
Some of the Finch's core components, including its error types, were designed a couple of years ago. Mistakes were made. We acknowledge it and fixing them now. See #694 for the full discussion.
Here is the summary of what's changed:
- A misleading type for error accumulation (
RequestErrros
) now represents a flat non-empty list of Finch's own errors (previously, a recursiveSeq
of genericThrowable
s). Technically, the new type tells exactly what happens at runtime (which is exactly why need types) - we always flatten errors while collecting, not nest them. - Now product endpoint only accumulates Finch's own errors and fails-fast with the first non-Finch error observed. We think this is a sane default behavior given that's not safe to keep evaluating endpoints while one of them failed with an unknown reasons that could have side-affected an entire application.
Lift Your Stuff
Endpoint.liftX
is a collection of factory methods allowing to build Finch Endpoint
s out of anything. Presumably, these could be useful for wrapping functions returning arbitrary value within an [Endpoint] context.
// The following endpoint will recompute a random integer on each request.
val nextInt: Endpoint[Int] = Endpoint.lift(scala.util.random.nextInt)
Behaviour Changes
- Finch now defines a very basic instance of
Encode.Aux[Exception, ?]
that is polymorphic to the content type. This means, if noEncode[Exception]
is provided for a given content-type, Finch app will still compile using this default instance (see #683).
API Changes
body
andbodyOption
endpoints now returnBuf
instead ofString
. Thusbody.as[User]
should still work as expected, but there is a newstringBody
instance that might be used in place ofbody
where a UTF-8 string of an HTTP body is expected.Encode
instance forEither
is removed (this shouldn't be defined in Finch). See #689.
Bug Fixes
- Extra new line character on streamed responses (see #652, thanks @ilya-murzinov)
- Exceptions encoded in JSON are now properly char-escaped (see #680, thanks @akozhemiakin)
Finch 0.11-M4
- Endpoints testing now easier with polymorphic
withBody
(see #646) - Simplifying the behavior around empty entries (see #642)
- Better error messages in
finch-circe
(see #648)
Polymorphic Input.withBody
It's now possible to utilize content-types (type-level strings) in Input.withBody
such that a proper encoder (i.e., io.finch.Encode
) is picked for the arbitrary value.
Before:
Input.post("/").withBody(Buf.Utf8("text"), Some("text/plain;charset=utf8"))
Input.post("/").withJson(Map("a" -> "b"), Some(Charsets.Utf8))
Now:
// Now:
Input.post("/").withBody[Text.Plain]("text", Some(Charsets.Utf8))
Input.post("/").withBody[Application.Json](Map("a" -> "b"), Some(Charsets.Utf8))
// We get the following for free:
import cats.instances.int._
Input.post("/").withBody[Text.Plain](42)
Empty strings are now treated as Some("")
, not None
For the sake of principle of least astonishment, we decided to simplify Finch's core endpoints such as header
, param
, and body
so they resolve into Some("")
if the given entity is represented as an empty string (None
before). This makes these endpoints both more flexible and less powerful (and actually more lightweight).
Finch 0.11-M3
Endpoint[Response]
is now a first class citizen (see #624)- The behavior for
params
endpoint is changed a bit to make it easier to reason about (see #625) - There is new API for testing endpoints (see #619)
- Cats updated to 0.7.0
Downgrading Endpoints
This release makes it easier to work with raw Finagle Response
s in the context of Finch Endpoint
s such that they can be used instead of Output
s.
This now works (no need to wrap with Output
):
val foo: Endpoint[Response] = get(/) { Response(Status.Ok) }
Finagle Response
s play an essential role in Finch's applications when it's much easier to take a shortcut and deal with HTTP types directly. This is why their support should be vital. For example, until 0.11 is here, dealing with raw Response
s is the only way to serve multiple content types within a single service.
Endpoints Testing
This release makes it easier to test Endpoint
s by proving a lightweight API for Input
and exposing some useful methods on Output
.
Here is an example of how Input.post
and Output.value
might be used to write simple tests similar to how functions are tested:
scala> val sum: Endpoint[Int] = post(int :: int) { (a: Int, b: Int) => Ok(a + b) }
sum: io.finch.Endpoint[Int] = POST /:int/:int
scala> sum(Input.post("/10/20")).value == Some(30)
res1: Boolean = true
scala> sum(Input.get("/foo/bar")).value == None
res2: Boolean = true