在
web
服務器和服務器通訊的時候,使用https
鏈接是很是重要的,可以對數據加密傳輸、身份認證。https
協議須要到ca
申請證書,部署到服務器,應用端鏈接都是對該連接受信任的。證書可申請也能夠自籤,自簽證書須要客戶端驗證經過才能訪問。php
HTTP
是互聯網的基礎協議,默認端口80
,爲知足應用需求HTTP也在不斷的版本升級改進,從0.9版本
到1.1版本
功能不斷的強大起來。HTTP
演變可參考:www.ruanyifeng.com/blog/2016/0…html
HTTP的特色:nginx
HTTP
客戶端請求只須要肯定請求方法和路徑。經常使用方法有GET、POST、HEAD
,因爲對參數封裝形式不同,通常獲取數據使用GET
,上傳數據使用POST
,GET
參數暴露在連接中,POST
則封裝在請求體中Content-Type
標記傳輸的數據類型HTTP協議
是無狀態協議,對處理過的事務無記憶能力,爲了客戶端服務端更好的交互,引用了Cookie
和Session
來存儲請求中產生的狀態工做流程:web
不須要任何處理,客戶端要服務端就給。swift
HTTPS
協議爲超文本傳輸安全協議,默認端口443
,HTTPS=HTTP+SSL
,對數據進行加密,保護數據在交互時不被竊取,提升了對服務器惡意攻擊及數據假裝的成本。使用該協議要求服務器申請證書並配置協議環境。api
HTTPS的工做流程:安全
須要申請證書,綁定域名,服務器中配置證書。通常我的會使用阿里的免費證書,雖然加密性通常,至少證書是受信任的。也能夠本身建立證書,自建證書可使用,但不會被信任,既然有免費的咱們就走正規路線😁。bash
分別發起http和https請求:服務器
經過Charles抓包觀察數據以下:
固然開啓SSL代理仍是能夠抓到的:
所以在作APP
的時候,爲了防止APP
被抓包,咱們須要作反代理設置抓包,判斷應用代理設置或證書驗證。
HTTPS協議
須要到ca
申請證書,我的頒發證書是不受信任的;
HTTP
是超文本傳輸協議,明文傳輸,HTTPS
則是在HTTP
上加了一層SSL
(安全套接層),對數據加密傳輸;
HTTP
在服務器上的默認端爲80
,HTTPS
爲443端口
;
HTTP
鏈接是無狀態的,HTTPS
協議等價於HTTP+SSL/TLS
,可進行加密傳輸、身份認證的網絡協議。
二者的優缺點很明顯,
HTTP
不存在加密,明文傳輸,不安全,但傳輸速度快,HTTPS
密文傳輸,傳輸中須要肯定應用端和服務端的保密性和數據完整性。
證書長什麼樣?
在ca
申請的證書,包括兩個文件.key文件
和.pem文件
,通常在nginx
配置文件中配置便可。.key
是證書的私鑰文件,.pem
爲證書文件。
在初始化SessionManager
中能夠看到,有一個初始化類型設置了ServerTrustPolicyManager
類對象,該對象就是安全策略的管理者。在實際開發中,APP可能會用到不一樣的主機地址host
,根據業務不一樣須要要給不一樣的host
設置不一樣的安全策略。所以管理者的實現以下:
open class ServerTrustPolicyManager {
public let policies: [String: ServerTrustPolicy]
public init(policies: [String: ServerTrustPolicy]) {
self.policies = policies
}
open func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
return policies[host]
}
}
複製代碼
policies
字典,key
爲host
,值爲選擇的響應的策略ServerTrustPolicy
serverTrustPolicy
方法經過host獲取ServerTrustPolicy
對象不要問爲何分離一個manager
來管理ServerTrustPolicy
,不直接使用,由於這是大廠分工要明確,責任到每一層。
ServerTrustPolicy是一個枚舉類:
public enum ServerTrustPolicy {
case performDefaultEvaluation(validateHost: Bool)
case performRevokedEvaluation(validateHost: Bool, revocationFlags: CFOptionFlags)
case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)
case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)
case disableEvaluation
case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool)
//代碼省略若干
}
複製代碼
以上是類型關聯,根據不一樣類型作處理。
performDefaultEvaluation
默認策略類型,始終驗證主機證書的有效性performRevokedEvaluation
對吊銷過的證書作設置pinCertificates
驗證服務器返回的證書的正確性,參數決定是否驗證證書鏈pinPublicKeys
公鑰驗證disableEvaluation
無需驗證,無條件信任customEvaluation
自定義驗證,返回一個布爾值以上方法在項目中並無默認配置,須要咱們配置使用,常常會選擇證書驗證、公鑰驗證、不作驗證模式。具體設置根據須要選擇。
實踐出真知,咱們發送一個https
請求看看效果。
let url = "https://www.yahibo.top/project/public/index.php?s=api/test/list"
sessionManager.request(url).response {
(response) in
print(response)
}
複製代碼
運行以下:
運行一切正常,可以請求到數據,好像也沒什麼區別,其實上面已經作了數據傳輸的說明了加密驗證。下面看一下抓包,開啓Charles
,從新發送請求。
http
請求一切正常。
https
請求運行結果以下:
http
可以正常請求,https
確報錯,這個好像是咱們想要的結果,都尚未配置相關信息😂。在AF
中便是https
在抓包狀態下也是能夠獲取數據的,而Alamofire
好像作了更多的處理,直接避免抓包。
該安全策略其實對自簽證書作的驗證,既然個人證書是合法的被承認的就不作配置了😂。若是是自簽證書須要對域名或證書作驗證以下設置:
let policies: [String: ServerTrustPolicy] = [
"www.yahibo.top": .disableEvaluation
]
let sessionManager = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: policies))
return sessionManager
複製代碼
我覺得的被驗證錯誤,打臉😂。後續補充自簽證書驗證,肯定是否可以經過請求。這是一個失敗的實踐,不過仍是有收穫的。
……
分別看一下AF
和Alamofire
的實現。
枚舉類型,肯定網絡狀態:
typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
AFNetworkReachabilityStatusUnknown = -1,
AFNetworkReachabilityStatusNotReachable = 0,
AFNetworkReachabilityStatusReachableViaWWAN = 1,
AFNetworkReachabilityStatusReachableViaWiFi = 2,
};
複製代碼
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status){
[weakSelf setNetworkStates:status];
switch (status) {
case AFNetworkReachabilityStatusUnknown:
NSLog(@"未知網絡");
break;
case AFNetworkReachabilityStatusNotReachable:
NSLog(@"這是一個不可達網絡");
break;
case AFNetworkReachabilityStatusReachableViaWWAN:
NSLog(@"這是一個蜂窩網絡");
break;
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(@"這是一個WiFi網絡");
break;
default:
break;
}
}];
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
複製代碼
網絡監測無非就是以上幾個狀態,設置回調代碼塊,具體監測行爲由AFNetworkReachabilityManager
完成。這裏就簡單看一下,下面看一下Alamofire
的網絡監測,其實都是同樣的調用的底層api
不變。
一樣提供了這麼一個類,來管理網絡監測任務。對框架的學習,最大的收穫就是可以更快一些的對源碼作分析,快速使用框架。
具體使用
枚舉類型:
public enum NetworkReachabilityStatus {
case unknown
case notReachable
case reachable(ConnectionType)
}
public enum ConnectionType {
case ethernetOrWiFi
case wwan
}
複製代碼
使用:
let networkManager = NetworkReachabilityManager(host: "www.yahibo.top")
networkManager?.listener = { status in
switch status {
case .unknown:
print("未知網絡")
break
case .notReachable:
print("這是一個不可達網絡")
break
case .reachable(.ethernetOrWiFi):
print("這是一個WiFi網絡")
break
case .reachable(.wwan):
print("這是一個蜂窩網絡")
break
}
networkManager?.startListening()
複製代碼
大同小異,編碼方式變的更簡潔了,這也符合swift
的編碼風格。以上能夠看出在Alamofire
中的網絡監測將WiFi
網絡和蜂窩網絡歸爲一類。下面看一下源碼。
一、屬性
1️⃣、聲明一個閉包,在外部實現呢,網絡變化時掉用閉包傳值
public typealias Listener = (NetworkReachabilityStatus) -> Void
複製代碼
2️⃣、網絡是否可達,包括蜂窩和WiFi網絡
open var isReachable: Bool { return isReachableOnWWAN || isReachableOnEthernetOrWiFi }
複製代碼
3️⃣、蜂窩網絡是否爲可達的
open var isReachableOnWWAN: Bool { return networkReachabilityStatus == .reachable(.wwan) }
複製代碼
4️⃣、經過WiFi鏈接網絡
open var isReachableOnEthernetOrWiFi: Bool { return networkReachabilityStatus == .reachable(.ethernetOrWiFi) }
複製代碼
5️⃣、獲取網絡類型
open var networkReachabilityStatus: NetworkReachabilityStatus {
guard let flags = self.flags else { return .unknown }
return networkReachabilityStatusForFlags(flags)
}
複製代碼
6️⃣、設置監聽閉包在哪一個隊列中調用你,默認給主隊列
open var listenerQueue: DispatchQueue = DispatchQueue.main
複製代碼
7️⃣、定一個閉包類型的監聽屬性
open var listener: Listener?
複製代碼
初始化
public convenience init?(host: String) {
guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil }
self.init(reachability: reachability)
}
複製代碼
ip
,傳入其餘只要不爲空也是能夠獲取到網絡類型,仍是設置爲咱們經常使用的服務地址比較好AF
中是同樣的SCNetworkReachability
對象,網絡地址或名稱的句柄 SCNetworkReachabilityRef
該對象能夠肯定當前主機的網絡狀態和目標地址的可達性,提供同步或異步接口,獲取當前的網絡狀態。以上初始化有調用了一個init
初始化方法:
private init(reachability: SCNetworkReachability) {
self.reachability = reachability
// Set the previous flags to an unreserved value to represent unknown status
self.previousFlags = SCNetworkReachabilityFlags(rawValue: 1 << 30)
}
複製代碼
二、啓動監聽網絡
networkManager?.startListening()
內部實現:
open func startListening() -> Bool {
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
context.info = Unmanaged.passUnretained(self).toOpaque()
let callbackEnabled = SCNetworkReachabilitySetCallback(
reachability,
{ (_, flags, info) in
let reachability = Unmanaged<NetworkReachabilityManager>.fromOpaque(info!).takeUnretainedValue()
reachability.notifyListener(flags)
},
&context
)
let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue)
listenerQueue.async {
guard let flags = self.flags else { return }
self.notifyListener(flags)
}
return callbackEnabled && queueEnabled
}
複製代碼
SCNetworkReachabilitySetCallback
監聽網絡狀態的變化,發生改變即調用該回調方法listenerQueue
在開啓監聽是調用一次notifyListener
通知Listener閉包
func notifyListener(_ flags: SCNetworkReachabilityFlags) {
guard previousFlags != flags else { return }
previousFlags = flags
listener?(networkReachabilityStatusForFlags(flags))
}
複製代碼
flags
listener閉包
向外傳遞當前的網絡狀態func networkReachabilityStatusForFlags(_ flags: SCNetworkReachabilityFlags) -> NetworkReachabilityStatus {
guard isNetworkReachable(with: flags) else { return .notReachable }
var networkStatus: NetworkReachabilityStatus = .reachable(.ethernetOrWiFi)
#if os(iOS)
if flags.contains(.isWWAN) { networkStatus = .reachable(.wwan) }
#endif
return networkStatus
}
複製代碼
flags
來獲取當前的網絡狀態,首先看網絡是否爲可達網絡func isNetworkReachable(with flags: SCNetworkReachabilityFlags) -> Bool {
let isReachable = flags.contains(.reachable)
let needsConnection = flags.contains(.connectionRequired)
let canConnectAutomatically = flags.contains(.connectionOnDemand) || flags.contains(.connectionOnTraffic)
let canConnectWithoutUserInteraction = canConnectAutomatically && !flags.contains(.interventionRequired)
return isReachable && (!needsConnection || canConnectWithoutUserInteraction)
}
複製代碼
三、關閉網絡監聽
當結束後執行deinit
方法來調用stopListening
方法:
open func stopListening() {
SCNetworkReachabilitySetCallback(reachability, nil, nil)
SCNetworkReachabilitySetDispatchQueue(reachability, nil)
}
複製代碼
其實網絡監測並不複雜,只是對系統網絡監測類的一個封裝,設置枚舉對網絡狀態歸類,經過
listener閉包
向外發送網絡監測數據。使用只須要初始化設置主機地址,而後開啓監聽便可。所謂的監聽就是實現對網絡狀態監聽的回調閉包,是框架和系統底層網絡層的消息傳遞。
學習原理及對底層源碼探索是頗有必要的,可以幫助咱們快速開發,快速的解決問題。加油⛽️