Skip to content

Releases: finagle/finch

Finch 0.17

08 Feb 22:05
c33218d
Compare
Choose a tag to compare

This release features Finagle 18.2 and the brand new support to server-driven content negotiation.

Content Negotiation

An idea of supporting a content negotiation initially came up about two years ago, and it's finally become a reality (thanks to @imliar's inspiring contribution!). As of 0.17, it's possible to specify the list of supported content-types per endpoint and have the appropriate encoders be resolved at compile time. An actual encoder is dispatched later, at runtime, when client's Accept header value is known.

Content negotiation is disabled by default (for now). Use Bootstrap.configure to enable it:

import io.finch._

val s = Bootstrap.configure(negotiateContentType = true)
  .serve[Application.Json :+: Text.Plain :+: CNil](Endpoint.lift("Hello, World!"))
  .toService

In the example above, the "Hello, World!" endpoint may be served within both JSON and plain text payloads, depending on the Accept header value sent by a client. If nothing is provided, it defaults to the first content-type in the list (actually, a coproduct). Note: this program won't compile until both encoders are resolved.

Explicit Syntax (verb matching)

We tried to make the transition from implicit to explicit syntax as smooth as possible but it caused some non-trivial shadowing issues in the 0.16 release. Based on that, we made an executive decision to break an API (a little) and stop supplying a default syntax API in the io.finch._ import. Enable verb-matching syntax explicitly by importing io.finch.syntax._.

Finch 0.16.1

10 Jan 19:56
Compare
Choose a tag to compare

This is patch-release for Finch 0.16 that fixes a couple of io.finch.syntax problems:

  • #896: deprecated syntax import (io.finch._)) conflits with exmplicit import (io.finch.syntax._)
  • #895: import io.finch.syntax._ shadows scala namespace

We decided to un-deprecate the implicit syntax coming from io.finch._ as it's the only way to avoid breaking API change. So the previous style should work just fine:

import io.finch._
val foo = get("foo") { Ok("bar") }

To enable the Scala Futures support, simply add an import io.finch.syntax.scalaFutures._.

Finch 0.16

02 Jan 18:15
Compare
Choose a tag to compare

The first release against Cats 1.0 (and the rest of the ecosystem) that also includes six milestone releases:

The following dependencies were bumped:

  • Cats to 1.0.1
  • Circe to 0.9
  • Finagle to 17.12

Circe Error Accumulation

In addition to the six milestone releases, the final release includes a new shiny feature that allows accumulating errors returned from a Circe decoder (i.e., report multiple parse errors at once). See #884 (thanks @imliar) for more details. To enable error accumulation, replace io.finch.circe._ import with:

import io.finch.circe.accumulating._

Finch 0.16-M6

22 Dec 19:09
78720cd
Compare
Choose a tag to compare

(Hopefully) the last milestone release for 0.16 (which will be released in against Cats 1.0 once it's available):

  • Cats 1.0-RC2 and Circe 0.9-M3
  • Finagle/Util 17.12

Multiple file-uploads & attributes

This milestone release accompanied by a new set of endpoints to read multiple file-uploads (thanks @jguitana, see #811) and multipart attributes (see #872).

import io.finch._

scala> val foo = multipartAttributesNel("foo") 
foo: io.finch.Endpoint[cats.data.NonEmptyList[String]] = attributes(foo)

scala> val bar = multipartFileUploadsNel("bar")
bar: io.finch.Endpoint[cats.data.NonEmptyList[com.twitter.finagle.http.exp.Multipart.FileUpload]] = bar

Deprecating .as[Foo] syntax

We're deprecating an old style type-converters available via the .as[A] syntax extension (thanks @imliar, see #875). Instead, we're offering a new API that accepts a target type at the moment endpoint is created. This is supported by param(s), header, and multipartAttribute(s) endpoints.

scala> val p = param("p").as[Int]
<console>:38: warning: method as in class StringEndpointOps is deprecated (since 0.16)
       val p = param("p").as[Int]
                          ^
p: io.finch.Endpoint[Int] = param(p)

scala> val q = param[Int]("q")
q: io.finch.Endpoint[Int] = param(q)

The new API isn't just easier to write/read but also more efficient as it allows to skip an interim endpoint creation (needed to accommodate the as operation).

Note:

  • param("foo") (no type-parameter) is still supported and defaults to param[String]("foo").
  • Endpoint[L <: HList] -> Endpoint[Foo] conversion is still supported via the standart means of as[Foo] operation.

Deprecating implicit Sinatra-like syntax

After 0.16, Finch's Sinatra-like syntax should be explicitly imported (see #876).

scala> import io.finch._
import io.finch._

scala> val p = get(/) { Ok("foo") }
val p = get(/) { Ok("foo") }
<console>:14: warning: method get in trait DeprecatedEndpointMappers is deprecated (since 0.16): Enable syntax explicitly: import io.finch.syntax._
       val p = get(/) { Ok("foo") }
               ^
p: io.finch.Endpoint[String] = GET /

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

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

Finch 0.16-M5

21 Nov 23:02
Compare
Choose a tag to compare

Another milestone release for Finagle 17.11.

Improved Multipart Support

Multipart attributes are now decoupled from param(s) endpoints as they were instigating a multipart decoding on each optional param (see #860 for discussion and #865 for a fix).

scala> val foo = multipartAttribute("foo")
foo: io.finch.Endpoint[String] = foo

scala> val bar = multipartFileUpload("bar")
bar: io.finch.Endpoint[com.twitter.finagle.http.exp.Multipart.FileUpload] = bar

Circe's Printing Size Predictor

Circe's size predictor allows saving a couple of copy-and-grow iterations while printing into a binary output. It uses a very primitive form of a feedback controller to grow/shrink a potentially expected size based on the history of allocations (see circe/circe#739 for more details).

We've seen quite a great improvements in our ToServiceBenchamrk (around ~2x of throughput improvement) yet we were unable to reproduce the numbers with the wrk (i.e., end-to-end test). Based on that, this option should be considered experimental (use with caution and run your own tests).

Size prediction could be enabled with io.finch.circe.predictSize._ import (see #868).

Finch 0.16-M4

10 Nov 19:11
2b1097c
Compare
Choose a tag to compare

A new milestone release that brings updated dependencies

Better Intellij Support

We've continued making progress towards better Intellij integration. It's been a persistent problem when IDEA marks compilable endpoints "red" (as it was reported in #631 about a year ago). The main issue is a magnet-pattern machinery that Finch relies upon for its syntax. We've filed a Scala plugin issue with Intellij to make sure they look into that as well yet it wasn't getting lots of JetBrains attention by far. With 31 votes (this is beyond awesome!) it's among the most highest-voted tickets Scala plugin has ever had.

We also tried to make some progress on our own and simplify the constructions we've been using. @imliar has done a decent job introducing ToTwitterFuture (see #834) and improving the overall highlighting experience later (see #858).

While we're not completely there yet, but we're confident in its current state Finch users should have much better experience with Intellij.

Finch 0.16-M3

08 Oct 20:23
Compare
Choose a tag to compare

Another (hopefully the last one) milestone release before 0.16. This time with Scala Futures syntax support.

Scala Syntax Support

In #834, @imliar suggested a new type-class, ToTwitterFuture to abstract over a concrete IO primitive powering endpoint's asynchrony. Right now there are only Scala Futures instances available for ToTwitterFuture, but one can imagine this approach to be scaled to any Futures implementations available for Scala.

Importing io.finch.syntax.scala._ allows to scala.concurrent.Future from within endpoints (assuming ExecutionContext is available in the scope).

import io.finch._, io.finch.syntax.scala._
import scala.concurrent.{Future, ExecutionContext.Implicits.global}

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

scala> e(Input.get("/foo")).awaitValueUnsafe()
res0: Option[String] = Some(bar)

Deprecations

Old-style path matchers such as int, string, boolean, etc are being deprecated in favor of more explicit path[Int], path[String], path[Boolean] and so on. We believe new API will make path matchers more discoverable and easier to reason about. See #844 for more context.

Dependencies

  • Finagle has been bumped to 7.1 (see #842)

Finch 0.16-M2

28 Aug 15:50
Compare
Choose a tag to compare

This milestone release brings new performance improvements (reducing the gap between Finagle and Finch to only 5%) as well as new finch-iteratee module enabling generic request/response streaming.

Iteratee Streaming

Integration with iteratee library was one of the most wanted features in Finch. Travis Brown (@travisbrown) started experimenting with it almost a year ago (see #557) yet that work was only finished now (see #812), by a tireless effort of Sergey Kolbasov (@imliar).

Essentially it's now possible to

  • receive chunked payloads in a form of io.iteratee.Enumerator[Future, A] (when it's known how to io.finch.iteratee.Enumerate[CT, A])
  • send chunked payloads in a form of io.iteratee.Enumerator[Future, B] (when it's known how to io.finch.Encode.Aux[CT, B])

For example, importing both

import io.finch.circe._, io.finch.iteratee._

enables new-line-delimited JSON streaming in Finch.

import io.finch._
import io.finch.circe._
import io.finch.iteratee._
import io.circe.generic.auto._
import io.iteratee._
import com.twitter.util.Future

case class Foo(bar: Int)

// Streaming request payload.
val sum: Endpoint[Int] =
  post("foos" :: enumeratorJsonBody[Foo]) { (foos: Enumerator[Future, Foo]) =>
    val ints: Enumerator[Future, Int] = foos.through(map[Foo, Int](_.bar))
    val sum: Future[Int] = ints.into(fold[Int, Int](0)(_ + _))

    sum.map(Ok) // a future will be completed when whole stream is folded
}

// Streaming response payload.
val foos: Endpoint[Enumerator[Future, Foo]] = get("foos") {
  Ok(enumList(List(Foo(1), Foo(2)))
}

See more details in the docs.

Performance Improvements

I learned this simple technics allowing to reduce allocations around closures from Flavio Brasil (@fwbrasil). Applying them to Finch yield quite significant performance gain (see #807) reducing the gap between Finch and Finagle to just 5% (was 10-12% before).

Here are some numbers for running time (less is better):

NEW:

MapBenchmark.map                             avgt   10   633.837 ±  14.613   ns/op
MapBenchmark.mapAsync                        avgt   10   624.249 ±  22.426   ns/op
MapBenchmark.mapOutput                       avgt   10   734.677 ±  30.215   ns/op
MapBenchmark.mapOutputAsync                  avgt   10   737.170 ±  29.808   ns/op
ProductBenchmark.bothMatched                 avgt   10  1175.716 ±  44.236   ns/op
ProductBenchmark.leftMatched                 avgt   10    26.510 ±   2.335   ns/op
ProductBenchmark.rightMatched                avgt   10     5.081 ±   0.112   ns/op

OLD:

MapBenchmark.map                             avgt   10   624.174 ±  28.621   ns/op
MapBenchmark.mapAsync                        avgt   10   647.369 ±  30.775   ns/op
MapBenchmark.mapOutput                       avgt   10  1221.053 ±  39.319   ns/op
MapBenchmark.mapOutputAsync                  avgt   10  1202.541 ±  44.432   ns/op
ProductBenchmark.bothMatched                 avgt   10  1224.278 ±  49.114   ns/op
ProductBenchmark.leftMatched                 avgt   10    29.856 ±   0.709   ns/op
ProductBenchmark.rightMatched                avgt   10     5.209 ±   0.112   ns/op

In a nutshell:

  • map* operations are now up to 40% faster (400 bytes fewer allocations on each call)
  • :: is now up to 5% faster (120 bytes fewer allocations on each call)

Dependencies

Finch 0.16-M1

16 Jun 01:51
Compare
Choose a tag to compare

Meet Bootstrap

The intention of this early 0.16 release (hence M1) is to allow people to experiment with the new Bootstrap abstraction that supposed to allow serving multiple content-types out of a single Service (i.e., coproduct endpoint). This was one of the most-wanted features since the type-level content type was shipped, over a year ago.

See the following tickets for more context/history on the subject:

  • Content-Type as a Type #541
  • Supporting multiple Content-Types in a single endpoint/service #575
  • ServiceBulder (abandoned) #583
  • Bootstrap (merged) #794

In a nutshell, the new Bootstrap abstraction is pretty much an evolution of ServiceBuilder (see #583) that also has a notion of a configuration params for derived services (i.e., Service[Request, Response]). At this point, we only support two options: includeServerHeader (enabled by default) and includeDateHeader (enabled by default), but one can easily imagine this machinery to be scaled to support a wide spectrum of use cases:

  • Reporting stats per endpoint into a given stats receiver (see #781)
  • Supporting HTTP 405 (see #784)

The quick start example looks pretty straightforward:

import io.finch._
import io.finch.circe._
import io.finch.generic._
import com.twitter.finagle.Http

scala> val json = get("json") { Ok(Map("foo" -> "bar")) }
json: io.finch.Endpoint[scala.collection.immutable.Map[String,String]] = GET /json

scala> val text = get("text") { Ok("Hello, World!") }
text: io.finch.Endpoint[String] = GET /text

scala> val s = Bootstrap.configure(includeServerHeader = false)
  .serve[Application.Json](json)
  .serve[Text.Plain](text)
  .toService
s: com.twitter.finagle.Service[com.twitter.finagle.http.Request,com.twitter.finagle.http.Response] = <function1>

scala> Http.server.serve(":8081", s)
Jun 15, 2017 5:35:01 PM com.twitter.finagle.Init$$anonfun$5 apply$mcV$sp
INFO: Finagle version 6.45.0 (rev=fadc80cdd804f2885ebc213964542d5568a4f485) built at 20170609-103047
res1: com.twitter.finagle.ListeningServer = com.twitter.finagle.server.ListeningStackServer$$anon$1@1da24919

And then using HTTPie:

$ http :8081/text
HTTP/1.1 200 OK
Content-Type: text/plain
Date: Fri, 16 Jun 2017 00:35:57 GMT

Hello, World!

$ http :8081/json
HTTP/1.1 200 OK
Content-Type: application/json
Date: Fri, 16 Jun 2017 00:36:01 GMT

{
    "foo": "bar"
}

If you need more examples, here is how Finch TechEmpower benchmark is evolved after this release: TechEmpower/FrameworkBenchmarks#2869

Other changes

  • Slightly faster parsing of integer paths (#800)
  • Slightly faster date header (#792)

Finch 0.15.1

14 Jun 18:05
Compare
Choose a tag to compare

A patch release that contains two fixes/additions:

  • #788: Upgrading Catbird dependency and Scala versions (thanks @travisbrown )
  • #796: Fixing optional bodies so they return None on empty payloads (thanks @JoeEnnever)