├── .swift-version
├── SwiftHTTP.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
├── xcshareddata
│ └── xcschemes
│ │ ├── SwiftHTTP.xcscheme
│ │ └── SwiftHTTPOSX.xcscheme
└── project.pbxproj
├── Source
├── SwiftHTTP.h
├── Info.plist
├── Upload.swift
├── StatusCode.swift
├── HTTPSecurity.swift
├── Request.swift
└── Operation.swift
├── SwiftHTTP.podspec
├── Tests
├── Info.plist
└── SwiftHTTPTests.swift
├── Package.swift
├── .gitignore
├── CHANGELOG.md
├── LICENSE
└── README.md
/.swift-version:
--------------------------------------------------------------------------------
1 | 3.0
--------------------------------------------------------------------------------
/SwiftHTTP.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Source/SwiftHTTP.h:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftHTTP.h
3 | // SwiftHTTP
4 | //
5 | // Created by Austin Cherry on 9/16/14.
6 | // Copyright (c) 2014 Vluxe. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for SwiftHTTP.
12 | FOUNDATION_EXPORT double SwiftHTTPVersionNumber;
13 |
14 | //! Project version string for SwiftHTTP.
15 | FOUNDATION_EXPORT const unsigned char SwiftHTTPVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/SwiftHTTP.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "SwiftHTTP"
3 | s.version = "3.0.1"
4 | s.summary = "Thin wrapper around NSURLSession in Swift. Simplifies HTTP requests."
5 | s.homepage = "https://github.com/daltoniam/SwiftHTTP"
6 | s.license = 'Apache License, Version 2.0'
7 | s.author = {'Dalton Cherry' => 'http://daltoniam.com'}
8 | s.source = { :git => 'https://github.com/daltoniam/SwiftHTTP.git', :tag => "#{s.version}"}
9 | s.social_media_url = 'http://twitter.com/daltoniam'
10 | s.ios.deployment_target = '8.0'
11 | s.osx.deployment_target = '10.10'
12 | s.watchos.deployment_target = '2.0'
13 | s.tvos.deployment_target = '9.0'
14 | s.source_files = 'Source/*.swift'
15 | end
16 |
--------------------------------------------------------------------------------
/Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Package.Swift
3 | // SwiftHTTP
4 | //
5 | // Created by Dalton Cherry on 5/16/15.
6 | // Copyright (c) 2014-2015 Dalton Cherry.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import PackageDescription
22 |
23 | let package = Package(
24 | name: "SwiftHTTP"
25 | )
--------------------------------------------------------------------------------
/Source/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 3.0.1
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by http://www.gitignore.io
2 |
3 | ### OSX ###
4 | .DS_Store
5 | .AppleDouble
6 | .LSOverride
7 |
8 | # Icon must end with two \r
9 | Icon
10 |
11 |
12 | # Thumbnails
13 | ._*
14 |
15 | # Files that might appear on external disk
16 | .Spotlight-V100
17 | .Trashes
18 |
19 | # Directories potentially created on remote AFP share
20 | .AppleDB
21 | .AppleDesktop
22 | Network Trash Folder
23 | Temporary Items
24 | .apdisk
25 |
26 |
27 | ### Swift ###
28 | # Xcode
29 | #
30 | build/
31 | *.pbxuser
32 | !default.pbxuser
33 | *.mode1v3
34 | !default.mode1v3
35 | *.mode2v3
36 | !default.mode2v3
37 | *.perspectivev3
38 | !default.perspectivev3
39 | xcuserdata
40 | *.xccheckout
41 | *.moved-aside
42 | DerivedData
43 | *.hmap
44 | *.ipa
45 | *.xcuserstate
46 |
47 | # CocoaPods
48 | #
49 | # We recommend against adding the Pods directory to your .gitignore. However
50 | # you should judge for yourself, the pros and cons are mentioned at:
51 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
52 | #
53 | # Pods/
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file.
3 | `SwiftHTTP` adheres to [Semantic Versioning](http://semver.org/).
4 |
5 | #### [3.0.1](https://github.com/daltoniam/SwiftHTTP/tree/3.0.1)
6 |
7 | fixed:
8 | [#282](https://github.com/daltoniam/SwiftHTTP/pull/282)
9 | [#281](https://github.com/daltoniam/SwiftHTTP/pull/281)
10 | [#280](https://github.com/daltoniam/SwiftHTTP/issues/280)
11 |
12 |
13 | Small bug fixes.
14 |
15 | #### [3.0.0](https://github.com/daltoniam/SwiftHTTP/tree/3.0.0)
16 |
17 | Refactor that has a simpler design. Single framework (no more platform suffixes! e.g. SwiftHTTPOSX, SwiftHTTPTVOS, etc).
18 |
19 | fixed:
20 | [#278](https://github.com/daltoniam/SwiftHTTP/issues/278)
21 | [#272](https://github.com/daltoniam/SwiftHTTP/pull/272)
22 | [#251](https://github.com/daltoniam/SwiftHTTP/issues/251)
23 | [#226](https://github.com/daltoniam/SwiftHTTP/issues/226)
24 | [#140](https://github.com/daltoniam/SwiftHTTP/issues/140)
25 |
26 | #### [2.1.0](https://github.com/daltoniam/SwiftHTTP/tree/2.1.0)
27 |
28 | Update for Swift 4 support
29 |
30 | fixed:
31 | [#259](https://github.com/daltoniam/SwiftHTTP/issues/259)
32 | [#265](https://github.com/daltoniam/SwiftHTTP/issues/265)
33 |
34 | #### [2.0.2](https://github.com/daltoniam/SwiftHTTP/tree/2.0.2)
35 |
36 | Small fix for JSON serializer.
37 |
38 | #### [2.0.1](https://github.com/daltoniam/SwiftHTTP/tree/2.0.1)
39 |
40 | Addresses bugs from the Swift 3 port.
41 |
42 | fixed:
43 | [#249](https://github.com/daltoniam/SwiftHTTP/issues/249)
44 | [#248](https://github.com/daltoniam/SwiftHTTP/issues/248)
45 |
46 | #### [2.0.0](https://github.com/daltoniam/SwiftHTTP/tree/2.0.0)
47 |
48 | Swift 3.0 update.
49 |
50 | fixed:
51 | [#192](https://github.com/daltoniam/SwiftHTTP/issues/192)
52 | [#240](https://github.com/daltoniam/SwiftHTTP/issues/240)
53 | [#241](https://github.com/daltoniam/SwiftHTTP/issues/241)
54 | [#245](https://github.com/daltoniam/SwiftHTTP/issues/245)
55 |
56 | #### [1.0.5](https://github.com/daltoniam/SwiftHTTP/tree/1.0.5)
57 |
58 | Swift 2.2 update.
59 |
60 | #### [1.0.4](https://github.com/daltoniam/SwiftHTTP/tree/1.0.4)
61 |
62 | fixed:
63 | [#215](https://github.com/daltoniam/SwiftHTTP/issues/215)
64 |
65 | #### [1.0.3](https://github.com/daltoniam/SwiftHTTP/tree/1.0.3)
66 |
67 | Changed:
68 | Add Swift package manager support
69 | TVOS support
70 | WatchOS support
71 |
72 | fixed:
73 | [#195](https://github.com/daltoniam/SwiftHTTP/issues/195)
74 | [#203](https://github.com/daltoniam/SwiftHTTP/issues/203)
75 |
76 | #### [1.0.2](https://github.com/daltoniam/SwiftHTTP/tree/1.0.2)
77 |
78 | Parameter encoding fixes ([#178](https://github.com/daltoniam/SwiftHTTP/issues/178), [#182](https://github.com/daltoniam/SwiftHTTP/issues/182)).
79 | Progress Closure.
80 | [Global handlers](https://github.com/daltoniam/SwiftHTTP#global-handlers).
81 |
82 | #### [1.0.1](https://github.com/daltoniam/SwiftHTTP/tree/1.0.1)
83 |
84 | Custom header support in `HTTP` factory methods.
85 |
86 | #### [1.0.1](https://github.com/daltoniam/SwiftHTTP/tree/1.0.1)
87 |
88 | Custom header support in `HTTP` factory methods.
89 |
90 | #### [1.0.0](https://github.com/daltoniam/SwiftHTTP/tree/1.0.0)
91 |
92 | API refactor and first release of Swift 2 support.
93 |
--------------------------------------------------------------------------------
/SwiftHTTP.xcodeproj/xcshareddata/xcschemes/SwiftHTTP.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
35 |
36 |
47 |
48 |
54 |
55 |
56 |
57 |
58 |
59 |
65 |
66 |
72 |
73 |
74 |
75 |
77 |
78 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/Tests/SwiftHTTPTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftHTTPTests.swift
3 | // SwiftHTTPTests
4 | //
5 | // Created by Austin Cherry on 9/16/14.
6 | // Copyright (c) 2014 Vluxe. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import SwiftHTTP
11 |
12 | class SwiftHTTPTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testGetRequest() {
25 | let expectation = self.expectation(description: "testGetRequest")
26 |
27 | do {
28 | let opt = try HTTP.GET("https://google.com", parameters: nil)
29 | opt.start { response in
30 | if response.error != nil {
31 | XCTAssert(false, "Failure")
32 | }
33 | XCTAssert(true, "Pass")
34 | expectation.fulfill()
35 | }
36 | } catch {
37 | XCTAssert(false, "Failure")
38 | }
39 | waitForExpectations(timeout: 30, handler: nil)
40 | }
41 |
42 | func testGetProgress() {
43 | let expectation1 = expectation(description: "testGetProgressFinished")
44 | let expectation2 = expectation(description: "testGetProgressIncremented")
45 |
46 | do {
47 | let opt = try HTTP.GET("http://photojournal.jpl.nasa.gov/tiff/PIA19330.tif", parameters: nil)
48 | var alreadyCheckedProgressIncremented: Bool = false
49 | opt.progress = { progress in
50 | if progress > 0 && !alreadyCheckedProgressIncremented {
51 | alreadyCheckedProgressIncremented = true
52 | XCTAssert(true, "Pass")
53 | expectation2.fulfill()
54 | }
55 | }
56 | opt.start { response in
57 | if response.error != nil {
58 | XCTAssert(false, "Failure")
59 | }
60 | XCTAssert(true, "Pass")
61 | expectation1.fulfill()
62 | }
63 | } catch {
64 | XCTAssert(false, "Failure")
65 | }
66 |
67 | waitForExpectations(timeout: 30, handler: nil)
68 | }
69 |
70 | func testOperationDependencies() {
71 | let expectation1 = expectation(description: "testOperationDependencies1")
72 | let expectation2 = expectation(description: "testOperationDependencies2")
73 |
74 | var operation1Finished = false
75 |
76 | let urlString1 = "http://photojournal.jpl.nasa.gov/tiff/PIA19330.tif" // (4.32 MB)
77 | let urlString2 = "http://photojournal.jpl.nasa.gov/jpeg/PIA19330.jpg" // (0.14 MB)
78 |
79 | let operationQueue = OperationQueue()
80 | operationQueue.maxConcurrentOperationCount = 2
81 |
82 | do {
83 | let opt1 = try HTTP.GET(urlString1, parameters: nil)
84 | opt1.onFinish = { response in
85 | if let err = response.error {
86 | XCTFail("request1 failed: \(err.localizedDescription)")
87 | }
88 | operation1Finished = true
89 | expectation1.fulfill()
90 | }
91 |
92 | let opt2 = try HTTP.GET(urlString2, parameters: nil)
93 | opt2.onFinish = { response in
94 | if let err = response.error {
95 | XCTFail("request2 failed: \(err.localizedDescription)")
96 | }
97 | XCTAssert(operation1Finished, "Operation 1 did not finish first")
98 | expectation2.fulfill()
99 | }
100 |
101 | opt2.addDependency(opt1)
102 | operationQueue.addOperation(opt1)
103 | operationQueue.addOperation(opt2)
104 |
105 | } catch {
106 | XCTAssert(false, "Failure")
107 | }
108 |
109 | waitForExpectations(timeout: 30, handler: nil)
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Source/Upload.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Upload.swift
3 | // SwiftHTTP
4 | //
5 | // Created by Dalton Cherry on 6/5/14.
6 | // Copyright (c) 2014 Vluxe. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | #if os(iOS)
12 | import MobileCoreServices
13 | #endif
14 |
15 | /**
16 | Upload errors
17 | */
18 | enum HTTPUploadError: Error {
19 | case noFileUrl
20 | case noData
21 | }
22 |
23 | /**
24 | This is how to upload files in SwiftHTTP. The upload object represents a file to upload by either a data blob or a url (which it reads off disk).
25 | */
26 | open class Upload: NSObject, NSCoding {
27 | var fileUrl: URL? {
28 | didSet {
29 | getMimeType()
30 | }
31 | }
32 | var mimeType: String?
33 | var data: Data?
34 | var fileName: String?
35 |
36 | /**
37 | Tries to determine the mime type from the fileUrl extension.
38 | */
39 | func getMimeType() {
40 | mimeType = "application/octet-stream"
41 | guard let url = fileUrl else { return }
42 | #if os(iOS) || os(OSX) //for watchOS support
43 | guard let UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, url.pathExtension as CFString, nil) else { return }
44 | guard let str = UTTypeCopyPreferredTagWithClass(UTI.takeRetainedValue(), kUTTagClassMIMEType) else { return }
45 | mimeType = str.takeRetainedValue() as String
46 | #endif
47 | }
48 |
49 | /**
50 | Reads the data from disk or from memory. Throws an error if no data or file is found.
51 | */
52 | open func getData() -> (data: Data?, error: Error?) {
53 | if let d = data {
54 | return (d, nil)
55 | }
56 | guard let url = fileUrl else { return (nil, HTTPUploadError.noFileUrl) }
57 | fileName = url.lastPathComponent
58 | do {
59 | let d = try Data(contentsOf: url, options: .mappedIfSafe)
60 | data = d
61 | getMimeType()
62 | return (d, nil)
63 | } catch let error {
64 | return (nil, error)
65 | }
66 | }
67 |
68 | /**
69 | Standard NSCoder support
70 | */
71 | open func encode(with aCoder: NSCoder) {
72 | aCoder.encode(self.fileUrl, forKey: "fileUrl")
73 | aCoder.encode(self.mimeType, forKey: "mimeType")
74 | aCoder.encode(self.fileName, forKey: "fileName")
75 | aCoder.encode(self.data, forKey: "data")
76 | }
77 |
78 | /**
79 | Required for NSObject support (because of NSCoder, it would be a struct otherwise!)
80 | */
81 | public override init() {
82 | super.init()
83 | }
84 |
85 | required public convenience init(coder aDecoder: NSCoder) {
86 | self.init()
87 | fileUrl = aDecoder.decodeObject(forKey: "fileUrl") as? URL
88 | mimeType = aDecoder.decodeObject(forKey: "mimeType") as? String
89 | fileName = aDecoder.decodeObject(forKey: "fileName") as? String
90 | data = aDecoder.decodeObject(forKey: "data") as? Data
91 | }
92 |
93 | /**
94 | Initializes a new Upload object with a fileUrl. The fileName and mimeType will be infered.
95 |
96 | -parameter fileUrl: The fileUrl is a standard url path to a file.
97 | */
98 | public convenience init(fileUrl: URL) {
99 | self.init()
100 | self.fileUrl = fileUrl
101 | }
102 |
103 | /**
104 | Initializes a new Upload object with a data blob.
105 |
106 | -parameter data: The data is a NSData representation of a file's data.
107 | -parameter fileName: The fileName is just that. The file's name.
108 | -parameter mimeType: The mimeType is just that. The mime type you would like the file to uploaded as.
109 | */
110 | ///upload a file from a a data blob. Must add a filename and mimeType as that can't be infered from the data
111 | public convenience init(data: Data, fileName: String, mimeType: String) {
112 | self.init()
113 | self.data = data
114 | self.fileName = fileName
115 | self.mimeType = mimeType
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/SwiftHTTP.xcodeproj/xcshareddata/xcschemes/SwiftHTTPOSX.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
45 |
46 |
48 |
54 |
55 |
56 |
57 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
80 |
81 |
87 |
88 |
89 |
90 |
91 |
92 |
98 |
99 |
105 |
106 |
107 |
108 |
110 |
111 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/Source/StatusCode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPStatusCode.swift
3 | // SwiftHTTP
4 | //
5 | // Created by Yu Kadowaki on 7/12/15.
6 | // Copyright (c) 2015 Vluxe. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// HTTP Status Code (RFC 2616)
12 | public enum HTTPStatusCode: Int {
13 | case `continue` = 100,
14 | switchingProtocols = 101
15 |
16 | case ok = 200,
17 | created = 201,
18 | accepted = 202,
19 | nonAuthoritativeInformation = 203,
20 | noContent = 204,
21 | resetContent = 205,
22 | partialContent = 206
23 |
24 | case multipleChoices = 300,
25 | movedPermanently = 301,
26 | found = 302,
27 | seeOther = 303,
28 | notModified = 304,
29 | useProxy = 305,
30 | unused = 306,
31 | temporaryRedirect = 307
32 |
33 | case badRequest = 400,
34 | unauthorized = 401,
35 | paymentRequired = 402,
36 | forbidden = 403,
37 | notFound = 404,
38 | methodNotAllowed = 405,
39 | notAcceptable = 406,
40 | proxyAuthenticationRequired = 407,
41 | requestTimeout = 408,
42 | conflict = 409,
43 | gone = 410,
44 | lengthRequired = 411,
45 | preconditionFailed = 412,
46 | requestEntityTooLarge = 413,
47 | requestUriTooLong = 414,
48 | unsupportedMediaType = 415,
49 | requestedRangeNotSatisfiable = 416,
50 | expectationFailed = 417
51 |
52 | case internalServerError = 500,
53 | notImplemented = 501,
54 | badGateway = 502,
55 | serviceUnavailable = 503,
56 | gatewayTimeout = 504,
57 | httpVersionNotSupported = 505
58 |
59 | case invalidUrl = -1001
60 |
61 | case unknownStatus = 0
62 |
63 | init(statusCode: Int) {
64 | self = HTTPStatusCode(rawValue: statusCode) ?? .unknownStatus
65 | }
66 |
67 | public var statusDescription: String {
68 | get {
69 | switch self {
70 | case .continue:
71 | return "Continue"
72 | case .switchingProtocols:
73 | return "Switching protocols"
74 | case .ok:
75 | return "OK"
76 | case .created:
77 | return "Created"
78 | case .accepted:
79 | return "Accepted"
80 | case .nonAuthoritativeInformation:
81 | return "Non authoritative information"
82 | case .noContent:
83 | return "No content"
84 | case .resetContent:
85 | return "Reset content"
86 | case .partialContent:
87 | return "Partial Content"
88 | case .multipleChoices:
89 | return "Multiple choices"
90 | case .movedPermanently:
91 | return "Moved Permanently"
92 | case .found:
93 | return "Found"
94 | case .seeOther:
95 | return "See other Uri"
96 | case .notModified:
97 | return "Not modified"
98 | case .useProxy:
99 | return "Use proxy"
100 | case .unused:
101 | return "Unused"
102 | case .temporaryRedirect:
103 | return "Temporary redirect"
104 | case .badRequest:
105 | return "Bad request"
106 | case .unauthorized:
107 | return "Access denied"
108 | case .paymentRequired:
109 | return "Payment required"
110 | case .forbidden:
111 | return "Forbidden"
112 | case .notFound:
113 | return "Page not found"
114 | case .methodNotAllowed:
115 | return "Method not allowed"
116 | case .notAcceptable:
117 | return "Not acceptable"
118 | case .proxyAuthenticationRequired:
119 | return "Proxy authentication required"
120 | case .requestTimeout:
121 | return "Request timeout"
122 | case .conflict:
123 | return "Conflict request"
124 | case .gone:
125 | return "Page is gone"
126 | case .lengthRequired:
127 | return "Lack content length"
128 | case .preconditionFailed:
129 | return "Precondition failed"
130 | case .requestEntityTooLarge:
131 | return "Request entity is too large"
132 | case .requestUriTooLong:
133 | return "Request uri is too long"
134 | case .unsupportedMediaType:
135 | return "Unsupported media type"
136 | case .requestedRangeNotSatisfiable:
137 | return "Request range is not satisfiable"
138 | case .expectationFailed:
139 | return "Expected request is failed"
140 | case .internalServerError:
141 | return "Internal server error"
142 | case .notImplemented:
143 | return "Server does not implement a feature for request"
144 | case .badGateway:
145 | return "Bad gateway"
146 | case .serviceUnavailable:
147 | return "Service unavailable"
148 | case .gatewayTimeout:
149 | return "Gateway timeout"
150 | case .httpVersionNotSupported:
151 | return "Http version not supported"
152 | case .invalidUrl:
153 | return "Invalid url"
154 | default:
155 | return "Unknown status code"
156 | }
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/Source/HTTPSecurity.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPSecurity.swift
3 | // SwiftHTTP
4 | //
5 | // Created by Dalton Cherry on 5/16/15.
6 | // Copyright (c) 2015 Vluxe. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Security
11 |
12 | open class SSLCert {
13 | var certData: Data?
14 | var key: SecKey?
15 |
16 | /**
17 | Designated init for certificates
18 |
19 | - parameter data: is the binary data of the certificate
20 |
21 | - returns: a representation security object to be used with
22 | */
23 | public init(data: Data) {
24 | self.certData = data
25 | }
26 |
27 | /**
28 | Designated init for public keys
29 |
30 | - parameter key: is the public key to be used
31 |
32 | - returns: a representation security object to be used with
33 | */
34 | public init(key: SecKey) {
35 | self.key = key
36 | }
37 | }
38 |
39 | open class HTTPSecurity {
40 | open var validatedDN = true //should the domain name be validated?
41 |
42 | var isReady = false //is the key processing done?
43 | var certificates: [Data]? //the certificates
44 | var pubKeys: [SecKey]? //the public keys
45 | var usePublicKeys = false //use public keys or certificate validation?
46 |
47 | /**
48 | Use certs from main app bundle
49 |
50 | - parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation
51 |
52 | - returns: a representation security object to be used with
53 | */
54 | public convenience init(usePublicKeys: Bool = false) {
55 | let paths = Bundle.main.paths(forResourcesOfType: "cer", inDirectory: ".")
56 | var collect = Array()
57 | for path in paths {
58 | if let d = try? Data(contentsOf: URL(fileURLWithPath: path as String)) {
59 | collect.append(SSLCert(data: d))
60 | }
61 | }
62 | self.init(certs:collect, usePublicKeys: usePublicKeys)
63 | }
64 |
65 | /**
66 | Designated init
67 |
68 | - parameter keys: is the certificates or public keys to use
69 | - parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation
70 |
71 | - returns: a representation security object to be used with
72 | */
73 | public init(certs: [SSLCert], usePublicKeys: Bool) {
74 | self.usePublicKeys = usePublicKeys
75 |
76 | if self.usePublicKeys {
77 | DispatchQueue.global().async {
78 | var collect = Array()
79 | for cert in certs {
80 | if let data = cert.certData , cert.key == nil {
81 | cert.key = self.extractPublicKey(data)
82 | }
83 | if let k = cert.key {
84 | collect.append(k)
85 | }
86 | }
87 | self.pubKeys = collect
88 | self.isReady = true
89 | }
90 | } else {
91 | var collect = Array()
92 | for cert in certs {
93 | if let d = cert.certData {
94 | collect.append(d)
95 | }
96 | }
97 | self.certificates = collect
98 | self.isReady = true
99 | }
100 | }
101 |
102 | /**
103 | Valid the trust and domain name.
104 |
105 | - parameter trust: is the serverTrust to validate
106 | - parameter domain: is the CN domain to validate
107 |
108 | - returns: if the key was successfully validated
109 | */
110 | open func isValid(_ trust: SecTrust, domain: String?) -> Bool {
111 |
112 | var tries = 0
113 | while(!self.isReady) {
114 | usleep(1000)
115 | tries += 1
116 | if tries > 5 {
117 | return false //doesn't appear it is going to ever be ready...
118 | }
119 | }
120 | var policy: SecPolicy
121 | if self.validatedDN {
122 | policy = SecPolicyCreateSSL(true, domain as CFString?)
123 | } else {
124 | policy = SecPolicyCreateBasicX509()
125 | }
126 | SecTrustSetPolicies(trust,policy)
127 | if self.usePublicKeys {
128 | if let keys = self.pubKeys {
129 | var trustedCount = 0
130 | let serverPubKeys = publicKeyChainForTrust(trust)
131 | for serverKey in serverPubKeys as [AnyObject] {
132 | for key in keys as [AnyObject] {
133 | if serverKey.isEqual(key) {
134 | trustedCount += 1
135 | break
136 | }
137 | }
138 | }
139 | if trustedCount == serverPubKeys.count {
140 | return true
141 | }
142 | }
143 | } else if let certs = self.certificates {
144 | let serverCerts = certificateChainForTrust(trust)
145 | var collect = Array()
146 | for cert in certs {
147 | collect.append(SecCertificateCreateWithData(nil,cert as CFData)!)
148 | }
149 | SecTrustSetAnchorCertificates(trust,collect as CFArray)
150 | var result: SecTrustResultType = SecTrustResultType(rawValue: UInt32(0))!
151 | SecTrustEvaluate(trust,&result)
152 | if result == SecTrustResultType.unspecified || result == SecTrustResultType.proceed {
153 | var trustedCount = 0
154 | for serverCert in serverCerts {
155 | for cert in certs {
156 | if cert == serverCert {
157 | trustedCount += 1
158 | break
159 | }
160 | }
161 | }
162 | if trustedCount == serverCerts.count {
163 | return true
164 | }
165 | }
166 | }
167 | return false
168 | }
169 |
170 | /**
171 | Get the public key from a certificate data
172 |
173 | - parameter data: is the certificate to pull the public key from
174 |
175 | - returns: a public key
176 | */
177 | func extractPublicKey(_ data: Data) -> SecKey? {
178 | let possibleCert = SecCertificateCreateWithData(nil,data as CFData)
179 | if let cert = possibleCert {
180 | return extractPublicKeyFromCert(cert, policy: SecPolicyCreateBasicX509())
181 | }
182 | return nil
183 | }
184 |
185 | /**
186 | Get the public key from a certificate
187 |
188 | - parameter data: is the certificate to pull the public key from
189 |
190 | - returns: a public key
191 | */
192 | func extractPublicKeyFromCert(_ cert: SecCertificate, policy: SecPolicy) -> SecKey? {
193 | var possibleTrust: SecTrust?
194 | SecTrustCreateWithCertificates(cert, policy, &possibleTrust)
195 | if let trust = possibleTrust {
196 | var result: SecTrustResultType = SecTrustResultType(rawValue: UInt32(0))!
197 | SecTrustEvaluate(trust, &result)
198 | return SecTrustCopyPublicKey(trust)
199 | }
200 | return nil
201 | }
202 |
203 | /**
204 | Get the certificate chain for the trust
205 |
206 | - parameter trust: is the trust to lookup the certificate chain for
207 |
208 | - returns: the certificate chain for the trust
209 | */
210 | func certificateChainForTrust(_ trust: SecTrust) -> Array {
211 | var collect = Array()
212 | for i in 0 ..< SecTrustGetCertificateCount(trust) {
213 | let cert = SecTrustGetCertificateAtIndex(trust,i)
214 | collect.append(SecCertificateCopyData(cert!) as Data)
215 | }
216 | return collect
217 | }
218 |
219 | /**
220 | Get the public key chain for the trust
221 |
222 | - parameter trust: is the trust to lookup the certificate chain and extract the public keys
223 |
224 | - returns: the public keys from the certifcate chain for the trust
225 | */
226 | func publicKeyChainForTrust(_ trust: SecTrust) -> Array {
227 | var collect = Array()
228 | let policy = SecPolicyCreateBasicX509()
229 | for i in 0 ..< SecTrustGetCertificateCount(trust) {
230 | let cert = SecTrustGetCertificateAtIndex(trust,i)
231 | if let key = extractPublicKeyFromCert(cert!, policy: policy) {
232 | collect.append(key)
233 | }
234 | }
235 | return collect
236 | }
237 |
238 |
239 | }
240 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/Source/Request.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Request.swift
3 | // SwiftHTTP
4 | //
5 | // Created by Dalton Cherry on 8/16/15.
6 | // Copyright © 2015 vluxe. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | extension String {
13 | /**
14 | A simple extension to the String object to encode it for web request.
15 |
16 | :returns: Encoded version of of string it was called as.
17 | */
18 | var escaped: String? {
19 | var set = CharacterSet()
20 | set.formUnion(CharacterSet.urlQueryAllowed)
21 | set.remove(charactersIn: "[].:/?&=;+!@#$()',*\"") // remove the HTTP ones from the set.
22 | return self.addingPercentEncoding(withAllowedCharacters: set)
23 | }
24 |
25 | /**
26 | A simple extension to the String object to url encode quotes only.
27 |
28 | :returns: string with .
29 | */
30 | var quoteEscaped: String {
31 | return self.replacingOccurrences(of: "\"", with: "%22").replacingOccurrences(of: "'", with: "%27")
32 | }
33 | }
34 |
35 | /**
36 | The standard HTTP Verbs
37 | */
38 | public enum HTTPVerb: String {
39 | case GET = "GET"
40 | case POST = "POST"
41 | case PUT = "PUT"
42 | case HEAD = "HEAD"
43 | case DELETE = "DELETE"
44 | case PATCH = "PATCH"
45 | case OPTIONS = "OPTIONS"
46 | case TRACE = "TRACE"
47 | case CONNECT = "CONNECT"
48 | case UNKNOWN = "UNKNOWN"
49 | }
50 |
51 | /**
52 | This is used to create key/value pairs of the parameters
53 | */
54 | public struct HTTPPair {
55 | var key: String?
56 | let storeVal: AnyObject
57 | /**
58 | Create the object with a possible key and a value
59 | */
60 | init(key: String?, value: AnyObject) {
61 | self.key = key
62 | self.storeVal = value
63 | }
64 | /**
65 | Computed property of the string representation of the storedVal
66 | */
67 | var upload: Upload? {
68 | return storeVal as? Upload
69 | }
70 | /**
71 | Computed property of the string representation of the storedVal
72 | */
73 | var value: String {
74 | if storeVal is NSNull {
75 | return ""
76 | } else if let v = storeVal as? String {
77 | return v
78 | } else {
79 | return storeVal.description ?? ""
80 | }
81 | }
82 | /**
83 | Computed property of the string representation of the storedVal escaped for URLs
84 | */
85 | var escapedValue: String {
86 | let v = value.escaped ?? ""
87 | if let k = key {
88 | if let escapedKey = k.escaped {
89 | return "\(escapedKey)=\(v)"
90 | }
91 | }
92 | return ""
93 | }
94 | }
95 |
96 | /**
97 | This is super gross, but it is just an edge case, I'm willing to live with it
98 | versus trying to handle such an rare need with more code and confusion
99 | */
100 | public class HTTPParameterProtocolSettings {
101 | public static var sendEmptyArray = false
102 | }
103 |
104 | /**
105 | This protocol is used to make the dictionary and array serializable into key/value pairs.
106 | */
107 | public protocol HTTPParameterProtocol {
108 | func createPairs(_ key: String?) -> [HTTPPair]
109 | }
110 |
111 | /**
112 | Support for the Dictionary type as an HTTPParameter.
113 | */
114 | extension Dictionary: HTTPParameterProtocol {
115 | public func createPairs(_ key: String?) -> [HTTPPair] {
116 | var collect = [HTTPPair]()
117 | for (k, v) in self {
118 | if let nestedKey = k as? String {
119 | let useKey = key != nil ? "\(key!)[\(nestedKey)]" : nestedKey
120 | if let subParam = v as? HTTPParameterProtocol {
121 | collect.append(contentsOf: subParam.createPairs(useKey))
122 | } else {
123 | collect.append(HTTPPair(key: useKey, value: v as AnyObject))
124 | }
125 | }
126 | }
127 | return collect
128 | }
129 | }
130 |
131 | /**
132 | Support for the Array type as an HTTPParameter.
133 | */
134 | extension Array: HTTPParameterProtocol {
135 |
136 | public func createPairs(_ key: String?) -> [HTTPPair] {
137 | var collect = [HTTPPair]()
138 | for v in self {
139 | let useKey = key != nil ? "\(key!)[]" : key
140 | if let subParam = v as? HTTPParameterProtocol {
141 | collect.append(contentsOf: subParam.createPairs(useKey))
142 | } else {
143 | collect.append(HTTPPair(key: useKey, value: v as AnyObject))
144 | }
145 | }
146 | if HTTPParameterProtocolSettings.sendEmptyArray && collect.count == 0 {
147 | collect.append(HTTPPair(key: key, value: "[]" as AnyObject))
148 | }
149 | return collect
150 | }
151 | }
152 |
153 | /**
154 | Support for the Upload type as an HTTPParameter.
155 | */
156 | extension Upload: HTTPParameterProtocol {
157 | public func createPairs(_ key: String?) -> Array {
158 | var collect = Array()
159 | collect.append(HTTPPair(key: key, value: self))
160 | return collect
161 | }
162 | }
163 |
164 | /**
165 | Adds convenience methods to URLRequest to make using it with HTTP much simpler.
166 | */
167 | extension URLRequest {
168 | /**
169 | Convenience init to allow init with a string.
170 | -parameter urlString: The string representation of a URL to init with.
171 | */
172 | public init?(urlString: String, parameters: HTTPParameterProtocol? = nil, headers: [String: String]? = nil, cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, timeoutInterval: TimeInterval = 60) {
173 | if let url = URL(string: urlString) {
174 | self.init(url: url)
175 | } else {
176 | return nil
177 | }
178 | if let params = parameters {
179 | let _ = appendParameters(params)
180 | }
181 | if let heads = headers {
182 | for (key,value) in heads {
183 | addValue(value, forHTTPHeaderField: key)
184 | }
185 | }
186 | }
187 |
188 | /**
189 | Convenience method to avoid having to use strings and allow using an enum
190 | */
191 | public var verb: HTTPVerb {
192 | set {
193 | httpMethod = newValue.rawValue
194 | }
195 | get {
196 | if let verb = httpMethod, let v = HTTPVerb(rawValue: verb) {
197 | return v
198 | }
199 | return .UNKNOWN
200 | }
201 | }
202 |
203 | /**
204 | Used to update the content type in the HTTP header as needed
205 | */
206 | var contentTypeKey: String {
207 | return "Content-Type"
208 | }
209 |
210 | /**
211 | append the parameters using the standard HTTP Query model.
212 | This is parameters in the query string of the url (e.g. ?first=one&second=two for GET, HEAD, DELETE.
213 | It uses 'application/x-www-form-urlencoded' for the content type of POST/PUT requests that don't contains files.
214 | If it contains a file it uses `multipart/form-data` for the content type.
215 | -parameter parameters: The container (array or dictionary) to convert and append to the URL or Body
216 | */
217 | public mutating func appendParameters(_ parameters: HTTPParameterProtocol) -> Error? {
218 | if isURIParam() {
219 | appendParametersAsQueryString(parameters)
220 | } else if containsFile(parameters) {
221 | return appendParametersAsMultiPartFormData(parameters)
222 | } else {
223 | appendParametersAsUrlEncoding(parameters)
224 | }
225 | return nil
226 | }
227 |
228 | /**
229 | append the parameters as a HTTP Query string. (e.g. domain.com?first=one&second=two)
230 | -parameter parameters: The container (array or dictionary) to convert and append to the URL
231 | */
232 | public mutating func appendParametersAsQueryString(_ parameters: HTTPParameterProtocol) {
233 | let queryString = parameters.createPairs(nil).map({ (pair) in
234 | return pair.escapedValue
235 | }).joined(separator: "&")
236 | if let u = self.url , queryString.count > 0 {
237 | let para = u.query != nil ? "&" : "?"
238 | self.url = URL(string: "\(u.absoluteString)\(para)\(queryString)")
239 | }
240 | }
241 |
242 | /**
243 | append the parameters as a url encoded string. (e.g. in the body of the request as: first=one&second=two)
244 | -parameter parameters: The container (array or dictionary) to convert and append to the HTTP body
245 | */
246 | public mutating func appendParametersAsUrlEncoding(_ parameters: HTTPParameterProtocol) {
247 | if value(forHTTPHeaderField: contentTypeKey) == nil {
248 | var contentStr = "application/x-www-form-urlencoded"
249 | if let charset = CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(String.Encoding.utf8.rawValue)) {
250 | contentStr += "; charset=\(charset)"
251 | }
252 | setValue(contentStr, forHTTPHeaderField:contentTypeKey)
253 | }
254 | let queryString = parameters.createPairs(nil).map({ (pair) in
255 | return pair.escapedValue
256 | }).joined(separator: "&")
257 | httpBody = queryString.data(using: .utf8)
258 | }
259 |
260 | /**
261 | append the parameters as a multpart form body. This is the type normally used for file uploads.
262 | -parameter parameters: The container (array or dictionary) to convert and append to the HTTP body
263 | */
264 | public mutating func appendParametersAsMultiPartFormData(_ parameters: HTTPParameterProtocol) -> Error? {
265 | let boundary = "Boundary+\(arc4random())\(arc4random())"
266 | if value(forHTTPHeaderField: contentTypeKey) == nil {
267 | setValue("multipart/form-data; boundary=\(boundary)",
268 | forHTTPHeaderField:contentTypeKey)
269 | }
270 | let mutData = NSMutableData()
271 | let multiCRLF = "\r\n"
272 | mutData.append("--\(boundary)".data(using: .utf8)!)
273 | for pair in parameters.createPairs(nil) {
274 | guard let key = pair.key else { continue } //this won't happen, but just to properly unwrap
275 | if let upload = pair.upload {
276 | let resp = upload.getData()
277 | if let error = resp.error {
278 | return error
279 | }
280 | mutData.append("\(multiCRLF)".data(using: .utf8)!)
281 | if let data = resp.data {
282 | mutData.append(multiFormHeader(key, fileName: upload.fileName,
283 | type: upload.mimeType, multiCRLF: multiCRLF).data(using: .utf8)!)
284 | mutData.append(data)
285 | } else {
286 | return HTTPUploadError.noData
287 | }
288 | } else {
289 | mutData.append("\(multiCRLF)".data(using: .utf8)!)
290 | let str = "\(multiFormHeader(key, fileName: nil, type: nil, multiCRLF: multiCRLF))\(pair.value)"
291 | mutData.append(str.data(using: .utf8)!)
292 | }
293 | mutData.append("\(multiCRLF)--\(boundary)".data(using: .utf8)!)
294 | }
295 | mutData.append("--\(multiCRLF)".data(using: .utf8)!)
296 | httpBody = mutData as Data
297 | return nil
298 | }
299 |
300 | /**
301 | Helper method to create the multipart form data
302 | */
303 | func multiFormHeader(_ name: String, fileName: String?, type: String?, multiCRLF: String) -> String {
304 | var str = "Content-Disposition: form-data; name=\"\(name.quoteEscaped)\""
305 | if let n = fileName {
306 | str += "; filename=\"\(n.quoteEscaped)\""
307 | }
308 | str += multiCRLF
309 | if let t = type {
310 | str += "Content-Type: \(t)\(multiCRLF)"
311 | }
312 | str += multiCRLF
313 | return str
314 | }
315 |
316 |
317 | /**
318 | send the parameters as a body of JSON
319 | -parameter parameters: The container (array or dictionary) to convert and append to the URL or Body
320 | */
321 | public mutating func appendParametersAsJSON(_ parameters: HTTPParameterProtocol) -> Error? {
322 | if isURIParam() {
323 | appendParametersAsQueryString(parameters)
324 | } else {
325 | do {
326 | httpBody = try JSONSerialization.data(withJSONObject: parameters as AnyObject, options: JSONSerialization.WritingOptions())
327 | } catch let error {
328 | return error
329 | }
330 | var contentStr = "application/json"
331 | if let charset = CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(String.Encoding.utf8.rawValue)) {
332 | contentStr += "; charset=\(charset)"
333 | }
334 | setValue(contentStr, forHTTPHeaderField: contentTypeKey)
335 | }
336 | return nil
337 | }
338 |
339 | /**
340 | Check if the request requires the parameters to be appended to the URL
341 | */
342 | public func isURIParam() -> Bool {
343 | if verb == .GET || verb == .HEAD || verb == .DELETE {
344 | return true
345 | }
346 | return false
347 | }
348 |
349 | /**
350 | check if the parameters contain a file object within them
351 | -parameter parameters: The parameters to search through for an upload object
352 | */
353 | public func containsFile(_ parameters: HTTPParameterProtocol) -> Bool {
354 | for pair in parameters.createPairs(nil) {
355 | if let _ = pair.upload {
356 | return true
357 | }
358 | }
359 | return false
360 | }
361 | }
362 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | SwiftHTTP
2 | =========
3 |
4 | SwiftHTTP is a thin wrapper around NSURLSession in Swift to simplify HTTP requests.
5 |
6 | ## Features
7 |
8 | - Convenient Closure APIs
9 | - Simple Queue Support
10 | - Parameter Encoding
11 | - Builtin JSON Request Serialization
12 | - Upload/Download with Progress Closure
13 | - Concise Codebase.
14 |
15 |
16 | First thing is to import the framework. See the Installation instructions on how to add the framework to your project.
17 |
18 | ```swift
19 | import SwiftHTTP
20 | ```
21 |
22 | ## Examples
23 |
24 | ### GET
25 |
26 | The most basic request. By default an Data object will be returned for the response.
27 | ```swift
28 | HTTP.GET("https://google.com") { response in
29 | if let err = response.error {
30 | print("error: \(err.localizedDescription)")
31 | return //also notify app of failure as needed
32 | }
33 | print("opt finished: \(response.description)")
34 | //print("data is: \(response.data)") access the response of the data with response.data
35 | }
36 | ```
37 |
38 | We can also add parameters as with standard container objects and they will be properly serialized to their respective HTTP equivalent.
39 |
40 | ```swift
41 | //the url sent will be https://google.com?hello=world¶m2=value2
42 | HTTP.GET("https://google.com", parameters: ["hello": "world", "param2": "value2"]) { response in
43 | if let err = response.error {
44 | print("error: \(err.localizedDescription)")
45 | return //also notify app of failure as needed
46 | }
47 | print("opt finished: \(response.description)")
48 | }
49 | ```
50 |
51 | The `Response` contains all the common HTTP response data, such as the responseObject of the data and the headers of the response.
52 |
53 | ### HTTP Methods
54 |
55 | All the common HTTP methods are avalaible as convenience methods as well.
56 |
57 | ### POST
58 |
59 | ```swift
60 | let params = ["param": "param1", "array": ["first array element","second","third"], "num": 23, "dict": ["someKey": "someVal"]]
61 | HTTP.POST("https://domain.com/new", parameters: params) { response in
62 | //do things...
63 | }
64 | ```
65 |
66 | ### PUT
67 |
68 | ```swift
69 | HTTP.PUT("https://domain.com/1")
70 | ```
71 |
72 | ### HEAD
73 |
74 | ```swift
75 | HTTP.HEAD("https://domain.com/1")
76 | ```
77 |
78 | ### DELETE
79 |
80 | ```swift
81 | HTTP.DELETE("https://domain.com/1")
82 | ```
83 |
84 | ### Download
85 |
86 | ```swift
87 | HTTP.Download("http://www.cbu.edu.zm/downloads/pdf-sample.pdf", completion: { (response, url) in
88 | //move the temp file to desired location...
89 | })
90 | ```
91 |
92 | ### Upload
93 |
94 | File uploads can be done using the `Upload` object. All files to upload should be wrapped in a Upload object and added as a parameter.
95 |
96 | ```swift
97 | let fileUrl = URL(fileURLWithPath: "/Users/dalton/Desktop/testfile")!
98 | HTTP.POST("https://domain.com/new", parameters: ["aParam": "aValue", "file": Upload(fileUrl: fileUrl)]) { response in
99 | //do things...
100 | }
101 | ```
102 | `Upload` comes in both a on disk fileUrl version and a Data version.
103 |
104 | ### Custom Headers
105 |
106 | Custom HTTP headers can be add to a request with the standard NSMutableRequest API:
107 |
108 | ```swift
109 | HTTP.GET("https://domain.com", parameters: ["hello": "there"], headers: ["header": "value"]) { response in
110 | //do stuff
111 | }
112 | ```
113 |
114 | ### SSL Pinning
115 |
116 | SSL Pinning is also supported in SwiftHTTP.
117 |
118 | ```swift
119 | var req = URLRequest(urlString: "https://domain.com")!
120 | req?.timeoutInterval = 5
121 | let task = HTTP(req)
122 | task.security = HTTPSecurity(certs: [HTTPSSLCert(data: data)], usePublicKeys: true)
123 | //opt.security = HTTPSecurity() //uses the .cer files in your app's bundle
124 | task.run { (response) in
125 | if let err = response.error {
126 | print("error: \(err.localizedDescription)")
127 | return //also notify app of failure as needed
128 | }
129 | print("opt finished: \(response.description)")
130 | }
131 | ```
132 | You load either a `Data` blob of your certificate or you can use a `SecKeyRef` if you have a public key you want to use. The `usePublicKeys` bool is whether to use the certificates for validation or the public keys. The public keys will be extracted from the certificates automatically if `usePublicKeys` is choosen.
133 |
134 | ### Authentication
135 |
136 | SwiftHTTP supports authentication through [NSURLCredential](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLCredential_Class/Reference/Reference.html). Currently only Basic Auth and Digest Auth have been tested.
137 |
138 | ```swift
139 | var req = URLRequest(urlString: "https://domain.com")!
140 | req.timeoutInterval = 5
141 | let task = HTTP(req)
142 | //the auth closures will continually be called until a successful auth or rejection
143 | var attempted = false
144 | task.auth = { challenge in
145 | if !attempted {
146 | attempted = true
147 | return NSURLCredential(user: "user", password: "passwd", persistence: .ForSession)
148 | }
149 | return nil //auth failed, nil causes the request to be properly cancelled.
150 | }
151 | task.run { (response) in
152 | //do stuff
153 | }
154 | ```
155 |
156 | Allow all certificates example:
157 |
158 | ```swift
159 | var req = URLRequest(urlString: "https://domain.com")!
160 | req.timeoutInterval = 5
161 | let task = HTTP(req)
162 | //the auth closures will continually be called until a successful auth or rejection
163 | var attempted = false
164 | task.auth = { challenge in
165 | if !attempted {
166 | attempted = true
167 | return NSURLCredential(forTrust: challenge.protectionSpace.serverTrust)
168 | }
169 | return nil //auth failed, nil causes the request to be properly cancelled.
170 | }
171 | task.run { (response) in
172 | //do stuff
173 | }
174 | ```
175 |
176 | ### Operation Queue
177 |
178 | SwiftHTTP also has a simple queue in it!
179 |
180 | ```swift
181 | let queue = HTTPQueue(maxSimultaneousRequest: 2)
182 | var req = URLRequest(urlString: "https://google.com")!
183 | req.timeoutInterval = 5
184 | let task = HTTP(req)
185 | task.onFinish = { (response) in
186 | print("item in the queue finished: \(response.URL!)")
187 | }
188 | queue.add(http: task) //the request will start running once added to the queue
189 |
190 |
191 | var req2 = URLRequest(urlString: "https://apple.com")!
192 | req2.timeoutInterval = 5
193 | let task2 = HTTP(req2)
194 | task2.onFinish = { (response) in
195 | print("item in the queue finished: \(response.URL!)")
196 | }
197 | queue.add(http: task2)
198 |
199 | //etc...
200 |
201 | queue.finished {
202 | print("all items finished")
203 | }
204 | ```
205 |
206 | ### Cancel
207 |
208 | Let's say you want to cancel the request a little later, call the `cancel` method.
209 |
210 | ```swift
211 | task.cancel()
212 | ```
213 |
214 | ### JSON Request Serializer
215 |
216 | Request parameters can also be serialized to JSON as needed. By default request are serialized using standard HTTP form encoding.
217 |
218 | ```swift
219 | HTTP.GET("https://google.com", requestSerializer: JSONParameterSerializer()) { response in
220 | //you already get it. The data property of the response object will have the json in it
221 | }
222 | ```
223 |
224 | ### Progress
225 |
226 | SwiftHTTP can monitor the progress of a request.
227 |
228 | ```swift
229 | var req = URLRequest(urlString: "https://domain.com/somefile")
230 | let task = HTTP(req!)
231 | task.progress = { progress in
232 | print("progress: \(progress)") //this will be between 0 and 1.
233 | }
234 | task.run { (response) in
235 | //do stuff
236 | }
237 | ```
238 |
239 |
240 | ### Global handlers
241 |
242 | SwiftHTTP also has global handlers, to reduce the requirement of repeat HTTP modifiers, such as a auth header or setting `NSMutableURLRequest` properties such as `timeoutInterval`.
243 |
244 | ```swift
245 | //modify NSMutableURLRequest for any Factory method call (e.g. HTTP.GET, HTTP.POST, HTTP.New, etc).
246 | HTTP.globalRequest { req in
247 | req.timeoutInterval = 5
248 | }
249 |
250 | //set a global SSL pinning setting
251 | HTTP.globalSecurity(HTTPSecurity()) //see the SSL section for more info
252 |
253 | //set global auth handler. See the Auth section for more info
254 | HTTP.globalAuth { challenge in
255 | return NSURLCredential(user: "user", password: "passwd", persistence: .ForSession)
256 | }
257 | ```
258 |
259 | ## Client/Server Example
260 |
261 | This is a full example swiftHTTP in action. First here is a quick web server in Go.
262 |
263 | ```go
264 | package main
265 |
266 | import (
267 | "fmt"
268 | "log"
269 | "net/http"
270 | )
271 |
272 | func main() {
273 | http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
274 | log.Println("got a web request")
275 | fmt.Println("header: ", r.Header.Get("someKey"))
276 | w.Write([]byte("{\"status\": \"ok\"}"))
277 | })
278 |
279 | log.Fatal(http.ListenAndServe(":8080", nil))
280 | }
281 | ```
282 |
283 | Now for the request:
284 |
285 | ```swift
286 | struct Response: Codable {
287 | let status: String
288 | }
289 |
290 | let decoder = JSONDecoder()
291 | HTTP.GET("http://localhost:8080/bar") { response in
292 | if let error = response.error {
293 | print("got an error: \(error)")
294 | return
295 | }
296 | do {
297 | let resp = try decoder.decode(Response.self, from: response.data)
298 | print("completed: \(resp.status)")
299 | } catch let error {
300 | print("decode json error: \(error)")
301 | }
302 | }
303 | ```
304 |
305 | ## POST example
306 |
307 | ```go
308 | package main
309 |
310 | import (
311 | "fmt"
312 | "io"
313 | "log"
314 | "net/http"
315 | "os"
316 | )
317 |
318 | func main() {
319 | http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
320 | fmt.Println("header: ", r.Header.Get("Content-Type"))
321 | upload, header, err := r.FormFile("file")
322 | if err != nil {
323 | w.Write([]byte("{\"error\": \"bad file upload\"}")) //normally be a 500 status code
324 | return
325 | }
326 | file, err := os.Create(header.Filename) // we would normally need to generate unique filenames.
327 | if err != nil {
328 | w.Write([]byte("{\"error\": \"system error occured\"}")) //normally be a 500 status code
329 | return
330 | }
331 | io.Copy(file, upload) // write the uploaded file to disk.
332 | w.Write([]byte("{\"status\": \"ok\"}"))
333 | })
334 |
335 | log.Fatal(http.ListenAndServe(":8080", nil))
336 | }
337 | ```
338 |
339 | Now for the Swift:
340 |
341 | ```swift
342 | struct Response: Codable {
343 | let status: String?
344 | let error: String?
345 | }
346 |
347 | let decoder = JSONDecoder()
348 | let url = URL(fileURLWithPath: "/Users/dalton/Desktop/picture.jpg")
349 | HTTP.POST("http://localhost:8080/bar", parameters: ["test": "value", "file": Upload(fileUrl: url)]) { response in
350 | if let error = response.error {
351 | print("got an error: \(error)")
352 | return
353 | }
354 | do {
355 | let resp = try decoder.decode(Response.self, from: response.data)
356 | if let err = resp.error {
357 | print("got an error: \(err)")
358 | }
359 | if let status = resp.status {
360 | print("completed: \(status)")
361 | }
362 | } catch let error {
363 | print("decode json error: \(error)")
364 | }
365 | }
366 | ```
367 |
368 | ## Requirements
369 |
370 | SwiftHTTP works with iOS 7/OSX 10.10 or above. It is recommended to use iOS 8/10.10 or above for CocoaPods/framework support.
371 | To use SwiftHTTP with a project targeting iOS 7, you must include all Swift files directly in your project.
372 |
373 | ## Installation
374 |
375 | ### CocoaPods
376 |
377 | Check out [Get Started](https://guides.cocoapods.org/using/getting-started.html) tab on [cocoapods.org](http://cocoapods.org/).
378 |
379 | To use SwiftHTTP in your project add the following 'Podfile' to your project
380 |
381 | source 'https://github.com/CocoaPods/Specs.git'
382 | platform :ios, '8.0'
383 | use_frameworks!
384 |
385 | pod 'SwiftHTTP', '~> 3.0.1'
386 |
387 | Then run:
388 |
389 | pod install
390 |
391 | ### Carthage
392 |
393 | Check out the [Carthage](https://github.com/Carthage/Carthage) docs on how to add a install. The `SwiftHTTP` framework is already setup with shared schemes.
394 |
395 | [Carthage Install](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application)
396 |
397 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command:
398 |
399 | ```bash
400 | $ brew update
401 | $ brew install carthage
402 | ```
403 |
404 | To integrate SwiftHTTP into your Xcode project using Carthage, specify it in your `Cartfile`:
405 |
406 | ```
407 | github "daltoniam/SwiftHTTP" >= 3.0.1
408 | ```
409 |
410 | ### Rogue
411 |
412 | First see the [installation docs](https://github.com/acmacalister/Rogue) for how to install Rogue.
413 |
414 | To install SwiftHTTP run the command below in the directory you created the rogue file.
415 |
416 | ```
417 | rogue add https://github.com/daltoniam/SwiftHTTP
418 | ```
419 |
420 | Next open the `libs` folder and add the `SwiftHTTP.xcodeproj` to your Xcode project. Once that is complete, in your "Build Phases" add the `SwiftHTTP.framework` to your "Link Binary with Libraries" phase. Make sure to add the `libs` folder to your `.gitignore` file.
421 |
422 | ### Other
423 |
424 | Simply grab the framework (either via git submodule or another package manager).
425 |
426 | Add the `SwiftHTTP.xcodeproj` to your Xcode project. Once that is complete, in your "Build Phases" add the `SwiftHTTP.framework` to your "Link Binary with Libraries" phase.
427 |
428 | ### Add Copy Frameworks Phase
429 |
430 | If you are running this in an OSX app or on a physical iOS device you will need to make sure you add the `SwiftHTTP.framework` included in your app bundle. To do this, in Xcode, navigate to the target configuration window by clicking on the blue project icon, and selecting the application target under the "Targets" heading in the sidebar. In the tab bar at the top of that window, open the "Build Phases" panel. Expand the "Link Binary with Libraries" group, and add `SwiftHTTP.framework`. Click on the + button at the top left of the panel and select "New Copy Files Phase". Rename this new phase to "Copy Frameworks", set the "Destination" to "Frameworks", and add `SwiftHTTP.framework`.
431 |
432 | ## TODOs
433 |
434 | - [ ] Linux support?
435 | - [ ] Add more unit tests
436 |
437 | ## License
438 |
439 | SwiftHTTP is licensed under the Apache v2 License.
440 |
441 | ## Contact
442 |
443 | ### Dalton Cherry
444 | * https://github.com/daltoniam
445 | * http://twitter.com/daltoniam
446 | * http://daltoniam.com
447 |
--------------------------------------------------------------------------------
/SwiftHTTP.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 5C135F161C47366700AA3A01 /* HTTPSecurity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C135F101C47366700AA3A01 /* HTTPSecurity.swift */; };
11 | 5C135F191C47366700AA3A01 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C135F111C47366700AA3A01 /* Operation.swift */; };
12 | 5C135F1C1C47366700AA3A01 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C135F121C47366700AA3A01 /* Request.swift */; };
13 | 5C135F1F1C47366700AA3A01 /* StatusCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C135F131C47366700AA3A01 /* StatusCode.swift */; };
14 | 5C135F221C47366700AA3A01 /* SwiftHTTP.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C135F141C47366700AA3A01 /* SwiftHTTP.h */; settings = {ATTRIBUTES = (Public, ); }; };
15 | 5C135F251C47366700AA3A01 /* Upload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C135F151C47366700AA3A01 /* Upload.swift */; };
16 | 6BFD904019C8D9A000DD99B6 /* SwiftHTTPTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BFD903D19C8D98000DD99B6 /* SwiftHTTPTests.swift */; };
17 | /* End PBXBuildFile section */
18 |
19 | /* Begin PBXContainerItemProxy section */
20 | 832C8E641B01064F0052A5D7 /* PBXContainerItemProxy */ = {
21 | isa = PBXContainerItemProxy;
22 | containerPortal = 6BFD900D19C8D8B500DD99B6 /* Project object */;
23 | proxyType = 1;
24 | remoteGlobalIDString = 6BFD901519C8D8B500DD99B6;
25 | remoteInfo = SwiftHTTP;
26 | };
27 | /* End PBXContainerItemProxy section */
28 |
29 | /* Begin PBXFileReference section */
30 | 5C135F101C47366700AA3A01 /* HTTPSecurity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HTTPSecurity.swift; path = Source/HTTPSecurity.swift; sourceTree = SOURCE_ROOT; };
31 | 5C135F111C47366700AA3A01 /* Operation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Operation.swift; path = Source/Operation.swift; sourceTree = SOURCE_ROOT; };
32 | 5C135F121C47366700AA3A01 /* Request.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Request.swift; path = Source/Request.swift; sourceTree = SOURCE_ROOT; };
33 | 5C135F131C47366700AA3A01 /* StatusCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StatusCode.swift; path = Source/StatusCode.swift; sourceTree = SOURCE_ROOT; };
34 | 5C135F141C47366700AA3A01 /* SwiftHTTP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SwiftHTTP.h; path = Source/SwiftHTTP.h; sourceTree = SOURCE_ROOT; };
35 | 5C135F151C47366700AA3A01 /* Upload.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Upload.swift; path = Source/Upload.swift; sourceTree = SOURCE_ROOT; };
36 | 5C135F281C47367400AA3A01 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Source/Info.plist; sourceTree = SOURCE_ROOT; };
37 | 6BFD901619C8D8B500DD99B6 /* SwiftHTTP.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftHTTP.framework; sourceTree = BUILT_PRODUCTS_DIR; };
38 | 6BFD902119C8D8B500DD99B6 /* SwiftHTTPTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftHTTPTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
39 | 6BFD903C19C8D98000DD99B6 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Tests/Info.plist; sourceTree = ""; };
40 | 6BFD903D19C8D98000DD99B6 /* SwiftHTTPTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftHTTPTests.swift; path = Tests/SwiftHTTPTests.swift; sourceTree = ""; };
41 | /* End PBXFileReference section */
42 |
43 | /* Begin PBXFrameworksBuildPhase section */
44 | 6BFD901219C8D8B500DD99B6 /* Frameworks */ = {
45 | isa = PBXFrameworksBuildPhase;
46 | buildActionMask = 2147483647;
47 | files = (
48 | );
49 | runOnlyForDeploymentPostprocessing = 0;
50 | };
51 | 6BFD901E19C8D8B500DD99B6 /* Frameworks */ = {
52 | isa = PBXFrameworksBuildPhase;
53 | buildActionMask = 2147483647;
54 | files = (
55 | );
56 | runOnlyForDeploymentPostprocessing = 0;
57 | };
58 | /* End PBXFrameworksBuildPhase section */
59 |
60 | /* Begin PBXGroup section */
61 | 6BFD900C19C8D8B500DD99B6 = {
62 | isa = PBXGroup;
63 | children = (
64 | 6BFD901819C8D8B500DD99B6 /* SwiftHTTP */,
65 | 6BFD903B19C8D96A00DD99B6 /* Tests */,
66 | 6BFD901719C8D8B500DD99B6 /* Products */,
67 | );
68 | sourceTree = "";
69 | };
70 | 6BFD901719C8D8B500DD99B6 /* Products */ = {
71 | isa = PBXGroup;
72 | children = (
73 | 6BFD901619C8D8B500DD99B6 /* SwiftHTTP.framework */,
74 | 6BFD902119C8D8B500DD99B6 /* SwiftHTTPTests.xctest */,
75 | );
76 | name = Products;
77 | sourceTree = "";
78 | };
79 | 6BFD901819C8D8B500DD99B6 /* SwiftHTTP */ = {
80 | isa = PBXGroup;
81 | children = (
82 | 5C135F101C47366700AA3A01 /* HTTPSecurity.swift */,
83 | 5C135F111C47366700AA3A01 /* Operation.swift */,
84 | 5C135F121C47366700AA3A01 /* Request.swift */,
85 | 5C135F131C47366700AA3A01 /* StatusCode.swift */,
86 | 5C135F151C47366700AA3A01 /* Upload.swift */,
87 | 5C135F141C47366700AA3A01 /* SwiftHTTP.h */,
88 | 6BFD901919C8D8B500DD99B6 /* Supporting Files */,
89 | );
90 | path = SwiftHTTP;
91 | sourceTree = "";
92 | };
93 | 6BFD901919C8D8B500DD99B6 /* Supporting Files */ = {
94 | isa = PBXGroup;
95 | children = (
96 | 5C135F281C47367400AA3A01 /* Info.plist */,
97 | );
98 | name = "Supporting Files";
99 | sourceTree = "";
100 | };
101 | 6BFD903B19C8D96A00DD99B6 /* Tests */ = {
102 | isa = PBXGroup;
103 | children = (
104 | 6BFD903C19C8D98000DD99B6 /* Info.plist */,
105 | 6BFD903D19C8D98000DD99B6 /* SwiftHTTPTests.swift */,
106 | );
107 | name = Tests;
108 | sourceTree = "";
109 | };
110 | /* End PBXGroup section */
111 |
112 | /* Begin PBXHeadersBuildPhase section */
113 | 6BFD901319C8D8B500DD99B6 /* Headers */ = {
114 | isa = PBXHeadersBuildPhase;
115 | buildActionMask = 2147483647;
116 | files = (
117 | 5C135F221C47366700AA3A01 /* SwiftHTTP.h in Headers */,
118 | );
119 | runOnlyForDeploymentPostprocessing = 0;
120 | };
121 | /* End PBXHeadersBuildPhase section */
122 |
123 | /* Begin PBXNativeTarget section */
124 | 6BFD901519C8D8B500DD99B6 /* SwiftHTTP */ = {
125 | isa = PBXNativeTarget;
126 | buildConfigurationList = 6BFD902919C8D8B500DD99B6 /* Build configuration list for PBXNativeTarget "SwiftHTTP" */;
127 | buildPhases = (
128 | 6BFD901119C8D8B500DD99B6 /* Sources */,
129 | 6BFD901219C8D8B500DD99B6 /* Frameworks */,
130 | 6BFD901319C8D8B500DD99B6 /* Headers */,
131 | 6BFD901419C8D8B500DD99B6 /* Resources */,
132 | );
133 | buildRules = (
134 | );
135 | dependencies = (
136 | );
137 | name = SwiftHTTP;
138 | productName = SwiftHTTP;
139 | productReference = 6BFD901619C8D8B500DD99B6 /* SwiftHTTP.framework */;
140 | productType = "com.apple.product-type.framework";
141 | };
142 | 6BFD902019C8D8B500DD99B6 /* SwiftHTTPTests */ = {
143 | isa = PBXNativeTarget;
144 | buildConfigurationList = 6BFD902C19C8D8B500DD99B6 /* Build configuration list for PBXNativeTarget "SwiftHTTPTests" */;
145 | buildPhases = (
146 | 6BFD901D19C8D8B500DD99B6 /* Sources */,
147 | 6BFD901E19C8D8B500DD99B6 /* Frameworks */,
148 | 6BFD901F19C8D8B500DD99B6 /* Resources */,
149 | );
150 | buildRules = (
151 | );
152 | dependencies = (
153 | 832C8E651B01064F0052A5D7 /* PBXTargetDependency */,
154 | );
155 | name = SwiftHTTPTests;
156 | productName = SwiftHTTPTests;
157 | productReference = 6BFD902119C8D8B500DD99B6 /* SwiftHTTPTests.xctest */;
158 | productType = "com.apple.product-type.bundle.unit-test";
159 | };
160 | /* End PBXNativeTarget section */
161 |
162 | /* Begin PBXProject section */
163 | 6BFD900D19C8D8B500DD99B6 /* Project object */ = {
164 | isa = PBXProject;
165 | attributes = {
166 | LastSwiftMigration = 0700;
167 | LastSwiftUpdateCheck = 0720;
168 | LastUpgradeCheck = 0900;
169 | ORGANIZATIONNAME = Vluxe;
170 | TargetAttributes = {
171 | 6BFD901519C8D8B500DD99B6 = {
172 | CreatedOnToolsVersion = 6.0;
173 | LastSwiftMigration = 0900;
174 | };
175 | 6BFD902019C8D8B500DD99B6 = {
176 | CreatedOnToolsVersion = 6.0;
177 | };
178 | };
179 | };
180 | buildConfigurationList = 6BFD901019C8D8B500DD99B6 /* Build configuration list for PBXProject "SwiftHTTP" */;
181 | compatibilityVersion = "Xcode 3.2";
182 | developmentRegion = English;
183 | hasScannedForEncodings = 0;
184 | knownRegions = (
185 | en,
186 | );
187 | mainGroup = 6BFD900C19C8D8B500DD99B6;
188 | productRefGroup = 6BFD901719C8D8B500DD99B6 /* Products */;
189 | projectDirPath = "";
190 | projectRoot = "";
191 | targets = (
192 | 6BFD901519C8D8B500DD99B6 /* SwiftHTTP */,
193 | 6BFD902019C8D8B500DD99B6 /* SwiftHTTPTests */,
194 | );
195 | };
196 | /* End PBXProject section */
197 |
198 | /* Begin PBXResourcesBuildPhase section */
199 | 6BFD901419C8D8B500DD99B6 /* Resources */ = {
200 | isa = PBXResourcesBuildPhase;
201 | buildActionMask = 2147483647;
202 | files = (
203 | );
204 | runOnlyForDeploymentPostprocessing = 0;
205 | };
206 | 6BFD901F19C8D8B500DD99B6 /* Resources */ = {
207 | isa = PBXResourcesBuildPhase;
208 | buildActionMask = 2147483647;
209 | files = (
210 | );
211 | runOnlyForDeploymentPostprocessing = 0;
212 | };
213 | /* End PBXResourcesBuildPhase section */
214 |
215 | /* Begin PBXSourcesBuildPhase section */
216 | 6BFD901119C8D8B500DD99B6 /* Sources */ = {
217 | isa = PBXSourcesBuildPhase;
218 | buildActionMask = 2147483647;
219 | files = (
220 | 5C135F1C1C47366700AA3A01 /* Request.swift in Sources */,
221 | 5C135F1F1C47366700AA3A01 /* StatusCode.swift in Sources */,
222 | 5C135F191C47366700AA3A01 /* Operation.swift in Sources */,
223 | 5C135F161C47366700AA3A01 /* HTTPSecurity.swift in Sources */,
224 | 5C135F251C47366700AA3A01 /* Upload.swift in Sources */,
225 | );
226 | runOnlyForDeploymentPostprocessing = 0;
227 | };
228 | 6BFD901D19C8D8B500DD99B6 /* Sources */ = {
229 | isa = PBXSourcesBuildPhase;
230 | buildActionMask = 2147483647;
231 | files = (
232 | 6BFD904019C8D9A000DD99B6 /* SwiftHTTPTests.swift in Sources */,
233 | );
234 | runOnlyForDeploymentPostprocessing = 0;
235 | };
236 | /* End PBXSourcesBuildPhase section */
237 |
238 | /* Begin PBXTargetDependency section */
239 | 832C8E651B01064F0052A5D7 /* PBXTargetDependency */ = {
240 | isa = PBXTargetDependency;
241 | target = 6BFD901519C8D8B500DD99B6 /* SwiftHTTP */;
242 | targetProxy = 832C8E641B01064F0052A5D7 /* PBXContainerItemProxy */;
243 | };
244 | /* End PBXTargetDependency section */
245 |
246 | /* Begin XCBuildConfiguration section */
247 | 6BFD902719C8D8B500DD99B6 /* Debug */ = {
248 | isa = XCBuildConfiguration;
249 | buildSettings = {
250 | ALWAYS_SEARCH_USER_PATHS = NO;
251 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
252 | CLANG_CXX_LIBRARY = "libc++";
253 | CLANG_ENABLE_MODULES = YES;
254 | CLANG_ENABLE_OBJC_ARC = YES;
255 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
256 | CLANG_WARN_BOOL_CONVERSION = YES;
257 | CLANG_WARN_COMMA = YES;
258 | CLANG_WARN_CONSTANT_CONVERSION = YES;
259 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
260 | CLANG_WARN_EMPTY_BODY = YES;
261 | CLANG_WARN_ENUM_CONVERSION = YES;
262 | CLANG_WARN_INFINITE_RECURSION = YES;
263 | CLANG_WARN_INT_CONVERSION = YES;
264 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
265 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
266 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
267 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
268 | CLANG_WARN_STRICT_PROTOTYPES = YES;
269 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
270 | CLANG_WARN_UNREACHABLE_CODE = YES;
271 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
272 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
273 | COPY_PHASE_STRIP = NO;
274 | CURRENT_PROJECT_VERSION = 1;
275 | DEFINES_MODULE = NO;
276 | ENABLE_STRICT_OBJC_MSGSEND = YES;
277 | ENABLE_TESTABILITY = YES;
278 | GCC_C_LANGUAGE_STANDARD = gnu99;
279 | GCC_DYNAMIC_NO_PIC = NO;
280 | GCC_NO_COMMON_BLOCKS = YES;
281 | GCC_OPTIMIZATION_LEVEL = 0;
282 | GCC_PREPROCESSOR_DEFINITIONS = (
283 | "DEBUG=1",
284 | "$(inherited)",
285 | );
286 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
287 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
288 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
289 | GCC_WARN_UNDECLARED_SELECTOR = YES;
290 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
291 | GCC_WARN_UNUSED_FUNCTION = YES;
292 | GCC_WARN_UNUSED_VARIABLE = YES;
293 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
294 | MTL_ENABLE_DEBUG_INFO = YES;
295 | ONLY_ACTIVE_ARCH = YES;
296 | PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)";
297 | SDKROOT = iphoneos;
298 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
299 | SWIFT_VERSION = 4.0;
300 | TARGETED_DEVICE_FAMILY = "1,2";
301 | VERSIONING_SYSTEM = "apple-generic";
302 | VERSION_INFO_PREFIX = "";
303 | };
304 | name = Debug;
305 | };
306 | 6BFD902819C8D8B500DD99B6 /* Release */ = {
307 | isa = XCBuildConfiguration;
308 | buildSettings = {
309 | ALWAYS_SEARCH_USER_PATHS = NO;
310 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
311 | CLANG_CXX_LIBRARY = "libc++";
312 | CLANG_ENABLE_MODULES = YES;
313 | CLANG_ENABLE_OBJC_ARC = YES;
314 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
315 | CLANG_WARN_BOOL_CONVERSION = YES;
316 | CLANG_WARN_COMMA = YES;
317 | CLANG_WARN_CONSTANT_CONVERSION = YES;
318 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
319 | CLANG_WARN_EMPTY_BODY = YES;
320 | CLANG_WARN_ENUM_CONVERSION = YES;
321 | CLANG_WARN_INFINITE_RECURSION = YES;
322 | CLANG_WARN_INT_CONVERSION = YES;
323 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
324 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
325 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
326 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
327 | CLANG_WARN_STRICT_PROTOTYPES = YES;
328 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
329 | CLANG_WARN_UNREACHABLE_CODE = YES;
330 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
331 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
332 | COPY_PHASE_STRIP = YES;
333 | CURRENT_PROJECT_VERSION = 1;
334 | DEFINES_MODULE = NO;
335 | ENABLE_NS_ASSERTIONS = NO;
336 | ENABLE_STRICT_OBJC_MSGSEND = YES;
337 | GCC_C_LANGUAGE_STANDARD = gnu99;
338 | GCC_NO_COMMON_BLOCKS = YES;
339 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
340 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
341 | GCC_WARN_UNDECLARED_SELECTOR = YES;
342 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
343 | GCC_WARN_UNUSED_FUNCTION = YES;
344 | GCC_WARN_UNUSED_VARIABLE = YES;
345 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
346 | MTL_ENABLE_DEBUG_INFO = NO;
347 | PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)";
348 | SDKROOT = iphoneos;
349 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
350 | SWIFT_VERSION = 4.0;
351 | TARGETED_DEVICE_FAMILY = "1,2";
352 | VALIDATE_PRODUCT = YES;
353 | VERSIONING_SYSTEM = "apple-generic";
354 | VERSION_INFO_PREFIX = "";
355 | };
356 | name = Release;
357 | };
358 | 6BFD902A19C8D8B500DD99B6 /* Debug */ = {
359 | isa = XCBuildConfiguration;
360 | buildSettings = {
361 | APPLICATION_EXTENSION_API_ONLY = YES;
362 | CLANG_ENABLE_MODULES = YES;
363 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
364 | DEFINES_MODULE = YES;
365 | DYLIB_COMPATIBILITY_VERSION = 1;
366 | DYLIB_CURRENT_VERSION = 1;
367 | DYLIB_INSTALL_NAME_BASE = "@rpath";
368 | INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist";
369 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
370 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
371 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
372 | MACOSX_DEPLOYMENT_TARGET = 10.10;
373 | PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
374 | PRODUCT_NAME = "$(TARGET_NAME)";
375 | SDKROOT = "";
376 | SKIP_INSTALL = YES;
377 | SUPPORTED_PLATFORMS = "macosx watchos appletvos appletvsimulator iphoneos iphonesimulator watchsimulator";
378 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
379 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
380 | SWIFT_VERSION = 4.0;
381 | TARGETED_DEVICE_FAMILY = "1,2,3,4";
382 | TVOS_DEPLOYMENT_TARGET = 9.0;
383 | VALID_ARCHS = "i386 x86_64 arm64 armv7s armv7 armv7k";
384 | WATCHOS_DEPLOYMENT_TARGET = 2.1;
385 | };
386 | name = Debug;
387 | };
388 | 6BFD902B19C8D8B500DD99B6 /* Release */ = {
389 | isa = XCBuildConfiguration;
390 | buildSettings = {
391 | APPLICATION_EXTENSION_API_ONLY = YES;
392 | CLANG_ENABLE_MODULES = YES;
393 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
394 | DEFINES_MODULE = YES;
395 | DYLIB_COMPATIBILITY_VERSION = 1;
396 | DYLIB_CURRENT_VERSION = 1;
397 | DYLIB_INSTALL_NAME_BASE = "@rpath";
398 | INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist";
399 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
400 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
401 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
402 | MACOSX_DEPLOYMENT_TARGET = 10.10;
403 | PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
404 | PRODUCT_NAME = "$(TARGET_NAME)";
405 | SDKROOT = "";
406 | SKIP_INSTALL = YES;
407 | SUPPORTED_PLATFORMS = "macosx watchos appletvos appletvsimulator iphoneos iphonesimulator watchsimulator";
408 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
409 | SWIFT_VERSION = 4.0;
410 | TARGETED_DEVICE_FAMILY = "1,2,3,4";
411 | TVOS_DEPLOYMENT_TARGET = 9.0;
412 | VALID_ARCHS = "i386 x86_64 arm64 armv7s armv7 armv7k";
413 | WATCHOS_DEPLOYMENT_TARGET = 2.1;
414 | };
415 | name = Release;
416 | };
417 | 6BFD902D19C8D8B500DD99B6 /* Debug */ = {
418 | isa = XCBuildConfiguration;
419 | buildSettings = {
420 | FRAMEWORK_SEARCH_PATHS = (
421 | "$(SDKROOT)/Developer/Library/Frameworks",
422 | "$(inherited)",
423 | );
424 | GCC_PREPROCESSOR_DEFINITIONS = (
425 | "DEBUG=1",
426 | "$(inherited)",
427 | );
428 | INFOPLIST_FILE = Tests/Info.plist;
429 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
430 | PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
431 | PRODUCT_NAME = "$(TARGET_NAME)";
432 | SDKROOT = "";
433 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos watchsimulator watchos appletvos appletvsimulator macosx";
434 | SWIFT_VERSION = 4.0;
435 | TARGETED_DEVICE_FAMILY = "1,2,3,4";
436 | VALID_ARCHS = "arm64 armv7 armv7s i386 x86_64";
437 | };
438 | name = Debug;
439 | };
440 | 6BFD902E19C8D8B500DD99B6 /* Release */ = {
441 | isa = XCBuildConfiguration;
442 | buildSettings = {
443 | FRAMEWORK_SEARCH_PATHS = (
444 | "$(SDKROOT)/Developer/Library/Frameworks",
445 | "$(inherited)",
446 | );
447 | INFOPLIST_FILE = Tests/Info.plist;
448 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
449 | PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
450 | PRODUCT_NAME = "$(TARGET_NAME)";
451 | SDKROOT = "";
452 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos watchsimulator watchos appletvos appletvsimulator macosx";
453 | SWIFT_VERSION = 4.0;
454 | TARGETED_DEVICE_FAMILY = "1,2,3,4";
455 | VALID_ARCHS = "arm64 armv7 armv7s i386 x86_64";
456 | };
457 | name = Release;
458 | };
459 | /* End XCBuildConfiguration section */
460 |
461 | /* Begin XCConfigurationList section */
462 | 6BFD901019C8D8B500DD99B6 /* Build configuration list for PBXProject "SwiftHTTP" */ = {
463 | isa = XCConfigurationList;
464 | buildConfigurations = (
465 | 6BFD902719C8D8B500DD99B6 /* Debug */,
466 | 6BFD902819C8D8B500DD99B6 /* Release */,
467 | );
468 | defaultConfigurationIsVisible = 0;
469 | defaultConfigurationName = Release;
470 | };
471 | 6BFD902919C8D8B500DD99B6 /* Build configuration list for PBXNativeTarget "SwiftHTTP" */ = {
472 | isa = XCConfigurationList;
473 | buildConfigurations = (
474 | 6BFD902A19C8D8B500DD99B6 /* Debug */,
475 | 6BFD902B19C8D8B500DD99B6 /* Release */,
476 | );
477 | defaultConfigurationIsVisible = 0;
478 | defaultConfigurationName = Release;
479 | };
480 | 6BFD902C19C8D8B500DD99B6 /* Build configuration list for PBXNativeTarget "SwiftHTTPTests" */ = {
481 | isa = XCConfigurationList;
482 | buildConfigurations = (
483 | 6BFD902D19C8D8B500DD99B6 /* Debug */,
484 | 6BFD902E19C8D8B500DD99B6 /* Release */,
485 | );
486 | defaultConfigurationIsVisible = 0;
487 | defaultConfigurationName = Release;
488 | };
489 | /* End XCConfigurationList section */
490 | };
491 | rootObject = 6BFD900D19C8D8B500DD99B6 /* Project object */;
492 | }
493 |
--------------------------------------------------------------------------------
/Source/Operation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Operation.swift
3 | // SwiftHTTP
4 | //
5 | // Created by Dalton Cherry on 8/2/15.
6 | // Copyright © 2015 vluxe. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum HTTPOptError: Error {
12 | case invalidRequest
13 | }
14 |
15 | /**
16 | This protocol exist to allow easy and customizable swapping of a serializing format within an class methods of HTTP.
17 | */
18 | public protocol HTTPSerializeProtocol {
19 |
20 | /**
21 | implement this protocol to support serializing parameters to the proper HTTP body or URL
22 | -parameter request: The URLRequest object you will modify to add the parameters to
23 | -parameter parameters: The container (array or dictionary) to convert and append to the URL or Body
24 | */
25 | func serialize(_ request: inout URLRequest, parameters: HTTPParameterProtocol) -> Error?
26 | }
27 |
28 | /**
29 | Standard HTTP encoding
30 | */
31 | public struct HTTPParameterSerializer: HTTPSerializeProtocol {
32 | public init() { }
33 | public func serialize(_ request: inout URLRequest, parameters: HTTPParameterProtocol) -> Error? {
34 | return request.appendParameters(parameters)
35 | }
36 | }
37 |
38 | /**
39 | Send the data as a JSON body
40 | */
41 | public struct JSONParameterSerializer: HTTPSerializeProtocol {
42 | public init() { }
43 | public func serialize(_ request: inout URLRequest, parameters: HTTPParameterProtocol) -> Error? {
44 | return request.appendParametersAsJSON(parameters)
45 | }
46 | }
47 |
48 | /**
49 | All the things of an HTTP response
50 | */
51 | open class Response {
52 | /// The header values in HTTP response.
53 | open var headers: Dictionary?
54 | /// The mime type of the HTTP response.
55 | open var mimeType: String?
56 | /// The suggested filename for a downloaded file.
57 | open var suggestedFilename: String?
58 | /// The body data of the HTTP response.
59 | open var data: Data {
60 | return collectData as Data
61 | }
62 | /// The status code of the HTTP response.
63 | open var statusCode: Int?
64 | /// The URL of the HTTP response.
65 | open var URL: Foundation.URL?
66 | /// The Error of the HTTP response (if there was one).
67 | open var error: Error?
68 | ///Returns the response as a string
69 | open var text: String? {
70 | return String(data: data, encoding: .utf8)
71 | }
72 | ///get the description of the response
73 | open var description: String {
74 | var buffer = ""
75 | if let u = URL {
76 | buffer += "URL:\n\(u)\n\n"
77 | }
78 | if let code = self.statusCode {
79 | buffer += "Status Code:\n\(code)\n\n"
80 | }
81 | if let heads = headers {
82 | buffer += "Headers:\n"
83 | for (key, value) in heads {
84 | buffer += "\(key): \(value)\n"
85 | }
86 | buffer += "\n"
87 | }
88 | if let t = text {
89 | buffer += "Payload:\n\(t)\n"
90 | }
91 | return buffer
92 | }
93 | ///private things
94 |
95 | ///holds the collected data
96 | var collectData = NSMutableData()
97 | ///finish closure
98 | var completionHandler:((Response) -> Void)?
99 |
100 | //progress closure. Progress is between 0 and 1.
101 | var progressHandler:((Float) -> Void)?
102 |
103 | //download closure. the URL is the file URL where the temp file has been download.
104 | //This closure will be called so you can move the file where you desire.
105 | var downloadHandler:((Response, URL) -> Void)?
106 |
107 | ///This gets called on auth challenges. If nil, default handling is use.
108 | ///Returning nil from this method will cause the request to be rejected and cancelled
109 | var auth:((URLAuthenticationChallenge) -> URLCredential?)?
110 |
111 | ///This is for doing SSL pinning
112 | var security: HTTPSecurity?
113 | }
114 |
115 | /**
116 | The class that does the magic. Is a subclass of NSOperation so you can use it with operation queues or just a good ole HTTP request.
117 | */
118 | open class HTTP {
119 | /**
120 | Get notified with a request finishes.
121 | */
122 | open var onFinish:((Response) -> Void)? {
123 | didSet {
124 | if let handler = onFinish {
125 | DelegateManager.sharedInstance.addTask(task, completionHandler: { (response: Response) in
126 | handler(response)
127 | })
128 | }
129 | }
130 | }
131 | ///This is for handling authenication
132 | open var auth:((URLAuthenticationChallenge) -> URLCredential?)? {
133 | set {
134 | guard let resp = DelegateManager.sharedInstance.responseForTask(task) else { return }
135 | resp.auth = newValue
136 | }
137 | get {
138 | guard let resp = DelegateManager.sharedInstance.responseForTask(task) else { return nil }
139 | return resp.auth
140 | }
141 | }
142 |
143 | ///This is for doing SSL pinning
144 | open var security: HTTPSecurity? {
145 | set {
146 | guard let resp = DelegateManager.sharedInstance.responseForTask(task) else { return }
147 | resp.security = newValue
148 | }
149 | get {
150 | guard let resp = DelegateManager.sharedInstance.responseForTask(task) else { return nil }
151 | return resp.security
152 | }
153 | }
154 |
155 | ///This is for monitoring progress
156 | open var progress: ((Float) -> Void)? {
157 | set {
158 | guard let resp = DelegateManager.sharedInstance.responseForTask(task) else { return }
159 | resp.progressHandler = newValue
160 | }
161 | get {
162 | guard let resp = DelegateManager.sharedInstance.responseForTask(task) else { return nil }
163 | return resp.progressHandler
164 | }
165 | }
166 |
167 | ///This is for handling downloads
168 | open var downloadHandler: ((Response, URL) -> Void)? {
169 | set {
170 | guard let resp = DelegateManager.sharedInstance.responseForTask(task) else { return }
171 | resp.downloadHandler = newValue
172 | }
173 | get {
174 | guard let resp = DelegateManager.sharedInstance.responseForTask(task) else { return nil }
175 | return resp.downloadHandler
176 | }
177 | }
178 |
179 | ///the actual task
180 | var task: URLSessionTask!
181 |
182 | /**
183 | creates a new HTTP request.
184 | */
185 | public init(_ req: URLRequest, session: URLSession = SharedSession.defaultSession, isDownload: Bool = false) {
186 | if isDownload {
187 | task = session.downloadTask(with: req)
188 | } else {
189 | task = session.dataTask(with: req)
190 | }
191 | DelegateManager.sharedInstance.addResponseForTask(task)
192 | }
193 |
194 | /**
195 | start/sends the HTTP task with a completionHandler. Use this when *NOT* using an NSOperationQueue.
196 | */
197 | open func run(_ completionHandler: ((Response) -> Void)? = nil) {
198 | if let handler = completionHandler {
199 | onFinish = handler
200 | }
201 | task.resume()
202 | }
203 |
204 | /**
205 | Cancel the running task
206 | */
207 | open func cancel() {
208 | task.cancel()
209 | }
210 |
211 | /**
212 | Class method to run a GET request that handles the URLRequest and parameter encoding for you.
213 | */
214 | @discardableResult open class func GET(_ url: String, parameters: HTTPParameterProtocol? = nil, headers: [String:String]? = nil,
215 | requestSerializer: HTTPSerializeProtocol = HTTPParameterSerializer(), completionHandler: ((Response) -> Void)? = nil) -> HTTP? {
216 | return Run(url, method: .GET, parameters: parameters, headers: headers, requestSerializer: requestSerializer, completionHandler: completionHandler)
217 | }
218 |
219 | /**
220 | Class method to run a HEAD request that handles the URLRequest and parameter encoding for you.
221 | */
222 | @discardableResult open class func HEAD(_ url: String, parameters: HTTPParameterProtocol? = nil, headers: [String:String]? = nil, requestSerializer: HTTPSerializeProtocol = HTTPParameterSerializer(), completionHandler: ((Response) -> Void)? = nil) -> HTTP? {
223 | return Run(url, method: .HEAD, parameters: parameters, headers: headers, requestSerializer: requestSerializer, completionHandler: completionHandler)
224 | }
225 |
226 | /**
227 | Class method to run a DELETE request that handles the URLRequest and parameter encoding for you.
228 | */
229 | @discardableResult open class func DELETE(_ url: String, parameters: HTTPParameterProtocol? = nil, headers: [String:String]? = nil, requestSerializer: HTTPSerializeProtocol = HTTPParameterSerializer(), completionHandler: ((Response) -> Void)? = nil) -> HTTP? {
230 | return Run(url, method: .DELETE, parameters: parameters, headers: headers, requestSerializer: requestSerializer, completionHandler: completionHandler)
231 | }
232 |
233 | /**
234 | Class method to run a POST request that handles the URLRequest and parameter encoding for you.
235 | */
236 | @discardableResult open class func POST(_ url: String, parameters: HTTPParameterProtocol? = nil, headers: [String:String]? = nil, requestSerializer: HTTPSerializeProtocol = HTTPParameterSerializer(), completionHandler: ((Response) -> Void)? = nil) -> HTTP? {
237 | return Run(url, method: .POST, parameters: parameters, headers: headers, requestSerializer: requestSerializer, completionHandler: completionHandler)
238 | }
239 |
240 | /**
241 | Class method to run a PUT request that handles the URLRequest and parameter encoding for you.
242 | */
243 | @discardableResult open class func PUT(_ url: String, parameters: HTTPParameterProtocol? = nil, headers: [String:String]? = nil,
244 | requestSerializer: HTTPSerializeProtocol = HTTPParameterSerializer(), completionHandler: ((Response) -> Void)? = nil) -> HTTP? {
245 | return Run(url, method: .PUT, parameters: parameters, headers: headers, requestSerializer: requestSerializer, completionHandler: completionHandler)
246 | }
247 |
248 | /**
249 | Class method to run a PUT request that handles the URLRequest and parameter encoding for you.
250 | */
251 | @discardableResult open class func PATCH(_ url: String, parameters: HTTPParameterProtocol? = nil, headers: [String:String]? = nil, requestSerializer: HTTPSerializeProtocol = HTTPParameterSerializer(), completionHandler: ((Response) -> Void)? = nil) -> HTTP? {
252 | return Run(url, method: .PATCH, parameters: parameters, headers: headers, requestSerializer: requestSerializer, completionHandler: completionHandler)
253 | }
254 |
255 | @discardableResult class func Run(_ url: String, method: HTTPVerb, parameters: HTTPParameterProtocol? = nil, headers: [String:String]? = nil, requestSerializer: HTTPSerializeProtocol = HTTPParameterSerializer(), completionHandler: ((Response) -> Void)? = nil) -> HTTP? {
256 | guard let task = HTTP.New(url, method: method, parameters: parameters, headers: headers, requestSerializer: requestSerializer, completionHandler: completionHandler) else {return nil}
257 | task.run()
258 | return task
259 | }
260 |
261 | /**
262 | Class method to create a Download request that handles the URLRequest and parameter encoding for you.
263 | */
264 | open class func Download(_ url: String, parameters: HTTPParameterProtocol? = nil, headers: [String:String]? = nil,
265 | requestSerializer: HTTPSerializeProtocol = HTTPParameterSerializer(), completion:@escaping ((Response, URL) -> Void)) {
266 | guard let task = HTTP.New(url, method: .GET, parameters: parameters, headers: headers, requestSerializer: requestSerializer) else {return}
267 | task.downloadHandler = completion
268 | task.run()
269 | }
270 |
271 | /**
272 | Class method to create a HTTP request that handles the URLRequest and parameter encoding for you.
273 | */
274 | open class func New(_ url: String, method: HTTPVerb, parameters: HTTPParameterProtocol? = nil, headers: [String:String]? = nil, requestSerializer: HTTPSerializeProtocol = HTTPParameterSerializer(), completionHandler: ((Response) -> Void)? = nil) -> HTTP? {
275 | guard var req = URLRequest(urlString: url, headers: headers) else {
276 | guard let handler = completionHandler else { return nil }
277 | let resp = Response()
278 | resp.error = HTTPOptError.invalidRequest
279 | handler(resp)
280 | return nil
281 | }
282 | if let handler = DelegateManager.sharedInstance.requestHandler {
283 | handler(&req)
284 | }
285 | req.verb = method
286 | if let params = parameters {
287 | if let error = requestSerializer.serialize(&req, parameters: params) {
288 | guard let handler = completionHandler else { return nil }
289 | let resp = Response()
290 | resp.error = error
291 | handler(resp)
292 | return nil
293 | }
294 | }
295 | let httpReq = HTTP(req)
296 | httpReq.onFinish = completionHandler
297 | return httpReq
298 | }
299 |
300 | /**
301 | Set the global auth handler
302 | */
303 | open class func globalAuth(_ handler: ((URLAuthenticationChallenge) -> URLCredential?)?) {
304 | DelegateManager.sharedInstance.auth = handler
305 | }
306 |
307 | /**
308 | Set the global security handler
309 | */
310 | open class func globalSecurity(_ security: HTTPSecurity?) {
311 | DelegateManager.sharedInstance.security = security
312 | }
313 |
314 | /**
315 | Set the global request handler
316 | */
317 | open class func globalRequest(_ handler: ((inout URLRequest) -> Void)?) {
318 | DelegateManager.sharedInstance.requestHandler = handler
319 | }
320 | }
321 |
322 | extension HTTP {
323 | static func == (left: HTTP, right: HTTP) -> Bool {
324 | return left.task.taskIdentifier == right.task.taskIdentifier
325 | }
326 |
327 | static func != (left: HTTP, right: HTTP) -> Bool {
328 | return !(left == right)
329 | }
330 | }
331 |
332 | /**
333 | Absorb all the delegates methods of NSURLSession and forwards them to pretty closures.
334 | This is basically the sin eater for NSURLSession.
335 | */
336 | public class DelegateManager: NSObject, URLSessionDataDelegate, URLSessionDownloadDelegate {
337 | //the singleton to handle delegate needs of NSURLSession
338 | static let sharedInstance = DelegateManager()
339 |
340 | /// this is for global authenication handling
341 | var auth:((URLAuthenticationChallenge) -> URLCredential?)?
342 |
343 | ///This is for global SSL pinning
344 | var security: HTTPSecurity?
345 |
346 | /// this is for global request handling
347 | var requestHandler:((inout URLRequest) -> Void)?
348 |
349 | var taskMap = Dictionary()
350 | //"install" a task by adding the task to the map and setting the completion handler
351 | func addTask(_ task: URLSessionTask, completionHandler:@escaping ((Response) -> Void)) {
352 | addResponseForTask(task)
353 | if let resp = responseForTask(task) {
354 | resp.completionHandler = completionHandler
355 | }
356 | }
357 |
358 | //"remove" a task by removing the task from the map
359 | func removeTask(_ task: URLSessionTask) {
360 | taskMap.removeValue(forKey: task.taskIdentifier)
361 | }
362 |
363 | //add the response task
364 | func addResponseForTask(_ task: URLSessionTask) {
365 | if taskMap[task.taskIdentifier] == nil {
366 | taskMap[task.taskIdentifier] = Response()
367 | }
368 | }
369 | //get the response object for the task
370 | func responseForTask(_ task: URLSessionTask) -> Response? {
371 | return taskMap[task.taskIdentifier]
372 | }
373 |
374 | //handle getting data
375 | public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
376 | addResponseForTask(dataTask)
377 | guard let resp = responseForTask(dataTask) else { return }
378 | resp.collectData.append(data)
379 | if resp.progressHandler != nil { //don't want the extra cycles for no reason
380 | guard let taskResp = dataTask.response else { return }
381 | progressHandler(resp, expectedLength: taskResp.expectedContentLength, currentLength: Int64(resp.collectData.length))
382 | }
383 | }
384 |
385 | //handle task finishing
386 | public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
387 | guard let resp = responseForTask(task) else { return }
388 | resp.error = error as NSError?
389 | if let hresponse = task.response as? HTTPURLResponse {
390 | resp.headers = hresponse.allHeaderFields as? Dictionary
391 | resp.mimeType = hresponse.mimeType
392 | resp.suggestedFilename = hresponse.suggestedFilename
393 | resp.statusCode = hresponse.statusCode
394 | resp.URL = hresponse.url
395 | }
396 | if let code = resp.statusCode, code > 299 {
397 | resp.error = createError(code)
398 | }
399 | if let handler = resp.completionHandler {
400 | handler(resp)
401 | }
402 | removeTask(task)
403 | }
404 |
405 | //handle authenication
406 | public func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
407 | var sec = security
408 | var au = auth
409 | if let resp = responseForTask(task) {
410 | if let s = resp.security {
411 | sec = s
412 | }
413 | if let a = resp.auth {
414 | au = a
415 | }
416 | }
417 | if let sec = sec , challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
418 | let space = challenge.protectionSpace
419 | if let trust = space.serverTrust {
420 | if sec.isValid(trust, domain: space.host) {
421 | completionHandler(.useCredential, URLCredential(trust: trust))
422 | return
423 | }
424 | }
425 | completionHandler(.cancelAuthenticationChallenge, nil)
426 | return
427 |
428 | } else if let a = au {
429 | let cred = a(challenge)
430 | if let c = cred {
431 | completionHandler(.useCredential, c)
432 | return
433 | }
434 | completionHandler(.rejectProtectionSpace, nil)
435 | return
436 | }
437 | completionHandler(.performDefaultHandling, nil)
438 | }
439 |
440 | //upload progress
441 | public func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
442 | guard let resp = responseForTask(task) else { return }
443 | progressHandler(resp, expectedLength: totalBytesExpectedToSend, currentLength: totalBytesSent)
444 | }
445 |
446 | //download progress
447 | public func urlSession(_ session: Foundation.URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
448 | guard let resp = responseForTask(downloadTask) else { return }
449 | progressHandler(resp, expectedLength: totalBytesExpectedToWrite, currentLength: totalBytesWritten)
450 | }
451 |
452 | //handle download task
453 | public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
454 | guard let resp = responseForTask(downloadTask) else { return }
455 | guard let handler = resp.downloadHandler else { return }
456 | handler(resp, location)
457 | }
458 |
459 | //handle progress
460 | public func progressHandler(_ response: Response, expectedLength: Int64, currentLength: Int64) {
461 | guard let handler = response.progressHandler else { return }
462 | let slice = Float(1.0)/Float(expectedLength)
463 | handler(slice*Float(currentLength))
464 | }
465 |
466 | /**
467 | Create an error for response you probably don't want (400-500 HTTP responses for example).
468 |
469 | -parameter code: Code for error.
470 |
471 | -returns An NSError.
472 | */
473 | fileprivate func createError(_ code: Int) -> NSError {
474 | let text = HTTPStatusCode(statusCode: code).statusDescription
475 | return NSError(domain: "HTTP", code: code, userInfo: [NSLocalizedDescriptionKey: text])
476 | }
477 | }
478 |
479 | /**
480 | Handles providing singletons of NSURLSession.
481 | */
482 | public class SharedSession {
483 | public static let defaultSession = URLSession(configuration: URLSessionConfiguration.default,
484 | delegate: DelegateManager.sharedInstance, delegateQueue: nil)
485 | static let ephemeralSession = URLSession(configuration: URLSessionConfiguration.ephemeral,
486 | delegate: DelegateManager.sharedInstance, delegateQueue: nil)
487 | }
488 |
489 |
490 | /**
491 | Bare bones queue to manage HTTP Requests
492 | */
493 | open class HTTPQueue {
494 | public var maxSimultaneousRequest = 5
495 | var queue = [HTTP]()
496 | let mutex = NSLock()
497 | var activeReq = [Int: HTTP]()
498 | var finishedHandler: (() -> Void)?
499 |
500 | public init(maxSimultaneousRequest: Int) {
501 | self.maxSimultaneousRequest = maxSimultaneousRequest
502 | }
503 |
504 | open func add(request: URLRequest) {
505 | add(http: HTTP(request))
506 | }
507 |
508 | open func add(http: HTTP) {
509 | var doWork = false
510 | mutex.lock()
511 | queue.append(http)
512 | if activeReq.count < maxSimultaneousRequest {
513 | doWork = true
514 | }
515 | mutex.unlock()
516 | if doWork {
517 | run()
518 | }
519 | }
520 |
521 | open func finished(queue: DispatchQueue = DispatchQueue.main, completionHandler: @escaping (() -> Void)) {
522 | finishedHandler = completionHandler
523 | }
524 |
525 | func run() {
526 | guard let http = nextItem() else {
527 | mutex.lock()
528 | let count = activeReq.count
529 | mutex.unlock()
530 | if count == 0 {
531 | finishedHandler?()
532 | }
533 | return
534 | }
535 | let handler = http.onFinish
536 | http.run {[weak self] (response) in
537 | handler?(response)
538 | self?.mutex.lock()
539 | self?.activeReq.removeValue(forKey: http.task.taskIdentifier)
540 | self?.mutex.unlock()
541 | self?.run()
542 | }
543 | }
544 |
545 | func nextItem() -> HTTP? {
546 | mutex.lock()
547 | if queue.count == 0 {
548 | mutex.unlock()
549 | return nil
550 | }
551 | let next = queue.removeFirst()
552 | activeReq[next.task.taskIdentifier] = next
553 | mutex.unlock()
554 | return next
555 | }
556 | }
557 |
--------------------------------------------------------------------------------