Swift製webアプリケーションフレームワーク Vapor を読むやつ #3
boot 以降にいくか、と思っていたのですが boot 以降よりも middleware まわりのほうがよさそうなのでそちらを読むことにします。
さて、 middleware ですが、とりあえず既存の middleware を読めばいいでしょう。 ErrorMiddleware
を読みます。
vapor/ErrorMiddleware.swift at 3.0.2 · vapor/vapor · GitHub
superclass あるいは adopted protocols1 として Middleware, ServiceType を宣言しているようです。Swift は Vapor でしか読んでないので文法事項が普通にわからなくて困りますね。
すげー雑に見ると、 public static func makeService(for worker: Container) throws -> ErrorMiddleware
でサービスとしての ErrorMiddleware を返しているようです。その実態は public func respond(to req: Request, chainingTo next: Responder) throws -> Future<Response>
として実装されているように見えますね。つまり以下のようになってればよさそうです。
では早速適当に middleware をつくりましょう。特定のヘッダがあったときに特定のヘッダを返す、という実リクエスト/レスポンスにあんまり影響を与えないような何かにしておきます。クラス名は適当に MyMiddleware
とかにしておきましょう。
/// Sources/App/mymiddleware.swift import Vapor public final class MyMiddleware: Middleware, ServiceType { /// See `ServiceType`. public static func makeService(for worker: Container) throws -> MyMiddleware { return try .default(environment: worker.environment, log: worker.make()) } /// Create a default `MyMiddleware`. /// /// - parameters: /// - environment: The environment to respect when presenting errors. /// - log: Log destination. public static func `default`(environment: Environment, log: Logger) -> MyMiddleware { return .init() } public init() { } /// See `Middleware`. public func respond(to req: Request, chainingTo next: Responder) throws -> Future<Response> { return try next.respond(to: req).map { res in if req.http.headers.contains(name: "X-NYA-N") { res.http.headers.add(name: "X-NYA-N", value: "RESPONSE") } return res } } }
respond
内では req.http.headers
を見て分岐しています。X-NYA-N
ヘッダがあれば X-NYA-N: RESPONSE
というヘッダを返すようにしています。
これを早速使いましょう。 configure.swift の middlewares
に追加すればよさそうです。
/// Sources/App/configure.swift /// Register middleware var middlewares = MiddlewareConfig() // Create _empty_ middleware config /// middlewares.use(FileMiddleware.self) // Serves files from `Public/` directory middlewares.use(ErrorMiddleware.self) // Catches errors and converts to HTTP response middlewares.use(MyMiddleware.self) // 追加
しかしこれだけではエラーになってしまいます。
Run[81340:13566121] Fatal error: Error raised at top level: ⚠️ Service Error: No services are available for 'MyMiddleware'. - id: ServiceError.make
対応するサービスがねーぞと言われてますね。うーん。DIする機構として Service というのがありそうなのはわかっているのですがいまいち把握できてません。とりあえず ErrorMiddleware は使えているので vapor/vapor 内で ErrorMiddleware に関して検索してみるとこんなんが出てきました。
vapor/Services+Default.swift at 3.0.2 · vapor/vapor · GitHub
services.register(ErrorMiddleware.self)
とやってサービスとしても登録していますね。おそらくDIのほうでサービスクラスのリストをもっておいて、ミドルウェアが必要になったときに該当のサービスをリストから探索 → makeService
を呼んでサービスのインスタンスを取得、としているのでしょう。MyMiddleware
クラスもサービスとして登録してやります2。
/// Sources/App/configure.swift /// Register middleware var middlewares = MiddlewareConfig() // Create _empty_ middleware config /// middlewares.use(FileMiddleware.self) // Serves files from `Public/` directory middlewares.use(ErrorMiddleware.self) // Catches errors and converts to HTTP response middlewares.use(MyMiddleware.self) services.register(middlewares) services.register(MyMiddleware.self) // これを追加
これで無事立ち上がりました。レスポンスもいい感じです。
~/.g/g/h/HelloVapor ❯❯❯ curl -i localhost:8080/todos HTTP/1.1 200 OK content-type: application/json; charset=utf-8 content-length: 2 date: Sun, 13 May 2018 18:57:43 GMT []% ~/.g/g/h/HelloVapor ❯❯❯ curl -i -H "X-NYA-N: REQUEST" localhost:8080/todos HTTP/1.1 200 OK content-type: application/json; charset=utf-8 content-length: 2 X-NYA-N: RESPONSE date: Sun, 13 May 2018 18:57:46 GMT []%
service の lookup とかはたぶんこっちのレポジトリなんですがちゃんと追えてないです。また次回かな。
-
これまとめてなんていうんだろ。 ref: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/swift/grammar/class-declaration↩
-
これミドルウェアに登録したときに該当する Service がなかったら asyncRun よりも前で落ちてほしいんだけどなあ↩