Skip to content

Instantly share code, notes, and snippets.

@nicholaslythall
Created April 27, 2018 02:41
Show Gist options
  • Save nicholaslythall/f001f8cb75aac5f26cb8cbba403cb936 to your computer and use it in GitHub Desktop.
Save nicholaslythall/f001f8cb75aac5f26cb8cbba403cb936 to your computer and use it in GitHub Desktop.
Basic middleware implementation in swift
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")
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