Sources/OSS/Internal/URLSessionMiddleware.swift (133 lines of code) (raw):
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
private class UploadProgressDelegate: NSObject, URLSessionTaskDelegate, @unchecked Sendable {
private var progress: ProgressDelegate
init(progress: ProgressDelegate) {
self.progress = progress
}
func urlSession(_: URLSession, task _: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
progress.onProgress(bytesSent, totalBytesSent, totalBytesExpectedToSend)
}
}
private class DownloadProgressDelegate: NSObject, URLSessionDataDelegate, @unchecked Sendable {
private var progress: ProgressDelegate
public var bytesWritten: Int64
public var totalBytesWritten: Int64
public var totalBytesExpectedToWrite: Int64
init(progress: ProgressDelegate) {
self.progress = progress
bytesWritten = 0
totalBytesWritten = 0
totalBytesExpectedToWrite = -1
}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @Sendable @escaping (URLSession.ResponseDisposition) -> Void) {
totalBytesExpectedToWrite = response.expectedContentLength
#if canImport(FoundationNetworking)
if let sessionDelegate = session.delegate as? URLSessionDataDelegate {
sessionDelegate.urlSession(session, dataTask: dataTask, didReceive: response, completionHandler: completionHandler)
} else {
completionHandler(.allow)
}
#endif
}
public func urlSession(_: URLSession, dataTask _: URLSessionDataTask, didReceive data: Data) {
bytesWritten = Int64(data.count)
totalBytesWritten += bytesWritten
progress.onProgress(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
}
}
class URLSessionMiddleware: ExecuteMiddleware {
private let _session: URLSession
private let logger: LogAgent?
init(_ session: URLSession,
_ logger: LogAgent? = nil)
{
_session = session
self.logger = logger
}
public func execute(request: RequestMessage, context: ExecuteContext) async throws -> ResponseMessage {
logger?.debug(request.description)
var urlRequest = URLRequest(url: request.requestUri)
urlRequest.httpMethod = request.method
for (key, value) in request.headers {
urlRequest.addValue(value, forHTTPHeaderField: key)
}
// task delegate
let delegate: URLSessionTaskDelegate?
if let progresInfo = context.progressDelegate {
delegate = progresInfo.upload ?
UploadProgressDelegate(progress: progresInfo.delegate) :
DownloadProgressDelegate(progress: progresInfo.delegate)
} else {
delegate = nil
}
let body: ByteStream
let urlResponse: URLResponse
let respData: Data
do {
switch request.content {
case let .file(url):
if #available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) {
(respData, urlResponse) = try await self._session.upload(
for: urlRequest,
fromFile: url,
delegate: delegate
)
} else {
(respData, urlResponse) = try await _session.upload(for: urlRequest, fromFile: url)
}
body = .data(respData)
default:
switch request.content {
case let .data(content):
urlRequest.httpBody = content
case let .stream(inputStream):
urlRequest.httpBodyStream = inputStream
default:
// no content
break
}
let respUrl: URL
if #available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *),
let saveToUrl = context.saveToURL, saveToUrl == true
{
/// because of the bug of URLSession, we can't get the progress of downloading
/// set delegate to nil always
(respUrl, urlResponse) = try await self._session.download(for: urlRequest, delegate: nil)
// save reponse body into memroy when status code is not 2xx
if let resp = (urlResponse as? HTTPURLResponse), resp.statusCode >= 300 {
body = .data(FileManager.default.contents(atPath: respUrl.path)!)
try FileManager.default.removeItem(at: respUrl)
} else {
body = .file(respUrl)
}
} else {
if #available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) {
(respData, urlResponse) = try await self._session.data(for: urlRequest, delegate: delegate)
} else {
(respData, urlResponse) = try await _session.data(for: urlRequest)
}
body = .data(respData)
}
}
} catch {
logger?.error("\(error)")
throw ClientError.responseError(
detail: "send request failed",
innerError: error
)
}
guard let urlResponse = (urlResponse as? HTTPURLResponse) else {
throw ClientError.responseError(detail: "Failed to convert URLResponse to HttpResponse")
}
// convert URLResponse to HttpResponse
var headers: [String: String] = [:]
for (key, value) in urlResponse.allHeaderFields {
headers[key as! String] = value as? String
}
let response = ResponseMessage(
statusCode: urlResponse.statusCode,
headers: headers,
content: body,
request: request
)
logger?.debug(response.description)
return response
}
}