Quickstart¶
In this section we’ll use Finagle to build a very simple HTTP server that is also an HTTP client — an HTTP proxy. We assume that you are familiar with Scala (if not, may we recommend Scala School).
The entire example is available, together with a self-contained script to launch sbt, in the Finagle git repository:
$ git clone https://github.com/twitter/finagle.git
$ cd finagle/doc/src/sphinx/code/quickstart
$ ./sbt compile
Setting up SBT¶
We’ll use sbt to build our project. Finagle is published to Maven Central,
so little setup is needed: see the build.sbt
in the quickstart
directory.
We no longer support scala
2.10
.
name := "quickstart" version := "1.0" libraryDependencies += "com.twitter" %% "finagle-http" % "24.2.0"
Any file in this directory will now be compiled by sbt
.
A minimal HTTP server¶
We’ll need to import a few things into our namespace.
import com.twitter.finagle.{Http, Service}
import com.twitter.finagle.http
import com.twitter.util.{Await, Future}
Service
is the interface used to represent a server or a client
(about which more later). Http
is Finagle’s HTTP
client and server.
Next, we’ll define a Service
to serve our HTTP requests:
val service = new Service[http.Request, http.Response] {
def apply(req: http.Request): Future[http.Response] =
Future.value(
http.Response(req.version, http.Status.Ok)
)
}
Services are functions from a request type (com.twitter.finagle.http.Request
)
to a Future
of a response type (com.twitter.finagle.http.Response
).
Put another way: given a request, we must promise a response some
time in the future. In this case, we just return a trivial HTTP-200
response immediately (through Future.value
), using the same
version of HTTP with which the request was dispatched.
Now that we’ve defined our Service
, we’ll need to export
it. We use Finagle’s Http server for this:
val server = Http.serve(":8080", service)
Await.ready(server)
The serve
method takes a bind target (which port to expose the
server) and the service itself. The server is responsible for
listening for incoming connections, translating the HTTP wire protocol
into com.twitter.finagle.http.Request
objects, and translating
our com.twitter.finagle.http.Response
object back into its wire
format, sending replies back to the client.
The complete server:
import com.twitter.finagle.{Http, Service}
import com.twitter.finagle.http
import com.twitter.util.{Await, Future}
object Server extends App {
val service = new Service[http.Request, http.Response] {
def apply(req: http.Request): Future[http.Response] =
Future.value(
http.Response(req.version, http.Status.Ok)
)
}
val server = Http.serve(":8080", service)
Await.ready(server)
}
We’re now ready to run it:
$ ./sbt 'runMain Server'
Which exposes an HTTP server on port 8080 which
dispatches requests to service
:
$ curl -D - localhost:8080
HTTP/1.1 200 OK
Using clients¶
In our server example, we define a Service
to respond to requests.
Clients work the other way around: we’re given a Service
to use. Just as we
exported services with the Http.serve
, method, we can import them
with a Http.newService
, giving us an instance of
Service[http.Request, http.Response]
:
val client: Service[http.Request, http.Response] = Http.newService("www.scala-lang.org:80")
client
is a Service
to which we can dispatch an http.Request
and in return receive a Future[http.Response]
— the promise of an
http.Response
(or an error) some time in the future. We furnish
newService
with the target of the client: the host or set of hosts
to which requests are dispatched.
val request = http.Request(http.Method.Get, "/")
request.host = "www.scala-lang.org"
val response: Future[http.Response] = client(request)
Now that we have response
, a Future[http.Response]
, we can register
a callback to notify us when the result is ready:
Await.result(response.onSuccess { rep: http.Response => println("GET success: " + rep) })
Completing the client:
import com.twitter.finagle.{Http, Service}
import com.twitter.finagle.http
import com.twitter.util.{Await, Future}
object Client extends App {
val client: Service[http.Request, http.Response] = Http.newService("www.scala-lang.org:80")
val request = http.Request(http.Method.Get, "/")
request.host = "www.scala-lang.org"
val response: Future[http.Response] = client(request)
Await.result(response.onSuccess { rep: http.Response => println("GET success: " + rep) })
}
which in turn is run by:
$ ./sbt 'runMain Client'
...
GET success: Response("HTTP/1.1 Status(200)")
...
Putting it together¶
Now we’re ready to create an HTTP proxy! Notice the symmetry above:
servers provide a Service
, while a client uses it. Indeed, an HTTP
proxy can be constructed by just replacing the service we defined in the Server
example with one that was imported with a Http.newService
:
import com.twitter.finagle.{Http, Service}
import com.twitter.finagle.http.{Request, Response}
import com.twitter.util.Await
object Proxy extends App {
val client: Service[Request, Response] =
Http.newService("twitter.com:80")
val server = Http.serve(":8080", client)
Await.ready(server)
}
And we can run it and dispatch requests to it (be sure to shutdown the Server example from earlier):
$ ./sbt 'runMain Proxy' &
$ curl --dump-header - --header "Host: twitter.com" localhost:8080
HTTP/1.1 301 Moved Permanently
content-length: 0
date: Wed, 01 Jun 2016 21:26:57 GMT
location: https://twitter.com/
...