2 // CustomHTTPProtocol.swift
5 // Created by Hori,Masaki on 2017/02/10.
6 // Copyright © 2017年 Hori,Masaki. All rights reserved.
12 protocol CustomHTTPProtocolDelegate: class {
13 func customHTTPProtocol(_ proto: CustomHTTPProtocol, didRecieve response: URLResponse)
14 func customHTTPProtocol(_ proto: CustomHTTPProtocol, didRecieve data: Data)
15 func customHTTPProtocolDidFinishLoading(_ proto: CustomHTTPProtocol)
16 func customHTTPProtocol(_ proto: CustomHTTPProtocol, didFailWithError error: Error)
19 class CustomHTTPProtocol: URLProtocol {
20 fileprivate static let requestProperty = "com.masakih.KCD.requestProperty"
21 static var classDelegate: CustomHTTPProtocolDelegate?
22 fileprivate static let cachedExtensions = ["swf", "flv", "png", "jpg", "jpeg", "mp3"]
23 fileprivate static let cacheFileURL: URL = ApplicationDirecrories.support.appendingPathComponent("Caches")
24 fileprivate static let kcdURLCache = URLCache(memoryCapacity: 32 * 1024 * 1024,
25 diskCapacity: 1024 * 1024 * 1024,
26 diskPath: cacheFileURL.path)
28 class func clearCache() { kcdURLCache.removeAllCachedResponses() }
29 class func start() { URLProtocol.registerClass(CustomHTTPProtocol.self) }
31 override class func canInit(with request: URLRequest) -> Bool {
32 if let _ = property(forKey: requestProperty, in: request) { return false }
33 if let scheme = request.url?.scheme?.lowercased(),
34 (scheme == "http" || scheme == "https") {
39 override class func canonicalRequest(for request: URLRequest) -> URLRequest {
43 fileprivate var delegate: CustomHTTPProtocolDelegate? { return CustomHTTPProtocol.classDelegate }
45 fileprivate var session: URLSession?
46 fileprivate var dataTask: URLSessionDataTask?
47 fileprivate var cachePolicy: URLCache.StoragePolicy = .notAllowed
48 fileprivate var data: Data = Data()
49 fileprivate var didRetry: Bool = false
50 fileprivate var didRecieveData: Bool = false
52 private func use(_ cache: CachedURLResponse) {
53 delegate?.customHTTPProtocol(self, didRecieve: cache.response)
54 client?.urlProtocol(self, didReceive: cache.response, cacheStoragePolicy: .allowed)
56 delegate?.customHTTPProtocol(self, didRecieve: cache.data)
57 client?.urlProtocol(self, didLoad: cache.data)
59 delegate?.customHTTPProtocolDidFinishLoading(self)
60 client?.urlProtocolDidFinishLoading(self)
62 override func startLoading() {
63 guard let newRequest = (request as NSObject).mutableCopy() as? NSMutableURLRequest
64 else { fatalError("Can not convert to NSMutableURLRequest") }
65 CustomHTTPProtocol.setProperty(true,
66 forKey: CustomHTTPProtocol.requestProperty,
69 if let cache = CustomHTTPProtocol.kcdURLCache.cachedResponse(for: request),
70 let info = cache.userInfo,
71 let expires = info["Expires"] as? Date,
72 Date().compare(expires) == .orderedAscending {
74 if let name = request.url?.lastPathComponent {
75 Debug.print("Use cache for", name, level: .full)
77 Debug.print("Use cache", level: .full)
83 let config = URLSessionConfiguration.default
84 config.protocolClasses = [type(of: self)]
85 session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
86 dataTask = session?.dataTask(with: newRequest as URLRequest)
89 override func stopLoading() {
94 extension CustomHTTPProtocol: URLSessionDataDelegate {
95 func urlSession(_ session: URLSession,
97 willPerformHTTPRedirection response: HTTPURLResponse,
98 newRequest request: URLRequest,
99 completionHandler: @escaping (URLRequest?) -> Void) {
100 Debug.print("willPerformHTTPRedirection", level: .full)
101 client?.urlProtocol(self, wasRedirectedTo: request, redirectResponse: response)
102 completionHandler(request)
104 func urlSession(_ session: URLSession,
105 dataTask: URLSessionDataTask,
106 didReceive response: URLResponse,
107 completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
108 Debug.print("didReceive response", level: .full)
110 if let response = response as? HTTPURLResponse,
111 let request = dataTask.originalRequest {
112 cachePolicy = CacheStoragePolicy(for: request, response: response)
115 delegate?.customHTTPProtocol(self, didRecieve: response)
116 client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: cachePolicy)
117 completionHandler(.allow)
119 func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
120 Debug.print("didReceive data", level: .full)
121 if cachePolicy == .allowed {
122 self.data.append(data)
124 delegate?.customHTTPProtocol(self, didRecieve: data)
125 client?.urlProtocol(self, didLoad: data)
126 didRecieveData = true
129 // cfurlErrorNetworkConnectionLost の場合はもう一度試す
130 private func canRetry(error: NSError) -> Bool {
131 guard error.code == Int(CFNetworkErrors.cfurlErrorNetworkConnectionLost.rawValue),
134 else { return false }
135 print("Retry download...")
138 func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
139 if let error = error {
140 if canRetry(error: error as NSError),
141 let request = task.originalRequest {
143 dataTask = session.dataTask(with: request)
147 Debug.print("didCompleteWithError ERROR", level: .full)
148 delegate?.customHTTPProtocol(self, didFailWithError: error)
149 client?.urlProtocol(self, didFailWithError: error)
152 Debug.print("didCompleteWithError SUCCESS", level: .full)
153 delegate?.customHTTPProtocolDidFinishLoading(self)
154 client?.urlProtocolDidFinishLoading(self)
156 if cachePolicy == .allowed,
157 let ext = task.originalRequest?.url?.pathExtension,
158 CustomHTTPProtocol.cachedExtensions.contains(ext),
159 let request = task.originalRequest,
160 let response = task.response as? HTTPURLResponse,
161 let expires = response.expires() {
162 let cache = CachedURLResponse(response: response,
164 userInfo: ["Expires": expires],
165 storagePolicy: .allowed)
166 CustomHTTPProtocol.kcdURLCache.storeCachedResponse(cache, for: request)
171 extension HTTPURLResponse {
172 private var httpDateFormatter: DateFormatter {
173 let formatter = DateFormatter()
174 formatter.dateFormat = "EEE',' dd' 'MMM' 'yyyy HH':'mm':'ss zzz"
175 formatter.locale = Locale(identifier: "en_US")
178 func expires() -> Date? {
179 if let cc = (allHeaderFields["Cache-Control"] as? String)?.lowercased(),
180 let range = cc.range(of: "max-age="),
181 let s = cc[range.upperBound..<cc.endIndex]
182 .components(separatedBy: ",")
184 let age = TimeInterval(s) {
185 return Date(timeIntervalSinceNow: age)
187 if let ex = (allHeaderFields["Expires"] as? String)?.lowercased(),
188 let exp = httpDateFormatter.date(from: ex) {