diff --git a/EhPanda/App/Tools/Defaults.swift b/EhPanda/App/Tools/Defaults.swift index e13c56cc..b71946f2 100644 --- a/EhPanda/App/Tools/Defaults.swift +++ b/EhPanda/App/Tools/Defaults.swift @@ -65,6 +65,21 @@ struct Defaults { struct Regex { static let tagSuggestion: NSRegularExpression? = try? .init(pattern: "(\\S+:\".+?\"|\".+?\"|\\S+:\\S+|\\S+)") } + struct Network { + /// 普通请求超时时间(秒) + static let normalRequestTimeout: TimeInterval = 30.0 + /// 普通请求资源超时时间(秒) + static let normalResourceTimeout: TimeInterval = 60.0 + + /// 图片请求超时时间(秒) + static let imageRequestTimeout: TimeInterval = 60.0 + /// 图片请求资源超时时间(秒) + static let imageResourceTimeout: TimeInterval = 120.0 + + /// 重试次数 + static let retryCount: Int = 3 + } + struct URL { static var host: Foundation.URL { AppUtil.galleryHost == .exhentai ? exhentai : ehentai } static let ehentai: Foundation.URL = .init(string: "https://e-hentai.org/").forceUnwrapped diff --git a/EhPanda/Network/DFExtensions.swift b/EhPanda/Network/DFExtensions.swift index 1171a2d8..e72d2989 100644 --- a/EhPanda/Network/DFExtensions.swift +++ b/EhPanda/Network/DFExtensions.swift @@ -62,6 +62,40 @@ extension URLSessionConfiguration { config.protocolClasses = [DFURLProtocol.self] return config } + + /// 为普通请求配置的URLSession + static var normalRequest: URLSessionConfiguration { + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = Defaults.Network.normalRequestTimeout + config.timeoutIntervalForResource = Defaults.Network.normalResourceTimeout + return config + } + + /// 为图片请求配置的URLSession + static var imageRequest: URLSessionConfiguration { + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = Defaults.Network.imageRequestTimeout + config.timeoutIntervalForResource = Defaults.Network.imageResourceTimeout + return config + } + + #if DEBUG + /// 验证网络配置的调试方法 + static func validateNetworkConfiguration() { + print("=== 网络配置验证 ===") + + let normalConfig = URLSessionConfiguration.normalRequest + print("普通请求超时: \(normalConfig.timeoutIntervalForRequest)秒") + print("普通资源超时: \(normalConfig.timeoutIntervalForResource)秒") + + let imageConfig = URLSessionConfiguration.imageRequest + print("图片请求超时: \(imageConfig.timeoutIntervalForRequest)秒") + print("图片资源超时: \(imageConfig.timeoutIntervalForResource)秒") + + print("重试次数: \(Defaults.Network.retryCount)次") + print("=== 配置验证完成 ===") + } + #endif } // MARK: CFHTTPMessage diff --git a/EhPanda/Network/Request.swift b/EhPanda/Network/Request.swift index f70b746a..ec57e726 100644 --- a/EhPanda/Network/Request.swift +++ b/EhPanda/Network/Request.swift @@ -37,7 +37,7 @@ extension Request { private extension Publisher { func genericRetry() -> Publishers.Retry { - retry(3) + retry(Defaults.Network.retryCount) } func async() async -> Result where Failure == AppError { @@ -408,7 +408,7 @@ struct GalleryReverseRequest: Request { func galleryURL(url: URL) -> AnyPublisher { switch isGalleryImageURL { case true: - return URLSession.shared.dataTaskPublisher(for: url) + return URLSession(configuration: .imageRequest).dataTaskPublisher(for: url) .tryMap { try Kanna.HTML(html: $0.data, encoding: .utf8) } .tryMap(Parser.parseGalleryURL) .mapError(mapAppError) @@ -422,7 +422,7 @@ struct GalleryReverseRequest: Request { } func gallery(url: URL) -> AnyPublisher { - URLSession.shared.dataTaskPublisher(for: url) + URLSession(configuration: .normalRequest).dataTaskPublisher(for: url) .tryMap { try Kanna.HTML(html: $0.data, encoding: .utf8) } .compactMap { guard let (detail, _) = try? Parser.parseGalleryDetail(doc: $0, gid: url.pathComponents[2]) @@ -545,7 +545,7 @@ struct GalleryNormalImageURLsRequest: Request { var publisher: AnyPublisher<([Int: URL], [Int: URL]), AppError> { thumbnailURLs.publisher .flatMap { index, url in - URLSession.shared.dataTaskPublisher(for: url) + URLSession(configuration: .imageRequest).dataTaskPublisher(for: url) .genericRetry() .tryMap { try Kanna.HTML(html: $0.data, encoding: .utf8) } .tryMap { try Parser.parseGalleryNormalImageURL(doc: $0, index: index) } @@ -589,7 +589,7 @@ struct GalleryNormalImageURLRefetchRequest: Request { .setFailureType(to: AppError.self) .eraseToAnyPublisher() } else { - return URLSession.shared.dataTaskPublisher(for: URLUtil.detailPage(url: galleryURL, pageNum: pageNum)) + return URLSession(configuration: .normalRequest).dataTaskPublisher(for: URLUtil.detailPage(url: galleryURL, pageNum: pageNum)) .tryMap { try Kanna.HTML(html: $0.data, encoding: .utf8) } .tryMap(Parser.parseThumbnailURLs) .compactMap({ thumbnailURLs in thumbnailURLs[index] }) @@ -599,7 +599,7 @@ struct GalleryNormalImageURLRefetchRequest: Request { } func renewThumbnailURL(stored: URL) -> AnyPublisher<(URL, URL), AppError> { - URLSession.shared.dataTaskPublisher(for: stored) + URLSession(configuration: .imageRequest).dataTaskPublisher(for: stored) .tryMap { try Kanna.HTML(html: $0.data, encoding: .utf8) } .tryMap { let identifier = try Parser.parseSkipServerIdentifier(doc: $0) @@ -612,7 +612,7 @@ struct GalleryNormalImageURLRefetchRequest: Request { func imageURL(thumbnailURL: URL, anotherImageURL: URL) -> AnyPublisher<(URL, URL, HTTPURLResponse?), AppError> { - URLSession.shared.dataTaskPublisher(for: thumbnailURL) + URLSession(configuration: .imageRequest).dataTaskPublisher(for: thumbnailURL) .tryMap { (try Kanna.HTML(html: $0.data, encoding: .utf8), $0.response as? HTTPURLResponse) } @@ -687,7 +687,11 @@ struct DataRequest: Request { let url: URL var publisher: AnyPublisher { - URLSession.shared.dataTaskPublisher(for: url) + let urlString = url.absoluteString.lowercased() + let isImageURL = ["jpg", "jpeg", "png", "gif", "bmp", "webp"].contains { urlString.contains($0) } + let session = isImageURL ? URLSession(configuration: .imageRequest) : URLSession(configuration: .normalRequest) + + return session.dataTaskPublisher(for: url) .genericRetry() .map(\.data) .mapError(mapAppError)