Uploading Image to S3 fails to complete upload - ios

I'm uploading an image to S3 and am having trouble completing the file transfer. Here is how the app acts.
Initiate uploadToS3()
File begins transfer sending bytes to server.
When around 600,000 bytes are sent, the upload halts.
After 20-40 seconds, the app continues its upload progress at 0%. It acts as if the file transfer never began in the first place.
During this entire time no errors appear in the logger.
In my view controller I have the following method that uploads the file.
func uploadToS3(){
// get the image from a UIImageView that is displaying the selected Image
var img: UIImage = imageView.image!
// create a local image that we can use to upload to s3
var path: NSString = NSTemporaryDirectory().stringByAppendingPathComponent("image.png")
var imageData: NSData = UIImagePNGRepresentation(img)
imageData.writeToFile(path as String, atomically: true)
// once the image is saved we can use the path to create a local fileurl
var url:NSURL = NSURL(fileURLWithPath: path as String)!
// next we set up the S3 upload request manager
let uploadRequest = AWSS3TransferManagerUploadRequest()
// set the bucket
uploadRequest?.bucket = "test-bucket"
// I want this image to be public to anyone to view it so I'm setting it to Public Read
uploadRequest?.ACL = AWSS3ObjectCannedACL.PublicRead
// set the image's name that will be used on the s3 server. I am also creating a folder to place the image in
uploadRequest?.key = "foldername/image.png"
// set the content type
uploadRequest?.contentType = "image/png"
// and finally set the body to the local file path
uploadRequest?.body = url;
// we will track progress through an AWSNetworkingUploadProgressBlock
uploadRequest?.uploadProgress = {[unowned self](bytesSent:Int64, totalBytesSent:Int64, totalBytesExpectedToSend:Int64) in
dispatch_sync(dispatch_get_main_queue(), { () -> Void in
println("total bytes sent")
println(totalBytesSent)
println("total bytes expected to send")
println(totalBytesExpectedToSend)
})
}
// now the upload request is set up we can creat the transfermanger, the credentials are already set up in the app delegate
var transferManager:AWSS3TransferManager = AWSS3TransferManager.defaultS3TransferManager()
// start the upload
transferManager.upload(uploadRequest).continueWithExecutor(BFExecutor.mainThreadExecutor(), withBlock:{ [unowned self]
task -> AnyObject in
// once the uploadmanager finishes check if there were any errors
if(task.error != nil){
println("%#", task.error);
}else{ // if there aren't any then the image is uploaded!
// this is the url of the image we just uploaded
println("https://s3.amazonaws.com/s3-demo-swift/foldername/image.png");
}
//self.removeLoadingView()
println("all done");
return ""
})
}
For anyone looking to recreate this app
Add to your Podfile:
pod 'AWSCore'
pod 'AWSS3'
pod 'AWSiOSSDKv2'
pod 'AWSCognitoSync'
Then add a bridge header containing:
#import <AWSCore/AWSCore.h>
#import <AWSS3/AWSS3.h>
In my AppDelegate I have:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
AWSCognitoCredentialsProvider.initialize()
var credentialsProvider = AWSCognitoCredentialsProvider(
regionType: AWSRegionType.USEast1,
identityPoolId: "identity pool id"
)
var configuration = AWSServiceConfiguration(
region: AWSRegionType.USEast1,
credentialsProvider: credentialsProvider
)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
return true
}
Finally, in the view controller that contains uploadToS3(), add import AWSS3.
Update
Here is the last section of logged errors.
}]
2015-05-09 19:24:24.540 CoolApp[4492:55681] AWSiOSSDKv2 [Verbose] AWSURLResponseSerialization.m line:278 | -[AWSXMLResponseSerializer responseObjectForResponse:originalRequest:currentRequest:data:error:] | Response body: [<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>A03D405FC272808A</RequestId><HostId>bhSw+xQkGrMVd9QWMKMG1qYezPJet8b5L2ZIoGXePoftuupMP3HdgbAgCpStiLefo5yA3m1OJvY=</HostId></Error>]
(%#, Error Domain=com.amazonaws.AWSS3ErrorDomain Code=1 "The operation couldn’t be completed. (com.amazonaws.AWSS3ErrorDomain error 1.)" UserInfo=0x7c17cdc0 {HostId=bhSw+xQkGrMVd9QWMKMG1qYezPJet8b5L2ZIoGXePoftuupMP3HdgbAgCpStiLefo5yA3m1OJvY=, Code=AccessDenied, Message=Access Denied, RequestId=A03D405FC272808A})
all done
My question is, how do I fix this and have the image upload succesfully.

As the error message states, the permission is not set up correctly with your Amazon Cognito Identity pool. Understanding Amazon Cognito Authentication blog series (Part 2, Part 3) and Amazon Cognito Developer Guide are your great resources for understanding and setting up Cognito Identity.

Related

iOS Swift: Firebase Storage upload error - only the initial file upload works

Environment:
Xcode 12.0.1 (Swift 5.x)
iOS 13
Firebase 6.34.0
FirebaseFirestore 1.19.0
FirebaseStorage 3.9.1
GoogleDataTransport 7.5.1
PromisesObjC 1.2.11
Problem:
I initialize Firebase in iOS app and successfully write database data to Cloud FireStore.
I then upload related video file to Firebase Storage with an asynch call.
Cloud Firestore database writes ALWAYS work.
Using the index generated from Cloud Firestore write, Firebase storage is then used to upload a video and a data file with names as the index from (1) above.
The first first file upload always works with a new app launch.
The second or any additional file upload fails with the following error:
cloud storage VIDEO file upload error: Error Domain=FIRStorageErrorDomain Code=-13000 "An unknown error occurred, please check the server response." UserInfo={object=PBY7Ost7nPWD8jWWF4qG.mov, ResponseBody=Can not finalize upload. Current size is 1692167. Expected final size is 1665242., bucket=launch-me-47860.appspot.com, data={length = 83, bytes = 0x43616e20 6e6f7420 66696e61 6c697a65 ... 31363635 3234322e }, data_content_type=text/plain; charset=utf-8, NSLocalizedDescription=An unknown error occurred, please check the server response., ResponseErrorDomain=com.google.HTTPStatus, ResponseErrorCode=400}
I have found similar questions posted from 2016 and 2017 but this Firebase write problem seems different as the initial upload always works and then the next time I try to perform an upload it fails with the error.
I added a routine to try additional upload attempts if the first upload fails. They all fail.
Here is the upload code:
// MARK: Write file to Firebase Cloud Storage
private func fbCloudFileWrite(indexName: String) {
let fbStorage = Storage.storage()
print("(DEBUG FB) fbCloudFileWrite: upload indexName.csv and indexName.mov")
// now upload file to cloud FireStore
let fbStorageRef = fbStorage.reference()
// Create a reference to the file you want to upload
//let LaunchMeDataRef = fbStorageRef.child("LaunchMe/" + indexName + ".csv")
//let LaunchMeVideoRef = fbStorageRef.child("LaunchMe/" + indexName + ".mov")
var LaunchMeDataRef = fbStorageRef.child(indexName + ".csv")
var LaunchMeVideoRef = fbStorageRef.child(indexName + ".mov")
// Upload the file to the path "images/rivers.jpg"
fbWriteAttempts += 1
DispatchQueue.main.async {
print("(DEBUG FB) ***** write attemp #: \(self.fbWriteAttempts)")
if let vURL = self.videoURL, let dURL = self.dataFileURL {
let uploadVideoTask = LaunchMeVideoRef.putFile(from: vURL, metadata: nil) { metadata, err in
if let err = err {
print("(DEBUG FB) cloud storage VIDEO file upload error: \(err)")
if self.fbWriteAttempts < 4 {
self.fbCloudFileWrite(indexName: indexName)
}
} else {
print("(DEBUG FB) video uploaded: \(indexName)")
let uploadDataTask = LaunchMeDataRef.putFile(from: dURL, metadata: nil) { metadata, err in
if let err = err {
print("(DEBUG FB) cloud storage SENSOR file upload error: \(err)")
} else {
print("(DEBUG FB) sensor data uploaded: \(indexName)")
print("(DEBUG FB) set newRecording = false to prevent duplicates")
// all files successfully uploaded. Set newRecording to false
self.newRecording = false
}
} // close uploadDataTask
} // close else
} // close let uploadVideoTask
} // close vURL unwrap
else {
print("(DEBUG FB) videoURL could not be unwrapped")
}
} // close Dispatch.main.async
Strangely enough, I was able to solve this problem by setting the URL variable of the movie local directory path within the VideoPlayerViewController instead of passing the URL in from the previous controller during the segue. This makes no sense to me that this would be necessary except perhaps some caching that happens behind the scenes.
I know the correct URL is passed in as the correct video plays on the screen when it is passed in, yet the Firebase Storage upload tries to upload the previous video (hence the expected size error) when provided the same URL that played the correct video.
New function added within the controller that sets the URL instead of passing it in (prior to performing the Firebase Storage upload):
private func setVideoStorageURL() {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let path = "output.mov"
videoURL = paths[0].appendingPathComponent(path)
}
whereas the previous implementation that was not working passed the URL into the controller:
if let destinationVC = segue.destination as? VideoPlayerViewController {
print("(DEBUG) Setting data to be passed to VideoPlayerViewController")
destinationVC.newRecording = newRecording
//now set newRecording to false if it is true
if newRecording {
newRecording = false
}
destinationVC.audioMode = audioMode
destinationVC.selectedDevice = selectedDevice
destinationVC.videoURL = passFileURL

AWSS3TransferUtilityErrorDomain Code=2 on iOS Swift While trying to upload Image/pdf

I am trying to upload pdf file or image file to AWSS3 bucket but I am getting AWSS3TransferUtilityErrorDomain Code=2 error. Please note I already have checked region and it is correct. Also I have verified that I am using correct accessKey and secretKey I also have visited below mentioned links with no luck:
(https://github.com/aws-amplify/aws-sdk-ios/issues/2553.)
(https://github.com/aws-amplify/aws-sdk-ios/issues/604)
(https://github.com/aws-amplify/aws-sdk-ios/issues/420)
(https://github.com/aws-amplify/aws-sdk-ios/issues/103)
(Upload image to S3 with Amazon Educate Starter Account)
(About permission in S3 file transfer)
(Swift iOS: Unable to Upload Image to AWS S3)
(AWSS3TransferUtilityErrorDomain Code=2 on ios)
My code to upload file is below:
let credentials = AWSStaticCredentialsProvider(accessKey: “accessKey” , secretKey: “secretKey”)
let configuration = AWSServiceConfiguration(region: AWSRegionType.APSouth1 , credentialsProvider: credentials)
AWSServiceManager.default().defaultServiceConfiguration = configuration
let expression = AWSS3TransferUtilityUploadExpression()
expression.progressBlock = { (task, progress) in
DispatchQueue.main.async(execute: {
// Update a progress bar
print("Task: \(task)")
print("Progress: \(progress)")
})
}
var completionHandler: AWSS3TransferUtilityUploadCompletionHandlerBlock?
completionHandler = { (task, error) -> Void in
DispatchQueue.main.async(execute: {
if let error = error {
CommonLoader.hide()
SCLAlertView().showError("Error", subTitle: error.localizedDescription)
return
}
// Do stuff after success
})
}
let transferUtility = AWSS3TransferUtility.default()
// contentType —-> “image/jpeg” for images && “application/pdf” for pdf files
transferUtility.uploadData(data, bucket: s3BucketName, key: remoteName, contentType: contentType, expression: expression, completionHandler: completionHandler).continueWith { (task) -> Any? in
if let error = task.error {
// error case
}
else {
if !task.isFaulted && task.result != nil {
// success case
}
}
return nil
}
After a lot of search and reading documentation I am able to solve this issue.
In my case there were two strange things, one with same credentials and bucket on Android it was working.
But on iOS exactly same code was working in Dubai but not in Pakistan.
I solved the issue by just adding the region to project info.plist file as mentioned below:
Please note in my case region was ap-south-1 but you need to put it here yours, you can check region from Amazon S3 Endpoints and then find corresponding region value to use in your info.plist. Hope this will help someone and save time. Happy coding. cheers!
<key>S3TransferUtility</key>
<dict>
<key>Default</key>
<dict>
<key>Region</key>
<string>"ap-south-1"</string>
</dict>
</dict>

Process for uploading image to s3 with AWS Appsync || iOS image uploading with Appsync

I'm working on a new project that requires uploading attachments in the form of images. I'm using DynamoDB and AppSync API's to insert and retrieve data from database. As we are new to the AppSync and all the amazon services and database we are using for the app i'm little bit confused about the authentication process. Right now we are using API key for authentication and I have tried these steps to upload image to s3.
1 Configue the AWSServiceManager with static configuration like :-
let staticCredit = AWSStaticCredentialsProvider(accessKey: kAppSyncAccessKey, secretKey: kAppSyncSecretKey)
let AppSyncRegion: AWSRegionType = .USEast2
let config = AWSServiceConfiguration(region: AppSyncRegion, credentialsProvider: staticCredit)
AWSServiceManager.default().defaultServiceConfiguration = config
2 Uploading picture with this method : -
func updatePictureToServer(url:URL, completion:#escaping (Bool)->Void){
let transferManager = AWSS3TransferManager.default()
let uploadingFileURL = url
let uploadRequest = AWSS3TransferManagerUploadRequest()
let userBucket = String(format: "BUCKET")
uploadRequest?.bucket = userBucket
let fileName = String(format: "%#%#", AppSettings.getUserId(),".jpg")
uploadRequest?.key = fileName
uploadRequest?.body = uploadingFileURL
transferManager.upload(uploadRequest!).continueWith(executor: AWSExecutor.mainThread(), block: { (task:AWSTask<AnyObject>) -> Any? in
if let error = task.error as NSError? {
if error.domain == AWSS3TransferManagerErrorDomain, let code = AWSS3TransferManagerErrorType(rawValue: error.code) {
switch code {
case .cancelled, .paused:
break
default:
print("Error uploading: \(String(describing: uploadRequest!.key)) Error: \(error)")
}
} else {
print("Error uploading: \(String(describing: uploadRequest!.key)) Error: \(error)")
}
completion(false)
return nil
}
_ = task.result
completion(true)
print("Upload complete for: \(String(describing: uploadRequest!.key))")
return nil
})
}
3 And finally i'm able to see the uploaded image on the S3 bucket
But i'm concerned about how to save the url of the image and how to retrieve the image because when i have to make the buket PUBLIC to retrieve the image and i don't think that's a good approach, plus is it necessary to have a Cognito user pool because we aren't using Cognito user pool yet in our app and not have much knowledge about that too and documents are not helping in practical situations because we are implementing ti for the first time so we need some little help.
So two question : -
Proper procedure to use for uploading and retrieving images for S3 and AppSync.
Is it necessary to use Cognito user pool for image uploading and retrieving.
Thanks
Note: Any suggestion or improvement or anything related to the AppSync, S3 or DynamoDB will be truly appreciated and language is not a barrier just looking for directions so swift or objective-c no problem.
You need per-identity security on the bucket using Cognito Federated Identities which gives each user their own secure bucket. You can leverage the AWS Amplify to set this up for your project with $amplify add auth and selecting the default config, then $amplify add storage which configures that bucket and pool with appropriate permissions to use private uploads.
For more info checkout the repo: https://github.com/aws-amplify/amplify-cli

Download and use files stored in Firebase Storage from different projects

I'm using Firebase Storage to store images in FB, I'm saving download URL in Realtime DB and to retrieve some photos from the storage, I'm using this code:
let storage = FIRStorage.storage()
imageRefInDB.observeEventType(.Value, withBlock: { (snapshot) in
// Get download URL from snapshot
let downloadURL = snapshot.value as! String
// Create a storage reference from the URL
let storageRef = storage.referenceForURL(downloadURL)
storageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if (error != nil) {
print(error.debugDescription)
} else {
let downloadedImage = UIImage(data: data!)
}
}
})
My problem is I wanna use some of my photo that are inside another Firebase project. For example, the download URLs will be:
https://firebasestorage.googleapis.com/v0/b/PROJECT1.appspot.com/o/someMedia&token
When in my current project, the bucket is different like :
https://firebasestorage.googleapis.com/v0/b/PROJECT2.appspot.com/o/someMedia&Token
When I try to using the PROJECT1 files in my current PROJECT2, I'm getting the following error:
reason: 'Provided bucket: PROJECT1.appspot.com does not match bucket specified in FIRApp configuration: PROJECT2.appspot.com
Is there a way to enable downloading theses files from other projects like a setting in Firebase without retrieving them with regular URL downloads?
Thanks for your help!
I tried that once and it lead to an unhappy time. In my case I had two different buckets for my images, so I had to find an alternate solution.
If you have only a single bucket, you can try configuring that bucket in your FIRApp:
let firOptions = FIROptions(googleAppID: googleAppID, bundleID: bundleID, GCMSenderID: GCMSenderID, APIKey: nil, clientID: nil, trackingID: nil, androidClientID: nil, databaseURL: databaseURL, storageBucket: storageBucket, deepLinkURLScheme: nil)
FIRApp.configureWithName("anotherClient", options: options)
let app = FIRApp(named: "anotherClient")
let storageRef = FIRStorage.storage(app: app!).reference()
Taken from this answer, so you may need to modify it to suit your needs
With that app, you can then use the Firebase Storage API as you're already doing to download the image data.
If you have multiple buckets (as I had), you either have to configure multiple apps or (as I did) handle downloading the image from the downloadURL yourself (without using the Firebase Storage API):
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
let data = NSData(contentsOfURL: downloadURL) //make sure your image in this url does exist, otherwise unwrap in a if let check
dispatch_async(dispatch_get_main_queue(), {
let downloadedImage = UIImage(data: data!)
});
}

Firebase Storage: child() doesn't work with iOS App

I'm getting the following error when trying to download an image from my Firebase Storage:
Error Domain=FIRStorageErrorDomain Code=-13010 "Object 2xxxxxxx8/profile_pic does not exist."
(I obviously put the x's up there to mask private info.)
I'm adding a path reference to my Firebase Storage using the following code:
let storage = FIRStorage.storage()
let storageRef = storage.referenceForURL("gs://project-4xxxxxxxxxxxxx.appspot.com")
let profilePicReference = storageRef.child(signedInUser.uid + "/profile_pic")
I know the code above is good cause everything was working correctly: I could see a folder was added in my Storage space, and an image was uploaded into that folder - all directly from my iOS App.
The problems started when I manually deleted said folder from my Firebase Storage (I did this through the Firebase web portal) - just cause I wanted verify everything was working, so I deleted the folder to start fresh - expecting the code above would recreate it once I ran the App again - and since then I'm getting this error over and over again.
Really makes no sense.
Are there any quirks or issues with Firebase Storage? Some sort of caching that has to be addressed?
Any tips would be greatly appreciated!
Are there any quirks or issues with Firebase Storage? Some sort of
caching that has to be addressed?
An UploadTask executes asynchronously. If I try downloading an image immediately after uploading an image, I can reproduce your error. What's happening is that the download code executes before the image finishes uploading, producing the image-does-not-exist error. You can see that the download code executes too early by printing out some messages in the callbacks:
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
//Upload the image:
let uploadTask = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
}
}
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
That code produces the output:
[My Download Error]: ...."Object images/align_menu.tiff does not exist."...
and then after a few seconds I see the output:
[My Upload Success]:
[URL for download]: ...
which demonstrates that the download callback is executing before the upload callback. I can't quite figure out the details of why that happens--but obviously the callbacks are not added to a serial queue.*
To cure the asynchronous problem, you have several options:
1) Put the download code inside the callback for the upload code.
That way, the download won't start executing until after the image has successfully uploaded. After I did that, deleting the image using the Firebase Storage webpage before running the app had no deleterious effect on my upload/download, and the messages were output in the expected order:
[My Upload Success]:
[URL for download]: ...
[My Download Success]:
2) Attach a .Success observer to the uploadTask.
As described in the Firebase docs, in the Monitor Upload Progress section, you can get notified if the uploadTask successfully uploads the image:
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
//Upload the image:
let uploadTask = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
}
}
let observer = uploadTask.observeStatus(.Success) { (snapshot) -> Void in
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
}
3) Use Grand Central Dispatch to notify you when the upload is successful.
You don't have control over what queues the callbacks get added to (the Firebase method implementations decide that), but you can use Grand Central Dispatch to notify you when arbitrary code finishes executing. The following works for me:
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
let myExecutionGroup = dispatch_group_create()
dispatch_group_enter(myExecutionGroup)
//Upload the image:
let _ = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
dispatch_group_leave(myExecutionGroup)
}
}
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
dispatch_group_notify(myExecutionGroup, queue) {
//This callback executes for every dispatch_group_leave().
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
}
* I tried putting a sleep(10) between the original upload code and download code, and that did not alleviate the problem. I thought that if the upload callback was executing on a background thread, then the upload callback would have time to complete while the main thread was sleeping, then after the sleep finished the download code would execute and the download callback would be added to a queue somewhere, then the download callback would execute. Because the sleep(10) didn't solve the problem, that meant the upload callback had to have been added to an execution queue for the main thread, and the sleep halted the main thread and anything in the queue from executing.
That leads me to believe that the upload and download callbacks are added to an asynchronous queue on the main thread (it's not a synchronous queue otherwise the callbacks would execute in order). I think an asynchronous queue on the main thread means that when there is dead time on the main thread, the tasks in the queue will execute, and you also get rapid switching between the various tasks when there is dead time in a particular task, like waiting for an HTTP response. For example, if there are two tasks in an asynchronous queue on the main thread, then there is rapid switching between the main thread, task1, and task2 whenever there is dead time in any one of them.

Resources