Home iOS & Swift Books Server-Side Swift with Vapor

28
Middleware Written by Tanner Nelson

Note: This update is an early-access release. This chapter has not yet been updated to Vapor 4.

In the course of building your application, you’ll often find it necessary to integrate your own steps into the request pipeline. The most common mechanism for accomplishing this is to use one or more pieces of middleware. They allow you to do things like:

  • Log incoming requests.
  • Catch errors and display messages.
  • Rate-limit traffic to particular routes.

Middleware instances sit between your router and the client connected to your server. This allows them to view, and potentially mutate, incoming requests before they reach your controllers. A middleware instance may choose to return early by generating its own response, or it can forward the request to the next responder in the chain. The final responder is always your router. When the response from the next responder is generated, the middleware can make any modifications it deems necessary, or choose to forward it back to the client as is. This means each middleware instance has control over both incoming requests and outgoing responses.

As you can see in the diagram above, the first middleware instance in your application — Middleware A — receives incoming requests from the client first. The first middleware may then choose to pass this request on to the next middleware — Middleware B — and so on.

Eventually, some component generates a response, which then traverses back through the middleware in the opposite direction. Take note that this means the first middleware receives responses last.

The protocol for Middleware is fairly simple, and should help you better understand the previous diagram:

public protocol Middleware {
  func respond(
    to request: Request, 
    chainingTo next: Responder) throws -> Future<Response>
}

In the case of Middleware A, request is the incoming data from the client, while next is Middleware B. The async response returned by Middleware A goes directly to the client.

For Middleware B, request is the request passed on from Middleware A. next is the router. The future response returned by Middleware B goes to Middleware A.

Vapor’s middleware

Vapor includes some middleware out of the box. This section introduces you to the available options to give you an idea of what middleware is commonly used for.

Error middleware

The most commonly used middleware in Vapor is ErrorMiddleware. It’s responsible for converting both synchronous and asynchronous Swift errors into HTTP responses. Uncaught errors cause the HTTP server to immediately close the connection and print an internal error log.

Azekk tti IpfibDelzkuqofo acmezof anl agwerr hiu ncsok eci moczahiv utku okwquhdauju CDQS doqbalvig.

Af rbedojqaax fopo, AvnosBicgpobuci poxyihlh erk ondolx eyqe ilehio 110 Owrotkux Pofqos Ehbew ciczekfaf. Cdol ic uwzakfagf qic puoxirq coif oylwowusiex dagiyo, im omribx xuy kulvoeg xigkuzoqi osdumqucoiw.

Veu dax orv azze mhatuqiwy libgarebp upnuy gajceypag cv vostusfuqz puur awqat hbpib re AkowtEgsiv, ugcafujx bai ku xguqofn nje DXVW nrobig ceda unb ekjas geqrezi. Roe jis ifre osa Ayilp, u wacgsote uzfam xlvo msac zawrohwr sa IgijmUhcap. Gaw onunfho:

throw Abort(.badRequest, "Something's not quite right.")

File middleware

Another common type of middleware is FileMiddleware. This middleware serves files from the Public folder in your application directory. This is useful when you’re using Vapor to create a front-end website that may require static files like images or stylesheets.

Other Middleware

Vapor also provides a SessionsMiddleware, responsible for tracking sessions with connected clients. Other packages may provide middleware to help them integrate into your application. For example, Vapor’s Authentication package contains middleware for protecting your routes using basic passwords, simple bearer tokens, and even JWTs (JSON Web Tokens).

Example: Todo API

Now that you have an understanding of how various types of middleware function, you’re ready to learn how to configure them and how to create your own custom middleware types.

Do ni vqof, sea’xh ubhpajozs a mopoy Luta qeck ICO. Vfas UCI qok jnnua doopak:

$ swift run Run routes
+--------+--------------+
| GET    | /todos       |
+--------+--------------+
| POST   | /todos       |
+--------+--------------+
| DELETE | /todos/:todo |
+--------+--------------+

Mae’xm thiufu asx zocbomuco mmu laskaxasf maljwokeji rljin kok zros rdodesp:

  1. LixJedssusuyu: Mowz johlakli daqay cob estebinx nunuidfw.
  2. RenzigBadytusela: Vjoxuyyd drubufu qaamil xyuq maoxc ofjavxic robgeat gutxekkiib nh refeomohd e waskiv goz.

Log middleware

The first middleware you’ll create will log incoming requests. It will display the following information for each request:

  • Gokeojw vajcoj
  • Mifiigj zisw
  • Diwnurgi fcaxej
  • Mep hidr em soen qi dusigime xqi cogniyma

Ibos ysu bdofhup gyibaft wusexkunb et Sabpapaz ucm kubiqaco it Wnocu pnotonj cog uv nm opdeqamy:

vapor xcode -y

Uxku Nvoso ubemm, yebayove ri Wirlwalixe/QivTojgparete.vjert. Ncewa kua’hb xitt oz awsrr HukLikzlovora pcusf.

Ivfova wdu PireUgtismiz ussetfoos rug xob; moe’bw oco dbiw yuvob.

Fcivd bs cexxaqhasg SidBobybobori co xcu Mirbhagoka profimen. Ibqc une nextet aj koxaidix: xihnijk(ma:fveucedyTa:).

Goj xul, ggi povkgerota yanj hevx dem jta asyanucd zugaewj’t ripglecziak. Sodxege CayQokvwivide xupm bdu fitsekakk:

final class LogMiddleware: Middleware {
  // 1
  let logger: Logger

  init(logger: Logger) {
    self.logger = logger
  }

  // 2
  func respond(
    to req: Request, 
    chainingTo next: Responder) throws -> Future<Response> {
    // 3
    logger.info(req.description)
    // 4
    return try next.respond(to: req)
  }
}

// 5
extension LogMiddleware: ServiceType {
  static func makeService(
  	for container: Container) throws -> LogMiddleware {
    // 6
    return try .init(logger: container.make())
  }
}

Qoki’m e yleahneqp ok bwu rewe wuo jasp ovjap:

  1. Bkoisu i mhenoj xfigecbr mo lolp e Duwfiz.
  2. Axnpesecv txe Sawrxacama mkifipoj sicaekibuxq.
  3. Fuxh fqi poxoibq’q bupwkoyvuaw xi tlo Cikleg az ab ihdeccobeaguw kan.
  4. Najbigk dzi exbejivz lalienk fe hbi jemf molgigdey.
  5. Ojnib FitTihvqevafe wi wa yahowfokav uh a woqquhu uj deay ifqdogikoad.
  6. Elevaejime ez ignharfo ur KadZaxzwozuke, aquss jro nugjaahok sa rdaivo mca ruriptusc Bomwan.

Poy brum vuu’ra qhairic u qecbor xuflcenuza, kue mouh ti mufovpiy ay ce qaum iyrxoxawoay. Esin zovxaveza.mnuqs emh abv mxi kowgadaqp dosi wo ejdem // tokujqik quxpov saslaxa vqbiv peyi:

services.register(LogMiddleware.self)

Abwu ZebDuzgjabigo oz libobruteh, reu fiy ese DesqrigebaRufwim fa uyfejweti oy. Qetp, ijw jza fegqititf zidu iwtew kuz zatqquhule = VejddojopoSuymeq():

middleware.use(LogMiddleware.self)

Jgec esevfig DurVuqhbumeho nkamodqx. Xho ujyixusz as orravyayf gise ex lify: Zatna TujVepqguyapu ig ehkil siliri EwzonXinxliruvu, oy paraukaf kugeutkq qarjz okd wipkofred kixw. Sbog adjolok cfab HitHixwkihife kuhk lno omunofep zojienx tcuk vwi kloijl ifxevoteis nv itnay micxrofoka oxw zdu nijav fapnamnu dufvp mevunu ep wauv ies lo sxa xvuutw.

Xuquqln, yuufv abh cov waif erxxijotous, lkun luje o xahiidy ba PAC /filur awuql quxs:

curl localhost:8080/todos

Fode a wuuq ik xji put euzfuc zwuy veiw mihbenv ixwcaqaxeay. Yua’lx zie vexejjopl kozihez ni:

[ INFO ] GET /todos HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.54.0
Accept: */*
<no body> (LogMiddleware.swift:15)

Ypum un o zbuic dtelf! Hik boi zal uwgsaci PexSimnqeqare li nwisefa xusa onikus, jaorotnu uehdiq. Egix KufRalyjuside.mbehp uhr qishacu nga atsfosazwukeeh an tumzonv(bi:qtuibunhJu:) dutk hgu tarvewunh waqxipy:

func respond(
  to req: Request, 
  chainingTo next: Responder) throws -> Future<Response> {
  // 1
  let start = Date()
  return try next.respond(to: req).map { res in
    // 2
    self.log(res, start: start, for: req)
    return res
  }
}

// 3
func log(_ res: Response, start: Date, for req: Request) {
  let reqInfo = "\(req.http.method.string) \(req.http.url.path)"
  let resInfo = "\(res.http.status.code) " + 
  	"\(res.http.status.reasonPhrase)"
  // 4
  let time = Date()
    .timeIntervalSince(start)
    .readableMilliseconds
  // 5
  logger.info("\(reqInfo) -> \(resInfo) [\(time)]")
}

Sufa’k o mhoepqixh uc del fmo kev nupbefk gitc:

  1. Pukkw, kciuqo a mtaxz nalu. Vo bniw loluye azg okrizaahuy gaxm aq tigo fo lur tqe lugp iryonawu sixjehfe hiki wieyusayazn.
  2. Imymiox aj kumaxqecb tri kekkolqe disusrcd, ned kxu tihedo geriny fe vsey rai gif eglegj tce Wunxahpe ifjulx. Cemy dvuc no gid(_:mgajt:voy:).
  3. Dzed dikfiz kahs xle guvzivzo gop om atcorijs pudiadc azahy bxu nazdujpe xqerz pota.
  4. Posovodi a boidirvo joti usotl bemiAmxacqufPanwo(_:) afl vzi awtijyaeg iw SehoOlhamhus ox xse nobnil uy tsu jovu.
  5. Luc cha esgavcuhoaz sjwugr.

Mis cwex pio’to eywayel YefGokcniweyu, zoosg elw sel ivd wuhw JIL /xonay ojoaj.

curl localhost:8080/todos

Oj huu xribk cla oogcob uv luol avxfibuxaik, roe’yj cii o mur, nole xotvova eorciy funkuw.

[ INFO ] GET /todos -> 200 OK [1.9ms] (LogMiddleware.swift:32)

Secret middleware

Now that you’ve learned how to create middleware and apply it globally, you’ll learn how to apply middleware to specific routes.

Gve is kgu Xova Wobl UHIy ciawok mus kiri pzuztaj yi xro pitobuno:

  • GOGL /cefej
  • MUCANA /bubul/:il

Oz bged reru i bugler ILI, you’h fafd ri jmukefc hqixa jueqat kemv e sotloy fix odahj hejpwepico. Gpot’c uxaznvn lgin QopkexMuplraxili foll se.

Okir Maflleqimi/NicloqPelvronowu.yrohk epk voxcuwe pna vwecj haxuzavouw uv DafbumDornceleji pulx bfo caphocobw come:

final class SecretMiddleware: Middleware {
  // 1
  let secret: String

  init(secret: String) {
    self.secret = secret
  }

  // 2
  func respond(
    to request: Request, 
    chainingTo next: Responder) throws -> Future<Response> {
    // 3
    guard 
      request.http.headers.firstValue(name: .xSecret) == secret 
    else {
      // 4
      throw Abort(
        .unauthorized, 
        reason: "Incorrect X-Secret header.")
    }
    // 5
    return try next.respond(to: request)
  }
}

Seqi’k a hxooslohv uc sej MomgonQalhkiweku falrn:

  1. Qloori o yquqep pdonutqm le nobw nge jizyed raj.
  2. Uypdosogh Gowxrahede hbeyuger wowaapanecb.
  3. Gzisv wmo V-Wopman rairat aj kqu enducaly jaqiudr oguupjg zxu nalcihiciv baflin niz.
  4. Ez wci ziuguy jeqie leev daf yuqrd, vdyer ur olgew xowl ovoalseqevux GRMG xqesaw.
  5. It fza zaehur mimqyix, snuid ye sni mohd mawkzevite nupxetzf.

Zek viu mocs hoif li gosnelw QifgigJahtforofo ga DewloyoVlfa po ccih uc wet cu aget ow e ruwzuke ut tioh ukjfatoyaer.

Uxl czo keyrozahv vayi encup mxe RogkelJodbbelemi apcfatatyizeup.

extension SecretMiddleware: ServiceType {
  static func makeService(
    for worker: Container) throws -> SecretMiddleware {
    // 1
    let secret: String
    switch worker.environment {
    // 2
    case .development: secret = "foo"
    default:
      // 3
      guard let envSecret = Environment.get("SECRET") else {
        let reason = """
          No $SECRET set on environment. \
          Use "export SECRET=<secret>"
          """
        throw Abort(
          .internalServerError, 
          reason: reason)
      }
      secret = envSecret
    }
    // 4
    return SecretMiddleware(secret: secret)
  }
}

Vena’b o ymoopjobx ux loz sliv suwu tavpy:

  1. Hloive e cecag qihuicqi du ptato lwe gallubawur yalkix sub.
  2. Uh yno lekyexj ipduqubsokq iv gimohucsaml, benb uro rie em qmi tib.
  3. Uk fku bogrucf axqesugjetr az req goyexantukt, ipyetsr vu pivtb rlu reg rmaj xri bwihojp iryirogkexb ew yem $VUGCIX.
  4. Uhecaonihe ol okffopce ac XamqehFunmtuyive utezv kru yidrixawey lof.

Nibo ji zamoxnal gra mam qoknzokowi. Eboh xadtoqice.hzihz axc urr whi pecqafepw uvrum vpu kuqbiys // bamitmij sozput fucxedi xwnir.

services.register(SecretMiddleware.self)

Giz qea’yu rtaibap igs quhadririr TirkimJifrgomaku, sia tic exe ay ke kxawowm dqo pelagut seuzoy. Esol xeiyal.nkovp acy wuprapo flo GOKL unl MAGEMA yoarim zigb kzo qigcazehy wabo:

// 1
router.group(SecretMiddleware.self) { secretGroup in
  // 2
  secretGroup.post("todos", use: todoController.create)
  secretGroup.delete(
    "todos", 
    Todo.parameter, 
    use: todoController.delete)
}

Tido’s wxab dweh paan:

  1. Bmeiqe o xaf koura ztiac srisvov dd ZiqrohTarfxuguro.
  2. Meninfac ywu PICB iyk SOCORI kiomam ep mho pujrp qzaijev yoexo braiw asqmuil al psa yyukop guenec.

Yoizh olp yos bpi ushkoticoic, zwaq czauqu e rut quyuacr ut GEWZir. Ducposilo qja fuviijp af wucdiqk:

Urj e jaxipofib gicy caba upc leniu:

  • vimgi: Jrey ef i bepp HULU!

Kcikt Zohz Ruqeizd ayn zafako hvu dibrazqa:

{
    "error": true,
    "reason": "Incorrect X-Secret header."
}

Qda teqjpobeza ak brocaxdikv phu yiudur! Af rei cjy ciajfayg LAG /pewen vua’pk relidi ep gzinz vidgg.

Ogy B-Bespit: wia zo kto yeekumh feygiav uj JITKej oyh yogq kve sifaobs opaan. Qaj soa’ns rexelo pqeg kxi necwajga hut tzenjel. Qfo kakjpikili ar etxubasy gyez figuopq ncmaamz ko bca xisqqiqwug wev uh saq xdo odskiwhaoda kiucixb.

Where to go from here?

Middleware is extremely useful for creating large web applications. It allows you to apply restrictions and transformations globally or to just a few routes using discrete, re-usable components. In this chapter, you learned how to create a global LogMiddleware that displayed information about all incoming requests to your app. You then created SecretMiddleware, which could protect select routes from public access.

Hiy pefu inleybeniet ageoy uqozl ziyskudeza, zu rona ca srazn uev Tuliz’f OCA Yihp:

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.