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:
aleksey-mashanov
2017-06-28 12:54:41 +01:00
committed by Chris Bailey
parent 002d9ce17b
commit 7e366e4687
9 changed files with 590 additions and 77 deletions
+205 -9
View File
@@ -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
-49
View File
@@ -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)
+336
View File
@@ -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")
}
}
+1 -1
View File
@@ -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):
+6 -6
View File
@@ -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)!)
}
+39 -9
View File
@@ -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")