標籤:
前言
在多數情況下,我們做的網路請求是返回200狀態代碼的,但也有返回302的時候,比如使用基於Oauth2認證協議的API時,在認證階段,需要提供一個回調地址,當使用者授權後,伺服器會返回一個302 Response,Response Header中會一個Location欄位,包含了我們的回調地址,同時會有一個Code參數。我們在程式中該如何處理這個請求,並拿到這個Code參數呢。下面由我來為大家講解下幾種方式的做法,各取所需。
假設您知道並使用過Oauth2認證協議
(一)UIWebView控制項
這是最常見的做法,但是UIWebView是無法攔截302請求的,只能等待整個流程完成回到回調地址時,我們在webView控制項的webViewDidFinishLoad回調方法處理資料。
首先,我們需要讓ViewController類繼承UIWebViewDelegate協議,然後實現webViewDidFinishLoad方法:
class WebLoginViewController: UIViewController,UIWebViewDelegate { @IBOutlet var webView: UIWebView! override func viewDidLoad() { super.viewDidLoad() webView.scalesPageToFit = true webView.delegate = self } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } func webViewDidFinishLoad(webView: UIWebView) { //處理資料 }}
接著在啟動時給webview一個載入地址,先載入指定的登陸頁面:
override func viewDidLoad() { super.viewDidLoad() webView.scalesPageToFit = true webView.delegate = self let url = "https://www.oschina.net/xxxxxx" //程式啟動後,讓webview載入 OSChina的驗證登陸介面 webView.loadRequest(NSURLRequest(URL: NSURL(string: url)!))}
當整個請求鏈完成後,我們在DidFinishLoad中通過判斷請求的url,來確認是否已經回到了回調地址上
func webViewDidFinishLoad(webView: UIWebView) { var url = webView.request?.URL!.absoluteString if url!.hasPrefix("回調地址url") { //從一個url字串中拿到Code值 let code = url!.GetCodeL() println("code = \(code)") //拿到Code後,可以開始請求Token了 }}
很顯然,這種方法還需要等待webView來處理回調地址的請求,而這個請求對我們的程式來說是完全沒有必要的。
我們要做的是攔截 302!
(二)基於NSURLConnection來設定攔截
在很多教程中都提到了NSURLConnection,它可以發送一個請求,比如:
let request = NSURLRequest(URL: NSURL(string: "http://devonios.com")!)NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue()) { (response, data,error) -> Void in //處理返回資料}
如果要發送POST的話,需要使用可編輯的
NSMutableURLRequest類(它是繼承NSURLRequest類的)。
我們需要的攔截效果,其實就是要給NSURLConnection設定一個delegate,提供一個事件發生時的回調方法。
NSURLConnection類有一個建構函式:
init?(request request: NSURLRequest, delegate delegate: AnyObject?)
第二個參數就是我們需要設定的delegate。對應的delegate是:
NSURLConnectionDataDelegate。
我們在Dask中可以看到它有這些東西:
開始寫代碼了:
class LoginViewController: UIViewController,NSURLConnectionDataDelegate { func connection(){ //建立一個可以編輯的NSURLRequest var mutableRequest = NSMutableURLRequest(URL: NSURL(string: "http://devonios.com")!) mutableRequest.HTTPMethod = "POST" //設定POST請求的表單資料 mutableRequest.HTTPBody = paramString.dataUsingEncoding(NSStringEncoding.allZeros, allowLossyConversion: true) //使用建構函式方法建立一個NSURLConnection的執行個體 var connection:NSURLConnection = NSURLConnection(request: mutableRequest, delegate: self)! connection.start() } //處理重新導向請求的方法 func connection(connection: NSURLConnection, willSendRequest request: NSURLRequest, redirectResponse response: NSURLResponse?) -> NSURLRequest? { if let r = response{ //當前重新導向請求的url,包含了Code參數 let requesturl = request.URLString //得到Code,由於Code參數設定了屬性觀察器,所以當Code被賦值時,會自動去擷取Token self.code = requesturl.GetCode() //因為已經拿到Code了,所以攔截掉當前這個重新導向請求,直接返回nil return nil } return request } //整個請求完成後,即攔截到302後,不再請求了就返回這裡 func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) { if (某些判斷條件){ self.navigationController?.popViewControllerAnimated(true) } }}
(三)基於NSURLSession類來設定攔截
NSURLSession是IOS 7中開始出現的全新的網路介面類,和NSURLConnection類似,同樣需要設定delegate。
class MyRequestController:NSObject,NSURLSessionTaskDelegate { let session:NSURLSession? init(){ let sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration() session = NSURLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil) } deinit{ session!.invalidateAndCancel() } //處理重新導向請求,直接使用nil來取消重新導向請求 func URLSession(session: NSURLSession, task: NSURLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: (NSURLRequest!) -> Void) { completionHandler(nil) } func sendRequest() { var URL = NSURL(string: "http://devonios.com") let request = NSMutableURLRequest(URL: URL!) request.HTTPMethod = "POST" request.HTTPBody = paramString.dataUsingEncoding(NSStringEncoding.allZeros, allowLossyConversion: true) let task = session!.dataTaskWithRequest(request, completionHandler: { (data : NSData!, response : NSURLResponse!, error : NSError!) -> Void in //由於攔截了302,設定了completionHandler參數為nil,所以忽略了重新導向請求,這裡返回的Response就是包含302狀態代碼的Response了。 let resp:NSHTTPURLResponse = response as! NSHTTPURLResponse println("包含302狀態的Response Header欄位 : \(resp.allHeaderFields)") }) task.resume() }}
目前為止,我們通過為NSURLConnection或者NSURLSession設定一個Delegate,通過回調方法來攔截(其實就是返回個nil)。
但是在一個項目中,我們通常會使用Alamofire這種第三庫來操作網路請求,我要是再自己再重新寫個請求,那豈不是很麻煩?
(四)完善Alamofire庫,實現攔截302請求
Alamofire啥就不多說了,分析它的代碼可以發現,是使用NSURLSession來實現請求的。
既然如此,那麼我們就要找到NSURLSession,為它設定delegate,然後重寫willPerformHttpRedirection。
在Alamofire.swift檔案中,request方法是暴露給我們調用的,Manager類的sharedInstance屬性來管理自身對象。
public func request(method: Method, URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL) -> Request { return Manager.sharedInstance.request(method, URLString, parameters: parameters, encoding: encoding)}
Manager.sharedInstance屬性的實現,定義了要求標頭資訊,然後調用建構函式
public static let sharedInstance: Manager = { let configuration: NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration() configuration.HTTPAdditionalHeaders = Manager.defaultHTTPHeaders return Manager(configuration: configuration) }()
建構函式,我們要找的NSURLSession就在這裡,它預設已經有了一個Class(SessionDelegate)來實現相應的delegate了:
required public init(configuration: NSURLSessionConfiguration? = nil) { self.delegate = SessionDelegate() self.session = NSURLSession(configuration: configuration, delegate: delegate, delegateQueue:nil) self.delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in if let strongSelf = self { strongSelf.backgroundCompletionHandler?() } }}
這個建構函式看上去動不了什麼,關鍵還在SessionDelegate類,它實現了所有了NSURLSessionDelegate:
public final class SessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate { public var taskWillPerformHTTPRedirection: ((NSURLSession, NSURLSessionTask, NSHTTPURLResponse,NSURLRequest) -> NSURLRequest?)? public func URLSession(session: NSURLSession, task: NSURLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: ((NSURLRequest!) -> Void)) { var redirectRequest: NSURLRequest? = request if taskWillPerformHTTPRedirection != nil { redirectRequest = taskWillPerformHTTPRedirection!(session, task, response, request) } completionHandler(redirectRequest) }}
仔細觀察會發現,有一個public的 變數(var)taskWillPerformHTTPRedirection、有一個重寫方法(willperformHTTPRedirection )。
從這個方法中可以看出,它期望我們給taskWillPerformHTTPRedirection變數傳一個自訂方法,如果我們賦值了,它就運行我們的自訂方法。
我們要給taskWillPerformHTTPRedirection變數賦值,參數是一個方法。
在Manager類中加入下面代碼:
public typealias TaskWillRedirectAction = ((NSURLSession, NSURLSessionTask, NSHTTPURLResponse,NSURLRequest) -> NSURLRequest?)public func setTaskWillRedirectAction(action:TaskWillRedirectAction){ self.delegate.taskWillPerformHTTPRedirection = action}
對Alamofire庫的修改就這樣可以了!
我們需要在發送網路請求前,先調用setTaskWillRedirectAction方法,傳入我們的自訂方法。
使用方法:
var manager = Manager.sharedInstancemanager.setTaskWillRedirectAction { (session, task, response, request) -> NSURLRequest? in return nil}manager.request(Method.POST, url, parameters: authparam.toDictionary(), encoding: ParameterEncoding.URL).response { (request, response, data, err) -> Void in //由於上面的setTaskWillRedirectAction方法返回nil,所以在處理NSURLSessionDataDelegate的重寫方法時,complectionHandler方法參數為nil,也就實現了攔截! println(response?.allHeaderFields["Location"])}
注意,這裡需要先從sharedInstance屬性中拿到一個Manager對象,然後再用這個對象設定攔截的回調方法,再發送請求。
如果您還是使用Alamofire.request來發送請求的話,就沒有作用了,因為你又重新建立了個Manager類對象。
參考資料
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/RequestChanges.html
http://stackoverflow.com/questions/1446509/handling-redirects-correctly-with-nsurlconnection
tips:
本文由wp2osc匯入,原文連結:http://devonios.com/intercept-302-request.html
由於OSChina的OpenAPI在處理content參數時會自動過濾img標籤,所以無法顯示圖片,詳見。
IOS攔截重新導向請求(302)的幾種方式