From e0fe56a3f27d8ab97cf3a42f7c9a675869f7afed Mon Sep 17 00:00:00 2001 From: LionNatsu <372691006@qq.com> Date: Thu, 24 Jul 2014 11:21:17 +0800 Subject: [PATCH] CLionetHttp --- CLionet.bas | 77 +++++++---- CLionet.bi | 31 +++-- CLionet.fbp | 25 ++-- CLionetHttp.bas | 350 ++++++++++++++++++++++++++++++++++++++++++++++++ CLionetHttp.bi | 72 ++++++++++ Lionet.bas | 48 +++++-- 6 files changed, 543 insertions(+), 60 deletions(-) create mode 100644 CLionetHttp.bas create mode 100644 CLionetHttp.bi diff --git a/CLionet.bas b/CLionet.bas index 89be715..514af24 100644 --- a/CLionet.bas +++ b/CLionet.bas @@ -5,6 +5,7 @@ #undef opensocket constructor CLionet() + this.startup() this.opensocket() end constructor @@ -43,31 +44,51 @@ function CLionet.error_string() as zstring ptr end function #endif +function CLionet.parseAddress( address as string ) as uinteger + dim as uinteger dwAddress = inet_addr( address ) + if dwAddress <> INADDR_NONE then return dwAddress + dim as hostent ptr host = gethostbyname( address ) + if host = 0 then return 0 + return *(host->h_addr) +end function + function CLionet.eventThread( boku as CLionet ptr ) as integer - print "thread" - dim as WSANETWORKEVENTS event_info - do - if boku->flag_thread = 2 then boku->flag_thread = 0 : exit do - if WSAWaitForMultipleEvents( 1, @boku->m_event, -1, 1000, -1 ) = WSA_WAIT_TIMEOUT then continue do - WSAEnumNetworkEvents( boku->m_socket, boku->m_event, @event_info ) - #macro ROUTER( fnc, evtmsk, bitmsk ) + #macro ROUTER_INIT_EXIT( fnc, msg ) + if boku->onSocket <> 0 then + boku->onSocket( boku, msg, 0 ) + elseif boku->fnc <> 0 then + boku->fnc( boku ) + endif + #endmacro + #macro ROUTER( fnc, evtmsk, bitmsk, msg ) if ( event_info.lNetworkEvents and evtmsk ) <> 0 then if boku->onSocket <> 0 then - boku->onSocket( boku, evtmsk, event_info.iErrorCode( bitmsk ) ) + boku->onSocket( boku, msg, event_info.iErrorCode( bitmsk ) ) elseif boku->fnc <> 0 then boku->fnc( boku, event_info.iErrorCode( bitmsk ) ) endif endif - #endmacro - ROUTER (onAccept,FD_ACCEPT,FD_ACCEPT_BIT) - ROUTER (onConnect,FD_CONNECT,FD_CONNECT_BIT) - ROUTER (onRead,FD_READ,FD_READ_BIT) - ROUTER (onWrite,FD_WRITE,FD_WRITE_BIT) - ROUTER (onClose,FD_CLOSE,FD_CLOSE_BIT) - #undef ROUTER + #endmacro + dim as WSANETWORKEVENTS event_info + ROUTER_INIT_EXIT( onInit, CLE_INIT ) + do + if boku->flag_thread = 2 then + boku->flag_thread = 0 + ROUTER_INIT_EXIT( onInit, CLE_INIT ) + exit do + endIf + if WSAWaitForMultipleEvents( 1, @boku->m_event, -1, 1000, -1 ) = WSA_WAIT_TIMEOUT then continue do + WSAEnumNetworkEvents( boku->m_socket, boku->m_event, @event_info ) + ROUTER( onAccept, FD_ACCEPT, FD_ACCEPT_BIT, CLIONET_EVENT.CLE_ACCEPT ) + ROUTER( onConnect, FD_CONNECT, FD_CONNECT_BIT, CLIONET_EVENT.CLE_CONNECT ) + ROUTER( onRead, FD_READ, FD_READ_BIT, CLIONET_EVENT.CLE_READ ) + ROUTER( onWrite, FD_WRITE, FD_WRITE_BIT, CLIONET_EVENT.CLE_WRITE ) + ROUTER( onClose, FD_CLOSE,FD_CLOSE_BIT, CLIONET_EVENT.CLE_CLOSE ) sleep_ 0 loop return 0 + #undef ROUTER_INIT_EXIT + #undef ROUTER end function function CLionet.accept() as integer @@ -116,7 +137,7 @@ function CLionet.bind( port as ushort ) as integer end function function CLionet.bind( addr as string, port as ushort ) as integer - return this.bind( .inet_addr( addr ), port ) + return this.bind( CLionet.parseAddress( addr ), port ) end function function CLionet.bind( addr as uinteger, port as ushort ) as integer @@ -124,8 +145,8 @@ function CLionet.bind( addr as uinteger, port as ushort ) as integer with serveraddr .sin_family = AF_INET .sin_addr.S_addr = addr + .sin_port = htons( port ) end with - serveraddr.sin_port = .htons( port ) return this.bind( cast( sockaddr ptr, @serveraddr ) ) end function @@ -134,7 +155,7 @@ function CLionet.bind( lpName as any ptr ) as integer end function function CLionet.connect( addr as string, port as ushort ) as integer - return this.connect( .inet_addr( addr ), port ) + return this.connect( CLionet.parseAddress( addr ), port ) end function function CLionet.connect( addr as uinteger, port as ushort ) as integer @@ -142,8 +163,8 @@ function CLionet.connect( addr as uinteger, port as ushort ) as integer with serveraddr .sin_family = AF_INET .sin_addr.S_addr = addr + .sin_port = htons( port ) end with - serveraddr.sin_port = .htons( port ) return this.connect( cast( sockaddr ptr, @serveraddr ) ) end function @@ -163,7 +184,7 @@ end function function CLionet.opensocket() as integer if this.m_socket then if this.closesocket() = 0 then return SOCKET_ERROR - this.m_socket = .socket_( AF_INET, SOCK_STREAM, IPPROTO_TCP ) + this.m_socket = socket_( AF_INET, SOCK_STREAM, IPPROTO_TCP ) this.attachEvent() return this.m_socket end function @@ -180,7 +201,7 @@ end property property CLionet.async( mode as CLIONET_MODE ) if this.async_mode = mode then exit property select case mode - case CLIONET_MODE.CLAM_EVENTSELECT + case CLIONET_MODE.CLM_EVENTSELECT if this.flag_thread <> 0 then exit property this.async_mode = mode this.m_event = WSACreateEvent() @@ -190,7 +211,7 @@ property CLionet.async( mode as CLIONET_MODE ) CreateThread( 0, 0, _ cast( LPTHREAD_START_ROUTINE, procptr( CLionet.eventThread ) ), _ @this, 0, @ThreadID ) - case CLIONET_MODE.CLAM_BLOCK + case CLIONET_MODE.CLM_BLOCK this.async_mode = mode this.detachEvent() this.flag_thread = 2 @@ -207,19 +228,19 @@ sub CLionet.exitEventThread() end sub sub CLionet.attachEvent() - if this.async_mode <> CLIONET_MODE.CLAM_EVENTSELECT then exit sub + if this.async_mode <> CLIONET_MODE.CLM_EVENTSELECT then exit sub WSAEventSelect( this.m_socket, this.m_event, FD_READ or FD_CLOSE or FD_CONNECT or FD_ACCEPT or FD_WRITE ) end sub sub CLionet.detachEvent() - if this.async_mode <> CLIONET_MODE.CLAM_EVENTSELECT then exit sub + if this.async_mode <> CLIONET_MODE.CLM_EVENTSELECT then exit sub WSAEventSelect( this.m_socket, this.m_event, 0 ) end sub -property CLionet.remoteip() as zstring ptr +property CLionet.remoteip() as string dim remoteAddr as sockaddr_in, i as integer = sizeof( sockaddr_in ) .getpeername( this.m_socket, cast( sockaddr ptr, @remoteAddr ), @i ) - return .inet_ntoa( remoteAddr.sin_addr ) + return *.inet_ntoa( remoteAddr.sin_addr ) end property property CLionet.remoteport() as ushort @@ -228,10 +249,10 @@ property CLionet.remoteport() as ushort return .ntohs( remoteAddr.sin_port ) end property -property CLionet.localip() as zstring ptr +property CLionet.localip() as string dim localAddr as sockaddr_in, i as integer = sizeof( sockaddr_in ) .getsockname( this.m_socket, cast( sockaddr ptr, @localAddr ), @i ) - return .inet_ntoa( localAddr.sin_addr ) + return *.inet_ntoa( localAddr.sin_addr ) end property property CLionet.localport() as ushort diff --git a/CLionet.bi b/CLionet.bi index 0d5e4d5..bfb29f5 100644 --- a/CLionet.bi +++ b/CLionet.bi @@ -5,8 +5,17 @@ #undef opensocket enum CLIONET_MODE - CLAM_BLOCK - CLAM_EVENTSELECT + CLM_BLOCK + CLM_EVENTSELECT +end enum +enum CLIONET_EVENT + CLE_ACCEPT + CLE_CONNECT + CLE_READ + CLE_WRITE + CLE_CLOSE + CLE_INIT + CLE_EXIT end enum type CLionet extends object public: @@ -37,20 +46,24 @@ type CLionet extends object declare virtual function accept( scklistener as SOCKET ) as integer declare virtual function send( in_buf as any ptr, length as integer ) as integer declare virtual function recv( out_buf as any ptr, length as integer, f_peek as integer = 0 ) as integer - declare property thesocket() as SOCKET - declare property remoteip() as zstring ptr - declare property remoteport() as ushort - declare property localip() as zstring ptr - declare property localport() as ushort - declare property async( mode as CLIONET_MODE ) - declare property async() as CLIONET_MODE + declare virtual property thesocket() as SOCKET + declare virtual property remoteip() as string + declare virtual property remoteport() as ushort + declare virtual property localip() as string + declare virtual property localport() as ushort + declare virtual property async( mode as CLIONET_MODE ) + declare virtual property async() as CLIONET_MODE onSocket as sub ( boku as CLionet ptr, msg as integer, errcode as integer ) + onInit as sub ( boku as CLionet ptr ) + onExit as sub ( boku as CLionet ptr ) onAccept as sub ( boku as CLionet ptr, errcode as integer ) onConnect as sub ( boku as CLionet ptr, errcode as integer ) onRead as sub ( boku as CLionet ptr, errcode as integer ) onWrite as sub ( boku as CLionet ptr, errcode as integer ) onClose as sub ( boku as CLionet ptr, errcode as integer ) + g_key as any ptr protected: + declare static function parseAddress( address as string ) as uinteger declare static function eventThread( boku as CLionet ptr ) as integer declare virtual function connect( lpName as any ptr ) as integer declare virtual function bind( lpName as any ptr ) as integer diff --git a/CLionet.fbp b/CLionet.fbp index 62d6730..e16ca20 100644 --- a/CLionet.fbp +++ b/CLionet.fbp @@ -10,32 +10,39 @@ CompileIfNewer=0 IncVersion=0 RunCmd=0 [Make] -Current=1 +Module=Module Build,fbc -c +Recompile=2 +Current=3 1=Windows Console (Server),fbc -s console -d __SERVER__ -x "Server.exe" 2=Windows Console (Client),fbc -s console -x "Client.exe" -Recompile=2 -Module=Module Build,fbc -c +3=Windows Console,fbc -s console -x "Lionet.exe" -g Output= -Run= +Run=Lionet.exe Delete= [TabOrder] -TabOrder=1,2,1001,3 +TabOrder=1,1002,5,1001,2,3 [File] 1=Lionet.bas 2=CLionet.bi 3=README.md Main=1 1001=CLionet.bas -4=inc\winsock2.bi +4= +5=CLionetHttp.bi +1002=CLionetHttp.bas [BreakPoint] 1= 2= 3= 1001= 4= +5= +1002= [FileInfo] -1=0,32,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -2=0,22,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +1=0,58,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +2=0,71,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 3=0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -1001=0,204,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +1001=0,210,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 4=0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +5=0,18,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +1002=0,139,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 diff --git a/CLionetHttp.bas b/CLionetHttp.bas new file mode 100644 index 0000000..f8356c6 --- /dev/null +++ b/CLionetHttp.bas @@ -0,0 +1,350 @@ +#include "CLionetHttp.bi" +#define NEWLINE !"\r\n" + +#ifdef __FB_DEBUG__ + #define WHAT_THE_HELL(x) print "[FATAL ERROR] in " & __FILE__ & " (" & __FUNCTION__ & ") line " & __LINE__ & " : " & x : ExitProcess( -1 ) + #define DEBUG_PRINT(x) print "[DEBUG] in " & __FILE__ & " (" & __FUNCTION__ & ") line " & __LINE__ & " : " & NEWLINE & x + #define HERE_I_AM print "[MILESTONE] in " & __FILE__ & " (" & __FUNCTION__ & ") line " & __LINE__ +#else + #define WHAT_THE_HELL(x) ExitProcess( -1 ) + #define DEBUG_PRINT(x) + #define HERE_I_AM +#endif + +declare function base64_encode( src as const string ) as string +declare function base64_decode( src as const string ) as string + +enum RECV_STATE + HEAD_NOTCOMPLETE + HEAD_COMPLETED +end enum + +sub CLionetHttp.onSocket( socketboku as CLionet ptr, msg as integer, errcode as integer ) + dim as CLionetHttp ptr boku = cast( CLionetHttp ptr, socketboku->g_key ) + select case msg + case CLE_INIT + boku->m_state = CLHS_STANDBY + case CLE_CONNECT + boku->SetState( CLHS_CONNECTED ) + case CLE_WRITE + if boku->m_state = CLHS_SENDING then + boku->m_state = CLHS_WAITING + endif + case CLE_READ + boku->SetState( CLHS_RECEIVING ) + dim as byte ptr _4kbuffer = boku->m_4k_buffer + dim as integer _size = boku->m_socket->recv( _4kbuffer, 10000 ) + if boku->m_recv_header_state = HEAD_NOTCOMPLETE then + 'Search the terminator of header + dim as integer header_ending_pos + 'Search from the tail of the header + if boku->m_recv_header_length > 0 then + 'Element cat_buffer( 6 ) is reserved for \0 + dim as byte cat_buffer( 6 ) + CopyMemory( @cat_buffer( 0 ), @boku->m_recv_header[ boku->m_recv_header_length - 3 ], 3 ) + if _size > 3 then + CopyMemory( @cat_buffer( 3 ), _4kbuffer, 3 ) + else + CopyMemory( @cat_buffer( 3 ), _4kbuffer, _size ) + endif + header_ending_pos = instr( *cast( zstring ptr, @cat_buffer( 0 ) ), NEWLINE & NEWLINE ) + endif + if header_ending_pos = 0 then + dim as string _str = space( _size ) + CopyMemory( strptr( _str ), _4kbuffer, _size ) + header_ending_pos = instr( _str, NEWLINE & NEWLINE ) + if header_ending_pos <> 0 then header_ending_pos += len( NEWLINE & NEWLINE ) - 1 + endif + if header_ending_pos <> 0 then + 'There's the terminator. + 'header + EnterCriticalSection( @boku->m_header_critical ) + boku->m_recv_header = reallocate( boku->m_recv_header, boku->m_recv_header_length + header_ending_pos ) + CopyMemory( @boku->m_recv_header[ boku->m_recv_header_length ], _4kbuffer, header_ending_pos ) + boku->m_recv_header_length += header_ending_pos + LeaveCriticalSection( @boku->m_header_critical ) + 'content + EnterCriticalSection( @boku->m_buffer_critical ) + boku->m_recv_buffer = allocate( _size - header_ending_pos ) + CopyMemory( boku->m_recv_buffer, @_4kbuffer[header_ending_pos], _size - header_ending_pos ) + boku->m_recv_length = _size - header_ending_pos + LeaveCriticalSection( @boku->m_buffer_critical ) + 'set the flag + boku->m_recv_header_state = HEAD_COMPLETED + boku->parseResponseHeader() + 'process completed. + if boku->m_recv_length = boku->m_recv_expected_length then boku->m_state = CLHS_COMPLETED + if boku->m_recv_length > boku->m_recv_expected_length then WHAT_THE_HELL("RECEIVED A LARGER PACKET") + else + 'There's no the terminator yet. + 'header + EnterCriticalSection( @boku->m_header_critical ) + boku->m_recv_header = reallocate( boku->m_recv_header, boku->m_recv_header_length + _size ) + CopyMemory( @boku->m_recv_header[ boku->m_recv_header_length ], _4kbuffer, _size ) + boku->m_recv_header_length += _size + LeaveCriticalSection( @boku->m_header_critical ) + endif + else + 'Receiving the content: Verify that the size is not overflow, and copy data to buffer. + 'content + EnterCriticalSection( @boku->m_buffer_critical ) + boku->m_recv_buffer = reallocate( boku->m_recv_buffer, boku->m_recv_length + _size ) + CopyMemory( @boku->m_recv_buffer[ boku->m_recv_length ], _4kbuffer, _size ) + boku->m_recv_length += _size + LeaveCriticalSection( @boku->m_buffer_critical ) + if boku->m_recv_length = boku->m_recv_expected_length then boku->SetState( CLHS_COMPLETED ) + if boku->m_recv_length > boku->m_recv_expected_length then WHAT_THE_HELL("RECEIVED A LARGER PACKET") + endif + end select +end sub + +constructor CLionetHttp() + this.m_socket = new CLionet() + this.m_socket->g_key = @this + this.m_socket->onSocket = procptr( CLionetHttp.onSocket ) + this.m_4k_buffer = allocate( 4100 ) + this.m_event_waitfor = 0 + this.m_event_statechange = CreateEvent( 0, 0, 0, 0 ) + InitializeCriticalSection( @this.m_header_critical ) + InitializeCriticalSection( @this.m_buffer_critical ) + + this.m_socket->async = CLM_EVENTSELECT +end constructor + +destructor CLionetHttp() + delete this.m_socket + deallocate this.m_4k_buffer + EnterCriticalSection( @this.m_header_critical ) + deallocate this.m_recv_header + LeaveCriticalSection( @this.m_header_critical ) + EnterCriticalSection( @this.m_buffer_critical ) + deallocate this.m_recv_buffer + LeaveCriticalSection( @this.m_buffer_critical ) + CloseHandle( this.m_event_statechange ) + DeleteCriticalSection( @this.m_header_critical ) + DeleteCriticalSection( @this.m_buffer_critical ) +end destructor + +sub CLionetHttp.WaitForState( stat as CLIONETHTTP_STATE, timeout as integer ) + this.m_event_waitfor = stat + WaitForSingleObject( this.m_event_statechange, timeout ) +end sub + +sub CLionetHttp.SetState( stat as CLIONETHTTP_STATE ) + this.m_state = stat + if this.m_event_waitfor <> 0 and this.m_event_waitfor = stat then SetEvent( this.m_event_statechange ) +end sub + +function CLionetHttp.open( url as string, method as string, async as integer, timeout as integer ) as integer + 'HTTP Only + if lcase( left( url, 7 ) ) <> "http://" then return -1 + this.m_async = async + this.m_timeout = timeout + dim as string url_host, url_temp + 'String after "http://" + url_temp = mid( url, 8 ) + 'Fix the URL from "http://www.example.org" to "http://www.example.org/" + if instr( url_temp, "/" ) = 0 then url_temp += "/" + 'String before "/", such as "www.example.org", "name:pswd@subdomain.example.com:80", etc. + url_host = left( url_temp, instr( url_temp, "/" ) - 1 ) + 'If it requests by an authentication. + if instr( url_host, "@" ) then + this.m_auth = base64_encode( left( url_host, instr( url_host, "@" ) - 1 ) ) + url_host = mid( url_host, instr( url_host, "@" ) + 1 ) + else + this.m_auth = "" + endif + 'If it requests by another port. + if instr( url_host, ":" ) then + this.m_port = valint( mid( url_host, instr( url_host, ":" ) + 1 ) ) + url_host = left( url_host, instr( url_host, ":" ) - 1 ) + else + this.m_port = 80 + endif + 'Host + this.m_host = url_host + 'URL: after "/" and including "/" + this.m_url = mid( url_temp, instr( url_temp, "/" ) ) + 'GET or POST + this.m_method = ucase( method ) + 'Construct the header --- except for "Content-Length" and "Content-Type" + this.m_header = this.m_method & " " & this.m_url & " HTTP/1.1" & NEWLINE + if this.m_port <> 80 then + this.m_header+= "Host: " & this.m_host & ":" & this.m_port & NEWLINE + else + this.m_header+= "Host: " & this.m_host & NEWLINE + endif + + this.m_header+= "Connection: keep-alive" & NEWLINE + this.m_header+= "Accept: */*" & NEWLINE + 'Create a socket (Class CLionet) + this.m_socket->connect( this.m_host, this.m_port ) + this.m_state = CLHS_CONNECTING + if this.m_async = 0 then + WaitForState( CLHS_CONNECTED, this.m_timeout ) + endif + return 0 +end function + +function CLionetHttp.send( body as string, mime_type as string ) as integer + if len(body) <> 0 then + this.setRequestHeader( "Content-Type", mime_type ) + this.setRequestHeader( "Content-Length", str( len( body ) ) ) + endif + this.m_header+= NEWLINE + dim as string buffer = this.m_header & body + print buffer + + this.m_recv_header_state = HEAD_NOTCOMPLETE + this.m_recv_length = 0 + this.m_responseHeader = "" + this.m_socket->send( strptr(buffer), len(buffer) ) + this.m_state = CLHS_SENDING + if this.m_async = 0 then + WaitForState( CLHS_COMPLETED, this.m_timeout ) + endif + return 0 +end function + +sub CLionetHttp.parseResponseHeader() + EnterCriticalSection( @this.m_header_critical ) + this.m_responseHeader = space( this.m_recv_header_length ) + CopyMemory( strptr( this.m_responseHeader ), this.m_recv_header, this.m_recv_header_length ) + deallocate(this.m_recv_header) + this.m_recv_header_length = 0 + if this.getResponseHeader( "Content-Length" ) <> "" then + this.m_recv_expected_length = valint( this.getResponseHeader( "Content-Length" ) ) + elseif lcase( this.getResponseHeader( "Transfer-Encoding" ) ) = "chunked" then + WHAT_THE_HELL("I hate the chunked-mode.") + else + WHAT_THE_HELL("OMG! What's that?") + endif + LeaveCriticalSection( @this.m_header_critical ) +end sub + +sub CLionetHttp.removeRequestHeader( key as string ) + if instr( this.m_header, NEWLINE & key ) = 0 then exit sub + dim as integer k_posA = instr( this.m_header, NEWLINE & key ) + ( len( NEWLINE ) - 1 ) + dim as integer k_posB = instr( k_posA, this.m_header, NEWLINE ) + ( len( NEWLINE ) - 1 ) + dim as string A_block = left( this.m_header, k_posA ) + dim as string B_block = mid( this.m_header, k_posB + 1 ) + this.m_header = A_block & B_block +end sub + +sub CLionetHttp.setRequestHeader( key as string, value as string ) + this.removeRequestHeader( key ) + this.m_header+= key & ": " & value & NEWLINE +end sub + +function CLionetHttp.getResponseHeader( key as string ) as string + dim as integer k_posA = instr( this.m_responseHeader, NEWLINE & key ) + len( NEWLINE & key ) + k_posA = instr( k_posA, this.m_responseHeader, ":" ) + 1 + dim as integer k_posB = instr( k_posA, this.m_responseHeader, NEWLINE ) + return trim( mid( this.m_responseHeader, k_posA + 1, k_posB - k_posA ) ) +end function + +property CLionetHttp.state() as CLIONETHTTP_STATE + return this.m_state +end property + +property CLionetHttp.responseRAWHeader() as string + return this.m_responseHeader +end property + +property CLionetHttp.responseNumber() as integer + dim as integer posA = instr( this.m_responseHeader, " " ) + dim as integer posB = instr( posA + 1, this.m_responseHeader, " " ) + return valint( mid( this.m_responseHeader, posA + 1, posB - posA - 1 ) ) +end property + +property CLionetHttp.responseDescription() as string + dim as integer posA = instr( this.m_responseHeader, " " ) + posA = instr( posA + 1, this.m_responseHeader, " " ) + dim as integer posB = instr( posA + 1, this.m_responseHeader, NEWLINE ) + return mid( this.m_responseHeader, posA + 1, posB - posA - 1 ) +end property + +property CLionetHttp.responseString() as string + EnterCriticalSection( @this.m_buffer_critical ) + dim as string l_responseContent = space( this.m_recv_length ) + CopyMemory( strptr( l_responseContent ), this.m_recv_buffer, this.m_recv_length ) + LeaveCriticalSection( @this.m_buffer_critical ) + return l_responseContent +end property + +property CLionetHttp.responseBytes() as byte ptr + return this.m_recv_buffer +end property + +property CLionetHttp.responseLength() as integer + return this.m_recv_length +end property + +private function base64_encode( src as const string ) as string +#define E0(v1) ((v1) shr 2) +#define E1(v1,v2) ((((v1) and 3) shl 4) + ((v2) shr 4)) +#define E2(v2,v3) ((((v2) and &h0F) shl 2) + ((v3) shr 6)) +#define E3(v3) ((v3) and &h3F) + static as string B64 + B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + dim as integer l = len(src), k = 0, j = 0 + dim as string t = "" + if l = 0 then return t + t = string( ((l+2)\3)*4,"=" ) + for j = 0 to l - ((l mod 3)+1) step 3 + t[k+0]=B64[e0(src[j+0])] + t[k+1]=B64[e1(src[j+0],src[j+1])] + t[k+2]=B64[e2(src[j+1],src[j+2])] + t[k+3]=B64[e3(src[j+2])]:k+=4 + next + if (l mod 3) = 2 then + t[k+0]=B64[e0(src[j+0])] + t[k+1]=B64[e1(src[j+0],src[j+1])] + t[k+2]=B64[e2(src[j+1],src[j+2])] + t[k+3]=61 + elseif (l mod 3) = 1 then + t[k+0]=B64[e0(src[j+0])] + t[k+1]=B64[e1(src[j+0],src[j+1])] + t[k+2]=61 + t[k+3]=61 + endif + return t +#undef E0 +#undef E1 +#undef E2 +#undef E3 +end function +private function base64_decode( src as const string ) as string + static _decode_tbl(255) as uinteger => { _ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, _ ' 0..31 + 0,0,0,0,0,0,0,0,0,0,0, _ ' 32..42 + 62,0,0,0,63, _ ' 43 (+), 44..46, 47 (/) + 52,53,54,55,56,57,58,59,60,61, _ ' 48..57 (0..9) + 0,0,0,0,0,0,0, _ ' 58..64 + 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25, _ ' 65..90 (A..Z) + 0,0,0,0,0,0, _ ' 91..96 + 26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,_ ' 97..122 (a..z) + 0,0,0,0,0 } ' 123..127 + dim as zstring ptr source = strptr( src ) + dim as integer source_length = len( src ) + dim as integer dest_length + if source = 0 or source_length = 0 then return "" + dest_length = (source_length \ 4) * 3 + if source_length mod 4 = 3 then : dest_length += 2 + elseif source_length mod 4 = 2 then : dest_length += 1 + endif + dim as string dst = space( dest_length + 1 ) + dim as zstring ptr dest = strptr( dst ) + for src_ctr as integer = 0 to source_length \ 4 -1 + dim as uinteger buf = (_decode_tbl(source[0]) shl 18) or _ + (_decode_tbl(source[1]) shl 12) or _ + (_decode_tbl(source[2]) shl 6) or _ + (_decode_tbl(source[3])) + dest[0] = buf shr 16 + dest[1] = (buf shr 8) and &hFF + dest[2] = buf and &hFF + source += 4 + dest += 3 + next + return dst +end function diff --git a/CLionetHttp.bi b/CLionetHttp.bi new file mode 100644 index 0000000..23b7960 --- /dev/null +++ b/CLionetHttp.bi @@ -0,0 +1,72 @@ +#ifndef __CLIONETHTTP_BI__ +#define __CLIONETHTTP_BI__ +#include "CLionet.bi" +enum CLIONETHTTP_STATE + CLHS_STANDBY + CLHS_CONNECTING + CLHS_CONNECTED + CLHS_SENDING + CLHS_WAITING + CLHS_RECEIVING + CLHS_COMPLETED +end enum + +type CLionetHttp extends object + public: + declare constructor() + declare virtual destructor() + + declare virtual function open( url as string, method as string = "GET", async as integer = 0, timeout as integer = 10000 ) as integer + declare virtual sub setRequestHeader( key as string, value as string ) + declare virtual sub removeRequestHeader( key as string ) + declare virtual function send( body as string = "", mime_type as string = "application/x-www-form-urlencoded" ) as integer + + declare virtual function getResponseHeader( key as string ) as string + + declare virtual property responseRAWHeader() as string + declare virtual property responseNumber() as integer + declare virtual property responseDescription() as string + declare virtual property responseString() as string + declare virtual property responseBytes() as byte ptr + declare virtual property responseLength() as integer + declare virtual property state() as CLIONETHTTP_STATE + + protected: + declare static sub onSocket( boku as CLionet ptr, msg as integer, errcode as integer ) + declare virtual sub parseResponseHeader() + m_socket as CLionet ptr + m_state as CLIONETHTTP_STATE + + m_method as string + m_host as string + m_port as ushort + m_url as string + m_auth as string + m_header as string + + m_responseHeader as string + m_async as integer + m_timeout as integer + + private: + declare virtual sub WaitForState( stat as CLIONETHTTP_STATE, timeout as integer ) + declare virtual sub SetState( stat as CLIONETHTTP_STATE ) + m_4k_buffer as byte ptr + + m_recv_header as byte ptr + m_recv_header_length as integer + + m_recv_buffer as byte ptr + m_recv_length as integer + m_recv_expected_length as integer + + m_recv_header_state as integer + m_chunked as integer + + m_header_critical as CRITICAL_SECTION + m_buffer_critical as CRITICAL_SECTION + m_event_statechange as HANDLE + m_event_waitfor as CLIONETHTTP_STATE +end type + +#endif \ No newline at end of file diff --git a/Lionet.bas b/Lionet.bas index 8c3bc0e..fe879f0 100644 --- a/Lionet.bas +++ b/Lionet.bas @@ -1,15 +1,21 @@ +'example for CLionet +#if 0 #ifdef __SERVER__ 'Server #include "CLionet.bi" sub OnB ( boku as CLionet ptr, msg as integer, errcode as integer ) select case msg - case FD_READ - print "READ" - dim p as zstring * 50 - boku->recv(@p,50) - print "data:["& p &"]" - case FD_CLOSE - print "CLOSE" + case CLAE_READ + print " READ" + dim p as zstring * 4096 + boku->recv(@p,4096) + print " data:["& p &"]" + case CLAE_INIT + print " CONNECTED" + dim p as string = "This is Server." + boku->send(strptr(p), len(p)) + case CLAE_CLOSE + print " CLOSE" boku->async = CLAM_BLOCK boku->closesocket() delete boku @@ -18,16 +24,15 @@ sub OnB ( boku as CLionet ptr, msg as integer, errcode as integer ) end sub sub OnA ( boku as CLionet ptr, msg as integer, errcode as integer ) select case msg - case FD_ACCEPT + case CLAE_ACCEPT print "ACCEPT" + print boku->remoteip dim as CLionet ptr cc = new CLionet() cc->onSocket = procptr(OnB) - cc->async = CLAM_EVENTSELECT cc->accept(boku) + cc->async = CLAM_EVENTSELECT end select end sub - -CLionet.startup() dim as CLionet ptr cl = new CLionet() cl->onSocket = procptr(OnA) cl->async = CLAM_EVENTSELECT @@ -40,14 +45,29 @@ end #include "CLionet.bi" CLionet.startup() dim as CLionet ptr cl = new CLionet() -cl->connect("127.0.0.1",85) -print *cl->error_string +cl->connect("reshh.gicp.net",85) print "connected" +dim px as zstring * 4096 +cl->recv(@px,4096) +print " data:["& px &"]" dim p as string = "Hello World!" cl->send(strptr(p),len(p)) -print *cl->error_string print "sent" sleep delete cl CLionet.cleanup() #endif +#endif + +'example for CLionetHttp + +#include "CLionetHttp.bi" +dim as CLionetHttp ptr c = new CLionetHttp() +c->open("http://www.baidu.com/img/baidu_sylogo1.gif") +c->send() +open "baidu.gif" for binary as 1 +dim as byte buffer(c->responseLength-1) +copymemory(@buffer(0),c->responseBytes,c->responseLength) +put #1,1,buffer() +close 1 +sleep