閱讀784 返回首頁    go 技術社區[雲棲]


單IP多HTTPS域名場景下的解決方案__最佳實踐_HTTPDNS-阿裏雲

1.背景說明

在搭建支持HTTPS的前端代理服務器時候,通常會遇到讓人頭痛的證書問題。根據HTTPS的工作原理,瀏覽器在訪問一個HTTPS站點時,先與服務器建立SSL連接,建立連接的第一步就是請求服務器的證書。而服務器在發送證書的時候,是不知道瀏覽器訪問的是哪個域名的,所以不能根據不同域名發送不同的證書。

SNI(Server Name Indication)是為了解決一個服務器使用多個域名和證書的SSL/TLS擴展。它的工作原理如下:

  1. 在連接到服務器建立SSL鏈接之前先發送要訪問站點的域名(Hostname)。
  2. 服務器根據這個域名返回一個合適的證書。

目前,大多數操作係統和瀏覽器都已經很好地支持SNI擴展,OpenSSL 0.9.8也已經內置這一功能。

上述過程中,當客戶端使用HTTPDNS解析域名時,請求URL中的host會被替換成HTTPDNS解析出來的IP,導致服務器獲取到的域名為解析後的IP,無法找到匹配的證書,隻能返回默認的證書或者不返回,所以會出現SSL/TLS握手不成功的錯誤。

比如當你需要通過HTTPS訪問CDN資源時,CDN的站點往往服務了很多的域名,所以需要通過SNI指定具體的域名證書進行通信。

2 解決方案

針對以上問題,可以采用如下方案解決:hook HTTPS訪問前SSL連接過程,根據網絡請求頭部域中的HOST信息,設置SSL連接PeerHost的值,再根據服務器返回的證書執行驗證過程。

下麵分別列出Android和iOS平台的示例代碼。

2.1 Android示例

HTTPDNS Android Demo Github中針對HttpsURLConnection接口,提供了在SNI業務場景下使用HTTPDNS的示例代碼。

定製SSLSocketFactory,在createSocket時替換為HTTPDNS的IP,並進行SNI/HostNameVerify配置。

  1. class TlsSniSocketFactory extends SSLSocketFactory {
  2. private final String TAG = TlsSniSocketFactory.class.getSimpleName();
  3. HostnameVerifier hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
  4. private HttpsURLConnection conn;
  5. public TlsSniSocketFactory(HttpsURLConnection conn) {
  6. this.conn = conn;
  7. }
  8. @Override
  9. public Socket createSocket() throws IOException {
  10. return null;
  11. }
  12. @Override
  13. public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
  14. return null;
  15. }
  16. @Override
  17. public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
  18. return null;
  19. }
  20. @Override
  21. public Socket createSocket(InetAddress host, int port) throws IOException {
  22. return null;
  23. }
  24. @Override
  25. public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
  26. return null;
  27. }
  28. // TLS layer
  29. @Override
  30. public String[] getDefaultCipherSuites() {
  31. return new String[0];
  32. }
  33. @Override
  34. public String[] getSupportedCipherSuites() {
  35. return new String[0];
  36. }
  37. @Override
  38. public Socket createSocket(Socket plainSocket, String host, int port, boolean autoClose) throws IOException {
  39. String peerHost = this.conn.getRequestProperty("Host");
  40. if (peerHost == null)
  41. peerHost = host;
  42. Log.i(TAG, "customized createSocket. host: " + peerHost);
  43. InetAddress address = plainSocket.getInetAddress();
  44. if (autoClose) {
  45. // we don't need the plainSocket
  46. plainSocket.close();
  47. }
  48. // create and connect SSL socket, but don't do hostname/certificate verification yet
  49. SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0);
  50. SSLSocket ssl = (SSLSocket) sslSocketFactory.createSocket(address, port);
  51. // enable TLSv1.1/1.2 if available
  52. ssl.setEnabledProtocols(ssl.getSupportedProtocols());
  53. // set up SNI before the handshake
  54. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
  55. Log.i(TAG, "Setting SNI hostname");
  56. sslSocketFactory.setHostname(ssl, peerHost);
  57. } else {
  58. Log.d(TAG, "No documented SNI support on Android <4.2, trying with reflection");
  59. try {
  60. java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class);
  61. setHostnameMethod.invoke(ssl, peerHost);
  62. } catch (Exception e) {
  63. Log.w(TAG, "SNI not useable", e);
  64. }
  65. }
  66. // verify hostname and certificate
  67. SSLSession session = ssl.getSession();
  68. if (!hostnameVerifier.verify(peerHost, session))
  69. throw new SSLPeerUnverifiedException("Cannot verify hostname: " + peerHost);
  70. Log.i(TAG, "Established " + session.getProtocol() + " connection with " + session.getPeerHost() +
  71. " using " + session.getCipherSuite());
  72. return ssl;
  73. }
  74. }

對於需要設置SNI的站點,通常需要重定向請求,示例中也給出了重定向請求的處理方法。

  1. public void recursiveRequest(String path, String reffer) {
  2. URL url = null;
  3. try {
  4. url = new URL(path);
  5. conn = (HttpsURLConnection) url.openConnection();
  6. // 同步接口獲取IP
  7. String ip = httpdns.getIpByHostAsync(url.getHost());
  8. if (ip != null) {
  9. // 通過HTTPDNS獲取IP成功,進行URL替換和HOST頭設置
  10. Log.d(TAG, "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");
  11. String newUrl = path.replaceFirst(url.getHost(), ip);
  12. conn = (HttpsURLConnection) new URL(newUrl).openConnection();
  13. // 設置HTTP請求頭Host域
  14. conn.setRequestProperty("Host", url.getHost());
  15. }
  16. conn.setConnectTimeout(30000);
  17. conn.setReadTimeout(30000);
  18. conn.setInstanceFollowRedirects(false);
  19. TlsSniSocketFactory sslSocketFactory = new TlsSniSocketFactory(conn);
  20. conn.setSSLSocketFactory(sslSocketFactory);
  21. conn.setHostnameVerifier(new HostnameVerifier() {
  22. /*
  23. * 關於這個接口的說明,官方有文檔描述:
  24. * This is an extended verification option that implementers can provide.
  25. * It is to be used during a handshake if the URL's hostname does not match the
  26. * peer's identification hostname.
  27. *
  28. * 使用HTTPDNS後URL裏設置的hostname不是遠程的主機名(如:m.taobao.com),與證書頒發的域不匹配,
  29. * Android HttpsURLConnection提供了回調接口讓用戶來處理這種定製化場景。
  30. * 在確認HTTPDNS返回的源站IP與Session攜帶的IP信息一致後,您可以在回調方法中將待驗證域名替換為原來的真實域名進行驗證。
  31. *
  32. */
  33. @Override
  34. public boolean verify(String hostname, SSLSession session) {
  35. String host = conn.getRequestProperty("Host");
  36. if (null == host) {
  37. host = conn.getURL().getHost();
  38. }
  39. return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
  40. }
  41. });
  42. int code = conn.getResponseCode();// Network block
  43. if (needRedirect(code)) {
  44. //臨時重定向和永久重定向location的大小寫有區分
  45. String location = conn.getHeaderField("Location");
  46. if (location == null) {
  47. location = conn.getHeaderField("location");
  48. }
  49. if (!(location.startsWith("https://") || location
  50. .startsWith("https://"))) {
  51. //某些時候會省略host,隻返回後麵的path,所以需要補全url
  52. URL originalUrl = new URL(path);
  53. location = originalUrl.getProtocol() + "://"
  54. + originalUrl.getHost() + location;
  55. }
  56. recursiveRequest(location, path);
  57. } else {
  58. // redirect finish.
  59. DataInputStream dis = new DataInputStream(conn.getInputStream());
  60. int len;
  61. byte[] buff = new byte[4096];
  62. StringBuilder response = new StringBuilder();
  63. while ((len = dis.read(buff)) != -1) {
  64. response.append(new String(buff, 0, len));
  65. }
  66. Log.d(TAG, "Response: " + response.toString());
  67. }
  68. } catch (MalformedURLException e) {
  69. Log.w(TAG, "recursiveRequest MalformedURLException");
  70. } catch (IOException e) {
  71. Log.w(TAG, "recursiveRequest IOException");
  72. } catch (Exception e) {
  73. Log.w(TAG, "unknow exception");
  74. } finally {
  75. if (conn != null) {
  76. conn.disconnect();
  77. }
  78. }
  79. }
  80. private boolean needRedirect(int code) {
  81. return code >= 300 && code < 400;
  82. }

2.2 iOS示例

由於iOS係統並沒有提供設置SNI的上層接口(NSURLConnection/NSURLSession),因此在HTTPDNS iOS Demo Github中,我們使用NSURLProtocol攔截網絡請求,然後使用CFHTTPMessageRef創建NSInputStream實例進行Socket通信,並設置其kCFStreamSSLPeerName的值。

需要注意的是,使用NSURLProtocol攔截NSURLSession發起的POST請求時,HTTPBody為空。解決方案有兩個:

  1. 使用NSURLConnection發POST請求。
  2. 先將HTTPBody放入HTTP Header field中,然後在NSURLProtocol中再取出來,Demo中主要演示該方案。

部分代碼如下:

在網絡請求前注冊NSURLProtocol子類,在示例的SNIViewController.m中。

  1. // 注冊攔截請求的NSURLProtocol
  2. [NSURLProtocol registerClass:[CFHttpMessageURLProtocol class]];
  3. // 初始化HTTPDNS
  4. HttpDnsService *httpdns = [HttpDnsService sharedInstance];
  5. // 需要設置SNI的URL
  6. NSString *originalUrl = @"your url";
  7. NSURL *url = [NSURL URLWithString:originalUrl];
  8. self.request = [[NSMutableURLRequest alloc] initWithURL:url];
  9. NSString *ip = [httpdns getIpByHostAsync:url.host];
  10. // 通過HTTPDNS獲取IP成功,進行URL替換和HOST頭設置
  11. if (ip) {
  12. NSLog(@"Get IP from HTTPDNS Successfully!");
  13. NSRange hostFirstRange = [originalUrl rangeOfString:url.host];
  14. if (NSNotFound != hostFirstRange.location) {
  15. NSString *newUrl = [originalUrl stringByReplacingCharactersInRange:hostFirstRange withString:ip];
  16. self.request.URL = [NSURL URLWithString:newUrl];
  17. [_request setValue:url.host forHTTPHeaderField:@"host"];
  18. }
  19. }
  20. // NSURLConnection例子
  21. [[NSURLConnection alloc] initWithRequest:_request delegate:self startImmediately:YES];
  22. // NSURLSession例子
  23. NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
  24. NSArray *protocolArray = @[ [CFHttpMessageURLProtocol class] ];
  25. configuration.protocolClasses = protocolArray;
  26. NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
  27. NSURLSessionTask *task = [session dataTaskWithRequest:_request];
  28. [task resume];
  29. // 注*:使用NSURLProtocol攔截NSURLSession發起的POST請求時,HTTPBody為空。
  30. // 解決方案有兩個:1. 使用NSURLConnection發POST請求。
  31. // 2. 先將HTTPBody放入HTTP Header field中,然後在NSURLProtocol中再取出來。
  32. // 下麵主要演示第二種解決方案
  33. // NSString *postStr = [NSString stringWithFormat:@"param1=%@&param2=%@", @"val1", @"val2"];
  34. // [_request addValue:postStr forHTTPHeaderField:@"originalBody"];
  35. // _request.HTTPMethod = @"POST";
  36. // NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
  37. // NSArray *protocolArray = @[ [CFHttpMessageURLProtocol class] ];
  38. // configuration.protocolClasses = protocolArray;
  39. // NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
  40. // NSURLSessionTask *task = [session dataTaskWithRequest:_request];
  41. // [task resume];

在NSURLProtocol子類中攔截網絡請求,在示例的CFHttpMessageURLProtocol.m中。

  1. + (BOOL)canInitWithRequest:(NSURLRequest *)request {
  2. /* 防止無限循環,因為一個請求在被攔截處理過程中,也會發起一個請求,這樣又會走到這裏,如果不進行處理,就會造成無限循環 */
  3. if ([NSURLProtocol propertyForKey:protocolKey inRequest:request]) {
  4. return NO;
  5. }
  6. NSString *url = request.URL.absoluteString;
  7. // 如果url以https開頭,則進行攔截處理,否則不處理
  8. if ([url hasPrefix:@"https"]) {
  9. return YES;
  10. }
  11. return NO;
  12. }

使用CFHTTPMessageRef創建NSInputStream,並設置kCFStreamSSLPeerName,重新發起請求。

  1. /**
  2. * 開始加載,在該方法中,加載一個請求
  3. */
  4. - (void)startLoading {
  5. NSMutableURLRequest *request = [self.request mutableCopy];
  6. // 表示該請求已經被處理,防止無限循環
  7. [NSURLProtocol setProperty:@(YES) forKey:protocolKey inRequest:request];
  8. curRequest = request;
  9. [self startRequest];
  10. }
  11. /**
  12. * 取消請求
  13. */
  14. - (void)stopLoading {
  15. if (inputStream.streamStatus == NSStreamStatusOpen) {
  16. [inputStream removeFromRunLoop:curRunLoop forMode:NSRunLoopCommonModes];
  17. [inputStream setDelegate:nil];
  18. [inputStream close];
  19. }
  20. [self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:@"stop loading" code:-1 userInfo:nil]];
  21. }
  22. /**
  23. * 使用CFHTTPMessage轉發請求
  24. */
  25. - (void)startRequest {
  26. // 原請求的header信息
  27. NSDictionary *headFields = curRequest.allHTTPHeaderFields;
  28. // 添加http post請求所附帶的數據
  29. CFStringRef requestBody = CFSTR("");
  30. CFDataRef bodyData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, requestBody, kCFStringEncodingUTF8, 0);
  31. if (curRequest.HTTPBody) {
  32. bodyData = (__bridge_retained CFDataRef) curRequest.HTTPBody;
  33. } else if (headFields[@"originalBody"]) {
  34. // 使用NSURLSession發POST請求時,將原始HTTPBody從header中取出
  35. bodyData = (__bridge_retained CFDataRef) [headFields[@"originalBody"] dataUsingEncoding:NSUTF8StringEncoding];
  36. }
  37. CFStringRef url = (__bridge CFStringRef) [curRequest.URL absoluteString];
  38. CFURLRef requestURL = CFURLCreateWithString(kCFAllocatorDefault, url, NULL);
  39. // 原請求所使用的方法,GET或POST
  40. CFStringRef requestMethod = (__bridge_retained CFStringRef) curRequest.HTTPMethod;
  41. // 根據請求的url、方法、版本創建CFHTTPMessageRef對象
  42. CFHTTPMessageRef cfrequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, requestURL, kCFHTTPVersion1_1);
  43. CFHTTPMessageSetBody(cfrequest, bodyData);
  44. // copy原請求的header信息
  45. for (NSString *header in headFields) {
  46. if (![header isEqualToString:@"originalBody"]) {
  47. // 不包含POST請求時存放在header的body信息
  48. CFStringRef requestHeader = (__bridge CFStringRef) header;
  49. CFStringRef requestHeaderValue = (__bridge CFStringRef) [headFields valueForKey:header];
  50. CFHTTPMessageSetHeaderFieldValue(cfrequest, requestHeader, requestHeaderValue);
  51. }
  52. }
  53. // 創建CFHTTPMessage對象的輸入流
  54. CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, cfrequest);
  55. inputStream = (__bridge_transfer NSInputStream *) readStream;
  56. // 設置SNI host信息,關鍵步驟
  57. NSString *host = [curRequest.allHTTPHeaderFields objectForKey:@"host"];
  58. if (!host) {
  59. host = curRequest.URL.host;
  60. }
  61. [inputStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey];
  62. NSDictionary *sslProperties = [[NSDictionary alloc] initWithObjectsAndKeys:
  63. host, (__bridge id) kCFStreamSSLPeerName,
  64. nil];
  65. [inputStream setProperty:sslProperties forKey:(__bridge_transfer NSString *) kCFStreamPropertySSLSettings];
  66. [inputStream setDelegate:self];
  67. if (!curRunLoop)
  68. // 保存當前線程的runloop,這對於重定向的請求很關鍵
  69. curRunLoop = [NSRunLoop currentRunLoop];
  70. // 將請求放入當前runloop的事件隊列
  71. [inputStream scheduleInRunLoop:curRunLoop forMode:NSRunLoopCommonModes];
  72. [inputStream open];
  73. CFRelease(cfrequest);
  74. CFRelease(requestURL);
  75. CFRelease(url);
  76. cfrequest = NULL;
  77. CFRelease(bodyData);
  78. CFRelease(requestBody);
  79. CFRelease(requestMethod);
  80. }

在NSStream的回調函數中,根據不同的eventCode通知原請求。此處eventCode主要有四種:

  1. NSStreamEventOpenCompleted:連接已打開。
  2. NSStreamEventHasBytesAvailable:響應頭部已下載完整,body中字節可讀。
  3. NSStreamEventErrorOccurred:連接發生錯誤。
  4. NSStreamEventEndEncountered:連接結束。
  1. - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
  2. if (eventCode == NSStreamEventHasBytesAvailable) {
  3. CFReadStreamRef readStream = (__bridge_retained CFReadStreamRef) aStream;
  4. CFHTTPMessageRef message = (CFHTTPMessageRef) CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
  5. if (CFHTTPMessageIsHeaderComplete(message)) {
  6. // 以防response的header信息不完整
  7. UInt8 buffer[16 * 1024];
  8. UInt8 *buf = NULL;
  9. unsigned long length = 0;
  10. NSInputStream *inputstream = (NSInputStream *) aStream;
  11. NSNumber *alreadyAdded = objc_getAssociatedObject(aStream, kAnchorAlreadyAdded);
  12. if (!alreadyAdded || ![alreadyAdded boolValue]) {
  13. objc_setAssociatedObject(aStream, kAnchorAlreadyAdded, [NSNumber numberWithBool:YES], OBJC_ASSOCIATION_COPY);
  14. // 通知client已收到response,隻通知一次
  15. NSDictionary *headDict = (__bridge NSDictionary *) (CFHTTPMessageCopyAllHeaderFields(message));
  16. CFStringRef httpVersion = CFHTTPMessageCopyVersion(message);
  17. // 獲取響應頭部的狀態碼
  18. CFIndex myErrCode = CFHTTPMessageGetResponseStatusCode(message);
  19. NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:curRequest.URL statusCode:myErrCode HTTPVersion:(__bridge NSString *) httpVersion headerFields:headDict];
  20. [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
  21. // 驗證證書
  22. SecTrustRef trust = (__bridge SecTrustRef) [aStream propertyForKey:(__bridge NSString *) kCFStreamPropertySSLPeerTrust];
  23. SecTrustResultType res = kSecTrustResultInvalid;
  24. NSMutableArray *policies = [NSMutableArray array];
  25. NSString *domain = [[curRequest allHTTPHeaderFields] valueForKey:@"host"];
  26. if (domain) {
  27. [policies addObject:(__bridge_transfer id) SecPolicyCreateSSL(true, (__bridge CFStringRef) domain)];
  28. } else {
  29. [policies addObject:(__bridge_transfer id) SecPolicyCreateBasicX509()];
  30. }
  31. /*
  32. * 綁定校驗策略到服務端的證書上
  33. */
  34. SecTrustSetPolicies(trust, (__bridge CFArrayRef) policies);
  35. if (SecTrustEvaluate(trust, &res) != errSecSuccess) {
  36. [aStream removeFromRunLoop:curRunLoop forMode:NSRunLoopCommonModes];
  37. [aStream setDelegate:nil];
  38. [aStream close];
  39. [self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:@"can not evaluate the server trust" code:-1 userInfo:nil]];
  40. }
  41. if (res != kSecTrustResultProceed && res != kSecTrustResultUnspecified) {
  42. /* 證書驗證不通過,關閉input stream */
  43. [aStream removeFromRunLoop:curRunLoop forMode:NSRunLoopCommonModes];
  44. [aStream setDelegate:nil];
  45. [aStream close];
  46. [self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:@"fail to evaluate the server trust" code:-1 userInfo:nil]];
  47. } else {
  48. // 證書通過,返回數據
  49. if (![inputstream getBuffer:&buf length:&length]) {
  50. NSInteger amount = [inputstream read:buffer maxLength:sizeof(buffer)];
  51. buf = buffer;
  52. length = amount;
  53. }
  54. NSData *data = [[NSData alloc] initWithBytes:buf length:length];
  55. [self.client URLProtocol:self didLoadData:data];
  56. }
  57. } else {
  58. // 證書已驗證過,返回數據
  59. if (![inputstream getBuffer:&buf length:&length]) {
  60. NSInteger amount = [inputstream read:buffer maxLength:sizeof(buffer)];
  61. buf = buffer;
  62. length = amount;
  63. }
  64. NSData *data = [[NSData alloc] initWithBytes:buf length:length];
  65. [self.client URLProtocol:self didLoadData:data];
  66. }
  67. }
  68. } else if (eventCode == NSStreamEventErrorOccurred) {
  69. [aStream removeFromRunLoop:curRunLoop forMode:NSRunLoopCommonModes];
  70. [aStream setDelegate:nil];
  71. [aStream close];
  72. // 通知client發生錯誤了
  73. [self.client URLProtocol:self didFailWithError:[aStream streamError]];
  74. } else if (eventCode == NSStreamEventEndEncountered) {
  75. [self handleResponse];
  76. }
  77. }
  78. - (void)handleResponse {
  79. // 獲取響應頭部信息
  80. CFReadStreamRef readStream = (__bridge_retained CFReadStreamRef) inputStream;
  81. CFHTTPMessageRef message = (CFHTTPMessageRef) CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
  82. if (CFHTTPMessageIsHeaderComplete(message)) {
  83. // 確保response頭部信息完整
  84. NSDictionary *headDict = (__bridge NSDictionary *) (CFHTTPMessageCopyAllHeaderFields(message));
  85. // 獲取響應頭部的狀態碼
  86. CFIndex myErrCode = CFHTTPMessageGetResponseStatusCode(message);
  87. // 把當前請求關閉
  88. [inputStream removeFromRunLoop:curRunLoop forMode:NSRunLoopCommonModes];
  89. [inputStream setDelegate:nil];
  90. [inputStream close];
  91. if (myErrCode >= 200 && myErrCode < 300) {
  92. // 返回碼為2xx,直接通知client
  93. [self.client URLProtocolDidFinishLoading:self];
  94. } else if (myErrCode >= 300 && myErrCode < 400) {
  95. // 返回碼為3xx,需要重定向請求,繼續訪問重定向頁麵
  96. NSString *location = headDict[@"Location"];
  97. NSURL *url = [[NSURL alloc] initWithString:location];
  98. curRequest = [[NSMutableURLRequest alloc] initWithURL:url];
  99. /***********重定向通知client處理或內部處理*************/
  100. // client處理
  101. // NSURLResponse* response = [[NSURLResponse alloc] initWithURL:curRequest.URL MIMEType:headDict[@"Content-Type"] expectedContentLength:[headDict[@"Content-Length"] integerValue] textEncodingName:@"UTF8"];
  102. // [self.client URLProtocol:self wasRedirectedToRequest:curRequest redirectResponse:response];
  103. // 內部處理,將url中的host通過HTTPDNS轉換為IP,不能在startLoading線程中進行同步網絡請求,會被阻塞
  104. NSString *ip = [[HttpDnsService sharedInstance] getIpByHostAsync:url.host];
  105. if (ip) {
  106. NSLog(@"Get IP from HTTPDNS Successfully!");
  107. NSRange hostFirstRange = [location rangeOfString:url.host];
  108. if (NSNotFound != hostFirstRange.location) {
  109. NSString *newUrl = [location stringByReplacingCharactersInRange:hostFirstRange withString:ip];
  110. curRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:newUrl]];
  111. [curRequest setValue:url.host forHTTPHeaderField:@"host"];
  112. }
  113. }
  114. [self startRequest];
  115. } else {
  116. // 其他情況,直接返回響應信息給client
  117. [self.client URLProtocolDidFinishLoading:self];
  118. }
  119. } else {
  120. // 頭部信息不完整,關閉inputstream,通知client
  121. [inputStream removeFromRunLoop:curRunLoop forMode:NSRunLoopCommonModes];
  122. [inputStream setDelegate:nil];
  123. [inputStream close];
  124. [self.client URLProtocolDidFinishLoading:self];
  125. }
  126. }

立即試用HTTPDNS服務,點擊此處

最後更新:2016-11-23 16:04:14

  上一篇:go HTTPDNS域名解析場景下如何使用Cookie?__最佳實踐_HTTPDNS-阿裏雲
  下一篇:go 金融雲特性__金融雲介紹_金融雲-阿裏雲