首先,上傳和下載需要server和client互相配合。同樣的客戶端代碼,可能在servlet裡能成功,換成node就不行,反過來也是一樣,因為不同的服務端,對http請求的處理可能不同。本文介紹的是服務端使用node,客戶端使用NSURLSession的情況
我還沒見過哪種實現方式,比node+express更簡單的:
var express = require("express"); var app = express(); app.use(express.bodyParser({ uploadDir: __dirname + '/../var/uploads', keepExtensions: true, limit: 100 * 1024 * 1024, defer: true })) .use('/svc/public', express.static(__dirname + '/../public')); app.post('/svc/upload', function (req, res) { req.form.on('progress', function (bytesReceived, bytesExpected) { }); req.form.on('end', function () { var tmp_path = req.files.file.path; var name = req.files.file.name; console.log("tmp_path: "+ tmp_path); console.log("name: "+name); res.end("success"); }); }); app.listen(3000); console.log("server started at 3000 port");
View省略,只介紹關鍵的ViewController代碼
@interface YLSUploadViewController : UIViewController-(void) doUpload; @end
下面是初始化的代碼:
{ NSString *boundary; NSString *fileParam; NSURL *uploadURL; } - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { boundary = @"----------V2ymHFg03ehbqgZCaKO6jy"; fileParam = @"file"; uploadURL = [NSURL URLWithString:@"http://192.168.1.103:3000/svc/upload"]; } return self; }
-(void) doUpload { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){ NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; NSData *body = [self prepareDataForUpload]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:uploadURL]; [request setHTTPMethod:@"POST"]; // 以下2行是關鍵,NSURLSessionUploadTask不會自動添加Content-Type頭 NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]; [request setValue:contentType forHTTPHeaderField: @"Content-Type"]; NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:body completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){ NSString *message = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"message: %@", message); [session invalidateAndCancel]; }]; [uploadTask resume]; }); } -(NSData*) prepareDataForUpload { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSString *uploadFilePath = [documentsDirectory stringByAppendingPathComponent:@"QQ.dmg"]; NSString *fileName = [uploadFilePath lastPathComponent]; NSMutableData *body = [NSMutableData data]; NSData *dataOfFile = [[NSData alloc] initWithContentsOfFile:uploadFilePath]; if (dataOfFile) { [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fileParam, fileName] dataUsingEncoding:NSUTF8StringEncoding]]; [body appendData:[@"Content-Type: application/zip\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; [body appendData:dataOfFile]; [body appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; } [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; return body; }
TypeError: Cannot call method 'on' of undefined
at /Users/apple/WebstormProjects/uploadAndDownloadServer/lib/main.js:15:14
at callbacks (/Users/apple/WebstormProjects/uploadAndDownloadServer/node_modules/express/lib/router/index.js:161:37)
at param (/Users/apple/WebstormProjects/uploadAndDownloadServer/node_modules/express/lib/router/index.js:135:11)
at pass (/Users/apple/WebstormProjects/uploadAndDownloadServer/node_modules/express/lib/router/index.js:142:5)
at Router._dispatch (/Users/apple/WebstormProjects/uploadAndDownloadServer/node_modules/express/lib/router/index.js:170:5)
at Object.router (/Users/apple/WebstormProjects/uploadAndDownloadServer/node_modules/express/lib/router/index.js:33:10)
at next (/Users/apple/WebstormProjects/uploadAndDownloadServer/node_modules/express/node_modules/connect/lib/proto.js:190:15)
at next (/Users/apple/WebstormProjects/uploadAndDownloadServer/node_modules/express/node_modules/connect/lib/proto.js:165:78)
at multipart (/Users/apple/WebstormProjects/uploadAndDownloadServer/node_modules/express/node_modules/connect/lib/middleware/multipart.js:60:27)
at /Users/apple/WebstormProjects/uploadAndDownloadServer/node_modules/express/node_modules/connect/lib/middleware/bodyParser.js:57:9
所以我最後的做法是,自己從File中讀出Data,並拼上所需的控制符,這都是在prepareDataForUpload()方法裡實現的
最後是Delegate method方法,我只需要一個:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
不過上面的示例代碼,為了方便把自己設置為delegate了。實際項目裡,應該把業務邏輯的類設置為upload組件的delegate。因為上傳之後應該做什麼,應該是在業務組件裡控制才對
相比上傳的代碼,下載簡單很多:
-(void) doDownload { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){ NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; NSURL *url = [NSURL URLWithString:@"http://192.168.1.103:3000/svc/public/bigfile.dmg"]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; [request setHTTPMethod:@"GET"]; NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request];// 未設置block [downloadTask resume]; }); }
@interface YLSDownloadViewController : UIViewController
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
下載後的文件,是放在tmp目錄下,如果不處理的話,馬上就會被移除,所以需要在另一個delegate method裡拷貝到最終路徑:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSString *distFilePath = [documentsDirectory stringByAppendingPathComponent:@"success.dmg"]; NSString* tempFilePath = [location path]; NSFileManager *fileManager = [NSFileManager defaultManager]; if([fileManager fileExistsAtPath:tempFilePath]){ [fileManager copyItemAtPath:tempFilePath toPath:distFilePath error:nil]; } [session invalidateAndCancel]; }