1

我正在尝试使用POST请求将多个文件上传到服务器,但由于某种原因,只提交了一个文件。我怀疑我在边界上做错了什么,但我不确定在哪里......

我的代码有什么问题?

    [_serverRequest setHTTPMethod:@"POST"];

    NSString *_boundary = @"14737809831466499882746641449";

    NSString *_contentType = [NSString stringWithFormat:@"multipart/form-data; charset=UTF-8; boundary=%@",_boundary];
    [_serverRequest setValue:_contentType forHTTPHeaderField:@"Content-Type"];

    /* reqeuest body */
    NSMutableData *_requestBody = [NSMutableData data];

    for (id _instance in self.currentBrowserInstances)
    {
        if ([_instance respondsToSelector:@selector(pathToDatabase)])
        {
            NSString *_databasePath = [_instance pathToDatabase];

            NSMutableString *_filename = [NSMutableString stringWithString:[self _generatedFilename]];
            [_filename appendFormat:@"_%@", [[_instance name] stringByReplacingOccurrencesOfString:@" " withString:@""]];

            if (_databasePath.pathExtension.length > 0)
                [_filename appendFormat:@".%@", _databasePath.pathExtension];

            /* Build Request Body */
            [_requestBody appendData:[[NSString stringWithFormat:@"--%@\r\n", _boundary] dataUsingEncoding:NSUTF8StringEncoding]];
            [_requestBody appendData:[[NSString stringWithFormat:@"Content-Disposition: multipart/form-data; name=\"databases\"; filename=\"%@\"\r\n", _filename] dataUsingEncoding:NSUTF8StringEncoding]];
            [_requestBody appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
            [_requestBody appendData:[NSData dataWithContentsOfFile:_databasePath]];
            [_requestBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
        }
    }

    [_requestBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", _boundary] dataUsingEncoding:NSUTF8StringEncoding]];

    [_serverRequest setHTTPBody:_requestBody];

这是服务器响应:

Array
(
    [databases] => Array
        (
            [name] => QTSKUFJM_test.dat
            [type] => application/octet-stream
            [tmp_name] => /private/var/tmp/php3w7qd5
            [error] => 0
            [size] => 64576
        )
)
4

2 回答 2

7

最后一个多部分之后的最终分隔符(“封装边界”)与正文部分两个连字符相同。

因此,最终的分隔符如下所示:

--<boundary>--

并不是

--<boundary>--<boundary>--

就像在你的实现中一样;)

最后一个分隔符之后的 CRLF 也不会受到伤害。


编辑:

更广泛的答案

a 的相应规范multipart/form-data是“HTML 中基于表单的文件上传” RFC 1867RFC 2388

媒体类型在RFC 2046multipart中更精确地定义(它已经过时 RFC 1521、RFC 1522、RFC 1590)。

以下是 RFC 2388 和 RFC 2046 的相关摘录:

根据 RFC 2388,amultipart/form-data包含一系列部分。每个部分必须包含一个特定的 content-disposition 标头(参见RFC 2183),其中处置类型为“form-data”,并且该处置具有附加参数“名称”。例如:

Content-Disposition: form-data; name="user"

注意:内容处置标头的最新 RFC 在RFC 2231中定义 (更新:2045、2047、2183;过时的 RFC 2184)。

media-typemultipart/form-data严格遵循所有多部分MIME 数据流的规则:

“multipart”类型的子类型必须使用相同的语法。子类型的语义可能不同,并且可能对语法施加额外的限制,但必须符合“多部分”类型所需的语法。

基本上,多部分的主体必须包含一个或多个主体部分,每个部分前面都有一个边界分隔线,最后一个后面是一个结束边界分隔线

每个正文部分由标题区空白行正文区组成。标题区域 允许为空。

注意:正文部分是一个实体,而不是 RFC 882消息。也就是说,正文部分实际上 不需要标题。没有 Content-Type 标头通常表示相应的正文具有“text/plain; charset=US-ASCII”的内容类型。

如果要传输文件的内容,则“Content-Type”应设置为文件的媒体类型(如果已知),否则为“application/octet-stream”。

提示:唯一定义了正文部分含义的标题字段是那些名称以“Content-”开头的字段。正文部分中的所有其他标头字段可能会被忽略。

注意:如果要作为单个表单条目的结果返回多个文件,则它们应表示为multipart/mixed嵌入在multipart/form-data.

原始本地文件名也可以作为“文件名”参数提供,或者作为“content-disposition:form-data”标头,或者在多个文件的情况下,在“content-disposition:file”标头中提供的子部分。

通用语法

multipart媒体类型的通用语法在 RFC 2046 § 5.1.1 中定义

这是一个简化的更全面的表格:

多部分必须具有 Content-Type。例如:

 Content-Type: multipart/subtype; boundary=gc0p4Jq0M2Yt08j34c0p

对于边界有一定的限制(请参阅 RFC 2046)。在实践中,用双引号将其括起来会使其更加健壮:

 Content-Type: multipart/subtype; boundary="---- boundary which requires quotes -----"

每个部分前面都有一个边界分隔符。边界分隔符必须出现在行首(即,在 CRLF 之后)。从概念上讲,CRLF 属于边界,而不是前面的元素:

boundary-delimiter :=     CRLF "--" boundary 

注意:边界后面可能有零个或多个空格,在此 BNF 中未显示。

部分正文由一个或多个封装组成,后跟一个结束分隔符:

multipart-body :=         +encapsulation
                          end-boundary-delimiter

其中封装是边界分隔符,后跟 CRLF,后跟主体部分:

encapsulation :=          boundary-delimiter CRLF 
                          body-part
                          
                          

正文部分(实体)由实体标头和正文组成:

body-part :=              MIME-part-headers [CRLF *OCTET]

注意:实体标头(MIME 部分标头)将由 CRLF 分隔 - 与任何其他标头一样。

最后一部分后跟一个结束分隔符

end-boundary-delimiter := CRLF "--" boundary "--"

警告:

尽管有大量论文,但上述边界的定义仍然不清楚——如果不是模棱两可的话!

在 RFC 2046,第 5.1.1 节通用语法中,它指出:

然后它(边界)由另一个 CRLF 和下一部分的头字段终止,或者由两个 CRLF 终止,在这种情况下,下一部分没有头字段。

边界分隔线之前的 CRLF 在概念上附加到边界...

身体部分可能完全是空的(根据 BNF)。边界后跟一个CRLF,然后是边界分隔符或结束边界分隔符。只是因为下一个分隔符以 CRLF 开头,所以前面的边界有两个后续的 CRLF!


例子

请注意,所有 CR 和 LF 都明确显示。

示例:具有一个部分的多部分主体,两个实体标题:

边界=“1234567890”

\r\n--1234567890\r\n
header1: value1\r\n
header2: value2\r\n
\r\n<data>
\r\n--0123456789--

示例:具有两个部分的 multipart-body:

\r\n--1234567890\r\n
header1: value1\r\n
header2: value2\r\n
\r\n<data1>
\r\n--0123456789\r\n
header1: value1\r\n
\r\n<data2>
\r\n--0123456789--

示例:没有标题的多部分正文:

\r\n--1234567890\r\n
\r\n<data1>
\r\n--0123456789--

示例:具有空正文部分的多部分正文:

\r\n--1234567890\r\n
\r\n--0123456789--
于 2013-09-20T19:13:01.457 回答
0

无法添加评论,所以我将在这里发布...

您只有一个文件的部分原因是,当您有多个文件时,您需要使用 a[]作为名称,如下所示:

[_requestBody appendData:[[NSString stringWithFormat:@"Content-Disposition: \ 
multipart/form-data;  name=\"databases[]\"; filename=\"%@\"\r\n", _filename] \
dataUsingEncoding:NSUTF8StringEncoding]];

你最终会得到一个数组而不是单个文件。

还要确保它_filename是唯一的,否则它会弄乱文件结构。

于 2014-11-25T19:51:49.580 回答