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).