2 // CustomHTTPProtocol.swift
5 // Created by Hori,Masaki on 2017/02/10.
6 // Copyright © 2017年 Hori,Masaki. All rights reserved.
12 protocol CustomHTTPProtocolDelegate: class {
14 func customHTTPProtocol(_ proto: CustomHTTPProtocol, didRecieve response: URLResponse)
15 func customHTTPProtocol(_ proto: CustomHTTPProtocol, didRecieve data: Data)
16 func customHTTPProtocolDidFinishLoading(_ proto: CustomHTTPProtocol)
17 func customHTTPProtocol(_ proto: CustomHTTPProtocol, didFailWithError error: Error)
21 /// 生成されたスレッド上でoprationを実行する
22 private final class ThreadOperator: NSObject {
24 private let thread: Thread
25 private let modes: [String]
26 private var operation: (() -> Void)?
30 thread = Thread.current
31 if let mode = RunLoop.current.currentMode {
33 modes = [mode, .defaultRunLoopMode].map { $0.rawValue }
37 modes = [RunLoopMode.defaultRunLoopMode.rawValue]
43 func execute(_ operation: @escaping () -> Void) {
45 self.operation = operation
46 perform(#selector(ThreadOperator.operate),
54 @objc private func operate() {
60 extension HTTPURLResponse {
62 private static var dateFormatter: DateFormatter = {
64 let formatter = DateFormatter()
65 formatter.dateFormat = "EEE',' dd' 'MMM' 'yyyy HH':'mm':'ss zzz"
66 formatter.locale = Locale(identifier: "en_US")
71 func expires() -> Date? {
73 if let cc = (allHeaderFields["Cache-Control"] as? String)?.lowercased(),
74 let range = cc.range(of: "max-age="),
75 let s = cc[range.upperBound...]
76 .components(separatedBy: ",")
78 let age = TimeInterval(s) {
80 return Date(timeIntervalSinceNow: age)
83 if let ex = (allHeaderFields["Expires"] as? String)?.lowercased(),
84 let exp = HTTPURLResponse.dateFormatter.date(from: ex) {
95 static let kcd = URLCache(memoryCapacity: 32 * 1024 * 1024,
96 diskCapacity: 1024 * 1024 * 1024,
97 diskPath: ApplicationDirecrories.shared.support.appendingPathComponent("Caches").path)
98 static let cachedExtensions = ["swf", "flv", "png", "jpg", "jpeg", "mp3"]
100 func storeIfNeeded(for task: URLSessionTask, data: Data) {
102 if let request = task.originalRequest,
103 let response = task.response as? HTTPURLResponse,
104 let ext = request.url?.pathExtension,
105 URLCache.cachedExtensions.contains(ext),
106 let expires = response.expires() {
108 let cache = CachedURLResponse(response: response,
110 userInfo: ["Expires": expires],
111 storagePolicy: .allowed)
112 storeCachedResponse(cache, for: request)
116 func validCach(for request: URLRequest) -> CachedURLResponse? {
118 if let cache = cachedResponse(for: request),
119 let info = cache.userInfo,
120 let expires = info["Expires"] as? Date,
121 Date().compare(expires) == .orderedAscending {
130 final class CustomHTTPProtocol: URLProtocol {
132 private static let requestProperty = "com.masakih.KCD.requestProperty"
133 static var classDelegate: CustomHTTPProtocolDelegate?
135 class func clearCache() {
137 URLCache.kcd.removeAllCachedResponses()
142 URLProtocol.registerClass(CustomHTTPProtocol.self)
145 override class func canInit(with request: URLRequest) -> Bool {
147 if let _ = property(forKey: requestProperty, in: request) { return false }
149 if let scheme = request.url?.scheme?.lowercased(),
150 (scheme == "http" || scheme == "https") {
158 override class func canonicalRequest(for request: URLRequest) -> URLRequest {
163 private var delegate: CustomHTTPProtocolDelegate? { return CustomHTTPProtocol.classDelegate }
165 private var session: URLSession?
166 private var dataTask: URLSessionDataTask?
167 private var cachePolicy: URLCache.StoragePolicy = .notAllowed
168 private var data: Data = Data()
169 private var didRetry: Bool = false
170 private var didRecieveData: Bool = false
172 private var threadOperator: ThreadOperator?
174 private func use(_ cache: CachedURLResponse) {
176 delegate?.customHTTPProtocol(self, didRecieve: cache.response)
177 client?.urlProtocol(self, didReceive: cache.response, cacheStoragePolicy: .allowed)
179 delegate?.customHTTPProtocol(self, didRecieve: cache.data)
180 client?.urlProtocol(self, didLoad: cache.data)
182 delegate?.customHTTPProtocolDidFinishLoading(self)
183 client?.urlProtocolDidFinishLoading(self)
186 override func startLoading() {
188 guard let newRequest = (request as NSObject).mutableCopy() as? NSMutableURLRequest else {
190 fatalError("Can not convert to NSMutableURLRequest")
193 URLProtocol.setProperty(true,
194 forKey: CustomHTTPProtocol.requestProperty,
197 if let cache = URLCache.kcd.validCach(for: request) {
201 Debug.excute(level: .full) {
203 if let name = request.url?.lastPathComponent {
205 print("Use cache for", name)
216 threadOperator = ThreadOperator()
218 let config = URLSessionConfiguration.default
219 session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
220 dataTask = session?.dataTask(with: newRequest as URLRequest)
224 override func stopLoading() {
230 extension CustomHTTPProtocol: URLSessionDataDelegate {
232 func urlSession(_ session: URLSession,
233 task: URLSessionTask,
234 willPerformHTTPRedirection response: HTTPURLResponse,
235 newRequest request: URLRequest,
236 completionHandler: @escaping (URLRequest?) -> Void) {
238 threadOperator?.execute { [weak self] in
240 guard let `self` = self else {
245 Debug.print("willPerformHTTPRedirection", level: .full)
247 self.client?.urlProtocol(self, wasRedirectedTo: request, redirectResponse: response)
249 completionHandler(request)
253 func urlSession(_ session: URLSession,
254 dataTask: URLSessionDataTask,
255 didReceive response: URLResponse,
256 completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
258 threadOperator?.execute { [weak self] in
260 guard let `self` = self else {
265 Debug.print("didReceive response", level: .full)
267 if let response = response as? HTTPURLResponse,
268 let request = dataTask.originalRequest {
270 self.cachePolicy = cacheStoragePolicy(for: request, response: response)
273 self.delegate?.customHTTPProtocol(self, didRecieve: response)
274 self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: self.cachePolicy)
276 completionHandler(.allow)
280 func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
282 threadOperator?.execute { [weak self] in
284 guard let `self` = self else {
289 Debug.print("didReceive data", level: .full)
290 if self.cachePolicy == .allowed {
292 self.data.append(data)
295 self.delegate?.customHTTPProtocol(self, didRecieve: data)
296 self.client?.urlProtocol(self, didLoad: data)
297 self.didRecieveData = true
301 // cfurlErrorNetworkConnectionLost の場合はもう一度試す
302 private func canRetry(error: NSError) -> Bool {
304 guard error.code == Int(CFNetworkErrors.cfurlErrorNetworkConnectionLost.rawValue),
306 !didRecieveData else {
311 print("Retry download...")
316 func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
318 threadOperator?.execute { [weak self] in
320 guard let `self` = self else {
325 if let error = error {
327 if self.canRetry(error: error as NSError),
328 let request = task.originalRequest {
331 self.dataTask = session.dataTask(with: request)
332 self.dataTask?.resume()
337 Debug.print("didCompleteWithError ERROR", level: .full)
339 self.delegate?.customHTTPProtocol(self, didFailWithError: error)
340 self.client?.urlProtocol(self, didFailWithError: error)
345 Debug.print("didCompleteWithError SUCCESS", level: .full)
347 self.delegate?.customHTTPProtocolDidFinishLoading(self)
348 self.client?.urlProtocolDidFinishLoading(self)
350 if self.cachePolicy == .allowed {
352 URLCache.kcd.storeIfNeeded(for: task, data: self.data)