Skip to content

Commit e257e14

Browse files
authored
Merge pull request #31 from RxSwiftCommunity/http-methods
Add support of HTTP methods
2 parents d5ca9c3 + 00164a7 commit e257e14

13 files changed

+427
-72
lines changed

RxHttpClient.xcodeproj/project.pbxproj

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
6E81B03A1D4A85CF00D099F1 /* MemoryDataCacheProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E81B0391D4A85CF00D099F1 /* MemoryDataCacheProvider.swift */; };
2929
6E9360EF1D2AABE100F80F76 /* RxHttpClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EFB9EB81D2A4E2600E30A5D /* RxHttpClient.framework */; };
3030
6EADD70B1DEB4528006DE223 /* Data+ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EADD70A1DEB4528006DE223 /* Data+ExtensionsTests.swift */; };
31+
6ED01FE01E11A14A00382F94 /* HttpClientSendTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED01FDF1E11A14A00382F94 /* HttpClientSendTests.swift */; };
3132
6ED185511DC7AE1C0071BD4D /* URLRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED185501DC7AE1C0071BD4D /* URLRequestTests.swift */; };
3233
6EF8CA431D5664520095A634 /* StreamDataTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFB9EEB1D2A57B600E30A5D /* StreamDataTaskTests.swift */; };
3334
6EFB9EE61D2A563000E30A5D /* MimeTypeConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFB9EE51D2A563000E30A5D /* MimeTypeConverter.swift */; };
@@ -66,6 +67,7 @@
6667
6E81B0371D4A850100D099F1 /* HttpClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpClient.swift; sourceTree = "<group>"; };
6768
6E81B0391D4A85CF00D099F1 /* MemoryDataCacheProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryDataCacheProvider.swift; sourceTree = "<group>"; };
6869
6EADD70A1DEB4528006DE223 /* Data+ExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+ExtensionsTests.swift"; sourceTree = "<group>"; };
70+
6ED01FDF1E11A14A00382F94 /* HttpClientSendTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpClientSendTests.swift; sourceTree = "<group>"; };
6971
6ED185501DC7AE1C0071BD4D /* URLRequestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLRequestTests.swift; sourceTree = "<group>"; };
7072
6EFB9EB81D2A4E2600E30A5D /* RxHttpClient.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RxHttpClient.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7173
6EFB9EBD1D2A4E2600E30A5D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -149,6 +151,7 @@
149151
children = (
150152
6EFB9EF01D2A587100E30A5D /* Fake.swift */,
151153
6E5B3CE71D2D1F7400775D9F /* HttpClientBasicTests.swift */,
154+
6ED01FDF1E11A14A00382F94 /* HttpClientSendTests.swift */,
152155
6E190F8B1DECC320002BA10F /* HttpClientCachingTests.swift */,
153156
6E190F8C1DECC320002BA10F /* HttpRequestFileSystemCacheProviderTests.swift */,
154157
6EFB9EEA1D2A57B600E30A5D /* MemoryCacheProviderTests.swift */,
@@ -322,6 +325,7 @@
322325
6E2690311DEB49BC00EA5CCE /* (null) in Sources */,
323326
6E190F8D1DECC320002BA10F /* HttpClientCachingTests.swift in Sources */,
324327
6E1329B71D478B7600BE6270 /* MemoryCacheProviderTests.swift in Sources */,
328+
6ED01FE01E11A14A00382F94 /* HttpClientSendTests.swift in Sources */,
325329
6E1329B81D47903E00BE6270 /* NSURLSessionTypeTests.swift in Sources */,
326330
6E5B3CF01D2D43C000775D9F /* MimeTypeConverterTests.swift in Sources */,
327331
6E190F8E1DECC320002BA10F /* HttpRequestFileSystemCacheProviderTests.swift in Sources */,
@@ -499,7 +503,7 @@
499503
PRODUCT_BUNDLE_IDENTIFIER = com.AntonEfimenko.RxHttpClientTests;
500504
PRODUCT_NAME = "$(TARGET_NAME)";
501505
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
502-
SWIFT_VERSION = 3.0.1;
506+
SWIFT_VERSION = 3.0;
503507
};
504508
name = Debug;
505509
};
@@ -515,7 +519,7 @@
515519
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
516520
PRODUCT_BUNDLE_IDENTIFIER = com.AntonEfimenko.RxHttpClientTests;
517521
PRODUCT_NAME = "$(TARGET_NAME)";
518-
SWIFT_VERSION = 3.0.1;
522+
SWIFT_VERSION = 3.0;
519523
};
520524
name = Release;
521525
};

RxHttpClient/HttpClient.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public final class HttpClient {
3939
}
4040

4141
extension HttpClient : HttpClientType {
42-
public func request(_ request: URLRequest, dataCacheProvider: DataCacheProviderType?) -> Observable<StreamTaskEvents> {
42+
public func request(_ request: URLRequest, dataCacheProvider: DataCacheProviderType? = nil) -> Observable<StreamTaskEvents> {
4343
return Observable.create { [weak self] observer in
4444
guard let object = self else { observer.onCompleted(); return Disposables.create() }
4545

RxHttpClient/HttpClientError.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,8 @@ public enum HttpClientError : Error {
2626
- parameter error: The error occurred while deserialization (throwed by JSONSerialization object)
2727
*/
2828
case jsonDeserializationError(error: Error)
29+
/**
30+
This error occurred when invalid JSON object was passed
31+
*/
32+
case invalidJsonObject
2933
}

RxHttpClient/HttpClientType+Extensions.swift

Lines changed: 90 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import RxSwift
33

44
/// Represents caching settings
55
public struct CacheMode {
6-
/// If true, response for GET request will be cached
6+
/// If true, response for GET request will be cached
77
public let cacheResponse: Bool
8-
/// If true, HttpClient will immediately return cacged respons if it exists
8+
/// If true, HttpClient will immediately return cacged respons if it exists
99
public let returnCachedResponse: Bool
10-
/// If true, HttpClient will invoke request
10+
/// If true, HttpClient will invoke request
1111
public let invokeRequest: Bool
1212

1313
public init(cacheResponse: Bool = true, returnCachedResponse: Bool = true, invokeRequest: Bool = true) {
@@ -16,15 +16,17 @@ public struct CacheMode {
1616
self.invokeRequest = invokeRequest
1717
}
1818

19-
/// Only cached response will be returned
20-
public static let cacheOnly = { return CacheMode(cacheResponse: false, returnCachedResponse: true, invokeRequest: false) }()
21-
/// Cached response will not be returned even if exists
22-
public static let withoutCache = { return CacheMode(cacheResponse: true, returnCachedResponse: false, invokeRequest: true) }()
23-
/// Response will not be cached
24-
public static let notCacheResponse = { return CacheMode(cacheResponse: false, returnCachedResponse: false, invokeRequest: true) }()
19+
/// Only cached response will be returned
20+
public static let cacheOnly = CacheMode(cacheResponse: false, returnCachedResponse: true, invokeRequest: false)
21+
/// Cached response will not be returned even if exists
22+
public static let withoutCache = CacheMode(cacheResponse: true, returnCachedResponse: false, invokeRequest: true)
23+
/// Response will not be cached
24+
public static let notCacheResponse = CacheMode(cacheResponse: false, returnCachedResponse: false, invokeRequest: true)
25+
/// All conditions are true
26+
public static let `default` = CacheMode(cacheResponse: true, returnCachedResponse: true, invokeRequest: true)
2527
}
2628

27-
public extension HttpClientType {
29+
public extension HttpClientType {
2830
/**
2931
Creates StreamDataTask
3032
- parameter request: URL request
@@ -38,36 +40,73 @@ public extension HttpClientType {
3840
/**
3941
Creates streaming observable for URL
4042
- parameter request: URL
43+
- parameter method: HTTP method for request
44+
- parameter body: Data that will be set to httpBody property of URLRequest
45+
- parameter httpHeaders: HTTP headers for request
4146
- parameter dataCacheProvider: Cache provider, that will be used to cache downloaded data
4247
- returns: Created observable that emits stream events
4348
*/
44-
func request(url: URL, dataCacheProvider: DataCacheProviderType? = nil) -> Observable<StreamTaskEvents> {
45-
return request(URLRequest(url: url), dataCacheProvider: dataCacheProvider)
49+
func request(url: URL, method: HttpMethod = .get, body: Data? = nil, httpHeaders: [String: String] = [:],
50+
dataCacheProvider: DataCacheProviderType? = nil) -> Observable<StreamTaskEvents> {
51+
return request(URLRequest(url: url, method: method, body: body, headers: httpHeaders), dataCacheProvider: dataCacheProvider)
4652
}
47-
53+
4854
/**
49-
Creates streaming observable for request
50-
- parameter request: URL request
55+
Creates streaming observable for URL
56+
- parameter request: URL
57+
- parameter method: HTTP method for request
58+
- parameter jsonBody: JSON object that will be serialized and send as HTTP Body
59+
- parameter options: Options for JSON serialization
60+
- parameter httpHeaders: HTTP headers for request
61+
- parameter dataCacheProvider: Cache provider, that will be used to cache downloaded data
5162
- returns: Created observable that emits stream events
5263
*/
53-
func request(_ urlRequest: URLRequest) -> Observable<StreamTaskEvents> {
54-
return request(urlRequest, dataCacheProvider: nil)
64+
func request(url: URL, method: HttpMethod = .get, jsonBody: Any, options: JSONSerialization.WritingOptions = [], httpHeaders: [String: String] = [:],
65+
dataCacheProvider: DataCacheProviderType? = nil) -> Observable<StreamTaskEvents> {
66+
guard let req = URLRequest(url: url, method: method, jsonBody: jsonBody, options: options, headers: httpHeaders) else {
67+
return Observable.error(HttpClientError.invalidJsonObject)
68+
}
69+
70+
return request(req, dataCacheProvider: dataCacheProvider)
5571
}
5672

5773
/**
5874
Creates streaming observable for URL
5975
- parameter request: URL
60-
- parameter requestCacheMode: CacheMode for request
76+
- parameter method: HTTP method for request
77+
- parameter body: Data that will be set to httpBody property of URLRequest
78+
- parameter httpHeaders: HTTP headers for request
79+
- parameter requestCacheMode: CacheMode for request
6180
- returns: Created observable that emits deserialized JSON object of HTTP request
6281
*/
63-
func requestJson(url: URL, requestCacheMode: CacheMode = CacheMode()) -> Observable<Any> {
64-
return requestJson(URLRequest(url: url), requestCacheMode: requestCacheMode)
82+
func requestJson(url: URL, method: HttpMethod = .get, body: Data? = nil, httpHeaders: [String: String] = [:],
83+
requestCacheMode: CacheMode = CacheMode()) -> Observable<Any> {
84+
return requestJson(URLRequest(url: url, method: method, body: body, headers: httpHeaders), requestCacheMode: requestCacheMode)
85+
}
86+
87+
/**
88+
Creates streaming observable for URL
89+
- parameter request: URL
90+
- parameter method: HTTP method for request
91+
- parameter jsonBody: JSON object that will be serialized and send as HTTP Body
92+
- parameter options: Options for JSON serialization
93+
- parameter httpHeaders: HTTP headers for request
94+
- parameter requestCacheMode: CacheMode for request
95+
- returns: Created observable that emits deserialized JSON object of HTTP request
96+
*/
97+
func requestJson(url: URL, method: HttpMethod = .get, jsonBody: Any, options: JSONSerialization.WritingOptions = [], httpHeaders: [String: String] = [:],
98+
requestCacheMode: CacheMode = CacheMode()) -> Observable<Any> {
99+
guard let req = URLRequest(url: url, method: method, jsonBody: jsonBody, options: options, headers: httpHeaders) else {
100+
return Observable.error(HttpClientError.invalidJsonObject)
101+
}
102+
103+
return requestJson(req, requestCacheMode: requestCacheMode)
65104
}
66105

67106
/**
68107
Creates streaming observable for request
69108
- parameter request: URL request
70-
- parameter requestCacheMode: CacheMode for request
109+
- parameter requestCacheMode: CacheMode for request
71110
- returns: Created observable that emits deserialized JSON object of HTTP request
72111
*/
73112
func requestJson(_ urlRequest: URLRequest, requestCacheMode: CacheMode = CacheMode()) -> Observable<Any> {
@@ -85,17 +124,40 @@ public extension HttpClientType {
85124
/**
86125
Creates an observable for URL
87126
- parameter request: URL
88-
- parameter requestCacheMode: CacheMode for request
127+
- parameter method: HTTP method for request
128+
- parameter body: Data that will be set to httpBody property of URLRequest
129+
- parameter httpHeaders: HTTP headers for request
130+
- parameter requestCacheMode: CacheMode for request
131+
- returns: Created observable that emits Data of HTTP request
132+
*/
133+
func requestData(url: URL, method: HttpMethod = .get, body: Data? = nil, httpHeaders: [String: String] = [:],
134+
requestCacheMode: CacheMode = CacheMode()) -> Observable<Data> {
135+
return requestData(URLRequest(url: url, method: method, body: body, headers: httpHeaders), requestCacheMode: requestCacheMode)
136+
}
137+
138+
/**
139+
Creates an observable for URL
140+
- parameter request: URL
141+
- parameter method: HTTP method for request
142+
- parameter jsonBody: JSON object that will be serialized and send as HTTP Body
143+
- parameter options: Options for JSON serialization
144+
- parameter httpHeaders: HTTP headers for request
145+
- parameter requestCacheMode: CacheMode for request
89146
- returns: Created observable that emits Data of HTTP request
90147
*/
91-
func requestData(url: URL, requestCacheMode: CacheMode = CacheMode()) -> Observable<Data> {
92-
return requestData(URLRequest(url: url), requestCacheMode: requestCacheMode)
148+
func requestData(url: URL, method: HttpMethod = .get, jsonBody: Any, options: JSONSerialization.WritingOptions = [], httpHeaders: [String: String] = [:],
149+
requestCacheMode: CacheMode = CacheMode()) -> Observable<Data> {
150+
guard let req = URLRequest(url: url, method: method, jsonBody: jsonBody, options: options, headers: httpHeaders) else {
151+
return Observable.error(HttpClientError.invalidJsonObject)
152+
}
153+
154+
return requestData(req, requestCacheMode: requestCacheMode)
93155
}
94156

95157
/**
96158
Creates an observable for request
97159
- parameter request: URL request
98-
- parameter requestCacheMode: CacheMode for request
160+
- parameter requestCacheMode: CacheMode for request
99161
- returns: Created observable that emits Data of HTTP request
100162
*/
101163
func requestData(_ urlRequest: URLRequest, requestCacheMode: CacheMode = CacheMode()) -> Observable<Data> {
@@ -105,7 +167,7 @@ public extension HttpClientType {
105167
var errorResponse: HTTPURLResponse? = nil
106168

107169
let cachedRequest: Observable<Data> = {
108-
if urlRequest.httpMethod == "GET", requestCacheMode.returnCachedResponse, let url = urlRequest.url, let cached = urlRequestCacheProvider?.load(resourceUrl: url) {
170+
if urlRequest.httpMethod == HttpMethod.get.rawValue, requestCacheMode.returnCachedResponse, let url = urlRequest.url, let cached = urlRequestCacheProvider?.load(resourceUrl: url) {
109171
// return cached response
110172
return Observable.just(cached)
111173
}
@@ -116,7 +178,7 @@ public extension HttpClientType {
116178
// if we should not invoke request, simply return cache request
117179
return cachedRequest
118180
}
119-
181+
120182
return cachedRequest.concat(request(urlRequest, dataCacheProvider: dataCacheProvider)
121183
.flatMapLatest { [weak self] result -> Observable<Data> in
122184
switch result {
@@ -137,7 +199,7 @@ public extension HttpClientType {
137199
guard let errorResponse = errorResponse else {
138200
let requestData = dataCacheProvider.getData()
139201

140-
if urlRequest.httpMethod == "GET", requestCacheMode.cacheResponse, let url = urlRequest.url {
202+
if urlRequest.httpMethod == HttpMethod.get.rawValue, requestCacheMode.cacheResponse, let url = urlRequest.url {
141203
// sache response
142204
self?.urlRequestCacheProvider?.save(resourceUrl: url, data: requestData)
143205
}

RxHttpClient/HttpClientType.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
import Foundation
22
import RxSwift
33

4+
public enum HttpMethod: String {
5+
case options = "OPTIONS"
6+
case get = "GET"
7+
case head = "HEAD"
8+
case post = "POST"
9+
case put = "PUT"
10+
case patch = "PATCH"
11+
case delete = "DELETE"
12+
case trace = "TRACE"
13+
case connect = "CONNECT"
14+
}
15+
416
public protocol HttpClientType : class {
517
/**
618
Creates streaming observable for request

RxHttpClient/HttpExtensions.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,20 @@ public extension URL {
4848

4949
//URLRequest
5050
public extension URLRequest {
51-
init(url: URL, headers: [String: String]?) {
51+
init(url: URL, method: HttpMethod = .get, body: Data? = nil, headers: [String: String]) {
5252
self = URLRequest(url: url)
53-
headers?.forEach { addValue($1, forHTTPHeaderField: $0) }
53+
self.httpMethod = method.rawValue
54+
self.httpBody = body
55+
headers.forEach { addValue($1, forHTTPHeaderField: $0) }
56+
}
57+
58+
init?(url: URL, method: HttpMethod = .get, jsonBody: Any,
59+
options: JSONSerialization.WritingOptions = [], headers: [String: String]) {
60+
guard JSONSerialization.isValidJSONObject(jsonBody) else { return nil }
61+
if let body: Data = try? JSONSerialization.data(withJSONObject: jsonBody, options: options) {
62+
self.init(url: url, method: method, body: body, headers: headers)
63+
} else {
64+
return nil
65+
}
5466
}
5567
}

RxHttpClientTests/Fake.swift

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,25 @@ class FakeDataTask : NSObject, URLSessionDataTaskType {
66
var originalRequest: URLRequest?
77
var isCancelled = false
88
var resumeInvokeCount = 0
9-
let resumeClosure: () -> ()!
10-
let cancelClosure: (() -> ())?
9+
let resumeClosure: (FakeDataTask) -> ()!
10+
let cancelClosure: ((FakeDataTask) -> ())?
1111

1212
var state: URLSessionTask.State = URLSessionTask.State.suspended
1313

14-
init(resumeClosure: @escaping () -> (), cancelClosure: (() -> ())? = nil) {
14+
init(resumeClosure: @escaping (FakeDataTask) -> (), cancelClosure: ((FakeDataTask) -> ())? = nil) {
1515
self.resumeClosure = resumeClosure
1616
self.cancelClosure = cancelClosure
1717
}
1818

1919
open func resume() {
2020
resumeInvokeCount += 1
2121
state = .running
22-
resumeClosure()
22+
resumeClosure(self)
2323
}
2424

2525
open func cancel() {
2626
if !isCancelled {
27-
cancelClosure?()
27+
cancelClosure?(self)
2828
state = .suspended
2929
isCancelled = true
3030
}
@@ -35,6 +35,8 @@ class FakeSession : URLSessionType {
3535
var task: FakeDataTask!
3636
var isFinished = false
3737

38+
var customFakeTask: ((URLRequest) -> (FakeDataTask))?
39+
3840
var state: URLSessionTask.State { return task.state }
3941

4042
var configuration: URLSessionConfiguration = URLSessionConfiguration.default
@@ -54,6 +56,10 @@ class FakeSession : URLSessionType {
5456
}
5557

5658
func dataTaskWithRequest(_ request: URLRequest) -> URLSessionDataTaskType {
59+
if let customFakeTask = customFakeTask?(request) {
60+
return customFakeTask
61+
}
62+
5763
if task == nil { fatalError("Data task not specified") }
5864
task.originalRequest = request
5965
return task

0 commit comments

Comments
 (0)