Jump to content
Fr0sT.Brutal

Correct way of using LineMode?

Recommended Posts

I wonder what's the correct way of using TWSocket.LineMode to read line by line. From what I see in sources and samples, this only works when receiving happens in OnDataAvailable handler. But I create socket descendant and would like to override TriggerDataAvailable instead (using handlers for catching parent events is weird + I want to leave the place vacant for client handler). Unfortunately, I can't make it work. If Receive is called from TriggerDataAvailable, there's no data at all and if inherited TriggerDataAvailable is called beforehand, Receive returns the whole piece of data.

Share this post


Link to post

When using LineMode, in the onDataAvailable event you use ReceiveStrA to get a complete raw line without needing to check line endings, but that just calls Receive and does all the hard work for you. 

 

If you are creating descendent components, you need to ensure the correct ancestor, which is not easy due to the deep hierarchy of wsocket.  LineMode is handled in TCustomLineWSocket.

 

Angus

Share this post


Link to post

Angus, thanks for reply. TCustomLineWSocket  is in straight inheritance line so no problem here. I descend from TWSocket.

I tried calling Receive which is almost the same as ReceiveStr from TriggerDataAvailable with no luck.

procedure MemoLog(const msg: string);
begin
  Form1.mLog.Lines.Add(msg);
end;

type
  TLineTestSocket = class(TWSocket)
  public
    Request: RawByteString;
    procedure TriggerSessionConnectedSpecial(ErrCode: Word); override;
    function TriggerDataAvailable(ErrCode : Word) : Boolean; override;
  end;

{ TLineTestSocket }

procedure TLineTestSocket.TriggerSessionConnectedSpecial(ErrCode: Word);
begin
  inherited;
  SendStr(Request);
end;

function TLineTestSocket.TriggerDataAvailable(ErrCode: Word): Boolean;
var s: RawByteString;
begin
  repeat
    s := ReceiveStrA;
    if s = '' then Break;
    MemoLog('Got line: '+ s);
  until True;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  sock: TLineTestSocket;
begin
  sock := TLineTestSocket.Create(Self);
  sock.LineMode := True;
  sock.Addr := 'google.com';
  sock.Port := '80';
  sock.Request := RawByteString('GET / HTTP/1.1'#13#10#13#10);
  sock.Connect;
end;

Here's an excerpt from test project. On execution I get:

Quote

 

 

Got line: HTTP/1.0 301 Moved Permanently
Location: http://www.google.com/
Content-Type: text/html; charset=UTF-8
Date: Wed, 30 Oct 2019 13:07:33 GMT
Expires: Fri, 29 Nov 2019 13:07:33 GMT
Cache-Control: public, max-age=2592000
Server: gws
Content-Length: 219
Got line:
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
X-Cache: MISS from IAC-GW1.glonass-iac.ru
X-Cache-Lookup: MISS from IAC-GW1.glonass-iac.ru:3128
Via: 1.0 IAC-GW1.glonass-iac.ru:3128 (squid/2.7.STABLE5)
Connection: close

<HTML><HEAD><meta http-equ
Got line: iv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.com/">here</A>.
</BODY></HTML>

So either I'm not cooking ReceiveStrA properly or it doesn't do what it supposed to do

Share this post


Link to post

You are not using the Inherited code in other versions of TriggerDataAvailable so need to handle line mode yourself. 

 

Angus

 

Share this post


Link to post

If I add a call to inherited

function TLineTestSocket.TriggerDataAvailable(ErrCode: Word): Boolean;
var s: RawByteString;
begin
  Result := inherited;
  repeat
    s := ReceiveStrA;
    if s = '' then Break;
    MemoLog('Got line: '+ s);
  until True;
end;

I get nothing at all, ReceiveStrA returns empty string.

Share this post


Link to post

I am not trying to write your code for you, just explaining why your function does not work.  You have a conceptual issue trying to override library functionality.

 

Angus

 

 

 

Edited by Angus Robertson

Share this post


Link to post

Well, actually to use library functionality is exactly what I'm trying to do! Alas, library doesn't seem to allow me this.

Of course first calling inherited is logical - that's where bytes are received, lines are buffered where they will be then extracted from and so on. Okay.

But after doing this I expect Receive to return line by line, right? But I get nothing. As far as I can understand the logic behind the code it relies heavily on the presence of OnDataAvailable handler and breaks if there's no any but of course I might be wrong.

Share this post


Link to post

The ICS library is designed to be used through event handlers, not by overriding internal functions.  That requires a high level understanding of the library structure and is beyond the support I can offer here.  

 

Angus

 

Share this post


Link to post

TriggerDataAvailable was never designed to be extendable.  Apart from the three version in wsocket no other ICS units override it.  So while you can override it, you need to replicate all the functionality like parsing line endings.   

 

Angus

Share this post


Link to post
28 minutes ago, Fr0sT.Brutal said:

I tend to create a wrapper class with socket as a field. 

This is probably the best thing to do if you want to spare learning the inner working of TWSocket. All ICS components does just like that: create a new class and have one or more field of TWSocket type.

 

By the way, given the log you showed, you are playing with HTTP protocol. Why not use ICS HTTP component instaed of TWSocket. This is the best choice unless you plan to reimplement HTTP.

 

ICS HTTP component if really powerfull. Rewriting it is probably a huge work.

Share this post


Link to post
3 hours ago, Angus Robertson said:

TriggerDataAvailable was never designed to be extendable.  Apart from the three version in wsocket no other ICS units override it.  So while you can override it, you need to replicate all the functionality like parsing line endings.   

 

Angus

It is extendable in wsocket descendants so why not. I kinda had luck with SchannelSocket doing that so I decided to proceed with encapsulating protocol-specific stuff into socket class itself.

3 hours ago, FPiette said:

By the way, given the log you showed, you are playing with HTTP protocol. Why not use ICS HTTP component instaed of TWSocket. This is the best choice unless you plan to reimplement HTTP.

 

ICS HTTP component if really powerfull. Rewriting it is probably a huge work.

Thanks Francois, I'm aware of it but that's something like HTTP that I want to implement. HTTP request was just the simplest one I found to use as a sample of line-based protocol.

Share this post


Link to post
8 hours ago, Fr0sT.Brutal said:

I decided to proceed with encapsulating protocol-specific stuff into socket class itself. 

Loooong time ago, back in 1996 when I started ICS, a went to that way. I quickly stopped it because it makes things much more complex for both the component developer and the component user. Since then, I am still happy to use delegation instead of inheritance. Inheritance is largely used in ICS, see TWSocket itself as an example. But delegation has to be used when it has to!

 

  • Thanks 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×