From added788ff152664b5bbdb0d997b55f77a25e566 Mon Sep 17 00:00:00 2001 From: Larry Hosken Date: Wed, 19 Sep 2012 14:38:05 -0700 Subject: [PATCH 01/12] corrections, diagrams, and clarifications for Finagle Add instructions for a console that can import finagle things. I needed it, maybe other folks will also. Show how to create+resolve Futures+Promises in the REPL. Change flatMap example to clarify which params are Future-thingies vs thingies. Instead of throwing a "map" into a flatMap example, bring it out into its own example. My puny brain took a while to figure out the combined example. Show examples for using the concurrent combinators. I really wanted an example for select; its API wasn't what I expected (though it makes sense in hindsight). But it looked funny that just one of these had an example so I added examples for the others too. The simple HTTP client and server examples in the Service section had drifted far enough away from the current API such that I had a tough time figuring out the API. So updated them, also filling in some of the setup which the examples assumed. Throw in motivating examples for some of constructs. Steal some diagrams from JDOwens Many misc clarifications --- web/finagle.textile | 413 ++++++++++++++++++++++++++++------ web/finagle_client_server.png | Bin 0 -> 10457 bytes web/finagle_filter.png | Bin 0 -> 10214 bytes 3 files changed, 338 insertions(+), 75 deletions(-) create mode 100644 web/finagle_client_server.png create mode 100644 web/finagle_filter.png diff --git a/web/finagle.textile b/web/finagle.textile index 2a7fa0e6..76bad581 100644 --- a/web/finagle.textile +++ b/web/finagle.textile @@ -7,61 +7,200 @@ layout: post "Finagle":https://github.com/twitter/finagle is Twitter's RPC system. "This":http://engineering.twitter.com/2011/08/finagle-protocol-agnostic-rpc-system.html blog post explains its motivations and core design tenets, the "finagle README":https://github.com/twitter/finagle/blob/master/README.md contains more detailed documentation. Finagle aims to make it easy to build robust clients and servers. -h2. Futures +* "REPL":#repl +* "Futures":#Future: "Sequential composition":#futsequential, "Concurrent composition":#futconcurrent, "Composition Example":#combined_combinator_example +* "Service":#Service +* "Client Example":#client +* "Server Example":#server +* "Filters":#Filter +* "Builders":#Builder -Finagle uses com.twitter.util.Future[1] to express delayed operations. Futures are highly expressive and composable, allowing for the succinct expression of concurrent and sequential operations with great clarity. Futures are a handle for a value not yet available, with methods to register callbacks to be invoked when the value becomes available. They invert the "traditional" model of asynchronous computing which typically expose APIs similar to this: +h2(#repl). Finagle-Friendly REPL + +We're about to discuss some code that's not part of standard Scala. If you like to use the REPL to learn, you might wonder how to get a Scala REPL that knows about Twitter's Finagle and things it depends on. + +You want the Finagle source code. + +If you have the Finagle source code in a directory named finagle, you can get a console via
-Callback cb = new Callback() {
-  void onComplete(R result) { … }
-  void onFailure(Throwable error) { … }
-}
+$ cd finagle
+$ ./sbt "project finagle-core" console
+ ...build output...
+scala>
+
+ +h2(#Future). Futures + +Finagle uses com.twitter.util.Future[1] to express delayed operations. A Future is a handle for a value not yet available. Finagle uses Futures as return values for its asynchronous APIs. A synchronous API returns a result immediately. An asynchronous API does something that might take a while. For example, an HTTP request to some service on the internet might not return a value for half a second. You don't want your program's execution to block for half a second waiting for that request to finish. "Slow" APIs can return a Future right away and then "fill in" its value when it resolves. -dispatch(req, cb); +
+val myFuture = MySlowService(request) // returns right away
+   ...do other things...
+val serviceResult = myFuture.get() // blocks until service "fills in" myFuture
 
-Here, the Callback.onComplete is invoked when the result of the dispatch operation is available, and Callback.onFailure if the operation fails. With futures, we instead invert this control flow: +In practice, you won't write code that sends a request and then calls myFuture.get a few statements later. A Future has methods to register callbacks to invoke when the value becomes available. + +If you've used other asynchronous APIs, you perhaps cringed when you saw the word "callbacks" just now. You might associate them with illegible code flows, functions hiding far from where they're invoked. But Futures can take advantage of Scala's first-class functions to present a more-readable code flow. You can define a simpler handler function in the place where it's invoked. + +For example to, write code that dispatches a request and then "handles" the response, you can keep the code together:
-val future = dispatch(req)
-future onSuccess { value => … }
-future onFailure { error => … }
+val future = dispatch(req) // returns immediately, but future is "empty"
+future onSuccess { // when the future gets "filled"...
+  ...do awesome things with future.get()...
+}
 
-Futures themselves have combinators similar to those we've encountered before in the various collections APIs. Combinators work by exploiting a uniform API, wrapping some underlying Future with new behavior without modifying that underlying Future. +You can play with Futures in the REPL. This is a bad way to learn how you'll use them in real code, but can help with understanding the API. When you use the REPL, Promise is a handy class. It's a concrete subclass of the abstract Future class. You can use it to create a Future that has no value yet. + +
+scala> import com.twitter.util.{Future,Promise}
+import com.twitter.util.{Future, Promise}
+
+scala> val f6 = Future.value(6) // create already-resolved future
+f6: com.twitter.util.Future[Int] = com.twitter.util.ConstFuture@c63a8af
+
+scala> f6.get()
+res0: Int = 6
+
+scala> val fex = Future.exception(new Exception) // create resolved sad future
+fex: com.twitter.util.Future[Nothing] = com.twitter.util.ConstFuture@38ddab20
+
+scala> fex.get()
+java.lang.Exception
+  ... stack trace ...
+
+scala> val pr7 = new Promise[Int] // create unresolved future
+pr7: com.twitter.util.Promise[Int] = Promise@1994943491(...)
+
+scala> pr7.get()
+  ...console hangs, waiting for future to resolve...
+Ctrl-C
+Execution interrupted by signal.
+
+scala> pr7.setValue(7)
+
+scala> pr7.get()
+res1: Int = 7
+
+scala>
+
+ +  h3. Sequential composition +Futures have combinators similar to those in the collections APIs (e.g., map, flatMap). A collection-combinator, you recall, lets you express things like "I have a List of integers and a square function: map that to the List of the squares of my integers." This is neat; you can put together the combinator-function with another function to effectively define a new function. A Future-combinator lets you express things like "I have a Future hypothetical-integer and a square function: map that to the Future square of my hypothetical-integer." + +If you're defining an asynchronous API, a request value comes in and your API gives back a response wrapped in a Future. Thus, these combinators that turn inputs and functions into Futures are darned useful: they help you define your asynchronous API in terms of other asychronous APIs. + The most important Future combinator is flatMap[2]:
def Future[A].flatMap[B](f: A => Future[B]): Future[B]
-flatMap sequences two features. The method signature tells the story: given the succesful value of the future f must provide the next Future. The result of this operation is another Future that is complete only when both of these futures have completed. If either Future fails, the given Future will also fail. This implicit interleaving of errors allow us to handle errors only in those places where they are semantically significant. flatMap is the standard name for the combinator with these semantics. Scala also has syntactic shorthand to invoke it: the for comprehension. +flatMap sequences two futures. That is, it defines a Future as the result of applying an asynchronous function to another Future. The method signature tells the story: given the succesful value of a future, the function f provides the next Future. The result of this operation is another Future that is complete only when both of these futures have completed. If either Future fails, the given Future will also fail. This implicit interleaving of errors allow us to handle errors only in those places where they are semantically significant. flatMap is the standard name for the combinator with these semantics. -As an example, let's assume we have methods authenticate: Request -> User, and rateLimit: User -> Boolean, then the following code: +If you have a Future and you want apply an asynchronous API to its value, use flatMap. For example, suppose you have a Future[User] and need a Future[Boolean] indicating whether the enclosed User has been banned. There is an isBanned API to determine whether a User has been banned, but it is asynchronous. You can use flatMap:
-val f = authenticate(request) flatMap { u =>
-  rateLimit(u) map { r => (u, r)
-}
+scala> import com.twitter.util.{Future,Promise}
+import com.twitter.util.{Future, Promise}
+
+scala> class User(n: String) { val name = n }
+defined class User
+
+scala> def isBanned(u: User) = { Future.value(false) }
+isBanned: (u: User)com.twitter.util.Future[Boolean]
+
+scala> val pru = new Promise[User]
+pru: com.twitter.util.Promise[User] = Promise@897588993(...)
+
+scala> val futBan = pru flatMap isBanned // apply isBanned to future
+futBan: com.twitter.util.Future[Boolean] = Promise@1733189548(...)
+
+scala> futBan.get()
+  ...REPL hangs, futBan not resolved yet...
+Ctrl-C
+Execution interrupted by signal.
+
+scala> pru.setValue(new User("prudence"))
+
+scala> futBan.get()
+res45: Boolean = false
+
+scala>
 
-With the help of for-comprehensions, we can write the above as: +Similarly, to apply a synchronous function to a Future, use map. For example, suppose you have a Future[RawCredentials] and need a Future[Credentials]. You have a synchronous normalize function that converts from RawCredentials to Credentials. You can use map:
-val f = for {
-  u <- authenticate(request)
-  r <- rateLimit(u)
-} yield (u, r)
+scala> class RawCredentials(u: String, pw: String) {
+     |   val username = u
+     |   val password = pw
+     | }
+defined class RawCredentials
+
+scala> class Credentials(u: String, pw: String) {
+     |   val username = u
+     |   val password = pw
+     | }
+defined class Credentials
+
+scala> def normalize(raw: RawCredentials) = {
+     |   new Credentials(raw.username.toLowerCase(), raw.password)
+     | }
+normalize: (raw: RawCredentials)Credentials
+
+scala> val praw = new Promise[RawCredentials]
+praw: com.twitter.util.Promise[RawCredentials] = Promise@1341283926(...)
+
+scala> val fcred = praw map normalize // apply normalize to future
+fcred: com.twitter.util.Future[Credentials] = Promise@1309582018(...)
+
+scala> fcred.get()
+   ...REPL hangs, fcred doesn't have a value yet...
+Ctrl-C
+Execution interrupted by signal.
+
+scala> praw.setValue(new RawCredentials("Florence", "nightingale"))
+
+scala> fcred.get().username
+res48: String = florence
+
+scala>
+
+ +Scala has syntactic shorthand to invoke flatMap: the for comprehension. Suppose you want to authenticate a login request via an asynchronous API and then check to see whether the user is banned via another asynchronous API. With the help of for-comprehensions, we can write this as: + +
+scala> def authenticate(req: LoginRequest) = {
+     |   // TODO: we should check the password
+     |   Future.value(new User(req.username))
+     | }
+authenticate: (req: LoginRequest)com.twitter.util.Future[User]
+
+scala> val f = for {
+     |  u <- authenticate(request)
+     |  b <- isBanned(u)
+     | } yield (u, b)
+f: com.twitter.util.Future[(User, Boolean)] = Promise@35785606(...)
+
+scala>
 
-produces a future f: Future[(User, Boolean)] that provides both the user object and and a boolean indicating whether that user has been rate limited. Note how sequential composition is required here: rateLimit takes as an argument the output of authenticate +produces a future f: Future[(User, Boolean)] with the user object and and a Boolean indicating whether that user has been rate limited. Note how sequential composition is required here: rateLimit takes as an argument the output of authenticate. + +  h3. Concurrent composition -There are also a number of concurrent combinators. Generally these convert a sequence of Future into a Future of sequence, in slightly different ways: +You might want to fetch data from more than one service at once. For example, if you're writing a web service that shows content and ads, it might fetch content from one service and ads from another. But how do you tell your code to wait for both replies? This could get tricky if you had to write it yourself, but instead you can use concurrent combinators. + +Future provides some concurrent combinators. Generally, these convert a sequence of Future into a Future of sequence in slightly different ways:
 object Future {
@@ -72,52 +211,160 @@ object Future {
 }
 
-collect is the most straightforward one: given a set of Futures of the same type, we are given a Future of a sequence of values of that type. This future is complete when all of the underlying futures have completed, or when any of them have failed. +collect takes a set of Futures of the same type, and yields a Future of a sequence of values of that type. This future is complete when all of the underlying futures have completed, or when any of them have failed. -join takes a sequence of Futures whose types may be mixed, yielding a Future[Unit] that is completely when all of the underlying futures are (or fails if any of them do). This is useful for indicating the completion of a set of heterogeneous operations. +
+scala> val f2 = Future.value(2)
+f2: com.twitter.util.Future[Int] = com.twitter.util.ConstFuture@13ecdec0
 
-select returns a Future that is complete when the first of the given Futures complete, together with the remaining uncompleted futures.
+scala> val f3 = Future.value(3)
+f3: com.twitter.util.Future[Int] = com.twitter.util.ConstFuture@263bb672
 
-In combination, this allows for powerful and concise expression of operations typical of network services. This hypothetical code performs rate limiting (in order to maintain a local rate limit cache) concurrently with dispatching a request on behalf of the user to the backend:
+scala> val f23 = Future.collect(Seq(f2, f3))
+f23: com.twitter.util.Future[Seq[Int]] = Promise@635209178(...)
+
+scala> f23.get() sum
+res82: Int = 5
+
+
+ +join takes a sequence of Futures whose types may be mixed, yielding a Future[Unit] that is complete when all of the underlying futures are (or fails if any of them do). This is useful for indicating the completion of a set of heterogeneous operations. It might be a good solution for the content-and-ads example.
-def serve(request: Request): Future[Response] = {
-  val userLimit: Future[(User, Boolean)] =
-    for {
-      user    <- auth(request)
-      limited <- isLimit(user)
-    } yield (user, limited)
-  
-  val done = 
-    dispatch(request) join userLimit
-  
-  done flatMap { case (rep, (usr, lim)) =>
-    if (lim) {
-      updateLocalRateLimitCache(usr)
-      Future.exception(new Exception("rate limited"))
-    } else {
-      Future.value(rep)
-    }
+scala> val ready = Future.join(Seq(f2, f3))
+ready: com.twitter.util.Future[Unit] = Promise@699347471(...)
+
+scala> ready.get() // doesn't ret value, but I know my futures are done
+
+scala>
+
+ +select returns a Future that is complete when the first of the given Futures complete. It returns that Future together with a Seq containing the remaining uncompleted Futures. + +
+scala> val pr7 = new Promise[Int] // unresolved future
+pr7: com.twitter.util.Promise[Int] = Promise@1608532943(...)
+
+scala> val sel = Future.select(Seq(f2, pr7)) // select from 2 futs, one resolved
+sel: com.twitter.util.Future[...] = Promise@1003382737(...)
+
+scala> val(complete, stragglers) = sel.get()
+complete: com.twitter.util.Try[Int] = Return(2)
+stragglers: Seq[...] = List(...)
+
+scala> complete.get()
+res110: Int = 2
+
+scala> stragglers(0).get() // our list of not-yet-finished futures has one item
+  ...get() hangs the REPL because this straggling future is not finished...
+Ctrl-C
+Execution interrupted by signal.
+
+scala> pr7.setValue(7)
+
+scala> stragglers(0).get()
+res113: Int = 7
+
+scala>
+
+ +  + +h3. Composition Example + +These combinators express operations typical of network services. This hypothetical code performs rate limiting (in order to maintain a local rate limit cache) concurrently with dispatching a request on behalf of the user to the backend: + +
+// Find out if user is rate-limited. This can be slow; we have to ask
+// the remote server that keeps track of who is rate-limited.
+def isRateLimited(u: User): Future[Boolean] = {
+  ...
+}
+
+// Notice how you can swap this implementation out now with something that might
+// implement a different, more restrictive policy.
+
+// Check the cache to find out if user is rate-limited. We find out right
+// away by checking local cache. But we return a Future anyhow in case we
+// need to use a slower system later.
+def isLimitedByCache(u: User): Future[Boolean] =  Future.value(limitCache(u))
+
+// Update the cache
+def setIsLimitedInCache(user: User, v: Boolean) { limitCache(user) = v }
+
+// Get a timeline of tweets... unless the user is rate-limited (then throw
+// an exception instead)
+def getTimeline(cred: Credentials): Future[Timeline] =
+  isLimitedByCache(cred.user) flatMap {
+    case true => Future.exception(new Exception("rate limited"))
+    case false =>
+      val timeline = auth(cred) flatMap(getTimeline)
+      val limited = isRateLimited(cred.user) onSuccess(
+                                       setIsLimitedInCache(cred.user, _))
+
+      timeline join limited flatMap {
+        case (_, true) => Future.exception(new Exception("rate limited"))
+        case (timeline, _) => Future.value(timeline)
+      }
   }
 }
 
-This hypothetical example combines both sequential and concurrent composition. Also note how there is no explicit error handling other than converting a rate limiting reply to an exception. If any future fails here, it is automatically propagated to the returned Future. +This hypothetical example combines sequential and concurrent composition. Also note how there is no explicit error handling other than converting a rate limiting reply to an exception. If any future fails here, it is automatically propagated to the returned Future. -h2. Service +h2(#Service). Service -A Service is a function Req => Future[Rep] for some request and reply types. Service is used by both clients and servers: servers implement Service and clients use builders to create one used for querying. +A Finagle Service represents a service that handles remote proceduce calls, taking requests and giving back replies. It represents the service's "logic," not the network "plumbing" (but works well with other things that configure that plumbing). A Service is a function Req => Future[Rep] for some request and reply types.
abstract class Service[-Req, +Rep] extends (Req => Future[Rep])
-A simple HTTP client might do: +!>finagle_client_server.png(Client and Server)! + +We define both clients and servers in terms of Services. + +A Finagle client "imports" a Service from the network. Conceptually, a Finagle client has two parts: + + + +Similarly, a Finagle server "exports" a Service to the network. A server has two parts: + + +This separates the Service "logic" from the configuration of how data flows across the network. + +!>finagle_filter.png(Filter and Server)! + +We also talk about Finagle "filters." A filter sits between services, modifying data that flows through it. Filters compose nicely with services. For example, if you have a rate-limiter filter and a tweet-serving service, you can put them together to make a rate-limited tweet-serving service. + +h2(#client). Client + +A Finagle client is defined in terms of a Service and some configuration about how to send data over the network. A simple HTTP client might like:
-service: Service[HttpRequest, HttpResponse]
+import org.jboss.netty.handler.codec.http.{DefaultHttpRequest, HttpRequest, HttpResponse, HttpVersion, HttpMethod}
+import com.twitter.finagle.Service
+import com.twitter.finagle.builder.ClientBuilder
+import com.twitter.finagle.http.Http
+
+// Don't worry, we discuss this magic "ClientBuilder" later
+val client: Service[HttpRequest, HttpResponse] = ClientBuilder()
+  .codec(Http())
+  .hosts("twitter.com:80")
+  .hostConnectionLimit(1)
+  .build()
+
+val req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/")
 
-val f = service(HttpRequest("/", HTTP_1_1))
+val f = client(req) // Client, send the request
+
+// Handle the response:
 f onSuccess { res =>
   println("got response", res)
 } onFailure { exc =>
@@ -125,28 +372,45 @@ f onSuccess { res =>
 }
 
-Servers implement Service: +h2(#server). Server + +A server is defined in terms of a Service and some configuration about how to "listen" for requests coming in over the network. A simple HTTP server might look like:
-class MyServer 
-  extends Service[HttpRequest, HttpResponse]
-{
+import com.twitter.finagle.Service
+import com.twitter.finagle.http.Http
+import com.twitter.util.Future
+import org.jboss.netty.handler.codec.http.{DefaultHttpResponse, HttpVersion, HttpResponseStatus, HttpRequest, HttpResponse}
+import java.net.{SocketAddress, InetSocketAddress}
+import com.twitter.finagle.builder.{Server, ServerBuilder}
+import com.twitter.finagle.builder.ServerBuilder
+
+// Define our service: OK response for root, 404 for other paths
+val rootService = new Service[HttpRequest, HttpResponse] {
   def apply(request: HttpRequest) = {
-    request.path match {
-      case "/" => 
-        Future.value(HttpResponse("root"))
-      case _ => 
-        Future.value(HttpResponse("default"))
+    val r = request.getUri match {
+      case "/" => new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK)
+      case _ => new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND)
     }
+    Future.value(r)
   }
 }
+
+// Serve our service on a port
+val address: SocketAddress = new InetSocketAddress(10000)
+val server: Server = ServerBuilder()
+  .codec(Http())
+  .bindTo(address)
+  .name("HttpServer")
+  .build(rootService)
 
-Combining them is easy. A simple proxy might look like this: +h2(#Filter). Filters + +You can combine servers and clients. A simple proxy might look like this:
-class MyServer(client: Service[..])
-  extends Service[HttpRequest, HttpResponse]
+class MyService(client: Service[..]) extends Service[HttpRequest, HttpResponse]
 {
   def apply(request: HttpRequest) = {
     client(rewriteReq(request)) map { res =>
@@ -158,9 +422,7 @@ class MyServer(client: Service[..])
 
 where rewriteReq and rewriteRes can provide protocol translation, for example.
 
-h2. Filters
-
-Filters are service transformers. They are useful both for providing functionality that's service generic as well as factoring a given service into distinct phases.
+Filters transform services. They can provide service generic functionality. For example, you might have several services that should support rate limiting; you can write one rate-limiting filter and apply it to all your services. Filters are also good for decomposing a service into distinct phases.
 
 
 abstract class Filter[-ReqIn, +RepOut, +ReqOut, -RepIn]
@@ -170,7 +432,7 @@ abstract class Filter[-ReqIn, +RepOut, +ReqOut, -RepIn]
 Its type is better viewed diagramatically:
 
 
-    ((ReqIn, Service[ReqOut, RepIn]) 
+    ((ReqIn, Service[ReqOut, RepIn])
          => Future[RepOut])
 
 
@@ -199,10 +461,9 @@ This example shows how you might provide authentication (via an authentication s
 
 
 class RequireAuthentication(authService: AuthService)
-  extends Filter[HttpReq, HttpRep, AuthHttpReq, HttpRep]
-{
+  extends Filter[HttpReq, HttpRep, AuthHttpReq, HttpRep] {
   def apply(
-    req: HttpReq, 
+    req: HttpReq,
     service: Service[AuthHttpReq, HttpRep]
   ) = {
     authService.auth(req) flatMap {
@@ -216,6 +477,8 @@ class RequireAuthentication(authService: AuthService)
 }
 
+Using a filter this way yields some nice advantages. It helps you keep your "auth logic" in one place. Having a separate type for authorized requests makes it easier to reason about your program's security. + Filters compose together with andThen. Providing a Service as an argument to andThen creates a (filtered) Service (types provided for illustration).
@@ -230,9 +493,9 @@ val authenticatedTimedOutService: Service[HttpReq, HttpRep] =
   authenticateAndTimedOut andThen serviceRequiringAuth
 
-h2. Builders +h2(#Builder). Builders -Finally, builders put it all together. A ClientBuilder produces a Service instance given a set of parameters, and a ServerBuilder takes a Service instance and dispatches incoming requests on it. In order to determine the type of Service, we must provide a Codec. Codecs provide the underlying protocol implementation (eg. HTTP, thrift, memcached). Both builders have many parameters, and require a few. +Builders put it all together. A ClientBuilder produces a Service instance given a set of parameters, and a ServerBuilder takes a Service instance and dispatches incoming requests on it. In order to determine the type of Service, we must provide a Codec. Codecs provide the underlying protocol implementation (eg. HTTP, thrift, memcached). Both builders have many parameters, and require a few. Here's an example ClientBuilder invocation (types provided for illustration): @@ -259,9 +522,9 @@ ServerBuilder() .bindTo(new InetSocketAddress(serverPort)) .build(myService)
- + This will serve, on port serverPort an HTTP server which dispatches requests to myService. Each connection is allowed to stay alive for up to 5 minutes, and we require a request to be sent within 2 minutes. The required ServerBuilder options are: name, bindTo and codec. -fn1. distinct from java.util.concurrent.Future +fn1. Careful, there are other "Future" classes out there. Don't confuse twitter.com.util.Future with scala.actor.Future or java.util.concurrent.Future! -fn2. this is equivalent to a monadic bind +fn2. If you study type systems and/or category theory, you'll be glad to learn that flatMap is equivalent to a monadic bind. diff --git a/web/finagle_client_server.png b/web/finagle_client_server.png new file mode 100644 index 0000000000000000000000000000000000000000..e5d39c6585ee9ce2ce53adb5368fdb96724489e0 GIT binary patch literal 10457 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 YeV|kFp~7P3M8J>gJx#?jd6Urp0S;*y#{d8T literal 0 HcmV?d00001 diff --git a/web/finagle_filter.png b/web/finagle_filter.png new file mode 100644 index 0000000000000000000000000000000000000000..4be4667b5ae2d96dc22b35ead77a279a736bf838 GIT binary patch literal 10214 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 literal 0 HcmV?d00001 From 551e57e7cf45bc5c581f77ceed1da5f2f677c524 Mon Sep 17 00:00:00 2001 From: Larry Hosken Date: Wed, 19 Sep 2012 16:36:52 -0700 Subject: [PATCH 02/12] incorporate moar JDOwens feedback --- web/collections.textile | 2 +- web/finagle.textile | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/web/collections.textile b/web/collections.textile index 59a9e5c8..96a97827 100644 --- a/web/collections.textile +++ b/web/collections.textile @@ -171,7 +171,7 @@ val result = res1 match { *See Also* Effective Scala has opinions about Options. -h1. Functional Combinators +h1(#combinators). Functional Combinators Combinators are so-called because they are meant to be combined. The output of one function is often suitable as the input for another. diff --git a/web/finagle.textile b/web/finagle.textile index 76bad581..d6fd54db 100644 --- a/web/finagle.textile +++ b/web/finagle.textile @@ -49,7 +49,7 @@ For example to, write code that dispatches a request and then "handles" the resp
 val future = dispatch(req) // returns immediately, but future is "empty"
 future onSuccess { // when the future gets "filled"...
-  ...do awesome things with future.get()...
+  case reply => println(reply) // ...use the value it contains
 }
 
@@ -102,7 +102,7 @@ The most important Future combinator is flatMap[2]: def Future[A].flatMap[B](f: A => Future[B]): Future[B] -flatMap sequences two futures. That is, it defines a Future as the result of applying an asynchronous function to another Future. The method signature tells the story: given the succesful value of a future, the function f provides the next Future. The result of this operation is another Future that is complete only when both of these futures have completed. If either Future fails, the given Future will also fail. This implicit interleaving of errors allow us to handle errors only in those places where they are semantically significant. flatMap is the standard name for the combinator with these semantics. +flatMap sequences two futures. That is, it takes a Future and an asynchronous function and returns a Future. The method signature tells the story: given the successful value of a future, the function f provides the next Future. flatMap automatically calls f if/when the input Future completes successfully. The result of this operation is another Future that is complete only when both of these futures have completed. If either Future fails, the given Future will also fail. This implicit interleaving of errors allow us to handle errors only in those places where they are semantically significant. flatMap is the standard name for the combinator with these semantics. If you have a Future and you want apply an asynchronous API to its value, use flatMap. For example, suppose you have a Future[User] and need a Future[Boolean] indicating whether the enclosed User has been banned. There is an isBanned API to determine whether a User has been banned, but it is asynchronous. You can use flatMap: @@ -200,7 +200,7 @@ h3. Concurrent composition You might want to fetch data from more than one service at once. For example, if you're writing a web service that shows content and ads, it might fetch content from one service and ads from another. But how do you tell your code to wait for both replies? This could get tricky if you had to write it yourself, but instead you can use concurrent combinators. -Future provides some concurrent combinators. Generally, these convert a sequence of Future into a Future of sequence in slightly different ways: +Future provides some concurrent combinators. Generally, these convert a sequence of Future into a Future of sequence in slightly different ways. This is nice because it lets you (essentially) package several Futures into a single Future.
 object Future {
@@ -314,7 +314,7 @@ This hypothetical example combines sequential and concurrent composition. Also n
 
 h2(#Service). Service
 
-A Finagle Service represents a service that handles remote proceduce calls, taking requests and giving back replies. It represents the service's "logic," not the network "plumbing" (but works well with other things that configure that plumbing). A Service is a function Req => Future[Rep] for some request and reply types.
+A Finagle Service represents a service that handles remote procedure calls, taking requests and giving back replies. It represents the service's "logic," not the network "plumbing" (but works well with other things that configure that plumbing). A Service is a function Req => Future[Rep] for some request and reply types.
 
 
abstract class Service[-Req, +Rep] extends (Req => Future[Rep]) @@ -327,7 +327,7 @@ We define both clients and servers in terms of Services. A Finagle client "imports" a Service from the network. Conceptually, a Finagle client has two parts:
    -
  • A function to use the Service: dispatch a Req and handle a Rep +
  • A function to use the Service: dispatch a Req and handle a Future[Rep]
  • Configuration of how to dispatch requests; e.g., as HTTP requests to port 80 of api.twitter.com
@@ -407,7 +407,9 @@ val server: Server = ServerBuilder() h2(#Filter). Filters -You can combine servers and clients. A simple proxy might look like this: +Filters transform services. They can provide service generic functionality. For example, you might have several services that should support rate limiting; you can write one rate-limiting filter and apply it to all your services. Filters are also good for decomposing a service into distinct phases. + +A simple proxy might look like this:
 class MyService(client: Service[..]) extends Service[HttpRequest, HttpResponse]
@@ -422,8 +424,6 @@ class MyService(client: Service[..]) extends Service[HttpRequest, HttpResponse]
 
 where rewriteReq and rewriteRes can provide protocol translation, for example.
 
-Filters transform services. They can provide service generic functionality. For example, you might have several services that should support rate limiting; you can write one rate-limiting filter and apply it to all your services. Filters are also good for decomposing a service into distinct phases.
-
 
 abstract class Filter[-ReqIn, +RepOut, +ReqOut, -RepIn]
   extends ((ReqIn, Service[ReqOut, RepIn]) => Future[RepOut])

From e871f1d8d0fe74035dff2b3b10b771612409414b Mon Sep 17 00:00:00 2001
From: Larry Hosken 
Date: Thu, 20 Sep 2012 11:26:25 -0700
Subject: [PATCH 03/12] moar JDOwens feedback. define "combinator". Say RPC.
 Clarify example's cache

---
 web/collections.textile | 2 +-
 web/finagle.textile     | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/web/collections.textile b/web/collections.textile
index 96a97827..1b799b9b 100644
--- a/web/collections.textile
+++ b/web/collections.textile
@@ -173,7 +173,7 @@ val result = res1 match {
 
 h1(#combinators). Functional Combinators
 
-Combinators are so-called because they are meant to be combined. The output of one function is often suitable as the input for another.
+Combinators are, roughly, functions that take other functions as parameters and are are meant to be combined. The output of one function is often suitable as the input for another.
 
 The most common use is on the standard data structures.
 
diff --git a/web/finagle.textile b/web/finagle.textile
index d6fd54db..4ed4c570 100644
--- a/web/finagle.textile
+++ b/web/finagle.textile
@@ -284,9 +284,9 @@ def isRateLimited(u: User): Future[Boolean] = {
 // Notice how you can swap this implementation out now with something that might
 // implement a different, more restrictive policy.
 
-// Check the cache to find out if user is rate-limited. We find out right
-// away by checking local cache. But we return a Future anyhow in case we
-// need to use a slower system later.
+// Check the cache to find out if user is rate-limited. This cache
+// implementation is just a Map, and can return a value right way. But we
+// return a Future anyhow in case we need to use a slower implementation later.
 def isLimitedByCache(u: User): Future[Boolean] =  Future.value(limitCache(u))
 
 // Update the cache
@@ -314,7 +314,7 @@ This hypothetical example combines sequential and concurrent composition. Also n
 
 h2(#Service). Service
 
-A Finagle Service represents a service that handles remote procedure calls, taking requests and giving back replies. It represents the service's "logic," not the network "plumbing" (but works well with other things that configure that plumbing). A Service is a function Req => Future[Rep] for some request and reply types.
+A Finagle Service represents a service that handles RPCs, taking requests and giving back replies. It represents the service's "logic," not the network "plumbing" (but works well with other things that configure that plumbing). A Service is a function Req => Future[Rep] for some request and reply types.
 
 
abstract class Service[-Req, +Rep] extends (Req => Future[Rep]) From f18b5365b63d21c1869af17dba1ef4979129be10 Mon Sep 17 00:00:00 2001 From: Larry Hosken Date: Fri, 21 Sep 2012 09:44:41 -0700 Subject: [PATCH 04/12] tweak "combinator" defn, plagiarizing Marius --- web/collections.textile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web/collections.textile b/web/collections.textile index 1b799b9b..452ec725 100644 --- a/web/collections.textile +++ b/web/collections.textile @@ -173,9 +173,7 @@ val result = res1 match { h1(#combinators). Functional Combinators -Combinators are, roughly, functions that take other functions as parameters and are are meant to be combined. The output of one function is often suitable as the input for another. - -The most common use is on the standard data structures. +List(1, 2, 3) map squared applies the function squared to the elements of the the list, returning a new list, perhaps List(1, 4, 9). We call operations like map combinators. Their most common use is on the standard data structures. h2(#map). map From c9ba861653171f68b6c4a958127539fee8d28500 Mon Sep 17 00:00:00 2001 From: Larry Hosken Date: Wed, 26 Sep 2012 11:54:13 -0700 Subject: [PATCH 05/12] +link SO combinator qn. +examples, +gloss on examples. Fix bad "server" defn --- web/collections.textile | 2 +- web/finagle.textile | 163 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 149 insertions(+), 16 deletions(-) diff --git a/web/collections.textile b/web/collections.textile index 452ec725..9146697d 100644 --- a/web/collections.textile +++ b/web/collections.textile @@ -173,7 +173,7 @@ val result = res1 match { h1(#combinators). Functional Combinators -List(1, 2, 3) map squared applies the function squared to the elements of the the list, returning a new list, perhaps List(1, 4, 9). We call operations like map combinators. Their most common use is on the standard data structures. +List(1, 2, 3) map squared applies the function squared to the elements of the the list, returning a new list, perhaps List(1, 4, 9). We call operations like map combinators. (If you'd like a better defintion, you might like Explanation of combinators on Stackoverflow.) Their most common use is on the standard data structures. h2(#map). map diff --git a/web/finagle.textile b/web/finagle.textile index 4ed4c570..27d98c70 100644 --- a/web/finagle.textile +++ b/web/finagle.textile @@ -8,7 +8,7 @@ layout: post "Finagle":https://github.com/twitter/finagle is Twitter's RPC system. "This":http://engineering.twitter.com/2011/08/finagle-protocol-agnostic-rpc-system.html blog post explains its motivations and core design tenets, the "finagle README":https://github.com/twitter/finagle/blob/master/README.md contains more detailed documentation. Finagle aims to make it easy to build robust clients and servers. * "REPL":#repl -* "Futures":#Future: "Sequential composition":#futsequential, "Concurrent composition":#futconcurrent, "Composition Example":#combined_combinator_example +* "Futures":#Future: "Sequential composition":#futsequential, "Concurrent composition":#futconcurrent, "Composition Example: Cached Rate Limit":#combined_combinator_example_cache, "Composition Example: Thumbnail Fetcher":#combined_combinator_thumbnail * "Service":#Service * "Client Example":#client * "Server Example":#server @@ -32,7 +32,7 @@ scala> h2(#Future). Futures -Finagle uses com.twitter.util.Future[1] to express delayed operations. A Future is a handle for a value not yet available. Finagle uses Futures as return values for its asynchronous APIs. A synchronous API returns a result immediately. An asynchronous API does something that might take a while. For example, an HTTP request to some service on the internet might not return a value for half a second. You don't want your program's execution to block for half a second waiting for that request to finish. "Slow" APIs can return a Future right away and then "fill in" its value when it resolves. +Finagle uses com.twitter.util.Future[1] to encode delayed operations. A Future is a handle for a value not yet available. Finagle uses Futures as return values for its asynchronous APIs. A synchronous API waits for a result before returning; an asynchronous API does not. For example, an HTTP request to some service on the internet might not return a value for half a second. You don't want your program's execution to block for half a second waiting. "Slow" APIs can return a Future right away and then "fill in" its value when it resolves.
 val myFuture = MySlowService(request) // returns right away
@@ -48,12 +48,12 @@ For example to, write code that dispatches a request and then "handles" the resp
 
 
 val future = dispatch(req) // returns immediately, but future is "empty"
-future onSuccess { // when the future gets "filled"...
-  case reply => println(reply) // ...use the value it contains
+future onSuccess { reply => // when the future gets "filled", use its value
+  println(reply)
 }
 
-You can play with Futures in the REPL. This is a bad way to learn how you'll use them in real code, but can help with understanding the API. When you use the REPL, Promise is a handy class. It's a concrete subclass of the abstract Future class. You can use it to create a Future that has no value yet. +You can play with Futures in the REPL. This is a bad way to learn how you will use them in real code, but can help with understanding the API. When you use the REPL, Promise is a handy class. It's a concrete subclass of the abstract Future class. You can use it to create a Future that has no value yet.
 scala> import com.twitter.util.{Future,Promise}
@@ -88,6 +88,8 @@ res1: Int = 7
 scala>
 
+When you use Futures in real code, you normally don't call get; you use callback functions instead. get is just handy for REPL tinkering. +   h3. Sequential composition @@ -102,7 +104,7 @@ The most important Future combinator is flatMap[2]: def Future[A].flatMap[B](f: A => Future[B]): Future[B]
-flatMap sequences two futures. That is, it takes a Future and an asynchronous function and returns a Future. The method signature tells the story: given the successful value of a future, the function f provides the next Future. flatMap automatically calls f if/when the input Future completes successfully. The result of this operation is another Future that is complete only when both of these futures have completed. If either Future fails, the given Future will also fail. This implicit interleaving of errors allow us to handle errors only in those places where they are semantically significant. flatMap is the standard name for the combinator with these semantics. +flatMap sequences two futures. That is, it takes a Future and an asynchronous function and returns another Future. The method signature tells the story: given the successful value of a future, the function f provides the next Future. flatMap automatically calls f if/when the input Future completes successfully. The result of this operation is another Future that is complete only when both of these futures have completed. If either Future fails, the given Future will also fail. This implicit interleaving of errors allow us to handle errors only in those places where they are semantically significant. flatMap is the standard name for the combinator with these semantics. If you have a Future and you want apply an asynchronous API to its value, use flatMap. For example, suppose you have a Future[User] and need a Future[Boolean] indicating whether the enclosed User has been banned. There is an isBanned API to determine whether a User has been banned, but it is asynchronous. You can use flatMap: @@ -211,7 +213,7 @@ object Future { }
-collect takes a set of Futures of the same type, and yields a Future of a sequence of values of that type. This future is complete when all of the underlying futures have completed, or when any of them have failed. +collect takes a set of Futures of the same type, and yields a Future of a sequence of values of that type. This future is complete when all of the underlying futures have completed or when any of them have failed.
 scala> val f2 = Future.value(2)
@@ -223,8 +225,11 @@ f3: com.twitter.util.Future[Int] = com.twitter.util.ConstFuture@263bb672
 scala> val f23 = Future.collect(Seq(f2, f3))
 f23: com.twitter.util.Future[Seq[Int]] = Promise@635209178(...)
 
-scala> f23.get() sum
-res82: Int = 5
+scala> val f5 = f23 map (_.sum)
+f5: com.twitter.util.Future[Int] = Promise@1954478838(...)
+
+scala> f5.get()
+res9: Int = 5
 
 
@@ -268,9 +273,9 @@ res113: Int = 7 scala>
-  +  -h3. Composition Example +h3. Composition Example: Cached Rate Limit These combinators express operations typical of network services. This hypothetical code performs rate limiting (in order to maintain a local rate limit cache) concurrently with dispatching a request on behalf of the user to the backend: @@ -298,10 +303,15 @@ def getTimeline(cred: Credentials): Future[Timeline] = isLimitedByCache(cred.user) flatMap { case true => Future.exception(new Exception("rate limited")) case false => + + // First we get auth'd user then we get timeline. + // Sequential composition of asynchronous APIs: use flatMap val timeline = auth(cred) flatMap(getTimeline) val limited = isRateLimited(cred.user) onSuccess( setIsLimitedInCache(cred.user, _)) + // 'join' concurrently combines differently-typed futures + // 'flatMap' sequentially combines, specifies what to do next timeline join limited flatMap { case (_, true) => Future.exception(new Exception("rate limited")) case (timeline, _) => Future.value(timeline) @@ -310,11 +320,134 @@ def getTimeline(cred: Credentials): Future[Timeline] = }
-This hypothetical example combines sequential and concurrent composition. Also note how there is no explicit error handling other than converting a rate limiting reply to an exception. If any future fails here, it is automatically propagated to the returned Future. +This hypothetical example combines sequential and concurrent composition. Note that there is no explicit error handling other than converting a rate limiting reply to an exception. If any future fails here, it is automatically propagated to the returned Future. + +  + +h3. Composition Examples: Web Crawlers + +You've seen how to to use combinators with Futures, but might appreciate more examples. Suppose you have a simple model of the internet. It has HTML pages and images. Pages can link to images and link to other pages. You can fetch a page or an image, but the API is asynchronous. This fake API calls these "fetchable" things Resources: + +
+import com.twitter.util.{Try,Future,Promise}
+
+// a fetchable thing
+trait Resource {
+  def imageLinks(): Seq[String]
+  def links(): Seq[String]
+}
+
+// HTML pages can link to Imgs and to other HTML pages.
+class HTMLPage(val i: Seq[String], val l: Seq[String]) extends Resource {
+  def imageLinks() = i
+  def links = l
+}
+
+// IMGs don't actually link to anything else
+class Img() extends Resource {
+  def imageLinks() = Seq()
+  def links() = Seq()
+}
+
+// profile.html links to gallery.html and has an image link to portrait.jpg
+val profile = new HTMLPage(Seq("portrait.jpg"), Seq("gallery.html"))
+val portrait = new Img
+
+// gallery.html links to profile.html and two images
+val gallery = new HTMLPage(Seq("kitten.jpg", "puppy.jpg"), Seq("profile.html"))
+val kitten = new Img
+val puppy = new Img
+
+val internet = Map(
+  "profile.html" -> profile,
+  "gallery.html" -> gallery,
+  "portrait.jpg" -> portrait,
+  "kitten.jpg" -> kitten,
+  "puppy.jpg" -> puppy
+)
+
+// fetch(url) attempts to fetch a resource from our fake internet.
+// Its returned Future might contain a Resource or an exception
+def fetch(url: String) = { new Promise(Try(internet(url))) }
+
+ +Sequential Composition + +Suppose you wish to fetch a page's first image, given that page's URL. Perhaps you're making a site where users can post links to interesting pages. To help other users decide whether a link is worth following, you want to display a thumbnail of the linked-to page's first image. + +If you didn't know about combinators, you could still write a thumbnail-getting function: + +
+def getThumbnail(url: String): Future[Resource]={
+  val returnVal = new Promise[Resource]
+
+  fetch(url) onSuccess { page => // callback for successful page fetch
+    fetch(page.imageLinks()(0)) onSuccess { p => // callback for successful img fetch
+      returnVal.setValue(p)
+    } onFailure { exc => // callback for failed img fetch
+      returnVal.setException(exc)
+    }
+  } onFailure { exc => // callback for failed page fetch
+    returnVal.setException(exc)
+  }
+  returnVal
+}
+
+ +This version of the function works OK. Most of it consists of unwrapping Futures and then putting their contents into another Future. + +We want to get one page and then get one image from that page. If you want A and then B, that usually means sequential composition. Since our B is asynchronous, we want flatMap: + +
+def getThumbnail(url: String): Future[Resource] =
+  fetch(url) flatMap { page => fetch(page.imageLinks()(0)) }
+
+ +...with Concurrent Composition + +Fetching a page's first image is nice, but maybe we should fetch all of them and let the user choose their favorite. We could write a for loop to fetch them one after the other, but that could take a long time. We'd like to fetch them in parallel. If you want things to happen "in parallel," that usually means concurrent composition. So we use Future.collect to fetch all of the images: + +
+def getThumbnails(url:String): Future[Seq[Resource]] =
+  fetch(url) flatMap { page =>
+    Future.collect(
+      page.imageLinks map { u => fetch(u) }
+    )
+  }
+
+ +If this makes sense to you, then great. You might worry about the line page.imageLinks map { u => fetch(u) }: it uses map and the thing after the map returns a Future. Aren't we supposed to use flatMap when the next thing returns a Future? But notice that the thing before the map isn't a Future; it's a collection. collection map function returns a collection; we use Future.collect to gather that collection of Futures into one Future. + +Concurrent + Recursion + +Instead of fetching a page's images, we might fetch the other pages that it links to. If we then recurse on those, we have a simple web crawler. + +
+// Return
+def crawl(url: String): Future[Seq[Resource]] =
+  fetch(url) flatMap { page =>
+    Future.collect(
+      page.links map { u => crawl(u) }
+    ) map { pps => pps.flatten }
+}
+
+crawl("profile.html")
+   ...hangs REPL, infinite loop...
+Ctrl-C
+Execution interrupted by signal.
+
+
+scala>
+// She's gone rogue, captain! Have to take her out!
+// Calling Thread.stop on runaway Thread[Thread-93,5,main] with offending code:
+// scala> crawl("profile.html")
+
+ +In practice, this web crawler is not so useful: we didn't tell it when to stop crawling; it merrily re-fetches resources that it just fetched a moment earlier. h2(#Service). Service -A Finagle Service represents a service that handles RPCs, taking requests and giving back replies. It represents the service's "logic," not the network "plumbing" (but works well with other things that configure that plumbing). A Service is a function Req => Future[Rep] for some request and reply types. +A Finagle Service represents a service that handles RPCs, taking requests and giving back replies. A Service is a function Req => Future[Rep] for some request and reply types.
abstract class Service[-Req, +Rep] extends (Req => Future[Rep]) @@ -345,7 +478,7 @@ We also talk about Finagle "filters." A filter sits between services, modifying h2(#client). Client -A Finagle client is defined in terms of a Service and some configuration about how to send data over the network. A simple HTTP client might like: +A Finagle client "imports" a Service. It has some configuration about how to send data over the network. A simple HTTP client might look like:
 import org.jboss.netty.handler.codec.http.{DefaultHttpRequest, HttpRequest, HttpResponse, HttpVersion, HttpMethod}
@@ -356,7 +489,7 @@ import com.twitter.finagle.http.Http
 // Don't worry, we discuss this magic "ClientBuilder" later
 val client: Service[HttpRequest, HttpResponse] = ClientBuilder()
   .codec(Http())
-  .hosts("twitter.com:80")
+  .hosts("twitter.com:80") // If >1 host, client does simple load-balancing
   .hostConnectionLimit(1)
   .build()
 

From fca13550abd0bcd142d8e780fc3425f34c740781 Mon Sep 17 00:00:00 2001
From: Larry Hosken 
Date: Fri, 28 Sep 2012 11:00:47 -0700
Subject: [PATCH 06/12] clearer. ServerBuilder example that compiles

---
 web/finagle.textile | 22 ++++++++++++----------
 1 file changed, 12 insertions(+), 10 deletions(-)

diff --git a/web/finagle.textile b/web/finagle.textile
index 27d98c70..8baf708e 100644
--- a/web/finagle.textile
+++ b/web/finagle.textile
@@ -643,20 +643,22 @@ val client: Service[HttpRequest, HttpResponse] = ClientBuilder()
   .build()
 
-This builds a client that load balances over the 3 given hosts, establishing at most 1 connection per host, and giving up only after 2 failures. Stats are reported to "ostrich":https://github.com/twitter/ostrich. The following builder options are required (and their presence statically enforced): hosts or cluster, codec and hostConnectionLimit. +This builds a client that load balances over the three given hosts, establishing at most one connection per host, and giving up only after two failures. Stats are reported to "ostrich":https://github.com/twitter/ostrich. The following builder options are required (and their presence statically enforced): hosts or cluster, codec and hostConnectionLimit. + +Similarly, you can use a ServerBuilder to make your service "listen" for incoming requests:
-val myService: Service[HttpRequest, HttpResponse] = // provided by the user
-ServerBuilder()
-  .codec(Http)
-  .hostConnectionMaxLifeTime(5.minutes)
-  .readTimeout(2.minutes)
-  .name("myHttpServer")
-  .bindTo(new InetSocketAddress(serverPort))
-  .build(myService)
+val service = new ParrotServerService.Service(parrotServer, new TBinaryProtocol.Factory()) // construct instance of your Finagle service
+val  server = ServerBuilder()
+  .bindTo(new InetSocketAddress(port))
+  .codec(ThriftServerFramedCodec())
+  .name("parrot thrift server")
+//  .hostConnectionMaxLifeTime(5.minutes)
+//  .readTimeout(2.minutes)
+  .build(service)
 
-This will serve, on port serverPort an HTTP server which dispatches requests to myService. Each connection is allowed to stay alive for up to 5 minutes, and we require a request to be sent within 2 minutes. The required ServerBuilder options are: name, bindTo and codec. +This will serve, on port port, a Thrift server which dispatches requests to service. If we un-comment the hostConnectionMaxLifeTime line, each connection would be allowed to stay alive for up to 5 minutes. If we un-comment the readTimeout line, then we require a request to be sent within 2 minutes. The required ServerBuilder options are: name, bindTo and codec. fn1. Careful, there are other "Future" classes out there. Don't confuse twitter.com.util.Future with scala.actor.Future or java.util.concurrent.Future! From 42f44a1fbe221a2326416ff9ed7e57074416e9e4 Mon Sep 17 00:00:00 2001 From: Larry Hosken Date: Wed, 19 Sep 2012 14:38:05 -0700 Subject: [PATCH 07/12] corrections, diagrams, and clarifications for Finagle Add instructions for a console that can import finagle things. I needed it, maybe other folks will also. Show how to create+resolve Futures+Promises in the REPL. Change flatMap example to clarify which params are Future-thingies vs thingies. Instead of throwing a "map" into a flatMap example, bring it out into its own example. My puny brain took a while to figure out the combined example. Show examples for using the concurrent combinators. I really wanted an example for select; its API wasn't what I expected (though it makes sense in hindsight). But it looked funny that just one of these had an example so I added examples for the others too. The simple HTTP client and server examples in the Service section had drifted far enough away from the current API such that I had a tough time figuring out the API. So updated them, also filling in some of the setup which the examples assumed. Throw in motivating examples for some of constructs. Steal some diagrams from JDOwens Many misc clarifications --- web/finagle.textile | 413 ++++++++++++++++++++++++++++------ web/finagle_client_server.png | Bin 0 -> 10457 bytes web/finagle_filter.png | Bin 0 -> 10214 bytes 3 files changed, 338 insertions(+), 75 deletions(-) create mode 100644 web/finagle_client_server.png create mode 100644 web/finagle_filter.png diff --git a/web/finagle.textile b/web/finagle.textile index cabbe4a6..76bad581 100644 --- a/web/finagle.textile +++ b/web/finagle.textile @@ -7,61 +7,200 @@ layout: post "Finagle":https://github.com/twitter/finagle is Twitter's RPC system. "This":http://engineering.twitter.com/2011/08/finagle-protocol-agnostic-rpc-system.html blog post explains its motivations and core design tenets, the "finagle README":https://github.com/twitter/finagle/blob/master/README.md contains more detailed documentation. Finagle aims to make it easy to build robust clients and servers. -h2. Futures +* "REPL":#repl +* "Futures":#Future: "Sequential composition":#futsequential, "Concurrent composition":#futconcurrent, "Composition Example":#combined_combinator_example +* "Service":#Service +* "Client Example":#client +* "Server Example":#server +* "Filters":#Filter +* "Builders":#Builder -Finagle uses com.twitter.util.Future[1] to express delayed operations. Futures are highly expressive and composable, allowing for the succinct expression of concurrent and sequential operations with great clarity. Futures are a handle for a value not yet available, with methods to register callbacks to be invoked when the value becomes available. They invert the "traditional" model of asynchronous computing which typically expose APIs similar to this: +h2(#repl). Finagle-Friendly REPL + +We're about to discuss some code that's not part of standard Scala. If you like to use the REPL to learn, you might wonder how to get a Scala REPL that knows about Twitter's Finagle and things it depends on. + +You want the Finagle source code. + +If you have the Finagle source code in a directory named finagle, you can get a console via
-Callback cb = new Callback() {
-  void onComplete(R result) { … }
-  void onFailure(Throwable error) { … }
-}
+$ cd finagle
+$ ./sbt "project finagle-core" console
+ ...build output...
+scala>
+
+ +h2(#Future). Futures + +Finagle uses com.twitter.util.Future[1] to express delayed operations. A Future is a handle for a value not yet available. Finagle uses Futures as return values for its asynchronous APIs. A synchronous API returns a result immediately. An asynchronous API does something that might take a while. For example, an HTTP request to some service on the internet might not return a value for half a second. You don't want your program's execution to block for half a second waiting for that request to finish. "Slow" APIs can return a Future right away and then "fill in" its value when it resolves. -dispatch(req, cb); +
+val myFuture = MySlowService(request) // returns right away
+   ...do other things...
+val serviceResult = myFuture.get() // blocks until service "fills in" myFuture
 
-Here, the Callback.onComplete is invoked when the result of the dispatch operation is available, and Callback.onFailure if the operation fails. With futures, we instead invert this control flow: +In practice, you won't write code that sends a request and then calls myFuture.get a few statements later. A Future has methods to register callbacks to invoke when the value becomes available. + +If you've used other asynchronous APIs, you perhaps cringed when you saw the word "callbacks" just now. You might associate them with illegible code flows, functions hiding far from where they're invoked. But Futures can take advantage of Scala's first-class functions to present a more-readable code flow. You can define a simpler handler function in the place where it's invoked. + +For example to, write code that dispatches a request and then "handles" the response, you can keep the code together:
-val future = dispatch(req)
-future onSuccess { value => … }
-future onFailure { error => … }
+val future = dispatch(req) // returns immediately, but future is "empty"
+future onSuccess { // when the future gets "filled"...
+  ...do awesome things with future.get()...
+}
 
-Futures themselves have combinators similar to those we've encountered before in the various collections APIs. Combinators work by exploiting a uniform API, wrapping some underlying Future with new behavior without modifying that underlying Future. +You can play with Futures in the REPL. This is a bad way to learn how you'll use them in real code, but can help with understanding the API. When you use the REPL, Promise is a handy class. It's a concrete subclass of the abstract Future class. You can use it to create a Future that has no value yet. + +
+scala> import com.twitter.util.{Future,Promise}
+import com.twitter.util.{Future, Promise}
+
+scala> val f6 = Future.value(6) // create already-resolved future
+f6: com.twitter.util.Future[Int] = com.twitter.util.ConstFuture@c63a8af
+
+scala> f6.get()
+res0: Int = 6
+
+scala> val fex = Future.exception(new Exception) // create resolved sad future
+fex: com.twitter.util.Future[Nothing] = com.twitter.util.ConstFuture@38ddab20
+
+scala> fex.get()
+java.lang.Exception
+  ... stack trace ...
+
+scala> val pr7 = new Promise[Int] // create unresolved future
+pr7: com.twitter.util.Promise[Int] = Promise@1994943491(...)
+
+scala> pr7.get()
+  ...console hangs, waiting for future to resolve...
+Ctrl-C
+Execution interrupted by signal.
+
+scala> pr7.setValue(7)
+
+scala> pr7.get()
+res1: Int = 7
+
+scala>
+
+ +  h3. Sequential composition +Futures have combinators similar to those in the collections APIs (e.g., map, flatMap). A collection-combinator, you recall, lets you express things like "I have a List of integers and a square function: map that to the List of the squares of my integers." This is neat; you can put together the combinator-function with another function to effectively define a new function. A Future-combinator lets you express things like "I have a Future hypothetical-integer and a square function: map that to the Future square of my hypothetical-integer." + +If you're defining an asynchronous API, a request value comes in and your API gives back a response wrapped in a Future. Thus, these combinators that turn inputs and functions into Futures are darned useful: they help you define your asynchronous API in terms of other asychronous APIs. + The most important Future combinator is flatMap[2]:
def Future[A].flatMap[B](f: A => Future[B]): Future[B]
-flatMap sequences two features. The method signature tells the story: given the succesful value of the future f must provide the next Future. The result of this operation is another Future that is complete only when both of these futures have completed. If either Future fails, the given Future will also fail. This implicit interleaving of errors allow us to handle errors only in those places where they are semantically significant. flatMap is the standard name for the combinator with these semantics. Scala also has syntactic shorthand to invoke it: the for comprehension. +flatMap sequences two futures. That is, it defines a Future as the result of applying an asynchronous function to another Future. The method signature tells the story: given the succesful value of a future, the function f provides the next Future. The result of this operation is another Future that is complete only when both of these futures have completed. If either Future fails, the given Future will also fail. This implicit interleaving of errors allow us to handle errors only in those places where they are semantically significant. flatMap is the standard name for the combinator with these semantics. -As an example, let's assume we have methods authenticate: Request -> User, and rateLimit: User -> Boolean, then the following code: +If you have a Future and you want apply an asynchronous API to its value, use flatMap. For example, suppose you have a Future[User] and need a Future[Boolean] indicating whether the enclosed User has been banned. There is an isBanned API to determine whether a User has been banned, but it is asynchronous. You can use flatMap:
-val f = authenticate(request) flatMap { u =>
-  rateLimit(u) map { r => (u, r) }
-}
+scala> import com.twitter.util.{Future,Promise}
+import com.twitter.util.{Future, Promise}
+
+scala> class User(n: String) { val name = n }
+defined class User
+
+scala> def isBanned(u: User) = { Future.value(false) }
+isBanned: (u: User)com.twitter.util.Future[Boolean]
+
+scala> val pru = new Promise[User]
+pru: com.twitter.util.Promise[User] = Promise@897588993(...)
+
+scala> val futBan = pru flatMap isBanned // apply isBanned to future
+futBan: com.twitter.util.Future[Boolean] = Promise@1733189548(...)
+
+scala> futBan.get()
+  ...REPL hangs, futBan not resolved yet...
+Ctrl-C
+Execution interrupted by signal.
+
+scala> pru.setValue(new User("prudence"))
+
+scala> futBan.get()
+res45: Boolean = false
+
+scala>
 
-With the help of for-comprehensions, we can write the above as: +Similarly, to apply a synchronous function to a Future, use map. For example, suppose you have a Future[RawCredentials] and need a Future[Credentials]. You have a synchronous normalize function that converts from RawCredentials to Credentials. You can use map:
-val f = for {
-  u <- authenticate(request)
-  r <- rateLimit(u)
-} yield (u, r)
+scala> class RawCredentials(u: String, pw: String) {
+     |   val username = u
+     |   val password = pw
+     | }
+defined class RawCredentials
+
+scala> class Credentials(u: String, pw: String) {
+     |   val username = u
+     |   val password = pw
+     | }
+defined class Credentials
+
+scala> def normalize(raw: RawCredentials) = {
+     |   new Credentials(raw.username.toLowerCase(), raw.password)
+     | }
+normalize: (raw: RawCredentials)Credentials
+
+scala> val praw = new Promise[RawCredentials]
+praw: com.twitter.util.Promise[RawCredentials] = Promise@1341283926(...)
+
+scala> val fcred = praw map normalize // apply normalize to future
+fcred: com.twitter.util.Future[Credentials] = Promise@1309582018(...)
+
+scala> fcred.get()
+   ...REPL hangs, fcred doesn't have a value yet...
+Ctrl-C
+Execution interrupted by signal.
+
+scala> praw.setValue(new RawCredentials("Florence", "nightingale"))
+
+scala> fcred.get().username
+res48: String = florence
+
+scala>
+
+ +Scala has syntactic shorthand to invoke flatMap: the for comprehension. Suppose you want to authenticate a login request via an asynchronous API and then check to see whether the user is banned via another asynchronous API. With the help of for-comprehensions, we can write this as: + +
+scala> def authenticate(req: LoginRequest) = {
+     |   // TODO: we should check the password
+     |   Future.value(new User(req.username))
+     | }
+authenticate: (req: LoginRequest)com.twitter.util.Future[User]
+
+scala> val f = for {
+     |  u <- authenticate(request)
+     |  b <- isBanned(u)
+     | } yield (u, b)
+f: com.twitter.util.Future[(User, Boolean)] = Promise@35785606(...)
+
+scala>
 
-produces a future f: Future[(User, Boolean)] that provides both the user object and and a boolean indicating whether that user has been rate limited. Note how sequential composition is required here: rateLimit takes as an argument the output of authenticate +produces a future f: Future[(User, Boolean)] with the user object and and a Boolean indicating whether that user has been rate limited. Note how sequential composition is required here: rateLimit takes as an argument the output of authenticate. + +  h3. Concurrent composition -There are also a number of concurrent combinators. Generally these convert a sequence of Future into a Future of sequence, in slightly different ways: +You might want to fetch data from more than one service at once. For example, if you're writing a web service that shows content and ads, it might fetch content from one service and ads from another. But how do you tell your code to wait for both replies? This could get tricky if you had to write it yourself, but instead you can use concurrent combinators. + +Future provides some concurrent combinators. Generally, these convert a sequence of Future into a Future of sequence in slightly different ways:
 object Future {
@@ -72,52 +211,160 @@ object Future {
 }
 
-collect is the most straightforward one: given a set of Futures of the same type, we are given a Future of a sequence of values of that type. This future is complete when all of the underlying futures have completed, or when any of them have failed. +collect takes a set of Futures of the same type, and yields a Future of a sequence of values of that type. This future is complete when all of the underlying futures have completed, or when any of them have failed. -join takes a sequence of Futures whose types may be mixed, yielding a Future[Unit] that is completely when all of the underlying futures are (or fails if any of them do). This is useful for indicating the completion of a set of heterogeneous operations. +
+scala> val f2 = Future.value(2)
+f2: com.twitter.util.Future[Int] = com.twitter.util.ConstFuture@13ecdec0
 
-select returns a Future that is complete when the first of the given Futures complete, together with the remaining uncompleted futures.
+scala> val f3 = Future.value(3)
+f3: com.twitter.util.Future[Int] = com.twitter.util.ConstFuture@263bb672
 
-In combination, this allows for powerful and concise expression of operations typical of network services. This hypothetical code performs rate limiting (in order to maintain a local rate limit cache) concurrently with dispatching a request on behalf of the user to the backend:
+scala> val f23 = Future.collect(Seq(f2, f3))
+f23: com.twitter.util.Future[Seq[Int]] = Promise@635209178(...)
+
+scala> f23.get() sum
+res82: Int = 5
+
+
+ +join takes a sequence of Futures whose types may be mixed, yielding a Future[Unit] that is complete when all of the underlying futures are (or fails if any of them do). This is useful for indicating the completion of a set of heterogeneous operations. It might be a good solution for the content-and-ads example.
-def serve(request: Request): Future[Response] = {
-  val userLimit: Future[(User, Boolean)] =
-    for {
-      user    <- auth(request)
-      limited <- isLimit(user)
-    } yield (user, limited)
-  
-  val done = 
-    dispatch(request) join userLimit
-  
-  done flatMap { case (rep, (usr, lim)) =>
-    if (lim) {
-      updateLocalRateLimitCache(usr)
-      Future.exception(new Exception("rate limited"))
-    } else {
-      Future.value(rep)
-    }
+scala> val ready = Future.join(Seq(f2, f3))
+ready: com.twitter.util.Future[Unit] = Promise@699347471(...)
+
+scala> ready.get() // doesn't ret value, but I know my futures are done
+
+scala>
+
+ +select returns a Future that is complete when the first of the given Futures complete. It returns that Future together with a Seq containing the remaining uncompleted Futures. + +
+scala> val pr7 = new Promise[Int] // unresolved future
+pr7: com.twitter.util.Promise[Int] = Promise@1608532943(...)
+
+scala> val sel = Future.select(Seq(f2, pr7)) // select from 2 futs, one resolved
+sel: com.twitter.util.Future[...] = Promise@1003382737(...)
+
+scala> val(complete, stragglers) = sel.get()
+complete: com.twitter.util.Try[Int] = Return(2)
+stragglers: Seq[...] = List(...)
+
+scala> complete.get()
+res110: Int = 2
+
+scala> stragglers(0).get() // our list of not-yet-finished futures has one item
+  ...get() hangs the REPL because this straggling future is not finished...
+Ctrl-C
+Execution interrupted by signal.
+
+scala> pr7.setValue(7)
+
+scala> stragglers(0).get()
+res113: Int = 7
+
+scala>
+
+ +  + +h3. Composition Example + +These combinators express operations typical of network services. This hypothetical code performs rate limiting (in order to maintain a local rate limit cache) concurrently with dispatching a request on behalf of the user to the backend: + +
+// Find out if user is rate-limited. This can be slow; we have to ask
+// the remote server that keeps track of who is rate-limited.
+def isRateLimited(u: User): Future[Boolean] = {
+  ...
+}
+
+// Notice how you can swap this implementation out now with something that might
+// implement a different, more restrictive policy.
+
+// Check the cache to find out if user is rate-limited. We find out right
+// away by checking local cache. But we return a Future anyhow in case we
+// need to use a slower system later.
+def isLimitedByCache(u: User): Future[Boolean] =  Future.value(limitCache(u))
+
+// Update the cache
+def setIsLimitedInCache(user: User, v: Boolean) { limitCache(user) = v }
+
+// Get a timeline of tweets... unless the user is rate-limited (then throw
+// an exception instead)
+def getTimeline(cred: Credentials): Future[Timeline] =
+  isLimitedByCache(cred.user) flatMap {
+    case true => Future.exception(new Exception("rate limited"))
+    case false =>
+      val timeline = auth(cred) flatMap(getTimeline)
+      val limited = isRateLimited(cred.user) onSuccess(
+                                       setIsLimitedInCache(cred.user, _))
+
+      timeline join limited flatMap {
+        case (_, true) => Future.exception(new Exception("rate limited"))
+        case (timeline, _) => Future.value(timeline)
+      }
   }
 }
 
-This hypothetical example combines both sequential and concurrent composition. Also note how there is no explicit error handling other than converting a rate limiting reply to an exception. If any future fails here, it is automatically propagated to the returned Future. +This hypothetical example combines sequential and concurrent composition. Also note how there is no explicit error handling other than converting a rate limiting reply to an exception. If any future fails here, it is automatically propagated to the returned Future. -h2. Service +h2(#Service). Service -A Service is a function Req => Future[Rep] for some request and reply types. Service is used by both clients and servers: servers implement Service and clients use builders to create one used for querying. +A Finagle Service represents a service that handles remote proceduce calls, taking requests and giving back replies. It represents the service's "logic," not the network "plumbing" (but works well with other things that configure that plumbing). A Service is a function Req => Future[Rep] for some request and reply types.
abstract class Service[-Req, +Rep] extends (Req => Future[Rep])
-A simple HTTP client might do: +!>finagle_client_server.png(Client and Server)! + +We define both clients and servers in terms of Services. + +A Finagle client "imports" a Service from the network. Conceptually, a Finagle client has two parts: + +
    +
  • A function to use the Service: dispatch a Req and handle a Rep +
  • Configuration of how to dispatch requests; e.g., as HTTP requests to port 80 of api.twitter.com +
+ +Similarly, a Finagle server "exports" a Service to the network. A server has two parts: +
    +
  • A function to implement the Service: take a Req and return a Future[Rep] +
  • Configuration of how to "listen" for incoming Reqs; e.g., as HTTP requests on port 80. +
+ +This separates the Service "logic" from the configuration of how data flows across the network. + +!>finagle_filter.png(Filter and Server)! + +We also talk about Finagle "filters." A filter sits between services, modifying data that flows through it. Filters compose nicely with services. For example, if you have a rate-limiter filter and a tweet-serving service, you can put them together to make a rate-limited tweet-serving service. + +h2(#client). Client + +A Finagle client is defined in terms of a Service and some configuration about how to send data over the network. A simple HTTP client might like:
-service: Service[HttpRequest, HttpResponse]
+import org.jboss.netty.handler.codec.http.{DefaultHttpRequest, HttpRequest, HttpResponse, HttpVersion, HttpMethod}
+import com.twitter.finagle.Service
+import com.twitter.finagle.builder.ClientBuilder
+import com.twitter.finagle.http.Http
+
+// Don't worry, we discuss this magic "ClientBuilder" later
+val client: Service[HttpRequest, HttpResponse] = ClientBuilder()
+  .codec(Http())
+  .hosts("twitter.com:80")
+  .hostConnectionLimit(1)
+  .build()
+
+val req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/")
 
-val f = service(HttpRequest("/", HTTP_1_1))
+val f = client(req) // Client, send the request
+
+// Handle the response:
 f onSuccess { res =>
   println("got response", res)
 } onFailure { exc =>
@@ -125,28 +372,45 @@ f onSuccess { res =>
 }
 
-Servers implement Service: +h2(#server). Server + +A server is defined in terms of a Service and some configuration about how to "listen" for requests coming in over the network. A simple HTTP server might look like:
-class MyServer 
-  extends Service[HttpRequest, HttpResponse]
-{
+import com.twitter.finagle.Service
+import com.twitter.finagle.http.Http
+import com.twitter.util.Future
+import org.jboss.netty.handler.codec.http.{DefaultHttpResponse, HttpVersion, HttpResponseStatus, HttpRequest, HttpResponse}
+import java.net.{SocketAddress, InetSocketAddress}
+import com.twitter.finagle.builder.{Server, ServerBuilder}
+import com.twitter.finagle.builder.ServerBuilder
+
+// Define our service: OK response for root, 404 for other paths
+val rootService = new Service[HttpRequest, HttpResponse] {
   def apply(request: HttpRequest) = {
-    request.path match {
-      case "/" => 
-        Future.value(HttpResponse("root"))
-      case _ => 
-        Future.value(HttpResponse("default"))
+    val r = request.getUri match {
+      case "/" => new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK)
+      case _ => new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND)
     }
+    Future.value(r)
   }
 }
+
+// Serve our service on a port
+val address: SocketAddress = new InetSocketAddress(10000)
+val server: Server = ServerBuilder()
+  .codec(Http())
+  .bindTo(address)
+  .name("HttpServer")
+  .build(rootService)
 
-Combining them is easy. A simple proxy might look like this: +h2(#Filter). Filters + +You can combine servers and clients. A simple proxy might look like this:
-class MyServer(client: Service[..])
-  extends Service[HttpRequest, HttpResponse]
+class MyService(client: Service[..]) extends Service[HttpRequest, HttpResponse]
 {
   def apply(request: HttpRequest) = {
     client(rewriteReq(request)) map { res =>
@@ -158,9 +422,7 @@ class MyServer(client: Service[..])
 
 where rewriteReq and rewriteRes can provide protocol translation, for example.
 
-h2. Filters
-
-Filters are service transformers. They are useful both for providing functionality that's service generic as well as factoring a given service into distinct phases.
+Filters transform services. They can provide service generic functionality. For example, you might have several services that should support rate limiting; you can write one rate-limiting filter and apply it to all your services. Filters are also good for decomposing a service into distinct phases.
 
 
 abstract class Filter[-ReqIn, +RepOut, +ReqOut, -RepIn]
@@ -170,7 +432,7 @@ abstract class Filter[-ReqIn, +RepOut, +ReqOut, -RepIn]
 Its type is better viewed diagramatically:
 
 
-    ((ReqIn, Service[ReqOut, RepIn]) 
+    ((ReqIn, Service[ReqOut, RepIn])
          => Future[RepOut])
 
 
@@ -199,10 +461,9 @@ This example shows how you might provide authentication (via an authentication s
 
 
 class RequireAuthentication(authService: AuthService)
-  extends Filter[HttpReq, HttpRep, AuthHttpReq, HttpRep]
-{
+  extends Filter[HttpReq, HttpRep, AuthHttpReq, HttpRep] {
   def apply(
-    req: HttpReq, 
+    req: HttpReq,
     service: Service[AuthHttpReq, HttpRep]
   ) = {
     authService.auth(req) flatMap {
@@ -216,6 +477,8 @@ class RequireAuthentication(authService: AuthService)
 }
 
+Using a filter this way yields some nice advantages. It helps you keep your "auth logic" in one place. Having a separate type for authorized requests makes it easier to reason about your program's security. + Filters compose together with andThen. Providing a Service as an argument to andThen creates a (filtered) Service (types provided for illustration).
@@ -230,9 +493,9 @@ val authenticatedTimedOutService: Service[HttpReq, HttpRep] =
   authenticateAndTimedOut andThen serviceRequiringAuth
 
-h2. Builders +h2(#Builder). Builders -Finally, builders put it all together. A ClientBuilder produces a Service instance given a set of parameters, and a ServerBuilder takes a Service instance and dispatches incoming requests on it. In order to determine the type of Service, we must provide a Codec. Codecs provide the underlying protocol implementation (eg. HTTP, thrift, memcached). Both builders have many parameters, and require a few. +Builders put it all together. A ClientBuilder produces a Service instance given a set of parameters, and a ServerBuilder takes a Service instance and dispatches incoming requests on it. In order to determine the type of Service, we must provide a Codec. Codecs provide the underlying protocol implementation (eg. HTTP, thrift, memcached). Both builders have many parameters, and require a few. Here's an example ClientBuilder invocation (types provided for illustration): @@ -259,9 +522,9 @@ ServerBuilder() .bindTo(new InetSocketAddress(serverPort)) .build(myService)
- + This will serve, on port serverPort an HTTP server which dispatches requests to myService. Each connection is allowed to stay alive for up to 5 minutes, and we require a request to be sent within 2 minutes. The required ServerBuilder options are: name, bindTo and codec. -fn1. distinct from java.util.concurrent.Future +fn1. Careful, there are other "Future" classes out there. Don't confuse twitter.com.util.Future with scala.actor.Future or java.util.concurrent.Future! -fn2. this is equivalent to a monadic bind +fn2. If you study type systems and/or category theory, you'll be glad to learn that flatMap is equivalent to a monadic bind. diff --git a/web/finagle_client_server.png b/web/finagle_client_server.png new file mode 100644 index 0000000000000000000000000000000000000000..e5d39c6585ee9ce2ce53adb5368fdb96724489e0 GIT binary patch literal 10457 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 YeV|kFp~7P3M8J>gJx#?jd6Urp0S;*y#{d8T literal 0 HcmV?d00001 diff --git a/web/finagle_filter.png b/web/finagle_filter.png new file mode 100644 index 0000000000000000000000000000000000000000..4be4667b5ae2d96dc22b35ead77a279a736bf838 GIT binary patch literal 10214 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 literal 0 HcmV?d00001 From a3b2221a06df7d1882de1828b8a1c36ce7707a27 Mon Sep 17 00:00:00 2001 From: Larry Hosken Date: Wed, 19 Sep 2012 16:36:52 -0700 Subject: [PATCH 08/12] incorporate moar JDOwens feedback --- web/collections.textile | 2 +- web/finagle.textile | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/web/collections.textile b/web/collections.textile index 59a9e5c8..96a97827 100644 --- a/web/collections.textile +++ b/web/collections.textile @@ -171,7 +171,7 @@ val result = res1 match { *See Also* Effective Scala has opinions about Options. -h1. Functional Combinators +h1(#combinators). Functional Combinators Combinators are so-called because they are meant to be combined. The output of one function is often suitable as the input for another. diff --git a/web/finagle.textile b/web/finagle.textile index 76bad581..d6fd54db 100644 --- a/web/finagle.textile +++ b/web/finagle.textile @@ -49,7 +49,7 @@ For example to, write code that dispatches a request and then "handles" the resp
 val future = dispatch(req) // returns immediately, but future is "empty"
 future onSuccess { // when the future gets "filled"...
-  ...do awesome things with future.get()...
+  case reply => println(reply) // ...use the value it contains
 }
 
@@ -102,7 +102,7 @@ The most important Future combinator is flatMap[2]: def Future[A].flatMap[B](f: A => Future[B]): Future[B]
-flatMap sequences two futures. That is, it defines a Future as the result of applying an asynchronous function to another Future. The method signature tells the story: given the succesful value of a future, the function f provides the next Future. The result of this operation is another Future that is complete only when both of these futures have completed. If either Future fails, the given Future will also fail. This implicit interleaving of errors allow us to handle errors only in those places where they are semantically significant. flatMap is the standard name for the combinator with these semantics. +flatMap sequences two futures. That is, it takes a Future and an asynchronous function and returns a Future. The method signature tells the story: given the successful value of a future, the function f provides the next Future. flatMap automatically calls f if/when the input Future completes successfully. The result of this operation is another Future that is complete only when both of these futures have completed. If either Future fails, the given Future will also fail. This implicit interleaving of errors allow us to handle errors only in those places where they are semantically significant. flatMap is the standard name for the combinator with these semantics. If you have a Future and you want apply an asynchronous API to its value, use flatMap. For example, suppose you have a Future[User] and need a Future[Boolean] indicating whether the enclosed User has been banned. There is an isBanned API to determine whether a User has been banned, but it is asynchronous. You can use flatMap: @@ -200,7 +200,7 @@ h3. Concurrent composition You might want to fetch data from more than one service at once. For example, if you're writing a web service that shows content and ads, it might fetch content from one service and ads from another. But how do you tell your code to wait for both replies? This could get tricky if you had to write it yourself, but instead you can use concurrent combinators. -Future provides some concurrent combinators. Generally, these convert a sequence of Future into a Future of sequence in slightly different ways: +Future provides some concurrent combinators. Generally, these convert a sequence of Future into a Future of sequence in slightly different ways. This is nice because it lets you (essentially) package several Futures into a single Future.
 object Future {
@@ -314,7 +314,7 @@ This hypothetical example combines sequential and concurrent composition. Also n
 
 h2(#Service). Service
 
-A Finagle Service represents a service that handles remote proceduce calls, taking requests and giving back replies. It represents the service's "logic," not the network "plumbing" (but works well with other things that configure that plumbing). A Service is a function Req => Future[Rep] for some request and reply types.
+A Finagle Service represents a service that handles remote procedure calls, taking requests and giving back replies. It represents the service's "logic," not the network "plumbing" (but works well with other things that configure that plumbing). A Service is a function Req => Future[Rep] for some request and reply types.
 
 
abstract class Service[-Req, +Rep] extends (Req => Future[Rep]) @@ -327,7 +327,7 @@ We define both clients and servers in terms of Services. A Finagle client "imports" a Service from the network. Conceptually, a Finagle client has two parts:
    -
  • A function to use the Service: dispatch a Req and handle a Rep +
  • A function to use the Service: dispatch a Req and handle a Future[Rep]
  • Configuration of how to dispatch requests; e.g., as HTTP requests to port 80 of api.twitter.com
@@ -407,7 +407,9 @@ val server: Server = ServerBuilder() h2(#Filter). Filters -You can combine servers and clients. A simple proxy might look like this: +Filters transform services. They can provide service generic functionality. For example, you might have several services that should support rate limiting; you can write one rate-limiting filter and apply it to all your services. Filters are also good for decomposing a service into distinct phases. + +A simple proxy might look like this:
 class MyService(client: Service[..]) extends Service[HttpRequest, HttpResponse]
@@ -422,8 +424,6 @@ class MyService(client: Service[..]) extends Service[HttpRequest, HttpResponse]
 
 where rewriteReq and rewriteRes can provide protocol translation, for example.
 
-Filters transform services. They can provide service generic functionality. For example, you might have several services that should support rate limiting; you can write one rate-limiting filter and apply it to all your services. Filters are also good for decomposing a service into distinct phases.
-
 
 abstract class Filter[-ReqIn, +RepOut, +ReqOut, -RepIn]
   extends ((ReqIn, Service[ReqOut, RepIn]) => Future[RepOut])

From 9c9bb334f665617ddd303e10f27d0cae41c68ed6 Mon Sep 17 00:00:00 2001
From: Larry Hosken 
Date: Thu, 20 Sep 2012 11:26:25 -0700
Subject: [PATCH 09/12] moar JDOwens feedback. define "combinator". Say RPC.
 Clarify example's cache

---
 web/collections.textile | 2 +-
 web/finagle.textile     | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/web/collections.textile b/web/collections.textile
index 96a97827..1b799b9b 100644
--- a/web/collections.textile
+++ b/web/collections.textile
@@ -173,7 +173,7 @@ val result = res1 match {
 
 h1(#combinators). Functional Combinators
 
-Combinators are so-called because they are meant to be combined. The output of one function is often suitable as the input for another.
+Combinators are, roughly, functions that take other functions as parameters and are are meant to be combined. The output of one function is often suitable as the input for another.
 
 The most common use is on the standard data structures.
 
diff --git a/web/finagle.textile b/web/finagle.textile
index d6fd54db..4ed4c570 100644
--- a/web/finagle.textile
+++ b/web/finagle.textile
@@ -284,9 +284,9 @@ def isRateLimited(u: User): Future[Boolean] = {
 // Notice how you can swap this implementation out now with something that might
 // implement a different, more restrictive policy.
 
-// Check the cache to find out if user is rate-limited. We find out right
-// away by checking local cache. But we return a Future anyhow in case we
-// need to use a slower system later.
+// Check the cache to find out if user is rate-limited. This cache
+// implementation is just a Map, and can return a value right way. But we
+// return a Future anyhow in case we need to use a slower implementation later.
 def isLimitedByCache(u: User): Future[Boolean] =  Future.value(limitCache(u))
 
 // Update the cache
@@ -314,7 +314,7 @@ This hypothetical example combines sequential and concurrent composition. Also n
 
 h2(#Service). Service
 
-A Finagle Service represents a service that handles remote procedure calls, taking requests and giving back replies. It represents the service's "logic," not the network "plumbing" (but works well with other things that configure that plumbing). A Service is a function Req => Future[Rep] for some request and reply types.
+A Finagle Service represents a service that handles RPCs, taking requests and giving back replies. It represents the service's "logic," not the network "plumbing" (but works well with other things that configure that plumbing). A Service is a function Req => Future[Rep] for some request and reply types.
 
 
abstract class Service[-Req, +Rep] extends (Req => Future[Rep]) From 4ca3f4c96ea1eb03b25a5e1335b45acd735b8cbc Mon Sep 17 00:00:00 2001 From: Larry Hosken Date: Fri, 21 Sep 2012 09:44:41 -0700 Subject: [PATCH 10/12] tweak "combinator" defn, plagiarizing Marius --- web/collections.textile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web/collections.textile b/web/collections.textile index 1b799b9b..452ec725 100644 --- a/web/collections.textile +++ b/web/collections.textile @@ -173,9 +173,7 @@ val result = res1 match { h1(#combinators). Functional Combinators -Combinators are, roughly, functions that take other functions as parameters and are are meant to be combined. The output of one function is often suitable as the input for another. - -The most common use is on the standard data structures. +List(1, 2, 3) map squared applies the function squared to the elements of the the list, returning a new list, perhaps List(1, 4, 9). We call operations like map combinators. Their most common use is on the standard data structures. h2(#map). map From 4752e5b0e75271f669b5637ba5a3ae190eefe480 Mon Sep 17 00:00:00 2001 From: Larry Hosken Date: Wed, 26 Sep 2012 11:54:13 -0700 Subject: [PATCH 11/12] +link SO combinator qn. +examples, +gloss on examples. Fix bad "server" defn --- web/collections.textile | 2 +- web/finagle.textile | 163 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 149 insertions(+), 16 deletions(-) diff --git a/web/collections.textile b/web/collections.textile index 452ec725..9146697d 100644 --- a/web/collections.textile +++ b/web/collections.textile @@ -173,7 +173,7 @@ val result = res1 match { h1(#combinators). Functional Combinators -List(1, 2, 3) map squared applies the function squared to the elements of the the list, returning a new list, perhaps List(1, 4, 9). We call operations like map combinators. Their most common use is on the standard data structures. +List(1, 2, 3) map squared applies the function squared to the elements of the the list, returning a new list, perhaps List(1, 4, 9). We call operations like map combinators. (If you'd like a better defintion, you might like Explanation of combinators on Stackoverflow.) Their most common use is on the standard data structures. h2(#map). map diff --git a/web/finagle.textile b/web/finagle.textile index 4ed4c570..27d98c70 100644 --- a/web/finagle.textile +++ b/web/finagle.textile @@ -8,7 +8,7 @@ layout: post "Finagle":https://github.com/twitter/finagle is Twitter's RPC system. "This":http://engineering.twitter.com/2011/08/finagle-protocol-agnostic-rpc-system.html blog post explains its motivations and core design tenets, the "finagle README":https://github.com/twitter/finagle/blob/master/README.md contains more detailed documentation. Finagle aims to make it easy to build robust clients and servers. * "REPL":#repl -* "Futures":#Future: "Sequential composition":#futsequential, "Concurrent composition":#futconcurrent, "Composition Example":#combined_combinator_example +* "Futures":#Future: "Sequential composition":#futsequential, "Concurrent composition":#futconcurrent, "Composition Example: Cached Rate Limit":#combined_combinator_example_cache, "Composition Example: Thumbnail Fetcher":#combined_combinator_thumbnail * "Service":#Service * "Client Example":#client * "Server Example":#server @@ -32,7 +32,7 @@ scala> h2(#Future). Futures -Finagle uses com.twitter.util.Future[1] to express delayed operations. A Future is a handle for a value not yet available. Finagle uses Futures as return values for its asynchronous APIs. A synchronous API returns a result immediately. An asynchronous API does something that might take a while. For example, an HTTP request to some service on the internet might not return a value for half a second. You don't want your program's execution to block for half a second waiting for that request to finish. "Slow" APIs can return a Future right away and then "fill in" its value when it resolves. +Finagle uses com.twitter.util.Future[1] to encode delayed operations. A Future is a handle for a value not yet available. Finagle uses Futures as return values for its asynchronous APIs. A synchronous API waits for a result before returning; an asynchronous API does not. For example, an HTTP request to some service on the internet might not return a value for half a second. You don't want your program's execution to block for half a second waiting. "Slow" APIs can return a Future right away and then "fill in" its value when it resolves.
 val myFuture = MySlowService(request) // returns right away
@@ -48,12 +48,12 @@ For example to, write code that dispatches a request and then "handles" the resp
 
 
 val future = dispatch(req) // returns immediately, but future is "empty"
-future onSuccess { // when the future gets "filled"...
-  case reply => println(reply) // ...use the value it contains
+future onSuccess { reply => // when the future gets "filled", use its value
+  println(reply)
 }
 
-You can play with Futures in the REPL. This is a bad way to learn how you'll use them in real code, but can help with understanding the API. When you use the REPL, Promise is a handy class. It's a concrete subclass of the abstract Future class. You can use it to create a Future that has no value yet. +You can play with Futures in the REPL. This is a bad way to learn how you will use them in real code, but can help with understanding the API. When you use the REPL, Promise is a handy class. It's a concrete subclass of the abstract Future class. You can use it to create a Future that has no value yet.
 scala> import com.twitter.util.{Future,Promise}
@@ -88,6 +88,8 @@ res1: Int = 7
 scala>
 
+When you use Futures in real code, you normally don't call get; you use callback functions instead. get is just handy for REPL tinkering. +   h3. Sequential composition @@ -102,7 +104,7 @@ The most important Future combinator is flatMap[2]: def Future[A].flatMap[B](f: A => Future[B]): Future[B]
-flatMap sequences two futures. That is, it takes a Future and an asynchronous function and returns a Future. The method signature tells the story: given the successful value of a future, the function f provides the next Future. flatMap automatically calls f if/when the input Future completes successfully. The result of this operation is another Future that is complete only when both of these futures have completed. If either Future fails, the given Future will also fail. This implicit interleaving of errors allow us to handle errors only in those places where they are semantically significant. flatMap is the standard name for the combinator with these semantics. +flatMap sequences two futures. That is, it takes a Future and an asynchronous function and returns another Future. The method signature tells the story: given the successful value of a future, the function f provides the next Future. flatMap automatically calls f if/when the input Future completes successfully. The result of this operation is another Future that is complete only when both of these futures have completed. If either Future fails, the given Future will also fail. This implicit interleaving of errors allow us to handle errors only in those places where they are semantically significant. flatMap is the standard name for the combinator with these semantics. If you have a Future and you want apply an asynchronous API to its value, use flatMap. For example, suppose you have a Future[User] and need a Future[Boolean] indicating whether the enclosed User has been banned. There is an isBanned API to determine whether a User has been banned, but it is asynchronous. You can use flatMap: @@ -211,7 +213,7 @@ object Future { }
-collect takes a set of Futures of the same type, and yields a Future of a sequence of values of that type. This future is complete when all of the underlying futures have completed, or when any of them have failed. +collect takes a set of Futures of the same type, and yields a Future of a sequence of values of that type. This future is complete when all of the underlying futures have completed or when any of them have failed.
 scala> val f2 = Future.value(2)
@@ -223,8 +225,11 @@ f3: com.twitter.util.Future[Int] = com.twitter.util.ConstFuture@263bb672
 scala> val f23 = Future.collect(Seq(f2, f3))
 f23: com.twitter.util.Future[Seq[Int]] = Promise@635209178(...)
 
-scala> f23.get() sum
-res82: Int = 5
+scala> val f5 = f23 map (_.sum)
+f5: com.twitter.util.Future[Int] = Promise@1954478838(...)
+
+scala> f5.get()
+res9: Int = 5
 
 
@@ -268,9 +273,9 @@ res113: Int = 7 scala>
-  +  -h3. Composition Example +h3. Composition Example: Cached Rate Limit These combinators express operations typical of network services. This hypothetical code performs rate limiting (in order to maintain a local rate limit cache) concurrently with dispatching a request on behalf of the user to the backend: @@ -298,10 +303,15 @@ def getTimeline(cred: Credentials): Future[Timeline] = isLimitedByCache(cred.user) flatMap { case true => Future.exception(new Exception("rate limited")) case false => + + // First we get auth'd user then we get timeline. + // Sequential composition of asynchronous APIs: use flatMap val timeline = auth(cred) flatMap(getTimeline) val limited = isRateLimited(cred.user) onSuccess( setIsLimitedInCache(cred.user, _)) + // 'join' concurrently combines differently-typed futures + // 'flatMap' sequentially combines, specifies what to do next timeline join limited flatMap { case (_, true) => Future.exception(new Exception("rate limited")) case (timeline, _) => Future.value(timeline) @@ -310,11 +320,134 @@ def getTimeline(cred: Credentials): Future[Timeline] = }
-This hypothetical example combines sequential and concurrent composition. Also note how there is no explicit error handling other than converting a rate limiting reply to an exception. If any future fails here, it is automatically propagated to the returned Future. +This hypothetical example combines sequential and concurrent composition. Note that there is no explicit error handling other than converting a rate limiting reply to an exception. If any future fails here, it is automatically propagated to the returned Future. + +  + +h3. Composition Examples: Web Crawlers + +You've seen how to to use combinators with Futures, but might appreciate more examples. Suppose you have a simple model of the internet. It has HTML pages and images. Pages can link to images and link to other pages. You can fetch a page or an image, but the API is asynchronous. This fake API calls these "fetchable" things Resources: + +
+import com.twitter.util.{Try,Future,Promise}
+
+// a fetchable thing
+trait Resource {
+  def imageLinks(): Seq[String]
+  def links(): Seq[String]
+}
+
+// HTML pages can link to Imgs and to other HTML pages.
+class HTMLPage(val i: Seq[String], val l: Seq[String]) extends Resource {
+  def imageLinks() = i
+  def links = l
+}
+
+// IMGs don't actually link to anything else
+class Img() extends Resource {
+  def imageLinks() = Seq()
+  def links() = Seq()
+}
+
+// profile.html links to gallery.html and has an image link to portrait.jpg
+val profile = new HTMLPage(Seq("portrait.jpg"), Seq("gallery.html"))
+val portrait = new Img
+
+// gallery.html links to profile.html and two images
+val gallery = new HTMLPage(Seq("kitten.jpg", "puppy.jpg"), Seq("profile.html"))
+val kitten = new Img
+val puppy = new Img
+
+val internet = Map(
+  "profile.html" -> profile,
+  "gallery.html" -> gallery,
+  "portrait.jpg" -> portrait,
+  "kitten.jpg" -> kitten,
+  "puppy.jpg" -> puppy
+)
+
+// fetch(url) attempts to fetch a resource from our fake internet.
+// Its returned Future might contain a Resource or an exception
+def fetch(url: String) = { new Promise(Try(internet(url))) }
+
+ +Sequential Composition + +Suppose you wish to fetch a page's first image, given that page's URL. Perhaps you're making a site where users can post links to interesting pages. To help other users decide whether a link is worth following, you want to display a thumbnail of the linked-to page's first image. + +If you didn't know about combinators, you could still write a thumbnail-getting function: + +
+def getThumbnail(url: String): Future[Resource]={
+  val returnVal = new Promise[Resource]
+
+  fetch(url) onSuccess { page => // callback for successful page fetch
+    fetch(page.imageLinks()(0)) onSuccess { p => // callback for successful img fetch
+      returnVal.setValue(p)
+    } onFailure { exc => // callback for failed img fetch
+      returnVal.setException(exc)
+    }
+  } onFailure { exc => // callback for failed page fetch
+    returnVal.setException(exc)
+  }
+  returnVal
+}
+
+ +This version of the function works OK. Most of it consists of unwrapping Futures and then putting their contents into another Future. + +We want to get one page and then get one image from that page. If you want A and then B, that usually means sequential composition. Since our B is asynchronous, we want flatMap: + +
+def getThumbnail(url: String): Future[Resource] =
+  fetch(url) flatMap { page => fetch(page.imageLinks()(0)) }
+
+ +...with Concurrent Composition + +Fetching a page's first image is nice, but maybe we should fetch all of them and let the user choose their favorite. We could write a for loop to fetch them one after the other, but that could take a long time. We'd like to fetch them in parallel. If you want things to happen "in parallel," that usually means concurrent composition. So we use Future.collect to fetch all of the images: + +
+def getThumbnails(url:String): Future[Seq[Resource]] =
+  fetch(url) flatMap { page =>
+    Future.collect(
+      page.imageLinks map { u => fetch(u) }
+    )
+  }
+
+ +If this makes sense to you, then great. You might worry about the line page.imageLinks map { u => fetch(u) }: it uses map and the thing after the map returns a Future. Aren't we supposed to use flatMap when the next thing returns a Future? But notice that the thing before the map isn't a Future; it's a collection. collection map function returns a collection; we use Future.collect to gather that collection of Futures into one Future. + +Concurrent + Recursion + +Instead of fetching a page's images, we might fetch the other pages that it links to. If we then recurse on those, we have a simple web crawler. + +
+// Return
+def crawl(url: String): Future[Seq[Resource]] =
+  fetch(url) flatMap { page =>
+    Future.collect(
+      page.links map { u => crawl(u) }
+    ) map { pps => pps.flatten }
+}
+
+crawl("profile.html")
+   ...hangs REPL, infinite loop...
+Ctrl-C
+Execution interrupted by signal.
+
+
+scala>
+// She's gone rogue, captain! Have to take her out!
+// Calling Thread.stop on runaway Thread[Thread-93,5,main] with offending code:
+// scala> crawl("profile.html")
+
+ +In practice, this web crawler is not so useful: we didn't tell it when to stop crawling; it merrily re-fetches resources that it just fetched a moment earlier. h2(#Service). Service -A Finagle Service represents a service that handles RPCs, taking requests and giving back replies. It represents the service's "logic," not the network "plumbing" (but works well with other things that configure that plumbing). A Service is a function Req => Future[Rep] for some request and reply types. +A Finagle Service represents a service that handles RPCs, taking requests and giving back replies. A Service is a function Req => Future[Rep] for some request and reply types.
abstract class Service[-Req, +Rep] extends (Req => Future[Rep]) @@ -345,7 +478,7 @@ We also talk about Finagle "filters." A filter sits between services, modifying h2(#client). Client -A Finagle client is defined in terms of a Service and some configuration about how to send data over the network. A simple HTTP client might like: +A Finagle client "imports" a Service. It has some configuration about how to send data over the network. A simple HTTP client might look like:
 import org.jboss.netty.handler.codec.http.{DefaultHttpRequest, HttpRequest, HttpResponse, HttpVersion, HttpMethod}
@@ -356,7 +489,7 @@ import com.twitter.finagle.http.Http
 // Don't worry, we discuss this magic "ClientBuilder" later
 val client: Service[HttpRequest, HttpResponse] = ClientBuilder()
   .codec(Http())
-  .hosts("twitter.com:80")
+  .hosts("twitter.com:80") // If >1 host, client does simple load-balancing
   .hostConnectionLimit(1)
   .build()
 

From 4226ce4b514d6726234849050ea4e41f84f00e45 Mon Sep 17 00:00:00 2001
From: Larry Hosken 
Date: Fri, 28 Sep 2012 11:00:47 -0700
Subject: [PATCH 12/12] clearer. ServerBuilder example that compiles

---
 web/finagle.textile | 22 ++++++++++++----------
 1 file changed, 12 insertions(+), 10 deletions(-)

diff --git a/web/finagle.textile b/web/finagle.textile
index 27d98c70..8baf708e 100644
--- a/web/finagle.textile
+++ b/web/finagle.textile
@@ -643,20 +643,22 @@ val client: Service[HttpRequest, HttpResponse] = ClientBuilder()
   .build()
 
-This builds a client that load balances over the 3 given hosts, establishing at most 1 connection per host, and giving up only after 2 failures. Stats are reported to "ostrich":https://github.com/twitter/ostrich. The following builder options are required (and their presence statically enforced): hosts or cluster, codec and hostConnectionLimit. +This builds a client that load balances over the three given hosts, establishing at most one connection per host, and giving up only after two failures. Stats are reported to "ostrich":https://github.com/twitter/ostrich. The following builder options are required (and their presence statically enforced): hosts or cluster, codec and hostConnectionLimit. + +Similarly, you can use a ServerBuilder to make your service "listen" for incoming requests:
-val myService: Service[HttpRequest, HttpResponse] = // provided by the user
-ServerBuilder()
-  .codec(Http)
-  .hostConnectionMaxLifeTime(5.minutes)
-  .readTimeout(2.minutes)
-  .name("myHttpServer")
-  .bindTo(new InetSocketAddress(serverPort))
-  .build(myService)
+val service = new ParrotServerService.Service(parrotServer, new TBinaryProtocol.Factory()) // construct instance of your Finagle service
+val  server = ServerBuilder()
+  .bindTo(new InetSocketAddress(port))
+  .codec(ThriftServerFramedCodec())
+  .name("parrot thrift server")
+//  .hostConnectionMaxLifeTime(5.minutes)
+//  .readTimeout(2.minutes)
+  .build(service)
 
-This will serve, on port serverPort an HTTP server which dispatches requests to myService. Each connection is allowed to stay alive for up to 5 minutes, and we require a request to be sent within 2 minutes. The required ServerBuilder options are: name, bindTo and codec. +This will serve, on port port, a Thrift server which dispatches requests to service. If we un-comment the hostConnectionMaxLifeTime line, each connection would be allowed to stay alive for up to 5 minutes. If we un-comment the readTimeout line, then we require a request to be sent within 2 minutes. The required ServerBuilder options are: name, bindTo and codec. fn1. Careful, there are other "Future" classes out there. Don't confuse twitter.com.util.Future with scala.actor.Future or java.util.concurrent.Future!