Make HTTPHeaders more user-friendly (#14)
* Make HTTPHeaders more user-friendly * Header name is a struct conforming to `ExpressibleByStringLiteral`. Standard header names are available through static constants. This allows to save some CPU on lowercasing and hash value calculation. * Original headers are stored until first modification. Iterator of `HTTPHeaders` iterates over original headers while they are actual, in other case it iterates over underlying mutable dictionary. * Default subscript returns values of all headers with the same name concatenated with comma [RFC7230, section 3.2.2], except for "Set-Cookie" for which only first value is returned. * As-is values of all headers with same name can be accessed through subscript with label `valuesFor`. * append() method is modified to accept dictionary literal. replace() method is added. * Split HTTPHeaders implementation to separate extensions
This commit is contained in:
committed by
Chris Bailey
parent
002d9ce17b
commit
7e366e4687
@@ -52,15 +52,211 @@ public enum HTTPBodyChunk {
|
||||
}
|
||||
|
||||
/// Headers structure.
|
||||
public struct HTTPHeaders {
|
||||
var storage: [String:[String]] /* lower cased keys */
|
||||
var original: [(String, String)] /* original casing */
|
||||
let description: String
|
||||
|
||||
public subscript(key: String) -> [String]
|
||||
func makeIterator() -> IndexingIterator<Array<(String, String)>>
|
||||
|
||||
public init(_ headers: [(String, String)] = [])
|
||||
public struct HTTPHeaders : Sequence, ExpressibleByDictionaryLiteral {
|
||||
public subscript(name: Name) -> String?
|
||||
public subscript(valuesFor name: Name) -> [String]
|
||||
|
||||
public struct Literal : ExpressibleByDictionaryLiteral { }
|
||||
public mutating func append(_ literal: HTTPHeaders.Literal)
|
||||
public mutating func replace(_ literal: HTTPHeaders.Literal)
|
||||
|
||||
public func makeIterator() -> AnyIterator<(name: Name, value: String)>
|
||||
|
||||
public struct Name : Hashable, ExpressibleByStringLiteral, CustomStringConvertible {
|
||||
public init(_ name: String)
|
||||
|
||||
// https://www.iana.org/assignments/message-headers/message-headers.xhtml
|
||||
// Permanent Message Header Field Names
|
||||
public static let aIM
|
||||
public static let accept
|
||||
public static let acceptAdditions
|
||||
public static let acceptCharset
|
||||
public static let acceptDatetime
|
||||
public static let acceptEncoding
|
||||
public static let acceptFeatures
|
||||
public static let acceptLanguage
|
||||
public static let acceptPatch
|
||||
public static let acceptPost
|
||||
public static let acceptRanges
|
||||
public static let age
|
||||
public static let allow
|
||||
public static let alpn
|
||||
public static let altSvc
|
||||
public static let altUsed
|
||||
public static let alternates
|
||||
public static let applyToRedirectRef
|
||||
public static let authenticationControl
|
||||
public static let authenticationInfo
|
||||
public static let authorization
|
||||
public static let cExt
|
||||
public static let cMan
|
||||
public static let cOpt
|
||||
public static let cPEP
|
||||
public static let cPEPInfo
|
||||
public static let cacheControl
|
||||
public static let calDAVTimezones
|
||||
public static let close
|
||||
public static let connection
|
||||
public static let contentBase
|
||||
public static let contentDisposition
|
||||
public static let contentEncoding
|
||||
public static let contentID
|
||||
public static let contentLanguage
|
||||
public static let contentLength
|
||||
public static let contentLocation
|
||||
public static let contentMD5
|
||||
public static let contentRange
|
||||
public static let contentScriptType
|
||||
public static let contentStyleType
|
||||
public static let contentType
|
||||
public static let contentVersion
|
||||
public static let cookie
|
||||
public static let cookie2
|
||||
public static let dasl
|
||||
public static let dav
|
||||
public static let date
|
||||
public static let defaultStyle
|
||||
public static let deltaBase
|
||||
public static let depth
|
||||
public static let derivedFrom
|
||||
public static let destination
|
||||
public static let differentialID
|
||||
public static let digest
|
||||
public static let eTag
|
||||
public static let expect
|
||||
public static let expires
|
||||
public static let ext
|
||||
public static let forwarded
|
||||
public static let from
|
||||
public static let getProfile
|
||||
public static let hobareg
|
||||
public static let host
|
||||
public static let http2Settings
|
||||
public static let im
|
||||
public static let `if`
|
||||
public static let ifMatch
|
||||
public static let ifModifiedSince
|
||||
public static let ifNoneMatch
|
||||
public static let ifRange
|
||||
public static let ifScheduleTagMatch
|
||||
public static let ifUnmodifiedSince
|
||||
public static let keepAlive
|
||||
public static let label
|
||||
public static let lastModified
|
||||
public static let link
|
||||
public static let location
|
||||
public static let lockToken
|
||||
public static let man
|
||||
public static let maxForwards
|
||||
public static let mementoDatetime
|
||||
public static let meter
|
||||
public static let mimeVersion
|
||||
public static let negotiate
|
||||
public static let opt
|
||||
public static let optionalWWWAuthenticate
|
||||
public static let orderingType
|
||||
public static let origin
|
||||
public static let overwrite
|
||||
public static let p3p
|
||||
public static let pep
|
||||
public static let picsLabel
|
||||
public static let pepInfo
|
||||
public static let position
|
||||
public static let pragma
|
||||
public static let prefer
|
||||
public static let preferenceApplied
|
||||
public static let profileObject
|
||||
public static let `protocol`
|
||||
public static let protocolInfo
|
||||
public static let protocolQuery
|
||||
public static let protocolRequest
|
||||
public static let proxyAuthenticate
|
||||
public static let proxyAuthenticationInfo
|
||||
public static let proxyAuthorization
|
||||
public static let proxyFeatures
|
||||
public static let proxyInstruction
|
||||
public static let `public`
|
||||
public static let publicKeyPins
|
||||
public static let publicKeyPinsReportOnly
|
||||
public static let range
|
||||
public static let redirectRef
|
||||
public static let referer
|
||||
public static let retryAfter
|
||||
public static let safe
|
||||
public static let scheduleReply
|
||||
public static let scheduleTag
|
||||
public static let secWebSocketAccept
|
||||
public static let secWebSocketExtensions
|
||||
public static let secWebSocketKey
|
||||
public static let secWebSocketProtocol
|
||||
public static let secWebSocketVersion
|
||||
public static let securityScheme
|
||||
public static let server
|
||||
public static let setCookie
|
||||
public static let setCookie2
|
||||
public static let setProfile
|
||||
public static let slug
|
||||
public static let soapAction
|
||||
public static let statusURI
|
||||
public static let strictTransportSecurity
|
||||
public static let surrogateCapability
|
||||
public static let surrogateControl
|
||||
public static let tcn
|
||||
public static let te
|
||||
public static let timeout
|
||||
public static let topic
|
||||
public static let trailer
|
||||
public static let transferEncoding
|
||||
public static let ttl
|
||||
public static let urgency
|
||||
public static let uri
|
||||
public static let upgrade
|
||||
public static let userAgent
|
||||
public static let variantVary
|
||||
public static let vary
|
||||
public static let via
|
||||
public static let wwwAuthenticate
|
||||
public static let wantDigest
|
||||
public static let warning
|
||||
public static let xFrameOptions
|
||||
|
||||
// https://www.iana.org/assignments/message-headers/message-headers.xhtml
|
||||
// Provisional Message Header Field Names
|
||||
public static let accessControl
|
||||
public static let accessControlAllowCredentials
|
||||
public static let accessControlAllowHeaders
|
||||
public static let accessControlAllowMethods
|
||||
public static let accessControlAllowOrigin
|
||||
public static let accessControlMaxAge
|
||||
public static let accessControlRequestMethod
|
||||
public static let accessControlRequestHeaders
|
||||
public static let compliance
|
||||
public static let contentTransferEncoding
|
||||
public static let cost
|
||||
public static let ediintFeatures
|
||||
public static let messageID
|
||||
public static let methodCheck
|
||||
public static let methodCheckExpires
|
||||
public static let nonCompliance
|
||||
public static let optional
|
||||
public static let refererRoot
|
||||
public static let resolutionHint
|
||||
public static let resolverLocation
|
||||
public static let subOK
|
||||
public static let subst
|
||||
public static let title
|
||||
public static let uaColor
|
||||
public static let uaMedia
|
||||
public static let uaPixels
|
||||
public static let uaResolution
|
||||
public static let uaWindowpixels
|
||||
public static let version
|
||||
public static let xDeviceAccept
|
||||
public static let xDeviceAcceptCharset
|
||||
public static let xDeviceAcceptEncoding
|
||||
public static let xDeviceAcceptLanguage
|
||||
public static let xDeviceUserAgent
|
||||
}
|
||||
}
|
||||
|
||||
/// Version number of the HTTP Protocol
|
||||
|
||||
@@ -17,55 +17,6 @@ public protocol WebAppContaining: class {
|
||||
func serve(req: HTTPRequest, res: HTTPResponseWriter ) -> HTTPBodyProcessing
|
||||
}
|
||||
|
||||
/// Headers structure.
|
||||
public struct HTTPHeaders {
|
||||
var storage: [String: [String]] /* lower cased keys */
|
||||
var original: [(String, String)] /* original casing */
|
||||
let description: String
|
||||
|
||||
public subscript(key: String) -> [String] {
|
||||
get {
|
||||
return storage[key.lowercased()] ?? []
|
||||
}
|
||||
mutating set {
|
||||
original = original.filter { $0.0 != key.lowercased() }
|
||||
storage[key.lowercased()]=nil
|
||||
for val in newValue {
|
||||
self.append(newHeader: (key, val))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeIterator() -> IndexingIterator<Array<(String, String)>> {
|
||||
return original.makeIterator()
|
||||
}
|
||||
|
||||
public mutating func append(newHeader: (String, String)) {
|
||||
original.append(newHeader)
|
||||
let key = newHeader.0.lowercased()
|
||||
let val = newHeader.1
|
||||
|
||||
var existing = storage[key] ?? []
|
||||
existing.append(val)
|
||||
storage[key] = existing
|
||||
}
|
||||
|
||||
/// Create Header structure from an array of string pairs
|
||||
public init(_ headers: [(String, String)] = []) {
|
||||
original = headers
|
||||
description = ""
|
||||
storage = [String: [String]]()
|
||||
makeIterator().forEach { (element: (String, String)) in
|
||||
let key = element.0.lowercased()
|
||||
let val = element.1
|
||||
|
||||
var existing = storage[key] ?? []
|
||||
existing.append(val)
|
||||
storage[key] = existing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum Result<POSIXError, Void> {
|
||||
case success(())
|
||||
case failure(POSIXError)
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
/// Headers structure.
|
||||
public struct HTTPHeaders {
|
||||
var original: [(name: Name, value: String)]?
|
||||
var storage: [Name: [String]] {
|
||||
didSet { original = nil }
|
||||
}
|
||||
|
||||
public subscript(name: Name) -> String? {
|
||||
get {
|
||||
guard let value = storage[name] else { return nil }
|
||||
switch name {
|
||||
case Name.setCookie: // Exception, see note in [RFC7230, section 3.2.2]
|
||||
return value.isEmpty ? nil : value[0]
|
||||
default:
|
||||
return value.joined(separator: ",")
|
||||
}
|
||||
}
|
||||
set {
|
||||
storage[name] = newValue.map { [$0] }
|
||||
}
|
||||
}
|
||||
|
||||
public subscript(valuesFor name: Name) -> [String] {
|
||||
get { return storage[name] ?? [] }
|
||||
set { storage[name] = newValue.isEmpty ? nil : newValue }
|
||||
}
|
||||
}
|
||||
|
||||
extension HTTPHeaders : ExpressibleByDictionaryLiteral {
|
||||
public init(dictionaryLiteral: (Name, String)...) {
|
||||
storage = [:]
|
||||
for (name, value) in dictionaryLiteral {
|
||||
#if swift(>=4.0)
|
||||
storage[name, default: []].append(value)
|
||||
#else
|
||||
if storage[name] == nil {
|
||||
storage[name] = [value]
|
||||
} else {
|
||||
storage[name]!.append(value)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
original = dictionaryLiteral
|
||||
}
|
||||
}
|
||||
|
||||
extension HTTPHeaders {
|
||||
// Used instead of HTTPHeaders to save CPU on dictionary construction
|
||||
public struct Literal : ExpressibleByDictionaryLiteral {
|
||||
let fields: [(name: Name, value: String)]
|
||||
|
||||
public init(dictionaryLiteral: (Name, String)...) {
|
||||
fields = dictionaryLiteral
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func append(_ literal: HTTPHeaders.Literal) {
|
||||
for (name, value) in literal.fields {
|
||||
#if swift(>=4.0)
|
||||
storage[name, default: []].append(value)
|
||||
#else
|
||||
if storage[name] == nil {
|
||||
storage[name] = [value]
|
||||
} else {
|
||||
storage[name]!.append(value)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func replace(_ literal: HTTPHeaders.Literal) {
|
||||
for (name, _) in literal.fields {
|
||||
storage[name] = []
|
||||
}
|
||||
for (name, value) in literal.fields {
|
||||
storage[name]!.append(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension HTTPHeaders : Sequence {
|
||||
public func makeIterator() -> AnyIterator<(name: Name, value: String)> {
|
||||
if let original = original {
|
||||
return AnyIterator(original.makeIterator())
|
||||
} else {
|
||||
return AnyIterator(StorageIterator(storage.makeIterator()))
|
||||
}
|
||||
}
|
||||
|
||||
struct StorageIterator : IteratorProtocol {
|
||||
var headers: DictionaryIterator<Name, [String]>
|
||||
var header: (name: Name, values: IndexingIterator<[String]>)?
|
||||
|
||||
init(_ iterator: DictionaryIterator<Name, [String]>) {
|
||||
headers = iterator
|
||||
header = headers.next().map { (name: $0.key, values: $0.value.makeIterator()) }
|
||||
}
|
||||
|
||||
mutating func next() -> (name: Name, value: String)? {
|
||||
while header != nil {
|
||||
if let value = header!.values.next() {
|
||||
return (name: header!.name, value: value)
|
||||
} else {
|
||||
header = headers.next().map { (name: $0.key, values: $0.value.makeIterator()) }
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension HTTPHeaders {
|
||||
public struct Name : Hashable, ExpressibleByStringLiteral, CustomStringConvertible {
|
||||
let original: String
|
||||
let lowercased: String
|
||||
public let hashValue: Int
|
||||
|
||||
public init(_ name: String) {
|
||||
original = name
|
||||
lowercased = name.lowercased()
|
||||
hashValue = lowercased.hashValue
|
||||
}
|
||||
|
||||
public init(stringLiteral: String) {
|
||||
self.init(stringLiteral)
|
||||
}
|
||||
|
||||
public init(unicodeScalarLiteral: String) {
|
||||
self.init(unicodeScalarLiteral)
|
||||
}
|
||||
|
||||
public init(extendedGraphemeClusterLiteral: String) {
|
||||
self.init(extendedGraphemeClusterLiteral)
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return original
|
||||
}
|
||||
|
||||
public static func == (lhs: Name, rhs: Name) -> Bool {
|
||||
return lhs.lowercased == rhs.lowercased
|
||||
}
|
||||
|
||||
// https://www.iana.org/assignments/message-headers/message-headers.xhtml
|
||||
// Permanent Message Header Field Names
|
||||
public static let aIM = Name("A-IM")
|
||||
public static let accept = Name("Accept")
|
||||
public static let acceptAdditions = Name("Accept-Additions")
|
||||
public static let acceptCharset = Name("Accept-Charset")
|
||||
public static let acceptDatetime = Name("Accept-Datetime")
|
||||
public static let acceptEncoding = Name("Accept-Encoding")
|
||||
public static let acceptFeatures = Name("Accept-Features")
|
||||
public static let acceptLanguage = Name("Accept-Language")
|
||||
public static let acceptPatch = Name("Accept-Patch")
|
||||
public static let acceptPost = Name("Accept-Post")
|
||||
public static let acceptRanges = Name("Accept-Ranges")
|
||||
public static let age = Name("Age")
|
||||
public static let allow = Name("Allow")
|
||||
public static let alpn = Name("ALPN")
|
||||
public static let altSvc = Name("Alt-Svc")
|
||||
public static let altUsed = Name("Alt-Used")
|
||||
public static let alternates = Name("Alternates")
|
||||
public static let applyToRedirectRef = Name("Apply-To-Redirect-Ref")
|
||||
public static let authenticationControl = Name("Authentication-Control")
|
||||
public static let authenticationInfo = Name("Authentication-Info")
|
||||
public static let authorization = Name("Authorization")
|
||||
public static let cExt = Name("C-Ext")
|
||||
public static let cMan = Name("C-Man")
|
||||
public static let cOpt = Name("C-Opt")
|
||||
public static let cPEP = Name("C-PEP")
|
||||
public static let cPEPInfo = Name("C-PEP-Info")
|
||||
public static let cacheControl = Name("Cache-Control")
|
||||
public static let calDAVTimezones = Name("CalDAV-Timezones")
|
||||
public static let close = Name("Close")
|
||||
public static let connection = Name("Connection")
|
||||
public static let contentBase = Name("Content-Base")
|
||||
public static let contentDisposition = Name("Content-Disposition")
|
||||
public static let contentEncoding = Name("Content-Encoding")
|
||||
public static let contentID = Name("Content-ID")
|
||||
public static let contentLanguage = Name("Content-Language")
|
||||
public static let contentLength = Name("Content-Length")
|
||||
public static let contentLocation = Name("Content-Location")
|
||||
public static let contentMD5 = Name("Content-MD5")
|
||||
public static let contentRange = Name("Content-Range")
|
||||
public static let contentScriptType = Name("Content-Script-Type")
|
||||
public static let contentStyleType = Name("Content-Style-Type")
|
||||
public static let contentType = Name("Content-Type")
|
||||
public static let contentVersion = Name("Content-Version")
|
||||
public static let cookie = Name("Cookie")
|
||||
public static let cookie2 = Name("Cookie2")
|
||||
public static let dasl = Name("DASL")
|
||||
public static let dav = Name("DAV")
|
||||
public static let date = Name("Date")
|
||||
public static let defaultStyle = Name("Default-Style")
|
||||
public static let deltaBase = Name("Delta-Base")
|
||||
public static let depth = Name("Depth")
|
||||
public static let derivedFrom = Name("Derived-From")
|
||||
public static let destination = Name("Destination")
|
||||
public static let differentialID = Name("Differential-ID")
|
||||
public static let digest = Name("Digest")
|
||||
public static let eTag = Name("ETag")
|
||||
public static let expect = Name("Expect")
|
||||
public static let expires = Name("Expires")
|
||||
public static let ext = Name("Ext")
|
||||
public static let forwarded = Name("Forwarded")
|
||||
public static let from = Name("From")
|
||||
public static let getProfile = Name("GetProfile")
|
||||
public static let hobareg = Name("Hobareg")
|
||||
public static let host = Name("Host")
|
||||
public static let http2Settings = Name("HTTP2-Settings")
|
||||
public static let im = Name("IM")
|
||||
public static let `if` = Name("If")
|
||||
public static let ifMatch = Name("If-Match")
|
||||
public static let ifModifiedSince = Name("If-Modified-Since")
|
||||
public static let ifNoneMatch = Name("If-None-Match")
|
||||
public static let ifRange = Name("If-Range")
|
||||
public static let ifScheduleTagMatch = Name("If-Schedule-Tag-Match")
|
||||
public static let ifUnmodifiedSince = Name("If-Unmodified-Since")
|
||||
public static let keepAlive = Name("Keep-Alive")
|
||||
public static let label = Name("Label")
|
||||
public static let lastModified = Name("Last-Modified")
|
||||
public static let link = Name("Link")
|
||||
public static let location = Name("Location")
|
||||
public static let lockToken = Name("Lock-Token")
|
||||
public static let man = Name("Man")
|
||||
public static let maxForwards = Name("Max-Forwards")
|
||||
public static let mementoDatetime = Name("Memento-Datetime")
|
||||
public static let meter = Name("Meter")
|
||||
public static let mimeVersion = Name("MIME-Version")
|
||||
public static let negotiate = Name("Negotiate")
|
||||
public static let opt = Name("Opt")
|
||||
public static let optionalWWWAuthenticate = Name("Optional-WWW-Authenticate")
|
||||
public static let orderingType = Name("Ordering-Type")
|
||||
public static let origin = Name("Origin")
|
||||
public static let overwrite = Name("Overwrite")
|
||||
public static let p3p = Name("P3P")
|
||||
public static let pep = Name("PEP")
|
||||
public static let picsLabel = Name("PICS-Label")
|
||||
public static let pepInfo = Name("Pep-Info")
|
||||
public static let position = Name("Position")
|
||||
public static let pragma = Name("Pragma")
|
||||
public static let prefer = Name("Prefer")
|
||||
public static let preferenceApplied = Name("Preference-Applied")
|
||||
public static let profileObject = Name("ProfileObject")
|
||||
public static let `protocol` = Name("Protocol")
|
||||
public static let protocolInfo = Name("Protocol-Info")
|
||||
public static let protocolQuery = Name("Protocol-Query")
|
||||
public static let protocolRequest = Name("Protocol-Request")
|
||||
public static let proxyAuthenticate = Name("Proxy-Authenticate")
|
||||
public static let proxyAuthenticationInfo = Name("Proxy-Authentication-Info")
|
||||
public static let proxyAuthorization = Name("Proxy-Authorization")
|
||||
public static let proxyFeatures = Name("Proxy-Features")
|
||||
public static let proxyInstruction = Name("Proxy-Instruction")
|
||||
public static let `public` = Name("Public")
|
||||
public static let publicKeyPins = Name("Public-Key-Pins")
|
||||
public static let publicKeyPinsReportOnly = Name("Public-Key-Pins-Report-Only")
|
||||
public static let range = Name("Range")
|
||||
public static let redirectRef = Name("Redirect-Ref")
|
||||
public static let referer = Name("Referer")
|
||||
public static let retryAfter = Name("Retry-After")
|
||||
public static let safe = Name("Safe")
|
||||
public static let scheduleReply = Name("Schedule-Reply")
|
||||
public static let scheduleTag = Name("Schedule-Tag")
|
||||
public static let secWebSocketAccept = Name("Sec-WebSocket-Accept")
|
||||
public static let secWebSocketExtensions = Name("Sec-WebSocket-Extensions")
|
||||
public static let secWebSocketKey = Name("Sec-WebSocket-Key")
|
||||
public static let secWebSocketProtocol = Name("Sec-WebSocket-Protocol")
|
||||
public static let secWebSocketVersion = Name("Sec-WebSocket-Version")
|
||||
public static let securityScheme = Name("Security-Scheme")
|
||||
public static let server = Name("Server")
|
||||
public static let setCookie = Name("Set-Cookie")
|
||||
public static let setCookie2 = Name("Set-Cookie2")
|
||||
public static let setProfile = Name("SetProfile")
|
||||
public static let slug = Name("SLUG")
|
||||
public static let soapAction = Name("SoapAction")
|
||||
public static let statusURI = Name("Status-URI")
|
||||
public static let strictTransportSecurity = Name("Strict-Transport-Security")
|
||||
public static let surrogateCapability = Name("Surrogate-Capability")
|
||||
public static let surrogateControl = Name("Surrogate-Control")
|
||||
public static let tcn = Name("TCN")
|
||||
public static let te = Name("TE")
|
||||
public static let timeout = Name("Timeout")
|
||||
public static let topic = Name("Topic")
|
||||
public static let trailer = Name("Trailer")
|
||||
public static let transferEncoding = Name("Transfer-Encoding")
|
||||
public static let ttl = Name("TTL")
|
||||
public static let urgency = Name("Urgency")
|
||||
public static let uri = Name("URI")
|
||||
public static let upgrade = Name("Upgrade")
|
||||
public static let userAgent = Name("User-Agent")
|
||||
public static let variantVary = Name("Variant-Vary")
|
||||
public static let vary = Name("Vary")
|
||||
public static let via = Name("Via")
|
||||
public static let wwwAuthenticate = Name("WWW-Authenticate")
|
||||
public static let wantDigest = Name("Want-Digest")
|
||||
public static let warning = Name("Warning")
|
||||
public static let xFrameOptions = Name("X-Frame-Options")
|
||||
|
||||
// https://www.iana.org/assignments/message-headers/message-headers.xhtml
|
||||
// Provisional Message Header Field Names
|
||||
public static let accessControl = Name("Access-Control")
|
||||
public static let accessControlAllowCredentials = Name("Access-Control-Allow-Credentials")
|
||||
public static let accessControlAllowHeaders = Name("Access-Control-Allow-Headers")
|
||||
public static let accessControlAllowMethods = Name("Access-Control-Allow-Methods")
|
||||
public static let accessControlAllowOrigin = Name("Access-Control-Allow-Origin")
|
||||
public static let accessControlMaxAge = Name("Access-Control-Max-Age")
|
||||
public static let accessControlRequestMethod = Name("Access-Control-Request-Method")
|
||||
public static let accessControlRequestHeaders = Name("Access-Control-Request-Headers")
|
||||
public static let compliance = Name("Compliance")
|
||||
public static let contentTransferEncoding = Name("Content-Transfer-Encoding")
|
||||
public static let cost = Name("Cost")
|
||||
public static let ediintFeatures = Name("EDIINT-Features")
|
||||
public static let messageID = Name("Message-ID")
|
||||
public static let methodCheck = Name("Method-Check")
|
||||
public static let methodCheckExpires = Name("Method-Check-Expires")
|
||||
public static let nonCompliance = Name("Non-Compliance")
|
||||
public static let optional = Name("Optional")
|
||||
public static let refererRoot = Name("Referer-Root")
|
||||
public static let resolutionHint = Name("Resolution-Hint")
|
||||
public static let resolverLocation = Name("Resolver-Location")
|
||||
public static let subOK = Name("SubOK")
|
||||
public static let subst = Name("Subst")
|
||||
public static let title = Name("Title")
|
||||
public static let uaColor = Name("UA-Color")
|
||||
public static let uaMedia = Name("UA-Media")
|
||||
public static let uaPixels = Name("UA-Pixels")
|
||||
public static let uaResolution = Name("UA-Resolution")
|
||||
public static let uaWindowpixels = Name("UA-Windowpixels")
|
||||
public static let version = Name("Version")
|
||||
public static let xDeviceAccept = Name("X-Device-Accept")
|
||||
public static let xDeviceAcceptCharset = Name("X-Device-Accept-Charset")
|
||||
public static let xDeviceAcceptEncoding = Name("X-Device-Accept-Encoding")
|
||||
public static let xDeviceAcceptLanguage = Name("X-Device-Accept-Language")
|
||||
public static let xDeviceUserAgent = Name("X-Device-User-Agent")
|
||||
}
|
||||
}
|
||||
@@ -179,7 +179,7 @@ public class StreamingParser: HTTPResponseWriter {
|
||||
}
|
||||
case .headerValueReceived:
|
||||
if let parserBuffer = self.parserBuffer, let lastHeaderName = self.lastHeaderName, let headerValue = String(data:parserBuffer, encoding: .utf8) {
|
||||
self.parsedHeaders.append(newHeader: (lastHeaderName, headerValue))
|
||||
self.parsedHeaders.append([HTTPHeaders.Name(lastHeaderName): headerValue])
|
||||
self.lastHeaderName = nil
|
||||
self.parserBuffer=nil
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,7 @@ class EchoWebApp: WebAppContaining {
|
||||
res.writeResponse(HTTPResponse(httpVersion: req.httpVersion,
|
||||
status: .ok,
|
||||
transferEncoding: .chunked,
|
||||
headers: HTTPHeaders([("X-foo", "bar")])))
|
||||
headers: ["X-foo": "bar"]))
|
||||
return .processBody { (chunk, stop) in
|
||||
switch chunk {
|
||||
case .chunk(let data, let finishedProcessing):
|
||||
|
||||
@@ -16,7 +16,7 @@ class HelloWorldKeepAliveWebApp: WebAppContaining {
|
||||
res.writeResponse(HTTPResponse(httpVersion: req.httpVersion,
|
||||
status: .ok,
|
||||
transferEncoding: .chunked,
|
||||
headers: HTTPHeaders([("Connection","Keep-Alive"),("Keep-Alive","timeout=5, max=10")])))
|
||||
headers: ["Connection": "Keep-Alive", "Keep-Alive": "timeout=5, max=10"]))
|
||||
return .processBody { (chunk, stop) in
|
||||
switch chunk {
|
||||
case .chunk(_, let finishedProcessing):
|
||||
|
||||
@@ -16,7 +16,7 @@ class HelloWorldWebApp: WebAppContaining {
|
||||
res.writeResponse(HTTPResponse(httpVersion: req.httpVersion,
|
||||
status: .ok,
|
||||
transferEncoding: .chunked,
|
||||
headers: HTTPHeaders([("X-foo", "bar")])))
|
||||
headers: ["X-foo": "bar"]))
|
||||
return .processBody { (chunk, stop) in
|
||||
switch chunk {
|
||||
case .chunk(_, let finishedProcessing):
|
||||
|
||||
@@ -13,7 +13,7 @@ import XCTest
|
||||
|
||||
class ServerTests: XCTestCase {
|
||||
func testResponseOK() {
|
||||
let request = HTTPRequest(method: .GET, target:"/echo", httpVersion: HTTPVersion(major: 1, minor: 1), headers: HTTPHeaders([("X-foo", "bar")]))
|
||||
let request = HTTPRequest(method: .GET, target:"/echo", httpVersion: HTTPVersion(major: 1, minor: 1), headers: ["X-foo": "bar"])
|
||||
let resolver = TestResponseResolver(request: request, requestBody: Data())
|
||||
resolver.resolveHandler(EchoWebApp().serve)
|
||||
XCTAssertNotNil(resolver.response)
|
||||
@@ -23,7 +23,7 @@ class ServerTests: XCTestCase {
|
||||
|
||||
func testEcho() {
|
||||
let testString="This is a test"
|
||||
let request = HTTPRequest(method: .POST, target:"/echo", httpVersion: HTTPVersion(major: 1, minor: 1), headers: HTTPHeaders([("X-foo", "bar")]))
|
||||
let request = HTTPRequest(method: .POST, target:"/echo", httpVersion: HTTPVersion(major: 1, minor: 1), headers: ["X-foo": "bar"])
|
||||
let resolver = TestResponseResolver(request: request, requestBody: testString.data(using: .utf8)!)
|
||||
resolver.resolveHandler(EchoWebApp().serve)
|
||||
XCTAssertNotNil(resolver.response)
|
||||
@@ -33,7 +33,7 @@ class ServerTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testHello() {
|
||||
let request = HTTPRequest(method: .GET, target:"/helloworld", httpVersion: HTTPVersion(major: 1, minor: 1), headers: HTTPHeaders([("X-foo", "bar")]))
|
||||
let request = HTTPRequest(method: .GET, target:"/helloworld", httpVersion: HTTPVersion(major: 1, minor: 1), headers: ["X-foo": "bar"])
|
||||
let resolver = TestResponseResolver(request: request, requestBody: Data())
|
||||
resolver.resolveHandler(HelloWorldWebApp().serve)
|
||||
XCTAssertNotNil(resolver.response)
|
||||
@@ -43,13 +43,13 @@ class ServerTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testSimpleHello() {
|
||||
let request = HTTPRequest(method: .GET, target:"/helloworld", httpVersion: HTTPVersion(major: 1, minor: 1), headers: HTTPHeaders([("X-foo", "bar")]))
|
||||
let request = HTTPRequest(method: .GET, target:"/helloworld", httpVersion: HTTPVersion(major: 1, minor: 1), headers: ["X-foo": "bar"])
|
||||
let resolver = TestResponseResolver(request: request, requestBody: Data())
|
||||
let simpleHelloWebApp = SimpleResponseCreator { (request, body) -> (reponse: HTTPResponse, responseBody: Data) in
|
||||
return (HTTPResponse(httpVersion: request.httpVersion,
|
||||
status: .ok,
|
||||
transferEncoding: .chunked,
|
||||
headers: HTTPHeaders([("X-foo", "bar")])),
|
||||
headers: ["X-foo": "bar"]),
|
||||
"Hello, World!".data(using: .utf8)!)
|
||||
|
||||
}
|
||||
@@ -96,7 +96,7 @@ class ServerTests: XCTestCase {
|
||||
return (HTTPResponse(httpVersion: request.httpVersion,
|
||||
status: .ok,
|
||||
transferEncoding: .chunked,
|
||||
headers: HTTPHeaders([("X-foo", "bar")])),
|
||||
headers: ["X-foo": "bar"]),
|
||||
"Hello, World!".data(using: .utf8)!)
|
||||
|
||||
}
|
||||
|
||||
@@ -12,21 +12,51 @@ import XCTest
|
||||
|
||||
class HeadersTests: XCTestCase {
|
||||
func testHeaders() {
|
||||
var headers = HTTPHeaders()
|
||||
var headers: HTTPHeaders = [
|
||||
.accept: "text/html",
|
||||
"Accept": "application/xhtml+xml",
|
||||
"Accept": "application/xml;q=0.9",
|
||||
"accept": "image/webp",
|
||||
.accept: "*/*;q=0.8",
|
||||
"Accept-Language": "ru-RU,ru;q=0.8",
|
||||
.acceptLanguage: "en-US;q=0.6",
|
||||
"accept-language": "en;q=0.4",
|
||||
"Content-Length": "200",
|
||||
"Set-Cookie": "test1=0; expires=Tue, 21 Jun 2016 16:26:50 GMT; path=/; domain=.my.mail.ru",
|
||||
"Set-Cookie": "test2=0; expires=Tue, 21 Jun 2016 16:26:50 GMT; path=/; domain=.my.mail.ru",
|
||||
]
|
||||
|
||||
XCTAssertEqual(headers["Content-Length"], "200")
|
||||
XCTAssertEqual(headers["content-length"], "200")
|
||||
XCTAssertEqual(headers[.contentLength], "200")
|
||||
XCTAssertEqual(headers[.accept], "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
|
||||
XCTAssertEqual(headers[.acceptLanguage], "ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4")
|
||||
XCTAssertEqual(headers[.setCookie], "test1=0; expires=Tue, 21 Jun 2016 16:26:50 GMT; path=/; domain=.my.mail.ru")
|
||||
|
||||
XCTAssertEqual(headers[valuesFor: "Content-Length"], ["200"])
|
||||
XCTAssertEqual(headers[valuesFor: "content-length"], ["200"])
|
||||
XCTAssertEqual(headers[valuesFor: .contentLength], ["200"])
|
||||
XCTAssertEqual(headers[valuesFor: .accept], ["text/html", "application/xhtml+xml", "application/xml;q=0.9", "image/webp", "*/*;q=0.8"])
|
||||
XCTAssertEqual(headers[valuesFor: .setCookie], [
|
||||
"test1=0; expires=Tue, 21 Jun 2016 16:26:50 GMT; path=/; domain=.my.mail.ru",
|
||||
"test2=0; expires=Tue, 21 Jun 2016 16:26:50 GMT; path=/; domain=.my.mail.ru",
|
||||
])
|
||||
|
||||
headers = HTTPHeaders()
|
||||
let initialCount = headers.makeIterator().reduce(0) { (last, element) -> Int in return last + 1 }
|
||||
XCTAssertEqual(0, initialCount)
|
||||
|
||||
headers.append(newHeader: ("Test-Header","Test Value"))
|
||||
headers.append(["Test-Header": "Test Value"])
|
||||
let nextCount = headers.makeIterator().reduce(0) { (last, element) -> Int in return last + 1 }
|
||||
XCTAssertEqual(1, nextCount)
|
||||
|
||||
let testHeaderValueArray = headers["test-header"]
|
||||
let testHeaderValueArray = headers[valuesFor: "test-header"]
|
||||
XCTAssertNotNil(testHeaderValueArray)
|
||||
XCTAssertEqual(1,testHeaderValueArray.count)
|
||||
XCTAssertEqual("Test Value",testHeaderValueArray.first ?? "Not Found")
|
||||
|
||||
headers.append(newHeader: ("Test-header","Test Value 2"))
|
||||
let testHeaderValueArray2 = headers["test-header"]
|
||||
headers.append(["Test-header": "Test Value 2"])
|
||||
let testHeaderValueArray2 = headers[valuesFor: "test-header"]
|
||||
XCTAssertNotNil(testHeaderValueArray2)
|
||||
XCTAssertEqual(2,testHeaderValueArray2.count)
|
||||
XCTAssertEqual("Test Value",testHeaderValueArray2.first ?? "Not Found")
|
||||
@@ -34,14 +64,14 @@ class HeadersTests: XCTestCase {
|
||||
XCTAssertEqual("Test Value 2",testHeaderValueArray2Remainder.first ?? "Not Found")
|
||||
|
||||
//This should overwrites, since the subscript is documented to use lowercase keys
|
||||
headers["TEST-HEADER"]=["Test Value 3"]
|
||||
let testHeaderValueArray3 = headers["test-header"]
|
||||
headers[valuesFor: "TEST-HEADER"]=["Test Value 3"]
|
||||
let testHeaderValueArray3 = headers[valuesFor: "test-header"]
|
||||
XCTAssertNotNil(testHeaderValueArray3)
|
||||
XCTAssertEqual(1,testHeaderValueArray3.count)
|
||||
|
||||
//Overwrite
|
||||
headers["TEST-HEADER"]=["Test Value 4a","Test Value 4b"]
|
||||
let testHeaderValueArray4 = headers["test-header"]
|
||||
headers[valuesFor: "TEST-HEADER"]=["Test Value 4a","Test Value 4b"]
|
||||
let testHeaderValueArray4 = headers[valuesFor: "test-header"]
|
||||
XCTAssertNotNil(testHeaderValueArray4)
|
||||
XCTAssertEqual(2,testHeaderValueArray4.count)
|
||||
XCTAssertEqual("Test Value 4a",testHeaderValueArray4.first ?? "Not Found")
|
||||
|
||||
Reference in New Issue
Block a user