├── .gitignore ├── .travis.yml ├── Example ├── Podfile.lock ├── SMHTTPClient.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── SMHTTPClient-Example.xcscheme └── Tests │ ├── Tests-Info.plist │ ├── Tests-Prefix.pch │ └── en.lproj │ └── InfoPlist.strings ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md ├── SMHTTPClient.podspec ├── SMHTTPClient.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── SMHTTPClient.xcscheme ├── SMHTTPClient ├── Classes │ ├── .gitkeep │ ├── Buffer.swift │ ├── Error.swift │ ├── HttpRequest.swift │ └── NameResolver.swift └── Info.plist └── SMHTTPClientTests ├── HttpRequestTests.swift ├── Info.plist └── NameResolverTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | Carthage 26 | # We recommend against adding the Pods directory to your .gitignore. However 27 | # you should judge for yourself, the pros and cons are mentioned at: 28 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 29 | # 30 | # Note: if you ignore the Pods directory, make sure to uncomment 31 | # `pod install` in .travis.yml 32 | # 33 | # Pods/ 34 | SMHTTPClient.xcworkspace 35 | Pods 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * http://www.objc.io/issue-6/travis-ci.html 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | language: objective-c 6 | cache: cocoapods 7 | podfile: Podfile 8 | before_install: 9 | - gem install cocoapods # Since Travis is not always on latest version 10 | - pod install 11 | script: 12 | - set -o pipefail && xcodebuild test -workspace SMHTTPClient.xcworkspace -scheme SMHTTPClient -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty 13 | - pod lib lint 14 | osx_image: xcode7.1 15 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - GCDWebServer (3.2.7): 3 | - GCDWebServer/Core (= 3.2.7) 4 | - GCDWebServer/Core (3.2.7) 5 | - Nimble (3.0.0) 6 | - Quick (0.8.0) 7 | - SMHTTPClient (0.1.0) 8 | 9 | DEPENDENCIES: 10 | - GCDWebServer 11 | - Nimble 12 | - Quick 13 | - SMHTTPClient (from `../`) 14 | 15 | EXTERNAL SOURCES: 16 | SMHTTPClient: 17 | :path: "../" 18 | 19 | SPEC CHECKSUMS: 20 | GCDWebServer: 76a9540371d5f12f850b32ec7f555dd2d266e543 21 | Nimble: 4c353d43735b38b545cbb4cb91504588eb5de926 22 | Quick: 563d0f6ec5f72e394645adb377708639b7dd38ab 23 | SMHTTPClient: 945e965dd8dac714300e19a4e7a35ba363fc2aad 24 | 25 | COCOAPODS: 0.39.0 26 | -------------------------------------------------------------------------------- /Example/SMHTTPClient.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; }; 11 | 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 12 | 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; 13 | 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; }; 14 | AB91FB7F1BEE5E3700807C88 /* NameResolverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB91FB7E1BEE5E3700807C88 /* NameResolverTests.swift */; }; 15 | AB91FB831BF01F9D00807C88 /* HttpRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB91FB821BF01F9D00807C88 /* HttpRequestTests.swift */; }; 16 | AB91FB931BF1E01C00807C88 /* Buffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB91FB8F1BF1E01C00807C88 /* Buffer.swift */; }; 17 | AB91FB941BF1E01C00807C88 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB91FB901BF1E01C00807C88 /* Error.swift */; }; 18 | AB91FB951BF1E01C00807C88 /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB91FB911BF1E01C00807C88 /* HttpRequest.swift */; }; 19 | AB91FB961BF1E01C00807C88 /* NameResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB91FB921BF1E01C00807C88 /* NameResolver.swift */; }; 20 | D5A5F3FEE041E70F4B4C8B3B /* Pods_SMHTTPClient_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D7C3351F625A340252A16E47 /* Pods_SMHTTPClient_Tests.framework */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | 6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 25 | 6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 26 | 6003F591195388D20070C39A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 27 | 6003F5AE195388D20070C39A /* SMHTTPClient_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SMHTTPClient_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | 6003F5AF195388D20070C39A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 29 | 6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = ""; }; 30 | 6003F5B9195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 31 | 606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = ""; }; 32 | A15E041BFDEFEF97CEE5A72C /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 33 | AB91FB7E1BEE5E3700807C88 /* NameResolverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NameResolverTests.swift; sourceTree = ""; }; 34 | AB91FB821BF01F9D00807C88 /* HttpRequestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRequestTests.swift; sourceTree = ""; }; 35 | AB91FB8F1BF1E01C00807C88 /* Buffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Buffer.swift; sourceTree = ""; }; 36 | AB91FB901BF1E01C00807C88 /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; 37 | AB91FB911BF1E01C00807C88 /* HttpRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRequest.swift; sourceTree = ""; }; 38 | AB91FB921BF1E01C00807C88 /* NameResolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NameResolver.swift; sourceTree = ""; }; 39 | AC744B44909CA40E36CDE464 /* Pods-SMHTTPClient_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SMHTTPClient_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SMHTTPClient_Tests/Pods-SMHTTPClient_Tests.debug.xcconfig"; sourceTree = ""; }; 40 | ADE2710BDD213BC8C5BFAF97 /* SMHTTPClient.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = SMHTTPClient.podspec; path = ../SMHTTPClient.podspec; sourceTree = ""; }; 41 | D3FA001E2B2D0F6B368E6AD3 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 42 | D7C3351F625A340252A16E47 /* Pods_SMHTTPClient_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SMHTTPClient_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | EB48CA3F49E1108AE090AE47 /* Pods-SMHTTPClient_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SMHTTPClient_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-SMHTTPClient_Tests/Pods-SMHTTPClient_Tests.release.xcconfig"; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | 6003F5AB195388D20070C39A /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */, 52 | 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */, 53 | 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */, 54 | D5A5F3FEE041E70F4B4C8B3B /* Pods_SMHTTPClient_Tests.framework in Frameworks */, 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | /* End PBXFrameworksBuildPhase section */ 59 | 60 | /* Begin PBXGroup section */ 61 | 6003F581195388D10070C39A = { 62 | isa = PBXGroup; 63 | children = ( 64 | AB91FB8E1BF1DFB200807C88 /* Classes */, 65 | 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */, 66 | 6003F5B5195388D20070C39A /* Tests */, 67 | 6003F58C195388D20070C39A /* Frameworks */, 68 | 6003F58B195388D20070C39A /* Products */, 69 | 71FEFC266C680802521D27E1 /* Pods */, 70 | ); 71 | sourceTree = ""; 72 | }; 73 | 6003F58B195388D20070C39A /* Products */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | 6003F5AE195388D20070C39A /* SMHTTPClient_Tests.xctest */, 77 | ); 78 | name = Products; 79 | sourceTree = ""; 80 | }; 81 | 6003F58C195388D20070C39A /* Frameworks */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 6003F58D195388D20070C39A /* Foundation.framework */, 85 | 6003F58F195388D20070C39A /* CoreGraphics.framework */, 86 | 6003F591195388D20070C39A /* UIKit.framework */, 87 | 6003F5AF195388D20070C39A /* XCTest.framework */, 88 | D7C3351F625A340252A16E47 /* Pods_SMHTTPClient_Tests.framework */, 89 | ); 90 | name = Frameworks; 91 | sourceTree = ""; 92 | }; 93 | 6003F5B5195388D20070C39A /* Tests */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 6003F5B6195388D20070C39A /* Supporting Files */, 97 | AB91FB7E1BEE5E3700807C88 /* NameResolverTests.swift */, 98 | AB91FB821BF01F9D00807C88 /* HttpRequestTests.swift */, 99 | ); 100 | path = Tests; 101 | sourceTree = ""; 102 | }; 103 | 6003F5B6195388D20070C39A /* Supporting Files */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 6003F5B7195388D20070C39A /* Tests-Info.plist */, 107 | 6003F5B8195388D20070C39A /* InfoPlist.strings */, 108 | 606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */, 109 | ); 110 | name = "Supporting Files"; 111 | sourceTree = ""; 112 | }; 113 | 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | ADE2710BDD213BC8C5BFAF97 /* SMHTTPClient.podspec */, 117 | A15E041BFDEFEF97CEE5A72C /* README.md */, 118 | D3FA001E2B2D0F6B368E6AD3 /* LICENSE */, 119 | ); 120 | name = "Podspec Metadata"; 121 | sourceTree = ""; 122 | }; 123 | 71FEFC266C680802521D27E1 /* Pods */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | AC744B44909CA40E36CDE464 /* Pods-SMHTTPClient_Tests.debug.xcconfig */, 127 | EB48CA3F49E1108AE090AE47 /* Pods-SMHTTPClient_Tests.release.xcconfig */, 128 | ); 129 | name = Pods; 130 | sourceTree = ""; 131 | }; 132 | AB91FB8E1BF1DFB200807C88 /* Classes */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | AB91FB8F1BF1E01C00807C88 /* Buffer.swift */, 136 | AB91FB901BF1E01C00807C88 /* Error.swift */, 137 | AB91FB911BF1E01C00807C88 /* HttpRequest.swift */, 138 | AB91FB921BF1E01C00807C88 /* NameResolver.swift */, 139 | ); 140 | name = Classes; 141 | path = ../Pod/Classes; 142 | sourceTree = ""; 143 | }; 144 | /* End PBXGroup section */ 145 | 146 | /* Begin PBXNativeTarget section */ 147 | 6003F5AD195388D20070C39A /* SMHTTPClient_Tests */ = { 148 | isa = PBXNativeTarget; 149 | buildConfigurationList = 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "SMHTTPClient_Tests" */; 150 | buildPhases = ( 151 | 256DC034C934CE522774105E /* Check Pods Manifest.lock */, 152 | 6003F5AA195388D20070C39A /* Sources */, 153 | 6003F5AB195388D20070C39A /* Frameworks */, 154 | 6003F5AC195388D20070C39A /* Resources */, 155 | E367D51D633318B1677F10E6 /* Embed Pods Frameworks */, 156 | 915B2892F074564E1842D4C8 /* Copy Pods Resources */, 157 | ); 158 | buildRules = ( 159 | ); 160 | dependencies = ( 161 | ); 162 | name = SMHTTPClient_Tests; 163 | productName = SMHTTPClientTests; 164 | productReference = 6003F5AE195388D20070C39A /* SMHTTPClient_Tests.xctest */; 165 | productType = "com.apple.product-type.bundle.unit-test"; 166 | }; 167 | /* End PBXNativeTarget section */ 168 | 169 | /* Begin PBXProject section */ 170 | 6003F582195388D10070C39A /* Project object */ = { 171 | isa = PBXProject; 172 | attributes = { 173 | CLASSPREFIX = SM; 174 | LastSwiftUpdateCheck = 0710; 175 | LastUpgradeCheck = 0710; 176 | ORGANIZATIONNAME = "Soutaro Matsumoto"; 177 | TargetAttributes = { 178 | 6003F5AD195388D20070C39A = { 179 | TestTargetID = 6003F589195388D20070C39A; 180 | }; 181 | }; 182 | }; 183 | buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "SMHTTPClient" */; 184 | compatibilityVersion = "Xcode 3.2"; 185 | developmentRegion = English; 186 | hasScannedForEncodings = 0; 187 | knownRegions = ( 188 | en, 189 | Base, 190 | ); 191 | mainGroup = 6003F581195388D10070C39A; 192 | productRefGroup = 6003F58B195388D20070C39A /* Products */; 193 | projectDirPath = ""; 194 | projectRoot = ""; 195 | targets = ( 196 | 6003F5AD195388D20070C39A /* SMHTTPClient_Tests */, 197 | ); 198 | }; 199 | /* End PBXProject section */ 200 | 201 | /* Begin PBXResourcesBuildPhase section */ 202 | 6003F5AC195388D20070C39A /* Resources */ = { 203 | isa = PBXResourcesBuildPhase; 204 | buildActionMask = 2147483647; 205 | files = ( 206 | 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */, 207 | ); 208 | runOnlyForDeploymentPostprocessing = 0; 209 | }; 210 | /* End PBXResourcesBuildPhase section */ 211 | 212 | /* Begin PBXShellScriptBuildPhase section */ 213 | 256DC034C934CE522774105E /* Check Pods Manifest.lock */ = { 214 | isa = PBXShellScriptBuildPhase; 215 | buildActionMask = 2147483647; 216 | files = ( 217 | ); 218 | inputPaths = ( 219 | ); 220 | name = "Check Pods Manifest.lock"; 221 | outputPaths = ( 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | shellPath = /bin/sh; 225 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 226 | showEnvVarsInLog = 0; 227 | }; 228 | 915B2892F074564E1842D4C8 /* Copy Pods Resources */ = { 229 | isa = PBXShellScriptBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | ); 233 | inputPaths = ( 234 | ); 235 | name = "Copy Pods Resources"; 236 | outputPaths = ( 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | shellPath = /bin/sh; 240 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SMHTTPClient_Tests/Pods-SMHTTPClient_Tests-resources.sh\"\n"; 241 | showEnvVarsInLog = 0; 242 | }; 243 | E367D51D633318B1677F10E6 /* Embed Pods Frameworks */ = { 244 | isa = PBXShellScriptBuildPhase; 245 | buildActionMask = 2147483647; 246 | files = ( 247 | ); 248 | inputPaths = ( 249 | ); 250 | name = "Embed Pods Frameworks"; 251 | outputPaths = ( 252 | ); 253 | runOnlyForDeploymentPostprocessing = 0; 254 | shellPath = /bin/sh; 255 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SMHTTPClient_Tests/Pods-SMHTTPClient_Tests-frameworks.sh\"\n"; 256 | showEnvVarsInLog = 0; 257 | }; 258 | /* End PBXShellScriptBuildPhase section */ 259 | 260 | /* Begin PBXSourcesBuildPhase section */ 261 | 6003F5AA195388D20070C39A /* Sources */ = { 262 | isa = PBXSourcesBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | AB91FB941BF1E01C00807C88 /* Error.swift in Sources */, 266 | AB91FB951BF1E01C00807C88 /* HttpRequest.swift in Sources */, 267 | AB91FB831BF01F9D00807C88 /* HttpRequestTests.swift in Sources */, 268 | AB91FB7F1BEE5E3700807C88 /* NameResolverTests.swift in Sources */, 269 | AB91FB961BF1E01C00807C88 /* NameResolver.swift in Sources */, 270 | AB91FB931BF1E01C00807C88 /* Buffer.swift in Sources */, 271 | ); 272 | runOnlyForDeploymentPostprocessing = 0; 273 | }; 274 | /* End PBXSourcesBuildPhase section */ 275 | 276 | /* Begin PBXVariantGroup section */ 277 | 6003F5B8195388D20070C39A /* InfoPlist.strings */ = { 278 | isa = PBXVariantGroup; 279 | children = ( 280 | 6003F5B9195388D20070C39A /* en */, 281 | ); 282 | name = InfoPlist.strings; 283 | sourceTree = ""; 284 | }; 285 | /* End PBXVariantGroup section */ 286 | 287 | /* Begin XCBuildConfiguration section */ 288 | 6003F5BD195388D20070C39A /* Debug */ = { 289 | isa = XCBuildConfiguration; 290 | buildSettings = { 291 | ALWAYS_SEARCH_USER_PATHS = NO; 292 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 293 | CLANG_CXX_LIBRARY = "libc++"; 294 | CLANG_ENABLE_MODULES = YES; 295 | CLANG_ENABLE_OBJC_ARC = YES; 296 | CLANG_WARN_BOOL_CONVERSION = YES; 297 | CLANG_WARN_CONSTANT_CONVERSION = YES; 298 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 299 | CLANG_WARN_EMPTY_BODY = YES; 300 | CLANG_WARN_ENUM_CONVERSION = YES; 301 | CLANG_WARN_INT_CONVERSION = YES; 302 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 303 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 304 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 305 | COPY_PHASE_STRIP = NO; 306 | ENABLE_TESTABILITY = YES; 307 | GCC_C_LANGUAGE_STANDARD = gnu99; 308 | GCC_DYNAMIC_NO_PIC = NO; 309 | GCC_OPTIMIZATION_LEVEL = 0; 310 | GCC_PREPROCESSOR_DEFINITIONS = ( 311 | "DEBUG=1", 312 | "$(inherited)", 313 | ); 314 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 315 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 316 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 317 | GCC_WARN_UNDECLARED_SELECTOR = YES; 318 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 319 | GCC_WARN_UNUSED_FUNCTION = YES; 320 | GCC_WARN_UNUSED_VARIABLE = YES; 321 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 322 | ONLY_ACTIVE_ARCH = YES; 323 | SDKROOT = iphoneos; 324 | TARGETED_DEVICE_FAMILY = "1,2"; 325 | }; 326 | name = Debug; 327 | }; 328 | 6003F5BE195388D20070C39A /* Release */ = { 329 | isa = XCBuildConfiguration; 330 | buildSettings = { 331 | ALWAYS_SEARCH_USER_PATHS = NO; 332 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 333 | CLANG_CXX_LIBRARY = "libc++"; 334 | CLANG_ENABLE_MODULES = YES; 335 | CLANG_ENABLE_OBJC_ARC = YES; 336 | CLANG_WARN_BOOL_CONVERSION = YES; 337 | CLANG_WARN_CONSTANT_CONVERSION = YES; 338 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 339 | CLANG_WARN_EMPTY_BODY = YES; 340 | CLANG_WARN_ENUM_CONVERSION = YES; 341 | CLANG_WARN_INT_CONVERSION = YES; 342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 343 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 344 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 345 | COPY_PHASE_STRIP = YES; 346 | ENABLE_NS_ASSERTIONS = NO; 347 | GCC_C_LANGUAGE_STANDARD = gnu99; 348 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 349 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 350 | GCC_WARN_UNDECLARED_SELECTOR = YES; 351 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 352 | GCC_WARN_UNUSED_FUNCTION = YES; 353 | GCC_WARN_UNUSED_VARIABLE = YES; 354 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 355 | SDKROOT = iphoneos; 356 | TARGETED_DEVICE_FAMILY = "1,2"; 357 | VALIDATE_PRODUCT = YES; 358 | }; 359 | name = Release; 360 | }; 361 | 6003F5C3195388D20070C39A /* Debug */ = { 362 | isa = XCBuildConfiguration; 363 | baseConfigurationReference = AC744B44909CA40E36CDE464 /* Pods-SMHTTPClient_Tests.debug.xcconfig */; 364 | buildSettings = { 365 | CLANG_ENABLE_MODULES = YES; 366 | FRAMEWORK_SEARCH_PATHS = ( 367 | "$(SDKROOT)/Developer/Library/Frameworks", 368 | "$(inherited)", 369 | "$(DEVELOPER_FRAMEWORKS_DIR)", 370 | ); 371 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 372 | GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; 373 | GCC_PREPROCESSOR_DEFINITIONS = ( 374 | "DEBUG=1", 375 | "$(inherited)", 376 | ); 377 | INFOPLIST_FILE = "Tests/Tests-Info.plist"; 378 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 379 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; 380 | PRODUCT_NAME = "$(TARGET_NAME)"; 381 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 382 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SMHTTPClient_Example.app/SMHTTPClient_Example"; 383 | WRAPPER_EXTENSION = xctest; 384 | }; 385 | name = Debug; 386 | }; 387 | 6003F5C4195388D20070C39A /* Release */ = { 388 | isa = XCBuildConfiguration; 389 | baseConfigurationReference = EB48CA3F49E1108AE090AE47 /* Pods-SMHTTPClient_Tests.release.xcconfig */; 390 | buildSettings = { 391 | CLANG_ENABLE_MODULES = YES; 392 | FRAMEWORK_SEARCH_PATHS = ( 393 | "$(SDKROOT)/Developer/Library/Frameworks", 394 | "$(inherited)", 395 | "$(DEVELOPER_FRAMEWORKS_DIR)", 396 | ); 397 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 398 | GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; 399 | INFOPLIST_FILE = "Tests/Tests-Info.plist"; 400 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 401 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; 402 | PRODUCT_NAME = "$(TARGET_NAME)"; 403 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SMHTTPClient_Example.app/SMHTTPClient_Example"; 404 | WRAPPER_EXTENSION = xctest; 405 | }; 406 | name = Release; 407 | }; 408 | /* End XCBuildConfiguration section */ 409 | 410 | /* Begin XCConfigurationList section */ 411 | 6003F585195388D10070C39A /* Build configuration list for PBXProject "SMHTTPClient" */ = { 412 | isa = XCConfigurationList; 413 | buildConfigurations = ( 414 | 6003F5BD195388D20070C39A /* Debug */, 415 | 6003F5BE195388D20070C39A /* Release */, 416 | ); 417 | defaultConfigurationIsVisible = 0; 418 | defaultConfigurationName = Release; 419 | }; 420 | 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "SMHTTPClient_Tests" */ = { 421 | isa = XCConfigurationList; 422 | buildConfigurations = ( 423 | 6003F5C3195388D20070C39A /* Debug */, 424 | 6003F5C4195388D20070C39A /* Release */, 425 | ); 426 | defaultConfigurationIsVisible = 0; 427 | defaultConfigurationName = Release; 428 | }; 429 | /* End XCConfigurationList section */ 430 | }; 431 | rootObject = 6003F582195388D10070C39A /* Project object */; 432 | } 433 | -------------------------------------------------------------------------------- /Example/SMHTTPClient.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/SMHTTPClient.xcodeproj/xcshareddata/xcschemes/SMHTTPClient-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Example/Tests/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 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/Tests/Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // The contents of this file are implicitly included at the beginning of every test case source file. 2 | 3 | #ifdef __OBJC__ 4 | 5 | @import Specta; 6 | @import Expecta; 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /Example/Tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Soutaro Matsumoto 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | use_frameworks! 3 | 4 | inhibit_all_warnings! 5 | xcodeproj 'SMHTTPClient' 6 | 7 | target 'SMHTTPClientTests', :exclusive => true do 8 | pod "GCDWebServer" 9 | pod 'Quick' 10 | pod 'Nimble' 11 | end 12 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - GCDWebServer (3.2.7): 3 | - GCDWebServer/Core (= 3.2.7) 4 | - GCDWebServer/Core (3.2.7) 5 | - Nimble (3.0.0) 6 | - Quick (0.8.0) 7 | 8 | DEPENDENCIES: 9 | - GCDWebServer 10 | - Nimble 11 | - Quick 12 | 13 | SPEC CHECKSUMS: 14 | GCDWebServer: 76a9540371d5f12f850b32ec7f555dd2d266e543 15 | Nimble: 4c353d43735b38b545cbb4cb91504588eb5de926 16 | Quick: 563d0f6ec5f72e394645adb377708639b7dd38ab 17 | 18 | COCOAPODS: 0.39.0 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SMHTTPClient 2 | 3 | [![CI Status](http://img.shields.io/travis/soutaro/SMHTTPClient.svg?style=flat)](https://travis-ci.org/soutaro/SMHTTPClient) 4 | [![Version](https://img.shields.io/cocoapods/v/SMHTTPClient.svg?style=flat)](http://cocoapods.org/pods/SMHTTPClient) 5 | [![License](https://img.shields.io/cocoapods/l/SMHTTPClient.svg?style=flat)](http://cocoapods.org/pods/SMHTTPClient) 6 | [![Platform](https://img.shields.io/cocoapods/p/SMHTTPClient.svg?style=flat)](http://cocoapods.org/pods/SMHTTPClient) 7 | 8 | SMHTTPClient is a HTTP/1.1 client based on socket API. 9 | Since it does not depend on NSURLSession, application transport security does not prohibit sending plain-text requests with this library. 10 | 11 | ## Usage 12 | 13 | ```swift 14 | let resolver = NameResolver(hostname: "your-server.local", port: 80) 15 | resolver.run() 16 | let addr = resolver.results.first! 17 | 18 | let request = HttpRequest(address: addr, path: "/", method: .GET, header: [("Host": "your-server.local")]) 19 | request.run() 20 | 21 | switch request.status { 22 | case .Completed(let code, let header, let data): 23 | // Success 24 | case .Error(let error): 25 | // An error occured 26 | case .Aborted: 27 | // Aborted 28 | default: 29 | // ... 30 | } 31 | ``` 32 | 33 | It provides a blocking API. 34 | When you want to cancel a running request, you should call `request.abort()` from another thread. 35 | 36 | ```swift 37 | let request = HttpRequest(...) 38 | let queue = dispatch_queue_create("abort.queue", nil) 39 | 40 | // Timeout after 5 seconds 41 | let delay = 5 * Double(NSEC_PER_SEC) 42 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay)), queue, { 43 | request.abort(); 44 | }) 45 | 46 | request.run() 47 | 48 | // request.status will be .Aborted 49 | ``` 50 | 51 | ## Installation 52 | 53 | SMHTTPClient is available through [CocoaPods](http://cocoapods.org). To install 54 | it, simply add the following line to your Podfile: 55 | 56 | ```ruby 57 | pod "SMHTTPClient" 58 | ``` 59 | 60 | ## Author 61 | 62 | Soutaro Matsumoto, matsumoto@soutaro.com 63 | 64 | ## License 65 | 66 | SMHTTPClient is available under the MIT license. See the LICENSE file for more info. 67 | -------------------------------------------------------------------------------- /SMHTTPClient.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint SMHTTPClient.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = "SMHTTPClient" 11 | s.version = "0.2.0" 12 | s.summary = "HTTP/1.1 client, based on socket" 13 | 14 | s.description = <<-DESC 15 | Use SMHTTPClient if you need a HTTP/1.1 access without TLS. 16 | This is expected for apps to be used with appliance which speaks HTTP/1.1 without TLS and Bonjour. 17 | DESC 18 | 19 | s.homepage = "https://github.com/soutaro/SMHTTPClient" 20 | s.license = 'MIT' 21 | s.author = { "Soutaro Matsumoto" => "matsumoto@soutaro.com" } 22 | s.source = { :git => "https://github.com/soutaro/SMHTTPClient.git", :tag => s.version.to_s } 23 | 24 | s.requires_arc = true 25 | s.ios.deployment_target = '8.0' 26 | 27 | s.source_files = 'SMHTTPClient/Classes/**/*' 28 | end 29 | -------------------------------------------------------------------------------- /SMHTTPClient.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A4CE73256C407D3C586B5DB6 /* Pods_SMHTTPClientTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 59FED70A409387D288D4856B /* Pods_SMHTTPClientTests.framework */; }; 11 | AB93849C1BF20F2400A372AF /* Buffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB9384981BF20F2400A372AF /* Buffer.swift */; }; 12 | AB93849D1BF20F2400A372AF /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB9384991BF20F2400A372AF /* Error.swift */; }; 13 | AB93849E1BF20F2400A372AF /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB93849A1BF20F2400A372AF /* HttpRequest.swift */; }; 14 | AB93849F1BF20F2400A372AF /* NameResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB93849B1BF20F2400A372AF /* NameResolver.swift */; }; 15 | AB9384A21BF20F7E00A372AF /* HttpRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB9384A01BF20F7E00A372AF /* HttpRequestTests.swift */; }; 16 | AB9384A31BF20F7E00A372AF /* NameResolverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB9384A11BF20F7E00A372AF /* NameResolverTests.swift */; }; 17 | ABBF8A4A1BF20EA500DBB536 /* SMHTTPClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ABBF8A3F1BF20EA500DBB536 /* SMHTTPClient.framework */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXContainerItemProxy section */ 21 | ABBF8A4B1BF20EA500DBB536 /* PBXContainerItemProxy */ = { 22 | isa = PBXContainerItemProxy; 23 | containerPortal = ABBF8A361BF20EA500DBB536 /* Project object */; 24 | proxyType = 1; 25 | remoteGlobalIDString = ABBF8A3E1BF20EA500DBB536; 26 | remoteInfo = SMHTTPClient; 27 | }; 28 | /* End PBXContainerItemProxy section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 59FED70A409387D288D4856B /* Pods_SMHTTPClientTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SMHTTPClientTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | AB9384981BF20F2400A372AF /* Buffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Buffer.swift; sourceTree = ""; }; 33 | AB9384991BF20F2400A372AF /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; 34 | AB93849A1BF20F2400A372AF /* HttpRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRequest.swift; sourceTree = ""; }; 35 | AB93849B1BF20F2400A372AF /* NameResolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NameResolver.swift; sourceTree = ""; }; 36 | AB9384A01BF20F7E00A372AF /* HttpRequestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRequestTests.swift; sourceTree = ""; }; 37 | AB9384A11BF20F7E00A372AF /* NameResolverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NameResolverTests.swift; sourceTree = ""; }; 38 | ABBF8A3F1BF20EA500DBB536 /* SMHTTPClient.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SMHTTPClient.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | ABBF8A441BF20EA500DBB536 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | ABBF8A491BF20EA500DBB536 /* SMHTTPClientTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SMHTTPClientTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | ABBF8A501BF20EA500DBB536 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | AEC4CA233AF26BB577137A94 /* Pods-SMHTTPClientTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SMHTTPClientTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-SMHTTPClientTests/Pods-SMHTTPClientTests.release.xcconfig"; sourceTree = ""; }; 43 | E0DA648C70A400982A6E6E48 /* Pods-SMHTTPClientTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SMHTTPClientTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SMHTTPClientTests/Pods-SMHTTPClientTests.debug.xcconfig"; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | ABBF8A3B1BF20EA500DBB536 /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | ABBF8A461BF20EA500DBB536 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | ABBF8A4A1BF20EA500DBB536 /* SMHTTPClient.framework in Frameworks */, 59 | A4CE73256C407D3C586B5DB6 /* Pods_SMHTTPClientTests.framework in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | /* End PBXFrameworksBuildPhase section */ 64 | 65 | /* Begin PBXGroup section */ 66 | 867B0AA6B06C624CDF747E70 /* Pods */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | E0DA648C70A400982A6E6E48 /* Pods-SMHTTPClientTests.debug.xcconfig */, 70 | AEC4CA233AF26BB577137A94 /* Pods-SMHTTPClientTests.release.xcconfig */, 71 | ); 72 | name = Pods; 73 | sourceTree = ""; 74 | }; 75 | 9CB9184F58E99607FA121893 /* Frameworks */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 59FED70A409387D288D4856B /* Pods_SMHTTPClientTests.framework */, 79 | ); 80 | name = Frameworks; 81 | sourceTree = ""; 82 | }; 83 | AB9384971BF20EFD00A372AF /* Classes */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | AB9384981BF20F2400A372AF /* Buffer.swift */, 87 | AB9384991BF20F2400A372AF /* Error.swift */, 88 | AB93849A1BF20F2400A372AF /* HttpRequest.swift */, 89 | AB93849B1BF20F2400A372AF /* NameResolver.swift */, 90 | ); 91 | path = Classes; 92 | sourceTree = ""; 93 | }; 94 | ABBF8A351BF20EA500DBB536 = { 95 | isa = PBXGroup; 96 | children = ( 97 | ABBF8A411BF20EA500DBB536 /* SMHTTPClient */, 98 | ABBF8A4D1BF20EA500DBB536 /* SMHTTPClientTests */, 99 | ABBF8A401BF20EA500DBB536 /* Products */, 100 | 867B0AA6B06C624CDF747E70 /* Pods */, 101 | 9CB9184F58E99607FA121893 /* Frameworks */, 102 | ); 103 | sourceTree = ""; 104 | }; 105 | ABBF8A401BF20EA500DBB536 /* Products */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | ABBF8A3F1BF20EA500DBB536 /* SMHTTPClient.framework */, 109 | ABBF8A491BF20EA500DBB536 /* SMHTTPClientTests.xctest */, 110 | ); 111 | name = Products; 112 | sourceTree = ""; 113 | }; 114 | ABBF8A411BF20EA500DBB536 /* SMHTTPClient */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | AB9384971BF20EFD00A372AF /* Classes */, 118 | ABBF8A441BF20EA500DBB536 /* Info.plist */, 119 | ); 120 | path = SMHTTPClient; 121 | sourceTree = ""; 122 | }; 123 | ABBF8A4D1BF20EA500DBB536 /* SMHTTPClientTests */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | AB9384A01BF20F7E00A372AF /* HttpRequestTests.swift */, 127 | AB9384A11BF20F7E00A372AF /* NameResolverTests.swift */, 128 | ABBF8A501BF20EA500DBB536 /* Info.plist */, 129 | ); 130 | path = SMHTTPClientTests; 131 | sourceTree = ""; 132 | }; 133 | /* End PBXGroup section */ 134 | 135 | /* Begin PBXHeadersBuildPhase section */ 136 | ABBF8A3C1BF20EA500DBB536 /* Headers */ = { 137 | isa = PBXHeadersBuildPhase; 138 | buildActionMask = 2147483647; 139 | files = ( 140 | ); 141 | runOnlyForDeploymentPostprocessing = 0; 142 | }; 143 | /* End PBXHeadersBuildPhase section */ 144 | 145 | /* Begin PBXNativeTarget section */ 146 | ABBF8A3E1BF20EA500DBB536 /* SMHTTPClient */ = { 147 | isa = PBXNativeTarget; 148 | buildConfigurationList = ABBF8A531BF20EA500DBB536 /* Build configuration list for PBXNativeTarget "SMHTTPClient" */; 149 | buildPhases = ( 150 | ABBF8A3A1BF20EA500DBB536 /* Sources */, 151 | ABBF8A3B1BF20EA500DBB536 /* Frameworks */, 152 | ABBF8A3C1BF20EA500DBB536 /* Headers */, 153 | ABBF8A3D1BF20EA500DBB536 /* Resources */, 154 | ); 155 | buildRules = ( 156 | ); 157 | dependencies = ( 158 | ); 159 | name = SMHTTPClient; 160 | productName = SMHTTPClient; 161 | productReference = ABBF8A3F1BF20EA500DBB536 /* SMHTTPClient.framework */; 162 | productType = "com.apple.product-type.framework"; 163 | }; 164 | ABBF8A481BF20EA500DBB536 /* SMHTTPClientTests */ = { 165 | isa = PBXNativeTarget; 166 | buildConfigurationList = ABBF8A561BF20EA500DBB536 /* Build configuration list for PBXNativeTarget "SMHTTPClientTests" */; 167 | buildPhases = ( 168 | 2C6051AE6C5E498DC0B9BEE9 /* Check Pods Manifest.lock */, 169 | ABBF8A451BF20EA500DBB536 /* Sources */, 170 | ABBF8A461BF20EA500DBB536 /* Frameworks */, 171 | ABBF8A471BF20EA500DBB536 /* Resources */, 172 | 051E0450DA83A695362A3228 /* Embed Pods Frameworks */, 173 | 657B33EB168E1491EDC954FA /* Copy Pods Resources */, 174 | ); 175 | buildRules = ( 176 | ); 177 | dependencies = ( 178 | ABBF8A4C1BF20EA500DBB536 /* PBXTargetDependency */, 179 | ); 180 | name = SMHTTPClientTests; 181 | productName = SMHTTPClientTests; 182 | productReference = ABBF8A491BF20EA500DBB536 /* SMHTTPClientTests.xctest */; 183 | productType = "com.apple.product-type.bundle.unit-test"; 184 | }; 185 | /* End PBXNativeTarget section */ 186 | 187 | /* Begin PBXProject section */ 188 | ABBF8A361BF20EA500DBB536 /* Project object */ = { 189 | isa = PBXProject; 190 | attributes = { 191 | LastSwiftUpdateCheck = 0710; 192 | LastUpgradeCheck = 0710; 193 | ORGANIZATIONNAME = "Soutaro Matsumoto"; 194 | TargetAttributes = { 195 | ABBF8A3E1BF20EA500DBB536 = { 196 | CreatedOnToolsVersion = 7.1; 197 | }; 198 | ABBF8A481BF20EA500DBB536 = { 199 | CreatedOnToolsVersion = 7.1; 200 | }; 201 | }; 202 | }; 203 | buildConfigurationList = ABBF8A391BF20EA500DBB536 /* Build configuration list for PBXProject "SMHTTPClient" */; 204 | compatibilityVersion = "Xcode 3.2"; 205 | developmentRegion = English; 206 | hasScannedForEncodings = 0; 207 | knownRegions = ( 208 | en, 209 | ); 210 | mainGroup = ABBF8A351BF20EA500DBB536; 211 | productRefGroup = ABBF8A401BF20EA500DBB536 /* Products */; 212 | projectDirPath = ""; 213 | projectRoot = ""; 214 | targets = ( 215 | ABBF8A3E1BF20EA500DBB536 /* SMHTTPClient */, 216 | ABBF8A481BF20EA500DBB536 /* SMHTTPClientTests */, 217 | ); 218 | }; 219 | /* End PBXProject section */ 220 | 221 | /* Begin PBXResourcesBuildPhase section */ 222 | ABBF8A3D1BF20EA500DBB536 /* Resources */ = { 223 | isa = PBXResourcesBuildPhase; 224 | buildActionMask = 2147483647; 225 | files = ( 226 | ); 227 | runOnlyForDeploymentPostprocessing = 0; 228 | }; 229 | ABBF8A471BF20EA500DBB536 /* Resources */ = { 230 | isa = PBXResourcesBuildPhase; 231 | buildActionMask = 2147483647; 232 | files = ( 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | /* End PBXResourcesBuildPhase section */ 237 | 238 | /* Begin PBXShellScriptBuildPhase section */ 239 | 051E0450DA83A695362A3228 /* Embed Pods Frameworks */ = { 240 | isa = PBXShellScriptBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | ); 244 | inputPaths = ( 245 | ); 246 | name = "Embed Pods Frameworks"; 247 | outputPaths = ( 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | shellPath = /bin/sh; 251 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SMHTTPClientTests/Pods-SMHTTPClientTests-frameworks.sh\"\n"; 252 | showEnvVarsInLog = 0; 253 | }; 254 | 2C6051AE6C5E498DC0B9BEE9 /* Check Pods Manifest.lock */ = { 255 | isa = PBXShellScriptBuildPhase; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | ); 259 | inputPaths = ( 260 | ); 261 | name = "Check Pods Manifest.lock"; 262 | outputPaths = ( 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | shellPath = /bin/sh; 266 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 267 | showEnvVarsInLog = 0; 268 | }; 269 | 657B33EB168E1491EDC954FA /* Copy Pods Resources */ = { 270 | isa = PBXShellScriptBuildPhase; 271 | buildActionMask = 2147483647; 272 | files = ( 273 | ); 274 | inputPaths = ( 275 | ); 276 | name = "Copy Pods Resources"; 277 | outputPaths = ( 278 | ); 279 | runOnlyForDeploymentPostprocessing = 0; 280 | shellPath = /bin/sh; 281 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SMHTTPClientTests/Pods-SMHTTPClientTests-resources.sh\"\n"; 282 | showEnvVarsInLog = 0; 283 | }; 284 | /* End PBXShellScriptBuildPhase section */ 285 | 286 | /* Begin PBXSourcesBuildPhase section */ 287 | ABBF8A3A1BF20EA500DBB536 /* Sources */ = { 288 | isa = PBXSourcesBuildPhase; 289 | buildActionMask = 2147483647; 290 | files = ( 291 | AB93849F1BF20F2400A372AF /* NameResolver.swift in Sources */, 292 | AB93849D1BF20F2400A372AF /* Error.swift in Sources */, 293 | AB93849C1BF20F2400A372AF /* Buffer.swift in Sources */, 294 | AB93849E1BF20F2400A372AF /* HttpRequest.swift in Sources */, 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | ABBF8A451BF20EA500DBB536 /* Sources */ = { 299 | isa = PBXSourcesBuildPhase; 300 | buildActionMask = 2147483647; 301 | files = ( 302 | AB9384A31BF20F7E00A372AF /* NameResolverTests.swift in Sources */, 303 | AB9384A21BF20F7E00A372AF /* HttpRequestTests.swift in Sources */, 304 | ); 305 | runOnlyForDeploymentPostprocessing = 0; 306 | }; 307 | /* End PBXSourcesBuildPhase section */ 308 | 309 | /* Begin PBXTargetDependency section */ 310 | ABBF8A4C1BF20EA500DBB536 /* PBXTargetDependency */ = { 311 | isa = PBXTargetDependency; 312 | target = ABBF8A3E1BF20EA500DBB536 /* SMHTTPClient */; 313 | targetProxy = ABBF8A4B1BF20EA500DBB536 /* PBXContainerItemProxy */; 314 | }; 315 | /* End PBXTargetDependency section */ 316 | 317 | /* Begin XCBuildConfiguration section */ 318 | ABBF8A511BF20EA500DBB536 /* Debug */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | ALWAYS_SEARCH_USER_PATHS = NO; 322 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 323 | CLANG_CXX_LIBRARY = "libc++"; 324 | CLANG_ENABLE_MODULES = YES; 325 | CLANG_ENABLE_OBJC_ARC = YES; 326 | CLANG_WARN_BOOL_CONVERSION = YES; 327 | CLANG_WARN_CONSTANT_CONVERSION = YES; 328 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 329 | CLANG_WARN_EMPTY_BODY = YES; 330 | CLANG_WARN_ENUM_CONVERSION = YES; 331 | CLANG_WARN_INT_CONVERSION = YES; 332 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 333 | CLANG_WARN_UNREACHABLE_CODE = YES; 334 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 335 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 336 | COPY_PHASE_STRIP = NO; 337 | CURRENT_PROJECT_VERSION = 1; 338 | DEBUG_INFORMATION_FORMAT = dwarf; 339 | ENABLE_STRICT_OBJC_MSGSEND = YES; 340 | ENABLE_TESTABILITY = YES; 341 | GCC_C_LANGUAGE_STANDARD = gnu99; 342 | GCC_DYNAMIC_NO_PIC = NO; 343 | GCC_NO_COMMON_BLOCKS = YES; 344 | GCC_OPTIMIZATION_LEVEL = 0; 345 | GCC_PREPROCESSOR_DEFINITIONS = ( 346 | "DEBUG=1", 347 | "$(inherited)", 348 | ); 349 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 350 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 351 | GCC_WARN_UNDECLARED_SELECTOR = YES; 352 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 353 | GCC_WARN_UNUSED_FUNCTION = YES; 354 | GCC_WARN_UNUSED_VARIABLE = YES; 355 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 356 | MTL_ENABLE_DEBUG_INFO = YES; 357 | ONLY_ACTIVE_ARCH = YES; 358 | SDKROOT = iphoneos; 359 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 360 | TARGETED_DEVICE_FAMILY = "1,2"; 361 | VERSIONING_SYSTEM = "apple-generic"; 362 | VERSION_INFO_PREFIX = ""; 363 | }; 364 | name = Debug; 365 | }; 366 | ABBF8A521BF20EA500DBB536 /* Release */ = { 367 | isa = XCBuildConfiguration; 368 | buildSettings = { 369 | ALWAYS_SEARCH_USER_PATHS = NO; 370 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 371 | CLANG_CXX_LIBRARY = "libc++"; 372 | CLANG_ENABLE_MODULES = YES; 373 | CLANG_ENABLE_OBJC_ARC = YES; 374 | CLANG_WARN_BOOL_CONVERSION = YES; 375 | CLANG_WARN_CONSTANT_CONVERSION = YES; 376 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 377 | CLANG_WARN_EMPTY_BODY = YES; 378 | CLANG_WARN_ENUM_CONVERSION = YES; 379 | CLANG_WARN_INT_CONVERSION = YES; 380 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 381 | CLANG_WARN_UNREACHABLE_CODE = YES; 382 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 383 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 384 | COPY_PHASE_STRIP = NO; 385 | CURRENT_PROJECT_VERSION = 1; 386 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 387 | ENABLE_NS_ASSERTIONS = NO; 388 | ENABLE_STRICT_OBJC_MSGSEND = YES; 389 | GCC_C_LANGUAGE_STANDARD = gnu99; 390 | GCC_NO_COMMON_BLOCKS = YES; 391 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 392 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 393 | GCC_WARN_UNDECLARED_SELECTOR = YES; 394 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 395 | GCC_WARN_UNUSED_FUNCTION = YES; 396 | GCC_WARN_UNUSED_VARIABLE = YES; 397 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 398 | MTL_ENABLE_DEBUG_INFO = NO; 399 | SDKROOT = iphoneos; 400 | TARGETED_DEVICE_FAMILY = "1,2"; 401 | VALIDATE_PRODUCT = YES; 402 | VERSIONING_SYSTEM = "apple-generic"; 403 | VERSION_INFO_PREFIX = ""; 404 | }; 405 | name = Release; 406 | }; 407 | ABBF8A541BF20EA500DBB536 /* Debug */ = { 408 | isa = XCBuildConfiguration; 409 | buildSettings = { 410 | CLANG_ENABLE_MODULES = YES; 411 | DEFINES_MODULE = YES; 412 | DYLIB_COMPATIBILITY_VERSION = 1; 413 | DYLIB_CURRENT_VERSION = 1; 414 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 415 | INFOPLIST_FILE = SMHTTPClient/Info.plist; 416 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 417 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 418 | PRODUCT_BUNDLE_IDENTIFIER = com.soutaro.SMHTTPClient; 419 | PRODUCT_NAME = "$(TARGET_NAME)"; 420 | SKIP_INSTALL = YES; 421 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 422 | }; 423 | name = Debug; 424 | }; 425 | ABBF8A551BF20EA500DBB536 /* Release */ = { 426 | isa = XCBuildConfiguration; 427 | buildSettings = { 428 | CLANG_ENABLE_MODULES = YES; 429 | DEFINES_MODULE = YES; 430 | DYLIB_COMPATIBILITY_VERSION = 1; 431 | DYLIB_CURRENT_VERSION = 1; 432 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 433 | INFOPLIST_FILE = SMHTTPClient/Info.plist; 434 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 435 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 436 | PRODUCT_BUNDLE_IDENTIFIER = com.soutaro.SMHTTPClient; 437 | PRODUCT_NAME = "$(TARGET_NAME)"; 438 | SKIP_INSTALL = YES; 439 | }; 440 | name = Release; 441 | }; 442 | ABBF8A571BF20EA500DBB536 /* Debug */ = { 443 | isa = XCBuildConfiguration; 444 | baseConfigurationReference = E0DA648C70A400982A6E6E48 /* Pods-SMHTTPClientTests.debug.xcconfig */; 445 | buildSettings = { 446 | CLANG_ENABLE_MODULES = YES; 447 | INFOPLIST_FILE = SMHTTPClientTests/Info.plist; 448 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 449 | PRODUCT_BUNDLE_IDENTIFIER = com.soutaro.SMHTTPClientTests; 450 | PRODUCT_NAME = "$(TARGET_NAME)"; 451 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 452 | }; 453 | name = Debug; 454 | }; 455 | ABBF8A581BF20EA500DBB536 /* Release */ = { 456 | isa = XCBuildConfiguration; 457 | baseConfigurationReference = AEC4CA233AF26BB577137A94 /* Pods-SMHTTPClientTests.release.xcconfig */; 458 | buildSettings = { 459 | CLANG_ENABLE_MODULES = YES; 460 | INFOPLIST_FILE = SMHTTPClientTests/Info.plist; 461 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 462 | PRODUCT_BUNDLE_IDENTIFIER = com.soutaro.SMHTTPClientTests; 463 | PRODUCT_NAME = "$(TARGET_NAME)"; 464 | }; 465 | name = Release; 466 | }; 467 | /* End XCBuildConfiguration section */ 468 | 469 | /* Begin XCConfigurationList section */ 470 | ABBF8A391BF20EA500DBB536 /* Build configuration list for PBXProject "SMHTTPClient" */ = { 471 | isa = XCConfigurationList; 472 | buildConfigurations = ( 473 | ABBF8A511BF20EA500DBB536 /* Debug */, 474 | ABBF8A521BF20EA500DBB536 /* Release */, 475 | ); 476 | defaultConfigurationIsVisible = 0; 477 | defaultConfigurationName = Release; 478 | }; 479 | ABBF8A531BF20EA500DBB536 /* Build configuration list for PBXNativeTarget "SMHTTPClient" */ = { 480 | isa = XCConfigurationList; 481 | buildConfigurations = ( 482 | ABBF8A541BF20EA500DBB536 /* Debug */, 483 | ABBF8A551BF20EA500DBB536 /* Release */, 484 | ); 485 | defaultConfigurationIsVisible = 0; 486 | defaultConfigurationName = Release; 487 | }; 488 | ABBF8A561BF20EA500DBB536 /* Build configuration list for PBXNativeTarget "SMHTTPClientTests" */ = { 489 | isa = XCConfigurationList; 490 | buildConfigurations = ( 491 | ABBF8A571BF20EA500DBB536 /* Debug */, 492 | ABBF8A581BF20EA500DBB536 /* Release */, 493 | ); 494 | defaultConfigurationIsVisible = 0; 495 | defaultConfigurationName = Release; 496 | }; 497 | /* End XCConfigurationList section */ 498 | }; 499 | rootObject = ABBF8A361BF20EA500DBB536 /* Project object */; 500 | } 501 | -------------------------------------------------------------------------------- /SMHTTPClient.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SMHTTPClient.xcodeproj/xcshareddata/xcschemes/SMHTTPClient.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /SMHTTPClient/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soutaro/SMHTTPClient/0836c0a0a514cce92e5e86677b2f026ae92cc8d4/SMHTTPClient/Classes/.gitkeep -------------------------------------------------------------------------------- /SMHTTPClient/Classes/Buffer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class Buffer { 4 | private var data: NSMutableData 5 | private var offset: Int 6 | private let filler: (UnsafeMutablePointer, Int) throws -> Int 7 | 8 | init(filler: (UnsafeMutablePointer, Int) throws -> Int) { 9 | self.data = NSMutableData() 10 | self.offset = 0 11 | self.filler = filler 12 | } 13 | 14 | func fillBuffer(expectedSize: Int) throws { 15 | let buf = UnsafeMutablePointer.alloc(expectedSize) 16 | let actualSize = try self.filler(buf, expectedSize) 17 | self.data = NSMutableData(bytes: buf, length: actualSize) 18 | self.offset = 0 19 | } 20 | 21 | private func readCharacter() throws -> Character { 22 | if self.offset == self.data.length { 23 | try self.fillBuffer(4096) 24 | } 25 | 26 | var c = [UInt8](count: 1, repeatedValue: 0) 27 | self.data.getBytes(&c, range: NSMakeRange(self.offset, 1)) 28 | self.offset += 1 29 | 30 | return Character(UnicodeScalar(c[0])) 31 | } 32 | 33 | func readLine() throws -> String { 34 | var line = "" 35 | var lastChar: Character = "\0" 36 | while true { 37 | let char = try self.readCharacter() 38 | if lastChar == "\r" && char == "\n" { 39 | return line 40 | } 41 | 42 | if char != "\r" && char != "\n" { 43 | line.append(char) 44 | } 45 | 46 | lastChar = char 47 | } 48 | } 49 | 50 | func readData(expectedSize: Int) throws -> NSData { 51 | let data = NSMutableData() 52 | 53 | var readSize = 0 54 | 55 | while readSize < expectedSize { 56 | if self.offset == self.data.length { 57 | try self.fillBuffer(4096) 58 | } 59 | 60 | var size = expectedSize - readSize 61 | if size > self.data.length - self.offset { 62 | size = self.data.length - self.offset 63 | } 64 | 65 | let buf = UnsafeMutablePointer.alloc(size) 66 | self.data.getBytes(buf, range: NSMakeRange(self.offset, size)) 67 | 68 | data.appendBytes(buf, length: size) 69 | readSize += size 70 | self.offset += size 71 | } 72 | 73 | return data 74 | } 75 | } -------------------------------------------------------------------------------- /SMHTTPClient/Classes/Error.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public let SMHTTPClientErrorDomain = "SMHTTPClientErrorDomain" 4 | public enum SMHTTPClientErrorCode: Int { 5 | case MulformedHTTPResponse 6 | case NameResolutionFailure 7 | case DoubleRun 8 | } 9 | -------------------------------------------------------------------------------- /SMHTTPClient/Classes/HttpRequest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum HttpMethod { 4 | case GET; 5 | case HEAD; 6 | case POST(NSData); 7 | case PUT(NSData); 8 | case PATCH(NSData); 9 | case DELETE; 10 | } 11 | 12 | public enum HttpRequestStatus: Equatable { 13 | case Initialized; 14 | case Connecting; 15 | case Connected; 16 | case RequestSent; 17 | case Completed(Int, [(String, String)], NSData); 18 | case Error(NSError); 19 | case Aborted; 20 | } 21 | 22 | internal func ==(a: [(String, String)], b: [(String, String)]) -> Bool { 23 | if a.count != b.count { 24 | return false 25 | } 26 | 27 | return zip(a, b).reduce(true, combine: { (pred, z) in 28 | let ((x1, y1), (x2, y2)) = z 29 | return pred && x1 == x2 && y1 == y2 30 | }) 31 | } 32 | 33 | public func ==(a: HttpRequestStatus, b: HttpRequestStatus) -> Bool { 34 | switch (a, b) { 35 | case (.Initialized, .Initialized): return true 36 | case (.Connecting, .Connecting): return true 37 | case (.Connected, .Connected): return true 38 | case (.RequestSent, .RequestSent): return true 39 | case (.Completed(let c1, let h1, let d1), .Completed(let c2, let h2, let d2)) where c1 == c2 && h1 == h2 && d1 == d2: return true 40 | case (.Error(let e1), .Error(let e2)) where e1 == e2: return true 41 | case (.Aborted, .Aborted): return true 42 | default: return false 43 | } 44 | } 45 | 46 | private enum HttpRequestError: ErrorType { 47 | case Error(NSError) 48 | case Aborted 49 | case MulformedResponse(String) 50 | } 51 | 52 | public class HttpRequest { 53 | public let address: sockaddr 54 | public let path: String 55 | public let method: HttpMethod 56 | public let requestHeader: [(String, String)] 57 | 58 | private var _socket: Int32 59 | private var _status: HttpRequestStatus 60 | 61 | private let _queue: dispatch_queue_t 62 | private let _semaphore: dispatch_semaphore_t 63 | 64 | public init(address: sockaddr, path: String, method: HttpMethod, header: [(String, String)]) { 65 | self.address = address 66 | self.path = path 67 | self.method = method 68 | self.requestHeader = header 69 | self._status = .Initialized 70 | 71 | self._queue = dispatch_queue_create("com.soutaro.SMHTTPClient.HttpRequest.queue", nil) 72 | self._semaphore = dispatch_semaphore_create(0) 73 | 74 | self._socket = 0 75 | } 76 | 77 | deinit { 78 | if self._socket != 0 { 79 | Darwin.close(self._socket) 80 | } 81 | } 82 | 83 | public var status: HttpRequestStatus { 84 | get { 85 | var status: HttpRequestStatus = .Initialized 86 | dispatch_sync(self._queue) { 87 | status = self._status 88 | } 89 | return status 90 | } 91 | } 92 | 93 | public func run() { 94 | if self.status != .Initialized { 95 | dispatch_sync(self._queue) { 96 | self._status = .Error(NSError(domain: SMHTTPClientErrorDomain, code: SMHTTPClientErrorCode.DoubleRun.rawValue, userInfo: nil)) 97 | } 98 | return 99 | } 100 | 101 | self.setStatus(.Connecting); 102 | 103 | let q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 104 | dispatch_async(q) { 105 | do { 106 | defer { 107 | if self._socket != 0 { 108 | shutdown(self._socket, SHUT_RDWR) 109 | } 110 | } 111 | 112 | try self.connect() 113 | try self.send() 114 | try self.receive() 115 | } catch HttpRequestError.Error(let error) { 116 | self.setErrorStatus(error) 117 | } catch HttpRequestError.MulformedResponse(let message) { 118 | self.setErrorStatus(NSError(domain: SMHTTPClientErrorDomain, code: SMHTTPClientErrorCode.MulformedHTTPResponse.rawValue, userInfo: [NSLocalizedDescriptionKey: message])) 119 | } catch HttpRequestError.Aborted { 120 | // Nothing to do 121 | } catch _ { 122 | // Nothing to do 123 | } 124 | 125 | dispatch_semaphore_signal(self._semaphore); 126 | } 127 | 128 | dispatch_semaphore_wait(self._semaphore, DISPATCH_TIME_FOREVER); 129 | } 130 | 131 | public func abort() { 132 | dispatch_sync(self._queue) { 133 | switch self._status { 134 | case .Aborted, .Error(_), .Completed(_): 135 | break 136 | default: 137 | self._status = .Aborted 138 | if self._socket != 0 { 139 | shutdown(self._socket, SHUT_RDWR) 140 | } 141 | } 142 | } 143 | 144 | dispatch_semaphore_signal(self._semaphore) 145 | } 146 | 147 | private func connect() throws { 148 | let sock = socket(Int32(self.address.sa_family), SOCK_STREAM, 0) 149 | try self.abortIfAborted() 150 | 151 | if sock != -1 { 152 | self._socket = sock 153 | } else { 154 | throw HttpRequestError.Error(NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)) 155 | } 156 | 157 | var one: Int32 = 1; 158 | setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &one, UInt32(sizeof(Int32))) 159 | 160 | var address = self.address 161 | let ret = withUnsafePointer(&address) { ptr in 162 | Darwin.connect(self._socket, ptr, UInt32(ptr.memory.sa_len)) 163 | } 164 | 165 | try self.abortIfAborted() 166 | 167 | if ret == 0 { 168 | self.setStatus(.Connected) 169 | } else { 170 | throw HttpRequestError.Error(NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)) 171 | } 172 | } 173 | 174 | private func send() throws { 175 | let data = NSMutableData() 176 | 177 | let requestLine: String 178 | 179 | switch self.method { 180 | case .GET: 181 | requestLine = "GET \(self.path) HTTP/1.1\r\n" 182 | case .DELETE: 183 | requestLine = "DELETE \(self.path) HTTP/1.1\r\n" 184 | case .POST(_): 185 | requestLine = "POST \(self.path) HTTP/1.1\r\n" 186 | case .PUT(_): 187 | requestLine = "PUT \(self.path) HTTP/1.1\r\n" 188 | case .PATCH(_): 189 | requestLine = "PATCH \(self.path) HTTP/1.1\r\n" 190 | case .HEAD: 191 | requestLine = "HEAD \(self.path) HTTP/1.1\r\n" 192 | } 193 | 194 | data.appendData(requestLine.dataUsingEncoding(NSUTF8StringEncoding)!) 195 | 196 | for (name, value) in self.requestHeader { 197 | let headerLine = "\(name): \(value)\r\n" 198 | data.appendData(headerLine.dataUsingEncoding(NSUTF8StringEncoding)!) 199 | } 200 | 201 | data.appendData("\r\n".dataUsingEncoding(NSUTF8StringEncoding)!) 202 | 203 | switch self.method { 204 | case .PATCH(let body): 205 | data.appendData(body) 206 | case .PUT(let body): 207 | data.appendData(body) 208 | case .POST(let body): 209 | data.appendData(body) 210 | default: break 211 | } 212 | 213 | var offset = 0 214 | while offset < data.length { 215 | var size = 4096 216 | if offset + size > data.length { 217 | size = data.length - offset 218 | } 219 | let buf = UnsafeMutablePointer.alloc(size) 220 | data.getBytes(buf, range: NSMakeRange(offset, size)) 221 | let ret = Darwin.send(self._socket, buf, size, 0) 222 | 223 | if ret >= 0 { 224 | offset = offset + ret 225 | } else { 226 | throw HttpRequestError.Error(NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)) 227 | } 228 | 229 | try self.abortIfAborted() 230 | } 231 | 232 | self.setStatus(.RequestSent) 233 | } 234 | 235 | private func receive() throws { 236 | let buffer = Buffer() { (buf: UnsafeMutablePointer, size: Int) throws -> Int in 237 | let read = recv(self._socket, buf, size, 0) 238 | try self.abortIfAborted() 239 | if read == 0 { 240 | throw HttpRequestError.MulformedResponse("recv returned 0 bytes read, through not aborted yet...") 241 | } 242 | if read < 0 { 243 | throw HttpRequestError.Error(NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)) 244 | } 245 | return read 246 | } 247 | 248 | let status = try self.readStatusLine(buffer) 249 | let header = try self.readResponseHeader(buffer) 250 | let body = self.hasBody(status) ? try self.readBody(header, buffer: buffer) : NSData() 251 | 252 | self.setStatus(.Completed(status, header, body)) 253 | } 254 | 255 | private func readStatusLine(buffer: Buffer) throws -> Int { 256 | let line = try buffer.readLine() 257 | 258 | if !line.hasPrefix("HTTP/1.1 ") { 259 | throw HttpRequestError.MulformedResponse("The response does not look like a HTTP/1.1 response") 260 | } 261 | 262 | let code = (line as NSString).substringWithRange(NSRange(location: 9, length: 3)) 263 | return Int(code)! 264 | } 265 | 266 | private func readResponseHeader(buffer: Buffer) throws -> [(String, String)] { 267 | var header: [(String, String)] = [] 268 | 269 | while true { 270 | let line = try buffer.readLine() 271 | if line == "" { 272 | return header 273 | } 274 | 275 | if let position = line.characters.indexOf(":") { 276 | let name = line.substringToIndex(position) 277 | let value = line.substringFromIndex(position.advancedBy(1)) 278 | let pair = ( 279 | name, 280 | value.stringByTrimmingCharactersInSet(NSCharacterSet(charactersInString: " ")) 281 | ) 282 | header.append(pair) 283 | } else { 284 | throw HttpRequestError.MulformedResponse("Header line looks mulformed... (\(line))") 285 | } 286 | 287 | } 288 | } 289 | 290 | private func readBody(header: [(String, String)], buffer: Buffer) throws -> NSData { 291 | let transferEncoding = self.findHeaderValue(header, name: "Transfer-Encoding", defaultEncoding: "identity") 292 | 293 | if transferEncoding.componentsSeparatedByString(" ").contains("chunked") { 294 | let result = NSMutableData() 295 | while true { 296 | let chunk = try readNextChunk(buffer) 297 | if chunk.length > 0 { 298 | result.appendData(chunk) 299 | } else { 300 | return result 301 | } 302 | } 303 | } else { 304 | let contentLength = Int(self.findHeaderValue(header, name: "Content-Length", defaultEncoding: "0"))! 305 | return try buffer.readData(contentLength) 306 | } 307 | } 308 | 309 | private func hasBody(statusCode: Int) -> Bool { 310 | if 100..<200 ~= statusCode { 311 | // Informational 312 | return false 313 | } 314 | 315 | if statusCode == 204 { 316 | // No content 317 | return false 318 | } 319 | 320 | if statusCode == 304 { 321 | // Not modified 322 | return false 323 | } 324 | 325 | switch self.method { 326 | case .HEAD: return false 327 | default: return true 328 | } 329 | } 330 | 331 | private func readNextChunk(buffer: Buffer) throws -> NSData { 332 | let line = try buffer.readLine() 333 | let scanner = NSScanner(string: line) 334 | var size: UInt32 = 0 335 | scanner.scanHexInt(&size) 336 | let data = try buffer.readData(Int(size)) 337 | try buffer.readLine() 338 | return data; 339 | } 340 | 341 | private func findHeaderValue(header: [(String, String)], name: String, defaultEncoding: String) -> String { 342 | for (k, v) in header { 343 | if k.uppercaseString == name.uppercaseString { 344 | return v 345 | } 346 | } 347 | 348 | return defaultEncoding 349 | } 350 | 351 | private func abortIfAborted() throws { 352 | var aborted = false 353 | 354 | dispatch_sync(self._queue) { 355 | aborted = self._status == .Aborted 356 | } 357 | 358 | if aborted { 359 | throw HttpRequestError.Aborted 360 | } 361 | } 362 | 363 | private func setErrorStatus(error: NSError) { 364 | switch self._status { 365 | case .Aborted, .Error(_): return 366 | default: break 367 | } 368 | 369 | dispatch_sync(self._queue) { 370 | switch self._status { 371 | case .Aborted, .Error(_): 372 | return 373 | default: 374 | self._status = .Error(error) 375 | } 376 | } 377 | } 378 | 379 | private func setStatus(status: HttpRequestStatus) { 380 | dispatch_sync(self._queue) { 381 | switch self._status { 382 | case .Aborted, .Error(_): 383 | return 384 | default: 385 | self._status = status 386 | } 387 | } 388 | } 389 | } -------------------------------------------------------------------------------- /SMHTTPClient/Classes/NameResolver.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum NameResolverState: Equatable { 4 | case Initialized; 5 | case Running; 6 | case Resolved; 7 | case Error(NSError); 8 | case Aborted; 9 | } 10 | 11 | public func ==(a: NameResolverState, b: NameResolverState) -> Bool { 12 | switch (a, b) { 13 | case (.Initialized, .Initialized): return true; 14 | case (.Running, .Running): return true; 15 | case (.Resolved, .Resolved): return true; 16 | case (.Error(let e), .Error(let f)) where e == f: return true; 17 | case (.Aborted, .Aborted): return true; 18 | default: return false; 19 | } 20 | } 21 | 22 | public class NameResolver { 23 | private var _results: [sockaddr]; 24 | private var _status: NameResolverState; 25 | private let _mutex: dispatch_queue_t; 26 | private let _semaphore: dispatch_semaphore_t; 27 | 28 | let hostname: String; 29 | let port: UInt; 30 | 31 | public init(hostname: String, port: UInt) { 32 | self._results = []; 33 | self._status = .Initialized; 34 | 35 | self.hostname = hostname; 36 | self.port = port; 37 | 38 | self._mutex = dispatch_queue_create("com.soutaro.SMHTTPClient.NameResolver.mutex", nil); 39 | self._semaphore = dispatch_semaphore_create(0); 40 | } 41 | 42 | public func run() { 43 | dispatch_sync(self._mutex) { 44 | self._status = .Running; 45 | } 46 | 47 | let resolveQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 48 | 49 | dispatch_async(resolveQueue) { 50 | let result = UnsafeMutablePointer>.alloc(1) 51 | 52 | var hints: addrinfo = addrinfo(ai_flags: 0, ai_family: PF_UNSPEC, ai_socktype: SOCK_STREAM, ai_protocol: 0, ai_addrlen: 0, ai_canonname: UnsafeMutablePointer(), ai_addr: UnsafeMutablePointer(), ai_next: UnsafeMutablePointer()); 53 | let ret = withUnsafePointer(&hints) { hintsPtr in 54 | self.hostname.withCString() { namePtr in 55 | String(self.port).withCString() { portPtr in 56 | getaddrinfo(namePtr, portPtr, hintsPtr, result) 57 | } 58 | } 59 | } 60 | 61 | dispatch_sync(self._mutex) { 62 | if self._status == .Aborted { 63 | return; 64 | } 65 | 66 | if ret == 0 { 67 | // success 68 | var addrs: [sockaddr] = [] 69 | 70 | var a: UnsafeMutablePointer = result.memory 71 | while a != UnsafeMutablePointer() { 72 | addrs.append(a.memory.ai_addr.memory) 73 | a = a.memory.ai_next 74 | } 75 | 76 | self._results = addrs 77 | self._status = .Resolved; 78 | } else { 79 | // error 80 | let message = String.fromCString(gai_strerror(ret)) 81 | let nserror = NSError( 82 | domain: SMHTTPClientErrorDomain, 83 | code: SMHTTPClientErrorCode.NameResolutionFailure.rawValue, 84 | userInfo: [NSLocalizedDescriptionKey: message!] 85 | ); 86 | self._status = .Error(nserror); 87 | } 88 | 89 | dispatch_semaphore_signal(self._semaphore); 90 | } 91 | } 92 | 93 | dispatch_semaphore_wait(self._semaphore, DISPATCH_TIME_FOREVER); 94 | } 95 | 96 | public func abort() { 97 | dispatch_sync(self._mutex) { 98 | if self._status == .Running || self._status == .Initialized { 99 | self._status = .Aborted; 100 | self._results.removeAll(); 101 | dispatch_semaphore_signal(self._semaphore); 102 | } 103 | } 104 | } 105 | 106 | public var results: [sockaddr] { 107 | get { 108 | return self._results; 109 | } 110 | } 111 | 112 | public var IPv4Results: [sockaddr] { 113 | get { 114 | return self._results.filter { Int32($0.sa_family) == PF_INET } 115 | } 116 | } 117 | 118 | public var IPv6Results: [sockaddr] { 119 | get { 120 | return self._results.filter { Int32($0.sa_family) == PF_INET6 } 121 | } 122 | } 123 | 124 | public var status: NameResolverState { 125 | get { 126 | return self._status; 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /SMHTTPClient/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 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SMHTTPClientTests/HttpRequestTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Quick 3 | import Nimble 4 | import GCDWebServer 5 | @testable import SMHTTPClient 6 | 7 | func requestSuccessfullyCompleted(request: HttpRequest) -> Bool { 8 | switch request.status { 9 | case .Completed(_): return true 10 | default: return false 11 | } 12 | } 13 | 14 | func requestHasError(request: HttpRequest) -> Bool { 15 | switch request.status { 16 | case .Error(_): return true 17 | default: return false 18 | } 19 | } 20 | 21 | func requestIsAborted(request: HttpRequest) -> Bool { 22 | return request.status == .Aborted 23 | } 24 | 25 | func valueOfHeader(header: [(String, String)], name: String) -> String? { 26 | let entry = header.filter() { (e: (String, String)) -> Bool in 27 | let (key, _) = e 28 | return key.uppercaseString == name.uppercaseString 29 | }.first 30 | 31 | if let (_, v) = entry { 32 | return v 33 | } else { 34 | return nil 35 | } 36 | } 37 | 38 | class HttpRequestTests: QuickSpec { 39 | override func spec() { 40 | var address: sockaddr? 41 | var server: GCDWebServer? 42 | 43 | beforeEach { 44 | let resolver = NameResolver(hostname: "localhost", port: 8080) 45 | resolver.run() 46 | address = resolver.IPv4Results.first 47 | 48 | server = GCDWebServer() 49 | } 50 | 51 | afterEach { 52 | address = nil 53 | 54 | if server!.running { 55 | server!.stop() 56 | } 57 | } 58 | 59 | describe("HttpRequest#run") { 60 | it("fails if called twice") { 61 | server!.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: { (request: GCDWebServerRequest!) -> GCDWebServerResponse! in 62 | GCDWebServerResponse(statusCode: 200) 63 | }) 64 | server!.startWithPort(8080, bonjourName: nil) 65 | 66 | let request = HttpRequest(address: address!, path: "/", method: .GET, header: []) 67 | 68 | request.run() 69 | expect(requestSuccessfullyCompleted(request)).to(beTrue()) 70 | 71 | request.run() 72 | expect(requestHasError(request)).to(beTrue()) 73 | } 74 | } 75 | 76 | describe("HttpRequest#connect") { 77 | describe("establishing connection") { 78 | it("connects to server") { 79 | server!.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: { (request: GCDWebServerRequest!) -> GCDWebServerResponse! in 80 | GCDWebServerResponse(statusCode: 200) 81 | }) 82 | server!.startWithPort(8080, bonjourName: nil) 83 | 84 | let request = HttpRequest(address: address!, path: "/", method: .GET, header: []) 85 | request.run() 86 | 87 | expect(requestHasError(request)).to(equal(false)) 88 | expect(requestIsAborted(request)).to(equal(false)) 89 | } 90 | 91 | it("sets error if connection failed") { 92 | let request = HttpRequest(address: address!, path: "/", method: .GET, header: []) 93 | request.run() 94 | 95 | expect(request.status).to(equal(HttpRequestStatus.Error(NSError(domain: NSPOSIXErrorDomain, code: 61, userInfo: [:])))) 96 | } 97 | } 98 | 99 | describe("Sending request") { 100 | it("sends path") { 101 | var requestedPath: String = "" 102 | server!.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: { (request: GCDWebServerRequest!) -> GCDWebServerResponse! in 103 | requestedPath = request.path 104 | return GCDWebServerResponse(statusCode: 200) 105 | }) 106 | server!.startWithPort(8080, bonjourName: nil) 107 | 108 | let request = HttpRequest(address: address!, path: "/test123", method: .GET, header: [("Connection", "close")]) 109 | request.run() 110 | 111 | expect(requestSuccessfullyCompleted(request)).to(beTrue()) 112 | expect(requestedPath).to(equal("/test123")) 113 | } 114 | 115 | it("sends header") { 116 | var requestHeader: [NSObject: AnyObject] = [:] 117 | server!.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: { (request: GCDWebServerRequest!) -> GCDWebServerResponse! in 118 | requestHeader = request.headers 119 | return GCDWebServerResponse(statusCode: 200) 120 | }) 121 | server!.startWithPort(8080, bonjourName: nil) 122 | 123 | let request = HttpRequest(address: address!, path: "/test123", method: .GET, header: [("Connection", "close"), ("Host", "localhost")]) 124 | request.run() 125 | 126 | expect(requestSuccessfullyCompleted(request)).to(beTrue()) 127 | expect(requestHeader["Host"] as? String).to(equal("localhost")) 128 | expect(requestHeader["Connection"] as? String).to(equal("close")) 129 | } 130 | 131 | it("sends body") { 132 | var requestJson: AnyObject = [] 133 | server!.addDefaultHandlerForMethod("POST", requestClass: GCDWebServerDataRequest.self, processBlock: { (request: GCDWebServerRequest!) -> GCDWebServerResponse! in 134 | let dataRequest: GCDWebServerDataRequest = request as! GCDWebServerDataRequest 135 | requestJson = dataRequest.jsonObject 136 | return GCDWebServerResponse(statusCode: 200) 137 | }) 138 | server!.startWithPort(8080, bonjourName: nil) 139 | 140 | let jsonData = try! NSJSONSerialization.dataWithJSONObject([1,2,3], options: NSJSONWritingOptions.PrettyPrinted) 141 | let request = HttpRequest(address: address!, path: "/test123", method: .POST(jsonData), header: [("Connection", "close"), ("Content-Type", "application/json"), ("Content-Length", String(jsonData.length))]) 142 | request.run() 143 | 144 | expect(requestSuccessfullyCompleted(request)).to(beTrue()) 145 | expect(requestJson as? [Int]).to(equal([1,2,3])) 146 | } 147 | } 148 | 149 | describe("receiving response") { 150 | it("receives status code") { 151 | server!.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: { (request: GCDWebServerRequest!) -> GCDWebServerResponse! in 152 | return GCDWebServerResponse(statusCode: 404) 153 | }) 154 | server!.startWithPort(8080, bonjourName: nil) 155 | 156 | let request = HttpRequest(address: address!, path: "/test123", method: .GET, header: [("Connection", "close"), ("Host", "localhost")]) 157 | request.run() 158 | 159 | expect(requestSuccessfullyCompleted(request)).to(beTrue()) 160 | 161 | switch request.status { 162 | case .Completed(let status, _, _): 163 | expect(status).to(equal(404)) 164 | default: 165 | expect(true).to(beFalse()) 166 | } 167 | } 168 | 169 | it("receives response header") { 170 | server!.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: { (request: GCDWebServerRequest!) -> GCDWebServerResponse! in 171 | return GCDWebServerResponse(statusCode: 200) 172 | }) 173 | server!.startWithPort(8080, bonjourName: nil) 174 | 175 | let request = HttpRequest(address: address!, path: "/test123", method: .GET, header: [("Connection", "close"), ("Host", "localhost")]) 176 | request.run() 177 | 178 | expect(requestSuccessfullyCompleted(request)).to(beTrue()) 179 | 180 | switch request.status { 181 | case .Completed(_, let h, _): 182 | expect(h.count).to(beGreaterThan(0)) 183 | expect(valueOfHeader(h, name: "Server")!).to(equal("GCDWebServer")) 184 | default: 185 | expect(true).to(beFalse()) 186 | } 187 | } 188 | 189 | it("receives identity body") { 190 | server!.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: { (request: GCDWebServerRequest!) -> GCDWebServerResponse! in 191 | let response = GCDWebServerDataResponse(text: "Hello World") 192 | return response 193 | }) 194 | server!.startWithPort(8080, bonjourName: nil) 195 | 196 | let request = HttpRequest(address: address!, path: "/test123", method: .GET, header: [("Connection", "close"), ("Host", "localhost")]) 197 | request.run() 198 | 199 | expect(requestSuccessfullyCompleted(request)).to(beTrue()) 200 | 201 | switch request.status { 202 | case .Completed(_, _, let body): 203 | let string = String(data: body, encoding: NSUTF8StringEncoding) 204 | expect(string).to(equal("Hello World")) 205 | default: 206 | expect(true).to(beFalse()) 207 | } 208 | } 209 | 210 | it("receives chunked body") { 211 | let resolver = NameResolver(hostname: "qiita.com", port: 80) 212 | resolver.run() 213 | let address = resolver.IPv4Results.first! 214 | 215 | let request = HttpRequest(address: address, path: "/ryotapoi/items/e674615a613061c08cae", method: .GET, header: [("Connection", "close"), ("Host", "qiita.com")]) 216 | request.run() 217 | 218 | expect(requestSuccessfullyCompleted(request)).to(beTrue()) 219 | 220 | switch request.status { 221 | case .Completed(_, _, let body): 222 | expect(body.length).to(beGreaterThan(0)) 223 | default: 224 | expect(true).to(beFalse()) 225 | } 226 | } 227 | 228 | it("receives empty body if the response does not have body") { 229 | server!.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: { (request: GCDWebServerRequest!) -> GCDWebServerResponse! in 230 | GCDWebServerDataResponse(text: "Hello World") 231 | }) 232 | server!.startWithPort(8080, bonjourName: nil) 233 | 234 | let request = HttpRequest(address: address!, path: "/test123", method: .HEAD, header: [("Connection", "close"), ("Host", "localhost")]) 235 | request.run() 236 | 237 | expect(requestSuccessfullyCompleted(request)).to(beTrue()) 238 | 239 | switch request.status { 240 | case .Completed(let code, _, let body): 241 | expect(code).to(equal(200)) 242 | expect(body.length).to(equal(0)) 243 | default: 244 | expect(true).to(beFalse()) 245 | } 246 | } 247 | } 248 | } 249 | 250 | describe("HttpRequest#abort") { 251 | it("stops running request") { 252 | server!.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: { (request: GCDWebServerRequest!) -> GCDWebServerResponse! in 253 | NSThread.sleepForTimeInterval(5) 254 | return GCDWebServerDataResponse(text: "Hello World") 255 | }) 256 | server!.startWithPort(8080, bonjourName: nil) 257 | 258 | let request = HttpRequest(address: address!, path: "/test123", method: .HEAD, header: [("Connection", "close"), ("Host", "localhost")]) 259 | 260 | let queue = dispatch_queue_create("com.soutaro.SMHttpRequestTests.test", nil) 261 | 262 | let delay = 0.5 * Double(NSEC_PER_SEC) 263 | let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay)) 264 | dispatch_after(time, queue, { 265 | expect(request.status).to(equal(HttpRequestStatus.RequestSent)); 266 | request.abort(); 267 | }) 268 | 269 | let start = NSDate() 270 | 271 | request.run() 272 | 273 | expect(requestIsAborted(request)).to(beTrue()) 274 | expect(NSDate().timeIntervalSinceDate(start)).to(beLessThan(1)) 275 | } 276 | 277 | it("does not update status if completed") { 278 | server!.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: { (request: GCDWebServerRequest!) -> GCDWebServerResponse! in 279 | return GCDWebServerDataResponse(text: "Hello World") 280 | }) 281 | server!.startWithPort(8080, bonjourName: nil) 282 | 283 | let request = HttpRequest(address: address!, path: "/test123", method: .HEAD, header: [("Connection", "close"), ("Host", "localhost")]) 284 | request.run() 285 | 286 | expect(requestSuccessfullyCompleted(request)).to(beTrue()) 287 | 288 | request.abort() 289 | 290 | expect(requestIsAborted(request)).notTo(beTrue()) 291 | } 292 | } 293 | } 294 | } -------------------------------------------------------------------------------- /SMHTTPClientTests/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 | -------------------------------------------------------------------------------- /SMHTTPClientTests/NameResolverTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Quick 3 | import Nimble 4 | @testable import SMHTTPClient 5 | 6 | func in_addr_from_sockaddr(address: sockaddr, closure: (UnsafePointer) -> T) -> T { 7 | var a = address 8 | var result: T? = nil 9 | 10 | if Int32(a.sa_family) == PF_INET { 11 | withUnsafePointer(&a) { sockaddr_ptr in 12 | let sockaddr_in_ptr = unsafeBitCast(sockaddr_ptr, UnsafePointer.self) 13 | var in_addr = sockaddr_in_ptr.memory.sin_addr 14 | withUnsafePointer(&in_addr) { 15 | result = closure(unsafeBitCast($0, UnsafePointer.self)) 16 | } 17 | } 18 | } 19 | 20 | if Int32(a.sa_family) == PF_INET6 { 21 | withUnsafePointer(&a) { sockaddr_ptr in 22 | let sockaddr_in6_ptr = unsafeBitCast(sockaddr_ptr, UnsafePointer.self) 23 | var in_addr = sockaddr_in6_ptr.memory.sin6_addr 24 | withUnsafePointer(&in_addr) { 25 | result = closure(unsafeBitCast($0, UnsafePointer.self)) 26 | } 27 | } 28 | } 29 | 30 | return result! 31 | } 32 | 33 | func numericAddress(address: sockaddr) -> String { 34 | return in_addr_from_sockaddr(address) { (in_addr: UnsafePointer) -> String in 35 | let buf = UnsafeMutablePointer.alloc(256) 36 | inet_ntop(Int32(address.sa_family), in_addr, buf, 256) 37 | return String.fromCString(unsafeBitCast(buf, UnsafeMutablePointer.self))! 38 | } 39 | } 40 | 41 | class NameResolverTests: QuickSpec { 42 | override func spec() { 43 | describe("NameResolver#run") { 44 | it("resolves hostname to sockaddrs") { 45 | let resolver = NameResolver(hostname: "google.com", port: 80); 46 | resolver.run(); 47 | print(resolver.status) 48 | expect(resolver.status).to(equal(NameResolverState.Resolved)); 49 | expect(resolver.results.count).to(beGreaterThan(0)); 50 | expect(resolver.IPv4Results.count + resolver.IPv6Results.count).to(equal(resolver.results.count)) 51 | } 52 | 53 | it("resolves numeric name to sockaddrs") { 54 | let resolver = NameResolver(hostname: "8.8.8.8", port: 80); 55 | resolver.run(); 56 | expect(resolver.status).to(equal(NameResolverState.Resolved)); 57 | expect(resolver.results.count).to(equal(1)); 58 | expect(numericAddress(resolver.results.first!)).to(equal("8.8.8.8")) 59 | } 60 | 61 | it("resolves to empty") { 62 | let resolver = NameResolver(hostname: "no-such-host.soutaro.com", port: 80); 63 | resolver.run(); 64 | 65 | let expectedError = NSError( 66 | domain: SMHTTPClientErrorDomain, 67 | code: SMHTTPClientErrorCode.NameResolutionFailure.rawValue, 68 | userInfo: ["NSLocalizedDescription": "nodename nor servname provided, or not known"]) 69 | expect(resolver.status).to(equal(NameResolverState.Error(expectedError))); 70 | expect(resolver.results.count).to(equal(0)); 71 | } 72 | 73 | it("can be aborted during resolve") { 74 | let resolver = NameResolver(hostname: "no-such-host.local", port: 80); 75 | 76 | let queue = dispatch_queue_create("name-resolver-test.test", nil); 77 | 78 | let delay = 0.5 * Double(NSEC_PER_SEC) 79 | let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay)) 80 | dispatch_after(time, queue, { 81 | expect(resolver.status).to(equal(NameResolverState.Running)); 82 | resolver.abort(); 83 | }) 84 | 85 | let startTime = NSDate() 86 | 87 | resolver.run(); 88 | 89 | let endTime = NSDate() 90 | 91 | expect(resolver.status).to(equal(NameResolverState.Aborted)); 92 | expect(resolver.results.count).to(equal(0)); 93 | expect(endTime.timeIntervalSinceDate(startTime)).to(beLessThan(1)); 94 | } 95 | } 96 | } 97 | } --------------------------------------------------------------------------------