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

专栏

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

一. HTTP发展史

HTTP版本年份新增命令连接方式摘要
0.91991GET短连接初版
1.01996POST、HEAD短连接 (非标准长连接Connection: keep-alive)1. 可以传输文字,还能传输图像、视频、二进制文件。
2. 加入头信息、状态码、多字符集支持、多部分发送、权限、缓存、内容编码等。
1.11999PUT、PATCH、TRACE、OPTIONS、DELETE默认持久连接1. 管道机制: 一个连接可同时发送多个请求(服务器端需要按顺序返回结果)。
2. 增加Host字段,指定服务器的域名,这样服务器上支持了虚拟主机,即一台机器多个站点。
22015-默认持久连接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 方法。

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

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
        }
    }
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值