Indy 10 分段上传到 OneDrive 错误
Indy 10 Multipart upload to OneDrive Error
我正在尝试 Multipart upload to OneDrive using POST 并获得 'HTTP/1.1 400 Bad Request'。 IdLogFile:
Stat Connected.
Sent 10.02.2017 12:50:08: POST /v1.0/drive/root::/children HTTP/1.0`<EOL>`Content-Type: multipart/related; boundary="Boundary"`<EOL>`Content-Length: 254`<EOL>`Authorization: Bearer EwA...%3d`<EOL>`Host: api.onedrive.com`<EOL>`Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8`<EOL>`Accept-Encoding: identity`<EOL>`User-Agent: Mozilla/3.0 (compatible; Indy Library)`<EOL><EOL>`
Sent 10.02.2017 12:50:08: --Boundary`<EOL>`Content-ID: <metadata>`<EOL>`Content-Type: application/json`<EOL>`{"name":"Dest.txt", "file":{}, "@content.sourceUrl":"cid:content"}`<EOL>`--Boundary`<EOL>`Content-ID: <content><EOL>Content-Type: application/octet-stream<SourceContent>--Boundary--
Recv 10.02.2017 12:50:08: H
Recv 10.02.2017 12:50:08: TTP/1.1 400 Bad Request`<EOL>`Via: 1.1 DM5SCH102210409 (wls-colorado)`<EOL>`Content-Length: 60`<EOL>`Content-Type: application/json`<EOL>`Server: Microsoft-IIS/8.5`<EOL>`P3P: CP="BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo"`<EOL>`X-WLSPROXY: DM5SCH102210409`<EOL>`X-MSNSERVER: DM5SCH102231823`<EOL>`Strict-Transport-Security: max-age=31536000; includeSubDomains`<EOL>`X-QosStats: {"ApiId":0,"ResultType":2,"SourcePropertyId":0,"TargetPropertyId":42}`<EOL>`X-ThrowSite: 1479.b891`<EOL>`X-AsmVersion: UNKNOWN; 16.0.0.0`<EOL>`X-MSEdge-Ref: Ref A: A9918FA26FAF469EB3797E9DAEA3172E Ref B: FRAEDGE0409 Ref C: Fri Feb 10 01:50:09 2017 PST`<EOL>`Date: Fri, 10 Feb 2017 09:50:09 GMT`<EOL>`Connection: close`<EOL><EOL>`{"error":{"code":"invalidRequest","message":"Bad Argument"}}
Stat Disconnected.
代码:
procedure TSaveFilter.UploadTest;
const
URL = 'https://api.onedrive.com/v1.0/drive/root::/children';
Boundary = 'Boundary';
var
IdHTTP: TIdHTTP;
MemoryStream: TMemoryStream;
FileStream: TFileStream;
procedure WriteLnString(str: AnsiString; CRLF: Boolean = True);
begin
if CRLF then str := str + #13#10;
MemoryStream.Write(str[1], Length(str));
end;
begin
IdHTTP := TIdHTTP.Create(nil);
try
IdHTTP.HandleRedirects := True;
IdHTTP.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP);
IdHTTP.Request.BasicAuthentication := False;
IdHTTP.Request.CustomHeaders.Values['Authorization'] := 'Bearer ' + FAccessToken;
IdHTTP.Request.ContentType := Format('multipart/related; boundary="%s"', [Boundary]);
MemoryStream := TMemoryStream.Create;
try
WriteLnString('--' + Boundary);
WriteLnString('Content-ID: <metadata>');
WriteLnString('Content-Type: application/json');
WriteLnString('{"name":"Dest.txt", "file":{}, "@content.sourceUrl":"cid:content"}');
WriteLnString('--' + Boundary);
WriteLnString('Content-ID: <content>');
WriteLnString('Content-Type: application/octet-stream', False);
FileStream := TFileStream.Create('Source.txt', fmOpenRead);
try
MemoryStream.CopyFrom(FileStream, FileStream.Size);
finally
FileStream.Free;
end;
WriteLnString('--' + Boundary + '--', False);
IdHTTP.Post(URL, MemoryStream);
finally
MemoryStream.Free;
end;
finally
IdHTTP.Free;
end;
end;
我做错了什么?
这是一个工作请求(列表 children):
Stat Connected.
Sent 10.02.2017 20:52:42: GET /v1.0/drive/root::/children?select=name,folder,file HTTP/1.1`<EOL>`Authorization: Bearer EwA...%3d`<EOL>`Host: api.onedrive.com`<EOL>`Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8`<EOL>`Accept-Encoding: identity`<EOL>`User-Agent: Mozilla/3.0 (compatible; Indy Library)`<EOL>``<EOL>`
Recv 10.02.2017 20:52:43: H
Recv 10.02.2017 20:52:43: TTP/1.1 200 OK`<EOL>`Via: 1.1 BN2BAP4ED8CB55D (wls-colorado)`<EOL>`Content-Length: 213`<EOL>`Content-Type: application/json; odata.metadata=minimal`<EOL>`Server: Microsoft-IIS/8.5`<EOL>`P3P: CP="BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo"`<EOL>`X-WLSPROXY: BN2BAP4ED8CB55D``X-MSNSERVER: DM5SCH102231619`<EOL>`Strict-Transport-Security: max-age=31536000; includeSubDomains`<EOL>`OData-Version: 4.0`<EOL>`X-AsmVersion: UNKNOWN; 16.0.0.0`<EOL>`X-MSEdge-Ref: Ref A: ECB06A4BE05B478AB36611C892C36CC7 Ref B: AM1EDGE0419 Ref C: Fri Feb 10 09:52:43 2017 PST``Date: Fri, 10 Feb 2017 17:52:43 GMT`<EOL>``<EOL>`{"@odata.context":"https://api.onedrive.com/v1.0/$metadata#drives('me')/items('root')/children(name,folder,file)","value":[{"name":"AB","folder":{"childCount":7}},{"name":"ArecaBackup","folder":{"childCount":6}}]}
Stat Disconnected.
您的 MIME 数据格式错误,这就是服务器拒绝它的原因。
这是您要发送的请求:
POST /v1.0/drive/root::/children HTTP/1.0
Content-Type: multipart/related; boundary="Boundary"
Content-Length: 254
Authorization: Bearer EwA...%3d
Host: api.onedrive.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: identity
User-Agent: Mozilla/3.0 (compatible; Indy Library)
--Boundary
Content-ID: <metadata>
Content-Type: application/json
{"name":"Dest.txt", "file":{}, "@content.sourceUrl":"cid:content"}
--Boundary
Content-ID: <content>
Content-Type: application/octet-stream<SourceContent>--Boundary--
如您所见,MIME 数据全乱了。具体来说,每个 MIME 字段都缺少一些必需的 CRLF
。就像 HTTP headers 和 body 一样,MIME headers 和 body 由 <CRLF><CRLF>
序列分隔,并且需要有一个 CRLF
在 TFileStream
数据和它后面的 MIME 边界之间。
请求需要看起来更像这样:
POST /v1.0/drive/root::/children HTTP/1.0
Content-Type: multipart/related; boundary="Boundary"
Content-Length: 260
Authorization: Bearer EwA...%3d
Host: api.onedrive.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: identity
User-Agent: Mozilla/3.0 (compatible; Indy Library)
--Boundary
Content-ID: <metadata>
Content-Type: application/json
{"name":"Dest.txt", "file":{}, "@content.sourceUrl":"cid:content"}
--Boundary
Content-ID: <content>
Content-Type: application/octet-stream
<SourceContent>
--Boundary--
尝试使用此代码填充 TMemoryStream
:
MemoryStream := TMemoryStream.Create;
try
WriteLnString('--' + Boundary);
WriteLnString('Content-ID: <metadata>');
WriteLnString('Content-Type: application/json');
WriteLnString(''); // <-- ADD THIS!!!
WriteLnString('{"name":"Dest.txt", "file":{}, "@content.sourceUrl":"cid:content"}');
WriteLnString('--' + Boundary);
WriteLnString('Content-ID: <content>');
WriteLnString('Content-Type: application/octet-stream'); // <-- REMOVE THE FALSE!!!
WriteLnString(''); // <-- ADD THIS!!!
FileStream := TFileStream.Create('Source.txt', fmOpenRead);
try
MemoryStream.CopyFrom(FileStream, 0);
finally
FileStream.Free;
end;
WriteLnString(''); // <!-- ADD THIS!!!
WriteLnString('--' + Boundary + '--', False);
...
finally
MemoryStream.Free;
end;
也就是说,Indy 有一个 TIdMultipartFormDataStream
class,通常在发送 multipart/form-data
帖子时与 TIdHTTP
一起使用。 OneDrive 不支持 multipart/form-data
,但奇怪的是 OneDrive 的文档明确规定了以下内容,仅适用于 multipart/form-data
而不适用于 multipart/related
:
The request will be rejected if more than two parts are included. Each part must specify a name
value in the Content-Disposition
header that indicates which part it is. Parts can be in either order, but should specify the metadata part first.
但是,同一文档中给出的示例使用的是 multipart/related
,就像您的代码一样。在 Microsoft/OneDrive 论坛和各种博客中有关于上传到 OneDrive 时是使用 multipart/form-data
还是 multipart/related
的讨论。一位 OneDrive 员工确实确认此问题需要他们进行一些工作。
万一 OneDrive 曾经支持 multipart/form-data
,下面是一个使用 TIdMultipartFormDataStream
的示例:
procedure TSaveFilter.UploadTest;
const
URL = 'https://api.onedrive.com/v1.0/drive/root::/children';
var
IdHTTP: TIdHTTP;
PostData: TIdMultipartFormDataStream;
begin
IdHTTP := TIdHTTP.Create(nil);
try
IdHTTP.HandleRedirects := True;
IdHTTP.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP);
IdHTTP.Request.BasicAuthentication := False;
IdHTTP.Request.CustomHeaders.Values['Authorization'] := 'Bearer ' + FAccessToken;
PostData := TIdMultipartFormDataStream.Create;
try
PostData.AddFormField('metadata', '{"name":"Dest.txt", "file":{}}', 'utf-8', 'application/json');
PostData.AddFile('content', 'Source.txt', 'application/octet-stream').FileName := '';
IdHTTP.Post(URL, PostData);
finally
PostData.Free;
end;
finally
IdHTTP.Free;
end;
end;
我正在尝试 Multipart upload to OneDrive using POST 并获得 'HTTP/1.1 400 Bad Request'。 IdLogFile:
Stat Connected. Sent 10.02.2017 12:50:08: POST /v1.0/drive/root::/children HTTP/1.0`<EOL>`Content-Type: multipart/related; boundary="Boundary"`<EOL>`Content-Length: 254`<EOL>`Authorization: Bearer EwA...%3d`<EOL>`Host: api.onedrive.com`<EOL>`Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8`<EOL>`Accept-Encoding: identity`<EOL>`User-Agent: Mozilla/3.0 (compatible; Indy Library)`<EOL><EOL>` Sent 10.02.2017 12:50:08: --Boundary`<EOL>`Content-ID: <metadata>`<EOL>`Content-Type: application/json`<EOL>`{"name":"Dest.txt", "file":{}, "@content.sourceUrl":"cid:content"}`<EOL>`--Boundary`<EOL>`Content-ID: <content><EOL>Content-Type: application/octet-stream<SourceContent>--Boundary-- Recv 10.02.2017 12:50:08: H Recv 10.02.2017 12:50:08: TTP/1.1 400 Bad Request`<EOL>`Via: 1.1 DM5SCH102210409 (wls-colorado)`<EOL>`Content-Length: 60`<EOL>`Content-Type: application/json`<EOL>`Server: Microsoft-IIS/8.5`<EOL>`P3P: CP="BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo"`<EOL>`X-WLSPROXY: DM5SCH102210409`<EOL>`X-MSNSERVER: DM5SCH102231823`<EOL>`Strict-Transport-Security: max-age=31536000; includeSubDomains`<EOL>`X-QosStats: {"ApiId":0,"ResultType":2,"SourcePropertyId":0,"TargetPropertyId":42}`<EOL>`X-ThrowSite: 1479.b891`<EOL>`X-AsmVersion: UNKNOWN; 16.0.0.0`<EOL>`X-MSEdge-Ref: Ref A: A9918FA26FAF469EB3797E9DAEA3172E Ref B: FRAEDGE0409 Ref C: Fri Feb 10 01:50:09 2017 PST`<EOL>`Date: Fri, 10 Feb 2017 09:50:09 GMT`<EOL>`Connection: close`<EOL><EOL>`{"error":{"code":"invalidRequest","message":"Bad Argument"}} Stat Disconnected.
代码:
procedure TSaveFilter.UploadTest;
const
URL = 'https://api.onedrive.com/v1.0/drive/root::/children';
Boundary = 'Boundary';
var
IdHTTP: TIdHTTP;
MemoryStream: TMemoryStream;
FileStream: TFileStream;
procedure WriteLnString(str: AnsiString; CRLF: Boolean = True);
begin
if CRLF then str := str + #13#10;
MemoryStream.Write(str[1], Length(str));
end;
begin
IdHTTP := TIdHTTP.Create(nil);
try
IdHTTP.HandleRedirects := True;
IdHTTP.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP);
IdHTTP.Request.BasicAuthentication := False;
IdHTTP.Request.CustomHeaders.Values['Authorization'] := 'Bearer ' + FAccessToken;
IdHTTP.Request.ContentType := Format('multipart/related; boundary="%s"', [Boundary]);
MemoryStream := TMemoryStream.Create;
try
WriteLnString('--' + Boundary);
WriteLnString('Content-ID: <metadata>');
WriteLnString('Content-Type: application/json');
WriteLnString('{"name":"Dest.txt", "file":{}, "@content.sourceUrl":"cid:content"}');
WriteLnString('--' + Boundary);
WriteLnString('Content-ID: <content>');
WriteLnString('Content-Type: application/octet-stream', False);
FileStream := TFileStream.Create('Source.txt', fmOpenRead);
try
MemoryStream.CopyFrom(FileStream, FileStream.Size);
finally
FileStream.Free;
end;
WriteLnString('--' + Boundary + '--', False);
IdHTTP.Post(URL, MemoryStream);
finally
MemoryStream.Free;
end;
finally
IdHTTP.Free;
end;
end;
我做错了什么?
这是一个工作请求(列表 children):
Stat Connected. Sent 10.02.2017 20:52:42: GET /v1.0/drive/root::/children?select=name,folder,file HTTP/1.1`<EOL>`Authorization: Bearer EwA...%3d`<EOL>`Host: api.onedrive.com`<EOL>`Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8`<EOL>`Accept-Encoding: identity`<EOL>`User-Agent: Mozilla/3.0 (compatible; Indy Library)`<EOL>``<EOL>` Recv 10.02.2017 20:52:43: H Recv 10.02.2017 20:52:43: TTP/1.1 200 OK`<EOL>`Via: 1.1 BN2BAP4ED8CB55D (wls-colorado)`<EOL>`Content-Length: 213`<EOL>`Content-Type: application/json; odata.metadata=minimal`<EOL>`Server: Microsoft-IIS/8.5`<EOL>`P3P: CP="BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo"`<EOL>`X-WLSPROXY: BN2BAP4ED8CB55D``X-MSNSERVER: DM5SCH102231619`<EOL>`Strict-Transport-Security: max-age=31536000; includeSubDomains`<EOL>`OData-Version: 4.0`<EOL>`X-AsmVersion: UNKNOWN; 16.0.0.0`<EOL>`X-MSEdge-Ref: Ref A: ECB06A4BE05B478AB36611C892C36CC7 Ref B: AM1EDGE0419 Ref C: Fri Feb 10 09:52:43 2017 PST``Date: Fri, 10 Feb 2017 17:52:43 GMT`<EOL>``<EOL>`{"@odata.context":"https://api.onedrive.com/v1.0/$metadata#drives('me')/items('root')/children(name,folder,file)","value":[{"name":"AB","folder":{"childCount":7}},{"name":"ArecaBackup","folder":{"childCount":6}}]} Stat Disconnected.
您的 MIME 数据格式错误,这就是服务器拒绝它的原因。
这是您要发送的请求:
POST /v1.0/drive/root::/children HTTP/1.0 Content-Type: multipart/related; boundary="Boundary" Content-Length: 254 Authorization: Bearer EwA...%3d Host: api.onedrive.com Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: identity User-Agent: Mozilla/3.0 (compatible; Indy Library) --Boundary Content-ID: <metadata> Content-Type: application/json {"name":"Dest.txt", "file":{}, "@content.sourceUrl":"cid:content"} --Boundary Content-ID: <content> Content-Type: application/octet-stream<SourceContent>--Boundary--
如您所见,MIME 数据全乱了。具体来说,每个 MIME 字段都缺少一些必需的 CRLF
。就像 HTTP headers 和 body 一样,MIME headers 和 body 由 <CRLF><CRLF>
序列分隔,并且需要有一个 CRLF
在 TFileStream
数据和它后面的 MIME 边界之间。
请求需要看起来更像这样:
POST /v1.0/drive/root::/children HTTP/1.0 Content-Type: multipart/related; boundary="Boundary" Content-Length: 260 Authorization: Bearer EwA...%3d Host: api.onedrive.com Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: identity User-Agent: Mozilla/3.0 (compatible; Indy Library) --Boundary Content-ID: <metadata> Content-Type: application/json {"name":"Dest.txt", "file":{}, "@content.sourceUrl":"cid:content"} --Boundary Content-ID: <content> Content-Type: application/octet-stream <SourceContent> --Boundary--
尝试使用此代码填充 TMemoryStream
:
MemoryStream := TMemoryStream.Create;
try
WriteLnString('--' + Boundary);
WriteLnString('Content-ID: <metadata>');
WriteLnString('Content-Type: application/json');
WriteLnString(''); // <-- ADD THIS!!!
WriteLnString('{"name":"Dest.txt", "file":{}, "@content.sourceUrl":"cid:content"}');
WriteLnString('--' + Boundary);
WriteLnString('Content-ID: <content>');
WriteLnString('Content-Type: application/octet-stream'); // <-- REMOVE THE FALSE!!!
WriteLnString(''); // <-- ADD THIS!!!
FileStream := TFileStream.Create('Source.txt', fmOpenRead);
try
MemoryStream.CopyFrom(FileStream, 0);
finally
FileStream.Free;
end;
WriteLnString(''); // <!-- ADD THIS!!!
WriteLnString('--' + Boundary + '--', False);
...
finally
MemoryStream.Free;
end;
也就是说,Indy 有一个 TIdMultipartFormDataStream
class,通常在发送 multipart/form-data
帖子时与 TIdHTTP
一起使用。 OneDrive 不支持 multipart/form-data
,但奇怪的是 OneDrive 的文档明确规定了以下内容,仅适用于 multipart/form-data
而不适用于 multipart/related
:
The request will be rejected if more than two parts are included. Each part must specify a
name
value in theContent-Disposition
header that indicates which part it is. Parts can be in either order, but should specify the metadata part first.
但是,同一文档中给出的示例使用的是 multipart/related
,就像您的代码一样。在 Microsoft/OneDrive 论坛和各种博客中有关于上传到 OneDrive 时是使用 multipart/form-data
还是 multipart/related
的讨论。一位 OneDrive 员工确实确认此问题需要他们进行一些工作。
万一 OneDrive 曾经支持 multipart/form-data
,下面是一个使用 TIdMultipartFormDataStream
的示例:
procedure TSaveFilter.UploadTest;
const
URL = 'https://api.onedrive.com/v1.0/drive/root::/children';
var
IdHTTP: TIdHTTP;
PostData: TIdMultipartFormDataStream;
begin
IdHTTP := TIdHTTP.Create(nil);
try
IdHTTP.HandleRedirects := True;
IdHTTP.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP);
IdHTTP.Request.BasicAuthentication := False;
IdHTTP.Request.CustomHeaders.Values['Authorization'] := 'Bearer ' + FAccessToken;
PostData := TIdMultipartFormDataStream.Create;
try
PostData.AddFormField('metadata', '{"name":"Dest.txt", "file":{}}', 'utf-8', 'application/json');
PostData.AddFile('content', 'Source.txt', 'application/octet-stream').FileName := '';
IdHTTP.Post(URL, PostData);
finally
PostData.Free;
end;
finally
IdHTTP.Free;
end;
end;