30k simple web server

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
zerospeed
Posts: 227
Joined: Nov 04, 2005 15:29

Post by zerospeed »

parakeet wrote:
Btw, you're using the file locking to get the ERR report correctly for the file you're opening? Why? and force to use file handle #1... from a multithread env... sounds could brake.
Sorry, I am a beginner in freebasic file handling. Am I really locking files? I thought using a mutex would protect me from any problems. Am I incorrect?
calling ERR to evalute errors in previous open function, could be changed to this:

Code: Select all

            'read requested file from disk
            'mutexlock(ctx.filemutex) 'lock access to ERR which is global, sadly...
            if dir(stPath) = "" then
                stReponseHeader = "HTTP/1.1 404 Not Found" & stNL & stNL
            else
				FileHandle = freefile
				if open( stPath for binary access read shared as #FileHandle ) <> 0 then
					stReponseHeader = "HTTP/1.1 403 Forbbiden" & stNL & stNL
				else
					FileLength = LOF(FileHandle)    'file len
					if FileLength <> 0 then
						dim FileBuffer(FileLength) as byte
						get #FileHandle, , FileBuffer()
					end if
					close #FileHandle

					stReponseHeader = "HTTP/1.1 200 OK" & stNL
					stReponseHeader += "Cache-Control: private" & stNL
					stReponseHeader += "content-length : " & str(FileLength) & stNL & stNL
				end if
            end if
            'mutexunlock(ctx.filemutex)
So far, loaded with concurrent test from ApacheBench and didn't brake :)


I'm working on a tcp load balancing tool for win32, later linux... what I need to figure out is the way it will be "intelligent".

I'll left the balancing algorithm to a embedded lua script.

The work you have done Anselme is excelent! (no matter how many times I say it, still amazed).

Later guys,

Zero
parakeet
Posts: 48
Joined: Mar 30, 2006 15:46
Location: Lille, France

Post by parakeet »

Thank you for the compliment. I just applied the proposed fixes. Thanx v1ctor and Zerospeed.
parakeet
Posts: 48
Joined: Mar 30, 2006 15:46
Location: Lille, France

Post by parakeet »

zerospeed wrote: I'm working on a tcp load balancing tool for win32, later linux... what I need to figure out is the way it will be "intelligent".
Zero
I just had this idea (imagine that you have 2 servers) :
-Initially send half of the requests to each of the 2 redundant servers.
-For each request, measure its delay (response date minus request date)
-compute regularly the average delay for each server
-send more requests to the most responsive server

Example :
the average delay for the last 100 requests sent to server A is 10
the average delay for the last 100 requests sent to server B is 20
then send more requests to A. But not twice as much, otherwise you will have an oscillation phenomenon.

Anselme
zerospeed
Posts: 227
Joined: Nov 04, 2005 15:29

Post by zerospeed »

parakeet wrote:I just had this idea (imagine that you have 2 servers) :
-Initially send half of the requests to each of the 2 redundant servers.
-For each request, measure its delay (response date minus request date)
-compute regularly the average delay for each server
-send more requests to the most responsive server
Your idea sounds great! I'll left that implementation to the scripting instead of hard-coding into the engine.

But will let statistics get accessed from the script env so anyone could take the "balancing" choice.

Thank you for your comment... I said that like your 30k web server? (hehehe) :-)

Zero
steven522
Posts: 265
Joined: May 27, 2005 13:02
Location: Alabama, USA
Contact:

Post by steven522 »

OK. I finally got the it to compile and run properly. I had to re-install a clean copy of 0.15 and then apply the latest 0.16 and headers. I guess somewhere along the way, too many over-the-old-release installations had caused a problem.

...looks good.
thesanman112
Posts: 538
Joined: Jul 15, 2005 4:13

awesome

Post by thesanman112 »

Man i cant believe, I got to get my 98 laptop out and remeber how to write html, link to all my proggy's heheheh thats prety neat.
oyster
Posts: 274
Joined: Oct 11, 2005 10:46

Post by oyster »

svr.bas(193) : error 42: Variable not declared, FileHandle

FileHandle = freefile
^
svr.bas(395) : warning level 0: Passing pointer to scalar, at parameter 4 of INSERTMENU()
Mindless
Posts: 110
Joined: Jun 25, 2005 14:50
Location: USA

Post by Mindless »

changes for 0.16b compile

after

Code: Select all

   dim FileBuffer() as byte 'fix for fb0.16beta, thx v1ctor
   dim SendBuffer() as byte 'fix for fb0.16beta, thx v1ctor
add

Code: Select all

   dim FileHandle as ubyte
change

Code: Select all

if( not serverRun( ) ) then end
to

Code: Select all

if( serverRun( ) = FALSE ) then end
change

Code: Select all

         InsertMenu (MainMenu, 0, MF_POPUP, FileMenu, "invisible menu")
to

Code: Select all

         InsertMenu (MainMenu, 0, MF_POPUP, cuint(FileMenu), "invisible menu")
Tatjana
Posts: 4
Joined: Aug 15, 2008 5:13

Can't compile it :(

Post by Tatjana »

FreeBASIC Compiler - Version 0.20.0 (08-10-2008) for win32 (target:win32)
Copyright (C) 2004-2008 The FreeBASIC development team.

G:/LS/sws.bas(7) error 135: Only valid in -lang deprecated or fblite or qb, found 'Option' in 'Option Explicit'

G:/LS/sws.bas(201) error 4: Duplicated definition, FileBuffer in 'Dim FileBuffer(FileLength) As Byte'

G:/LS/sws.bas(217) error 4: Duplicated definition, SendBuffer in 'Dim SendBuffer(SendBufferLen) As Byte'

Some help please? Newbie on FB, but good on QB.

Thanks in advance

Tatjana
cha0s
Site Admin
Posts: 5319
Joined: May 27, 2005 6:42
Location: USA
Contact:

Re: Can't compile it :(

Post by cha0s »

Tatjana wrote:Some help please? Newbie on FB, but good on QB.
This should do the trick:

Code: Select all

'Simple Web Server, (c) Anselme Dewavrin 2006 - dewavrin@yahoo.com
'Feel free to use it, provided you mention my name.

'based on the example provided with freebasic.

#include once "win/winsock2.bi"
#include once "windows.bi"
#include once "win/shellapi.bi"

Const WM_SHELLNOTIFY  = WM_USER + 5
Const ID_RUN          = 1001
Const ID_PAUSE        = 1002
Const ID_EXIT         = 1003
Const SERVER_ADDR     = "localhost"
Const HOMEDIR              = "wwwroot"
Const DEFAULT_PORT    = 80
Const SERVER_BUFFSIZE = 16

Dim Shared note As NOTIFYICONDATA
Dim Shared TB_CREATED As Integer
Dim Shared szAppName As String
Dim Shared As Integer ServerPort = DEFAULT_PORT

'---

Type CLIENT
        socket                        As SOCKET
        ip                        As Integer
        port                        As Integer
        recvthread                As any ptr
        prev                        As CLIENT Ptr
        Next                        As CLIENT Ptr
End Type

Type SERVERCTX
        socket                        As SOCKET
        acceptthread                As any ptr
        isrunning                As Integer
        globmutex                As any ptr
        filemutex                As any ptr
        clientlisthead  As CLIENT Ptr
End Type


Dim Shared ctx As SERVERCTX


'multithreaded socket handling

'':::::
Sub serverDel( Byval client As CLIENT Ptr )
        Dim s As SOCKET

        '' not already removed?
        If( client->socket <> NULL ) Then
                s = NULL
                Swap s, client->socket        ' this should be atomic..

                '' close connection
        shutdown( s, 2 )
        closesocket( s )

                '' recv thread stills running?
                If( client->recvthread <> NULL ) Then
                        threadwait( client->recvthread )
                End If

                '' remove from list
                If( client->next ) Then
                        client->next->prev = client->prev
                End If
                If( client->prev ) Then
                        client->prev->next = client->next
                Else
                        ctx.clientlisthead = client->next
                End If
        End If

End Sub

'':::::
Function serverEnd( ) As Integer
        Dim client As CLIENT Ptr

        ctx.isrunning = FALSE

        '' close the listening socket
        If( ctx.socket <> 0 ) Then
        shutdown(ctx.socket, 2 )
        closesocket( ctx.socket )
                ctx.socket = 0
        End If

        '' remove all clients yet running
        Dim i As Integer

        Do
                client = ctx.clientlisthead
                If( client = NULL ) Then
                        Exit Do
                End If
                serverDel( client )
        Loop

        '' shutdown winsock
        Function = WSACleanup( )

End Function

'thread waiting for data to arrive, parsing HTTP GET requests and sending responses
Sub serverReceive( Byval client As CLIENT Ptr )

        Dim PacketBuffer(SERVER_BUFFSIZE) As Byte
        Dim As Integer  ReceivedLen = 0
        Dim As Byte Ptr ReceivedBuffer = 0
    Dim As String   stNL   = Chr(13) & Chr(10)
    Dim As String   stNLNL = stNL & stNL

   Dim FileBuffer() As Byte 'fix for fb0.16beta, thx v1ctor
   Dim SendBuffer() As Byte 'fix for fb0.16beta, thx v1ctor
   
   Dim FileHandle As Ubyte

        'receive loop
        Do While( ctx.isrunning And (client->socket <> NULL) )

                ' block until some data
        Dim bytes As Integer
                bytes = recv( client->socket, @PacketBuffer(0), SERVER_BUFFSIZE, 0 )

                ' connection closed?
                If( bytes <= 0 ) Then
                        Exit Do
                End If

        ' accumulate received data
        ReceivedBuffer = reallocate(ReceivedBuffer, bytes + ReceivedLen)
        Dim i As Integer
        For i=0 To bytes-1
            ReceivedBuffer[ReceivedLen+i] = PacketBuffer(i)
        Next i
        ReceivedLen += bytes

        'CRLF found twice ?
        If (ReceivedLen >= 4)                   And _
           (ReceivedBuffer[ReceivedLen-4] = 13) And _
           (ReceivedBuffer[ReceivedLen-3] = 10) And _
           (ReceivedBuffer[ReceivedLen-2] = 13) And _
           (ReceivedBuffer[ReceivedLen-1] = 10) Then

            'extract get path + url decoding (special chars are coded %XY)
            Dim As String stPath = HOMEDIR
            Dim As Integer iAcc = 0, iHex = 0
            For i = 4 To ReceivedLen-1
                Dim c As Byte
                c = ReceivedBuffer[i]
                If c = Asc(" ") Then Exit For
                If iHex <> 0 Then
                    iHex += 1   'decode hex code
                    iAcc *= 16
                    iAcc += (c-48)
                    If iHex = 3 Then
                        c = iAcc
                        iAcc = 0
                        iHex = 0
                    Endif
                Endif
                If c=Asc("%") Then 'hex code coming ?
                    iHex = 1
                    iAcc = 0
                Endif
                If iHex = 0 Then stPath += Chr(c)
            Next i

            If (stPath = HOMEDIR + "/") Or _  'default page and
               (Instr(stPath,"..") <> 0) Then 'restrict to wwwroot
                stPath = HOMEDIR + "/index.html"
            End If

            'get rid of received data
            ReceivedLen = 0
            Deallocate(ReceivedBuffer)

            'prepare response
            Dim As String  stReponseHeader
            Dim As Integer FileLength = 0

            'read requested file from disk (no mutex, thanx to Zerospeed)
            If dir(stPath) = "" Then
                stReponseHeader = "HTTP/1.1 404 Not Found" & stNL & stNL
            Else
                FileHandle = Freefile
                If Open( stPath For Binary Access Read Shared As #FileHandle ) <> 0 Then
                    stReponseHeader = "HTTP/1.1 403 Forbbiden" & stNL & stNL
                Else
                    FileLength = Lof(FileHandle)    'file len
                    If FileLength <> 0 Then
                        reDim FileBuffer(FileLength) As Byte
                        Get #FileHandle, , FileBuffer()
                    End If
                    Close #FileHandle

                    stReponseHeader = "HTTP/1.1 200 OK" & stNL
                    stReponseHeader += "Cache-Control: private" & stNL
                    stReponseHeader += "content-length : " & Str(FileLength) & stNL & stNL
                End If
            End If

            'copy response header to sendbuffer
            Dim HeaderLength As Integer
            HeaderLength = Len(stReponseHeader)

            Dim As Integer SendBufferLen = HeaderLength + FileLength
            reDim SendBuffer(SendBufferLen) As Byte

            'copy loop (thx v1ctor for this simplified version)
            For i = 0 To HeaderLength-1
                SendBuffer(i) = stReponseHeader[i]
            Next i

            'copy response data to sendbuffer
            If FileLength <> 0 Then
                For i = 0 To FileLength-1
                    SendBuffer(i+HeaderLength) = FileBuffer(i)
                Next i
            End If

            'send response
            Dim As Byte Ptr sendptr
            sendptr = @sendBuffer(0)
            Do While (ctx.isrunning And (client->socket <> NULL) And (SendBufferLen > 0))

                ' loop until the whole buffer is sent
                bytes = send( client->socket, sendptr, SendBufferLen, 0 )

                '' connection closed?
                If( bytes <= 0 ) Then
                    Exit Do
                End If

                sendptr       += bytes
                SendBufferLen -= bytes
            Loop 'send loop

            ' remove client
            client->recvthread = NULL
            serverDel( client )

        End If 'bFound

    Loop 'receive loop

        ' remove client
        client->recvthread = NULL
        serverDel( client )

End Sub


Sub serverAccept( Byval unused As any ptr )
        Dim sa As sockaddr_in
        Dim s As SOCKET

        Do While( ctx.isrunning )

        Dim salen As Integer
        salen = Len( sockaddr_in )
                s = accept( ctx.socket, cptr( PSOCKADDR, @sa ), @salen )
                If( s = INVALID_SOCKET ) Then
                        Exit Do
                End If

        Dim client As CLIENT Ptr
       
        '' access global data, lock it
        mutexlock( ctx.globmutex )
       
        '' allocate node
        client = allocate( Len( CLIENT ) )
       
        '' add to head of list
        client->next = ctx.clientlisthead
        ctx.clientlisthead = client
        If client->next Then client->next->prev = client
        client->prev = NULL

        mutexunlock( ctx.globmutex )
       
        '' setup the client
        client->socket                                 = s
        client->ip                                        = (@sa)->sin_addr.S_addr
        client->port                                = (@sa)->sin_port
   
        '' start new recv and send threads
        client->recvthread                         = threadcreate( cast( sub( byval as any ptr ), @serverReceive ), client )

        Loop

        ctx.isrunning = FALSE
End Sub


Function serverRun( ) As Integer
        ' start winsock
        Dim wsaData As WSAData
        If( WSAStartup( MAKEWORD( 2, 0 ), @wsaData ) <> 0 ) Then
                'print "error calling WSAStartup: "; WSAGetLastError( )
                Return FALSE
        End If

        If( wsaData.wVersion <> MAKEWORD( 2, 0 ) ) Then
                WSACleanup( )       
                Return FALSE
        End If

        ' create a socket for listening
        ctx.socket = opensocket( AF_INET, SOCK_STREAM, IPPROTO_TCP )
   
        If( ctx.socket = NULL ) Then
                'print "error calling opensocket: "; WSAGetLastError( )
                Return FALSE
        End If

        ' bind it to the server port
        Dim sa As sockaddr_in
    sa.sin_port                        = htons( ServerPort )
        sa.sin_family                = AF_INET
        sa.sin_addr.S_addr        = INADDR_ANY
    If(bind( ctx.socket, cptr( PSOCKADDR, @sa ), Len( sa ) ) = SOCKET_ERROR ) Then
                'print "error calling bind: "; WSAGetLastError( )
                Return FALSE
        End If       

    If( listen( ctx.socket, SOMAXCONN ) = SOCKET_ERROR ) Then
                Return FALSE
        End If

        ctx.clientlisthead = NULL
        ctx.isrunning = TRUE

        ctx.globmutex = mutexcreate( )
        ctx.filemutex = mutexcreate( )

        ctx.acceptthread = threadcreate( @serverAccept ) 'launch accept thread
       
        Function = TRUE
End Function

'----------- TRAY ICON HANDLING --------------

Function WndProc ( Byval hWnd As HWND, Byval message As UINT, Byval wParam As WPARAM, Byval lParam As LPARAM ) As LRESULT

   Static pt As Point
   Function = 0

   Select Case (message)
   Case WM_CREATE
      TB_CREATED = RegisterWindowMessage ("TaskbarCreated")
      Exit Function

   Case WM_DESTROY
      UnregisterClass (szAppName, GetModuleHandle( null ))
      Shell_NotifyIcon (NIM_DELETE, @note)
      PostQuitMessage( 0 )
      Exit Function

   Case WM_COMMAND
      If LOWORD (wParam)= ID_RUN Then
           If ctx.isrunning = FALSE Then serverRun( ) 'Listenning on 80
      End If
      If LOWORD (wParam)= ID_PAUSE Then
            If         ctx.isrunning = TRUE Then serverEnd( ) 'pause
      End If
      If LOWORD (wParam) = ID_EXIT Then
         DestroyWindow (hWnd)
      End If

   Case WM_SHELLNOTIFY
      If (lParam = WM_RBUTTONDOWN) Or (lParam = WM_LBUTTONDOWN) Then
         GetCursorPos (@pt)
         SetForegroundWindow (hWnd)
         Dim MainMenu As HANDLE
         Dim FileMenu As HANDLE
         MainMenu = CreateMenu ()
         FileMenu = CreateMenu ()
         If ctx.isrunning = TRUE Then
            AppendMenu (FileMenu, MF_STRING Or MF_CHECKED Or MF_GRAYED, ID_RUN, "&Run")
            AppendMenu (FileMenu, MF_STRING, ID_PAUSE, "&Pause")
         Else
            AppendMenu (FileMenu, MF_STRING, ID_RUN, "&Run")
            AppendMenu (FileMenu, MF_STRING Or MF_CHECKED Or MF_GRAYED, ID_PAUSE, "&Pause")
         End If
         AppendMenu (FileMenu, MF_STRING, ID_EXIT, "E&xit")
         InsertMenu (MainMenu, 0, MF_POPUP, cuint(FileMenu), "invisible menu")

         TrackPopupMenuEx (FileMenu, TPM_LEFTALIGN Or TPM_RIGHTBUTTON, pt.x, pt.y, hWnd, NULL)

         PostMessage (hWnd, WM_NULL, 0, 0)
      End If

   Case TB_CREATED
      Shell_NotifyIcon (NIM_ADD, @note)

   End Select

   Function = DefWindowProc( hWnd, message, wParam, lParam )
End Function

'---------------- SIMILI-WINMAIN ----------------

Dim hInstance As HINSTANCE
hInstance = GetModuleHandle( null )

If Command$ <> "" Then
    ServerPort = Val(Command$)
Endif

If( FALSE = serverRun( ) ) Then End

Dim wMsg As MSG
Dim wcls As WNDCLASS
Dim hWnd As HWND

szAppName = "SWS"

'already running ?
hWnd=FindWindow(szAppName,NULL)
If hWnd <> 0 Then
    End
end If

With wcls
  .style = CS_HREDRAW Or CS_VREDRAW
  .lpfnWndProc = @WndProc
  .cbClsExtra = 0
  .cbWndExtra = 0
  .hInstance  = hInstance
  .hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (1))
  .hCursor = LoadCursor( NULL, IDC_ARROW )
  .hbrBackground = GetStockObject( WHITE_BRUSH )
  .lpszMenuName = NULL
  .lpszClassName = Strptr( szAppName )
End With

If( RegisterClass( @wcls ) = FALSE ) Then
    End
end If

'' Create the window and _BUT DONT_ show it
hWnd = CreateWindowEx( 0, szAppName, "", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL )

note.cbSize = sizeof (NOTIFYICONDATA)
note.hWnd = hWnd
note.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (1))
note.uFlags = NIF_ICON Or NIF_TIP Or NIF_MESSAGE
note.uCallbackMessage = WM_SHELLNOTIFY
note.szTip= szAppName
Shell_NotifyIcon (NIM_ADD, @note)

'wait for quit message
While GetMessage( @wMsg, NULL, 0, 0 ) <> FALSE
    TranslateMessage( @wMsg )
    DispatchMessage( @wMsg )
Wend

'eventually stop server
If ctx.isrunning = TRUE Then
    serverEnd( )
End If
 
Tatjana
Posts: 4
Joined: Aug 15, 2008 5:13

thank you! no errors now, but nothing happens....

Post by Tatjana »

You are an angel :)

but I get no icon and the process is not running on task manager.
maybe it quits too soon for some reason I don't know....

but thanks a lot for your help!


Tatjana
cha0s
Site Admin
Posts: 5319
Joined: May 27, 2005 6:42
Location: USA
Contact:

Re: thank you! no errors now, but nothing happens....

Post by cha0s »

Tatjana wrote:You are an angel :)

but I get no icon and the process is not running on task manager.
maybe it quits too soon for some reason I don't know....

but thanks a lot for your help!


Tatjana
Remember to read the instructions in the first post ;)
parakeet wrote:Instructions to compile:
-copy the code below into a sws.bas file
-create a sws.rc file containing one line : 1 ICON "icon.ico"
-find a .ico file on your hard disk or draw it yourself, and save it as "icon.ico" near the .bas file.
-Compile the code with "fbc sws.rc sws.bas -s gui"

Instructions to run:
-place the resulting sws.exe near a wwwroot directory in which you have put your web-site (html pages and images, default page is "index.html")
-run the sws.exe, your icon appears in the tray.

Instruction to test:
-run your web browser
-type localhost as the URL. You should see your web-site.
I haven't tested it, but if you try all those steps and it still doesn't work, feel free to post back.
Tatjana
Posts: 4
Joined: Aug 15, 2008 5:13

Still not working.... doesn't compile.

Post by Tatjana »

C:\Programas\FreeBASIC>fbc sws.rc sws.bas -s gui

Error!
No resources found in RC file
OBJ file not made

C:\Programas\FreeBASIC>

And I am sure I followed all instructions, and sws.rc is correct:

C:\Programas\FreeBASIC>type sws.rc
1 ICON "icon.ico"
C:\Programas\FreeBASIC>
also, the icon.ico is on same folder.

But the compiler doesn't create the .exe file.

Thank you for all your help

Tatjana
cha0s
Site Admin
Posts: 5319
Joined: May 27, 2005 6:42
Location: USA
Contact:

Post by cha0s »

I dunno. Try this, it includes the source, an rc file, an icon, and a batch file to compile it.

http://therealcha0s.net/tmp/sws.zip
Tatjana
Posts: 4
Joined: Aug 15, 2008 5:13

It worked!!!

Post by Tatjana »

Thank you a lot! :)

Of course, I had to know WHY :)

I snooped into the batch file... fbc sws.bas sws.rc -s gui

NOT fbc sws.rc sws.bas -s gui

That was my mistake :)

Thanks a lot!


:)
Tat !
Post Reply