Skip to content

Commit

Permalink
add form data support
Browse files Browse the repository at this point in the history
  • Loading branch information
rogermolas committed Jan 13, 2020
1 parent dbd144e commit 67eac37
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 25 deletions.
4 changes: 2 additions & 2 deletions Example/Demo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@
1D2CCCE220BD3F56002D0ACC /* Sources */,
1D2CCCE320BD3F56002D0ACC /* Frameworks */,
1D2CCCE420BD3F56002D0ACC /* Resources */,
79D3AEF72DB2D37E9BD18D2B /* [CP] Embed Pods Frameworks */,
34D1D2AE99CF2328B0AD8BD6 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
Expand Down Expand Up @@ -263,7 +263,7 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
79D3AEF72DB2D37E9BD18D2B /* [CP] Embed Pods Frameworks */ = {
34D1D2AE99CF2328B0AD8BD6 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
Expand Down
88 changes: 88 additions & 0 deletions Example/Demo.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1130"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1D2CCCE520BD3F56002D0ACC"
BuildableName = "Demo.app"
BlueprintName = "Demo"
ReferencedContainer = "container:Demo.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1D2CCCF920BD3F58002D0ACC"
BuildableName = "DemoTests.xctest"
BlueprintName = "DemoTests"
ReferencedContainer = "container:Demo.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1D2CCCE520BD3F56002D0ACC"
BuildableName = "Demo.app"
BlueprintName = "Demo"
ReferencedContainer = "container:Demo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1D2CCCE520BD3F56002D0ACC"
BuildableName = "Demo.app"
BlueprintName = "Demo"
ReferencedContainer = "container:Demo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
25 changes: 19 additions & 6 deletions Example/Demo/DestinationViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@ struct Model: Decodable {
}
}

struct PROFILE: Decodable {
var avatar: String?
var message: String?
}


class DestinationViewController: UIViewController {
@IBOutlet weak var textView: UITextView!
@IBOutlet weak var activity: UIActivityIndicatorView!

public var type: String = ""
var GET: RMRequest {
let urlString = "https://httpbin.org/get"
let request = RMRequest(urlString, method: .GET(.URLEncoding), parameters: nil, hearders: nil)
Expand Down Expand Up @@ -90,6 +94,16 @@ class DestinationViewController: UIViewController {
let request = RMRequest(urlString, method: .GET(.URLEncoding), parameters: nil, hearders: nil)
return request
}

var FORM_DATA: RMRequest {
let params = [ "name":"Roger" ]
let urlString = "https://httpbin.org/post"
// let rmRequest = RMRequest(url: URL(string: urlString)!)
// rmRequest.setHttp(method: .POST(.FomDataEncoding))
// rmRequest.setFormData(fields: params)
let request = RMRequest(urlString, method: .POST(.FomDataEncoding), parameters: params, hearders: nil)
return request
}

override func viewDidLoad() {
super.viewDidLoad()
Expand All @@ -99,7 +113,6 @@ class DestinationViewController: UIViewController {
if type == "GET with PARAMS" {
reques(request: GET_PARAMS, expected: JSONObject())
}

if type == "POST" {
reques(request: POST, expected: JSONObject())
}
Expand All @@ -109,18 +122,18 @@ class DestinationViewController: UIViewController {
if type == "POST JSON BODY" {
reques(request: POST_JSON_BODY, expected: JSONObject())
}

if type == "DELETE" {
reques(request: DELETE, expected: JSONObject())
}

if type == "STRING RESPONSE" {
reques(request: GETHTMLString, expected: String())
}

if type == "CODABLE REQUEST" {
reques(request: GET, model: Model.self)
}
if type == "FORM-DATA REQUEST" {
reques(request: FORM_DATA, expected: JSONObject())
}
}

func reques<T:RMHttpProtocol>(request: RMRequest, expected: T) {
Expand Down
5 changes: 5 additions & 0 deletions Example/Demo/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,10 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>
3 changes: 2 additions & 1 deletion Example/Demo/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ class ViewController: UIViewController {
"POST JSON BODY",
"DELETE",
"STRING RESPONSE",
"CODABLE REQUEST"]
"CODABLE REQUEST",
"FORM-DATA REQUEST"]


func decode<T:Decodable> (model: T.Type) {
Expand Down
4 changes: 2 additions & 2 deletions Example/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PODS:
- RMHttp (1.4.1)
- RMHttp (1.5.0)

DEPENDENCIES:
- RMHttp (from `../`)
Expand All @@ -9,7 +9,7 @@ EXTERNAL SOURCES:
:path: "../"

SPEC CHECKSUMS:
RMHttp: b649a2dd258d4c5dd0aededa35b58a59c7247499
RMHttp: fce70e5558179acde55ccb99ef7e0eeb1e9b3314

PODFILE CHECKSUM: 823b377b5b04076a920183be778a664128b2ad2d

Expand Down
2 changes: 1 addition & 1 deletion RMHttp.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

Pod::Spec.new do |s|
s.name = 'RMHttp'
s.version = '1.4.1'
s.version = '1.5.0'
s.summary = 'Lightweight RESTful library for iOS and watchOS'

s.homepage = 'https://github.com/rogermolas/RMHttp'
Expand Down
35 changes: 29 additions & 6 deletions RMHttp/Classes/RMBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,30 @@ private enum HeaderField: String {

private enum HeaderValue: String {
case JSON = "application/json"
case urlEncoded = "application/x-www-form-urlencoded; charset=utf-8"
case URLEncoded = "application/x-www-form-urlencoded; charset=utf-8"
case FormData = "multipart/form-data; boundary="
}

open class RMBuilder {

public func build(request: URLRequest?, parameter: [String: Any]?, method: RMHttpMethod<Encoding>) -> URLRequest {
public func build(request: URLRequest?,
parameter: [String: Any]?,
method: RMHttpMethod<Encoding>) -> URLRequest {

var mUrlRequest = request
guard parameter != nil else { return mUrlRequest! }

if method.encoding == .URLEncoding { // URL Default
if method.encoder == .URLEncoding { // URL Default
// Default Encoding
if encodeParametersInUlr(method: method) {
mUrlRequest = buildQuery(request!, parameters: parameter!)
} else {
mUrlRequest = buildHttpBodyQuery(request!, parameters: parameter!)
}
} else { // JSON Body
} else if method.encoder == .FomDataEncoding { // Form data Body
mUrlRequest = addForm(request: request, parameters: parameter!)

} else { // JSON Body
mUrlRequest = buildJSONHttpBody(request!, parameters: parameter!)
if mUrlRequest?.httpBody == nil {
let data = try! JSONSerialization.data(withJSONObject: parameter ?? "", options: [])
Expand Down Expand Up @@ -102,13 +108,13 @@ open class RMBuilder {
var mUrlRequest = urlRequest
let paramString = build(parameters)
if mUrlRequest.value(forHTTPHeaderField: HeaderField.contentType.rawValue) == nil {
mUrlRequest.setValue(HeaderValue.urlEncoded.rawValue, forHTTPHeaderField: HeaderField.contentType.rawValue)
mUrlRequest.setValue(HeaderValue.URLEncoded.rawValue, forHTTPHeaderField: HeaderField.contentType.rawValue)
}
mUrlRequest.httpBody = paramString.data(using: .utf8, allowLossyConversion: false)
return mUrlRequest
}

// JSON Data Encoding
//MARK: - JSON Data Encoding
private func buildJSONHttpBody(_ urlRequest: URLRequest, parameters: [String:Any]) -> URLRequest? {
var mUrlRequest = urlRequest
do {
Expand All @@ -122,6 +128,23 @@ open class RMBuilder {
}
return urlRequest
}

//MARK: - Form-data Encoding
private func addForm(request: URLRequest?, parameters: [String: Any]) -> URLRequest {
var mUrlRequest = request
let boundary = UUID().uuidString
mUrlRequest?.setValue("multipart/form-data; boundary=\(boundary)",
forHTTPHeaderField: "Content-Type")
var data = Data()
for key in parameters.keys.sorted(by: <) {
let value = parameters[key]!
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
data.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!)
data.append("\(value)".data(using: .utf8)!)
}
mUrlRequest?.httpBody = data
return mUrlRequest!
}

// Check if parameters encoded to HttpBody or within URL
private func encodeParametersInUlr(method: RMHttpMethod<Encoding>) -> Bool {
Expand Down
12 changes: 11 additions & 1 deletion RMHttp/Classes/RMCommons.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ public protocol RMHttpProtocol {
public enum Encoding {
case URLEncoding
case JSONEncoding
case FomDataEncoding

// Parameters Encoding
public var encoding: String {
switch self {
case .URLEncoding: return "URL Default/Query Encoding"
case .JSONEncoding: return "JSON Body Encoding"
case .FomDataEncoding: return "Fom-data Encoding"
}
}
}

// HTTP Methods
Expand All @@ -50,7 +60,7 @@ public enum RMHttpMethod<Encoder> {
case PATCH(Encoder)

// Parameters Encoding
public var encoding: Encoder {
public var encoder: Encoder {
switch self {
case .GET (let encoder): return encoder
case .POST(let encoder): return encoder
Expand Down
37 changes: 31 additions & 6 deletions RMHttp/Classes/RMRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ open class RMRequest {
public var urlRequest:URLRequest!
public var url:URL!
public var parameters:[String : Any]? = nil
public var requestEncoding: String = ""
public var allHeaders:[String : String]? = nil
public var sessionConfig:URLSessionConfiguration!
public var restrictStatusCodes:Set<Int> = [] // e.g 404, 500, return Error
Expand All @@ -38,7 +39,7 @@ open class RMRequest {
public var timeoutIntervalForResource: TimeInterval = 30
public var httpMaximumConnectionsPerHost: Int = 1

private func defaulSessionConfig() {
private func defaulSessionConfig() {
sessionConfig = URLSessionConfiguration.default
sessionConfig.allowsCellularAccess = true // use cellular data
sessionConfig.timeoutIntervalForRequest = timeoutIntervalForRequest // timeout per request
Expand All @@ -52,6 +53,7 @@ open class RMRequest {
hearders: [String : String]!) {

self.url = URL(string: urlString)
self.requestEncoding = method.encoder.encoding
self.urlRequest = URLRequest(url: url!)
self.setHttp(method: method)
self.setHttp(hearders: hearders)
Expand Down Expand Up @@ -99,11 +101,34 @@ open class RMRequest {
public func setValue(value: String, headerField: String) {
self.urlRequest.setValue(value, forHTTPHeaderField: headerField)
}

// Custom Session config
public func setSession(config: URLSessionConfiguration) {
sessionConfig = config
}

// Form-Data
public func setFormData(fields: [String : Any]) {
let boundary = UUID().uuidString
self.urlRequest.setValue("multipart/form-data; boundary=\(boundary)",
forHTTPHeaderField: "Content-Type")
var data = Data()
for (key, value) in fields {
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
data.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!)
data.append("\(value)".data(using: .utf8)!)
}
self.urlRequest.httpBody = data
}

// Custom Http body
public func setHttpBody(data: Data) {
self.urlRequest.httpBody = data
}

public func addForm(params: [String : Any]) {
print(parameters)
}

// Custom Session config
public func setSession(config: URLSessionConfiguration) {
sessionConfig = config
}
}

extension RMRequest: CustomStringConvertible {
Expand Down

0 comments on commit 67eac37

Please sign in to comment.