├── .gitignore ├── HttpProxyDemo.xcodeproj └── project.pbxproj ├── HttpProxyDemo ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── HttpProxyDemo-Bridging-Header.h ├── HttpProxyHandler.swift ├── HttpProxyProtocol.swift ├── HttpProxySession.swift ├── Info.plist ├── NSURLRequest+ProxyCanonicalRequest.h ├── NSURLRequest+ProxyCanonicalRequest.m ├── UIWebViewVC.swift ├── URLSessionConfiguration+HttpProxy.swift ├── ViewController.swift ├── WKWebView1VC.swift └── WKWebView2VC.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/xcode,macos 3 | 4 | ### macOS ### 5 | # General 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # Icon must end with two \r 11 | Icon 12 | 13 | # Thumbnails 14 | ._* 15 | 16 | # Files that might appear in the root of a volume 17 | .DocumentRevisions-V100 18 | .fseventsd 19 | .Spotlight-V100 20 | .TemporaryItems 21 | .Trashes 22 | .VolumeIcon.icns 23 | .com.apple.timemachine.donotpresent 24 | 25 | # Directories potentially created on remote AFP share 26 | .AppleDB 27 | .AppleDesktop 28 | Network Trash Folder 29 | Temporary Items 30 | .apdisk 31 | 32 | ### Xcode ### 33 | # Xcode 34 | # 35 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 36 | 37 | ## User settings 38 | xcuserdata/ 39 | 40 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 41 | *.xcscmblueprint 42 | *.xccheckout 43 | 44 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 45 | build/ 46 | DerivedData/ 47 | *.moved-aside 48 | *.pbxuser 49 | !default.pbxuser 50 | *.mode1v3 51 | !default.mode1v3 52 | *.mode2v3 53 | !default.mode2v3 54 | *.perspectivev3 55 | !default.perspectivev3 56 | 57 | ### Xcode Patch ### 58 | *.xcodeproj/* 59 | !*.xcodeproj/project.pbxproj 60 | !*.xcodeproj/xcshareddata/ 61 | !*.xcworkspace/contents.xcworkspacedata 62 | /*.gcno 63 | 64 | 65 | # End of https://www.gitignore.io/api/xcode,macos 66 | -------------------------------------------------------------------------------- /HttpProxyDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | DA18E5672642E04700E33768 /* URLSessionConfiguration+HttpProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA18E5662642E04700E33768 /* URLSessionConfiguration+HttpProxy.swift */; }; 11 | DA39BA582174A18D008D6D20 /* NSURLRequest+ProxyCanonicalRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = DA39BA562174A18C008D6D20 /* NSURLRequest+ProxyCanonicalRequest.m */; }; 12 | DA89DBE12159D1020057D858 /* HttpProxySession.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA89DBE02159D1020057D858 /* HttpProxySession.swift */; }; 13 | DAC001332157CB7E001450C6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC001322157CB7E001450C6 /* AppDelegate.swift */; }; 14 | DAC001352157CB7E001450C6 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC001342157CB7E001450C6 /* ViewController.swift */; }; 15 | DAC001382157CB7E001450C6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DAC001362157CB7E001450C6 /* Main.storyboard */; }; 16 | DAC0013A2157CB7F001450C6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DAC001392157CB7F001450C6 /* Assets.xcassets */; }; 17 | DAC0013D2157CB7F001450C6 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DAC0013B2157CB7F001450C6 /* LaunchScreen.storyboard */; }; 18 | DAC00146215911C9001450C6 /* HttpProxyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC00144215911C9001450C6 /* HttpProxyHandler.swift */; }; 19 | DAC00147215911C9001450C6 /* HttpProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC00145215911C9001450C6 /* HttpProxyProtocol.swift */; }; 20 | DAC00149215919BB001450C6 /* UIWebViewVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC00148215919BB001450C6 /* UIWebViewVC.swift */; }; 21 | DAC0014B215919D7001450C6 /* WKWebView1VC.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC0014A215919D7001450C6 /* WKWebView1VC.swift */; }; 22 | DAC0014D21591A08001450C6 /* WKWebView2VC.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC0014C21591A08001450C6 /* WKWebView2VC.swift */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | DA18E5662642E04700E33768 /* URLSessionConfiguration+HttpProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionConfiguration+HttpProxy.swift"; sourceTree = ""; }; 27 | DA39BA552174A18C008D6D20 /* HttpProxyDemo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "HttpProxyDemo-Bridging-Header.h"; sourceTree = ""; }; 28 | DA39BA562174A18C008D6D20 /* NSURLRequest+ProxyCanonicalRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURLRequest+ProxyCanonicalRequest.m"; sourceTree = ""; }; 29 | DA39BA572174A18D008D6D20 /* NSURLRequest+ProxyCanonicalRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURLRequest+ProxyCanonicalRequest.h"; sourceTree = ""; }; 30 | DA89DBE02159D1020057D858 /* HttpProxySession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpProxySession.swift; sourceTree = ""; }; 31 | DAC0012F2157CB7E001450C6 /* HttpProxyDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HttpProxyDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | DAC001322157CB7E001450C6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33 | DAC001342157CB7E001450C6 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 34 | DAC001372157CB7E001450C6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 35 | DAC001392157CB7F001450C6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 36 | DAC0013C2157CB7F001450C6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 37 | DAC0013E2157CB7F001450C6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | DAC00144215911C9001450C6 /* HttpProxyHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpProxyHandler.swift; sourceTree = ""; }; 39 | DAC00145215911C9001450C6 /* HttpProxyProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpProxyProtocol.swift; sourceTree = ""; }; 40 | DAC00148215919BB001450C6 /* UIWebViewVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIWebViewVC.swift; sourceTree = ""; }; 41 | DAC0014A215919D7001450C6 /* WKWebView1VC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKWebView1VC.swift; sourceTree = ""; }; 42 | DAC0014C21591A08001450C6 /* WKWebView2VC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKWebView2VC.swift; sourceTree = ""; }; 43 | /* End PBXFileReference section */ 44 | 45 | /* Begin PBXFrameworksBuildPhase section */ 46 | DAC0012C2157CB7E001450C6 /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | /* End PBXFrameworksBuildPhase section */ 54 | 55 | /* Begin PBXGroup section */ 56 | DAC001262157CB7E001450C6 = { 57 | isa = PBXGroup; 58 | children = ( 59 | DAC001312157CB7E001450C6 /* HttpProxyDemo */, 60 | DAC001302157CB7E001450C6 /* Products */, 61 | ); 62 | sourceTree = ""; 63 | }; 64 | DAC001302157CB7E001450C6 /* Products */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | DAC0012F2157CB7E001450C6 /* HttpProxyDemo.app */, 68 | ); 69 | name = Products; 70 | sourceTree = ""; 71 | }; 72 | DAC001312157CB7E001450C6 /* HttpProxyDemo */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | DA39BA572174A18D008D6D20 /* NSURLRequest+ProxyCanonicalRequest.h */, 76 | DA39BA562174A18C008D6D20 /* NSURLRequest+ProxyCanonicalRequest.m */, 77 | DAC001322157CB7E001450C6 /* AppDelegate.swift */, 78 | DAC001342157CB7E001450C6 /* ViewController.swift */, 79 | DAC00148215919BB001450C6 /* UIWebViewVC.swift */, 80 | DAC0014A215919D7001450C6 /* WKWebView1VC.swift */, 81 | DAC0014C21591A08001450C6 /* WKWebView2VC.swift */, 82 | DAC00144215911C9001450C6 /* HttpProxyHandler.swift */, 83 | DA89DBE02159D1020057D858 /* HttpProxySession.swift */, 84 | DAC00145215911C9001450C6 /* HttpProxyProtocol.swift */, 85 | DA18E5662642E04700E33768 /* URLSessionConfiguration+HttpProxy.swift */, 86 | DAC001362157CB7E001450C6 /* Main.storyboard */, 87 | DAC001392157CB7F001450C6 /* Assets.xcassets */, 88 | DAC0013B2157CB7F001450C6 /* LaunchScreen.storyboard */, 89 | DAC0013E2157CB7F001450C6 /* Info.plist */, 90 | DA39BA552174A18C008D6D20 /* HttpProxyDemo-Bridging-Header.h */, 91 | ); 92 | path = HttpProxyDemo; 93 | sourceTree = ""; 94 | }; 95 | /* End PBXGroup section */ 96 | 97 | /* Begin PBXNativeTarget section */ 98 | DAC0012E2157CB7E001450C6 /* HttpProxyDemo */ = { 99 | isa = PBXNativeTarget; 100 | buildConfigurationList = DAC001412157CB7F001450C6 /* Build configuration list for PBXNativeTarget "HttpProxyDemo" */; 101 | buildPhases = ( 102 | DAC0012B2157CB7E001450C6 /* Sources */, 103 | DAC0012C2157CB7E001450C6 /* Frameworks */, 104 | DAC0012D2157CB7E001450C6 /* Resources */, 105 | ); 106 | buildRules = ( 107 | ); 108 | dependencies = ( 109 | ); 110 | name = HttpProxyDemo; 111 | productName = HttpProxyDemo; 112 | productReference = DAC0012F2157CB7E001450C6 /* HttpProxyDemo.app */; 113 | productType = "com.apple.product-type.application"; 114 | }; 115 | /* End PBXNativeTarget section */ 116 | 117 | /* Begin PBXProject section */ 118 | DAC001272157CB7E001450C6 /* Project object */ = { 119 | isa = PBXProject; 120 | attributes = { 121 | LastSwiftUpdateCheck = 1000; 122 | LastUpgradeCheck = 1000; 123 | ORGANIZATIONNAME = Nemo; 124 | TargetAttributes = { 125 | DAC0012E2157CB7E001450C6 = { 126 | CreatedOnToolsVersion = 10.0; 127 | LastSwiftMigration = 1250; 128 | }; 129 | }; 130 | }; 131 | buildConfigurationList = DAC0012A2157CB7E001450C6 /* Build configuration list for PBXProject "HttpProxyDemo" */; 132 | compatibilityVersion = "Xcode 9.3"; 133 | developmentRegion = en; 134 | hasScannedForEncodings = 0; 135 | knownRegions = ( 136 | en, 137 | Base, 138 | ); 139 | mainGroup = DAC001262157CB7E001450C6; 140 | productRefGroup = DAC001302157CB7E001450C6 /* Products */; 141 | projectDirPath = ""; 142 | projectRoot = ""; 143 | targets = ( 144 | DAC0012E2157CB7E001450C6 /* HttpProxyDemo */, 145 | ); 146 | }; 147 | /* End PBXProject section */ 148 | 149 | /* Begin PBXResourcesBuildPhase section */ 150 | DAC0012D2157CB7E001450C6 /* Resources */ = { 151 | isa = PBXResourcesBuildPhase; 152 | buildActionMask = 2147483647; 153 | files = ( 154 | DAC0013D2157CB7F001450C6 /* LaunchScreen.storyboard in Resources */, 155 | DAC0013A2157CB7F001450C6 /* Assets.xcassets in Resources */, 156 | DAC001382157CB7E001450C6 /* Main.storyboard in Resources */, 157 | ); 158 | runOnlyForDeploymentPostprocessing = 0; 159 | }; 160 | /* End PBXResourcesBuildPhase section */ 161 | 162 | /* Begin PBXSourcesBuildPhase section */ 163 | DAC0012B2157CB7E001450C6 /* Sources */ = { 164 | isa = PBXSourcesBuildPhase; 165 | buildActionMask = 2147483647; 166 | files = ( 167 | DAC00146215911C9001450C6 /* HttpProxyHandler.swift in Sources */, 168 | DA89DBE12159D1020057D858 /* HttpProxySession.swift in Sources */, 169 | DA18E5672642E04700E33768 /* URLSessionConfiguration+HttpProxy.swift in Sources */, 170 | DAC001352157CB7E001450C6 /* ViewController.swift in Sources */, 171 | DAC0014B215919D7001450C6 /* WKWebView1VC.swift in Sources */, 172 | DAC00147215911C9001450C6 /* HttpProxyProtocol.swift in Sources */, 173 | DAC0014D21591A08001450C6 /* WKWebView2VC.swift in Sources */, 174 | DA39BA582174A18D008D6D20 /* NSURLRequest+ProxyCanonicalRequest.m in Sources */, 175 | DAC00149215919BB001450C6 /* UIWebViewVC.swift in Sources */, 176 | DAC001332157CB7E001450C6 /* AppDelegate.swift in Sources */, 177 | ); 178 | runOnlyForDeploymentPostprocessing = 0; 179 | }; 180 | /* End PBXSourcesBuildPhase section */ 181 | 182 | /* Begin PBXVariantGroup section */ 183 | DAC001362157CB7E001450C6 /* Main.storyboard */ = { 184 | isa = PBXVariantGroup; 185 | children = ( 186 | DAC001372157CB7E001450C6 /* Base */, 187 | ); 188 | name = Main.storyboard; 189 | sourceTree = ""; 190 | }; 191 | DAC0013B2157CB7F001450C6 /* LaunchScreen.storyboard */ = { 192 | isa = PBXVariantGroup; 193 | children = ( 194 | DAC0013C2157CB7F001450C6 /* Base */, 195 | ); 196 | name = LaunchScreen.storyboard; 197 | sourceTree = ""; 198 | }; 199 | /* End PBXVariantGroup section */ 200 | 201 | /* Begin XCBuildConfiguration section */ 202 | DAC0013F2157CB7F001450C6 /* Debug */ = { 203 | isa = XCBuildConfiguration; 204 | buildSettings = { 205 | ALWAYS_SEARCH_USER_PATHS = NO; 206 | CLANG_ANALYZER_NONNULL = YES; 207 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 208 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 209 | CLANG_CXX_LIBRARY = "libc++"; 210 | CLANG_ENABLE_MODULES = YES; 211 | CLANG_ENABLE_OBJC_ARC = YES; 212 | CLANG_ENABLE_OBJC_WEAK = YES; 213 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 214 | CLANG_WARN_BOOL_CONVERSION = YES; 215 | CLANG_WARN_COMMA = YES; 216 | CLANG_WARN_CONSTANT_CONVERSION = YES; 217 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 218 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 219 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 220 | CLANG_WARN_EMPTY_BODY = YES; 221 | CLANG_WARN_ENUM_CONVERSION = YES; 222 | CLANG_WARN_INFINITE_RECURSION = YES; 223 | CLANG_WARN_INT_CONVERSION = YES; 224 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 225 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 226 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 227 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 228 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 229 | CLANG_WARN_STRICT_PROTOTYPES = YES; 230 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 231 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 232 | CLANG_WARN_UNREACHABLE_CODE = YES; 233 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 234 | CODE_SIGN_IDENTITY = "iPhone Developer"; 235 | COPY_PHASE_STRIP = NO; 236 | DEBUG_INFORMATION_FORMAT = dwarf; 237 | ENABLE_STRICT_OBJC_MSGSEND = YES; 238 | ENABLE_TESTABILITY = YES; 239 | GCC_C_LANGUAGE_STANDARD = gnu11; 240 | GCC_DYNAMIC_NO_PIC = NO; 241 | GCC_NO_COMMON_BLOCKS = YES; 242 | GCC_OPTIMIZATION_LEVEL = 0; 243 | GCC_PREPROCESSOR_DEFINITIONS = ( 244 | "DEBUG=1", 245 | "$(inherited)", 246 | ); 247 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 248 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 249 | GCC_WARN_UNDECLARED_SELECTOR = YES; 250 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 251 | GCC_WARN_UNUSED_FUNCTION = YES; 252 | GCC_WARN_UNUSED_VARIABLE = YES; 253 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 254 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 255 | MTL_FAST_MATH = YES; 256 | ONLY_ACTIVE_ARCH = YES; 257 | SDKROOT = iphoneos; 258 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 259 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 260 | }; 261 | name = Debug; 262 | }; 263 | DAC001402157CB7F001450C6 /* Release */ = { 264 | isa = XCBuildConfiguration; 265 | buildSettings = { 266 | ALWAYS_SEARCH_USER_PATHS = NO; 267 | CLANG_ANALYZER_NONNULL = YES; 268 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 269 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 270 | CLANG_CXX_LIBRARY = "libc++"; 271 | CLANG_ENABLE_MODULES = YES; 272 | CLANG_ENABLE_OBJC_ARC = YES; 273 | CLANG_ENABLE_OBJC_WEAK = YES; 274 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 275 | CLANG_WARN_BOOL_CONVERSION = YES; 276 | CLANG_WARN_COMMA = YES; 277 | CLANG_WARN_CONSTANT_CONVERSION = YES; 278 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 279 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 280 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 281 | CLANG_WARN_EMPTY_BODY = YES; 282 | CLANG_WARN_ENUM_CONVERSION = YES; 283 | CLANG_WARN_INFINITE_RECURSION = YES; 284 | CLANG_WARN_INT_CONVERSION = YES; 285 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 286 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 287 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 288 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 289 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 290 | CLANG_WARN_STRICT_PROTOTYPES = YES; 291 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 292 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 293 | CLANG_WARN_UNREACHABLE_CODE = YES; 294 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 295 | CODE_SIGN_IDENTITY = "iPhone Developer"; 296 | COPY_PHASE_STRIP = NO; 297 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 298 | ENABLE_NS_ASSERTIONS = NO; 299 | ENABLE_STRICT_OBJC_MSGSEND = YES; 300 | GCC_C_LANGUAGE_STANDARD = gnu11; 301 | GCC_NO_COMMON_BLOCKS = YES; 302 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 303 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 304 | GCC_WARN_UNDECLARED_SELECTOR = YES; 305 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 306 | GCC_WARN_UNUSED_FUNCTION = YES; 307 | GCC_WARN_UNUSED_VARIABLE = YES; 308 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 309 | MTL_ENABLE_DEBUG_INFO = NO; 310 | MTL_FAST_MATH = YES; 311 | SDKROOT = iphoneos; 312 | SWIFT_COMPILATION_MODE = wholemodule; 313 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 314 | VALIDATE_PRODUCT = YES; 315 | }; 316 | name = Release; 317 | }; 318 | DAC001422157CB7F001450C6 /* Debug */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 322 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 323 | CLANG_ENABLE_MODULES = YES; 324 | CODE_SIGN_STYLE = Automatic; 325 | DEVELOPMENT_TEAM = 8HQ2P3SCVF; 326 | INFOPLIST_FILE = HttpProxyDemo/Info.plist; 327 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 328 | LD_RUNPATH_SEARCH_PATHS = ( 329 | "$(inherited)", 330 | "@executable_path/Frameworks", 331 | ); 332 | PRODUCT_BUNDLE_IDENTIFIER = com.nemocdz.HttpProxyDemo; 333 | PRODUCT_NAME = "$(TARGET_NAME)"; 334 | SWIFT_INSTALL_OBJC_HEADER = YES; 335 | SWIFT_OBJC_BRIDGING_HEADER = "HttpProxyDemo/HttpProxyDemo-Bridging-Header.h"; 336 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 337 | SWIFT_VERSION = 5.0; 338 | TARGETED_DEVICE_FAMILY = "1,2"; 339 | }; 340 | name = Debug; 341 | }; 342 | DAC001432157CB7F001450C6 /* Release */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 346 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 347 | CLANG_ENABLE_MODULES = YES; 348 | CODE_SIGN_STYLE = Automatic; 349 | DEVELOPMENT_TEAM = 8HQ2P3SCVF; 350 | INFOPLIST_FILE = HttpProxyDemo/Info.plist; 351 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 352 | LD_RUNPATH_SEARCH_PATHS = ( 353 | "$(inherited)", 354 | "@executable_path/Frameworks", 355 | ); 356 | PRODUCT_BUNDLE_IDENTIFIER = com.nemocdz.HttpProxyDemo; 357 | PRODUCT_NAME = "$(TARGET_NAME)"; 358 | SWIFT_INSTALL_OBJC_HEADER = YES; 359 | SWIFT_OBJC_BRIDGING_HEADER = "HttpProxyDemo/HttpProxyDemo-Bridging-Header.h"; 360 | SWIFT_VERSION = 5.0; 361 | TARGETED_DEVICE_FAMILY = "1,2"; 362 | }; 363 | name = Release; 364 | }; 365 | /* End XCBuildConfiguration section */ 366 | 367 | /* Begin XCConfigurationList section */ 368 | DAC0012A2157CB7E001450C6 /* Build configuration list for PBXProject "HttpProxyDemo" */ = { 369 | isa = XCConfigurationList; 370 | buildConfigurations = ( 371 | DAC0013F2157CB7F001450C6 /* Debug */, 372 | DAC001402157CB7F001450C6 /* Release */, 373 | ); 374 | defaultConfigurationIsVisible = 0; 375 | defaultConfigurationName = Release; 376 | }; 377 | DAC001412157CB7F001450C6 /* Build configuration list for PBXNativeTarget "HttpProxyDemo" */ = { 378 | isa = XCConfigurationList; 379 | buildConfigurations = ( 380 | DAC001422157CB7F001450C6 /* Debug */, 381 | DAC001432157CB7F001450C6 /* Release */, 382 | ); 383 | defaultConfigurationIsVisible = 0; 384 | defaultConfigurationName = Release; 385 | }; 386 | /* End XCConfigurationList section */ 387 | }; 388 | rootObject = DAC001272157CB7E001450C6 /* Project object */; 389 | } 390 | -------------------------------------------------------------------------------- /HttpProxyDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // HttpProxyDemo 4 | // 5 | // Created by Nemo on 2018/9/23. 6 | // Copyright © 2018年 Nemo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /HttpProxyDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /HttpProxyDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /HttpProxyDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /HttpProxyDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 27 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /HttpProxyDemo/HttpProxyDemo-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | 6 | #import "NSURLRequest+ProxyCanonicalRequest.h" 7 | -------------------------------------------------------------------------------- /HttpProxyDemo/HttpProxyHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HttpProxyHandler.swift 3 | // Test 4 | // 5 | // Created by Nemo on 2018/9/21. 6 | // Copyright © 2018年 Nemo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import WebKit 11 | import ObjectiveC 12 | 13 | final class HttpProxyHandler: NSObject { 14 | private var dataTask: URLSessionDataTask? 15 | private static var session: URLSession? 16 | 17 | init(proxyConfig: HttpProxyConfig) { 18 | Self.updateSession(of: proxyConfig) 19 | } 20 | 21 | private static func updateSession(of proxyConfig: HttpProxyConfig) { 22 | if let session = Self.session, session.configuration.hasProxyConfig(proxyConfig) { 23 | return 24 | } 25 | let config = URLSessionConfiguration.default 26 | config.addProxyConfig(proxyConfig) 27 | Self.session = URLSession(configuration: config) 28 | } 29 | } 30 | 31 | extension HttpProxyHandler: WKURLSchemeHandler { 32 | func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) { 33 | dataTask = Self.session?.dataTask(with: urlSchemeTask.request) { [weak urlSchemeTask] data, response, error in 34 | guard let urlSchemeTask = urlSchemeTask else { return } 35 | if let error = error, error._code != NSURLErrorCancelled { 36 | urlSchemeTask.didFailWithError(error) 37 | } else { 38 | if let response = response { 39 | urlSchemeTask.didReceive(response) 40 | } 41 | if let data = data { 42 | urlSchemeTask.didReceive(data) 43 | } 44 | urlSchemeTask.didFinish() 45 | } 46 | } 47 | dataTask?.resume() 48 | } 49 | 50 | func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) { 51 | dataTask?.cancel() 52 | } 53 | } 54 | 55 | private var hookWKWebView: () = { 56 | guard let origin = class_getClassMethod(WKWebView.self, #selector(WKWebView.handlesURLScheme(_:))), 57 | let hook = class_getClassMethod(WKWebView.self, #selector(WKWebView._handlesURLScheme(_:))) else { 58 | return 59 | } 60 | method_exchangeImplementations(origin, hook) 61 | }() 62 | 63 | fileprivate extension WKWebView { 64 | @objc static func _handlesURLScheme(_ urlScheme: String) -> Bool { 65 | if httpSchemes.contains(urlScheme) { 66 | return false 67 | } 68 | return Self.handlesURLScheme(urlScheme) 69 | } 70 | } 71 | 72 | extension WKWebViewConfiguration { 73 | func addProxyConfig(_ config: HttpProxyConfig) { 74 | let handler = HttpProxyHandler(proxyConfig: config) 75 | _ = hookWKWebView 76 | httpSchemes.forEach { 77 | setURLSchemeHandler(handler, forURLScheme: $0) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /HttpProxyDemo/HttpProxyProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLProxyProtocol.swift 3 | // Test 4 | // 5 | // Created by Nemo on 2018/9/20. 6 | // Copyright © 2018年 Nemo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import WebKit 11 | 12 | final class HttpProxyProtocol: URLProtocol { 13 | private static var proxyConfig: HttpProxyConfig? 14 | private static var isRegistered = false 15 | private static let customKey = "HttpProxyProtocolKey" 16 | private static var session: HttpProxySession? 17 | private var dataTask: URLSessionDataTask? 18 | 19 | static func start(proxyConfig: HttpProxyConfig) { 20 | guard !isRegistered else { return } 21 | URLProtocol.registerClass(self) 22 | isRegistered = true 23 | self.proxyConfig = proxyConfig 24 | } 25 | 26 | static func stop() { 27 | guard isRegistered else { return } 28 | URLProtocol.unregisterClass(self) 29 | isRegistered = false 30 | } 31 | 32 | private static func updateSession(of proxyConfig: HttpProxyConfig) { 33 | if let session = Self.session, session.currentSession.configuration.hasProxyConfig(proxyConfig) { 34 | return 35 | } 36 | Self.session = HttpProxySession(proxyConfig: proxyConfig) 37 | } 38 | } 39 | 40 | extension HttpProxyProtocol { 41 | private static let contextControllerType: AnyObject = { 42 | let controller = WKWebView().value(forKey: "browsingContextController") as AnyObject 43 | return type(of: controller) as AnyObject 44 | }() 45 | 46 | static var webKitSupport: Bool = false { 47 | didSet { 48 | let selName = webKitSupport ? "registerSchemeForCustomProtocol:" : "unregisterSchemeForCustomProtocol:" 49 | httpSchemes.forEach { 50 | _ = contextControllerType.perform(Selector(selName), with: $0) 51 | } 52 | } 53 | } 54 | } 55 | 56 | extension HttpProxyProtocol { 57 | override class func canInit(with request: URLRequest) -> Bool { 58 | guard let scheme = request.url?.scheme?.lowercased(), 59 | httpSchemes.contains(scheme) else { 60 | return false 61 | } 62 | if property(forKey: customKey, in: request) != nil { 63 | return false 64 | } 65 | return true 66 | } 67 | 68 | override class func canonicalRequest(for request: URLRequest) -> URLRequest { 69 | return (request as NSURLRequest).cdz_canonical() 70 | } 71 | 72 | override func startLoading() { 73 | if let request = request as? NSMutableURLRequest { 74 | Self.setProperty((), forKey: Self.customKey, in: request) 75 | } 76 | Self.updateSession(of: Self.proxyConfig!) 77 | dataTask = Self.session?.dataTask(with: request, delegate: self) 78 | dataTask?.resume() 79 | } 80 | 81 | override func stopLoading() { 82 | dataTask?.cancel() 83 | dataTask = nil 84 | } 85 | } 86 | 87 | extension HttpProxyProtocol: URLSessionDataDelegate { 88 | func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, 89 | didReceive response: URLResponse, 90 | completionHandler: (URLSession.ResponseDisposition) -> Void) 91 | { 92 | client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) 93 | completionHandler(.allow) 94 | } 95 | 96 | func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { 97 | client?.urlProtocol(self, didLoad: data) 98 | } 99 | } 100 | 101 | extension HttpProxyProtocol: URLSessionTaskDelegate { 102 | func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { 103 | if let request = request as? NSMutableURLRequest { 104 | Self.removeProperty(forKey: Self.customKey, in: request) 105 | } 106 | client?.urlProtocol(self, wasRedirectedTo: request, redirectResponse: response) 107 | dataTask?.cancel() 108 | let error = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) 109 | client?.urlProtocol(self, didFailWithError: error) 110 | } 111 | 112 | func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { 113 | if let error = error, error._code != NSURLErrorCancelled { 114 | client?.urlProtocol(self, didFailWithError: error) 115 | } else { 116 | client?.urlProtocolDidFinishLoading(self) 117 | } 118 | } 119 | } 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /HttpProxyDemo/HttpProxySession.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProxyURLSession.swift 3 | // HttpProxyDemo 4 | // 5 | // Created by Nemo on 2018/9/25. 6 | // Copyright © 2018年 Nemo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class HttpProxySession { 12 | private let sessionDelegate = Delegate() 13 | let currentSession: URLSession 14 | 15 | init(proxyConfig: HttpProxyConfig) { 16 | let config = URLSessionConfiguration.default 17 | config.addProxyConfig(proxyConfig) 18 | currentSession = URLSession(configuration: config, delegate: sessionDelegate, delegateQueue: nil) 19 | } 20 | 21 | deinit { 22 | currentSession.invalidateAndCancel() 23 | } 24 | 25 | func dataTask(with request: URLRequest, delegate: URLSessionDelegate) -> URLSessionDataTask { 26 | let dataTask = currentSession.dataTask(with: request) 27 | sessionDelegate[dataTask] = delegate 28 | return dataTask 29 | } 30 | } 31 | 32 | extension HttpProxySession { 33 | final class Delegate: NSObject { 34 | private let lock = NSLock() 35 | private var taskDelegates = [Int: URLSessionDelegate]() 36 | subscript(task: URLSessionTask) -> URLSessionDelegate? { 37 | get { 38 | lock.lock() 39 | defer { 40 | lock.unlock() 41 | } 42 | return taskDelegates[task.taskIdentifier] 43 | } 44 | set { 45 | lock.lock() 46 | defer { 47 | lock.unlock() 48 | } 49 | taskDelegates[task.taskIdentifier] = newValue 50 | } 51 | } 52 | } 53 | } 54 | 55 | extension HttpProxySession.Delegate: URLSessionDataDelegate { 56 | func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, 57 | didReceive response: URLResponse, 58 | completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) 59 | { 60 | if let delegate = self[dataTask] as? URLSessionDataDelegate { 61 | delegate.urlSession!(session, dataTask: dataTask, didReceive: response, completionHandler: completionHandler) 62 | } else { 63 | completionHandler(.cancel) 64 | } 65 | } 66 | 67 | func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { 68 | if let delegate = self[dataTask] as? URLSessionDataDelegate { 69 | delegate.urlSession!(session, dataTask: dataTask, didReceive: data) 70 | } 71 | } 72 | } 73 | 74 | extension HttpProxySession.Delegate: URLSessionTaskDelegate { 75 | func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { 76 | if let delegate = self[task] as? URLSessionTaskDelegate { 77 | delegate.urlSession?(session, task: task, willPerformHTTPRedirection: response, newRequest: request, completionHandler: completionHandler) 78 | } 79 | } 80 | 81 | func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { 82 | if let delegate = self[task] as? URLSessionTaskDelegate { 83 | delegate.urlSession!(session, task: task, didCompleteWithError: error) 84 | } 85 | self[task] = nil 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /HttpProxyDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSAppTransportSecurity 24 | 25 | NSAllowsArbitraryLoads 26 | 27 | 28 | UILaunchStoryboardName 29 | LaunchScreen 30 | UIMainStoryboardFile 31 | Main 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /HttpProxyDemo/NSURLRequest+ProxyCanonicalRequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLRequest+ProxyCanonicalRequest.h 3 | // FrameworkCommon 4 | // 5 | // Created by Nemo on 2018/10/15. 6 | // Copyright © 2018 Nemo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NSURLRequest (ProxyCanonicalRequest) 14 | 15 | - (NSURLRequest *)cdz_canonicalRequest; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /HttpProxyDemo/NSURLRequest+ProxyCanonicalRequest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLRequest+ProxyCanonicalRequest.m 3 | // FrameworkCommon 4 | // 5 | // Created by Nemo on 2018/10/15. 6 | // Copyright © 2018 Nemo. All rights reserved. 7 | // 8 | 9 | #import "NSURLRequest+ProxyCanonicalRequest.h" 10 | #include 11 | 12 | @implementation NSURLRequest (ProxyCanonicalRequest) 13 | 14 | #pragma mark * URL canonicalization steps 15 | 16 | /*! A step in the canonicalisation process. 17 | * \details The canonicalisation process is made up of a sequence of steps, each of which is 18 | * implemented by a function that matches this function pointer. The function gets a URL 19 | * and a mutable buffer holding that URL as bytes. The function can mutate the buffer as it 20 | * sees fit. It typically does this by calling CFURLGetByteRangeForComponent to find the range 21 | * of interest in the buffer. In that case bytesInserted is the amount to adjust that range, 22 | * and the function should modify that to account for any bytes it inserts or deletes. If 23 | * the function modifies the buffer too much, it can return kCFNotFound to force the system 24 | * to re-create the URL from the buffer. 25 | * \param url The original URL to work on. 26 | * \param urlData The URL as a mutable buffer; the routine modifies this. 27 | * \param bytesInserted The number of bytes that have been inserted so far the mutable buffer. 28 | * \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed. 29 | */ 30 | 31 | typedef CFIndex (*CanonicalRequestStepFunction)(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted); 32 | 33 | /*! The post-scheme separate should be "://"; if that's not the case, fix it. 34 | * \param url The original URL to work on. 35 | * \param urlData The URL as a mutable buffer; the routine modifies this. 36 | * \param bytesInserted The number of bytes that have been inserted so far the mutable buffer. 37 | * \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed. 38 | */ 39 | 40 | static CFIndex FixPostSchemeSeparator(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted) 41 | { 42 | CFRange range; 43 | uint8_t * urlDataBytes; 44 | NSUInteger urlDataLength; 45 | NSUInteger cursor; 46 | NSUInteger separatorLength; 47 | NSUInteger expectedSeparatorLength; 48 | 49 | assert(url != nil); 50 | assert(urlData != nil); 51 | assert(bytesInserted >= 0); 52 | 53 | range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentScheme, NULL); 54 | if (range.location != kCFNotFound) { 55 | assert(range.location >= 0); 56 | assert(range.length >= 0); 57 | 58 | urlDataBytes = [urlData mutableBytes]; 59 | urlDataLength = [urlData length]; 60 | 61 | separatorLength = 0; 62 | cursor = (NSUInteger) range.location + (NSUInteger) bytesInserted + (NSUInteger) range.length; 63 | if ( (cursor < urlDataLength) && (urlDataBytes[cursor] == ':') ) { 64 | cursor += 1; 65 | separatorLength += 1; 66 | if ( (cursor < urlDataLength) && (urlDataBytes[cursor] == '/') ) { 67 | cursor += 1; 68 | separatorLength += 1; 69 | if ( (cursor < urlDataLength) && (urlDataBytes[cursor] == '/') ) { 70 | cursor += 1; 71 | separatorLength += 1; 72 | } 73 | } 74 | } 75 | #pragma unused(cursor) // quietens an analyser warning 76 | 77 | expectedSeparatorLength = strlen("://"); 78 | if (separatorLength != expectedSeparatorLength) { 79 | [urlData replaceBytesInRange:NSMakeRange((NSUInteger) range.location + (NSUInteger) bytesInserted + (NSUInteger) range.length, separatorLength) withBytes:"://" length:expectedSeparatorLength]; 80 | bytesInserted = kCFNotFound; // have to build everything now 81 | } 82 | } 83 | 84 | return bytesInserted; 85 | } 86 | 87 | /*! The scheme should be lower case; if it's not, make it so. 88 | * \param url The original URL to work on. 89 | * \param urlData The URL as a mutable buffer; the routine modifies this. 90 | * \param bytesInserted The number of bytes that have been inserted so far the mutable buffer. 91 | * \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed. 92 | */ 93 | 94 | static CFIndex LowercaseScheme(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted) 95 | { 96 | CFRange range; 97 | uint8_t * urlDataBytes; 98 | CFIndex i; 99 | 100 | assert(url != nil); 101 | assert(urlData != nil); 102 | assert(bytesInserted >= 0); 103 | 104 | range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentScheme, NULL); 105 | if (range.location != kCFNotFound) { 106 | assert(range.location >= 0); 107 | assert(range.length >= 0); 108 | 109 | urlDataBytes = [urlData mutableBytes]; 110 | for (i = range.location + bytesInserted; i < (range.location + bytesInserted + range.length); i++) { 111 | urlDataBytes[i] = (uint8_t) tolower_l(urlDataBytes[i], NULL); 112 | } 113 | } 114 | return bytesInserted; 115 | } 116 | 117 | /*! The host should be lower case; if it's not, make it so. 118 | * \param url The original URL to work on. 119 | * \param urlData The URL as a mutable buffer; the routine modifies this. 120 | * \param bytesInserted The number of bytes that have been inserted so far the mutable buffer. 121 | * \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed. 122 | */ 123 | 124 | static CFIndex LowercaseHost(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted) 125 | // The host should be lower case; if it's not, make it so. 126 | { 127 | CFRange range; 128 | uint8_t * urlDataBytes; 129 | CFIndex i; 130 | 131 | assert(url != nil); 132 | assert(urlData != nil); 133 | assert(bytesInserted >= 0); 134 | 135 | range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentHost, NULL); 136 | if (range.location != kCFNotFound) { 137 | assert(range.location >= 0); 138 | assert(range.length >= 0); 139 | 140 | urlDataBytes = [urlData mutableBytes]; 141 | for (i = range.location + bytesInserted; i < (range.location + bytesInserted + range.length); i++) { 142 | urlDataBytes[i] = (uint8_t) tolower_l(urlDataBytes[i], NULL); 143 | } 144 | } 145 | return bytesInserted; 146 | } 147 | 148 | /*! An empty host should be treated as "localhost" case; if it's not, make it so. 149 | * \param url The original URL to work on. 150 | * \param urlData The URL as a mutable buffer; the routine modifies this. 151 | * \param bytesInserted The number of bytes that have been inserted so far the mutable buffer. 152 | * \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed. 153 | */ 154 | 155 | static CFIndex FixEmptyHost(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted) 156 | { 157 | CFRange range; 158 | CFRange rangeWithSeparator; 159 | 160 | assert(url != nil); 161 | assert(urlData != nil); 162 | assert(bytesInserted >= 0); 163 | 164 | range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentHost, &rangeWithSeparator); 165 | if (range.length == 0) { 166 | NSUInteger localhostLength; 167 | 168 | assert(range.location >= 0); 169 | assert(range.length >= 0); 170 | 171 | localhostLength = strlen("localhost"); 172 | if (range.location != kCFNotFound) { 173 | [urlData replaceBytesInRange:NSMakeRange( (NSUInteger) range.location + (NSUInteger) bytesInserted, 0) withBytes:"localhost" length:localhostLength]; 174 | bytesInserted += localhostLength; 175 | } else if ( (rangeWithSeparator.location != kCFNotFound) && (rangeWithSeparator.length == 0) ) { 176 | [urlData replaceBytesInRange:NSMakeRange((NSUInteger) rangeWithSeparator.location + (NSUInteger) bytesInserted, 0) withBytes:"localhost" length:localhostLength]; 177 | bytesInserted += localhostLength; 178 | } 179 | } 180 | return bytesInserted; 181 | } 182 | 183 | /*! Transform an empty URL path to "/". For example, "http://www.apple.com" becomes "http://www.apple.com/". 184 | * \param url The original URL to work on. 185 | * \param urlData The URL as a mutable buffer; the routine modifies this. 186 | * \param bytesInserted The number of bytes that have been inserted so far the mutable buffer. 187 | * \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed. 188 | */ 189 | 190 | static CFIndex FixEmptyPath(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted) 191 | { 192 | CFRange range; 193 | CFRange rangeWithSeparator; 194 | 195 | assert(url != nil); 196 | assert(urlData != nil); 197 | assert(bytesInserted >= 0); 198 | 199 | range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentPath, &rangeWithSeparator); 200 | // The following is not a typo. We use rangeWithSeparator to find where to insert the 201 | // "/" and the range length to decide whether we /need/ to insert the "/". 202 | if ( (rangeWithSeparator.location != kCFNotFound) && (range.length == 0) ) { 203 | assert(range.location >= 0); 204 | assert(range.length >= 0); 205 | assert(rangeWithSeparator.location >= 0); 206 | assert(rangeWithSeparator.length >= 0); 207 | 208 | [urlData replaceBytesInRange:NSMakeRange( (NSUInteger) rangeWithSeparator.location + (NSUInteger) bytesInserted, 0) withBytes:"/" length:1]; 209 | bytesInserted += 1; 210 | } 211 | return bytesInserted; 212 | } 213 | 214 | /*! If the user specified the default port (80 for HTTP, 443 for HTTPS), remove it from the URL. 215 | * \details Actually this code is disabled because the equivalent code in the default protocol 216 | * handler has also been disabled; some setups depend on get the port number in the URL, even if it 217 | * is the default. 218 | * \param url The original URL to work on. 219 | * \param urlData The URL as a mutable buffer; the routine modifies this. 220 | * \param bytesInserted The number of bytes that have been inserted so far the mutable buffer. 221 | * \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed. 222 | */ 223 | 224 | __attribute__((unused)) static CFIndex DeleteDefaultPort(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted) 225 | { 226 | NSString * scheme; 227 | BOOL isHTTP; 228 | BOOL isHTTPS; 229 | CFRange range; 230 | uint8_t * urlDataBytes; 231 | NSString * portNumberStr; 232 | int portNumber; 233 | 234 | assert(url != nil); 235 | assert(urlData != nil); 236 | assert(bytesInserted >= 0); 237 | 238 | scheme = [[url scheme] lowercaseString]; 239 | assert(scheme != nil); 240 | 241 | isHTTP = [scheme isEqual:@"http" ]; 242 | isHTTPS = [scheme isEqual:@"https"]; 243 | 244 | range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentPort, NULL); 245 | if (range.location != kCFNotFound) { 246 | assert(range.location >= 0); 247 | assert(range.length >= 0); 248 | 249 | urlDataBytes = [urlData mutableBytes]; 250 | 251 | portNumberStr = [[NSString alloc] initWithBytes:&urlDataBytes[range.location + bytesInserted] length:(NSUInteger) range.length encoding:NSUTF8StringEncoding]; 252 | if (portNumberStr != nil) { 253 | portNumber = [portNumberStr intValue]; 254 | if ( (isHTTP && (portNumber == 80)) || (isHTTPS && (portNumber == 443)) ) { 255 | // -1 and +1 to account for the leading ":" 256 | [urlData replaceBytesInRange:NSMakeRange((NSUInteger) range.location + (NSUInteger) bytesInserted - 1, (NSUInteger) range.length + 1) withBytes:NULL length:0]; 257 | bytesInserted -= (range.length + 1); 258 | } 259 | } 260 | } 261 | return bytesInserted; 262 | } 263 | 264 | #pragma mark * Other request canonicalization 265 | 266 | /*! Canonicalise the request headers. 267 | * \param request The request to canonicalise. 268 | */ 269 | 270 | static void CanonicaliseHeaders(NSMutableURLRequest * request) 271 | { 272 | // If there's no content type and the request is a POST with a body, add a default 273 | // content type of "application/x-www-form-urlencoded". 274 | 275 | if ( ([request valueForHTTPHeaderField:@"Content-Type"] == nil) 276 | && ([[request HTTPMethod] caseInsensitiveCompare:@"POST"] == NSOrderedSame) 277 | && (([request HTTPBody] != nil) || ([request HTTPBodyStream] != nil)) ) { 278 | [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; 279 | } 280 | 281 | // If there's no "Accept" header, add a default. 282 | 283 | if ([request valueForHTTPHeaderField:@"Accept"] == nil) { 284 | [request setValue:@"*/*" forHTTPHeaderField:@"Accept"]; 285 | } 286 | 287 | // If there's not "Accept-Encoding" header, add a default. 288 | 289 | if ([request valueForHTTPHeaderField:@"Accept-Encoding"] == nil) { 290 | [request setValue:@"gzip, deflate" forHTTPHeaderField:@"Accept-Encoding"]; 291 | } 292 | 293 | // If there's not an "Accept-Language" headre, add a default. This is quite bogus; ideally we 294 | // should derive the correct "Accept-Language" value from the langauge that the app is running 295 | // in. However, that's quite difficult to get right, so rather than show some general purpose 296 | // code that might fail in some circumstances, I've decided to just hardwire US English. 297 | // If you use this code in your own app you can customise it as you see fit. One option might be 298 | // to base this value on -[NSBundle preferredLocalizations], so that the web page comes back in 299 | // the language that the app is running in. 300 | 301 | if ([request valueForHTTPHeaderField:@"Accept-Language"] == nil) { 302 | [request setValue:@"en-us" forHTTPHeaderField:@"Accept-Language"]; 303 | } 304 | } 305 | 306 | #pragma mark * API 307 | 308 | /*! Returns a canonical form of the supplied request. 309 | * \details The Foundation URL loading system needs to be able to canonicalize URL 310 | * requests for various reasons (for example, to look for cache hits). The default 311 | * HTTP/HTTPS protocol has a complex chunk of code to perform this function. Unfortunately 312 | * there's no way for third party code to access this. Instead, we have to reimplement 313 | * it all ourselves. This is split off into a separate file to emphasise that this 314 | * is standard boilerplate that you probably don't need to look at. 315 | * 316 | * IMPORTANT: While you can take most of this code as read, you might want to tweak 317 | * the handling of the "Accept-Language" in the CanonicaliseHeaders routine. 318 | * \param request The request to canonicalise; must not be nil. 319 | * \returns The canonical request; should never be nil. 320 | */ 321 | 322 | 323 | extern NSMutableURLRequest * CanonicalRequestForRequest(NSURLRequest *request) 324 | { 325 | NSMutableURLRequest * result; 326 | NSString * scheme; 327 | 328 | assert(request != nil); 329 | 330 | // Make a mutable copy of the request. 331 | 332 | result = [request mutableCopy]; 333 | 334 | // First up check that we're dealing with HTTP or HTTPS. If not, do nothing (why were we 335 | // we even called?). 336 | 337 | scheme = [[[request URL] scheme] lowercaseString]; 338 | assert(scheme != nil); 339 | 340 | if ( ! [scheme isEqual:@"http" ] && ! [scheme isEqual:@"https"]) { 341 | assert(NO); 342 | } else { 343 | CFIndex bytesInserted; 344 | NSURL * requestURL; 345 | NSMutableData * urlData; 346 | static const CanonicalRequestStepFunction kStepFunctions[] = { 347 | FixPostSchemeSeparator, 348 | LowercaseScheme, 349 | LowercaseHost, 350 | FixEmptyHost, 351 | // DeleteDefaultPort, -- The built-in canonicalizer has stopped doing this, so we don't do it either. 352 | FixEmptyPath 353 | }; 354 | size_t stepIndex; 355 | size_t stepCount; 356 | 357 | // Canonicalise the URL by executing each of our step functions. 358 | 359 | bytesInserted = kCFNotFound; 360 | urlData = nil; 361 | requestURL = [request URL]; 362 | assert(requestURL != nil); 363 | 364 | stepCount = sizeof(kStepFunctions) / sizeof(*kStepFunctions); 365 | for (stepIndex = 0; stepIndex < stepCount; stepIndex++) { 366 | 367 | // If we don't have valid URL data, create it from the URL. 368 | 369 | assert(requestURL != nil); 370 | if (bytesInserted == kCFNotFound) { 371 | NSData * urlDataImmutable; 372 | 373 | urlDataImmutable = CFBridgingRelease( CFURLCreateData(NULL, (CFURLRef) requestURL, kCFStringEncodingUTF8, true) ); 374 | assert(urlDataImmutable != nil); 375 | 376 | urlData = [urlDataImmutable mutableCopy]; 377 | assert(urlData != nil); 378 | 379 | bytesInserted = 0; 380 | } 381 | assert(urlData != nil); 382 | 383 | // Run the step. 384 | 385 | bytesInserted = kStepFunctions[stepIndex](requestURL, urlData, bytesInserted); 386 | 387 | // Note: The following logging is useful when debugging this code. Change the 388 | // if expression to YES to enable it. 389 | 390 | if (NO) { 391 | fprintf(stderr, " [%zu] %.*s\n", stepIndex, (int) [urlData length], (const char *) [urlData bytes]); 392 | } 393 | 394 | // If the step invalidated our URL (or we're on the last step, whereupon we'll need 395 | // the URL outside of the loop), recreate the URL from the URL data. 396 | 397 | if ( (bytesInserted == kCFNotFound) || ((stepIndex + 1) == stepCount) ) { 398 | requestURL = CFBridgingRelease( CFURLCreateWithBytes(NULL, [urlData bytes], (CFIndex) [urlData length], kCFStringEncodingUTF8, NULL) ); 399 | assert(requestURL != nil); 400 | 401 | urlData = nil; 402 | } 403 | } 404 | 405 | [result setURL:requestURL]; 406 | 407 | // Canonicalise the headers. 408 | 409 | CanonicaliseHeaders(result); 410 | } 411 | 412 | return result; 413 | } 414 | 415 | - (NSURLRequest *)cdz_canonicalRequest{ 416 | return CanonicalRequestForRequest(self); 417 | } 418 | 419 | @end 420 | -------------------------------------------------------------------------------- /HttpProxyDemo/UIWebViewVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIWebView-VC.swift 3 | // HttpProxyDemo 4 | // 5 | // Created by Nemo on 2018/9/24. 6 | // Copyright © 2018年 Nemo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class UIWebViewVC: UIViewController { 12 | @IBOutlet weak var webView: UIWebView! 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | let host = ViewController.host 17 | let port = ViewController.port 18 | let url = URL(string: "http://ip111.cn/") 19 | let request = URLRequest(url: url!) 20 | HttpProxyProtocol.start(proxyConfig: (host, port)) 21 | if (HttpProxyProtocol.canInit(with: request)){ 22 | webView.loadRequest(request) 23 | } 24 | } 25 | 26 | override func viewDidDisappear(_ animated: Bool) { 27 | HttpProxyProtocol.stop() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /HttpProxyDemo/URLSessionConfiguration+HttpProxy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLSessionConfiguration+HttpProxy.swift 3 | // HttpProxyDemo 4 | // 5 | // Created by Nemo on 2021/5/5. 6 | // Copyright © 2021 Nemo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | fileprivate let httpProxyKey = kCFNetworkProxiesHTTPEnable as String 12 | fileprivate let httpHostKey = kCFNetworkProxiesHTTPProxy as String 13 | fileprivate let httpPortKey = kCFNetworkProxiesHTTPPort as String 14 | fileprivate let httpsProxyKey = "HTTPSEnable" 15 | fileprivate let httpsHostKey = "HTTPSProxy" 16 | fileprivate let httpsPortKey = "HTTPSPort" 17 | 18 | typealias HttpProxyConfig = (host: String, port: Int) 19 | let httpSchemes = ["http", "https"] 20 | 21 | extension URLSessionConfiguration { 22 | func addProxyConfig(_ config: HttpProxyConfig) { 23 | let (host, port) = config 24 | let proxyDict: [String: Any] = [httpProxyKey: true, 25 | httpHostKey: host, 26 | httpPortKey: port, 27 | httpsProxyKey: true, 28 | httpsHostKey: host, 29 | httpsPortKey: port] 30 | connectionProxyDictionary = proxyDict 31 | } 32 | 33 | func hasProxyConfig(_ config: HttpProxyConfig) -> Bool { 34 | guard let proxyDic = connectionProxyDictionary, 35 | let host = proxyDic[httpHostKey] as? String, 36 | let port = proxyDic[httpPortKey] as? Int, 37 | config == (host, port) 38 | else { 39 | return false 40 | } 41 | return true 42 | } 43 | } 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /HttpProxyDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // HttpProxyDemo 4 | // 5 | // Created by Nemo on 2018/9/23. 6 | // Copyright © 2018年 Nemo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | static var host = "" 13 | static var port = 0 14 | 15 | @IBAction func changeHsot(_ sender: UITextField) { 16 | type(of: self).host = hostTextField.text ?? "" 17 | } 18 | @IBAction func changePort(_ sender: UITextField) { 19 | type(of: self).port = Int(portTextField.text ?? "") ?? 0 20 | } 21 | @IBOutlet weak var hostTextField: UITextField! 22 | @IBOutlet weak var portTextField: UITextField! 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | type(of: self).host = hostTextField.text ?? "" 26 | type(of: self).port = Int(portTextField.text ?? "") ?? 0 27 | } 28 | 29 | 30 | 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /HttpProxyDemo/WKWebView1VC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WkWebView1-VC.swift 3 | // HttpProxyDemo 4 | // 5 | // Created by Nemo on 2018/9/24. 6 | // Copyright © 2018年 Nemo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import WebKit 11 | 12 | class WKWebView1VC: UIViewController { 13 | 14 | @IBOutlet weak var webView: WKWebView! 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | let host = ViewController.host 18 | let port = ViewController.port 19 | let url = URL(string: "http://ip111.cn/") 20 | let request = URLRequest(url: url!) 21 | HttpProxyProtocol.webKitSupport = true 22 | HttpProxyProtocol.start(proxyConfig: (host, port)) 23 | if (HttpProxyProtocol.canInit(with: request)){ 24 | webView.load(request) 25 | } 26 | } 27 | 28 | deinit { 29 | HttpProxyProtocol.webKitSupport = false 30 | HttpProxyProtocol.stop() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /HttpProxyDemo/WKWebView2VC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebview2+VC.swift 3 | // HttpProxyDemo 4 | // 5 | // Created by Nemo on 2018/9/24. 6 | // Copyright © 2018年 Nemo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import WebKit 11 | 12 | class WKWebView2VC: UIViewController { 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | let host = ViewController.host 16 | let port = ViewController.port 17 | let url = URL(string: "http://ip111.cn/") 18 | let request = URLRequest(url: url!) 19 | let config = WKWebViewConfiguration() 20 | config.addProxyConfig((host, port)) 21 | let webView = WKWebView(frame: view.frame, configuration: config) 22 | view.addSubview(webView) 23 | webView.load(request) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iOSHttpProxyDemo 2 | [iOS设置代理(Proxy)方案总结](https://nemocdz.github.io/post/ios-%E8%AE%BE%E7%BD%AE%E4%BB%A3%E7%90%86proxy%E6%96%B9%E6%A1%88%E6%80%BB%E7%BB%93/) 3 | --------------------------------------------------------------------------------