Что касается Indy 10 IdHTTP, многие вещи работают отлично, но есть некоторые вещи, которые здесь не так хорошо работают. Вот почему, опять же, мне нужна ваша помощь.
Кнопка загрузки работает отлично. Я использую следующий код:
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()
метод:
призвание Connected()
бесполезен, так как вы ничего не делаете с результатом. Также нет никакой гарантии, что соединение останется подключенным, так как сервер может отправить Connection: close
заголовок ответа. Я не вижу в вашем коде ничего, что запрашивало бы HTTP keep-alives. Позволять TIdHTTP
управлять соединением для вас.
Вы заставляете Response->ResponseCode
до 200. Не делай этого. Уважайте код ответа, который фактически отправил сервер. Тот факт, что исключение не возникло, означает, что ответ был успешным, будь то 200 или 206.
Вы читаете ReuseSocket
Значение свойства и его игнорирование.
Нет необходимости сбрасывать Fist->Position
свойство до 0 до закрытия файла.
Теперь, с этим сказал, ваш CancelResumeClick()
Метод имеет много проблем.
Вы используете fmCreate
флаг при открытии файла. Если файл уже существует, вы перезапишите его с нуля, таким образом TFist->Position
ВСЕГДА будет 0. Используйте fmOpenReadWrite
вместо этого существующий файл будет открыт как есть. И тогда вы должны искать в конце файла, чтобы обеспечить правильный Position
к Ranges
заголовок.
Вы полагаетесь на сокеты Connected()
государство для принятия решений. Не делай этого. Соединение может быть разорвано после предыдущего ответа или может быть превышено время ожидания и закрыто до того, как будет сделан новый запрос. Файл все еще может быть возобновлен в любом случае. HTTP не имеет состояния. Неважно, если сокет остается открытым между запросами или закрыт между ними. Каждый запрос является автономным. Используйте информацию, представленную в предыдущем ответе, для управления следующим запросом. Не состояние сокета.
Вы изменяете значение Response->AcceptRanges
свойство, вместо использования значения, предоставленного предыдущим ответом. Сервер сообщает вам, поддерживает ли файл возобновление, поэтому вы должны запомнить это значение или запросить его, прежде чем пытаться возобновить загрузку.
Когда вы на самом деле звоните Get()
сервер может или не может уважать запрошенный Range
в зависимости от того, поддерживает ли запрошенный файл диапазоны байтов или нет. Если сервер отвечает кодом ответа 206, запрошенный диапазон принимается, и сервер отправляет ТОЛЬКО запрошенные байты, поэтому вам необходимо ПРИЛОЖИТЬ их к существующему файлу. Однако, если ответ сервера с кодом ответа 200, сервер отправляет весь файл с нуля, поэтому вам необходимо ЗАМЕНИТЬ существующий файл новыми байтами. Вы не принимаете это во внимание.
В вашем IdHTTPWork()
Чтобы рассчитать скорость загрузки / передачи, необходимо отслеживать, сколько байтов фактически передается между каждым срабатыванием события. Когда событие запущено, сохраните текущий AWorkCount
и отметьте галочку, а затем при следующем запуске события вы можете сравнить новые AWorkCount
и текущие отметки, чтобы узнать, сколько времени прошло и сколько байтов было передано. Из этих значений вы можете рассчитать скорость и даже приблизительное оставшееся время.
Что касается вашего индикатора выполнения, вы не можете использовать AWorkCount
в одиночку, чтобы рассчитать новую позицию. Это работает только если вы установите индикатор выполнения Max
в AWorkCountMax
в OnWorkBegin
событие, и это значение не всегда известно до начала загрузки. Вам необходимо принять во внимание размер загружаемого файла, загружается ли он свежим или возобновляется, сколько байтов запрашивается во время возобновления и т. Д. Таким образом, для отображения индикатора выполнения для 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;
}
@Ромео:
Кроме того, вы можете попробовать следующую функцию, чтобы определить реальное имя файла загрузки.
Я перевел это на C ++ на основе RRUZ’function. Пока все хорошо, я использую его и в своей простой программе загрузки 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;
}