IOS拦截重定向请求(302)的几种方式

前言

在多数情况下,我们做的网络请求是返回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.sharedInstance
manager.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标签,所以无法显示图片,详见

时间: 2024-10-13 01:42:49

IOS拦截重定向请求(302)的几种方式的相关文章

iOS UIWebview添加请求头的两种方式

1.在UIWebviewDelegate的方法中拦截request,设置request的请求头,废话不多说看代码: - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSString *urlString = [[request URL]  absoluteS

跨域请求资源的几种方式

跨域请求资源的几种方式 由于浏览器同源策略,凡是发送请求URL的协议.域名.端口三者之间任意一与当前页面地址不同即为跨域. (1)JSONP(jsonp跨域get请求) 这种方式主要是通过动态创建一个script标签,浏览器对script的资源引用没有同源限制,同时资源加载到页面后会立即执行:(创建script标签向不同域提交http请求的不会被拒绝的方法,jsonp标签的src属性是没有跨域限制的) 实际项目中JSONP通常用来获取json格式数据,这时前后端通常约定一个参数callback,

iOS开发 跳转场景的三种方式

假设A跳转到B,三种方法: 1.按住ctrl键,拖动A上的控件(比如说UIButton)到B上,弹出菜单,选择Modal.不需要写任何代码,在A上点击Button就会跳转到B 2. 按住ctrl键,拖动A上的View Controller到B上,弹出菜单,选择Modal,两个场景间自动添加连接线和图标,选中该图标,打开Storyboard Segue,identifier输入一个标识符,这里以”aaaa”为例.A里需要跳转时,执行下面的代码: 1 [self performSegueWithId

ios给NSMutableDictionary循环赋值的两种方式,在循环内初始化NSMutableDictionary和在循环外初始化NSMutableDictionary有何区别?(已解决)

NSMutableArray * arrayName = [NSMutableArray array]; NSMutableArray * array = [NSMutableArray array]; [array removeAllObjects]; for (int i = 0; i< 10; i++) { NSString * str = [NSString stringWithFormat:@"name%i",i]; [arrayName addObject:str];

android/IOS常用图片上传的两种方式

android/IOS常用图片上传的两种方式: 1.上传到服务器的文件服务器(FileServer) 原理:上传到文件服务器的方式是先在服务器端搭建文件服务器,配置好路径(url),该路径是我们待会上传图片的路径,配置成功后便通过http+post的模式上传到文件服务器,同时文件服务器将返回一个图片ID,这个ID就是图片的唯一标识,并将该ID写入数据库保存,当需要下载该图片时只需要将此ID带上即可. 两个核心问题: (1)服务端:配置FileServer,并写处理响应上传图片的代码,这个值得去网

IOS 关闭键盘 退出键盘 的5种方式(转)

IOS 关闭键盘 退出键盘 的5种方式 转自“http://blog.csdn.net/w88193363/article/details/24423635” 分类: iphone2014-04-24 17:03 2197人阅读 评论(0) 收藏 举报 1.点击编辑区以外的地方(UIView) 2.点击编辑区域以外的地方(UIControl) 3.使用制作收起键盘的按钮 4.使用判断输入字元 5.关于键盘遮蔽的问题 1,点击编辑区以外的地方(UIView) 这是一种很直觉的方法,当不再需要使用虚

iOS中保证线程安全的几种方式与性能对比

来源:景铭巴巴 链接:http://www.jianshu.com/p/938d68ed832c 一.前言 前段时间看了几个开源项目,发现他们保持线程同步的方式各不相同,有@synchronized.NSLock.dispatch_semaphore.NSCondition.pthread_mutex.OSSpinLock.后来网上查了一下,发现他们的实现机制各不相同,性能也各不一样.不好意思,我们平常使用最多的@synchronized是性能最差的.下面我们先分别介绍每个加锁方式的使用,在使用

struts2 添加请求后缀的3种方式

第一种方式在struts.xml文件中添加 <constant name="struts.action.extension" value=""></constant> 第二种方式在struts.properties中添加 第三种在web.xml的过滤器中添加 <!-- 定义Filter -->    <filter>        <!-- 指定Filter的名字,不能为空 -->        <f

IOS 多线程,线程同步的三种方式

一般情况下我们使用线程,在多个线程共同访问同一块资源.为保护线程资源的安全和线程访问的正确性. 在IOS中我们一般情况下使用以下三种线程同步代码方式: 第一种和第二种代码同步的使用方法,一般情况下我们只需要使用NSLock和NSCondition申明2个属性.然后给此属性赋对应的值.那么即可作为安全防控的线程手段. 同时也可以保证线程的资源安全. 1:NSLock方式 [xxxlock   lock] //上锁 同步代码块 [xxxlock   unlock]//解锁 2:NSCondition