|
@@ -0,0 +1,175 @@
|
|
|
|
|
+//
|
|
|
|
|
+// HttpResponse.swift
|
|
|
|
|
+// Swifter
|
|
|
|
|
+//
|
|
|
|
|
+// Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved.
|
|
|
|
|
+//
|
|
|
|
|
+
|
|
|
|
|
+import Foundation
|
|
|
|
|
+
|
|
|
|
|
+public enum SerializationError: ErrorProtocol {
|
|
|
|
|
+ case InvalidObject
|
|
|
|
|
+ case NotSupported
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+public protocol HttpResponseBodyWriter {
|
|
|
|
|
+ func write(_ data: [UInt8])
|
|
|
|
|
+ func write(_ data: ArraySlice<UInt8>)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+public enum HttpResponseBody {
|
|
|
|
|
+
|
|
|
|
|
+ case Json(AnyObject)
|
|
|
|
|
+ case Html(String)
|
|
|
|
|
+ case Text(String)
|
|
|
|
|
+ case Data([UInt8])
|
|
|
|
|
+ case Custom(Any, (Any) throws -> String)
|
|
|
|
|
+
|
|
|
|
|
+ func content() -> (Int, (HttpResponseBodyWriter throws -> Void)?) {
|
|
|
|
|
+ do {
|
|
|
|
|
+ switch self {
|
|
|
|
|
+ case .Json(let object):
|
|
|
|
|
+ #if os(Linux)
|
|
|
|
|
+ NSRunLoop.currentRunLoop().run()
|
|
|
|
|
+ #else
|
|
|
|
|
+ guard NSJSONSerialization.isValidJSONObject(object) else {
|
|
|
|
|
+ throw SerializationError.InvalidObject
|
|
|
|
|
+ }
|
|
|
|
|
+ let json = try NSJSONSerialization.data(withJSONObject: object, options: NSJSONWritingOptions.prettyPrinted)
|
|
|
|
|
+ let data = Array(UnsafeBufferPointer(start: UnsafePointer<UInt8>(json.bytes), count: json.length))
|
|
|
|
|
+ return (data.count, {
|
|
|
|
|
+ $0.write(data)
|
|
|
|
|
+ })
|
|
|
|
|
+ #endif
|
|
|
|
|
+ case .Text(let body):
|
|
|
|
|
+ let data = [UInt8](body.utf8)
|
|
|
|
|
+ return (data.count, {
|
|
|
|
|
+ $0.write(data)
|
|
|
|
|
+ })
|
|
|
|
|
+ case .Html(let body):
|
|
|
|
|
+ let serialised = "<html><meta charset=\"UTF-8\"><body>\(body)</body></html>"
|
|
|
|
|
+ let data = [UInt8](serialised.utf8)
|
|
|
|
|
+ return (data.count, {
|
|
|
|
|
+ $0.write(data)
|
|
|
|
|
+ })
|
|
|
|
|
+ case .Data(let body):
|
|
|
|
|
+ return (body.count, {
|
|
|
|
|
+ $0.write(body)
|
|
|
|
|
+ })
|
|
|
|
|
+ case .Custom(let object, let closure):
|
|
|
|
|
+ let serialised = try closure(object)
|
|
|
|
|
+ let data = [UInt8](serialised.utf8)
|
|
|
|
|
+ return (data.count, {
|
|
|
|
|
+ $0.write(data)
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ let data = [UInt8]("Serialisation error: \(error)".utf8)
|
|
|
|
|
+ return (data.count, {
|
|
|
|
|
+ $0.write(data)
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+public enum HttpResponse {
|
|
|
|
|
+
|
|
|
|
|
+ case SwitchProtocols([String: String], Socket -> Void)
|
|
|
|
|
+ case OK(HttpResponseBody), Created, Accepted
|
|
|
|
|
+ case MovedPermanently(String)
|
|
|
|
|
+ case BadRequest(HttpResponseBody?), Unauthorized, Forbidden, NotFound
|
|
|
|
|
+ case InternalServerError
|
|
|
|
|
+ case RAW(Int, String, [String:String]?, (HttpResponseBodyWriter -> Void)? )
|
|
|
|
|
+
|
|
|
|
|
+ func statusCode() -> Int {
|
|
|
|
|
+ switch self {
|
|
|
|
|
+ case .SwitchProtocols(_, _) : return 101
|
|
|
|
|
+ case .OK(_) : return 200
|
|
|
|
|
+ case .Created : return 201
|
|
|
|
|
+ case .Accepted : return 202
|
|
|
|
|
+ case .MovedPermanently : return 301
|
|
|
|
|
+ case .BadRequest(_) : return 400
|
|
|
|
|
+ case .Unauthorized : return 401
|
|
|
|
|
+ case .Forbidden : return 403
|
|
|
|
|
+ case .NotFound : return 404
|
|
|
|
|
+ case .InternalServerError : return 500
|
|
|
|
|
+ case .RAW(let code, _ , _, _) : return code
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ func reasonPhrase() -> String {
|
|
|
|
|
+ switch self {
|
|
|
|
|
+ case .SwitchProtocols(_, _) : return "Switching Protocols"
|
|
|
|
|
+ case .OK(_) : return "OK"
|
|
|
|
|
+ case .Created : return "Created"
|
|
|
|
|
+ case .Accepted : return "Accepted"
|
|
|
|
|
+ case .MovedPermanently : return "Moved Permanently"
|
|
|
|
|
+ case .BadRequest(_) : return "Bad Request"
|
|
|
|
|
+ case .Unauthorized : return "Unauthorized"
|
|
|
|
|
+ case .Forbidden : return "Forbidden"
|
|
|
|
|
+ case .NotFound : return "Not Found"
|
|
|
|
|
+ case .InternalServerError : return "Internal Server Error"
|
|
|
|
|
+ case .RAW(_, let phrase, _, _) : return phrase
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ func headers() -> [String: String] {
|
|
|
|
|
+ var headers = ["Server" : "Swifter \(HttpServer.VERSION)"]
|
|
|
|
|
+ switch self {
|
|
|
|
|
+ case .SwitchProtocols(let switchHeaders, _):
|
|
|
|
|
+ for (key, value) in switchHeaders {
|
|
|
|
|
+ headers[key] = value
|
|
|
|
|
+ }
|
|
|
|
|
+ case .OK(let body):
|
|
|
|
|
+ switch body {
|
|
|
|
|
+ case .Text(_) : headers["Content-Type"] = "text/plain"
|
|
|
|
|
+ case .Json(_) : headers["Content-Type"] = "application/json"
|
|
|
|
|
+ case .Html(_) : headers["Content-Type"] = "text/html"
|
|
|
|
|
+ case .Data(_) : headers["Content-Type"] = "application/octet-stream"
|
|
|
|
|
+ default:break
|
|
|
|
|
+ }
|
|
|
|
|
+ case .MovedPermanently(let location):
|
|
|
|
|
+ headers["Location"] = location
|
|
|
|
|
+ case .RAW(_, _, let rawHeaders, _):
|
|
|
|
|
+ if let rawHeaders = rawHeaders {
|
|
|
|
|
+ for (k, v) in rawHeaders {
|
|
|
|
|
+ headers.updateValue(v, forKey: k)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ default:break
|
|
|
|
|
+ }
|
|
|
|
|
+ return headers
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ func content() -> (length: Int, write: (HttpResponseBodyWriter throws -> Void)?) {
|
|
|
|
|
+ switch self {
|
|
|
|
|
+ case .OK(let body) : return body.content()
|
|
|
|
|
+ case .BadRequest(let body) : return body?.content() ?? (-1, nil)
|
|
|
|
|
+ case .RAW(_, _, _, let writer) : return (-1, writer)
|
|
|
|
|
+ default : return (-1, nil)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ func socketSession() -> (Socket -> Void)? {
|
|
|
|
|
+ switch self {
|
|
|
|
|
+ case SwitchProtocols(_, let handler) : return handler
|
|
|
|
|
+ default: return nil
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ Makes it possible to compare handler responses with '==', but
|
|
|
|
|
+ ignores any associated values. This should generally be what
|
|
|
|
|
+ you want. E.g.:
|
|
|
|
|
+
|
|
|
|
|
+ let resp = handler(updatedRequest)
|
|
|
|
|
+ if resp == .NotFound {
|
|
|
|
|
+ print("Client requested not found: \(request.url)")
|
|
|
|
|
+ }
|
|
|
|
|
+*/
|
|
|
|
|
+
|
|
|
|
|
+func ==(inLeft: HttpResponse, inRight: HttpResponse) -> Bool {
|
|
|
|
|
+ return inLeft.statusCode() == inRight.statusCode()
|
|
|
|
|
+}
|
|
|
|
|
+
|