I am developing a tiny Web Server that should allow Download Managers to make multiple connections and download a huge file in multiple segments simultaneously.
I can detect that the download manager requests a partial download (the ARequestInfo.Ranges
property) and have made a function that serves these fragments using my own TFileFragmentStream
class that serves the given range of a file as a TStream
descendant.
However, my Download Manager doesn't seem to accept that my server supports this, and only downloads it using one connection. I can see that it requests partial downloads, so I'm probably not satisfying it that I support it.
I currently use code like this:
PROCEDURE SendPartialFile(Response : TIdHttpResponseInfo ; CONST N : TFileName ; RangeStart,RangeEnd : Int64);
BEGIN
Response.ContentType:='application/octet-stream';
Response.ContentDisposition:='attachment; filename="'+ExrractFileName(N)+'"';
Response.ContentLength:=SUCC(RangeEnd-RangeStart);
Response.ContentStream:=TFileFragmentStream.Create(N,RangeStart,ContentLength)
Response.FreeContentStream:=TRUE
END;
Are there other properties of my Response that I need to set?
Updated Final code:
ContentDisposition:='attachment; filename="'+N.NameOnly+'"';
VAR S:=TIdHTTPRangeStream.Create(TFileStream.Create(N,fmOpenRead OR fmShareDenyNone),RangeStart,RangeEnd);
ContentStream:=S;
ResponseNo:=S.ResponseCode;
ContentRangeStart:=S.RangeStart;
ContentRangeEnd:=S.RangeEnd;
ContentText:='';
ContentRangeInstanceLength:=S.SourceStream.Size;
OutputDebugString('Sending File Fragment: '+ContentType+' ['+IntToStr(ContentRangeStart)+'-'+IntToStr(ContentRangeEnd)+'] ('+IntToStr(ContentLength)+' bytes) "'+ContentDisposition+'" ResponseNo='+IntToStr(ResponseNo));
FreeContentStream:=TRUE;
Here's the logging:
Sending Full File: video/mp4 XX.mp4 (6573584385 bytes)
Sending File Fragment: video/mp4 [233600-6573584384] (6573350785 bytes) "attachment; filename="XX.mp4"" ResponseNo=206
Sending File Fragment: video/mp4 [3286914832-6573584384] (3286669553 bytes) "attachment; filename="XX.mp4"" ResponseNo=206
Sending File Fragment: video/mp4 [4930249645-6573584384] (1643334740 bytes) "attachment; filename="XX.mp4"" ResponseNo=206
Sending File Fragment: video/mp4 [1655659767-3286914895] (1631255129 bytes) "attachment; filename="XX.mp4"" ResponseNo=206
Sending File Fragment: video/mp4 [4108582275-4930249708] (821667434 bytes) "attachment; filename="XX.mp4"" ResponseNo=206
Sending File Fragment: video/mp4 [5751917052-6573584384] (821667333 bytes) "attachment; filename="XX.mp4"" ResponseNo=206
Sending File Fragment: video/mp4 [2471287336-3286914895] (815627560 bytes) "attachment; filename="XX.mp4"" ResponseNo=206
Sending File Fragment: video/mp4 [855370091-1655659830] (800289740 bytes) "attachment; filename="XX.mp4"" ResponseNo=206
The Full File is just a
ContentType:='application/octet-stream'
ContentDisposition:='attachment; filename="'+ExtractFileName(FileName)+'"'
ContentStream:=TFileStream.Create(FileName,fmOpenRead OR fmShareDenyNone)
FreeContentStream:=TRUE
ResponseNo:=200
You need to set the
various AResponseInfo.ContentRange...
properties to describe the range you are actually sending back to the client:
ContentRangeStart
ContentRangeEnd
ContentRangeInstanceLength
ContentRangeUnits
Also, make sure you are setting the AResponseInfo.ResponseNo
to indicate whether you are sending a partial range (206) or the complete file (200).
Read RFC 2616 sections 10.2.7 206 Partial Content and 14.16 Content-Range for more details.
BTW, TIdHTTPServer
has its own TIdHTTPRangeStream
helper class for sending a partial range of a source TStream
. You would construct it with the source stream (such as a TFileStream
) and requested range, and then use its ResponseCode
, RangeStart
, and RangeEnd
properties to update the AResponseInfo
, eg:
Procedure SendPartialFile(Response: TIdHTTPResponseInfo; const FileName: TFileName; RangeStart, RangeEnd: Int64);
begin
Response.ContentType := 'application/octet-stream';
Response.ContentDisposition := 'attachment; filename="'+ExtractFileName(FileName)+'"';
Response.ContentStream := TIdHTTPRangeStream.Create(TIdReadFileExclusiveStream.Create(FileName), RangeStart, RangeEnd);
Response.FreeContentStream := True;
Response.ResponseNo := TIdHTTPRangeStream(Response.ContentStream).ResponseCode;
if Response.ResponseNo = 206 then
begin
Response.ContentRangeStart := TIdHTTPRangeStream(Response.ContentStream).RangeStart;
Response.ContentRangeEnd := TIdHTTPRangeStream(Response.ContentStream).RangeEnd;
Response.ContentRangeInstanceLength := TIdHTTPRangeStream(Response.ContentStream).SourceStream.Size;
end;
end;