Search code examples
delphitcpc++builderindyhttpserver

Indy Http Server with resume support


I have written an indy http server, I want to serve file download with resume support. When I use IDM to download file, the download is single threaded:

enter image description here

Note that the Resume capability is Yes, but When I pause and resume the download, It starts form beginning.

My Indy Http Server is as:

void __fastcall TfrmMain::httpServerCommandGet(TIdContext *AContext,
    TIdHTTPRequestInfo *ARequestInfo, TIdHTTPResponseInfo *AResponseInfo)
{
    Beep(1000, 100);
    string fileName = ExtractFileDir(Application->ExeName) + ARequestInfo->Document;
    fileName = fileName.replace("/", "\\");

    TFileStream *stream = new TFileStream(fileName, fmOpenRead | fmShareDenyNone);

    int start = 0, end = 0;
    string range = ARequestInfo->Range;
    if(!range.empty())
    {
        int dash_pos = range.find("-");
        start = range.substr(0, dash_pos).intValue();
        end = range.substr(dash_pos + 1).intValue();
        if(end == 0) // Endless Range: start-
            end = stream->Size;
    }
    else
    {
        start = 0;
        end = stream->Size;
    }
    OutputDebugStringW(string("ctx=[] start=[] end=[]") <<
        IntToHex((int)AContext, 8) << start << end);

    stream->Seek((__int64)start, soBeginning);
    AResponseInfo->ContentStream = stream;
    AResponseInfo->FreeContentStream = true;
    AResponseInfo->ContentLength = stream->Size;
    if(!range.empty())
    {
        AResponseInfo->ContentRangeStart = start;
        AResponseInfo->ContentRangeEnd = end;
        AResponseInfo->ResponseNo = 206;
        AResponseInfo->ContentRangeInstanceLength = end + 1;
        AResponseInfo->ContentLength = end - start + 1;
        AResponseInfo->AcceptRanges = "bytes";
    }
    AResponseInfo->WriteHeader();
    AResponseInfo->WriteContent();
}

any help would be appreciated.


Solution

  • The IdCustomHTTPServer unit has a TIdHTTPRangeStream helper class for this very purpose.

    If the client requests a ranged download, create an instance of TIdHTTPRangeStream and pass it your intended TStream and the client's requested range, then assign it as the ContentStream to be sent. TIdHTTPRangeStream also has a ResponseCode property that you need to assign to the response's ResponseNo property.

    For example:

    void __fastcall TfrmMain::httpServerCommandGet(TIdContext *AContext, TIdHTTPRequestInfo *ARequestInfo, TIdHTTPResponseInfo *AResponseInfo)
    {
        // create file stream ...
    
        // THTTPServer parses the ranges for you
        if (ARequestInfo->Ranges->Count > 0)
        {
            if (ARequestInfo->Ranges->Count > 1)
            {
                AResponseInfo->ResponseNo = 416;
                return;
            }
    
            TIdEntityRange *range = ARequestInfo->Ranges->Range[0];
    
            TIdHTTPRangeStream *rstream = new TIdHTTPRangeStream(stream, range->StartPos, range->EndPos, true);
    
            AResponseInfo->ResponseNo = rstream->ResponseCode;
            AResponseInfo->ContentRangeStart = rstream->RangeStart;
            AResponseInfo->ContentRangeEnd = rstream->RangeEnd;
            AResponseInfo->ContentStream = rstream;
            AResponseInfo->AcceptRanges = "bytes";
        }
        else
        {
            AResponseInfo->ContentStream = stream;
        }
    
        // no need to seek the target stream manually
        // no need to set the ContentLength manually
        // no need to call WriteHeader() and WriteContent() manually
        //
        // TIdHTTPServer handles all that for you internally!
    }