0

关于 IdHTTP 的 Indy 10,很多事情都运行得很完美,但是这里也有一些事情做得不太好。这就是为什么,再一次,我需要你的帮助。

下载按钮已完美运行。我正在使用以下代码:

void __fastcall TForm1::DownloadClick(TObject *Sender)
{
  MyFile = SaveDialog->FileName;
  TFileStream* Fist = new TFileStream(MyFile, fmCreate | fmShareDenyNone);
  Download->Enabled = false;
  Urlz = Edit1->Text;
  Url->Caption = Urlz;
   try
    {
     IdHTTP->Get(Edit1->Text, Fist);
     IdHTTP->Connected();
     IdHTTP->Response->ResponseCode = 200;
     IdHTTP->ReadTimeout = 70000;
     IdHTTP->ConnectTimeout = 70000;
     IdHTTP->ReuseSocket;
     Fist->Position = 0;
    }
   __finally
  {
    delete Fist;
    Form1->Updated();
  }
}

但是,一个"Cancel Resume"按钮仍然无法恢复中断的下载。Get()意思是,尽管我使用了IdHTTP->Request->Ranges属性,但每次我打电话时它总是发回整个文件。

我使用以下代码:

void __fastcall TForm1::CancelResumeClick(TObject *Sender)
{
  MyFile = SaveDialog->FileName;;
  TFileStream* TFist = new TFileStream(MyFile, fmCreate | fmShareDenyNone);

 if (IdHTTP->Connected() == true)
   {
    IdHTTP->Disconnect();
    CancelResume->Caption = "RESUME";
    IdHTTP->Response->AcceptRanges = "Bytes";
   }
 else
   {
  try {
   CancelResume->Caption = "CANCEL";
   // IdHTTP->Request->Ranges == "0-100";
   // IdHTTP->Request->Range = Format("bytes=%d-",ARRAYOFCONST((TFist->Position)));
   IdHTTP->Request->Ranges->Add()->StartPos = TFist->Position;
   IdHTTP->Get(Edit1->Text, TFist);
   IdHTTP->Request->Referer = Edit1->Text;
   IdHTTP->ConnectTimeout = 70000;
   IdHTTP->ReadTimeout = 70000;
    }
  __finally {
    delete TFist;
   }
}

同时,通过使用此处的 FormatBytes 函数,已经能够仅显示下载文件的大小。但仍无法确定下载速度或传输速度。

我正在使用以下代码:

void __fastcall TForm1::IdHTTPWork(TObject *ASender, TWorkMode AWorkMode, __int64 AWorkCount)
  {
   __int64 Romeo = 0;
   Romeo = IdHTTP->Response->ContentStream->Position;
   // Romeo = AWorkCount;
   Download->Caption = FormatBytes(Romeo) + " (" + IntToStr(Romeo) + " Bytes)";
   ForSpeed->Caption = FormatBytes(Romeo);
   ProgressBar->Position = AWorkCount;
   ProgressBar->Update();
   Form1->Updated();
 }

请指教并举个例子。任何帮助都将不胜感激!

4

2 回答 2

0

在你的DownloadClick()方法中:

  1. 调用Connected()是无用的,因为您不会对结果做任何事情。也不能保证连接将保持连接,因为服务器可以发送Connection: close响应标头。我在您的代码中没有看到任何要求 HTTP 保持活动状态的内容。让TIdHTTP您管理连接。

  2. 你正在强制Response->ResponseCode到 200。不要那样做。尊重服务器实际发送的响应代码。没有引发异常的事实意味着无论是 200 还是 206,响应都是成功的。

  3. 您正在读取ReuseSocket属性值并忽略它。

  4. Fist->Position在关闭文件之前无需将属性重置为 0。

现在,话虽如此,您的CancelResumeClick()方法有很多问题。

  1. 打开文件时您正在使用该fmCreate标志。如果文件已经存在,您将从头开始覆盖它,因此TFist->Position将始终为 0。请fmOpenReadWrite改用现有文件,以便按原样打开。然后你必须寻找到文件的末尾以提供正确PositionRanges标题。

  2. 您依靠套接字的Connected()状态来做出决定。不要那样做。连接可能在上一次响应之后就消失了,或者在发出新请求之前已经超时并关闭。该文件仍然可以以任何一种方式恢复。HTTP 是无状态的。套接字在请求之间保持打开还是在其间关闭并不重要。每个请求都是独立的。使用上一个响应中提供的信息来管理下一个请求。不是套接字状态。

  3. 您正在修改Response->AcceptRanges属性的值,而不是使用先前响应提供的值。服务器会告诉您文件是否支持恢复,因此您必须记住该值,或者在尝试恢复下载之前对其进行查询。

  4. 当您实际调用Get()时,服务器可能会或可能不会尊重请求Range,这取决于请求的文件是否支持字节范围。如果服务器以 206 的响应代码响应,则接受请求的范围,并且服务器仅发送请求的字节,因此您需要将它们附加到现有文件中。但是,如果服务器响应代码为 200,则服务器正在从头开始发送整个文件,因此您需要用新字节替换现有文件。你没有考虑到这一点。

在您的IdHTTPWork()方法中,为了计算下载/传输速度,您必须跟踪每次事件触发之间实际传输的字节数。触发事件时,保存当前AWorkCount和滴答计数,然后在下次触发事件时,您可以比较新的AWorkCount和当前滴答,以了解已经过去了多少时间以及传输了多少字节。根据这些值,您可以计算速度,甚至估计剩余时间。

至于你的进度条,你不能AWorkCount单独使用来计算一个新的位置。这仅在您将进度条设置Max为事件时才有效,并且AWorkCountMaxOnWorkBegin下载开始之前并不总是知道该值。您需要考虑正在下载的文件的大小,无论是新鲜下载还是恢复,恢复期间请求的字节数等。因此,显示进度条涉及更多工作HTTP 下载。

现在,回答你的两个问题:

如何使用原始名称检索下载文件并将其保存到磁盘?

它由服务器在标头的filename参数中Content-Disposition和/或在标头的name参数中提供Content-Type。如果服务器没有提供任何值,您可以使用您请求的 URL 中的文件名。TIdHTTP有一个URL属性提供最后请求的 URL 的解析版本。

但是,由于您是在发送下载请求之前在本地创建文件,因此您必须使用临时文件名创建本地文件,然后在下载完成后重命名本地文件。否则,在使用TIdHTTP.Head()该文件名创建本地文件之前,用于确定真实文件名(您也可以使用它来确定是否支持恢复),然后用于TIdHTTP.Get()下载到该本地文件。TMemoryStream否则,使用代替将文件下载到内存TFileStream,然后在完成后使用所需的文件名保存。

当我单击http://get.videolan.org/vlc/2.2.1/win32/vlc-2.2.1-win32.exe时,服务器将处理对其实际 url 的请求。http://mirror.vodien.com/videolan/vlc/2.2.1/win32/vlc-2.2.1-win32.exe。问题是 IdHTTP 不会自动抓取它。

这是因为 VideoLan 不使用 HTTP 重定向将客户端发送到真实 URL(TIdHTTP支持 HTTP 重定向)。VideoLan 使用的是 HTML 重定向(TIdHTTP不支持 HTML 重定向)。当网络浏览器下载第一个 URL 时,会在真正的下载开始之前显示一个 5 秒的倒数计时器。因此,您必须手动检测服务器正在向您发送 HTML 页面而不是真实文件(查看TIdHTTP.Response.ContentType属性),解析 HTML 以确定真正的 URL,然后下载它。这也意味着您不能将第一个 URL 直接下载到目标本地文件中,否则会损坏它,尤其是在恢复期间。您必须首先将服务器的响应缓存到临时文件或内存中,以便在决定如何处理之前对其进行分析。这也意味着您必须记住恢复的真实 URL,您无法使用原始倒计时 URL 恢复下载。

请尝试以下类似的方法。它没有考虑到上面提到的所有内容(特别是速度/进度跟踪、HTML 重定向等),但应该让你更接近一点:

void __fastcall TForm1::DownloadClick(TObject *Sender)
{
    Urlz = Edit1->Text;
    Url->Caption = Urlz;

    IdHTTP->Head(Urlz);
    String FileName = IdHTTP->Response->RawHeaders->Params["Content-Disposition"]["filename"];
    if (FileName.IsEmpty())
    {
        FileName = IdHTTP->Response->RawHeaders->Params["Content-Type"]["name"];
        if (FileName.IsEmpty())
            FileName = IdHTTP->URL->Document;
    }

    SaveDialog->FileName = FileName;
    if (!SaveDialog->Execute()) return;

    MyFile = SaveDialog->FileName;
    TFileStream* Fist = new TFileStream(MyFile, fmCreate | fmShareDenyWrite);
    try
    {
        try
        {
            Download->Enabled = false;
            Resume->Enabled = false;

            IdHTTP->Request->Clear();
            //...

            IdHTTP->ReadTimeout = 70000;
            IdHTTP->ConnectTimeout = 70000;

            IdHTTP->Get(Urlz, Fist);
        }
        __finally
        {
            delete Fist;
            Download->Enabled = true;
            Updated();
        }
    }
    catch (const EIdHTTPProtocolException &)
    {
        DeleteFile(MyFile);
        throw;
    }
}

void __fastcall TForm1::ResumeClick(TObject *Sender)
{
    TFileStream* Fist = new TFileStream(MyFile, fmOpenReadWrite | fmShareDenyWrite);
    try
    {
        Download->Enabled = false;
        Resume->Enabled = false;

        IdHTTP->Request->Clear();
        //...

        Fist->Seek(0, soEnd);
        IdHTTP->Request->Ranges->Add()->StartPos = Fist->Position;
        IdHTTP->Request->Referer = Edit1->Text;

        IdHTTP->ConnectTimeout = 70000;
        IdHTTP->ReadTimeout = 70000;

        IdHTTP->Get(Urlz, Fist);
    }
    __finally
    {
        delete Fist;
        Download->Enabled = true;
        Updated();
    }
}

void __fastcall TForm1::IdHTTPHeadersAvailable(TObject*Sender, TIdHeaderList *AHeaders, bool &VContinue)
{
    Resume->Enabled = ( ((IdHTTP->Response->ResponseCode == 200) || (IdHTTP->Response->ResponseCode == 206)) && TextIsSame(AHeaders->Values["Accept-Ranges"], "bytes") );

    if ((IdHTTP->Response->ContentStream) && (IdHTTP->Request->Ranges->Count > 0) && (IdHTTP->Response->ResponseCode == 200))
        IdHTTP->Response->ContentStream->Size = 0;
}
于 2015-08-04T19:35:32.477 回答
0

@罗密欧:

此外,您可以尝试以下功能来确定真正的下载文件名。

我已经根据RRUZ'function将它翻译成 C++ 。到目前为止一切顺利,我也在我的简单 IdHTTP 下载程序中使用它。

但是,这个翻译结果当然仍然需要 Remy Lebeau、RRUZ 或这里的任何其他大师的价值改进输入。

String __fastcall GetRemoteFileName(const String URI)
{
    String result;
    try
    {
        TIdHTTP* HTTP = new TIdHTTP(NULL);
        try
        {
            HTTP->Head(URI);
            result = HTTP->Response->RawHeaders->Params["Content-Disposition"]["filename"];
            if (result.IsEmpty())
            {
                result = HTTP->Response->RawHeaders->Params["Content-Type"]["name"];
                if (result.IsEmpty())
                    result = HTTP->URL->Document;
            }
        }
        __finally
        {
            delete HTTP;
        }
    }
    catch(const Exception &ex)
    {
        ShowMessage(const_cast<Exception&>(ex).ToString());
    }

    return result;
}
于 2015-08-06T20:05:23.043 回答