iosとnodeダウンロードファイルのアップロード


まず、アップロードとダウンロードには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");

上はサービス側のすべてのコードです.deferプロパティをtrueに設定すると、次の2つのライフサイクルコールバックが有効になります.しかし、このサービスは、直接CocoaRestClientでPOSTリクエストを送信しても通じず、httpヘッダにContent-Typeを追加する必要があるようです.
アップロードされたクライアントコード
Viewは省略し、重要なViewControlコードのみを紹介します
@interface YLSUploadViewController : UIViewController<NSURLSessionTaskDelegate>

-(void) doUpload;

@end

主にNSURLSessionTaskDelegateプロトコルを実装します.進捗バーを実装するには、ライフサイクルメソッドが必要です.
初期化コードは次のとおりです.
{
    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
", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r
", fileParam, fileName] dataUsingEncoding:NSUTF8StringEncoding]]; [body appendData:[@"Content-Type: application/zip\r
\r
" dataUsingEncoding:NSUTF8StringEncoding]]; [body appendData:dataOfFile]; [body appendData:[[NSString stringWithFormat:@"\r
"] dataUsingEncoding:NSUTF8StringEncoding]]; } [body appendData:[[NSString stringWithFormat:@"--%@--\r
", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; return body; }

NSURLSessionUploadTaskをどうやって手に入れるかがポイントです.NSURLSessionはuploadTaskWithRequest:fromFile:方法を提供していますが、実践を経て、走っていないことがわかりました.NSURLSessionはContent-Typeヘッダを自動的に追加したり、Dataにboundaryを自動的に追加したりしないようです.結果としてserver側がエラーを報告します.
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

これは簡単です.あまり紹介しません.totalBytesSentとtotalBytesExpectedSendの2つの変数があります.テキストヒントを作るにしても、進捗バーを作るにしても、簡単に実現できます.
しかし、上記のサンプルコードは、自分を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];
    });
}

コードは、block callback付きの別のAPIではなくdownloadTaskWithRequest:メソッドを呼び出すことに注意してください.completionHandlerが設定されている場合、delegate methodは呼び出されませんが、アップロードと同様にdelegate methodがダウンロードの進捗バーを実現する必要があることがわかりました.
@interface YLSDownloadViewController : UIViewController<NSURLSessionDownloadDelegate>

この方法では、進捗バーを実装できます.
- (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];
}