1. 程式人生 > >Airframe HTTP: A Minimalist Approach For Building Web Services in Scala

Airframe HTTP: A Minimalist Approach For Building Web Services in Scala

airframe-http is the latest addition to Airframe, lightweight building blocks for Scala. airframe-http is a library for mapping HTTP requests to functions in Scala. This is not a web framework, rather, it can be used as a thin-wrapper over HTTP server libraries, such as Finagle.

To define REST APIs on top of a low-level HTTP server library like Finagle, we usually needed an additional web framework, such as

Finatra, Finch, etc., each of them has its own coding style. For example, Finatra uses Google Guice for injecting necessary modules. Finch’s focus is helping people who prefer purely-functional style, so this does not need to be everyone’s choice.

Airframe has a built-in dependency injection library, which redesigned Google Guice from scratch to be more Scala-friendly

. So using Finatra, which depends on Google Guice, is a drawback for me. That is why I built airframe-http and its binding for Finagle (airframe-http-finagle) to provide the best practice for building web services using Airframe.

Defining Web APIs as Scala Functions

To define web service APIs, we basically need a set of functions which receive HTTP requests, then return HTTP responses. The content body of HTTP requests and responses often has JSON data (or MessagePack for efficiency), representing some message objects. Manually writing mappings between HTTP requests/response and message objects (e.g., Scala case classes) are cumbersome, so we need to simplify this mapping process.

In airframe-http we use Endpoint annotation to define a mapping from an HTTP request path to a function in Scala. This example shows how to map http request to /user/:name to a function call getUser(name):

No DSL is required other than Endpoint annotations. The above example is just a regular Scala trait that is independent from any web framework, so we can write test code for the implementation without running web servers. When we use this trait with airframe-http, it will translate HTTP requests to corresponding function calls in Scala, and returns its result as JSON HTTP responses.

Starting A Finagle HTTP Server

airframe-http-finagle is an extension of airframe-http to use Finagle as the web-server backend. airframe-http-finagle is available since Airframe version 0.66:

To start an HTTP server that uses Finagle as a backend implementation, you need to define Router and Airframe design as follows:

Building a FinagleServer instance will start up a server thread and it will automatically terminate if Airframe session finishes. To add more REST endpoints, you can use Router.add[X] method.

That’s it! You can find more advanced examples in the documentation. You might be wondering why existing web frameworks (e.g., Finatra, Finch, akka-http, etc.) needed to define complex DSLs to do such HTTP request mapping. In the following section, we will see why airframe-http can provide such a simple interface without creating DSL on top of Scala.

A Secret Source: Aiframe Surface

To map HTTP request parameters to function calls, we need to know function argument names and their types. However, during compilation from Scala code into JVM byte code, these argument names and types will be removed (type erasure). So existing web frameworks (e.g., Finatra, Finch, akka-http, etc.) needed to define DSLs to complement the information that will be lost after the compilation. For example, annotations to function arguments are frequently used to tell the frameworks about the method argument names and types.

What we can do to avoid such heavy annotation usage? The answer is Scala Signatures (ScalaSigs), which are the detailed type information embedded by Scala compiler as hidden data inside class files. Scala signatures retain the original function argument names and types within class files. By reading Scala signatures, we can know even generic type parameters, which will be lost after type erasure. For example, Seq[User] type will be compiled to just Seq[java.lang.Object] inside the class file, but by looking at the Scala signature, we can find its original type Seq[User]. We can also read method argument names and types as well.

airframe-surface is a library for reading Scala signatures by using Scala reflection (For Scala.js, it uses Scala Macros to extract type information). airframe-surface enables reading detailed type information from classes and methods. This information makes easier to define mappings between HTTP requests to Scala functions.

Mapping HTTP Requests to Method Calls

airframe-http reads the surface of a given Scala class to check the method signatures. After finding methods that have Endpoint annotations, we read method arguments to define HTTP request routing table.

HTTP request/response data are string (or JSON) values, but method arguments can be Int, Double, Boolean, or more complex case classes. To perform data conversion between these types, we usually needed to use some serialization libraries, such as Jackson, play-json, circe, etc. In airframe-http, we even don’t need to think about selecting one of such serialization libraries.

Thanks to airframe-surface, we already know the source data types (Strings or JSON in HTTP requests) and target data types (of method arguments). So we only need to provide data conversion rules for all possible types. airframe-codec contains a collection of such conversion rules for regular Scala types, including primitive types, Scala collections, case objects, etc. So for almost all data classes, we can generate data conversion codecs (e.g., JSON -> case class mappings) by combinding these pre-defined rules.

Here are the detailed steps on how airframe-http calls methods from HTTP requests:

  • Given an HTTP request, airframe-http finds a matching method from the routing table.
  • For each function argument of the targetr method, it will find a MessagePack codec, which translates given MessagePack data into the corresponding argument type data.
  • Convert the HTTP request path parameters and query parameters into MessagePack Map value (e.g., Map(“name” -> “xxx”)). If the HTTP request body contains JSON data, it will be converted to MessagePack as well.
  • By using the prepared MessagePack codecs, generate method call argument values.
  • ControllerProvider will find an instance of the target Web API class registered in the Airframe Session. Then call the method of the instance using the method arguments generated in the above step.
  • ResponseHandler reads the method return type, and finds an appropriate MessageCodec to convert the return value into JSON format. First the returned objects will be converted to MessagePack by using the MessageCodec, then converted into JSON data.

This HTTP request mapping process might look intimidating, but individual steps are implemented by using small libraries (e.g., airframe-surface, airframe-codec, airframe-json, etc.) that are already available in Airframe.

Use Cases & Future Direction

airframe-http is designed to be an web-framework agonistic library, so it’s possible to create more adapters not only for Finagle, but also for other web frameworks. A reason we created airframe-http-finagle first is that Finagle is already a matured library, and it has been used in our production without any major problems.

A practical use case of airframe-http would be creating a lightweight web server for micro-services. In this context airframe-http is simple enough and can save the learning cost, compared to using existing feature-rich web frameworks.

We also understand that web frameworks need to address various types of problems (e.g., authentication, complex filtering, HTML rendering, request rate control, etc.). In this sense, airframe-http is still in an early phase, so it will not be an immediate replacement of major web frameworks (e.g., akka-http, Play!, Finatra, etc.). Because of time constraint, we may not be able to add rich features that exist in other web frameworks, but we highly appreciate contributions (e.g., PR, bug reports, fixes to documentations, etc.) to improve the quality of the code.

Airframe will continue to be lightweight building blocks for Scala. airframe-http is just one of the examples to show the power of such building blocks. We are planning to add more modules to Airframe that will be helpful to simplify your Scala programming!