Indy 10 IdHTTP实现的一些问题
Some Problems of Indy 10 IdHTTP Implementation
关于 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"
按钮仍然无法恢复中断的下载。意思是,尽管我使用了 IdHTTP->Request->Ranges
属性,但每次我调用 Get()
时它总是发回整个文件。
我使用以下代码:
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函数,found here已经能够只显示下载文件的大小。但仍然无法确定下载速度或传输速度。
我正在使用以下代码:
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()
方法中:
调用 Connected()
是没有用的,因为您没有对结果做任何事情。也不能保证连接将保持连接状态,因为服务器可以发送 Connection: close
响应 header。我在您的代码中没有看到任何请求 HTTP keep-alives 的内容。让 TIdHTTP
为您管理连接。
您正在将 Response->ResponseCode
强制为 200。不要那样做。尊重服务器实际发送的响应代码。没有抛出异常就说明响应成功了,不管是200还是206。
您正在读取 ReuseSocket
属性 值并忽略它。
关闭文件前无需将Fist->Position
属性重置为0
话虽如此,您的 CancelResumeClick()
方法有很多问题。
您在打开文件时使用了 fmCreate
标志。如果该文件已经存在,您将从头开始覆盖它,因此 TFist->Position
将始终为 0。使用 fmOpenReadWrite
代替,这样现有文件将打开 as-is。然后你必须寻找到文件的末尾以提供正确的 Position
到 Ranges
header.
您依靠套接字的 Connected()
状态来做出决定。不要那样做。连接可能在上一个响应之后就消失了,或者可能在发出新请求之前已经超时并关闭了。该文件仍然可以通过任何一种方式恢复。 HTTP 是无状态的。套接字在请求之间保持打开状态还是关闭状态都没有关系。每个请求都是 self-contained。使用先前响应中提供的信息来管理下一个请求。不是套接字状态。
您正在修改 Response->AcceptRanges
属性 的值,而不是使用之前响应提供的值。服务器告诉你文件是否支持恢复,所以你必须记住那个值,或者在尝试恢复下载之前查询它。
当您实际调用 Get()
时,服务器可能会或可能不会尊重所请求的 Range
,这取决于所请求的文件是否支持字节范围。如果服务器以 206 的响应代码进行响应,则接受请求的范围,并且服务器仅发送请求的字节,因此您需要将它们附加到现有文件中。但是,如果服务器响应的响应代码为 200,则服务器正在从头开始发送整个文件,因此您需要用新字节替换现有文件。你没有考虑到这一点。
在您的 IdHTTPWork()
方法中,为了计算 download/transfer 速度,您必须跟踪每次事件触发之间实际传输的字节数。当事件被触发时,保存当前的 AWorkCount
和滴答计数,然后在下次触发事件时,您可以比较新的 AWorkCount
和当前的滴答来知道已经过去了多少时间以及如何许多字节被传输。根据这些值,您可以计算出速度,甚至可以估计剩余时间。
至于你的进度条,你不能单独使用AWorkCount
来计算一个新的位置。仅当您在 OnWorkBegin
事件中将进度条的 Max
设置为 AWorkCountMax
时才有效,并且在下载开始之前并不总是知道该值。您需要考虑正在下载的文件的大小,是重新下载还是正在恢复,恢复期间请求了多少字节等。因此,显示进度条涉及更多工作HTTP 下载。
现在,回答你的两个问题:
How to retrieve and save the download file to a disk by using its original name?
由服务器在Content-Disposition
的filename
参数中提供 header,and/or在name
的参数中[=38] =] header。如果服务器未提供任何值,您可以使用您请求的 URL 中的文件名。 TIdHTTP
有一个 URL
属性 提供最后请求的解析版本 URL.
但是,由于您是在发送下载请求之前在本地创建文件,因此您必须使用临时文件名创建一个本地文件,然后在下载完成后重命名该本地文件。否则,在使用该文件名创建本地文件之前,使用 TIdHTTP.Head()
确定真实文件名(您也可以使用它确定是否支持恢复),然后使用 TIdHTTP.Get()
下载到该本地文件。否则,使用 TMemoryStream
而不是 TFileStream
将文件下载到内存,然后在完成后使用所需的文件名保存。
when I click http://get.videolan.org/vlc/2.2.1/win32/vlc-2.2.1-win32.exe then the server will process requests to its actual url. http://mirror.vodien.com/videolan/vlc/2.2.1/win32/vlc-2.2.1-win32.exe. The problem is that IdHTTP will not automatically grab through it.
这是因为 VideoLan 没有使用 HTTP 重定向将客户端发送到真正的 URL(TIdHTTP
支持 HTTP 重定向)。 VideoLan 正在使用 HTML 重定向(TIdHTTP
不支持 HTML 重定向)。当网络浏览器下载第一个 URL 时,会在真正的下载开始之前显示一个 5 秒的倒计时计时器。因此,您必须手动检测服务器是否向您发送HTML页面而不是真正的文件(看TIdHTTP.Response.ContentType
属性),解析HTML确定真正的URL,然后下载.这也意味着您不能将第一个 URL 直接下载到您的目标本地文件中,否则您会损坏它,尤其是在恢复期间。您必须首先将服务器的响应缓存到临时文件或内存中,以便您可以在决定如何对其进行操作之前对其进行分析。这也意味着你必须记住真正的 URL 才能恢复,你无法使用原始倒计时 URL.
恢复下载
试试下面的方法。它没有考虑上面提到的所有内容(特别是 speed/progress 跟踪、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'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;
}
关于 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"
按钮仍然无法恢复中断的下载。意思是,尽管我使用了 IdHTTP->Request->Ranges
属性,但每次我调用 Get()
时它总是发回整个文件。
我使用以下代码:
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函数,found here已经能够只显示下载文件的大小。但仍然无法确定下载速度或传输速度。
我正在使用以下代码:
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()
方法中:
调用
Connected()
是没有用的,因为您没有对结果做任何事情。也不能保证连接将保持连接状态,因为服务器可以发送Connection: close
响应 header。我在您的代码中没有看到任何请求 HTTP keep-alives 的内容。让TIdHTTP
为您管理连接。您正在将
Response->ResponseCode
强制为 200。不要那样做。尊重服务器实际发送的响应代码。没有抛出异常就说明响应成功了,不管是200还是206。您正在读取
ReuseSocket
属性 值并忽略它。关闭文件前无需将
Fist->Position
属性重置为0
话虽如此,您的 CancelResumeClick()
方法有很多问题。
您在打开文件时使用了
fmCreate
标志。如果该文件已经存在,您将从头开始覆盖它,因此TFist->Position
将始终为 0。使用fmOpenReadWrite
代替,这样现有文件将打开 as-is。然后你必须寻找到文件的末尾以提供正确的Position
到Ranges
header.您依靠套接字的
Connected()
状态来做出决定。不要那样做。连接可能在上一个响应之后就消失了,或者可能在发出新请求之前已经超时并关闭了。该文件仍然可以通过任何一种方式恢复。 HTTP 是无状态的。套接字在请求之间保持打开状态还是关闭状态都没有关系。每个请求都是 self-contained。使用先前响应中提供的信息来管理下一个请求。不是套接字状态。您正在修改
Response->AcceptRanges
属性 的值,而不是使用之前响应提供的值。服务器告诉你文件是否支持恢复,所以你必须记住那个值,或者在尝试恢复下载之前查询它。当您实际调用
Get()
时,服务器可能会或可能不会尊重所请求的Range
,这取决于所请求的文件是否支持字节范围。如果服务器以 206 的响应代码进行响应,则接受请求的范围,并且服务器仅发送请求的字节,因此您需要将它们附加到现有文件中。但是,如果服务器响应的响应代码为 200,则服务器正在从头开始发送整个文件,因此您需要用新字节替换现有文件。你没有考虑到这一点。
在您的 IdHTTPWork()
方法中,为了计算 download/transfer 速度,您必须跟踪每次事件触发之间实际传输的字节数。当事件被触发时,保存当前的 AWorkCount
和滴答计数,然后在下次触发事件时,您可以比较新的 AWorkCount
和当前的滴答来知道已经过去了多少时间以及如何许多字节被传输。根据这些值,您可以计算出速度,甚至可以估计剩余时间。
至于你的进度条,你不能单独使用AWorkCount
来计算一个新的位置。仅当您在 OnWorkBegin
事件中将进度条的 Max
设置为 AWorkCountMax
时才有效,并且在下载开始之前并不总是知道该值。您需要考虑正在下载的文件的大小,是重新下载还是正在恢复,恢复期间请求了多少字节等。因此,显示进度条涉及更多工作HTTP 下载。
现在,回答你的两个问题:
How to retrieve and save the download file to a disk by using its original name?
由服务器在Content-Disposition
的filename
参数中提供 header,and/or在name
的参数中[=38] =] header。如果服务器未提供任何值,您可以使用您请求的 URL 中的文件名。 TIdHTTP
有一个 URL
属性 提供最后请求的解析版本 URL.
但是,由于您是在发送下载请求之前在本地创建文件,因此您必须使用临时文件名创建一个本地文件,然后在下载完成后重命名该本地文件。否则,在使用该文件名创建本地文件之前,使用 TIdHTTP.Head()
确定真实文件名(您也可以使用它确定是否支持恢复),然后使用 TIdHTTP.Get()
下载到该本地文件。否则,使用 TMemoryStream
而不是 TFileStream
将文件下载到内存,然后在完成后使用所需的文件名保存。
when I click http://get.videolan.org/vlc/2.2.1/win32/vlc-2.2.1-win32.exe then the server will process requests to its actual url. http://mirror.vodien.com/videolan/vlc/2.2.1/win32/vlc-2.2.1-win32.exe. The problem is that IdHTTP will not automatically grab through it.
这是因为 VideoLan 没有使用 HTTP 重定向将客户端发送到真正的 URL(TIdHTTP
支持 HTTP 重定向)。 VideoLan 正在使用 HTML 重定向(TIdHTTP
不支持 HTML 重定向)。当网络浏览器下载第一个 URL 时,会在真正的下载开始之前显示一个 5 秒的倒计时计时器。因此,您必须手动检测服务器是否向您发送HTML页面而不是真正的文件(看TIdHTTP.Response.ContentType
属性),解析HTML确定真正的URL,然后下载.这也意味着您不能将第一个 URL 直接下载到您的目标本地文件中,否则您会损坏它,尤其是在恢复期间。您必须首先将服务器的响应缓存到临时文件或内存中,以便您可以在决定如何对其进行操作之前对其进行分析。这也意味着你必须记住真正的 URL 才能恢复,你无法使用原始倒计时 URL.
试试下面的方法。它没有考虑上面提到的所有内容(特别是 speed/progress 跟踪、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'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;
}