Learn NSURLSession using Swift Part 3— Upload

In URL Loading System Programming Guide, Apple mentioned there are four ways to upload content.

  1. Uploading Body Content Using an NSData Object
  2. Uploading Body Content Using a File
  3. Uploading Body Content Using a Stream
  4. Uploading a File Using a Download Task

The first and second one are the most useful items for me, basically you don’t have to set those complicated header values on your own, only focus on upload body content. The session object computes the Content-Length header based on the size of the data object if you using NSData or File type, and if your app does not provide a value for theContent-Type header, the session also provides one.

(P.S. I think the screen capture image is better for viewing rather than type code on Medium, if you need copy the sample code, please download full project: https://github.com/samwang0723/URLSessionUpload )

Upload using NSData

Now let us to test the upload function using Python SimpleHTTPServer, if you are not familiar with Python, just use the code below(execute in console ./SimpleHTTPServerWithUpload.py) and save the retrieved file to Desktop on your Mac (In this example I will write swift.png, you can modify the file name you want). This server allows you to POST a file and will return “Upload Complete” while upload complete.

Next I create a project to store the image file to Bundle.

Then I read the file in Bundle and save as NSData object.

let bundle = NSBundle.mainBundle()
let path = bundle.pathForResource(“swift”, ofType: “png”)
var data: NSData = NSData(contentsOfFile: path!)

And create a NSMutableURLRequest for handling the upload request, set HTTP method to “POST”.

var request = NSMutableURLRequest(URL: NSURL(string: “http://127.0.0.1:8000/swift.png"))
request.HTTPMethod = “POST”
request.setValue(“Keep-Alive”, forHTTPHeaderField: “Connection”)

Next, like previous article described, we need to create the NSURLSession, this time using a defaultSessionConfiguration and implement delegate on ViewController itself, whole code will be like:

Handling Response with Delegate

Noticed that I implemented four delegate methods, we can realize the upload progress using URLSession:task:didSendBodyData:totalBytesSent:
totalBytesExpectedToSend:
 , compute progress float by

var uploadProgress: Float = Float(totalBytesSent) / Float(totalBytesExpectedToSend)

And receive response from didReceiveData, didReceiveResponse, append the result using NSMutableData, then I will handle the completion with didCompleteWithError, the reason this method will be triggered is because I didn’t use the completion Closure in uploadTaskWithRequest, or you can handle the completion in Closure if you want to, convert the NSMutableData into String result.

Execute

You can see the server console log will print the incoming POST request detail:

web server on 8000..
Host: 127.0.0.1:8000
Content-Type: application/x-www-form-urlencoded
User-Agent: NSURLSessionUpload/1 CFNetwork/711.0.6 Darwin/14.0.0
Accept: */*
Accept-Language: en-us
Content-Length: 2849623
Accept-Encoding: gzip, deflate
Connection: keep-alive
2849623
127.0.0.1 — — [07/Nov/2014 11:17:27] “POST /swift.png HTTP/1.1" 200 -

Logs in Xcode:

session <__NSURLSessionLocal: 0x7fe559517560> uploaded 93.1424255371094%.
session <__NSURLSessionLocal: 0x7fe559517560> uploaded 94.2923278808594%.
session <__NSURLSessionLocal: 0x7fe559517560> uploaded 95.4422378540039%.
session <__NSURLSessionLocal: 0x7fe559517560> uploaded 96.5921478271484%.
session <__NSURLSessionLocal: 0x7fe559517560> uploaded 97.7420501708984%.
session <__NSURLSessionLocal: 0x7fe559517560> uploaded 98.891960144043%.
session <__NSURLSessionLocal: 0x7fe559517560> uploaded 100.0%.
session <__NSURLSessionLocal: 0x7fe559517560>, received response <NSHTTPURLResponse: 0x7fe5595208a0> { URL:
http://127.0.0.1:8000/swift.png } { status code: 200, headers {
“Content-Type” = “text/html;charset=utf-8";
Date = “Fri, 07 Nov 2014 03:17:27 GMT”;
Server = “BaseHTTP/0.3 Python/2.7.6";
} }
session <__NSURLSessionLocal: 0x7fe559517560> upload completed, response: Upload Complete

Content Type

If we use the upload task without provide the Content-Type header field, NSURLSession will gives us the “application/x-www-form-urlencoded”, which is an “old” format (as the query string or as a single data part of type application/x-www-form-urlencoded). Most of the servers support the well-known new MIME media type, “multipart/form-data” now, so I will suggest you to define the Content-Type before send out the request.

Background Upload

Upload task also support background execution, you can simply change the configuration to backgroundSessionConfiguration, if you have problems to setup the configuration, please refer to part 2: https://medium.com/swift-programming/learn-nsurlsession-using-swift-part-2-background-download-863426842e21

Handle Stream Upload

If you have large file, better to use streaming upload with chunked header, the NSMutableURLRequest should be modify with these additional items:

request.setValue(“application/octet-stream”, forHTTPHeaderField: “Content-Type”)
request.HTTPBodyStream = NSInputStream(data: data)

And because the session cannot necessarily rewind the provided stream to re-read data, your app is responsible for providing a new stream in the event that the session must retry a request (for example, if authentication fails). To do this, your app provides aURLSession:task:needNewBodyStream: method. When that method is called, your app should perform whatever actions are needed to obtain or create a new body stream, and then call the provided completion handler block with the new stream.

(Note: The Python server sample here doesn’t support streaming data transfer)

Conclusion

So far we learned how to download and upload no matter in foreground or background, I believe these are enough for a developer to handle the HTTP network communication. Another topic I didn’t discuss about is Ephemeral sessions, it depends on the usage of your App and will not cache any content to local disk, think about the private browsing in Safari, if you need the similar usage in your App, use with Ephemeral sessions.

Thanks again for viewing my articles, and please feel free to contact with me if any questions or suggestion.