HTML5 WebSockets support for Harbour

Visto 420 veces
Saltar al primer mensaje no leído

Antonio Linares

no leída,
10 oct 2011, 17:29:3110/10/11
a Harbour Developers
I am doing some tests to implement HTML5 WebSockets support from
Harbour.

Now I need to do the right handshake with Chrome, and a key has to be
generated this way:

http://blog.vunie.com/implementing-websocket-draft-10

var key = req.headers['sec-websocket-key']; // I already get this
value from Harbour in my tests
var shasum = crypto.createHash('sha1');
shasum.update(key);
shasum.update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
key = shasum.digest('base64');

I wonder if Harbour provides such functionality. I have seen hb_sha1()
example from Viktor.

I think this is a good way for "Broadway" for Harbour and for a GTWEB,
so if someone else is interested to join efforts, you are welcome :-)
Thanks,

Antonio

Massimo Belgrano

no leída,
10 oct 2011, 18:08:3710/10/11
a harbou...@googlegroups.com
I think that HTML5 WebSockets in harbour will be very important step in harbour evolution
 I am  interested in folow  this steep of harbour evolution
Here discussion Harbour SHA2 and SHA2-HMAC support



--
Massimo Belgrano

Daniel Garcia-Gil

no leída,
10 oct 2011, 21:16:4110/10/11
a Harbour Developers
Hello

it's a idea...

#define MAGIC_KEY "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
...

cContext = GetContext( cData, "Sec-WebSocket-Key" )
cKey = StringSha164( hb_sha1( cContext + MAGIC_KEY ) )

...

static function StringSha164( cKey )

local n
local cVal, cNewKey := ""

for n = 1 to len( cKey ) step 2
cVal = SubStr( cKey, n, 2 )
cNewKey += Chr( hb_HexToNum( cVal ) )
next

return hb_Base64Encode( cNewKey )

...

static function GetContext( cData, cContext )

local nLen := len( cContext )
local cValue := ""
local aLines := hb_ATokens( cData, CRLF )
local aSubLine
local cRow

for each cRow in aLines
if cContext $ cRow
aSubLine = hb_ATokens( cRow, ":" )
cValue = AllTrim( aSubLine[ 2 ] )
exit
endif
next

return cValue

On Oct 10, 5:29 pm, Antonio Linares <antonio.fivet...@gmail.com>
wrote:

Antonio Linares

no leída,
11 oct 2011, 3:53:4311/10/11
a Harbour Developers
Daniel,

Handshake working! Thanks! :-)

Now on the next step...

Antonio

Przemysław Czerpak

no leída,
11 oct 2011, 6:26:5811/10/11
a harbou...@googlegroups.com
On Mon, 10 Oct 2011, Daniel Garcia-Gil wrote:

Hi,

> cKey = StringSha164( hb_sha1( cContext + MAGIC_KEY ) )

> static function StringSha164( cKey )
> local n
> local cVal, cNewKey := ""
> for n = 1 to len( cKey ) step 2
> cVal = SubStr( cKey, n, 2 )
> cNewKey += Chr( hb_HexToNum( cVal ) )
> next
> return hb_Base64Encode( cNewKey )

In Harbour hash functions like HB_SHA*() or HB_MD5*() accept
additional logical parameter which can be used to force
binary representation of hash result so above code can be
rewritten as:

cKey := hb_Base64Encode( hb_sha1( cContext + MAGIC_KEY, .t. ) )

best regards,
Przemek

Antonio Linares

no leída,
11 oct 2011, 18:53:4711/10/11
a Harbour Developers
For those of you that are curious to play with this (and hopefully
help too) here you have a small example of a very early HTML5
WebSocket server developed with Harbour. You can easily build it using
hbmk2:

set HB_COMPILER=bcc
set PATH=c:\bcc582\bin
c:\harbour\bin\hbmk2 wsserver.prg
wsserver.exe

On a next msg I show the javascript code that you need to connect to
this Harbour server. Remember to use Chrome and use its inspector (and
network option) to monitor the conversation.

The problem that we have is that the handshake is ok (if you modify
the calculated cKey, Chrome will complain about it) but after
connecting (sometimes), the socket is closed. Hopefully we can find
together the reason for it, so we can continue towards a "Broadway"
for Harbour... :-)

wsserver.prg (based on harbour\contrib\hbhttpd\core.prg, removing MT
for now and just keeping what we need for now)

#include "hbclass.ch"
#include "hbsocket.ch"

#define THREAD_COUNT_MAX 50
#define SESSION_TIMEOUT 600

#define CRLF Chr(13)+Chr(10)

function Main()

local oServer := HB_WebSocketServer()

oServer:bTrace = { | cMsg, nValue | QOut( cMsg, nValue ) }
oServer:bLogError = { | cErrorMsg | QOut( cErrorMsg ) }

oServer:Run()

return nil

CLASS HB_WebSocketServer

DATA nPort INIT 8080
DATA cBindAddress INIT "0.0.0.0"
DATA bLogAccess INIT {|| NIL }
DATA bLogError INIT {|| NIL }
DATA bTrace INIT {|| NIL }
DATA bIdle INIT {|| NIL }
DATA hMount INIT { => }

/* Results */
DATA cError INIT ""

/* Private */
DATA hmtxQueue
DATA hmtxLog
DATA hmtxSession

DATA hListen
DATA hSession

DATA lStop

METHOD Run()

METHOD Stop() INLINE ::lStop := .T.

METHOD LogError( cError )

ENDCLASS

METHOD Run() CLASS HB_WebSocketServer

LOCAL hSocket, nI, aThreads
LOCAL nWaiters := 0

// IF ! HB_MTVM()
// ::cError := "Multithread support required"
// RETURN .F.
// ENDIF

IF ::nPort < 1 .OR. ::nPort > 65535
::cError := "Invalid port number"
RETURN .F.
ENDIF

// ::hmtxQueue := hb_mutexCreate()
// ::hmtxLog := hb_mutexCreate()
// ::hmtxSession := hb_mutexCreate()

IF Empty( ::hListen := hb_socketOpen() )
::cError := "Socket create error " +
hb_ntos( hb_socketGetError() )
RETURN .F.
ENDIF

IF ! hb_socketBind( ::hListen,
{ HB_SOCKET_AF_INET, ::cBindAddress, ::nPort } )
::cError := "Bind error " + hb_ntos( hb_socketGetError() )
hb_socketClose( ::hListen )
RETURN .F.
ENDIF

IF ! hb_socketListen( ::hListen )
::cError := "Listen error " + hb_ntos( hb_socketGetError() )
hb_socketClose( ::hListen )
RETURN .F.
ENDIF

aThreads := {}
// FOR nI := 1 TO THREAD_COUNT_PREALLOC
// AAdd( aThreads, hb_threadStart( @ProcessConnection(),
Self ) )
// NEXT

::lStop := .F.
::hSession := { => }

? "Harbour HTML5 WebSocket server started"
? "Waiting for a ws://localhost:8080 request..."
? "Press a key to exit"

DO WHILE .T.
Inkey( 0.1 )
if ! Empty( LastKey() )
::Stop()
endif
IF Empty( hSocket := hb_socketAccept( ::hListen,, 1000 ) )
IF hb_socketGetError() == HB_SOCKET_ERR_TIMEOUT
Eval( ::bIdle, Self )
IF ::lStop
EXIT
ENDIF
ELSE
::LogError( "[error] Accept error " +
hb_ntos( hb_socketGetError() ) )
// ::Stop()
ENDIF
ELSE
// hb_mutexQueueInfo( ::hmtxQueue, @nWaiters )
Eval( ::bTrace, "New connection", hSocket )
// Eval( ::bTrace, "Waiters:", nWaiters )
IF nWaiters < 2 .AND. Len( aThreads ) < THREAD_COUNT_MAX
/*
We need two threads in worst case. If first thread
becomes a sessioned
thread, the second one will continue to serve
sessionless requests for
the same connection. We create two threads here to
avoid free thread count
check (and aThreads variable sync) in ProcessRequest().
*/
// AAdd( aThreads, hb_threadStart( @ProcessConnection(),
Self ) )
// AAdd( aThreads, hb_threadStart( @ProcessConnection(),
Self ) )
ProcessConnection( Self, hSocket )
ENDIF
// hb_mutexNotify( ::hmtxQueue, { hSocket, "" } )
ENDIF
ENDDO
hb_socketClose( ::hListen )

/* End child threads */
// hb_mutexLock( ::hmtxSession )
// HB_HEVAL( ::hSession, {|k, v| hb_mutexNotify( v[ 2 ], NIL ),
HB_SYMBOL_UNUSED( k ) } )
// hb_mutexUnlock( ::hmtxSession )
// AEval( aThreads, {|| hb_mutexNotify( ::hmtxQueue, NIL ) } )
// AEval( aThreads, {|h| hb_threadJoin( h ) } )

RETURN .T.

METHOD LogError( cError ) CLASS HB_WebSocketServer

// hb_mutexLock( Self:hmtxLog )
Eval( ::bLogError, DToS( Date() ) + " " + Time() + " " + cError )
// hb_mutexUnlock( Self:hmtxLog )

RETURN NIL

#define MAGIC_KEY "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"

static function ProcessConnection( oServer, hSocket )

local nLen, cBuffer := Space( 4096 ), cContext, cKey, cSend

? "Processing request..."

if ( nLen := hb_socketRecv( hSocket, @cBuffer,,, 10000 ) ) > 0
cBuffer = AllTrim( cBuffer )
? cBuffer
endif

cContext = GetContext( cBuffer, "Sec-WebSocket-Key" )
cKey = hb_Base64Encode( hb_sha1( cContext +
MAGIC_KEY, .T. ) ) // + "." add something to check that the handshake
gets wrong

cSend = "HTTP/1.1 101 Switching Protocols" + CRLF + ;
"Upgrade: websocket" + CRLF + ;
"Connection: Upgrade" + CRLF + ;
"Sec-WebSocket-Accept: " + cKey + CRLF + CRLF

DO WHILE Len( cSend ) > 0
IF ( nLen := hb_socketSend( hSocket, cSend ) ) == - 1
Eval( oServer:bTrace, "send() error:", hb_socketGetError(),
hSocket )
EXIT
ELSEIF nLen > 0
cSend = SubStr( cSend, nLen + 1 )
ENDIF
ENDDO

? "Handshake sent"

return nil

static function GetContext( cData, cContext )

local nLen := Len( cContext )
local cValue := ""
local aLines := hb_ATokens( cData, CRLF )
local aSubLine
local cRow

for each cRow in aLines
if cContext $ cRow
aSubLine = hb_ATokens( cRow, ":" )
cValue = AllTrim( aSubLine[ 2 ] )
exit
endif
next

return cValue

Antonio

Antonio Linares

no leída,
11 oct 2011, 18:59:2311/10/11
a Harbour Developers
When you run it, and make a ws request from Chrome, you will see
something similar to this:

Harbour HTML5 WebSocket server started
Waiting for a ws://localhost:8080 request...
Press a key to exit
New connection 0x003FE3FC
Processing request...
GET /harbour HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: localhost:8080
Sec-WebSocket-Origin: null
Sec-WebSocket-Key: TiorZ6CBL4HN6dPVnV7tjA==
Sec-WebSocket-Version: 8

Handshake sent // this is fine
20111012 00:40:01 [error] Accept error 0 // but the Chrome socket
gets closed here...

Antonio

Antonio Linares

no leída,
11 oct 2011, 19:05:5511/10/11
a Harbour Developers
This is the javascript code that you can use from a HTML page to
connect to the Harbour server. Simply call connect() from a button,
href, etc:

<script language="JavaScript">

var socket;

function connect()
{
try
{
socket = new WebSocket( "ws://localhost:8080/harbour" );

alert( '<p class="event">Socket Status: ' +
socket.readyState );

socket.onopen = function( event )
{
alert( event.data + ", Socket Status: " +
socket.readyState + ' (open)' );
}

socket.onmessage = function( msg )
{
alert( '<p class="message">Received: ' + msg.data );
}

socket.onclose = function( event )
{
alert( event.data + ', Socket Status: ' +
socket.readyState + ' (closed)' );
}

socket.onerror = function()
{
alert( '<p class="event">Socket Status: ' +
socket.readyState + ' (error)' );
}
}
catch( exception )
{
alert( '<p>Error' + exception );
}
}

function send( text )
{
try
{
socket.send( text );
alert( '<p class="event">Sent: ' + text );
}
catch( exception )
{
alert( '<p class="cant send">' );
}
}

</script>

Antonio

Antonio Linares

no leída,
11 oct 2011, 19:11:2111/10/11
a Harbour Developers
A good reading for understanding the power and fun that we could get
using WebSockets and Harbour :-)

http://www.codeproject.com/KB/HTML/Web-Socket-in-Essence.aspx

Antonio

Bacco

no leída,
11 oct 2011, 19:47:0211/10/11
a harbou...@googlegroups.com
For those interested in testing websockets with Opera, enable it by
entering opera:config#enable%20websockets at the address field.
The folks at Opera decided to left it off by default until some of the
protocol's security limitations are decided, but you can activate it
and deactivate any time.


Regards
Bacco

marek.h...@interia.pl

no leída,
12 oct 2011, 4:28:1612/10/11
a harbou...@googlegroups.com,Antonio Linares

"Antonio Linares" <antonio....@gmail.com> pisze:

When I tested Mindaugas HTTP server from Chrome, Chrome wanted (required) *. png (I do not remember the name) file with a graphical icon to display it in head card. The server did not respond: "there is no file" or "file not exist" but "see other" and session "goes in raspberries".
Maybe there is something similar?

Regards,
Marek Horodyski


----------------------------------------------------------------
Najtansze auta w Internecie!
http://linkint.pl/f2a5a

marek.h...@interia.pl

no leída,
12 oct 2011, 4:50:1112/10/11
a harbou...@googlegroups.com,marek.h...@interia.pl,Antonio Linares
marek.h...@interia.pl pisze:
[...]

>
> (required) *. png (I do not remember the name)
[...]


I have : favicon.ico, in HTTP header :
----------------------------------------------
GET /favicon.ico HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
Accept: */*
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24
Accept-Encoding: gzip,deflate,sdch
Accept-Language: pl-PL,pl;q=0.8,en-US;q=0.6,en;q=0.4
Accept-Charset: ISO-8859-2,utf-8;q=0.7,*;q=0.3
------------------------------------------------

and answer is "See Other" :
------------------------------------------------
HTTP/1.1 303 See Other
Connection: keep-alive
Location: /app/login
Content-Type: text/html
Date: Thu, 19 May 201114:23:53 GMT
Content-Length: 48

<html><body><h1>303 See Other</h1></body></html>
------------------------------------------------

Regards,
Marek Horodyski


----------------------------------------------------------------------
Uczestniczki 36. Międzynarodowego Konkursu Miss Bikini na plaży.
Zobacz galerię >>> http://linkint.pl/f2a4a

Antonio Linares

no leída,
12 oct 2011, 5:34:4212/10/11
a Harbour Developers
Marek,

Thanks for your help. I have checked for some received bytes after the
handshake and found 13 bytes always but don't know what they are. Also
I am sending a message ("frame") after those bytes, but the connection
finishes. Here is the modified code:

...

? "Handshake sent"

if ( nLen := hb_socketRecv( hSocket, @cBuffer,,, 10000 ) ) > 0
if Left( cBuffer, 1 ) == Chr( 0 )
? "Frame received..."
? hb_UTF8ToStr( SubStr( cBuffer, 2, nLen - 2 ) )
else
? "Got " + hb_ntos( nLen ) + " bytes but it is not a frame"
? SubStr( cBuffer, 1, nLen )
endif
endif

hb_socketSend( hSocket, Chr( 0 ) + hb_StrToUTF8( "Hello from
Harbour" ) + Chr( 0xFF ) )

return nil

Also I found that we should not display any alert() just after the
request. Now it is opening the connection always, though closes it in
a few secs. In javascript:

socket = new WebSocket( "ws://localhost:8080/harbour" );

// alert( '<p class="event">Socket Status: ' +
socket.readyState );

Hopefully we may find the solution as there is a whole world of
possibilities waiting for us there... :-)

Antonio

Mindaugas Kavaliauskas

no leída,
12 oct 2011, 5:43:4112/10/11
a harbou...@googlegroups.com
Hi,


On 2011.10.12 11:50, marek.h...@interia.pl wrote:
> When I tested Mindaugas HTTP server from Chrome, Chrome wanted (required) *. png (I do not remember the name) file with a graphical icon to display it in head card. The server did not respond: "there is no file" or "file not exist" but "see other" and session "goes in raspberries".
> Maybe there is something similar?
>

> I have : favicon.ico, in HTTP header :
> ----------------------------------------------
> GET /favicon.ico HTTP/1.1
> Host: 127.0.0.1:8002
> Connection: keep-alive
> Accept: */*
> User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24
> Accept-Encoding: gzip,deflate,sdch
> Accept-Language: pl-PL,pl;q=0.8,en-US;q=0.6,en;q=0.4
> Accept-Charset: ISO-8859-2,utf-8;q=0.7,*;q=0.3
> ------------------------------------------------
>
> and answer is "See Other" :
> ------------------------------------------------
> HTTP/1.1 303 See Other
> Connection: keep-alive
> Location: /app/login
> Content-Type: text/html
> Date: Thu, 19 May 201114:23:53 GMT
> Content-Length: 48
>
> <html><body><h1>303 See Other</h1></body></html>
> ------------------------------------------------

I do not remember the details, but as I see from the protocol log above,
the sample app was written to redirect to login page if user is not
logged in (or URL is not valid). So, the same happens for favicon.ico. I
guess you'll be able to find a way and fix sample code.


Antonio case is different. He put connection processing into main
thread, so, main thread cannot accept new connections.


Regards,
Mindaugas

Antonio Linares

no leída,
12 oct 2011, 6:03:5712/10/11
a Harbour Developers
It seems as those 13 bytes comes from a send() that I did from the
javascript:

socket.onopen = function( event )
{
alert( event.data + ", Socket Status: " +
socket.readyState + ' (open)' );
send( "testing" );
}

If I remove that send() then I don't get the 13 bytes. Also if I
change "testing" with a shorter string, I receive fewer bytes. So it
seems as the sent msg is arriving to the Harbour server.

Mindaugas, thanks for joining. After reading your post, could it be
that we are loosing an income connection because we are not using MT ?
Going to do some more tests, thanks

Antonio

Mindaugas Kavaliauskas

no leída,
12 oct 2011, 6:50:3112/10/11
a harbou...@googlegroups.com
Hi,

On 2011.10.12 13:03, Antonio Linares wrote:
> Mindaugas, thanks for joining. After reading your post, could it be
> that we are loosing an income connection because we are not using MT ?

Yes.


Regards,
Mindaugas

marek.h...@interia.pl

no leída,
12 oct 2011, 6:59:0912/10/11
a harbou...@googlegroups.com,Mindaugas Kavaliauskas
"Mindaugas Kavaliauskas" <dbt...@dbtopas.lt> pisze:
303 See Other
> > ------------------------------------------------
[...]
> Antonio case is different. He put connection processing into main
> thread, so, main thread cannot accept new connections.

I've noticed. IMHO Antonio should turn off "code for sesioned", but not MT.
By the way, is not better support sessinons ID on "Cookie: SESSID=" ?
For example establish : Cookie: SESSID=fc9c59934cd91e1524f51065a86eed42 // unique series for id session.

Regards,
Marek Horodyski

----------------------------------------------------------------
Twoj wlasny dom za 675 zl/m-c!
http://linkint.pl/f2a56

Daniel Garcia-Gil

no leída,
11 oct 2011, 6:41:3511/10/11
a Harbour Developers
Hi,

Thanks, it's work fine
Responder a todos
Responder al autor
Reenviar
0 mensajes nuevos