提交 12a10a2a 编写于 作者: C Christian Noon

Merge pull request #539 from Alamofire/feature/multipart_form_data

Merge pull request #539 from Alamofire/feature/multipart_form_data.
......@@ -7,8 +7,16 @@
objects = {
/* Begin PBXBuildFile section */
4C23EB431B327C5B0090E0BC /* MultipartFormData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C23EB421B327C5B0090E0BC /* MultipartFormData.swift */; };
4C23EB441B327C5B0090E0BC /* MultipartFormData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C23EB421B327C5B0090E0BC /* MultipartFormData.swift */; };
4C256A531B096C770065714F /* BaseTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C256A501B096C2C0065714F /* BaseTestCase.swift */; };
4C256A541B096C770065714F /* BaseTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C256A501B096C2C0065714F /* BaseTestCase.swift */; };
4C3238E71B3604DB00FE04AE /* MultipartFormDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3238E61B3604DB00FE04AE /* MultipartFormDataTests.swift */; };
4C3238E81B3604DB00FE04AE /* MultipartFormDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3238E61B3604DB00FE04AE /* MultipartFormDataTests.swift */; };
4C3238EC1B3617BB00FE04AE /* rainbow.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 4C3238EA1B3617BB00FE04AE /* rainbow.jpg */; };
4C3238ED1B3617BB00FE04AE /* rainbow.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 4C3238EA1B3617BB00FE04AE /* rainbow.jpg */; };
4C3238EE1B3617BB00FE04AE /* unicorn.png in Resources */ = {isa = PBXBuildFile; fileRef = 4C3238EB1B3617BB00FE04AE /* unicorn.png */; };
4C3238EF1B3617BB00FE04AE /* unicorn.png in Resources */ = {isa = PBXBuildFile; fileRef = 4C3238EB1B3617BB00FE04AE /* unicorn.png */; };
4C341BBA1B1A865A00C1B34D /* CacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C341BB91B1A865A00C1B34D /* CacheTests.swift */; };
4C341BBB1B1A865A00C1B34D /* CacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C341BB91B1A865A00C1B34D /* CacheTests.swift */; };
4CCFA79A1B2BE71600B6F460 /* URLProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCFA7991B2BE71600B6F460 /* URLProtocolTests.swift */; };
......@@ -70,7 +78,11 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
4C23EB421B327C5B0090E0BC /* MultipartFormData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipartFormData.swift; sourceTree = "<group>"; };
4C256A501B096C2C0065714F /* BaseTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTestCase.swift; sourceTree = "<group>"; };
4C3238E61B3604DB00FE04AE /* MultipartFormDataTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipartFormDataTests.swift; sourceTree = "<group>"; };
4C3238EA1B3617BB00FE04AE /* rainbow.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = rainbow.jpg; path = Resources/rainbow.jpg; sourceTree = "<group>"; };
4C3238EB1B3617BB00FE04AE /* unicorn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = unicorn.png; path = Resources/unicorn.png; sourceTree = "<group>"; };
4C341BB91B1A865A00C1B34D /* CacheTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacheTests.swift; sourceTree = "<group>"; };
4CCFA7991B2BE71600B6F460 /* URLProtocolTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLProtocolTests.swift; sourceTree = "<group>"; };
4CDE2C361AF8932A00BABAE5 /* Manager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Manager.swift; sourceTree = "<group>"; };
......@@ -149,6 +161,7 @@
children = (
4C341BB91B1A865A00C1B34D /* CacheTests.swift */,
F8111E5B19A9674D0040E7D1 /* DownloadTests.swift */,
4C3238E61B3604DB00FE04AE /* MultipartFormDataTests.swift */,
F86AEFE51AE6A282007D9C76 /* TLSEvaluationTests.swift */,
F8111E5F19A9674D0040E7D1 /* UploadTests.swift */,
4CCFA7991B2BE71600B6F460 /* URLProtocolTests.swift */,
......@@ -157,6 +170,15 @@
name = Features;
sourceTree = "<group>";
};
4C3238E91B3617A600FE04AE /* Resources */ = {
isa = PBXGroup;
children = (
4C3238EA1B3617BB00FE04AE /* rainbow.jpg */,
4C3238EB1B3617BB00FE04AE /* unicorn.png */,
);
name = Resources;
sourceTree = "<group>";
};
4CDE2C481AF8A14A00BABAE5 /* Core */ = {
isa = PBXGroup;
children = (
......@@ -171,6 +193,7 @@
isa = PBXGroup;
children = (
4CDE2C3C1AF89D4900BABAE5 /* Download.swift */,
4C23EB421B327C5B0090E0BC /* MultipartFormData.swift */,
4CDE2C451AF89FF300BABAE5 /* ResponseSerialization.swift */,
4CDE2C3F1AF89E0700BABAE5 /* Upload.swift */,
4CDE2C421AF89F0900BABAE5 /* Validation.swift */,
......@@ -224,6 +247,7 @@
4C256A501B096C2C0065714F /* BaseTestCase.swift */,
4C256A4E1B09656A0065714F /* Core */,
4C256A4F1B09656E0065714F /* Features */,
4C3238E91B3617A600FE04AE /* Resources */,
F8111E4019A95C8B0040E7D1 /* Supporting Files */,
);
path = Tests;
......@@ -393,6 +417,8 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4C3238EE1B3617BB00FE04AE /* unicorn.png in Resources */,
4C3238EC1B3617BB00FE04AE /* rainbow.jpg in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -400,6 +426,8 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4C3238EF1B3617BB00FE04AE /* unicorn.png in Resources */,
4C3238ED1B3617BB00FE04AE /* rainbow.jpg in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -416,6 +444,7 @@
4CDE2C471AF89FF300BABAE5 /* ResponseSerialization.swift in Sources */,
4CDE2C381AF8932A00BABAE5 /* Manager.swift in Sources */,
4DD67C251A5C590000ED2280 /* Alamofire.swift in Sources */,
4C23EB441B327C5B0090E0BC /* MultipartFormData.swift in Sources */,
4CDE2C3E1AF89D4900BABAE5 /* Download.swift in Sources */,
4CDE2C441AF89F0900BABAE5 /* Validation.swift in Sources */,
);
......@@ -431,6 +460,7 @@
4CDE2C461AF89FF300BABAE5 /* ResponseSerialization.swift in Sources */,
4CDE2C371AF8932A00BABAE5 /* Manager.swift in Sources */,
F897FF4119AA800700AB5182 /* Alamofire.swift in Sources */,
4C23EB431B327C5B0090E0BC /* MultipartFormData.swift in Sources */,
4CDE2C3D1AF89D4900BABAE5 /* Download.swift in Sources */,
4CDE2C431AF89F0900BABAE5 /* Validation.swift in Sources */,
);
......@@ -440,6 +470,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4C3238E71B3604DB00FE04AE /* MultipartFormDataTests.swift in Sources */,
4C341BBA1B1A865A00C1B34D /* CacheTests.swift in Sources */,
4CCFA79A1B2BE71600B6F460 /* URLProtocolTests.swift in Sources */,
F86AEFE71AE6A312007D9C76 /* TLSEvaluationTests.swift in Sources */,
......@@ -459,6 +490,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4C3238E81B3604DB00FE04AE /* MultipartFormDataTests.swift in Sources */,
4C341BBB1B1A865A00C1B34D /* CacheTests.swift in Sources */,
4CCFA79B1B2BE71600B6F460 /* URLProtocolTests.swift in Sources */,
F829C6BE1A7A950600A2CD59 /* ParameterEncodingTests.swift in Sources */,
......
......@@ -10,7 +10,7 @@ Alamofire is an HTTP networking library written in Swift.
- [x] Chainable Request / Response methods
- [x] URL / JSON / plist Parameter Encoding
- [x] Upload File / Data / Stream
- [x] Upload File / Data / Stream / MultipartFormData
- [x] Download using Request or Resume data
- [x] Authentication with NSURLCredential
- [x] HTTP Response Validation
......@@ -298,6 +298,7 @@ Caching is handled on the system framework level by [`NSURLCache`](https://devel
- File
- Data
- Stream
- MultipartFormData
#### Uploading a File
......@@ -309,7 +310,7 @@ let fileURL = NSBundle.mainBundle()
Alamofire.upload(.POST, "http://httpbin.org/post", file: fileURL)
```
#### Uploading w/Progress
#### Uploading with Progress
```swift
Alamofire.upload(.POST, "http://httpbin.org/post", file: fileURL)
......@@ -321,6 +322,29 @@ Alamofire.upload(.POST, "http://httpbin.org/post", file: fileURL)
}
```
#### Uploading MultipartFormData
```swift
Alamofire.upload(
.POST,
URLString: "http://httpbin.org/post",
multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(fileURL: unicornImageURL, name: "unicorn")
multipartFormData.appendBodyPart(fileURL: rainbowImageURL, name: "rainbow")
},
encodingCompletion: { encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
upload.responseJSON { request, response, JSON, error in
println(JSON)
}
case .Failure(let encodingError):
println(encodingError)
}
}
)
```
### Downloading
**Supported Download Types**
......@@ -850,7 +874,6 @@ Use AFNetworking for any of the following:
- TLS verification, using `AFSecurityManager`
- Situations requiring `NSOperation` or `NSURLConnection`, using `AFURLConnectionOperation`
- Network reachability monitoring, using `AFNetworkReachabilityManager`
- Multipart HTTP request construction, using `AFHTTPRequestSerializer`
### What's the origin of the name Alamofire?
......
......@@ -24,6 +24,8 @@ import Foundation
/// Alamofire errors
public let AlamofireErrorDomain = "com.alamofire.error"
public let AlamofireInputStreamReadFailed = -6000
public let AlamofireOutputStreamWriteFailed = -6001
// MARK: - URLStringConvertible
......@@ -204,6 +206,57 @@ public func upload(URLRequest: URLRequestConvertible, stream: NSInputStream) ->
return Manager.sharedInstance.upload(URLRequest, stream: stream)
}
// MARK: MultipartFormData
/**
Creates an upload request using the shared manager instance for the specified method and URL string.
:param: method The HTTP method.
:param: URLString The URL string.
:param: multipartFormData The closure used to append body parts to the `MultipartFormData`.
:param: encodingMemoryThreshold The encoding memory threshold in bytes. `MultipartFormDataEncodingMemoryThreshold`
by default.
:param: encodingCompletion The closure called when the `MultipartFormData` encoding is complete.
*/
public func upload(
method: Method,
#URLString: URLStringConvertible,
#multipartFormData: MultipartFormData -> Void,
encodingMemoryThreshold: UInt64 = Manager.MultipartFormDataEncodingMemoryThreshold,
#encodingCompletion: (Manager.MultipartFormDataEncodingResult -> Void)?)
{
return Manager.sharedInstance.upload(
method,
URLString,
multipartFormData: multipartFormData,
encodingMemoryThreshold: encodingMemoryThreshold,
encodingCompletion: encodingCompletion
)
}
/**
Creates an upload request using the shared manager instance for the specified method and URL string.
:param: URLRequest The URL request.
:param: multipartFormData The closure used to append body parts to the `MultipartFormData`.
:param: encodingMemoryThreshold The encoding memory threshold in bytes. `MultipartFormDataEncodingMemoryThreshold`
by default.
:param: encodingCompletion The closure called when the `MultipartFormData` encoding is complete.
*/
public func upload(
URLRequest: URLRequestConvertible,
#multipartFormData: MultipartFormData -> Void,
encodingMemoryThreshold: UInt64 = Manager.MultipartFormDataEncodingMemoryThreshold,
#encodingCompletion: (Manager.MultipartFormDataEncodingResult -> Void)?)
{
return Manager.sharedInstance.upload(
URLRequest,
multipartFormData: multipartFormData,
encodingMemoryThreshold: encodingMemoryThreshold,
encodingCompletion: encodingCompletion
)
}
// MARK: - Download Methods
// MARK: URL Request
......
此差异已折叠。
......@@ -156,6 +156,153 @@ extension Manager {
public func upload(method: Method, _ URLString: URLStringConvertible, stream: NSInputStream) -> Request {
return upload(URLRequest(method, URLString), stream: stream)
}
// MARK: MultipartFormData
/// Default memory threshold used when encoding `MultipartFormData`.
public static let MultipartFormDataEncodingMemoryThreshold: UInt64 = 10 * 1024 * 1024
/**
Defines whether the `MultipartFormData` encoding was successful and contains result of the encoding as
associated values.
- Success: Represents a successful `MultipartFormData` encoding and contains the new `Request` along with
streaming information.
- Failure: Used to represent a failure in the `MultipartFormData` encoding and also contains the encoding
error.
*/
public enum MultipartFormDataEncodingResult {
case Success(request: Request, streamingFromDisk: Bool, streamFileURL: NSURL?)
case Failure(NSError)
}
/**
Encodes the `MultipartFormData` and creates a request to upload the result to the specified URL request.
It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
used for larger payloads such as video content.
The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
technique was used.
If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
:param: method The HTTP method.
:param: URLString The URL string.
:param: multipartFormData The closure used to append body parts to the `MultipartFormData`.
:param: encodingMemoryThreshold The encoding memory threshold in bytes. `MultipartFormDataEncodingMemoryThreshold`
by default.
:param: encodingCompletion The closure called when the `MultipartFormData` encoding is complete.
*/
public func upload(
method: Method,
_ URLString: URLStringConvertible,
multipartFormData: MultipartFormData -> Void,
encodingMemoryThreshold: UInt64 = Manager.MultipartFormDataEncodingMemoryThreshold,
encodingCompletion: (MultipartFormDataEncodingResult -> Void)?)
{
let urlRequest = URLRequest(method, URLString)
return upload(
urlRequest,
multipartFormData: multipartFormData,
encodingMemoryThreshold: encodingMemoryThreshold,
encodingCompletion: encodingCompletion
)
}
/**
Encodes the `MultipartFormData` and creates a request to upload the result to the specified URL request.
It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
used for larger payloads such as video content.
The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
technique was used.
If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
:param: URLRequest The URL request.
:param: multipartFormData The closure used to append body parts to the `MultipartFormData`.
:param: encodingMemoryThreshold The encoding memory threshold in bytes. `MultipartFormDataEncodingMemoryThreshold`
by default.
:param: encodingCompletion The closure called when the `MultipartFormData` encoding is complete.
*/
public func upload(
URLRequest: URLRequestConvertible,
multipartFormData: MultipartFormData -> Void,
encodingMemoryThreshold: UInt64 = Manager.MultipartFormDataEncodingMemoryThreshold,
encodingCompletion: (MultipartFormDataEncodingResult -> Void)?)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let formData = MultipartFormData()
multipartFormData(formData)
let URLRequestWithContentType = URLRequest.URLRequest.mutableCopy() as! NSMutableURLRequest
URLRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type")
if formData.contentLength < encodingMemoryThreshold {
let encodingResult = formData.encode()
dispatch_async(dispatch_get_main_queue()) {
switch encodingResult {
case .Success(let data):
let encodingResult = MultipartFormDataEncodingResult.Success(
request: self.upload(URLRequestWithContentType, data: data),
streamingFromDisk: false,
streamFileURL: nil
)
encodingCompletion?(encodingResult)
case .Failure(let error):
encodingCompletion?(.Failure(error))
}
}
} else {
let fileManager = NSFileManager.defaultManager()
let tempDirectoryURL = NSURL(fileURLWithPath: NSTemporaryDirectory())!
let directoryURL = tempDirectoryURL.URLByAppendingPathComponent("com.alamofire.manager/multipart.form.data")
let fileName = NSUUID().UUIDString
let fileURL = directoryURL.URLByAppendingPathComponent(fileName)
var error: NSError?
if fileManager.createDirectoryAtURL(directoryURL, withIntermediateDirectories: true, attributes: nil, error: &error) {
formData.writeEncodedDataToDisk(fileURL) { error in
dispatch_async(dispatch_get_main_queue()) {
if let error = error {
encodingCompletion?(.Failure(error))
} else {
let encodingResult = MultipartFormDataEncodingResult.Success(
request: self.upload(URLRequestWithContentType, file: fileURL),
streamingFromDisk: true,
streamFileURL: fileURL
)
encodingCompletion?(encodingResult)
}
}
}
} else {
dispatch_async(dispatch_get_main_queue()) {
encodingCompletion?(.Failure(error!))
}
}
}
}
}
}
// MARK: -
......
......@@ -26,4 +26,9 @@ import XCTest
class BaseTestCase: XCTestCase {
let defaultTimeout: NSTimeInterval = 10
func URLForResource(fileName: String, withExtension: String) -> NSURL {
let bundle = NSBundle(forClass: BaseTestCase.self)
return bundle.URLForResource(fileName, withExtension: withExtension)!
}
}
此差异已折叠。
......@@ -24,8 +24,8 @@ import Alamofire
import Foundation
import XCTest
class UploadResponseTestCase: BaseTestCase {
func testUploadRequest() {
class UploadDataTestCase: BaseTestCase {
func testUploadDataRequest() {
// Given
let URLString = "http://httpbin.org/post"
let data = "Lorem ipsum dolor sit amet".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
......@@ -54,7 +54,7 @@ class UploadResponseTestCase: BaseTestCase {
XCTAssertNil(error, "error should be nil")
}
func testUploadRequestWithProgress() {
func testUploadDataRequestWithProgress() {
// Given
let URLString = "http://httpbin.org/post"
let data: NSData = {
......@@ -114,7 +114,430 @@ class UploadResponseTestCase: BaseTestCase {
}
}
if let lastByteValue = byteValues.last,
if let
lastByteValue = byteValues.last,
lastProgressValue = progressValues.last
{
let byteValueFractionalCompletion = Double(lastByteValue.totalBytes) / Double(lastByteValue.totalBytesExpected)
let progressValueFractionalCompletion = Double(lastProgressValue.0) / Double(lastProgressValue.1)
XCTAssertEqual(byteValueFractionalCompletion, 1.0, "byte value fractional completion should equal 1.0")
XCTAssertEqual(progressValueFractionalCompletion, 1.0, "progress value fractional completion should equal 1.0")
} else {
XCTFail("last item in bytesValues and progressValues should not be nil")
}
}
}
// MARK: -
class UploadMultipartFormDataTestCase: BaseTestCase {
// MARK: Tests
func testThatUploadingMultipartFormDataSetsContentTypeHeader() {
// Given
let URLString = "http://httpbin.org/post"
let uploadData = "upload_data".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let expectation = expectationWithDescription("multipart form data upload should succeed")
var formData: MultipartFormData?
var request: NSURLRequest?
var response: NSHTTPURLResponse?
var data: AnyObject?
var error: NSError?
// When
Alamofire.upload(
.POST,
URLString: URLString,
multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(data: uploadData, name: "upload_data")
formData = multipartFormData
},
encodingCompletion: { result in
switch result {
case .Success(let upload, _, _):
upload.response { responseRequest, responseResponse, responseData, responseError in
request = responseRequest
response = responseResponse
data = responseData
error = responseError
expectation.fulfill()
}
case .Failure:
expectation.fulfill()
}
}
)
waitForExpectationsWithTimeout(self.defaultTimeout, handler: nil)
// Then
XCTAssertNotNil(request, "request should not be nil")
XCTAssertNotNil(response, "response should not be nil")
XCTAssertNotNil(data, "data should not be nil")
XCTAssertNil(error, "error should be nil")
if let
request = request,
multipartFormData = formData,
contentType = request.valueForHTTPHeaderField("Content-Type")
{
XCTAssertEqual(contentType, multipartFormData.contentType, "Content-Type header value should match")
} else {
XCTFail("Content-Type header value should not be nil")
}
}
func testThatUploadingMultipartFormDataSucceedsWithDefaultParameters() {
// Given
let URLString = "http://httpbin.org/post"
let french = "français".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let japanese = "日本語".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let expectation = expectationWithDescription("multipart form data upload should succeed")
var request: NSURLRequest?
var response: NSHTTPURLResponse?
var data: AnyObject?
var error: NSError?
// When
Alamofire.upload(
.POST,
URLString: URLString,
multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(data: french, name: "french")
multipartFormData.appendBodyPart(data: japanese, name: "japanese")
},
encodingCompletion: { result in
switch result {
case .Success(let upload, _, _):
upload.response { responseRequest, responseResponse, responseData, responseError in
request = responseRequest
response = responseResponse
data = responseData
error = responseError
expectation.fulfill()
}
case .Failure:
expectation.fulfill()
}
}
)
waitForExpectationsWithTimeout(self.defaultTimeout, handler: nil)
// Then
XCTAssertNotNil(request, "request should not be nil")
XCTAssertNotNil(response, "response should not be nil")
XCTAssertNotNil(data, "data should not be nil")
XCTAssertNil(error, "error should be nil")
}
func testThatUploadingMultipartFormDataWhileStreamingFromMemoryMonitorsProgress() {
executeMultipartFormDataUploadRequestWithProgress(streamFromDisk: false)
}
func testThatUploadingMultipartFormDataWhileStreamingFromDiskMonitorsProgress() {
executeMultipartFormDataUploadRequestWithProgress(streamFromDisk: true)
}
func testThatUploadingMultipartFormDataBelowMemoryThresholdStreamsFromMemory() {
// Given
let URLString = "http://httpbin.org/post"
let french = "français".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let japanese = "日本語".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let expectation = expectationWithDescription("multipart form data upload should succeed")
var streamingFromDisk: Bool?
var streamFileURL: NSURL?
// When
Alamofire.upload(
.POST,
URLString: URLString,
multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(data: french, name: "french")
multipartFormData.appendBodyPart(data: japanese, name: "japanese")
},
encodingCompletion: { result in
switch result {
case let .Success(upload, uploadStreamingFromDisk, uploadStreamFileURL):
streamingFromDisk = uploadStreamingFromDisk
streamFileURL = uploadStreamFileURL
upload.response { _, _, _, _ in
expectation.fulfill()
}
case .Failure:
expectation.fulfill()
}
}
)
waitForExpectationsWithTimeout(self.defaultTimeout, handler: nil)
// Then
XCTAssertNotNil(streamingFromDisk, "streaming from disk should not be nil")
XCTAssertNil(streamFileURL, "stream file URL should be nil")
if let streamingFromDisk = streamingFromDisk {
XCTAssertFalse(streamingFromDisk, "streaming from disk should be false")
}
}
func testThatUploadingMultipartFormDataBelowMemoryThresholdSetsContentTypeHeader() {
// Given
let URLString = "http://httpbin.org/post"
let uploadData = "upload data".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let expectation = expectationWithDescription("multipart form data upload should succeed")
var formData: MultipartFormData?
var request: NSURLRequest?
var streamingFromDisk: Bool?
// When
Alamofire.upload(
.POST,
URLString: URLString,
multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(data: uploadData, name: "upload_data")
formData = multipartFormData
},
encodingCompletion: { result in
switch result {
case let .Success(upload, uploadStreamingFromDisk, uploadStreamFileURL):
streamingFromDisk = uploadStreamingFromDisk
upload.response { responseRequest, _, _, _ in
request = responseRequest
expectation.fulfill()
}
case .Failure:
expectation.fulfill()
}
}
)
waitForExpectationsWithTimeout(self.defaultTimeout, handler: nil)
// Then
XCTAssertNotNil(streamingFromDisk, "streaming from disk should not be nil")
if let streamingFromDisk = streamingFromDisk {
XCTAssertFalse(streamingFromDisk, "streaming from disk should be false")
}
if let
request = request,
multipartFormData = formData,
contentType = request.valueForHTTPHeaderField("Content-Type")
{
XCTAssertEqual(contentType, multipartFormData.contentType, "Content-Type header value should match")
} else {
XCTFail("Content-Type header value should not be nil")
}
}
func testThatUploadingMultipartFormDataAboveMemoryThresholdStreamsFromDisk() {
// Given
let URLString = "http://httpbin.org/post"
let french = "français".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let japanese = "日本語".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let expectation = expectationWithDescription("multipart form data upload should succeed")
var streamingFromDisk: Bool?
var streamFileURL: NSURL?
// When
Alamofire.upload(
.POST,
URLString: URLString,
multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(data: french, name: "french")
multipartFormData.appendBodyPart(data: japanese, name: "japanese")
},
encodingMemoryThreshold: 0,
encodingCompletion: { result in
switch result {
case let .Success(upload, uploadStreamingFromDisk, uploadStreamFileURL):
streamingFromDisk = uploadStreamingFromDisk
streamFileURL = uploadStreamFileURL
upload.response { _, _, _, _ in
expectation.fulfill()
}
case .Failure:
expectation.fulfill()
}
}
)
waitForExpectationsWithTimeout(self.defaultTimeout, handler: nil)
// Then
XCTAssertNotNil(streamingFromDisk, "streaming from disk should not be nil")
XCTAssertNotNil(streamFileURL, "stream file URL should not be nil")
if let
streamingFromDisk = streamingFromDisk,
streamFilePath = streamFileURL?.path
{
XCTAssertTrue(streamingFromDisk, "streaming from disk should be true")
XCTAssertTrue(NSFileManager.defaultManager().fileExistsAtPath(streamFilePath), "stream file path should exist")
}
}
func testThatUploadingMultipartFormDataAboveMemoryThresholdSetsContentTypeHeader() {
// Given
let URLString = "http://httpbin.org/post"
let uploadData = "upload data".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let expectation = expectationWithDescription("multipart form data upload should succeed")
var formData: MultipartFormData?
var request: NSURLRequest?
var streamingFromDisk: Bool?
// When
Alamofire.upload(
.POST,
URLString: URLString,
multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(data: uploadData, name: "upload_data")
formData = multipartFormData
},
encodingMemoryThreshold: 0,
encodingCompletion: { result in
switch result {
case let .Success(upload, uploadStreamingFromDisk, uploadStreamFileURL):
streamingFromDisk = uploadStreamingFromDisk
upload.response { responseRequest, _, _, _ in
request = responseRequest
expectation.fulfill()
}
case .Failure:
expectation.fulfill()
}
}
)
waitForExpectationsWithTimeout(self.defaultTimeout, handler: nil)
// Then
XCTAssertNotNil(streamingFromDisk, "streaming from disk should not be nil")
if let streamingFromDisk = streamingFromDisk {
XCTAssertTrue(streamingFromDisk, "streaming from disk should be true")
}
if let
request = request,
multipartFormData = formData,
contentType = request.valueForHTTPHeaderField("Content-Type")
{
XCTAssertEqual(contentType, multipartFormData.contentType, "Content-Type header value should match")
} else {
XCTFail("Content-Type header value should not be nil")
}
}
// MARK: Combined Test Execution
private func executeMultipartFormDataUploadRequestWithProgress(#streamFromDisk: Bool) {
// Given
let URLString = "http://httpbin.org/post"
let loremData1: NSData = {
var loremValues: [String] = []
for _ in 1...1_500 {
loremValues.append("Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
}
return join(" ", loremValues).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
}()
let loremData2: NSData = {
var loremValues: [String] = []
for _ in 1...1_500 {
loremValues.append("Lorem ipsum dolor sit amet, nam no graeco recusabo appellantur.")
}
return join(" ", loremValues).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
}()
let expectation = expectationWithDescription("multipart form data upload should succeed")
var byteValues: [(bytes: Int64, totalBytes: Int64, totalBytesExpected: Int64)] = []
var progressValues: [(completedUnitCount: Int64, totalUnitCount: Int64)] = []
var request: NSURLRequest?
var response: NSHTTPURLResponse?
var data: AnyObject?
var error: NSError?
// When
Alamofire.upload(
.POST,
URLString: URLString,
multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(data: loremData1, name: "lorem1")
multipartFormData.appendBodyPart(data: loremData2, name: "lorem2")
},
encodingMemoryThreshold: streamFromDisk ? 0 : 100_000_000,
encodingCompletion: { result in
switch result {
case .Success(let upload, let streamingFromDisk, _):
upload.progress { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
let bytes = (bytes: bytesWritten, totalBytes: totalBytesWritten, totalBytesExpected: totalBytesExpectedToWrite)
byteValues.append(bytes)
let progress = (completedUnitCount: upload.progress.completedUnitCount, totalUnitCount: upload.progress.totalUnitCount)
progressValues.append(progress)
}
upload.response { responseRequest, responseResponse, responseData, responseError in
request = responseRequest
response = responseResponse
data = responseData
error = responseError
expectation.fulfill()
}
case .Failure:
expectation.fulfill()
}
}
)
waitForExpectationsWithTimeout(self.defaultTimeout, handler: nil)
// Then
XCTAssertNotNil(request, "request should not be nil")
XCTAssertNotNil(response, "response should not be nil")
XCTAssertNotNil(data, "data should not be nil")
XCTAssertNil(error, "error should be nil")
XCTAssertEqual(byteValues.count, progressValues.count, "byteValues count should equal progressValues count")
if byteValues.count == progressValues.count {
for index in 0..<byteValues.count {
let byteValue = byteValues[index]
let progressValue = progressValues[index]
XCTAssertGreaterThan(byteValue.bytes, 0, "reported bytes should always be greater than 0")
XCTAssertEqual(byteValue.totalBytes, progressValue.completedUnitCount, "total bytes should be equal to completed unit count")
XCTAssertEqual(byteValue.totalBytesExpected, progressValue.totalUnitCount, "total bytes expected should be equal to total unit count")
}
}
if let
lastByteValue = byteValues.last,
lastProgressValue = progressValues.last
{
let byteValueFractionalCompletion = Double(lastByteValue.totalBytes) / Double(lastByteValue.totalBytesExpected)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册