doudizhu2222 2016-10-11 16:48
浏览 40
已采纳

为什么NSURLSessionUploadTask到基于身份验证的PHP端点发送数据两次?

I am using an NSURLSessionUploadTask via an NSURLSession on macOS 10.12 Sierra with a custom delegate to upload a local file to an Apache server with a PHP script that requests basic authentication. It works except that the upload task appears to send the complete file data then is prompted by the server for the NSURLAuthenticationChallenge and, upon sending the proper credential, the upload task sends the entire data payload again. I would expect that the basic authentication challenge would come before the upload or that if it does come after the upload, that once confirmed, the data already uploaded would be accepted and not uploaded a second time. Any help getting the uploaded data to be posted only once would be most appreciated.

Endpoint script uploader.php:

<?php
$u = $_SERVER['PHP_AUTH_USER'];
$p = $_SERVER['PHP_AUTH_PW'];
if (($u !== 'user') || ($p !== 'password')) {
    header('WWW-Authenticate: Basic realm="Restricted Area"');
    header('HTTP/1.0 401 Unauthorized');
    die('<h1>401 Unauthorized</h1>Access Denied.');
}
$response = 'file upload failed: upload not specified';
if (isset($_FILES['upload'])) {
    $file_tmp_name = $_FILES['upload']['tmp_name'];
    $file_name = $_FILES['upload']['name'];
    $file_name_new = ('uploads/' . stripslashes($file_name));
    if (!is_writable(dirname($file_name_new))) {
        $response = 'file upload failed: directory is not writable.';
    } else {
        if (!move_uploaded_file($file_tmp_name, $file_name_new)) {
            $response = 'file upload failed: couldn\'t move file to ' . $new_name;
        } else {
            $response = $file_name_new;
        }
    }
}
echo($response);
?>

FileUploader.m:

- (void)startUpload {
    NSLog(@"starting upload");

    NSURL *url = [NSURL URLWithString:@"https://www.domain.com/uploader.php"];
    NSString *localPath = @"/path/to/file.ext";
    NSString *inputName = @"upload";

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:30.0];
    request.HTTPMethod = @"POST";
    NSString *boundary = [NSString stringWithFormat:@"x-mime-boundary://%@", [NSUUID UUID].UUIDString];
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
    [request setValue:contentType forHTTPHeaderField:@"Content-Type"];
    [request setValue:[NSBundle mainBundle].bundleIdentifier forHTTPHeaderField:@"User-Agent"];

    NSMutableData *postData = [NSMutableData data];
    [postData appendData:[[NSString stringWithFormat:@"
--%@
", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [postData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; type=\"file\"; filename=\"%@\"

", inputName, localPath.lastPathComponent] dataUsingEncoding:NSUTF8StringEncoding]];
    [postData appendData:[NSData dataWithContentsOfFile:localPath]];
    [postData appendData:[[NSString stringWithFormat:@"

--%@--
", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [request setValue:[NSString stringWithFormat:@"%ld", postData.length] forHTTPHeaderField:@"Content-Length"];

    NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
    self.session = [NSURLSession sessionWithConfiguration:defaultConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    [[self.session uploadTaskWithRequest:request fromData:[NSData dataWithData:postData]] resume];
}

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    NSLog(@"URLSession didReceiveChallenge: %@", challenge.protectionSpace.authenticationMethod);
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        completionHandler(((credential) ? NSURLSessionAuthChallengePerformDefaultHandling : NSURLSessionAuthChallengeUseCredential), credential);
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    NSLog(@"NSURLSessionTask didReceiveChallenge: %@", challenge.protectionSpace.authenticationMethod);
    NSString *username = @"user";
    NSString *password = @"password";
    NSURLCredential *credential = [NSURLCredential credentialWithUser:username password:password persistence:NSURLCredentialPersistenceForSession];
    completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
    NSLog(@"sent %ld b of %ld b (%.1f%%)", (long)totalBytesSent, (long)totalBytesExpectedToSend, (((float)totalBytesSent / (float)totalBytesExpectedToSend) * 100.0));
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    NSLog(@"upload complete");
    if (error) [NSApp presentError:error];
    [self.session invalidateAndCancel];
    self.session = nil;
}

Abridged console output:

2016-10-11 12:14:34.323485 FileUploader[23624:5580925] starting upload
2016-10-11 12:14:34.429419 FileUploader[23624:5580925] URLSession didReceiveChallenge: NSURLAuthenticationMethodServerTrust
2016-10-11 12:14:34.459239 FileUploader[23624:5580925] sent 32768 b of 10616647 b (0.3%)
2016-10-11 12:14:34.459351 FileUploader[23624:5580925] sent 65536 b of 10616647 b (0.6%)
...
2016-10-11 12:14:42.849080 FileUploader[23624:5580925] sent 10584064 b of 10616647 b (99.7%)
2016-10-11 12:14:42.849179 FileUploader[23624:5580925] sent 10616647 b of 10616647 b (100.0%)
2016-10-11 12:14:43.038092 FileUploader[23624:5580925] NSURLSessionTask didReceiveChallenge: NSURLAuthenticationMethodHTTPBasic
2016-10-11 12:14:43.040085 FileUploader[23624:5580925] sent 10649415 b of 21233294 b (50.2%)
2016-10-11 12:14:43.040141 FileUploader[23624:5580925] sent 10682183 b of 21233294 b (50.3%)
...
2016-10-11 12:14:46.508339 FileUploader[23624:5580925] sent 21200711 b of 21233294 b (99.8%)
2016-10-11 12:14:46.594864 FileUploader[23624:5580925] sent 21233294 b of 21233294 b (100.0%)
2016-10-11 12:14:46.757213 FileUploader[23624:5580925] upload complete
  • 写回答

1条回答 默认 最新

  • dongming6201 2016-12-06 03:06
    关注

    There are several ways to solve this. The first two that come to mind are:

    • Send an explicit HEAD or GET request before you send the POST request to verify the credentials. This will work >99% of the time.
    • Instead of sending an authentication error code, send a blob of JSON that your app can recognize as an error, and in that JSON blob, provide a UUID for the previous upload that your app can then provide in a new request to associate the upload with the user's account. This will work 100% of the time, but you'll need to add a cron job on the server to periodically delete old files.

    Either approach involves client-side and server-side changes.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 c语言怎么用printf(“\b \b”)与getch()实现黑框里写入与删除?
  • ¥20 怎么用dlib库的算法识别小麦病虫害
  • ¥15 华为ensp模拟器中S5700交换机在配置过程中老是反复重启
  • ¥15 java写代码遇到问题,求帮助
  • ¥15 uniapp uview http 如何实现统一的请求异常信息提示?
  • ¥15 有了解d3和topogram.js库的吗?有偿请教
  • ¥100 任意维数的K均值聚类
  • ¥15 stamps做sbas-insar,时序沉降图怎么画
  • ¥15 买了个传感器,根据商家发的代码和步骤使用但是代码报错了不会改,有没有人可以看看
  • ¥15 关于#Java#的问题,如何解决?