0

我在 macOS 10.12 Sierra 上使用带有自定义委托的NSURLSessionUploadTaskvia将本地文件上传到带有请求基本身份验证的脚本的服务器。它的工作原理是上传任务似乎发送完整的文件数据,然后由服务器提示输入,并且在发送正确的凭据后,上传任务再次发送整个数据有效负载。我希望基本身份验证挑战会在上传之前出现,或者如果它确实在上传之后出现,一旦确认,已经上传的数据将被接受并且不会再次上传。任何帮助将上传的数据仅发布一次将不胜感激。NSURLSessionApachePHPNSURLAuthenticationChallenge

端点脚本 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);
?>

文件上传器.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:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [postData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; type=\"file\"; filename=\"%@\"\r\n\r\n", inputName, localPath.lastPathComponent] dataUsingEncoding:NSUTF8StringEncoding]];
    [postData appendData:[NSData dataWithContentsOfFile:localPath]];
    [postData appendData:[[NSString stringWithFormat:@"\r\n\r\n--%@--\r\n", 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;
}

精简的控制台输出:

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
4

1 回答 1

1

有几种方法可以解决这个问题。最先想到的两个是:

  • 在发送 POST 请求以验证凭据之前发送显式 HEAD 或 GET 请求。这将在 >99% 的时间内有效。
  • 不要发送身份验证错误代码,而是发送您的应用程序可以识别为错误的 JSON blob,并在该 JSON blob 中,为上一次上传提供 UUID,然后您的应用程序可以在新请求中提供该 UUID 以将上传与相关联用户的帐户。这将在 100% 的时间内有效,但您需要在服务器上添加一个 cron 作业以定期删除旧文件。

任何一种方法都涉及客户端和服务器端的更改。

于 2016-12-06T03:06:53.600 回答