问题
I am trying to do Multipart upload to OneDrive using POST and get '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.
The code:
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;
What am I doing wrong?
This is a working request (List 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.
回答1:
Your MIME data is malformed, that is why the server is rejecting it.
This is the request you are sending:
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--
As you can see, the MIME data is all messed up. Specifically, each MIME field is missing some required CRLF
s. Just like with the HTTP headers and body, MIME headers and body are separated by a <CRLF><CRLF>
sequence, and there needs to be a CRLF
between the TFileStream
data and the MIME boundary that follows it.
The request needs to look more like this instead:
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--
Try this code to populate the 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;
That being said, Indy has a TIdMultipartFormDataStream
class that is typically used with TIdHTTP
when sending multipart/form-data
posts. OneDrive does not support multipart/form-data
, but what is odd is that OneDrive's documentation clearly specifies the following, which only applies to multipart/form-data
and not to 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.
However, the example given in the same documentation is using multipart/related
, just like your code is. There are discussions in Microsoft/OneDrive forums and various blogs regarding whether to use multipart/form-data
or multipart/related
when uploading to OneDrive. One OneDrive employee did confirm this issue needs some work on their end.
Just in case OneDrive ever supports multipart/form-data
, here is a example using 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;
来源:https://stackoverflow.com/questions/42157101/indy-10-multipart-upload-to-onedrive-error