I use NSURLSession downloadTaskWithURL: to download a file, and use NSURLSessionDownloadTask cancelByProducingResumeData: to produce an NSData and save it to a local temp file.
Then I want to resume the download task by using NSURLSession downloadTaskWithResumeData:.
There's a problem, the URL I used to download the file is a temp url, I need to request a new URL to download the same file.
After using downloadTaskWithResumeData: , it helps me to create a NSURLSessionDownloadTask with the same URL as before.
How can I replace the URL with the new URL that I newly request?
Or how can I change the HTTP Request of this NSURLSessionDownloadTask?
How do you deal with the situation that resume a NSURLSessionDownloadTask with a different URL?
I'm thinking about to get the .tmp file that NSURLSession downloaded, and set the Range in HTTP Header, then write to this file with the new temp URL.
I don't believe that we can resume download if the path of the resource has been modified.
According to Apple Developer Class Reference:
A download can be resumed only if the following conditions are met:
The resource has not changed since you first requested it
The task is an HTTP or HTTPS GET request
The server provides either the ETag or Last-Modified header (or both) in its response
The server supports byte-range requests
The temporary file hasn’t been deleted by the system in response to disk space pressure
Also as we see, there is no way we can modify URL programmatically while resuming a download. We just have an option to provided the partially downloaded data.
Related
Coming from the world of web programming, I'm pretty much comfortable with working with multipart form requests to upload files. However, in iOS, we have a thing called NSURLSession with the method uploadTaskWithRequest, which seems to be the method to call to do image uploads and the likes.
Can you explain the difference between the two approach, multipart form upload vs uploadTaskWithRequest? If I already have a backend that handle multipart form uploads, what kind of adjustments that I might need so that it support uploadTaskWithRequest as well?
The uploadTaskWithRequest simply sends the NSData, file, or stream as the body of the request. It doesn't do anything beyond that. It simply has the benefit that it can be used with background sessions.
So, if you have web service that is expecting multipart/form-data request, you have to build that request yourself (unless you use something like AFNetworking or Alamofire to do this for you). Once you've built that request, you can either use dataTaskWithRequest (having set the HTTPBody of the NSMutableURLRequest) or uploadTaskWithRequest (in which case you don't set HTTPBody, but rather provide it as a parameter to uploadTaskWithRequest).
By the way, a tool like Charles is very useful in these cases, letting you observe what's going on behind the scenes.
File Upload with multipart/form-data
The first approach using a multipart/form-data Content-type was originally defined in RFC 1867, then moved to the World Wide Web Consortium, which included it in the specification for HTML 4.0, where forms are expressed in HTML and where form values are sent via HTTP and electronic mail. When the form has been filled out by a user the form was sent to the server. This technique is widely supported and used by browsers and web servers.
However multipart/form-data can also be used to define form data which are presented in other representations than HTML. That is, you don't necessarily need a web browser or web server. The current specification which can be used by a wide variety of applications and transported by a wide variety of protocols is RFC 7578 (form IETF).
It must be mentioned, though, that the multipart/form-data content type was not always/is not without issues. It is quite complex by itself. Additionally, it uses/refers to a lot of other RFCs and - as a result of clearing things up - it and those which it depends on have been changed, obsoleted and updated quite frequently. Due to its complexity, serialisers and parsers are getting quite complicated, too and there's a lot of room for bugs and other issues.
NSURLSession uploadTaskWithRequest
How NSURLSession composes a request is not precisely documented. It certainly does not use a multipart/form-data content type, though.
For upload tasks, NSURLSession uses a POST request with a NSURLRequest as parameter which you can setup yourself. That is, you optionally can set the content type (for example text/plain; charset=utf-8), and other headers. NSURLSession can also derive an appropriate content type itself from the given content (file, stream or NSData). That is, we may say, it becomes a "simple" POST request. Due to less complexity, the request is less troublesome.
So, in order for your server to support an uploadTaskWithRequest where a file should be uploaded, it should simply support a POST request with some "simple" content type. That is, as opposed to a "file upload" with a multipart/form-data content type which contains the file name in a disposition header, the server would need to return the URL of the location where the resource (the file) has been written to.
Fortunately, it is quite easy to do a multipart/form-data POST request in the background, for example if you want to upload an image along with some other information.
First, create the NSMutableURLRequest in the same way like you would for a synchronous request (see for example POST multipart/form-data with Objective-C).
Then, write the body of the request to a file and supply it to the uploadTaskWithRequest method of the NSURLSession that you created with a backgroundSessionConfiguration:
NSString *filePath = [[NSSearchPathForDirectoriesInDomains(
NSCachesDirectory, NSUserDomainMask, YES) lastObject]
stringByAppendingPathComponent:imageUUID];
[request.HTTPBody writeToFile:filePath atomically:YES];
NSURLSessionUploadTask *task = [urlSession uploadTaskWithRequest:request
fromFile:[NSURL fileURLWithPath:filePath]];
[task resume];
If you have multiple tasks and want to be able to distinguish them in the delegate callback, you can set a parameter (after you have created the request) with the NSURLProtocol class:
[NSURLProtocol setProperty:imageUUID
forKey:#"yourKeyForTheImageUUID"
inRequest:request];
and get it back in the callback like this:
- (void)URLSession:(NSURLSession *)session
task:(nonnull NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error
{
NSString *imageUUID = [NSURLProtocol propertyForKey:#"yourKeyForTheImageUUID"
inRequest:task.originalRequest];
I am trying to implement a (jetty-based) servlet supporting the uploading of (large) files from a web client, where a little javascript splits some user-selected file into chunks, and sends these chunks to the server using several POSTs with appropriate Content-Range headers (the rationale of this technique is to be able to track progress, pause and resume upload).
I have come up with an HttpServlet overriding the doPost() method, which handles the Content-Range header - i.e. which writes the payload at the specified location into a file on the server.
Is there a better/recommended way to support (large) file upload in a servlet?
Is there a set of classes in jetty that does just that?
Thanks in advance
I am using NSUrlConnection to grab some data from a web service that returns JSON data when I hit a url but sometimes the page goes down or something is wrong.
Is there a way to use the cached version of the url in my iOS App to grab that data when the webpage returns a 404?
In the browser if I go to cache:mysite.com/test.php I can see the JSON data even when the page is down but when I try to use the same url in my iOS App, I do not get the JSON data back.
Is there maybe an Obj C class I am not aware of or an option for the NSUrlConnection?
NSURLConnection already uses a cache. By default it only caches responses in-memory, you can customize this by setting it to also use on-disk storage:
NSURLCache *result = [[NSURLCache alloc] initWithMemoryCapacity:[(1024*1024*512) diskCapacity:(1024*1024*1024 * 100) diskPath#"Cache.db"]:
[NSURLCache setSharedURLCache:result];
The reason you are seeing the behavior you describe in your question is that the remote web service is telling your client not to cache the response. You can check this using REDbot or a tool like Charles. By default NSURLRequest will use the protocol's caching policy and semantics, which is almost always the correct thing to do. You can specify a different cache policy by changing the cachePolicy property of the NSURLRequest.
After much search, I didn't find a way to get a cached version of a web page in my iOS app but I handled the 404 error by looking for the response status code and using a service like Google Cache or archive.org/web/ to get the cached version of the url if a 404 was found.
Might not be the most delicate way but I could not find another way to get a cached version of the website when making the request.
I'm trying to understand how to properly use NSURLSession for my scenario, reading through specification, need more clarification..
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/UsingNSURLSession.html#//apple_ref/doc/uid/TP40013509-SW1
My server API is very simple. I use protobufs for data communication and message payload very small, turnaround very quick. From data standpoint it's just plain binary data being transferred.
Server supports only POST request and responds with data.
So, it goes like this:
- POST request with custom headers and binary payload
- server responds with message and binary payload (response also might include custom headers)
From what I see in documentation data tasks is exactly what I need, but they say
Data tasks send and receive data using NSData objects. Data tasks are
intended for short, often interactive requests from your app to a
server. Data tasks can return data to your app one piece at a time
after each piece of data is received, or all at once through a
completion handler. Because data tasks do not store the data to a
file, they are not supported in background sessions
So, I left with download and upload tasks and they go into a file. How do I go about achieving what I need? Sounds like I should use upload task, but will I get response data back?
ok. I decided not to delete my question in case someone else needs this info.
Same documentation article says:
Uploading a File Using a Download Task To upload body content for a
download task, your app must provide either an NSData object or a body
stream as part of the NSURLRequest object provided when it creates the
download request.
If you provide the data using a stream, your app must provide a
URLSession:task:needNewBodyStream: delegate method to provide a new
body stream in the event of an authentication failure. This method is
described further in “Uploading Body Content Using a Stream.”
The download task behaves just like a data task except for the way in
which the data is returned to your app.
I am downloading WMS tiles which I want to cache. I'm using AFNetworking which includes NSURLCache. The responses from the server do not contain Cache-Control protocols in the header.
I asked the server guy about this and was unfamiliar with server side cache-control. At the moment, he is swamped with other work. Do I need him to implement the cache-control or can I force NSURLCache to cache them w/out the info the response header?
Is NSURLCache persistent? If so, how can I clear the cache? The tiles will need to be retrieved per session and can not be persistent.
Or should I create my own cache?
When you activate NSURLCache it will work for any request that is based on the NSUrlRequest (like AFNetworking). The moment you activate the NSURLCache you can specify it's maximum size. You can also clear the cache by calling the removeAllCachedResponses or removeCachedResponsesForRequest methods. If the server does not send any cache control information, then the cache will still cache the file. If you want complete control over the cache, you could create your own cache. If you would like to see a sample code for that, then have a look at https://github.com/evermeer/EVURLCache