-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: enhance plain and multipart upload + applying SRP
Resolves: none.
- Loading branch information
1 parent
6c8175c
commit b3c2e9e
Showing
28 changed files
with
971 additions
and
617 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
60 changes: 60 additions & 0 deletions
60
Source/Common/Request/Logger/Factory/CURLCommandFactory.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// | ||
// CURLCommandFactory.swift | ||
// RxNetworkKit | ||
// | ||
// Created by Loay Ashraf on 01/10/2024. | ||
// | ||
|
||
import Foundation | ||
|
||
final class CURLCommandFactory { | ||
|
||
/// Makes cURL command representation for a given request. | ||
/// | ||
/// - Parameters: | ||
/// - request: `URLRequest` used to make cURL command representation. | ||
/// - body: `CURLCommandBody` that is used to make the cURL command body option. | ||
/// | ||
/// - Returns: cURL command representation for the given request. | ||
func make(for request: URLRequest, bodyOption: HTTPLogBodyOption) -> String { | ||
guard let url = request.url else { return "" } | ||
var baseCommand = #"curl "\#(url.absoluteString)""# | ||
|
||
if request.httpMethod == "HEAD" { | ||
baseCommand += " --head" | ||
} | ||
|
||
var command = [baseCommand] | ||
|
||
if let method = request.httpMethod, method != "GET" && method != "HEAD" { | ||
command.append("-X \(method)") | ||
} | ||
|
||
if let headers = request.allHTTPHeaderFields { | ||
for (key, value) in headers where key != "Cookie" { | ||
command.append("-H '\(key): \(value)'") | ||
} | ||
} | ||
|
||
switch bodyOption { | ||
case .plain: | ||
if let data = request.httpBody, | ||
let body = String(data: data, encoding: .utf8) { | ||
command.append("-d '\(body)'") | ||
} | ||
case .file(let file): | ||
command.append("--upload-file \(file.path ?? "{ Path to the file }")") | ||
case .formData(let formData): | ||
for parameter in formData.parameters { | ||
command.append("-F '\(parameter.key)=\(parameter.value)'") | ||
} | ||
|
||
for file in formData.files { | ||
command.append("-F '\(file.key)=@\(file.path ?? "{ Path to the file }");filename=\(file.name)'") | ||
} | ||
} | ||
|
||
return command.joined(separator: " \\\n\t") | ||
} | ||
|
||
} |
105 changes: 105 additions & 0 deletions
105
Source/Common/Request/Logger/Factory/HTTPLogBodyMessageFactory.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
// | ||
// HTTPLogBodyMessageFactory.swift | ||
// RxNetworkKit | ||
// | ||
// Created by Loay Ashraf on 01/10/2024. | ||
// | ||
|
||
import Foundation | ||
|
||
final class HTTPLogBodyMessageFactory { | ||
|
||
/// Makes console body message for outgoing request. | ||
/// | ||
/// - Parameters: | ||
/// - bodyOption: `HTTPLogBodyOption` option for how body is represented in the log message. | ||
/// | ||
/// - Returns: `String` of outgoing request body message. | ||
func make(bodyOption: HTTPLogBodyOption) -> String { | ||
switch bodyOption { | ||
case .plain(let body): | ||
return make(body) | ||
case .file(let file): | ||
return make(file) | ||
case .formData(let formData): | ||
return make(formData) | ||
} | ||
} | ||
|
||
private func make(_ rawData: Data?) -> String { | ||
var logBodyMessage = "" | ||
if let rawData = rawData { | ||
if let jsonString = rawData.json { | ||
logBodyMessage += "\n\(jsonString)\n" | ||
} else { | ||
logBodyMessage += "\n\(String(data: rawData, encoding: .utf8) ?? "{ HTTP Body }")\n" | ||
} | ||
} | ||
return logBodyMessage | ||
} | ||
|
||
/// Makes console body message for outgoing request. | ||
/// | ||
/// - Parameters: | ||
/// - file: `FileType` file details to be printed in place of body. | ||
/// | ||
/// - Returns: `String` of outgoing request body message. | ||
private func make(_ file: FileType) -> String { | ||
var logBodyMessage: String = "" | ||
|
||
if let filePath = file.path { | ||
let fileName = file.name | ||
let fileType = file.mimeType.rawValue | ||
let fileSize = file.size.formattedSize | ||
logBodyMessage += "{ File From Disk }\n" | ||
logBodyMessage += "- Name: \(fileName)\n" | ||
logBodyMessage += "- Type: \(fileType)\n" | ||
logBodyMessage += "- Size: \(fileSize)\n" | ||
logBodyMessage += "- Path: \(filePath)" | ||
} else if file.data != nil { | ||
let fileName = file.name | ||
let fileType = file.mimeType.rawValue | ||
let fileSize = file.size.formattedSize | ||
logBodyMessage += "{ File From Memory }\n" | ||
logBodyMessage += "- Name: \(fileName)\n" | ||
logBodyMessage += "- Type: \(fileType)\n" | ||
logBodyMessage += "- Size: \(fileSize)" | ||
} | ||
|
||
return logBodyMessage | ||
} | ||
|
||
/// Makes console body message for outgoing request. | ||
/// | ||
/// - Parameters: | ||
/// - formData: `FormData` form data details to be printed in place of body. | ||
/// | ||
/// - Returns: `String` of outgoing request body message. | ||
private func make(_ formData: FormData) -> String { | ||
var logBodyMessage: String = "\n" | ||
let boundary = formData.boundary | ||
let lineBreak: String = "\r\n" | ||
|
||
for parameter in formData.parameters { | ||
logBodyMessage += "--\(formData.boundary + lineBreak)" | ||
logBodyMessage += "Content-Disposition: form-data; name=\"\(parameter.key)\"\(lineBreak + lineBreak)" | ||
logBodyMessage += "\(parameter.value + lineBreak)" | ||
} | ||
|
||
for file in formData.files { | ||
logBodyMessage += "--\(boundary + lineBreak)" | ||
logBodyMessage += "Content-Disposition: form-data; name=\"\(file.key)\"; filename=\"\(file.name)\"\(lineBreak)" | ||
logBodyMessage += "Content-Type: \(file.mimeType.rawValue + lineBreak + lineBreak)" | ||
|
||
let fileBodyLogMessage = make(file) | ||
logBodyMessage += fileBodyLogMessage | ||
|
||
logBodyMessage += lineBreak | ||
} | ||
|
||
logBodyMessage += "--\(boundary)--\(lineBreak)" | ||
|
||
return logBodyMessage | ||
} | ||
|
||
} |
127 changes: 127 additions & 0 deletions
127
Source/Common/Request/Logger/Factory/HTTPLogMessageFactory.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
// | ||
// HTTPLogMessageFactory.swift | ||
// RxNetworkKit | ||
// | ||
// Created by Loay Ashraf on 01/10/2024. | ||
// | ||
|
||
import Foundation | ||
|
||
final class HTTPLogMessageFactory { | ||
|
||
private let logBodyMessageFactory: HTTPLogBodyMessageFactory = .init() | ||
private let curlCommandFactory: CURLCommandFactory = .init() | ||
|
||
/// Makes console message for outgoing request. | ||
/// | ||
/// - Parameters: | ||
/// - request: `URLRequest` to be included in message. | ||
/// - bodyOption: `HTTPLogBodyOption` option for how body is represented in the log message. | ||
/// | ||
/// - Returns: `String` of outgoing request message. | ||
func make(for request: URLRequest, bodyOption: HTTPLogBodyOption) -> String { | ||
var logMessage: String = "" | ||
|
||
logMessage += "* * * * * * * * * * OUTGOING REQUEST * * * * * * * * * *\n" | ||
|
||
let urlString = request.url?.absoluteString ?? "" | ||
let urlComponents = URLComponents(string: urlString) | ||
let method = request.httpMethod != nil ? "\(request.httpMethod ?? "")" : "" | ||
let path = "\(urlComponents?.path ?? "")" | ||
let query = "\(urlComponents?.query ?? "")" | ||
let host = "\(urlComponents?.host ?? "")" | ||
|
||
var requestDetails = """ | ||
\n\(urlString) \n | ||
\(method) \(path)?\(query) HTTP/1.1 \n | ||
HOST: \(host)\n | ||
""" | ||
|
||
for (key,value) in request.allHTTPHeaderFields ?? [:] { | ||
requestDetails += "\(key): \(value) \n" | ||
} | ||
|
||
let logBodyMessage = logBodyMessageFactory.make(bodyOption: bodyOption) | ||
requestDetails += logBodyMessage.isEmpty ? "" : "\n" | ||
requestDetails += logBodyMessage | ||
|
||
logMessage += requestDetails | ||
|
||
let curlCommand = curlCommandFactory.make(for: request, bodyOption: bodyOption) | ||
logMessage += """ | ||
\n- - - - - - - - - - - CURL COMMAND - - - - - - - - - - -\n | ||
""" | ||
logMessage += "\n\(curlCommand)\n" | ||
logMessage += "\n* * * * * * * * * * * * * END * * * * * * * * * * * * *\n" | ||
|
||
return logMessage | ||
} | ||
|
||
/// Makes console message for incoming response. | ||
/// | ||
/// - Parameters: | ||
/// - responseArguments: `(URL?, URLResponse?, Data?, Error?)` to be included in message. | ||
/// - bodyLogMessage: `String?` placeholder to be included in message in place of actual body. | ||
/// | ||
/// - Returns: `String` of incoming response message. | ||
func make(for responseArguments: (URL?, Data?, URLResponse?, Error?), bodyLogMessage: String?) -> String { | ||
var logMessage: String = "" | ||
|
||
logMessage += "* * * * * * * * * * INCOMING RESPONSE * * * * * * * * * *\n" | ||
|
||
let url = responseArguments.0 | ||
let httpResponse = responseArguments.2 as? HTTPURLResponse | ||
let responseBody = responseArguments.1 | ||
let responseError = responseArguments.3 | ||
|
||
|
||
let urlString = url?.absoluteString ?? "" | ||
let urlComponents = URLComponents(string: urlString) | ||
let host = "\(urlComponents?.host ?? "")" | ||
let path = "\(urlComponents?.path ?? "")" | ||
let query = "\(urlComponents?.query ?? "")" | ||
|
||
var responseDetails = "" | ||
|
||
responseDetails += "\n\(urlString)" | ||
responseDetails += "\n\n" | ||
|
||
if let statusCode = httpResponse?.statusCode { | ||
responseDetails += "HTTP \(statusCode) \(path)?\(query)\n" | ||
} | ||
|
||
responseDetails += "Host: \(host)\n" | ||
|
||
for (key, value) in httpResponse?.allHeaderFields ?? [:] { | ||
responseDetails += "\(key): \(value)\n" | ||
} | ||
|
||
if let bodyLogMessage = bodyLogMessage, | ||
responseError == nil { | ||
responseDetails += "\n\(bodyLogMessage)\n" | ||
} else if let body = responseBody { | ||
if let jsonString = body.json { | ||
responseDetails += "\n\(jsonString)\n" | ||
} else { | ||
responseDetails += "\n\(String(decoding: body, as: UTF8.self))\n" | ||
} | ||
} | ||
|
||
if let responseError = responseError { | ||
let errorCode = (responseError as NSError).code | ||
if errorCode == -999, | ||
TLSTrustEvaluator.getBlockedHosts().contains(host) { | ||
responseDetails += "\nError: TLS trust evaluation failed for the specified host, you may need to update the pinned certificates or public keys.\n" | ||
} else { | ||
responseDetails += "\nError: \(responseError.localizedDescription)\n" | ||
} | ||
|
||
} | ||
|
||
logMessage += responseDetails | ||
logMessage += "\n* * * * * * * * * * * * * END * * * * * * * * * * * * *\n" | ||
|
||
return logMessage | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// | ||
// HTTPLogBodyOption.swift | ||
// RxNetworkKit | ||
// | ||
// Created by Loay Ashraf on 01/10/2024. | ||
// | ||
|
||
import Foundation | ||
|
||
enum HTTPLogBodyOption { | ||
case plain(_ body: Data? = nil) | ||
case file(_ file: FileType) | ||
case formData(_ formData: FormData) | ||
} |
Oops, something went wrong.