美文网首页
iOS开发之网络通信(2)—— HTTP(S)

iOS开发之网络通信(2)—— HTTP(S)

作者: 看影成痴 | 来源:发表于2021-02-23 18:02 被阅读0次

    文集

    iOS开发之网络通信(1)—— 计算机网络
    iOS开发之网络通信(2)—— HTTP(S)
    iOS开发之网络通信(3)—— XML & JSON
    iOS开发之网络通信(4)—— socket
    iOS开发之网络通信(5)—— CocoaAsyncSocket
    iOS开发之网络通信(6)—— AFNetworking & Alamofire

    文章目录

    一. HTTP发展史
    二. HTTP简介
    三. HTTPS简介
    四. HTTP与HTTPS区别
    五. 代码部分
    1. 简单请求
    2. 上传数据
    3. 下载数据
    4. 后台下载

    一. HTTP发展史

    HTTP版本 年份 新增命令 连接方式 摘要
    0.9 1991 GET 短连接 初版
    1.0 1996 POST、HEAD 短连接 (非标准长连接Connection: keep-alive) 1. 可以传输文字,还能传输图像、视频、二进制文件。
    2. 加入头信息、状态码、多字符集支持、多部分发送、权限、缓存、内容编码等。
    1.1 1999 PUT、PATCH、TRACE、OPTIONS、DELETE 默认持久连接 1. 管道机制: 一个连接可同时发送多个请求(服务器端需要按顺序返回结果)。
    2. 增加Host字段,指定服务器的域名,这样服务器上支持了虚拟主机,即一台机器多个站点。
    2 2015 - 默认持久连接 1. 无论是header还是body都是二进制数据(打包成帧frame)。
    2. 在一个连接里,客户端和服务端同时发送多个请求,为了区分它们就需要对数据做标记。
    3. 支持header信息索引。
    4. 支持服务端主动推送功能。

    二. HTTP简介

    HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写, 是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。
    HTTP基于TCP/IP通信协议来传递数据 (HTML文件, 图片文件, 查询结果等), 默认端口号为80。
    HTTP使用统一资源标识符(Uniform Resource Identifiers, URI)来传输数据和建立连接。

    请求报文结构
    客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。

    请求结构.png

    请求方法
    根据 HTTP 标准,HTTP 请求可以使用多种请求方法。
    HTTP1.0 定义了三种请求方法: GET, POST 和 HEAD方法。
    HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。

    序号 方法 描述
    1 GET 请求指定的页面信息,并返回实体主体。
    2 HEAD 类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头
    3 POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。
    4 PUT 从客户端向服务器传送的数据取代指定的文档的内容。
    5 DELETE 请求服务器删除指定的页面。
    6 CONNECT HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。
    7 OPTIONS 允许客户端查看服务器的性能。
    8 TRACE 回显服务器收到的请求,主要用于测试或诊断。
    9 PATCH 是对 PUT 方法的补充,用来对已知资源进行局部更新。

    三. HTTPS简介

    HTTPS(Hypertext Transfer Protocol Secure:超文本传输安全协议)是一种透过计算机网络进行安全通信的传输协议。HTTPS 经由 HTTP 进行通信,但利用 SSL/TLS 来加密数据包。HTTPS 开发的主要目的,是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。
    HTTPS 默认工作在 TCP 协议443端口.

    工作原理:

    https工作原理.png

    四. HTTP与HTTPS区别

    1. HTTP工作在80端口; HTTPS工作在443端口。
    2. HTTP 明文传输,数据都是未加密的,安全性较差; HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。
    3. 使用 HTTPS 协议需要到 CA(Certificate Authority,数字证书认证机构) 申请证书,一般免费证书较少,因而需要一定费用。证书颁发机构如:Symantec、Comodo、GoDaddy 和 GlobalSign 等。
    4. HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。
    5. HTTPS 其实就是建构在 SSL/TLS 之上的 HTTP 协议,所以,HTTPS 比 HTTP 要更耗费服务器资源。

    五. 代码部分

    Apple默认使用HTTPS,如需开启HTTP,需在info.plist添加如下配置:

        <key>NSAppTransportSecurity</key>
        <dict>
            <key>NSAllowsArbitraryLoads</key>
            <true/>
        </dict>
    
    开启HTTP.png

    1. 简单请求

        // 创建URLRequest
        let url = URL(string: "http://www.baidu.com")
        var request = URLRequest.init(url: url!) // 默认 cachePolicy = .useProtocolCachePolicy,timeoutInterval = 60.0
        request.httpMethod = "GET"  // or HEAD、POST、PUT...
    
        // 创建URLSession
        let session = URLSession.shared
    
        // 创建请求任务
        let dataTask = session.dataTask(with: request) { (data, response, error) in
    
            NSLog("data:\(String(describing: data)), response:\(String(describing: response)), error:\(String(describing: error))")
    
        }
    
        // 发起请求
        dataTask.resume()
    

    以上请求最终会转换成如下真正的请求结构。使用Charles抓到raw数据:

    GET / HTTP/1.1
    Host: www.baidu.com
    Accept: */*
    User-Agent: KKHttpDemo/1 CFNetwork/1220.1 Darwin/20.3.0
    Accept-Language: en-us
    Accept-Encoding: gzip, deflate
    Connection: keep-alive
    
    

    上面提到了三个要点:URLRequest、URLSession 和 会话任务。

    URLRequest
    URLRequest封装了加载请求的两个基本属性:要加载的URL和用于加载该请求的策略。此外,对于HTTP和HTTPS请求,URLRequest包括HTTP方法(GET,POST等)和HTTP标头。
    URLRequest除了可设置URL及请求方法,还可定制缓存策略cachePolicy、设置请求超时timeoutInterval等。

    URLSession
    URLSession提供了一系列API用于执行数据传输任务。每个App创建一个或多个URLSession实例,每个实例协调一组相关的数据传输任务。
    URLSession有四种类型:

    1. shared单例会话。单例会话没有会话配置对象(URLSessionConfiguration),即会话不可自定义。通常用于简单的请求。
    2. default默认会话。默认会话与单例会话非常相似,但是您可以对其进行配置。您还可以将委托分配给默认会话以增量获取数据。
    3. ephemeral临时会话。临时会话类似于单例会话,但是不会将缓存,cookie或凭据写入磁盘。例如应用于无痕浏览,你懂的。😏
    4. background后台会话。后台会话使您可以在应用未运行时在后台执行内容的上载和下载。
    

    URLSessionConfiguration
    既然介绍了URLSession,那么也应该来了解下URLSessionConfiguration。URLSessionConfiguration作为URLSession的配置属性,可以进行一些自定义配置。上面提到的default、ephemeral及background都用到了URLSessionConfiguration。
    使用URLSessionConfiguration的allowsCellularAccess属性还可以设置会话是否能通过蜂窝网络进行连接。

    会话任务
    会话任务(URL Session Tasks)有以下四种,上面例子只是用到的其中一种(dataTask)。

    1. Data tasks。使用NSData对象发送和接收数据。数据任务旨在向服务器发出简短的,经常是交互式的请求。
    2. Upload tasks。与数据任务相似,但是它们还发送数据(通常以文件的形式),并在应用程序不运行时支持后台上传。
    3. Download tasks。以文件形式检索数据,并在应用程序不运行时支持后台下载和上传。
    4. WebSocket tasks。使用RFC 6455中定义的WebSocket协议通过TCP和TLS交换消息。
    

    2. 上传数据

        // 要上传的数据
        let uploadData = "test123".data(using: .utf8)
    
        // 配置URL请求
        let url = URL(string: "http://httpbin.org/post")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
    
        // 使用单例会话
        let session = URLSession.shared
    
        // 启动一个上传请求
        let task = session.uploadTask(with: request, from: uploadData) { data, response, error in
            if let error = error {
                print ("error: \(error)")
                return
            }
            guard let response = response as? HTTPURLResponse,
                (200...299).contains(response.statusCode) else {
                print ("server error")
                return
            }
            if let mimeType = response.mimeType,
                mimeType == "application/json",
                let data = data,
                let dataString = String(data: data, encoding: .utf8) {
                print ("got data: \(dataString)")
            }
        }
        task.resume()
    

    httpbin.org是一个测试HTTP请求的GitHub开源网站

    log:

    got data: {
      "args": {}, 
      "data": "test123", 
      "files": {}, 
      "form": {}, 
      "headers": {
        "Accept": "*/*", 
        "Accept-Encoding": "gzip, deflate", 
        "Accept-Language": "en-us", 
        "Content-Length": "7", 
        "Content-Type": "text/plain", 
        "Host": "httpbin.org", 
        "User-Agent": "KKHttpDemo/1 CFNetwork/1220.1 Darwin/20.3.0", 
        "X-Amzn-Trace-Id": "Root=1-603317a5-511395fa2f7c282800345ec2"
      }, 
      "json": null, 
      "origin": "113.90.245.35", 
      "url": "http://httpbin.org/post"
    }
    

    3. 下载数据

        let url = URL(string: "http://httpbin.org/image/jpeg")!
        
        let downloadTask = URLSession.shared.downloadTask(with: url) {
        urlOrNil, responseOrNil, errorOrNil in
        
            if let error = errorOrNil {
                print ("error: \(error)")
                return
            }
    
            guard let fileURL = urlOrNil else { return }
            do {
                let documentsURL = try
                    FileManager.default.url(for: .documentDirectory,
                                            in: .userDomainMask,
                                            appropriateFor: nil,
                                            create: false)
                var savedURL = documentsURL.appendingPathComponent(fileURL.lastPathComponent)
                // 修改后缀名
                savedURL.deletePathExtension()
                savedURL.appendPathExtension("jpeg")
                // 从tmp文件夹下移动到documents文件夹下
                try FileManager.default.moveItem(at: fileURL, to: savedURL)
                // 生成image
                let imageData = try Data.init(contentsOf: savedURL)
                let image = UIImage(data: imageData)
                if image != nil {
                    // 刷新UI
                    DispatchQueue.main.async {
                        self.imageView?.image = image
                    }
                }
            } catch {
                print ("file error: \(error)")
            }
        }
        downloadTask.resume()
    

    4. 后台下载

    AppDelegate.swift

    var backgroundCompletionHandler: (() -> Void)?
    
    // 下载完成后调用:App在后台还是被系统杀死,都会调用。手动杀死App或在前台时不调用
    func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
    
        backgroundCompletionHandler = completionHandler
    }
    

    ViewController.swift

        // 后台下载
        func backgroundDownload() {
            
            // 创建后台URLSession
            let config = URLSessionConfiguration.background(withIdentifier: "com.Kang.downloadSession")
            config.isDiscretionary = true
            config.sessionSendsLaunchEvents = true
            let urlSession = URLSession(configuration: config, delegate: self, delegateQueue: nil)
            
            // 开启下载任务
            let url = URL(string: "http://httpbin.org/image/jpeg")!
            let backgroundTask = urlSession.downloadTask(with: url)
            backgroundTask.earliestBeginDate = Date().addingTimeInterval(5)     // 延迟5秒下载
            backgroundTask.countOfBytesClientExpectsToSend = 200                // 最大上传200Byte
            backgroundTask.countOfBytesClientExpectsToReceive = 500 * 1024      // 最大下载500KB
            backgroundTask.resume()
        }
        
        
        //MARK: - URLSessionDelegate
    
        // 任务完成
        func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
            print(#function, "后台下载完成")
    
            DispatchQueue.main.async {
                guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
                    let backgroundCompletionHandler = appDelegate.backgroundCompletionHandler else {
                        return
                }
                backgroundCompletionHandler()   // 告诉系统,已经处理完成
            }
        }
        
        
        //MARK: - URLSessionDownloadDelegate
        
        // 下载完成
        func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
            print(#function, "下载完成")
                    
            let fileURL = location
            do {
                let documentsURL = try
                    FileManager.default.url(for: .documentDirectory,
                                            in: .userDomainMask,
                                            appropriateFor: nil,
                                            create: false)
                var savedURL = documentsURL.appendingPathComponent(fileURL.lastPathComponent)
                // 修改后缀名
                savedURL.deletePathExtension()
                savedURL.appendPathExtension("jpeg")
                // 从tmp文件夹下移动到documents文件夹下
                try FileManager.default.moveItem(at: fileURL, to: savedURL)
                // 生成image
                let imageData = try Data.init(contentsOf: savedURL)
                let image = UIImage(data: imageData)
                if image != nil {
                    // 刷新UI
                    DispatchQueue.main.async {
                        self.imageView?.image = image
                    }
                }
            } catch {
                print ("file error: \(error)")
            }
        }
    
        
        // 下载进度
        func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
            
            // 可以将字节转化为我们需要的Kb或者Mb
            let receiveSize = ByteCountFormatter.string(fromByteCount: totalBytesWritten, countStyle: .file)
            let totalSize = ByteCountFormatter.string(fromByteCount: totalBytesExpectedToWrite, countStyle: .file)
            let status = receiveSize + " / " + totalSize
            print("process: \(status)")
            // 刷新UI
            DispatchQueue.main.async {
                self.statusLabel?.text = status
            }
        }
    

    相关文章

      网友评论

          本文标题:iOS开发之网络通信(2)—— HTTP(S)

          本文链接:https://www.haomeiwen.com/subject/jfawzktx.html