通过流式传输上传文件:显示错误日志“操作无法完成。(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。)” 的相关文章

  • 调整 UIImage 的大小而不将其完全加载到内存中?

    我正在开发一个应用程序 用户可以在其中尝试加载非常非常大的图像 这些图像首先在表格视图中显示为缩略图 我的原始代码会在大图像上崩溃 因此我重写它以首先将图像直接下载到磁盘 是否有一种已知的方法可以调整磁盘上图像的大小 而无需通过以下方式将其
  • 访问目标 c 中的类方法。使用 self 还是类名?

    我正在学习 iOS 编程 并且对以下有关关键字 self 的使用的代码感到困惑 据我了解 self就像Java的this 它指的是当前实例 当我想调用类方法时 通常的方式应该是这样 PlayingCard validSuits 但是侵入实例
  • 在 UIWebView 中播放 Facebook 视频

    有谁知道如何在 Facebook 上播放视频UIWebView 我的应用程序将视频上 传到 Facebook 并检索视频的网址 我想将此网址嵌入到UIWebView播放 我已经为 youtube 解决了这个问题 但没有为 Facebook
  • 如何解决 Xcode 5 中的红色(已移动)文件?

    在 Xcode 4 中 当您要移动文件时 可以通过单击右侧菜单中的按钮并通过 Finder 选择新位置来解析文件的新位置 在 Xcode 5 中 右侧菜单中没有按钮 我还没有找到任何方法通过右键单击文件或顶部菜单栏选项来指定文件的新位置 在
  • 在 Swift 中从 Parse 加载图像

    我成功地将数据从 Parse 提取到 swift 中 但我的图像似乎没有按照我的方式工作 在我的 cellForRowAtIndexPath 方法中 我执行以下操作 var event AnyObject eventContainerArr
  • CoreBluetooth:检测设备超出范围/连接超时

    我正在设计一个 iOS 框架来处理多个 BLE 设备 均为同一类型 目前一切都运行良好 除了一件事 客户想要一个包含可用设备的列表 但是 我如何检测过去发现的设备何时不再可用 当我尝试连接到不再可用的设备时 会出现另一个问题 文档说 连接尝
  • ios - 如何声明静态变量? [复制]

    这个问题在这里已经有答案了 C 中声明的静态变量如下 private const string Host http 80dfgf7c22634nbbfb82339d46 cloudapp net private const string S
  • 更改 NSMutableAttributedString 中链接的颜色

    我有以下代码 但我的链接始终是蓝色的 我如何改变它们的颜色 string addAttribute NSLinkAttributeName value tag range NSMakeRange position length string
  • React Native facebook iOS sdk 构建失败

    我已遵循 Facebook 开发人员指南中列出的 iOS React Native sdk 的所有准则 但我仍然无法构建该应用程序 附上我的配置和构建日志的屏幕截图 Ld Users alaaattya Library Developer
  • CAShapeLayer 上的渐变颜色效果

    我正在尝试在 CAShapeLayer 上应用渐变颜色 为此我编写代码 void addCircle Drawing code UIBezierPath aPath UIBezierPath bezierPathWithArcCenter
  • 在 iOS 上构建 WebRtc

    我按照以下说明成功地在 MAC 上构建了 WebRTC http www webrtc org reference getting started http www webrtc org reference getting started
  • HttpClient setReachabilityStatusChangedBlock 声明没有接口

    尝试使用 AFNetworkings ReachabilityStatusChanged 但得到 HTTPCLIENT 没有可见的 interface 声明选择器 setReachabilityStatusChangeBlock 但Http
  • 如何将 RGB 值转换为十六进制字符串 iOS swift

    我想将 RGB 值转换为十六进制字符串 我将十六进制转换为 RGB 如下所示 但反之亦然 func hexStringToRGB hexString String gt red CGFloat green CGFloat blue CGFl
  • iOS 中第一响应者的正式定义是什么?

    据我所知 第一响应者对象是根据输入活动等接收回调信号 并且它将沿着链向上冒泡 直到找到愿意处理它的响应者 但更正式地说 第一响应者的范围是什么 例如 它是应用程序范围的响应程序吗 似乎作为第一响应者只是说这个特定的对象将收到交互通知 其他响
  • 当您从不同的视图控制器进行segue时,如何将数据从一个视图控制器保存(追加)到另一个视图控制器?

    抱歉 问题有点长 请多多包涵 基本上 我正在尝试使用 swift 编写一个简单的递增 递减 ios 应用程序 我有三个主视图控制器 一个是 初始视图控制器 即根视图控制器 仅包含两个按钮 一个以模态方式呈现到实际计数页面 第二个视图控制器
  • -[_SwiftValueencodeWithCoder:]:无法识别的选择器发送到实例

    尝试使用 NSCoder 时出现错误 玩家 swift class Player NSObject NSCoding private var playerName String private var playerScore Int pri
  • FIRApp 链接器错误 [“_OBJC_CLASS_$_FIRApp”]

    我已经搜索过 SO 和 Google 但找不到有效的答案 我已经在多个项目中使用了新的 Firebase Cocoapod 但是现在 当将其添加到不同的项目时 我收到以下错误 我正在使用 Xcode 7 3 1 和 cocoapods 1
  • 当键盘出现时调整 UITextView 的大小

    我想在键盘出现时调整文本视图的大小 我的代码如下 我打开了自动布局 因此使用来自超级视图的 textView gt bottom 空间的约束 并通过 IBOutlet distanceFromBottom 引用它 void keyboard
  • iOS 上的推送通知渐进式 Web 应用程序

    我需要开发一个集成了推送通知的渐进式网络应用程序 在网上搜索我发现了关于这个主题的不同意见 如果我理解正确的话 目前我们无法在移动版 safari 中推送通知 但仅限桌面版 这样对吗 你有什么建议来获得相同的结果吗 我不是iOS专家 我想知
  • 如何从 App Store Connect 中删除自动创建的 macOS 应用程序

    I have an iOS app Recently macOS app automatically appeared in App Store Connect 我不打算在 macOS 上发布 iOS 应用程序 我怎样才能摆脱它 我打开了

随机推荐