Getting Started With HTTP Middleware in Kitura

Middleware is a popular way to to handle incoming server-side requests and outgoing responses. In this tutorial, you’ll learn how to add middleware to a REST API that’s built in Swift and uses the Kitura framework. You’ll learn how to add CORS (Cross-Origin Resource Sharing) policies, custom logic and authentication to the routes of an app that knows the meaning of life. By David Okun.

Leave a rating/review
Download materials
Save for later
Share

As Swift on the server continues to mature, many popular frameworks are adopting industry standards to handle incoming requests and outgoing responses. One of the most popular ways to handle traffic is by implementing middleware, which intercepts requests and performs logic that you define on specific routes.

In this tutorial, you’ll take a hands-on approach to using middleware in a REST API. You will:

  • Enable cross-origin traffic by adding CORS to your router.
  • Use middleware to authenticate a HTTP(s) request.
  • Ensure that your API properly understands the meaning of life!

You’ll follow test-driven development principles throughout this tutorial by starting with routes that behave incorrectly, and adding the needed HTTP Route and middleware logic that you need to resolve the issues.

This tutorial uses Kitura. However, the concepts of middleware and HTTP routing work in the same manner in Vapor, as well as many other Swift server frameworks. From a technical standpoint, here’s what you’ll use throughout this tutorial:

  • Kitura 2.7 or higher
  • macOS 10.14 or higher
  • Swift 5.0 or higher
  • Xcode 10.2 or higher
  • Terminal

Getting Started

To start, click on the Download Materials button at the top or bottom of this page to download the projects that you’ll use throughout this tutorial. Open Terminal and navigate the starter project folder.

You’ll notice the lack of a .xcodeproj file in this folder, and that’s a-OK! Enter the following commands in Terminal:

swift package generate-xcodeproj
xed .

This will pull all the dependencies needed to run the project and open Xcode.

Note: If you are looking for a great tutorial on how to use Swift Package Manager, check out here.

In Xcode, build and run your project by pressing Command-R. Check the Xcode console and you should see a server listening on port 8080. Open a web browser, and navigate to http://localhost:8080. You should see Kitura’s “Hello, World!” page:

kitura hello world page

You’re ready to dive right into the middle of this tutorial!

First, revisit some core concepts of HTTP.

The Request/Response Model

The server you’ll build throughout this tutorial is responsible for handling a request made from a client, then responding appropriately based on the content of that request. In this tutorial, you will only use cURL commands from Terminal to interact with your server, but these commands could come just as easily from an iOS or Android app. :]

What makes up a request?

a. A domain or host (e.g., www.raywenderlich.com, localhost:8080, or even 127.0.0.1:8080)

b. A path (e.g., /users)

c. Query parameters (e.g., ?username=ray&id=42)

  1. Any HTTP request must have an address, which contains:
  2. A method must be specified, (e.g., GET, POST, or OPTIONS).
  3. All requests can have headers, which you can think of as metadata for your request (e.g., {"Origin": "www.raywenderlich.com"}).
  4. For certain methods, a body must be specified, which you usually serialize into JSON.

When your server creates a response, it needs to specify the following:

  1. A status code between 200 and 599 that indicates the result of the request.
  2. A set of headers as response metadata.
  3. A body, which is usually in text, JSON, or another data type.

Later in this tutorial, you are going to write a server that responds to a request validating your interpretation of the meaning of life. Using cURL from Terminal, your request will look like this:

curl -X POST \
  http://localhost:8080/raze \
  -H 'content-type: application/json' \
  -H 'origin: www.raywenderlich.com' \
  -d '{"meaningOfLife": 42}'

When you write your handler for this request at first, this request won’t quite work out of the box. However, you’ll use middleware to inspect the headers and body of the request to make it work, until you get this response:

Yes, the meaning of life is indeed 42!

Before you start writing code, review what happens when you send off your request from your client.

HTTP Routing

Consider two components in your server: the HTTP router and the HTTP route. The router is the object responsible for handling incoming requests and routing them to the appropriate route. When you’ve set up a route on your server, the router will hand it off to the handler function that is responsible for sending the response back to the client.

Assume that you are working on a team of developers who have already put a lot of work into their existing routes. You join the team and your first task is to validate data coming into the /test route — all requests that are routed to /test must have the Access-Control-Allow-Origin header specified as true. This is what the route looks like:

func corsHandler(request: RouterRequest, response: RouterResponse, next: () -> Void) {
  if response.headers["Access-Control-Allow-Origin"] == "false" {
    response.status(.badRequest).send("Bad origin")
  } else {
    response.status(.OK).send("Good origin")
  }
}

One idea would be to ask everyone who connects to this API very nicely to specify this header in their request, but what if someone doesn’t get the message? Furthermore, what if you could get your hands on the request without having to mess with any of the existing code in the route handler? This is the crux of this tutorial — middleware to the rescue.

Middleware

Simply put, middleware is code that runs in the middle of your router and your route handler. Whenever you write middleware, you can intercept a request on its way to its specified route, and you can do whatever you need to do with the request at that point. You can even choose to send back a response early if the request doesn’t meet your needs, ignoring the route handler altogether.

In Kitura, middleware is achieved through writing a class or struct that adheres to a protocol, and then by registering the middleware on specific routes.

The example read above, in terms of middleware, is a common requirement called CORS, an acronym for Cross Origin Resource Sharing. If you wanted to roughly write your own code to fulfill this need, you could write middleware that looks like this:

class RazeMiddleWare: RouterMiddleware {
  public func handle(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) throws {
    request.headers.append("Access-Control-Allow-Origin", value: "true")
    next()
  }
}

And then you could add it to the /test route, as required previously, like so:

let middleware = RazeMiddleware()
router.all("/test", middleware: middleware)
Note: Remember how you were required to handle POST requests to /test? Instead of specifying only this method, you are choosing to handle all methods used on this route. The next section is going to explain why.

Contributors

Cesare Rocchi

Tech Editor

Adriana Kutenko

Illustrator

Shai Mishali

Final Pass Editor

Tim Condon

Team Lead

Over 300 content creators. Join our team.