Skip to content

Commit

Permalink
feat: enhance plain and multipart upload + applying SRP
Browse files Browse the repository at this point in the history
Resolves: none.
  • Loading branch information
loay-ashraf committed Oct 1, 2024
1 parent 6c8175c commit b3c2e9e
Show file tree
Hide file tree
Showing 28 changed files with 971 additions and 617 deletions.
122 changes: 93 additions & 29 deletions RxNetworkKit.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

60 changes: 60 additions & 0 deletions Source/Common/Request/Logger/Factory/CURLCommandFactory.swift
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 Source/Common/Request/Logger/Factory/HTTPLogBodyMessageFactory.swift
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 Source/Common/Request/Logger/Factory/HTTPLogMessageFactory.swift
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
}

}
14 changes: 14 additions & 0 deletions Source/Common/Request/Logger/HTTPLogBodyOption.swift
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)
}
Loading

0 comments on commit b3c2e9e

Please sign in to comment.