通过流式传输上传文件:显示错误日志“操作无法完成。(kCFErrorDomainCFNetwork 错误 303。)”

2024-01-06

我正在尝试通过流式传输上传大文件,最近我收到以下错误日志:

Error Domain=kCFErrorDomainCFNetwork Code=303 "The operation couldn’t be completed. (kCFErrorDomainCFNetwork error 303.)" UserInfo=0x103c0610 {NSErrorFailingURLKey=/adv,/cgi-bin/file_upload-cgic, NSErrorFailingURLStringKey/adv,/cgi-bin/file_upload-cgic}<br>

这是我设置 bodystream 的地方:

-(void)finishedRequestBody{ // set bodyinput stream
    [self appendBodyString:[NSString stringWithFormat:@"\r\n--%@--\r\n",[self getBoundaryStr]]];
    [bodyFileOutputStream close];
    bodyFileOutputStream = nil;
    //calculate content length
    NSError *fileReadError = nil;
    NSDictionary *fileAttrs = [[NSFileManager defaultManager] attributesOfItemAtPath:pathToBodyFile error:&fileReadError];
    NSAssert1((fileAttrs != nil),@"Couldn't read post body file",fileReadError);
    NSNumber *contentLength = [fileAttrs objectForKey:NSFileSize];

   NSInputStream *bodyStream = [[NSInputStream alloc] initWithFileAtPath:pathToBodyFile];
    [request setHTTPBodyStream:bodyStream];
    [bodyStream release];

    if (staticUpConneciton == nil) {          
        NSURLResponse *response = nil;
        NSError *error = nil;
        NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];    
        staticUpConneciton = [[[NSURLConnection alloc]initWithRequest:request delegate:self] retain];                   
    }else{
        staticUpConneciton = [[NSURLConnection connectionWithRequest:request delegate:self]retain];
    }  
}

这就是我写蒸汽的方式:

    -(void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode{
        uint8_t buf[1024*100];
        NSUInteger len = 0;
        switch (eventCode) {
            case NSStreamEventOpenCompleted:
                NSLog(@"media file opened");
                break;
            case NSStreamEventHasBytesAvailable:
              //  NSLog(@"should never happened for output stream");
                len = [self.uploadFileInputStream read:buf maxLength:1024];
                if (len) {
                    [self.bodyFileOutputStream write:buf maxLength:len];
                }else{
                    NSLog(@"buf finished wrote %@",self.pathToBodyFile);
                    [self handleStreamCompletion];
                }
                break;
            case NSStreamEventErrorOccurred:
                NSLog(@"stream error");
                break;
            case NSStreamEventEndEncountered:
                NSLog(@"should never for output stream");
                break;
            default:
                break;
        }
}

关闭流

-(void)finishMediaInputStream{
    [self.uploadFileInputStream close];
    [self.uploadFileInputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    self.uploadFileInputStream = nil;
}

-(void)handleStreamCompletion{
    [self finishMediaInputStream];
    // finish requestbody
    [self finishedRequestBody];
}

当我实现这个方法 needNewBodyStream: 时发现错误:请参阅以下代码:

-(NSInputStream *)connection:(NSURLConnection *)connection needNewBodyStream:(NSURLRequest *)request{
    [NSThread sleepForTimeInterval:2];
    NSInputStream *fileStream = [NSInputStream inputStreamWithFileAtPath:pathToBodyFile];
    if (fileStream == nil) {
        NSLog(@"NSURLConnection was asked to retransmit a new body stream for a request. returning nil!");
    }
    return fileStream;
}

这是我设置标题和 mediaInputStream 的地方

-(void)setPostHeaders{
    pathToBodyFile = [[NSString alloc] initWithFormat:@"%@%@",NSTemporaryDirectory(),bodyFileName];
    bodyFileOutputStream = [[NSOutputStream alloc] initToFileAtPath:pathToBodyFile append:YES];
    [bodyFileOutputStream open];

    //set bodysteam
    [self appendBodyString:[NSString stringWithFormat:@"--%@\r\n", [self getBoundaryStr]]];
    [self appendBodyString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", @"target_path"]];
    [self appendBodyString:[NSString stringWithFormat:@"/%@",[NSString stringWithFormat:@"%@/%@/%@",UploaderController.getDestination,APP_UPLOADER,[Functions getDateString]]]];
    [self appendBodyString:[NSString stringWithFormat:@"\r\n--%@\r\n", [self getBoundaryStr]]];
    [self appendBodyString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"file_path\"; filename=\"%@\"\r\n", fileName]];
    [self appendBodyString:[NSString stringWithString:@"Content-Type: application/octet-stream\r\n\r\n"]];

    NSString *tempFile = [NSTemporaryDirectory() stringByAppendingPathComponent:@"uploadFile"]; 

    NSError *fileReadError = nil;
    NSDictionary *fileAttrs = [[NSFileManager defaultManager] attributesOfItemAtPath:tempFile error:&fileReadError];
    NSAssert1((fileAttrs != nil),@"Couldn't read post body file",fileReadError);
    NSNumber *contentLength = [fileAttrs objectForKey:NSFileSize];
    [request setValue:[contentLength stringValue] forHTTPHeaderField:@"Content-Length"];    
    NSInputStream *mediaInputStream = [[NSInputStream alloc] initWithFileAtPath:tempFile];
    self.uploadFileInputStream = mediaInputStream;    
    [self.uploadFileInputStream setDelegate:self];
    [self.uploadFileInputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [self.uploadFileInputStream open];    
}

这就是我从相机胶卷复制数据的方法

-(void)copyFileFromCamaroll:(ALAssetRepresentation *)rep{
    //copy the file from the camarall to tmp folder (automatically cleaned out every 3 days)
    NSUInteger chunkSize = 100 * 1024;
    NSString *tempFile = [NSTemporaryDirectory() stringByAppendingPathComponent:@"uploadFile"];
    NSLog(@"tmpfile %@",tempFile);
    uint8_t *chunkBuffer = malloc(chunkSize * sizeof(uint8_t));
    NSUInteger length = [rep size];

    NSFileHandle *fileHandle = [[NSFileHandle fileHandleForWritingAtPath: tempFile] retain];
    if(fileHandle == nil) {
        [[NSFileManager defaultManager] createFileAtPath:tempFile contents:nil attributes:nil];
        fileHandle = [[NSFileHandle fileHandleForWritingAtPath:tempFile] retain];
    }

    NSUInteger offset = 0;
    do {
        NSUInteger bytesCopied = [rep getBytes:chunkBuffer fromOffset:offset length:chunkSize error:nil];
        offset += bytesCopied;
        NSData *data = [[NSData alloc] initWithBytes:chunkBuffer length:bytesCopied];
        [fileHandle writeData:data];
        [data release];
    } while (offset < length);
    [fileHandle closeFile];
    [fileHandle release];
    free(chunkBuffer);
    chunkBuffer = NULL;          
    NSError *error;
    NSData *fileData = [NSData dataWithContentsOfFile:tempFile options:NSDataReadingMappedIfSafe error:&error];
    if (!fileData) {
        NSLog(@"Error %@ %@", error, [error description]);
        NSLog(@"%@", tempFile);
        //do what you need with the error
    }            
}

有人有什么想法吗?我错过了什么?


Edit:

为了提前提到这一点:

在 iOS 7 中,可能有一个简单的解决方案来上传大文件file。请参阅NSURLSession, NSURLSessionTask, 尤其:

- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request 
                                         fromFile:(NSURL *)fileURL 
                                completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;

否则,

您的代码有很多问题:

  • 多部分消息的构造不正确(包括内容长度)。

  • You use sendSynchronousRequest并将它们与委托方法混合。删除该行:

    NSData *responseData = [NSURLConnection sendSynchronousRequest:request  returningResponse:&response error:&error];)    
    
  • 假设您想通过创建临时文件来上传资产,则可以更轻松地完成此操作(从资产创建临时文件)。事实上,您不需要流委托方法。避免临时文件的另一种方法需要“绑定的流对” - 然后您需要流委托。但后者更为复杂。

鉴于您的要求,我强烈推荐使用NSURLConnection在异步模式下实现委托。

您的问题仍然有足够的内容可以分为三个或更多问题,因此我将限制只回答一个问题:

当上传一个file对于服务器来说,有一些既定的方法可以通过 HTTP 来实现这一点。建议的方法(但不是唯一的方法)是使用POST请求与multipart/form-data具有特殊媒体类型处置.

让我们看一下与以下内容相关的部分的代码上传中文件:

您提供的代码似乎在声明中有问题

[self appendBodyString:[NSString stringWithFormat:@"\r\n--%@--\r\n",[self getBoundaryStr]]];

在方法的开头finishedRequestBody。这看起来像“多部分正文”的“结束分隔符”,它必须出现在最后一部分之后 - 但不能更早。所以,这是一个错误。

现在,让我们弄清楚如何构建一个正确的multipart/form-data信息:

构建用于文件上传的多部分消息

我们假设你already在路径中有要上传的文件正文文件路径表示为NSInputStream。这在声明中正确完成:

NSInputStream *bodyStream = [[NSInputStream alloc] initWithFileAtPath:pathToBodyFile];

通过 multipart/form-data 消息上传文件的规则定义在RFC 1867 https://www.rfc-editor.org/rfc/rfc1867 "HTML 中基于表单的文件上传“以及一大堆指定协议的相关和依赖的 RFC非常详细(您现在不需要阅读它,但可能稍后会阅读)。

最近有一个关于 SO 的问题,我试图澄清多部分媒体类型:NSURLRequest 上传多个文件 https://stackoverflow.com/questions/18879469/nsurlrequest-upload-multiple-files/18924214#18924214。我也推荐去那里看看。

根据 RFC 1867 的文件上传基本上是一个多部分/表单数据消息,除了它can use a 专门您可以在处置参数中指定原始文件名的处置。相关的 RFC 是RFC 2388 https://www.rfc-editor.org/rfc/rfc2388“从表单返回值:multipart/form-data”,以及其他几十个,可能特别相关RFC 2047 https://www.rfc-editor.org/rfc/rfc2047, RFC 6657 https://www.rfc-editor.org/rfc/rfc6657, RFC 2231 https://www.rfc-editor.org/rfc/rfc2231 ).

Note:如果你有任何specific有关任何详细信息的疑问,始终建议阅读相关的 RFC。 (不过,找到最新的和实际的是一个挑战。)

A multipart/form-data消息包含一系列parts。表单数据部分由一些“参数名称”或“标签”(通过配置标头表示)、其他可选标头和正文组成。

Each part必须有一个内容处置标题(代表“参数名称”或“标签”),其“值”等于“表单数据”并且具有名称属性其中指定了一个字段名称(通常但不排他地指“HTTP 表单”中的字段)。例如:

content-disposition: form-data; name="fieldname"

每个部分可能有一个可选的Content-Type标头。如果没有指定任何人,text/plain假设。

在标题(如果有)之后body接下来。

因此,一个部分可以被视为“参数/值”对(加上一些可选的标头)。

如果正文是文件内容,则可以在 content-disposition 中指定原始文件名filename参数,例如:

content-disposition: form-data; name="image"; filename="image.jpg"

此外,您应该设置Content-Type该部分的标头相应地与实际文件类型匹配,例如:

Content-Type: image/jpeg

A multipart/form-data消息体由以下部分组成one或更多零件。这些部件用一个分隔开boundary.

(如何设置边界,在 SO 上给定的链接中有更详细的描述NSURLRequest 上传多个文件 https://stackoverflow.com/questions/18879469/nsurlrequest-upload-multiple-files/18924214#18924214以及相关的 RFC。)


Example:

上传 MIME 类型为“image/jpeg”的文件“image.jpg”

使用方法创建 HTTP 消息POST并设置Content-Type标头至multipart/form-data指定一个boundary:

Content-type: multipart/form-data, boundary=AaB03x

“multipart/form-data”消息的“multipart body”由以下部分组成one部分如下所示(注意:CRLF 是明确地可见的):

\r\n--AaB03x\r\n
Content-Disposition: form-data; name="image"; filename="image.jpg"\r\n
Content-Type: image/jpeg\r\n
\r\n<file-content>--AaB03x--

现在,您需要使用以下命令将这个“大纲”“翻译”为 Objective-C:NSURLConnection and NSURLRequest,乍一看似乎很简单。然而,出现了一些微妙的问题:

First:

A 多部分消息体由以下部分组成one或更多零件。正如您所看到的,部件本身包含边界和标题以及主体。构建零件主体现在变得复杂,因为零件主体是stream(您的文件输入流)。现在的任务是“合并”NSData对象(边界和标题)和文件输入流导致(一些摘要)新的输入源。这个新的输入源现在需要与其他部件(如果有的话)一起再次形成新的输入源这最终是一个NSInputStream代表整体多部分体 of the multipart/form-data要求。这eventual输入流必须设置为HTTPBodyStream的财产NSMutableURLRequest.

我承认,这是一个挑战需要许多辅助类和它自己的单元测试!

使用内存映射文件作为表示的简化large资产文件可能是徒劳的,因为您需要形成(又名合并)complete多部分主体(一个或多个部分)。这最终将成为NSData包含标头和文件内容的对象,最终分配在堆上。对于非常大的资产(>300MByte),这可能会失败。

一个解决方案是使用绑定流对(通过固定大小的缓冲区连接的输入流和输出流),其中一端(输出流)用于write所有部分(通过输入流的标头和文件内容),另一端(输入流)用于“绑定”到HTTPBodyStream财产。

这个单一问题的解决方案值得一个新的 SO 问题。 (Apple 提供了演示此技术的示例)。

现有的解决方案可以轻松设置第三方库提供的多部分/表单数据请求。然而,即使是知名的第三方库也很难做到这一点。

Second:

一个警告:

与任何“语言”一样,HTTP 协议对正确的语法非常挑剔,即分隔符元素的出现、字符编码、转义和引用等。例如,如果您错过了 CRLF 或错过了应用proper特定字符串(例如文件名)的编码,或者如果您没有在协议的某些元素(例如边界或文件名)中必要时应用引号,您的服务器可能无法理解该消息或误解它。

有大量 RFC 试图明确指定具体细节。但要小心,找到真正指定当前问题的 RFC 需要付出一些努力。 RFC 偶尔会更新和废弃,以不同的“当前”RFC 结尾。因此,在编写代码时请记住这一点:可能存在边缘情况,即您的代码未根据当前 RFC 编写,并且会出现意外行为。

因此,您现在可以接受挑战 - 这确实是先进的东西 - 并尝试实现“多部分/表单数据主体作为 NSInputStream“正确,或者您尝试第三方解决方案,该解决方案may在某些条件下工作,有时不工作。


文件上传的技巧和提示NSURLRequest

  • 对于较大的文件,请使用NSInputStream文件的表示, 代替NSData表示。 (您可以尝试使用映射 文件和NSData不过,也是)。

  • 当设置一个NSInputStream作为请求正文,不要打开输入 溪流。

  • 当设置一个NSInputStream作为请求正文,您必须覆盖connection:needNewBodyStream:委托方法并提供一个新的 再次流对象。 (你这样做是正确的,尽管我没有 了解延迟的目的。)

  • 当提供输入流作为请求正文without设置一个Content-Length明确标头,NSURLConnection将使用 ”分块传输 编码 http://en.wikipedia.org/wiki/Chunked_transfer_encoding”。 通常,这对服务器来说不是问题 - 但在某些情况下 是,你可以设置Content-Length明确地(如果可以的话) 确定长度)和NSURLConnection不会使用“分块 传输编码”以不再传输请求正文。

  • 设置“Content-Length”标头时,请确保设置correct length.

  • 当使用NSData对象作为请求体,不需要设置Content-Length标头,NSURLConnection将设置这个 自动,除非明确指定。

  • 文件名在配置标头可能需要引用和编码(参见RFC 2231 https://www.rfc-editor.org/rfc/rfc2231).

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

通过流式传输上传文件:显示错误日志“操作无法完成。(kCFErrorDomainCFNetwork 错误 303。)” 的相关文章

  • 不明白 Swift 中的闭包示例

    我正在尝试了解 swift 和闭包 我被这个例子困住了 numbers map number Int gt Int in let result 3 number return result 什么是 number Int gt Int 它是一
  • iOS 解决方法:在没有 CSS 属性的情况下平滑滚动 滚动行为:平滑?

    编辑 我找到了一个 jQuery 解决方案 https codepen io chriscoyier pen dpBMVP https codepen io chriscoyier pen dpBMVP这个确实可以在 iOS 上运行 我想
  • ios 在后台处理推送通知

    我想保存应用程序处于后台状态时到达的推送通知 我知道关于 void application UIApplication application didReceiveRemoteNotification NSDictionary userIn
  • ios - 在哪里放置 s.static_framework = true

    我在 CocoaPods 中的级别为 0 当我使用pod install有一个错误说 The Pods App target has transitive dependencies that include static framework
  • 将 UIButton 中的图像缩放到 AspectFit?

    我想将图像添加到 UIButton 并且还想缩放图像以适合 UIButton 使图像变小 请告诉我该怎么做 这是我尝试过的 但它不起作用 将图像添加到按钮并使用setContentMode self itemImageButton setI
  • 在 HTML5 iOS 7 / iOS 8 中显示十进制键盘

    经过几个小时的搜索后 我只是有一个简单的问题 是否有可能在网络浏览器输入字段中显示小数键盘 input type number 只显示数字 但我需要在左下角使用逗号或点 我尝试过任何事情 pattern step等等 但没有显示十进制键盘
  • 在 SwiftUI 中使用分段式选取器在两个页面之间滑动

    我有一个Picker with pickerStyle SegmentedPickerStyle 使其成为分段控件 我想让页面在之间平滑滑动 而不是使用条件语句替换视图 这是我迄今为止所做的 gif 这是到目前为止的代码 由if 而不是在不
  • 在带有 Storyboard 的 XCode 4 中以模态方式推送视图时,出现“对开始/结束外观转换的不平衡调用”警告

    在网上进行了一些研究但没有成功后 我来这里向您询问有关我的警告的问题 实际上 我有一个带有导航控制器的视图 V1 我想在 V1 完成加载时推送模态视图 V2 所以我用performSegueWithIdentifier方法 我正在使用故事板
  • iPhone UINavigationBar 使用 [UINavigationBar 外观] 更改所有控制器的字体样式

    我知道我可以单独更改导航栏的字体 如本答案所述 更改导航栏的字体 https stackoverflow com questions 5832036 change the navigation bars font 目前我正在使用一种更全局的
  • 所需框架与静态库

    构建现代框架 https developer apple com videos play wwdc2014 416 says 每个应用程序都有自己的自定义框架副本 https stackoverflow com a 15262463 242
  • 黑莓上的视频流

    有没有办法从服务器流式传输和播放视频文件 黑莓是否提供可以播放流视频的内置视频播放器 是的你可以 在 bb 设备上串流视频有两种方法 使用 jsr 135 中的 javax microedition media Player 使用标准媒体应
  • iOS 中的构建对象文件扩展名是什么?

    当我在项目中构建java对象类时 将创建带有 class扩展名的构建文件 并且人类不可读 快速构建文件怎么样 example car java gt build gt car class 构建后会是什么 car swift gt build
  • 个人帐户开发者之间的 Apple 开发/分发证书

    我一直在到处寻找有关处理证书的正确答案 想象一下以下帐户 Joe拥有个人 Apple 帐户 但他根本不会编码 他只是发布了该应用程序并将其称为自己的 Bob还有一个个人 Apple 帐户 Bob 是一位编码专家 Joe 付费让他开发他的第一
  • 频繁绘制 CGPath 时的性能

    我正在开发一个将数据可视化为折线图的 iOS 应用程序 该图被绘制为CGPath在全屏自定义中UIView最多包含 320 个数据点 数据经常更新 图表需要相应地重新绘制 刷新率为 10 秒就很好了 到目前为止很容易 然而 我的方法似乎需要
  • iOS 循环对象的属性并添加操作

    我有一个具有几个类似属性的类 UISliders 我想添加用户开始和结束使用每个滑块时的操作 每个滑块都将链接到同一个选择器 因此我考虑只是迭代它们 而不是编写 10 个几乎相同的代码块 问题是 最有效的方法是什么 我尝试过这样的事情 在运
  • NVActivityIndi​​catorView 仅适用于特定视图

    我正在使用这个库https github com ninjaprox NVActivityIndi catorView https github com ninjaprox NVActivityIndicatorView用于显示加载指示器
  • 为什么我的视图仍然以横向呈现?

    我的视图是由导航控制器控制的 因此我将导航控制器支持的方向设置为明确的纵向和纵向UpSideDown 这可以工作 但是如果调用视图时前一个视图处于横向状态 它将以横向方式呈现并保持横向状态 直到设备旋转 如何防止这种情况发生 这是我的代码
  • 苹果企业程序分发问题[关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 这个问题涉及到Apple iOS 开发者企业计划 http developer apple com programs ios enterprise 我
  • 如何反转 CGPath 的点顺序

    我想画一个圆圈 并用它打出字母 为此 我需要顺时针抚摸圆圈 逆时针抚摸字母 这一切都很好 但是当我使用 Core Text 获取字母路径时 我不知道如何从本质上反转该路径 不是镜像或旋转或任何东西 这很简单 我希望点笔画顺序是逆时针的 这实
  • RemoteIO 音频单元播放回调中的 AudioBufferList 内容

    我想 拦截 音频数据传送到 iOS 设备扬声器的过程 我相信这可以使用 RemoteIO 音频单元和回调来完成 在下面的playbackCallback中 ioData实际上包含任何音频数据吗 static OSStatus playbac

随机推荐