392 lines
16 KiB
Swift
392 lines
16 KiB
Swift
// This source file is part of the Swift.org Server APIs open source project
|
|
//
|
|
// Copyright (c) 2017 Swift Server API project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See http://swift.org/LICENSE.txt for license information
|
|
//
|
|
|
|
import Foundation
|
|
import Dispatch
|
|
|
|
/// A structure representing the headers for a HTTP response, without the body of the response.
|
|
public struct HTTPResponse {
|
|
/// HTTP response version
|
|
public var httpVersion: HTTPVersion
|
|
/// HTTP response status
|
|
public var status: HTTPResponseStatus
|
|
/// HTTP response headers
|
|
public var headers: HTTPHeaders
|
|
}
|
|
|
|
/// HTTPResponseWriter provides functions to create an HTTP response
|
|
public protocol HTTPResponseWriter: class {
|
|
/// Writer function to create the headers for an HTTP response
|
|
/// - Parameter status: The status code to include in the HTTP response
|
|
/// - Parameter headers: The HTTP headers to include in the HTTP response
|
|
/// - Parameter completion: Closure that is called when the HTTP headers have been written to the HTTP respose
|
|
func writeHeader(status: HTTPResponseStatus, headers: HTTPHeaders, completion: @escaping (Result) -> Void)
|
|
|
|
/// Writer function to write a trailer header as part of the HTTP response
|
|
/// - Parameter trailers: The trailers to write as part of the HTTP response
|
|
/// - Parameter completion: Closure that is called when the trailers has been written to the HTTP response
|
|
/// This is not currently implemented
|
|
func writeTrailer(_ trailers: HTTPHeaders, completion: @escaping (Result) -> Void)
|
|
|
|
/// Writer function to write data to the body of the HTTP response
|
|
/// - Parameter data: The data to write as part of the HTTP response
|
|
/// - Parameter completion: Closure that is called when the data has been written to the HTTP response
|
|
func writeBody(_ data: UnsafeHTTPResponseBody, completion: @escaping (Result) -> Void)
|
|
|
|
/// Writer function to complete the HTTP response
|
|
/// - Parameter completion: Closure that is called when the HTTP response has been completed
|
|
func done(completion: @escaping (Result) -> Void)
|
|
|
|
/// abort: Abort the HTTP response
|
|
func abort()
|
|
}
|
|
|
|
/// Convenience methods for HTTP response writer.
|
|
extension HTTPResponseWriter {
|
|
/// Convenience function to write the headers for an HTTP response without a completion handler
|
|
/// - See: `writeHeader(status:headers:completion:)`
|
|
public func writeHeader(status: HTTPResponseStatus, headers: HTTPHeaders) {
|
|
writeHeader(status: status, headers: headers) { _ in }
|
|
}
|
|
|
|
/// Convenience function to write a HTTP response with no headers or completion handler
|
|
/// - See: `writeHeader(status:headers:completion:)`
|
|
public func writeHeader(status: HTTPResponseStatus) {
|
|
writeHeader(status: status, headers: [:])
|
|
}
|
|
|
|
/// Convenience function to write a trailer header as part of the HTTP response without a completion handler
|
|
/// - See: `writeTrailer(_:completion:)`
|
|
public func writeTrailer(_ trailers: HTTPHeaders) {
|
|
writeTrailer(trailers) { _ in }
|
|
}
|
|
|
|
/// Convenience function for writing `data` to the body of the HTTP response without a completion handler.
|
|
/// - See: writeBody(_:completion:)
|
|
public func writeBody(_ data: UnsafeHTTPResponseBody) {
|
|
return writeBody(data) { _ in }
|
|
}
|
|
|
|
/// Convenience function to complete the HTTP response without a completion handler.
|
|
/// - See: done(completion:)
|
|
public func done() {
|
|
done { _ in }
|
|
}
|
|
}
|
|
|
|
/// The response status for the HTTP response
|
|
/// - See: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml for more information
|
|
public struct HTTPResponseStatus: Equatable, CustomStringConvertible, ExpressibleByIntegerLiteral {
|
|
/// The status code, eg. 200 or 404
|
|
public let code: Int
|
|
/// The reason phrase for the status code
|
|
public let reasonPhrase: String
|
|
|
|
/// Creates an HTTP response status
|
|
/// - Parameter code: The status code used for the response status
|
|
/// - Parameter reasonPhrase: The reason phrase to use for the response status
|
|
public init(code: Int, reasonPhrase: String) {
|
|
self.code = code
|
|
self.reasonPhrase = reasonPhrase
|
|
}
|
|
|
|
/// Creates an HTTP response status
|
|
/// The reason phrase is added for the status code, or "http_(code)" if the code is not well known
|
|
/// - Parameter code: The status code used for the response status
|
|
public init(code: Int) {
|
|
self.init(code: code, reasonPhrase: HTTPResponseStatus.defaultReasonPhrase(forCode: code))
|
|
}
|
|
|
|
/// :nodoc:
|
|
public init(integerLiteral: Int) {
|
|
self.init(code: integerLiteral)
|
|
}
|
|
|
|
/* all the codes from http://www.iana.org/assignments/http-status-codes */
|
|
/// 100 Continue
|
|
public static let `continue` = HTTPResponseStatus(code: 100)
|
|
/// 101 Switching Protocols
|
|
public static let switchingProtocols = HTTPResponseStatus(code: 101)
|
|
/// 200 OK
|
|
public static let ok = HTTPResponseStatus(code: 200)
|
|
/// 201 Created
|
|
public static let created = HTTPResponseStatus(code: 201)
|
|
/// 202 Accepted
|
|
public static let accepted = HTTPResponseStatus(code: 202)
|
|
/// 203 Non-Authoritative Information
|
|
public static let nonAuthoritativeInformation = HTTPResponseStatus(code: 203)
|
|
/// 204 No Content
|
|
public static let noContent = HTTPResponseStatus(code: 204)
|
|
/// 205 Reset Content
|
|
public static let resetContent = HTTPResponseStatus(code: 205)
|
|
/// 206 Partial Content
|
|
public static let partialContent = HTTPResponseStatus(code: 206)
|
|
/// 207 Multi-Status
|
|
public static let multiStatus = HTTPResponseStatus(code: 207)
|
|
/// 208 Already Reported
|
|
public static let alreadyReported = HTTPResponseStatus(code: 208)
|
|
/// 226 IM Used
|
|
public static let imUsed = HTTPResponseStatus(code: 226)
|
|
/// 300 Multiple Choices
|
|
public static let multipleChoices = HTTPResponseStatus(code: 300)
|
|
/// 301 Moved Permanently
|
|
public static let movedPermanently = HTTPResponseStatus(code: 301)
|
|
/// 302 Found
|
|
public static let found = HTTPResponseStatus(code: 302)
|
|
/// 303 See Other
|
|
public static let seeOther = HTTPResponseStatus(code: 303)
|
|
/// 304 Not Modified
|
|
public static let notModified = HTTPResponseStatus(code: 304)
|
|
/// 305 Use Proxy
|
|
public static let useProxy = HTTPResponseStatus(code: 305)
|
|
/// 307 Temporary Redirect
|
|
public static let temporaryRedirect = HTTPResponseStatus(code: 307)
|
|
/// 308 Permanent Redirect
|
|
public static let permanentRedirect = HTTPResponseStatus(code: 308)
|
|
/// 400 Bad Request
|
|
public static let badRequest = HTTPResponseStatus(code: 400)
|
|
/// 401 Unauthorized
|
|
public static let unauthorized = HTTPResponseStatus(code: 401)
|
|
/// 402 Payment Required
|
|
public static let paymentRequired = HTTPResponseStatus(code: 402)
|
|
/// 403 Forbidden
|
|
public static let forbidden = HTTPResponseStatus(code: 403)
|
|
/// 404 Not Found
|
|
public static let notFound = HTTPResponseStatus(code: 404)
|
|
/// 405 Method Not Allowed
|
|
public static let methodNotAllowed = HTTPResponseStatus(code: 405)
|
|
/// 406 Not Acceptable
|
|
public static let notAcceptable = HTTPResponseStatus(code: 406)
|
|
/// 407 Proxy Authentication Required
|
|
public static let proxyAuthenticationRequired = HTTPResponseStatus(code: 407)
|
|
/// 408 Request Timeout
|
|
public static let requestTimeout = HTTPResponseStatus(code: 408)
|
|
/// 409 Conflict
|
|
public static let conflict = HTTPResponseStatus(code: 409)
|
|
/// 410 Gone
|
|
public static let gone = HTTPResponseStatus(code: 410)
|
|
/// 411 Length Required
|
|
public static let lengthRequired = HTTPResponseStatus(code: 411)
|
|
/// 412 Precondition Failed
|
|
public static let preconditionFailed = HTTPResponseStatus(code: 412)
|
|
/// 413 Payload Too Large
|
|
public static let payloadTooLarge = HTTPResponseStatus(code: 413)
|
|
/// 414 URI Too Long
|
|
public static let uriTooLong = HTTPResponseStatus(code: 414)
|
|
/// 415 Unsupported Media Type
|
|
public static let unsupportedMediaType = HTTPResponseStatus(code: 415)
|
|
/// 416 Range Not Satisfiable
|
|
public static let rangeNotSatisfiable = HTTPResponseStatus(code: 416)
|
|
/// 417 Expectation Failed
|
|
public static let expectationFailed = HTTPResponseStatus(code: 417)
|
|
/// 421 Misdirected Request
|
|
public static let misdirectedRequest = HTTPResponseStatus(code: 421)
|
|
/// 422 Unprocessable Entity
|
|
public static let unprocessableEntity = HTTPResponseStatus(code: 422)
|
|
/// 423 Locked
|
|
public static let locked = HTTPResponseStatus(code: 423)
|
|
/// 424 Failed Dependency
|
|
public static let failedDependency = HTTPResponseStatus(code: 424)
|
|
/// 426 Upgrade Required
|
|
public static let upgradeRequired = HTTPResponseStatus(code: 426)
|
|
/// 428 Precondition Required
|
|
public static let preconditionRequired = HTTPResponseStatus(code: 428)
|
|
/// 429 Too Many Requests
|
|
public static let tooManyRequests = HTTPResponseStatus(code: 429)
|
|
/// 431 Request Header Fields Too Large
|
|
public static let requestHeaderFieldsTooLarge = HTTPResponseStatus(code: 431)
|
|
/// 451 Unavailable For Legal Reasons
|
|
public static let unavailableForLegalReasons = HTTPResponseStatus(code: 451)
|
|
/// 500 Internal Server Error
|
|
public static let internalServerError = HTTPResponseStatus(code: 500)
|
|
/// 501 Not Implemented
|
|
public static let notImplemented = HTTPResponseStatus(code: 501)
|
|
/// 502 Bad Gateway
|
|
public static let badGateway = HTTPResponseStatus(code: 502)
|
|
/// 503 Service Unavailable
|
|
public static let serviceUnavailable = HTTPResponseStatus(code: 503)
|
|
/// 504 Gateway Timeout
|
|
public static let gatewayTimeout = HTTPResponseStatus(code: 504)
|
|
/// 505 HTTP Version Not Supported
|
|
public static let httpVersionNotSupported = HTTPResponseStatus(code: 505)
|
|
/// 506 Variant Also Negotiates
|
|
public static let variantAlsoNegotiates = HTTPResponseStatus(code: 506)
|
|
/// 507 Insufficient Storage
|
|
public static let insufficientStorage = HTTPResponseStatus(code: 507)
|
|
/// 508 Loop Detected
|
|
public static let loopDetected = HTTPResponseStatus(code: 508)
|
|
/// 510 Not Extended
|
|
public static let notExtended = HTTPResponseStatus(code: 510)
|
|
/// 511 Network Authentication Required
|
|
public static let networkAuthenticationRequired = HTTPResponseStatus(code: 511)
|
|
|
|
// swiftlint:disable cyclomatic_complexity switch_case_on_newline
|
|
static func defaultReasonPhrase(forCode code: Int) -> String {
|
|
switch code {
|
|
case 100: return "Continue"
|
|
case 101: return "Switching Protocols"
|
|
case 200: return "OK"
|
|
case 201: return "Created"
|
|
case 202: return "Accepted"
|
|
case 203: return "Non-Authoritative Information"
|
|
case 204: return "No Content"
|
|
case 205: return "Reset Content"
|
|
case 206: return "Partial Content"
|
|
case 207: return "Multi-Status"
|
|
case 208: return "Already Reported"
|
|
case 226: return "IM Used"
|
|
case 300: return "Multiple Choices"
|
|
case 301: return "Moved Permanently"
|
|
case 302: return "Found"
|
|
case 303: return "See Other"
|
|
case 304: return "Not Modified"
|
|
case 305: return "Use Proxy"
|
|
case 307: return "Temporary Redirect"
|
|
case 308: return "Permanent Redirect"
|
|
case 400: return "Bad Request"
|
|
case 401: return "Unauthorized"
|
|
case 402: return "Payment Required"
|
|
case 403: return "Forbidden"
|
|
case 404: return "Not Found"
|
|
case 405: return "Method Not Allowed"
|
|
case 406: return "Not Acceptable"
|
|
case 407: return "Proxy Authentication Required"
|
|
case 408: return "Request Timeout"
|
|
case 409: return "Conflict"
|
|
case 410: return "Gone"
|
|
case 411: return "Length Required"
|
|
case 412: return "Precondition Failed"
|
|
case 413: return "Payload Too Large"
|
|
case 414: return "URI Too Long"
|
|
case 415: return "Unsupported Media Type"
|
|
case 416: return "Range Not Satisfiable"
|
|
case 417: return "Expectation Failed"
|
|
case 421: return "Misdirected Request"
|
|
case 422: return "Unprocessable Entity"
|
|
case 423: return "Locked"
|
|
case 424: return "Failed Dependency"
|
|
case 426: return "Upgrade Required"
|
|
case 428: return "Precondition Required"
|
|
case 429: return "Too Many Requests"
|
|
case 431: return "Request Header Fields Too Large"
|
|
case 451: return "Unavailable For Legal Reasons"
|
|
case 500: return "Internal Server Error"
|
|
case 501: return "Not Implemented"
|
|
case 502: return "Bad Gateway"
|
|
case 503: return "Service Unavailable"
|
|
case 504: return "Gateway Timeout"
|
|
case 505: return "HTTP Version Not Supported"
|
|
case 506: return "Variant Also Negotiates"
|
|
case 507: return "Insufficient Storage"
|
|
case 508: return "Loop Detected"
|
|
case 510: return "Not Extended"
|
|
case 511: return "Network Authentication Required"
|
|
default: return "http_\(code)"
|
|
}
|
|
}
|
|
|
|
/// :nodoc:
|
|
public var description: String {
|
|
return "\(code) \(reasonPhrase)"
|
|
}
|
|
|
|
/// - The `Class` representing the class of status code for this response status
|
|
public var `class`: Class {
|
|
return Class(code: code)
|
|
}
|
|
|
|
/// The class of a `HTTPResponseStatus` code
|
|
/// - See: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml for more information
|
|
public enum Class {
|
|
/// Informational: the request was received, and is continuing to be processed
|
|
case informational
|
|
/// Success: the action was successfully received, understood, and accepted
|
|
case successful
|
|
/// Redirection: further action must be taken in order to complete the request
|
|
case redirection
|
|
/// Client Error: the request contains bad syntax or cannot be fulfilled
|
|
case clientError
|
|
/// Server Error: the server failed to fulfill an apparently valid request
|
|
case serverError
|
|
/// Invalid: the code does not map to a well known status code class
|
|
case invalidStatus
|
|
|
|
init(code: Int) {
|
|
switch code {
|
|
case 100..<200: self = .informational
|
|
case 200..<300: self = .successful
|
|
case 300..<400: self = .redirection
|
|
case 400..<500: self = .clientError
|
|
case 500..<600: self = .serverError
|
|
default: self = .invalidStatus
|
|
}
|
|
}
|
|
}
|
|
|
|
// [RFC2616, section 4.4]
|
|
var bodyAllowed: Bool {
|
|
switch code {
|
|
case 100..<200: return false
|
|
case 204: return false
|
|
case 304: return false
|
|
default: return true
|
|
}
|
|
}
|
|
|
|
var suppressedHeaders: [HTTPHeaders.Name] {
|
|
if self == .notModified {
|
|
return ["Content-Type", "Content-Length", "Transfer-Encoding"]
|
|
} else if !bodyAllowed {
|
|
return ["Content-Length", "Transfer-Encoding"]
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
/// :nodoc:
|
|
public static func == (lhs: HTTPResponseStatus, rhs: HTTPResponseStatus) -> Bool {
|
|
return lhs.code == rhs.code
|
|
}
|
|
}
|
|
|
|
/// :nodoc:
|
|
public protocol UnsafeHTTPResponseBody {
|
|
func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R
|
|
}
|
|
|
|
/// :nodoc:
|
|
extension UnsafeRawBufferPointer: UnsafeHTTPResponseBody {
|
|
public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
|
|
return try body(self)
|
|
}
|
|
}
|
|
|
|
/// :nodoc:
|
|
public protocol HTTPResponseBody: UnsafeHTTPResponseBody {}
|
|
|
|
extension Data: HTTPResponseBody {
|
|
/// :nodoc:
|
|
public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
|
|
return try withUnsafeBytes { try body(UnsafeRawBufferPointer(start: $0, count: count)) }
|
|
}
|
|
}
|
|
|
|
extension DispatchData: HTTPResponseBody {
|
|
/// :nodoc:
|
|
public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
|
|
return try withUnsafeBytes { try body(UnsafeRawBufferPointer(start: $0, count: count)) }
|
|
}
|
|
}
|
|
|
|
extension String: HTTPResponseBody {
|
|
/// :nodoc:
|
|
public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
|
|
return try ContiguousArray(utf8).withUnsafeBytes(body)
|
|
}
|
|
}
|