Created
April 27, 2018 02:41
-
-
Save nicholaslythall/f001f8cb75aac5f26cb8cbba403cb936 to your computer and use it in GitHub Desktop.
Basic middleware implementation in swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
class MiddlewareStack<T, U> { | |
typealias Next = (T) -> U | |
typealias Layer = (T, Next) -> U | |
private var layers = [Layer]() | |
private let center: Next | |
private var stack: Next | |
init(center: @escaping Next) { | |
self.center = center | |
self.stack = center | |
} | |
func addLayer(_ layer: @escaping Layer) { | |
layers.append(layer) | |
stack = compileIterative(layers, center: center) | |
// stack = compileRecursive(layer, center: center) | |
} | |
func run(_ t: T) -> U { | |
return stack(t) | |
} | |
private func compileIterative(_ layers: [Layer], center: @escaping Next) -> Next { | |
var next: Next = center | |
for layer in layers.reversed() { | |
let current = next | |
next = { layer($0, current) } | |
} | |
return next | |
} | |
private func compileRecursive(_ layers: [Layer], center: @escaping Next) -> Next { | |
if let layer = layers.first { | |
let next = compileRecursive(Array(layers.dropFirst()), center: center) | |
return { layer($0, next) } | |
} else { | |
return center | |
} | |
} | |
} | |
// Hello World Example | |
let greetingStack = MiddlewareStack(center: { (thing: String) -> Bool in | |
print("Hello \(thing)") | |
return true | |
}) | |
let worldWasGreeted = greetingStack.run("World") | |
print(worldWasGreeted) | |
print() | |
greetingStack.addLayer { (input, next) -> Bool in | |
print("Before greeting \(input)") | |
let result = next(input) | |
if (result) { | |
print("\(input) was greeted") | |
} else { | |
print("\(input) was not greeted") | |
} | |
return result | |
} | |
let dogWasGreeted = greetingStack.run("Dog") | |
print(dogWasGreeted) | |
print() | |
greetingStack.addLayer { (input, next) -> Bool in | |
if input == "Cat" { | |
print("Felines are not allowed") | |
return false | |
} | |
return next(input) | |
} | |
let catWasGreeted = greetingStack.run("Cat") | |
print(catWasGreeted) | |
print() | |
// 'Real world' example | |
struct Request { | |
var method: String | |
var url: String | |
var body: String? = nil | |
var headers: [String:String] = [:] | |
} | |
struct Response { | |
var status: Int | |
var body: String? = nil | |
var headers: [String:String] = [:] | |
} | |
class HttpClient { | |
func get(url: String) -> Response { | |
return send(request: Request(method: "GET", url: url, body: nil, headers: [:])) | |
} | |
func post(url: String, body: String? = nil) -> Response { | |
return send(request: Request(method: "POST", url: url, body: body, headers: [:])) | |
} | |
func send(request: Request) -> Response { | |
return requestStack.run(request) | |
} | |
private let requestStack = MiddlewareStack(center: { (request: Request) -> Response in | |
// Actually make the request here | |
print("Performing request") | |
return Response(status: 200, body: "Request was successful", headers: [:]) | |
}) | |
func addRequestMiddleware(layers: [MiddlewareStack<Request, Response>.Layer]) { | |
for layer in layers { | |
requestStack.addLayer(layer) | |
} | |
} | |
} | |
let client = HttpClient() | |
// Middleware that will add default Authentication header to every request that doesn't have one | |
let authorizationMiddleware: MiddlewareStack<Request, Response>.Layer = { (request, next) in | |
var mutableRequest = request | |
if request.headers["Authorization"] == nil { | |
let credentials = "username:password".data(using: .utf8)!.base64EncodedString() | |
mutableRequest.headers["Authorization"] = "Basic \(credentials)" | |
} | |
return next(mutableRequest) | |
} | |
// Middleware that caches GET requets | |
var cache: [String:Response] = [:] | |
let cachingMiddleware: MiddlewareStack<Request, Response>.Layer = { (request, next) in | |
switch request.method { | |
case "GET": | |
let requestHash = "\(request.method):\(request.url)" | |
if let cachedResponse = cache[requestHash] { | |
print("Returning cached response") | |
return cachedResponse | |
} | |
let response = next(request) | |
cache[requestHash] = response | |
print("Response was cached") | |
return response | |
default: | |
print("Request can't be cached") | |
return next(request) | |
} | |
} | |
// Middleware that logs every request | |
let loggingMiddlware: MiddlewareStack<Request, Response>.Layer = { (request, next) in | |
print("Sending request: \(request.method) \(request.url)") | |
let response = next(request) | |
print("Received response: \(response.status)") | |
if let body = response.body { | |
print(body) | |
} | |
return response | |
} | |
// Add the middleware | |
client.addRequestMiddleware(layers: [ | |
authorizationMiddleware, | |
cachingMiddleware, | |
loggingMiddlware | |
]) | |
client.get(url: "https://service.api/messages") | |
print() | |
client.get(url: "https://service.api/messages") | |
print() | |
client.post(url: "https://service.api/messges", body: "This is a new message") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Hello World | |
true | |
Before greeting Dog | |
Hello Dog | |
Dog was greeted | |
true | |
Before greeting Cat | |
Felines are not allowed | |
Cat was not greeted | |
false | |
Sending request: GET https://service.api/messages | |
Performing request | |
Received response: 200 | |
Request was successful | |
Response was cached | |
Returning cached response | |
Request can't be cached | |
Sending request: POST https://service.api/messges | |
Performing request | |
Received response: 200 | |
Request was successful |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment