mark_c wrote:with this example, I noticed that using the BCB OnRead event is not possible to determine the end of the file, is it correct?
Yes, it is possible, just not the way you have coded it.
You are sending an HTTP 1.1 request, and by default HTTP 1.1 uses keep-alives, meaning the TCP connection is left open after the response has been sent/consumed, unless the HTTP client explicitly requests otherwise, or the HTTP server has keep-alives disabled. Closing the connection at the end of the response is only one possible scenario when dealing with HTTP, and frankly it is not a very common one nowadays, or the preferred way. However, you CAN force it, by explicitly asking in your GET request that no keep-alive be used:
Code: Select all
ClientSocket1->Socket->SendText("GET / HTTP/1.1\r\nHost: "+MyHost+"\r\nConnection: close\r\n\r\n");
But, you really shouldn't rely on this. What you should be doing instead is actually PARSING THE RESPONSE according to the rules of the HTTP protocol. In particular,
RFC 2616 section 4.4 (and later,
RFC 7230 section 3.3.3) describes exactly how to determine when a response has ended. You need to read the HTTP response headers first until you encounter the terminating "\r\n\r\n" sequence, then parse the headers to determine how to read the rest of the response so yo know when to stop reading.
That CAN be done with the OnRead logic you have, albeit not in one go. You need a state machine, adding raw bytes received to your MyBuffer variable as they come in, and parsing it along the way until you detect the end of the response according to the HTTP protocol, and then you can do whatever you want with the received body content.
That being said, there are some logic bugs in the code that you currently have!
- not checking to make sure that SendText() actually sends the complete request (FYI, it is not a guarantee!)
- not checking for errors on ReceiveLength() before allocating memory
- ignoring the return value of ReceiveBuf(), which tells you how many bytes were actually read. You are assuming the buffer is always null-terminated (FYI, it never is!)
Try something more like this:
Code: Select all
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
AnsiString MyBuffer;
AnsiString MyHost = "www.google.com";
enum eParsingState {ReadingStatus, ReadingHeaders, ReadingBody, EndOfResponse};
eParsingState ParsingState;
enum eBodyState {ReadingBodyByChunks, ReadingBodyUntilLength, ReadingBodyUntilClose};
eBodyState BodyState;
enum eChunkState {ReadingChunkHeader, ReadingChunkData, ReadingChunkDataCRLF, ReadingChunksTrailer};
eChunkState ChunkState;
__int64 BytesRemaining = 0;
int StatusCode;
TStringList *Headers;
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
Headers = new TStringList;
Headers->NameValueSeparator = ':';
Headers->CaseSensitive = false;
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
delete Headers;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
ClientSocket1->Close();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
ClientSocket1->Close();
ClientSocket1->Host = MyHost;
ClientSocket1->Port = 80;
ClientSocket1->Open();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ClientSocket1Error(TObject *Sender,
TCustomWinSocket* Socket, TErrorEvent ErrorEvent, int &ErrorCode)
{
ErrorCode = 0;
Socket->Close();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ClientSocket1Read(TObject *Sender,
TCustomWinSocket *Socket)
{
int buffer_len = Socket->ReceiveLength();
if (buffer_len <= 0) return;
AnsiString buffer;
buffer.SetLength(buffer_len);
buffer_len = Socket->ReceiveBuf(buffer.c_str(), buffer_len);
if (buffer_len <= 0) return;
buffer.SetLength(buffer_len);
//Memo1->Lines->Add(buffer);
Memo1->SelStart = Memo1->GetTextLen();
Memo1->SelLength = 0;
Memo1->SelText = buffer;
MyBuffer += buffer;
UpdateState();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::UpdateState()
{
do
{
switch (ParsingState)
{
case ReadingStatus:
{
// clear body storage as needed...
int Term = MyBuffer.Pos("\r\n");
if (Term == 0) return;
String StatusLine = MyBuffer.SubString(1, Term-1);
MyBuffer.Delete(1, Term+1);
Term = StatusLine.Pos(" ");
StatusLine.Delete(1, Term); // HTTP-Version
Term = StatusLine.Pos(" ");
StatusCode = StatusLine.SubString(1, Term-1).Trim().ToInt();
Headers->Clear();
ParsingState = ReadingHeaders;
break;
}
case ReadingHeaders:
{
int Term = MyBuffer.Pos("\r\n\r\n");
if (Term == 0) return;
Headers->Text = MyBuffer.SubString(1, Term-1);
MyBuffer.Delete(1, Term+3);
if (/*(Request was "HEAD") ||*/ ((StatusCode / 100) == 1) || (StatusCode == 204) || (StatusCode == 304))
{
ParsingState = EndOfResponse;
break;
}
String TransferEncoding = Headers->Values["Transfer-Encoding"].Trim();
if ((TransferEncoding != "") && !SameText(TransferEncoding, "identity"))
{
BodyState = ReadingBodyByChunks;
ChunkState = ReadingChunkHeader;
}
else
{
BytesRemaining = StrToInt64Def(Headers->Values["Content-Length"].Trim(), -1);
if (BytesRemaining >= 0)
BodyState = ReadingBodyUntilLength;
else
BodyState = ReadingBodyUntilClose;
}
ParsingState = ReadingBody;
break;
}
case ReadingBody:
{
switch (BodyState)
{
case ReadingBodyByChunks:
{
switch (ChunkState)
{
case ReadingChunkHeader:
{
int Term = MyBuffer.Pos("\r\n");
if (Term == 0) return;
AnsiString Line = MyBuffer.SubString(1, Term-1);
MyBuffer.Delete(1, Term+1);
Term = Line.Pos(";");
if (Term != 0) Line.SetLength(Term-1);
BytesRemaining = StrToInt64("0x" + Line);
if (BytesRemaining > 0)
ChunkState = ReadingChunkData;
else
ChunkState = ReadingChunksTrailer;
break;
}
case ReadingChunkData:
{
if (MyBuffer.Length() < BytesRemaining)
{
// save all of MyBuffer bytes to body storage as needed...
BytesRemaining -= MyBuffer.Length();
MyBuffer = "";
return;
}
// save MyBuffer up to BytesRemaining bytes to body storage as needed...
MyBuffer.Delete(1, BytesRemaining);
BytesRemaining = 0;
ChunkState = ReadingChunkDataCRLF;
break;
}
case ReadingChunkDataCRLF:
{
int Term = MyBuffer.Pos("\r\n");
if (Term == 0) return;
MyBuffer.Delete(1, Term+1);
ChunkState = ReadingChunkHeader;
break;
}
case ReadingChunksTrailer:
{
int Term = MyBuffer.Pos("\r\n");
if (Term == 0) return;
AnsiString Header = MyBuffer.SubString(1, Term-1).Trim();
MyBuffer.Delete(1, Term+1);
if (Header == "")
{
ParsingState = EndOfResponse;
break;
}
// update Headers with Header data as needed...
break;
}
}
break;
}
case ReadingBodyUntilLength:
{
if (MyBuffer.Length() < BytesRemaining)
{
// save all of MyBuffer bytes to body storage as needed...
BytesRemaining -= MyBuffer.Length();
MyBuffer = "";
return;
}
// save MyBuffer up to BytesRemaining bytes to body storage as needed...
while (BytesRemaining > MaxInt)
{
MyBuffer.Delete(1, MaxInt);
BytesRemaining -= MaxInt;
}
MyBuffer.Delete(1, (int) BytesRemaining);
BytesRemaining = 0;
ParsingState = EndOfResponse;
break;
}
case ReadingBodyUntilClose:
{
// save all of MyBuffer bytes to body storage as needed...
MyBuffer = "";
return;
}
}
break;
}
case EndOfResponse:
{
ProcessResponse();
ParsingState = ReadingStatus;
break;
}
}
}
while (true);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ProcessResponse()
{
// process StatusCode, Headers, and body storage as needed...
if (SameText(Headers->Values["Connection"].Trim(), "close"))
ClientSocket1->Close();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ClientSocket1Connect(TObject *Sender,
TCustomWinSocket *Socket)
{
Memo1->Clear();
MyBuffer = "";
//AnsiString Request = "GET / HTTP/1.1\r\nHost: " + MyHost + "\r\nConnection: close\r\n\r\n";
AnsiString Request = "GET / HTTP/1.1\r\nHost: " + MyHost + "\r\n\r\n";
char *buffer = Request.c_str();
int buffer_len = Request.Length();
int sent;
do
{
sent = ClientSocket1->Socket->SendBuf(buffer, buffer_len);
if (sent == -1)
{
ClientSocket1->Close();
return;
}
buffer += sent;
buffer_len -= sent;
}
while (buffer_len > 0);
ParsingState = ReadingStatus;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ClientSocket1Connecting(TObject *Sender,
TCustomWinSocket *Socket)
{
RadioButton1->Checked = true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ClientSocket1Disconnect(TObject *Sender,
TCustomWinSocket *Socket)
{
RadioButton1->Checked = false;
Socket->Close();
if ((ParsingState == ReadingBody) && (BodyState == ReadingBodyUntilClose))
ProcessResponse();
}
//---------------------------------------------------------------------------
But why are you using TClientSocket for handling HTTP? That is not a good choice. HTTP is a complex protocol, especially in an asynchronous mode like you are trying to use. You really should be using a pre-existing HTTP library/API instead, such as Microsoft's WinInet/WinHTTP, or Indy's TIdHTTP component, or any number of other 3rd party HTTP client APIs that are readably available.