Indy 10 IdHTTP实现中的一些问题

Some Problems of Indy 10 IdHTTP Implementation

本文关键字:问题 IdHTTP 实现 Indy      更新时间:2023-10-16

关于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();
}

请提出建议并举例说明。任何帮助都将不胜感激!

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,以便现有文件将按原样打开。然后,您必须查找到文件的末尾,以便为Ranges标头提供正确的Position

  2. 您依赖于套接字的Connected()状态来做出决定。不要那样做。连接可能在上一次响应之后断开,也可能在发出新请求之前超时并关闭。无论哪种方式,文件仍然可以恢复。HTTP是无状态的。套接字是在请求之间保持打开,还是在请求之间关闭,都无关紧要。每个请求都是独立的。使用上一个响应中提供的信息来管理下一个请求。不是套接字状态。

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

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

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

至于进度条,您不能单独使用AWorkCount来计算新位置。只有当您在OnWorkBegin事件中将进度条的Max设置为AWorkCountMax,并且在下载开始之前并不总是知道该值时,这才有效。您需要考虑正在下载的文件的大小,是新下载还是恢复下载,恢复过程中请求的字节数等。因此,显示HTTP下载的进度条需要做更多的工作。

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

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

它由服务器在Content-Disposition报头的filename参数和/或Content-Type报头的name参数中提供。如果服务器没有提供这两个值,则可以使用请求的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;
}

@罗密欧:

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

我已经基于RRUZ的函数将其翻译成C++。到目前为止还不错,我还在我的简单IdHTTP下载程序中使用它。

但是,这个翻译结果当然还需要雷米·勒博、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;
}