├── .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 | --------------------------------------------------------------------------------