-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from SwiftOnEdge/improvements
Improved Request and Response APIs
- Loading branch information
Showing
9 changed files
with
357 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// | ||
// Message.swift | ||
// Edge | ||
// | ||
// Created by Tyler Fleming Cloutier on 10/30/16. | ||
// | ||
// | ||
|
||
import Foundation | ||
|
||
public protocol HTTPMessage { | ||
var version: Version { get set } | ||
var rawHeaders: [String] { get set } | ||
var headers: [String:String] { get } | ||
var cookies: [String] { get } | ||
var body: [UInt8] { get set } | ||
} | ||
|
||
public extension HTTPMessage { | ||
|
||
/// Groups the `rawHeaders` into key-value pairs. If there is an odd number | ||
/// of `rawHeaders`, the last value will be discarded. | ||
var rawHeaderPairs: [(String, String)] { | ||
return stride(from: 0, to: self.rawHeaders.count, by: 2).flatMap { | ||
let chunk = rawHeaders[$0..<min($0 + 2, rawHeaders.count)] | ||
if let first = chunk.first, let last = chunk.last { | ||
return (first, last) | ||
} | ||
return nil | ||
} | ||
} | ||
|
||
/// The same as `rawHeaderPairs` with the key lowercased. | ||
var lowercasedRawHeaderPairs: [(String, String)] { | ||
return rawHeaderPairs.map { ($0.0.lowercased(), $0.1) } | ||
} | ||
|
||
/// Duplicates are handled in a way very similar to the way they are handled | ||
/// by Node.js. Which is to say that duplicates in the raw headers are handled as follows. | ||
/// | ||
/// * Duplicates of age, authorization, content-length, content-type, etag, expires, from, | ||
/// host, if-modified-since, if-unmodified-since, last-modified, location, max-forwards, | ||
/// proxy-authorization, referer, retry-after, or user-agent are discarded. | ||
/// * set-cookie is *excluded* from the formatted headers are handled by the request and | ||
/// response. The cookies field on the Request and Response objects can be users to get | ||
/// and set the cookies. | ||
/// * For all other headers, the values are joined together with ', '. | ||
/// | ||
/// The rawHeaders are processed from the 0th index forward. | ||
var headers: [String:String] { | ||
get { | ||
var headers: [String:String] = [:] | ||
let discardable = Set([ | ||
"age", | ||
"authorization", | ||
"content-length", | ||
"content-type", | ||
"etag", | ||
"expires", | ||
"from", | ||
"host", | ||
"if-modified-since", | ||
"if-unmodified-since", | ||
"last-modified", | ||
"location", | ||
"max-forwards", | ||
"proxy-authorization", | ||
"referer", | ||
"retry-after", | ||
"user-agent" | ||
]) | ||
let cookies = Set([ | ||
"set-cookie", | ||
"cookie" | ||
]) | ||
for (key, value) in lowercasedRawHeaderPairs { | ||
guard !cookies.contains(key) else { | ||
continue | ||
} | ||
if let currentValue = headers[key] { | ||
if discardable.contains(key) { | ||
headers[key] = value | ||
} else { | ||
headers[key] = [currentValue, value].joined(separator: ", ") | ||
} | ||
} else { | ||
headers[key] = value | ||
} | ||
} | ||
return headers | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// | ||
// HTTPMessageTests.swift | ||
// Edge | ||
// | ||
// Created by Tyler Fleming Cloutier on 10/30/16. | ||
// | ||
// | ||
|
||
import Foundation | ||
import XCTest | ||
@testable import HTTP | ||
|
||
class HTTPMessageTests: XCTestCase { | ||
|
||
|
||
struct TestMessageType: HTTPMessage { | ||
var version = Version(major: 1, minor: 1) | ||
var rawHeaders: [String] = [] | ||
var cookies: [String] { | ||
return lowercasedRawHeaderPairs.filter { (key, value) in | ||
key == "set-cookie" | ||
}.map { $0.1 } | ||
} | ||
var body: [UInt8] = [] | ||
} | ||
|
||
func testHeaders() { | ||
var testMessage = TestMessageType() | ||
testMessage.rawHeaders = [ | ||
"Date", "Sun, 30 Oct 2016 09:06:40 GMT", | ||
"Expires", "-1", | ||
"Cache-Control", "private, max-age=0", | ||
"Content-Type", "application/json", | ||
"Content-Type", "text/html; charset=ISO-8859-1", | ||
"P3P","CP=\"See https://www.google.com/support/accounts/answer/151657?hl=en for more info.\"", | ||
"Server", "gws", | ||
"Server", "gws", // Duplicate servers for test purposes. | ||
"X-XSS-Protection", "1; mode=block", | ||
"X-Frame-Options", "SAMEORIGIN", | ||
"Set-Cookie", "NID=89=c6V5PAWCEOXgvA6TQrNSR8Pnih2iX3Aa3rIQS005IG6WS8RHH" + | ||
"_3YTmymtEk5yMxLkz19C_qr2zBNspy7zwubAVo38-kIdjbArSJcXCBbjCcn_hJ" + | ||
"TEi9grq_ZgHxZTZ5V2YLnH3uxx6U4EA; expires=Mon, 01-May-2017 09:06:40 GMT;" + | ||
" path=/; domain=.google.com; HttpOnly", | ||
"Accept-Ranges", "none", | ||
"Vary", "Accept-Encoding", | ||
"Transfer-Encoding", "chunked" | ||
] | ||
let expectedHeaders = [ | ||
"date": "Sun, 30 Oct 2016 09:06:40 GMT", | ||
"expires": "-1", | ||
"cache-control": "private, max-age=0", | ||
"content-type": "text/html; charset=ISO-8859-1", | ||
"p3p": "CP=\"See https://www.google.com/support/accounts/answer/151657?hl=en for more info.\"", | ||
"server": "gws, gws", | ||
"x-xss-protection": "1; mode=block", | ||
"x-frame-options": "SAMEORIGIN", | ||
"accept-ranges": "none", | ||
"vary": "Accept-Encoding", | ||
"transfer-encoding": "chunked" | ||
] | ||
XCTAssert(testMessage.headers == expectedHeaders, "Actual headers, \(testMessage.headers), did not match expected.") | ||
let expectedCookies = [ | ||
"NID=89=c6V5PAWCEOXgvA6TQrNSR8Pnih2iX3Aa3rIQS005IG6WS8RHH" + | ||
"_3YTmymtEk5yMxLkz19C_qr2zBNspy7zwubAVo38-kIdjbArSJcXCBbjCcn_hJ" + | ||
"TEi9grq_ZgHxZTZ5V2YLnH3uxx6U4EA; expires=Mon, 01-May-2017 09:06:40 GMT;" + | ||
" path=/; domain=.google.com; HttpOnly" | ||
] | ||
XCTAssert(testMessage.cookies == expectedCookies, "Actual cookies, \(testMessage.cookies), did not match expected.") | ||
|
||
} | ||
|
||
} | ||
|
||
extension HTTPMessageTests { | ||
static var allTests = [ | ||
("testHeaders", testHeaders), | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// | ||
// RequestSerializationTests.swift | ||
// Edge | ||
// | ||
// Created by Tyler Fleming Cloutier on 10/30/16. | ||
// | ||
// | ||
|
||
import Foundation | ||
import XCTest | ||
@testable import HTTP | ||
|
||
class RequestSerializationTests: XCTestCase { | ||
|
||
func testBasicSerialization() { | ||
let expected = "GET / HTTP/1.1\r\n\r\n" | ||
let request = Request( | ||
method: .get, | ||
uri: URL(string: "/")!, | ||
version: Version(major: 1, minor: 1), | ||
rawHeaders: [], | ||
body: [] | ||
) | ||
let actual = String(bytes: request.serialized, encoding: .utf8)! | ||
XCTAssert(expected == actual, "Actual request, \(actual), did not match expected.") | ||
} | ||
|
||
func testHeaderSerialization() { | ||
let expected = "GET / HTTP/1.1\r\nAccept: */*\r\nHost: www.google.com\r\nConnection: Keep-Alive\r\n\r\n" | ||
let request = Request( | ||
method: .get, | ||
uri: URL(string: "/")!, | ||
version: Version(major: 1, minor: 1), | ||
rawHeaders: ["Accept", "*/*", "Host", "www.google.com", "Connection", "Keep-Alive"], | ||
body: [] | ||
) | ||
let actual = String(bytes: request.serialized, encoding: .utf8)! | ||
XCTAssert(expected == actual, "Actual request, \(actual), did not match expected.") | ||
} | ||
|
||
func testDefaultParameters() { | ||
let expected = "GET / HTTP/1.1\r\n\r\n" | ||
let request = Request( | ||
method: .get, | ||
uri: URL(string: "/")! | ||
) | ||
let actual = String(bytes: request.serialized, encoding: .utf8)! | ||
XCTAssert(expected == actual, "Actual request, \(actual), did not match expected.") | ||
} | ||
|
||
} | ||
|
||
extension RequestSerializationTests { | ||
static var allTests = [ | ||
("testBasicSerialization", testBasicSerialization), | ||
("testHeaderSerialization", testHeaderSerialization), | ||
("testDefaultParameters", testDefaultParameters), | ||
] | ||
} |
Oops, something went wrong.