Simple HTTP Server crashes on linux/mac on stress test

Just starting out? Need help? Post your questions and find answers here.
User avatar
skinkairewalker
Addict
Addict
Posts: 824
Joined: Fri Dec 04, 2015 9:26 pm

Simple HTTP Server crashes on linux/mac on stress test

Post by skinkairewalker »

I’m sharing a simple HTTP server I wrote in PureBasic. The server is very minimal, based on CreateNetworkServer() and a loop that continuously calls NetworkServerEvent() to accept connections, spawning a new thread to handle each request. On Windows, the server behaves correctly and remains stable even under stress testing with many simultaneous connections. However, on Linux and macOS, running the same stress test causes the application to crash after some time for an unknown reason. The crash seems to happen around the call to NetEvent = NetworkServerEvent(), resulting in a silent termination or, in some cases, a segmentation fault. I’ve verified that connections are being closed properly and that the number of threads is limited, but the issue still occurs only on Linux and macOS, not on Windows. I’m trying to understand whether this is a platform-specific limitation, a threading issue, or a known problem with NetworkServerEvent() on these systems.

Threaded version (non-professional)

simplehttpwebview.pb

Code: Select all


EnableExplicit

#PORT = 8080
#WWW = "www/"

Global event

Structure ClientCtx
  id.i
  request.s
EndStructure

;-----------------------------------------
Procedure.s GetMimeType(file.s)
  Select LCase(GetExtensionPart(file))
    Case "html","htm" : ProcedureReturn "text/html"
    Case "js"         : ProcedureReturn "application/javascript"
    Case "css"        : ProcedureReturn "text/css"
    Case "png"        : ProcedureReturn "image/png"
    Case "jpg","jpeg" : ProcedureReturn "image/jpeg"
    Case "gif"        : ProcedureReturn "image/gif"
  EndSelect
  ProcedureReturn "application/octet-stream"
EndProcedure

;-----------------------------------------

Procedure HandleClient(*ctx.ClientCtx);HandleClient(client)
  
  Protected client = *ctx\id
  Protected request.s = *ctx\request
  
  Debug "cliente = "+Str(client)
  ;Debug "request = "+request
  
  Protected *req, *buf
  Protected path.s, file.s
  Protected size.q, sent, bytesRead, n.q
  Protected fn, mime.s
  Protected header.s
  Protected TotalEnviado.q = 0
  Protected fileSize.q


  path = StringField(StringField(request, 1, #CRLF$), 2, " ")
  
  If (FindString(path,"?"))
    path = StringField(path, 1, "?")
  EndIf  
  
  If path = "/" : path = "/index.html" : EndIf
  Debug "PATH === "+path
  file = #WWW + Mid(path, 2)

  If FileSize(file) >= 0
    fn = ReadFile(#PB_Any, file)
    size = Lof(fn)
    mime = GetMimeType(file)

    header = "HTTP/1.1 200 OK" + #CRLF$ +
             "Content-Type: " + mime + #CRLF$ +
             "Content-Length: " + Str(size) + #CRLF$ +
             "Connection: close" + #CRLF$ +
             #CRLF$
    
    SendNetworkString(client, header, #PB_UTF8)

    ; ===== STREAMING =====
    ;*buf = AllocateMemory(16384)
    
    *buf = AllocateMemory(16384)
    fileSize = Lof(fn)
    
    While TotalEnviado < fileSize And Not Eof(fn)
      
      bytesRead = ReadData(fn, *buf, 16384 )
      If bytesRead <= 0
        Break
      EndIf
      
      sent = 0
      While sent < bytesRead
        n = SendNetworkData(client, *buf + sent, bytesRead - sent)
        
        If n > 0
          sent + n
          TotalEnviado + n
          
        ElseIf n = -1
          ; Socket ocupado, espera um pouco
          Delay(1)
          
        Else
          Debug "Cliente fechou a conexão"
          Break 2
        EndIf
      Wend
      
    ;  Debug "Chunk enviado: " + Str(sent)
     ; Debug "Total enviado: " + Str(TotalEnviado) + " / " + Str(fileSize)
      
    Wend
    
    FreeMemory(*buf)
    CloseFile(fn)
    
    Debug "ENVIO FINALIZADO: " + Str(TotalEnviado)

  Else
    header = "HTTP/1.1 404 Not Found" + #CRLF$ +
             "Content-Length: 13" + #CRLF$ +
             "Connection: close" + #CRLF$ +
             #CRLF$ +
             "404 Not Found"
    SendNetworkString(client, header, #PB_UTF8)
  EndIf

  ;Debug "Conexão Encerrada"
  ;CloseNetworkConnection(client)
  FreeStructure(*ctx)
EndProcedure


Procedure ServerThread(void) 

;-----------------------------------------
Protected NetEvent, client

Repeat
  NetEvent = NetworkServerEvent()
  If NetEvent
    client = EventClient()
    If NetEvent = #PB_NetworkEvent_Data
      ;HandleClient(client)
        ;Debug "################# DADOS recebidos ######################"
        Protected *ctx.ClientCtx = AllocateStructure(ClientCtx)
        Protected *buffer = AllocateMemory(4096)
        Protected result
        
        *ctx\id = client
        
        Repeat
          FillMemory(*buffer, 4096, 0)
          result = ReceiveNetworkData(client, *buffer, 4096)
          If result > 0
            *ctx\request + PeekS(*buffer, result, #PB_UTF8)
          EndIf
        Until result < 4096
        
        FreeMemory(*buffer)
        
        ; AGORA o socket está “isolado”
 
        CreateThread(@HandleClient(), *ctx)
        
      
    ElseIf NetEvent = #PB_NetworkEvent_Disconnect
     ; Debug "DESCONECTADO DO SERVIDOR"
    EndIf
  Else
    Delay(1)  ; Ne pas saturer le CPU / Don't stole the whole CPU !
  EndIf
ForEver


EndProcedure

If CreateNetworkServer(0, #PORT,#PB_Network_TCP|#PB_Network_IPv4|#PB_Network_IPv6)
  Debug "Servidor em http://localhost:" + Str(#PORT)
  
  CreateThread(@ServerThread(),0) 
  
  If OpenWindow(0, 0, 0, 800, 600, "AppComposer", #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_MaximizeGadget | #PB_Window_MinimizeGadget)
    If CreateMenu(0, WindowID(0))  ; Create a regular menu with title and one item
      MenuTitle("File")
      MenuItem(1, "Reload" + #TAB$ + "Ctrl+O")
    EndIf
    
    WebViewGadget(0, 0, 0, 800, 600)
    SetGadgetText(0, "http://localhos:8080")
    
    
    HideWindow(0, #False)
    
    Repeat 
      event = WaitWindowEvent()
      Select event 
        Case #PB_Event_Menu
          Select EventMenu()
            Case 1
              SetGadgetText(0, "http://localhost:8080")
          EndSelect  
        Case #PB_Event_CloseWindow
          Break 
      EndSelect   
    ForEver  
  EndIf
    
Else
  End
EndIf

stress_test.pb

Code: Select all

Global port = 8080

#Loadtest=01

Global semtest = CreateSemaphore() 

Procedure _loadLest(amount) 
  
  Protected get.s, rec.s,con,a,try=1  
  
  rec = "Host: 192.168.3.91:" + Str(port) + #CRLF$
  rec + "Connection: keep-alive" + #CRLF$
  rec + "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0" + #CRLF$
  rec + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" + #CRLF$
  rec + "Sec-Fetch-Site: none" + #CRLF$
  rec + "Sec-Fetch-Mode: navigate" + #CRLF$
  rec + "Sec-Fetch-User: ?1" + #CRLF$
  rec + "Sec-Fetch-Dest: document" + #CRLF$
  rec + "Accept-Encoding: gzip, deflate, br" + #CRLF$
  rec + "Accept-Language: en-US,en;q=0.9" + #CRLF$ + #CRLF$
  
  WaitSemaphore(semtest) 
  
  Repeat
    con = OpenNetworkConnection("192.168.3.91",port,#PB_Network_TCP | #PB_Network_IPv4,1000)  
    If con 
      get = "GET / HTTP/1.1" + #CRLF$
      SendNetworkString(Con,get+rec)
      Delay(100)
      get = "GET /favicon.ico HTTP/1.1" + #CRLF$ 
      SendNetworkString(Con,get+rec)
      Debug "Sent on try " + Str(try) 
      Break 
    Else 
      Debug "can't connect retry " + Str(try) 
      Delay(10) 
      try+1
    EndIf 
  Until try > 5 
  If try > 5 
    Debug "couldn't connect" 
  EndIf 
  
EndProcedure 

Procedure loadTest(amount)
  Protected  a 
  
  Dim threads(amount) 
  
  For a = 1 To amount
    threads(a) = CreateThread(@_loadLest(),0) 
  Next 
  
  Delay(1000) 
  Debug "signal" 
  
  For a = 1 To amount 
    SignalSemaphore(semtest)
    Delay(1)
  Next 
  
  For a = 1 To amount 
    If IsThread(threads(a))
      WaitThread(threads(a),1000)
    EndIf   
  Next 
  
  Debug "done test" 
  
EndProcedure 


loadTest(10000)
User avatar
mk-soft
Always Here
Always Here
Posts: 6471
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Simple HTTP Server crashes on linux/mac on stress test

Post by mk-soft »

ThreadSafe executable active?
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
skinkairewalker
Addict
Addict
Posts: 824
Joined: Fri Dec 04, 2015 9:26 pm

Re: Simple HTTP Server crashes on linux/mac on stress test

Post by skinkairewalker »

mk-soft wrote: Tue Dec 23, 2025 1:26 am ThreadSafe executable active?
yep
User avatar
mk-soft
Always Here
Always Here
Posts: 6471
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Simple HTTP Server crashes on linux/mac on stress test

Post by mk-soft »

You should create a map list (ConncectionID) per client with a structure Size, Data Buffer.
After ReceiveNetworkData, read the size from the header, take over the data and wait for the next #PB_NetworkEvent_Data to receive the current or the next client.
Only when after reaching size data call the client thread.

If SendNetworkData is missing, you must cancel. Or read the NetworkError and then decide whether the sending buffer is full or the connection has been closed.
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
infratec
Always Here
Always Here
Posts: 7766
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Simple HTTP Server crashes on linux/mac on stress test

Post by infratec »

Also you have a bug in:

Code: Select all

*ctx\request + PeekS(*buffer, result, #PB_UTF8)
It has to be:

Code: Select all

*ctx\request + PeekS(*buffer, result, #PB_UTF8|#PB_ByteLength)
User avatar
skinkairewalker
Addict
Addict
Posts: 824
Joined: Fri Dec 04, 2015 9:26 pm

Re: Simple HTTP Server crashes on linux/mac on stress test

Post by skinkairewalker »

infratec wrote: Tue Dec 23, 2025 10:57 am Also you have a bug in:

Code: Select all

*ctx\request + PeekS(*buffer, result, #PB_UTF8)
It has to be:

Code: Select all

*ctx\request + PeekS(*buffer, result, #PB_UTF8|#PB_ByteLength)
fixed xD, thanks
User avatar
skinkairewalker
Addict
Addict
Posts: 824
Joined: Fri Dec 04, 2015 9:26 pm

Re: Simple HTTP Server crashes on linux/mac on stress test

Post by skinkairewalker »

mk-soft wrote: Tue Dec 23, 2025 9:51 am You should create a map list (ConncectionID) per client with a structure Size, Data Buffer.
After ReceiveNetworkData, read the size from the header, take over the data and wait for the next #PB_NetworkEvent_Data to receive the current or the next client.
Only when after reaching size data call the client thread.

If SendNetworkData is missing, you must cancel. Or read the NetworkError and then decide whether the sending buffer is full or the connection has been closed.
I tried to do it here, but with the little knowledge I have I ended up breaking the code xD. Is that the only way to solve the problem? I even asked ChatGPT for help, but it couldn’t help me either.
infratec
Always Here
Always Here
Posts: 7766
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Simple HTTP Server crashes on linux/mac on stress test

Post by infratec »

You can try this:

Code: Select all

;
; as stress test use curl >= 8.16 https://curl.se/download.html
;
; curl -6 --parallel --parallel-immediate --parallel-max-host 1000 -o nul "http://localhost:8080/portmon.exe?[1-1000]"
; curl -4 -k --parallel --parallel-immediate --parallel-max-host 1000 -o nul "https://localhost:8443/portmon.exe?[1-1000]"
;
; linux/macOS: use -o /dev/null instead of -o nul
;

CompilerIf Not #PB_Compiler_Thread
  CompilerError "You have to enable thread safe in compiler options"
CompilerEndIf

EnableExplicit

#HTTP_PORT = 8080
#HTTPS_PORT = 8443
#WWW = "www"


Structure WebServerClient_Structure
  Client.i
  Thread.i
  *Buffer
  Size.i
  LastSeen.i
  Disconnected.i
  RootPath$
EndStructure


Structure WebServer_Structure
  Thread.i
  Port.i
  ServerFlags.i
  PrivateKey$
  Certificate$
  CACertificate$
  Server.i
  RootPath$
  Map ClientMap.WebServerClient_Structure()
  Exit.i
EndStructure




Procedure.s GetMimeType(Filename$)
  
  Protected Mime$
  
  
  Select LCase(GetExtensionPart(Filename$))
    Case "html","htm" : Mime$ = "text/html"
    Case "js"         : Mime$ = "application/javascript"
    Case "css"        : Mime$ = "text/css"
    Case "png"        : Mime$ = "image/png"
    Case "jpg","jpeg" : Mime$ = "image/jpeg"
    Case "gif"        : Mime$ = "image/gif"
    Case "ico"        : Mime$ = "image/vnd.microsoft.icon"
    Default
      Mime$ = "application/octet-stream"
  EndSelect
  
  ProcedureReturn Mime$
  
EndProcedure




Procedure WebServerClientThread(*Parameter.WebServerClient_Structure)
  
  Protected.i File, Pos1, Pos2, Chunk, Res, Timeout
  Protected *Buffer
  Protected.q Size
  Protected Received$, Header$, Type$, Parameter$, Filename$, Mime$
  
  
  Debug "ClientThread " + Str(*Parameter\Client)
  
  Received$ = PeekS(*Parameter\Buffer, *Parameter\Size, #PB_UTF8|#PB_ByteLength)
  Header$ = StringField(Received$, 1, #CRLF$ + #CRLF$)
  
  ;Debug Str(*Parameter\Client) + #LF$ + Header$
  
  Pos1 = FindString(Header$, " ")
  Type$ = Left(Header$, Pos1 - 1)
  Pos1 + 1
  Pos2 = FindString(Header$, "HTTP", Pos1)
  Pos2 - 1
  Parameter$ = Mid(Header$, Pos1, Pos2 - Pos1)
  
  Filename$ = StringField(Parameter$, 1, "?")
  Parameter$ = StringField(Parameter$, 2, "?")
  If Filename$ = "/"
    Filename$ = "/index.html"
  EndIf
  Filename$ = *Parameter\RootPath$ + Filename$
  Mime$ = GetMimeType(Filename$)
  
  Size = FileSize(Filename$)
  
  Debug Filename$ + " " + Str(Size) + " " + Str(*Parameter\Client)
  
  If Size > 0
    Header$ = "HTTP/1.1 200 OK" + #CRLF$
    Header$ + "Content-Type: " + Mime$ + #CRLF$
    Header$ + "Content-Length: " + Str(Size) + #CRLF$
    Header$ + "Connection: close" + #CRLF$
    Header$ +  #CRLF$
    
    ;Debug Str(*Parameter\Client) + #LF$ + Header$
    
    Timeout = 1000
    Repeat
      If Not *Parameter\Disconnected
        Res = SendNetworkString(*Parameter\Client, Header$, #PB_UTF8)
        If Res < StringByteLength(Header$, #PB_UTF8)
          Debug "SendNetworkString failed " + Str(*Parameter\Client)
        EndIf
      EndIf
      Timeout - 1
    Until Res > 0 Or *Parameter\Disconnected Or Timeout = 0
    
    If Not *Parameter\Disconnected And Timeout > 0
      *Buffer = AllocateMemory(16348, #PB_Memory_NoClear)
      If *Buffer
        File = ReadFile(#PB_Any, Filename$, #PB_File_SharedRead)
        If File
          While Not Eof(File) And Not *Parameter\Disconnected
            Chunk = ReadData(File, *Buffer, MemorySize(*Buffer))
            If Chunk > 0
              Timeout = 1000
              Repeat
                Res = SendNetworkData(*Parameter\Client, *Buffer, Chunk)
                If Res < Chunk
                  Debug "SendNetworkData failed " + Str(Res) + " " + Str(*Parameter\Client)
                EndIf
                Timeout - 1
                If Timeout = 0
                  Debug "Timeout"
                  Break 2
                EndIf
              Until Res = Chunk Or *Parameter\Disconnected
            EndIf
          Wend
          CloseFile(File)
        Else
          Debug "File not open " + Str(*Parameter\Client)
        EndIf
        FreeMemory(*Buffer)
      Else
        Debug "No file buffer " + Str(*Parameter\Client)
      EndIf
      
    EndIf
    
  Else
    
    Header$ = "HTTP/1.1 404 Not Found" + #CRLF$
    Header$ + "Content-Length: 13" + #CRLF$
    Header$ + "Connection: close" + #CRLF$
    Header$ + #CRLF$
    Header$ + "404 Not Found"
    If Not *Parameter\Disconnected
      SendNetworkString(*Parameter\Client, Header$, #PB_UTF8)
    EndIf
    
  EndIf
  
  *Parameter\Thread = 0
  
EndProcedure




Procedure WebServerThread(*Parameter.WebServer_Structure)
  
  Protected.i NetEvent, Client, Offset, Size
  Protected Client$
  
  
  If *Parameter\ServerFlags & (#PB_Network_TLSv1 | #PB_Network_TLSv1_0 | #PB_Network_TLSv1_1 | #PB_Network_TLSv1_2 | #PB_Network_TLSv1_3)
    If *Parameter\ServerFlags & #PB_Network_IPv6
      Debug "HTTPS IPv6 on port " + Str(*Parameter\Port)
    Else
      Debug "HTTPS IPv4 on port " + Str(*Parameter\Port)
    EndIf
    If *Parameter\CACertificate$ <> ""
      UseNetworkTLS(*Parameter\PrivateKey$, *Parameter\Certificate$, *Parameter\CACertificate$)
    Else
      UseNetworkTLS(*Parameter\PrivateKey$, *Parameter\Certificate$)
    EndIf
  Else
    If *Parameter\ServerFlags & #PB_Network_IPv6
      Debug "HTTP IPv6 on port " + Str(*Parameter\Port)
    Else
      Debug "HTTP IPv4 on port " + Str(*Parameter\Port)
    EndIf
  EndIf
  
  *Parameter\Server = CreateNetworkServer(#PB_Any, *Parameter\Port, *Parameter\ServerFlags)
  If *Parameter\Server
    
    *Parameter\RootPath$ = RTrim(*Parameter\RootPath$, "/")
    
    Repeat
      
      Select NetworkServerEvent(*Parameter\Server)
        Case #PB_NetworkEvent_Connect
          Client = EventClient()
          Client$ = Str(Client)
          Debug "Connect " + Client$
          If Not FindMapElement(*Parameter\ClientMap(), Client$)
            AddMapElement(*Parameter\ClientMap(), Client$)
            *Parameter\ClientMap()\Buffer = AllocateMemory(4096, #PB_Memory_NoClear)
            *Parameter\ClientMap()\Client = Client
            *Parameter\ClientMap()\RootPath$ = *Parameter\RootPath$
          EndIf
          *Parameter\ClientMap()\Disconnected = #False
          *Parameter\ClientMap()\LastSeen = Date()
          If Not *Parameter\ClientMap()\Buffer
            Debug "Memory allocation for new client not possible"
            DeleteMapElement(*Parameter\ClientMap())
          EndIf
          
        Case #PB_NetworkEvent_Data
          Client = EventClient()
          Client$ = Str(Client)
          Debug "Data " + Client$
          If FindMapElement(*Parameter\ClientMap(), Client$)
            *Parameter\ClientMap()\LastSeen = Date()
            Offset = 0
            Repeat
              Size = ReceiveNetworkData(Client, *Parameter\ClientMap()\Buffer + Offset, MemorySize(*Parameter\ClientMap()\Buffer))
              If Size < 0
                If Size = -1
                  DeleteMapElement(*Parameter\ClientMap())
                EndIf
                Break
              Else
                If Size = MemorySize(*Parameter\ClientMap()\Buffer)
                  Offset = MemorySize(*Parameter\ClientMap()\Buffer)
                  ReAllocateMemory(*Parameter\ClientMap()\Buffer, Offset + 4096, #PB_Memory_NoClear)
                  Size = 0
                Else
                  Size = Offset + Size
                  If Size > 0
                    *Parameter\ClientMap()\Size = Size
                    *Parameter\ClientMap()\Thread = CreateThread(@WebserverClientThread(), @*Parameter\ClientMap())
                  Else
                    Break
                  EndIf
                EndIf
              EndIf
            Until Size > 0
          EndIf
          
        Case #PB_NetworkEvent_Disconnect
          Client = EventClient()
          Client$ = Str(Client)
          If FindMapElement(*Parameter\ClientMap(), Client$)
            Debug "Delete " + MapKey(*Parameter\ClientMap()) + " by disconnect"
            *Parameter\ClientMap()\Disconnected = #True
          EndIf
          
        Case #PB_NetworkEvent_None
          Delay(10)  ; Ne pas saturer le CPU / Don't stole the whole CPU !
          ForEach *Parameter\ClientMap()
            If *Parameter\ClientMap()\LastSeen + 60 < Date()
              If Not *Parameter\ClientMap()\Disconnected
                Debug "Delete " + MapKey(*Parameter\ClientMap()) + " by timeout"
              EndIf
              FreeMemory(*Parameter\ClientMap()\Buffer)
              DeleteMapElement(*Parameter\ClientMap())
            EndIf
          Next
          
      EndSelect
      
    Until *Parameter\Exit
    
    While MapSize(*Parameter\ClientMap())
      ForEach *Parameter\ClientMap()
        *Parameter\ClientMap()\Disconnected = #True
        If *Parameter\ClientMap()\Thread = 0
          FreeMemory(*Parameter\ClientMap()\Buffer)
          DeleteMapElement(*Parameter\ClientMap())
        EndIf
      Next
    Wend
    
    CloseNetworkServer(*Parameter\Server)
    *Parameter\Server = 0
    
  Else
    Debug "Was not able to start server " + Str(*Parameter\Port)
  EndIf
  
EndProcedure



;-Main
Define.i Event, Exit
Define NewList WebServerList.WebServer_Structure()


AddElement(WebServerList())
WebServerList()\Port = #HTTP_Port
WebServerList()\RootPath$ = GetPathPart(ProgramFilename()) + #WWW
WebServerList()\ServerFlags = #PB_Network_TCP|#PB_Network_IPv4|#PB_Network_NoTLS

AddElement(WebServerList())
WebServerList()\Port = #HTTP_Port
WebServerList()\RootPath$ = GetPathPart(ProgramFilename()) + #WWW
WebServerList()\ServerFlags = #PB_Network_TCP|#PB_Network_IPv6|#PB_Network_NoTLS

AddElement(WebServerList())
WebServerList()\Port = #HTTPS_Port
WebServerList()\RootPath$ = GetPathPart(ProgramFilename()) + #WWW
WebServerList()\ServerFlags = #PB_Network_TCP|#PB_Network_IPv4|#PB_Network_TLSv1
WebServerList()\PrivateKey$ = "-----BEGIN PRIVATE KEY-----" + #LF$ +
                              "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAPEbSQq/uwESZduPtDd83qXXkSPf6lUNa17xhM2fOZQxGr0Fdmvw6IsC+QGX25EE1TG6TFQkHlM2rW8y6a3WEC/WzCNWaTCPYD/rguiAFG+4eQmwHjiJFVec0InjjSG9SX8xwS/gQeWdQniKROO4DmMJO8N7mdUhdHODSntXdr9zAgMBAAECgYAE+VMgbaQl+YMwbF6DZogRU8kivFPRPV2hr8nVlBtT+09Z5uryfx3NAFqytbdJ3penVviMI9KcVNxvFtXLSEc9KyjzgysorAfUpwFuECCLDbOXX0HlV6rgkqJdhyV6FybcDLvgcvulHQ64QdYRhW+jPx7vXk3h0/JRFqKQJsY7QQJBAPrDLJRPbAw+Mlq1fHBWk8Z1Qn1ivPAmz+2nPAgDya/xdAlb9GbFAMzCS3upIBpxW70uLI04OuTVhwYL194I5C0CQQD2JHtHp25SkIDpBgZGicEC7yAIE/wPC0P9X85UJqXx5dPx4HbEc8lqSKMbCzkbHyvjHonSHu00QxU1W6ZALFYfAkBcPWzphSl+e2Z0XWvPutkS2FFD5A0R3YUAq1J2tEX9NTj0tGF7aB36M8ImU7jeYTJYrWJv8+4d/Ll1LOgT4XtlAkAxofOV5EYTsf28fzF+wcJAtDUyS81Uv0HLcqkpQM3PdDeDm253eJ2Rp+nzxxSRynxQBNVnoELWefxp0Pw6DnajAkBF5h7fQIbwAEPrhDzhjMXU7g9k9KzkkJN/bluLbleqkkAz1kfkGtWXJdGITZuY4K/X2yp1diWQ0utZjmOmhWsl" + #LF$ +
                              "-----END PRIVATE KEY-----"
WebServerList()\Certificate$ = "-----BEGIN CERTIFICATE-----" + #LF$ +
                               "MIICnTCCAgYCCQD0AWy2vzfcpzANBgkqhkiG9w0BAQUFADCBkjELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVN0YXRlMQ0wCwYDVQQHEwRDaXR5MRUwEwYDVQQKEwxPcmdhbml6YXRpb24xHDAaBgNVBAsTE09yZ2FuaXphdGlvbmFsIFVuaXQxLzAtBgNVBAMTJkNvbW1vbiBOYW1lIChlLmcuLCB5b3VyIHNlcnZlciBkb21haW4pMB4XDTI0MTEwNjE2NTI1N1oXDTI0MTIwNjE2NTI1N1owgZIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIEwVTdGF0ZTENMAsGA1UEBxMEQ2l0eTEVMBMGA1UEChMMT3JnYW5pemF0aW9uMRwwGgYDVQQLExNPcmdhbml6YXRpb25hbCBVbml0MS8wLQYDVQQDEyZDb21tb24gTmFtZSAoZS5nLiwgeW91ciBzZXJ2ZXIgZG9tYWluKTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA8RtJCr+7ARJl24+0N3zepdeRI9/qVQ1rXvGEzZ85lDEavQV2a/DoiwL5AZfbkQTVMbpMVCQeUzatbzLprdYQL9bMI1ZpMI9gP+uC6IAUb7h5CbAeOIkVV5zQieONIb1JfzHBL+BB5Z1CeIpE47gOYwk7w3uZ1SF0c4NKe1d2v3MCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCprm5a5bg1LqCDdtwDTnRDmVcca6HoUlvbjZLmWdLjltG1McNAATppTy/bF7vT3jXLobA1Vzs2g14POjYQhPnIbRPEnNzvAe+Se3y0YeFOwYarEyFBHKHODGIPaCnXGH8gB9fgcp2SYtLaPKvXdNL44VeYGbD4+fvUcu/zkXqTSg==" + #LF$ +
                               "-----END CERTIFICATE-----"
WebServerList()\CACertificate$ = ""

AddElement(WebServerList())
WebServerList()\Port = #HTTPS_Port
WebServerList()\RootPath$ = GetPathPart(ProgramFilename()) + #WWW
WebServerList()\ServerFlags = #PB_Network_TCP|#PB_Network_IPv6|#PB_Network_TLSv1
WebServerList()\PrivateKey$ = "-----BEGIN PRIVATE KEY-----" + #LF$ +
                              "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAPEbSQq/uwESZduPtDd83qXXkSPf6lUNa17xhM2fOZQxGr0Fdmvw6IsC+QGX25EE1TG6TFQkHlM2rW8y6a3WEC/WzCNWaTCPYD/rguiAFG+4eQmwHjiJFVec0InjjSG9SX8xwS/gQeWdQniKROO4DmMJO8N7mdUhdHODSntXdr9zAgMBAAECgYAE+VMgbaQl+YMwbF6DZogRU8kivFPRPV2hr8nVlBtT+09Z5uryfx3NAFqytbdJ3penVviMI9KcVNxvFtXLSEc9KyjzgysorAfUpwFuECCLDbOXX0HlV6rgkqJdhyV6FybcDLvgcvulHQ64QdYRhW+jPx7vXk3h0/JRFqKQJsY7QQJBAPrDLJRPbAw+Mlq1fHBWk8Z1Qn1ivPAmz+2nPAgDya/xdAlb9GbFAMzCS3upIBpxW70uLI04OuTVhwYL194I5C0CQQD2JHtHp25SkIDpBgZGicEC7yAIE/wPC0P9X85UJqXx5dPx4HbEc8lqSKMbCzkbHyvjHonSHu00QxU1W6ZALFYfAkBcPWzphSl+e2Z0XWvPutkS2FFD5A0R3YUAq1J2tEX9NTj0tGF7aB36M8ImU7jeYTJYrWJv8+4d/Ll1LOgT4XtlAkAxofOV5EYTsf28fzF+wcJAtDUyS81Uv0HLcqkpQM3PdDeDm253eJ2Rp+nzxxSRynxQBNVnoELWefxp0Pw6DnajAkBF5h7fQIbwAEPrhDzhjMXU7g9k9KzkkJN/bluLbleqkkAz1kfkGtWXJdGITZuY4K/X2yp1diWQ0utZjmOmhWsl" + #LF$ +
                              "-----END PRIVATE KEY-----"
WebServerList()\Certificate$ = "-----BEGIN CERTIFICATE-----" + #LF$ +
                               "MIICnTCCAgYCCQD0AWy2vzfcpzANBgkqhkiG9w0BAQUFADCBkjELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVN0YXRlMQ0wCwYDVQQHEwRDaXR5MRUwEwYDVQQKEwxPcmdhbml6YXRpb24xHDAaBgNVBAsTE09yZ2FuaXphdGlvbmFsIFVuaXQxLzAtBgNVBAMTJkNvbW1vbiBOYW1lIChlLmcuLCB5b3VyIHNlcnZlciBkb21haW4pMB4XDTI0MTEwNjE2NTI1N1oXDTI0MTIwNjE2NTI1N1owgZIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIEwVTdGF0ZTENMAsGA1UEBxMEQ2l0eTEVMBMGA1UEChMMT3JnYW5pemF0aW9uMRwwGgYDVQQLExNPcmdhbml6YXRpb25hbCBVbml0MS8wLQYDVQQDEyZDb21tb24gTmFtZSAoZS5nLiwgeW91ciBzZXJ2ZXIgZG9tYWluKTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA8RtJCr+7ARJl24+0N3zepdeRI9/qVQ1rXvGEzZ85lDEavQV2a/DoiwL5AZfbkQTVMbpMVCQeUzatbzLprdYQL9bMI1ZpMI9gP+uC6IAUb7h5CbAeOIkVV5zQieONIb1JfzHBL+BB5Z1CeIpE47gOYwk7w3uZ1SF0c4NKe1d2v3MCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCprm5a5bg1LqCDdtwDTnRDmVcca6HoUlvbjZLmWdLjltG1McNAATppTy/bF7vT3jXLobA1Vzs2g14POjYQhPnIbRPEnNzvAe+Se3y0YeFOwYarEyFBHKHODGIPaCnXGH8gB9fgcp2SYtLaPKvXdNL44VeYGbD4+fvUcu/zkXqTSg==" + #LF$ +
                               "-----END CERTIFICATE-----"
WebServerList()\CACertificate$ = ""


ForEach WebServerList()
  WebServerList()\Thread = CreateThread(@WebServerThread(), @WebServerList())
Next

If OpenWindow(0, 0, 0, 800, 600, "AppComposer", #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_MaximizeGadget | #PB_Window_MinimizeGadget)
  If CreateMenu(0, WindowID(0))  ; Create a regular menu with title and one item
    MenuTitle("File")
    MenuItem(1, "Load HTTP")
    MenuItem(2, "Load HTTPS")
  EndIf
  
  WebViewGadget(0, 0, 0, 800, 600)
  SetGadgetText(0, "http://localhost:" + Str(#HTTP_PORT))
  
  
  HideWindow(0, #False)
  
  Repeat 
    Event = WaitWindowEvent()
    Select Event 
      Case #PB_Event_Menu
        Select EventMenu()
          Case 1 : SetGadgetText(0, "http://localhost:" + Str(#HTTP_PORT))
          Case 2 : SetGadgetText(0, "https://localhost:" + Str(#HTTPS_PORT))
        EndSelect
        
      Case #PB_Event_CloseWindow
        Debug "CloseWindow"
        Exit = #True
        
    EndSelect
  Until Exit
  
  ForEach WebServerList()
    If IsThread(WebServerList()\Thread)
      WebServerList()\Exit = #True
      If WaitThread(WebServerList()\Thread, 3000) = 0
        Debug "Should never reached"
        KillThread(WebServerList()\Thread)
      EndIf
    EndIf
  Next
  
EndIf
Last edited by infratec on Wed Dec 31, 2025 2:55 pm, edited 5 times in total.
infratec
Always Here
Always Here
Posts: 7766
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Simple HTTP Server crashes on linux/mac on stress test

Post by infratec »

I changed the code above, because my stress test failed (windows):

Code: Select all

curl --parallel --parallel-immediate --parallel-max-host 1000 -o nul "http://localhost:8080/portmon.exe?[1-1000]"
But for the new parameter --parallel-max-host you need to download curl 8.16 or higher

SendNetworkData() returned -1 an the delivery stucked.
portmon.exe is 451392 bytes in size.
DL% UL% Dled Uled Xfers Live Total Current Left Speed
100 -- 430M 0 1000 0 0:00:04 0:00:05 --:--:-- 89.9M
User avatar
skinkairewalker
Addict
Addict
Posts: 824
Joined: Fri Dec 04, 2015 9:26 pm

Re: Simple HTTP Server crashes on linux/mac on stress test

Post by skinkairewalker »

infratec wrote: Mon Dec 29, 2025 10:22 pm You can try this:

Code: Select all

EnableExplicit

#PORT = 8080
#WWW = "www"

Structure WebServerClient_Structure
  Client.i
  Thread.i
  *Buffer
  Size.i
  LastSeen.i
  RootPath$
EndStructure


Structure WebServer_Structure
  Thread.i
  Port.i
  Server.i
  RootPath$
  Map ClientMap.WebServerClient_Structure()
  Exit.i
EndStructure




Procedure.s GetMimeType(Filename$)
  
  Protected Mime$
  
  
  Select LCase(GetExtensionPart(Filename$))
    Case "html","htm" : Mime$ = "text/html"
    Case "js"         : Mime$ = "application/javascript"
    Case "css"        : Mime$ = "text/css"
    Case "png"        : Mime$ = "image/png"
    Case "jpg","jpeg" : Mime$ = "image/jpeg"
    Case "gif"        : Mime$ = "image/gif"
    Case "ico"        : Mime$ = "image/vnd.microsoft.icon"
    Default
      Mime$ = "application/octet-stream"
  EndSelect
  
  ProcedureReturn Mime$
  
EndProcedure




Procedure WebServerClientThread(*Parameter.WebServerClient_Structure)
  
  Protected.i File, Pos1, Pos2, Chunk, Res
  Protected *Buffer
  Protected.q Size
  Protected Received$, Header$, Type$, Parameter$, Filename$, Mime$
  
  
  Debug "ClientThread " + Str(*Parameter\Client)
  
  Received$ = PeekS(*Parameter\Buffer, *Parameter\Size, #PB_UTF8|#PB_ByteLength)
  Header$ = StringField(Received$, 1, #CRLF$ + #CRLF$)
  
  ;Debug Str(*Parameter\Client) + #LF$ + Header$
  
  Pos1 = FindString(Header$, " ")
  Type$ = Left(Header$, Pos1 - 1)
  Pos1 + 1
  Pos2 = FindString(Header$, "HTTP", Pos1)
  Pos2 - 1
  Parameter$ = Mid(Header$, Pos1, Pos2 - Pos1)
  
  Filename$ = StringField(Parameter$, 1, "?")
  Parameter$ = StringField(Parameter$, 2, "?")
  If Filename$ = "/"
    Filename$ = "/index.html"
  EndIf
  Filename$ = *Parameter\RootPath$ + Filename$
  Mime$ = GetMimeType(Filename$)
  
  Size = FileSize(Filename$)
  
  Debug Filename$ + " " + Str(Size) + " " + Str(*Parameter\Client)
  
  If Size > 0
    Header$ = "HTTP/1.1 200 OK" + #CRLF$
    Header$ + "Content-Type: " + Mime$ + #CRLF$
    Header$ + "Content-Length: " + Str(Size) + #CRLF$
    Header$ + "Connection: close" + #CRLF$
    Header$ +  #CRLF$
    
    ;Debug Str(*Parameter\Client) + #LF$ + Header$
    
    Res = SendNetworkString(*Parameter\Client, Header$, #PB_UTF8)
    If Res < StringByteLength(Header$, #PB_UTF8)
      Debug "SendNetworkString failed " + Str(*Parameter\Client)
    EndIf
    
    *Buffer = AllocateMemory(16348, #PB_Memory_NoClear)
    If *Buffer
      File = ReadFile(#PB_Any, Filename$, #PB_File_SharedRead)
      If File
        While Not Eof(File)
          Chunk = ReadData(File, *Buffer, MemorySize(*Buffer))
          If Chunk > 0
            Repeat
            Res = SendNetworkData(*Parameter\Client, *Buffer, Chunk)
            If Res < Chunk
              Debug "SendNetworkData failed " + Str(Res) + " " + Str(*Parameter\Client)
            EndIf
            Until Res = Chunk
          EndIf
        Wend
        CloseFile(File)
      Else
        Debug "File not open " + Str(*Parameter\Client)
      EndIf
      FreeMemory(*Buffer)
    Else
      Debug "No file buffer " + Str(*Parameter\Client)
    EndIf
    
  Else
    
    Header$ = "HTTP/1.1 404 Not Found" + #CRLF$
    Header$ + "Content-Length: 13" + #CRLF$ +
    Header$ + "Connection: close" + #CRLF$ +
    Header$ + #CRLF$ +
    Header$ + "404 Not Found"
    SendNetworkString(*Parameter\Client, Header$, #PB_UTF8)
    
  EndIf
  
  *Parameter\Thread = 0
  
EndProcedure


Procedure WebServerThread(*Parameter.WebServer_Structure)
  
  Protected.i NetEvent, Client, Offset, Size
  Protected Client$
  
  
  *Parameter\Server = CreateNetworkServer(#PB_Any, *Parameter\Port, #PB_Network_TCP|#PB_Network_IPv4)
  If *Parameter\Server
    
    Debug "Servidor em http://localhost:" + Str(#PORT)
    
    Repeat
      
      Select NetworkServerEvent()
        Case #PB_NetworkEvent_Connect
          Client = EventClient()
          Client$ = Str(Client)
          Debug "Connect " + Client$
          If Not FindMapElement(*Parameter\ClientMap(), Client$)
            AddMapElement(*Parameter\ClientMap(), Client$)
            *Parameter\ClientMap()\Buffer = AllocateMemory(4096, #PB_Memory_NoClear)
            *Parameter\ClientMap()\Client = Client
            *Parameter\ClientMap()\RootPath$ = *Parameter\RootPath$
          EndIf
          *Parameter\ClientMap()\LastSeen = Date()
          If Not *Parameter\ClientMap()\Buffer
            Debug "Memory allocation for new client not possible"
            DeleteMapElement(*Parameter\ClientMap())
          EndIf
          
        Case #PB_NetworkEvent_Data
          Client = EventClient()
          Client$ = Str(Client)
          Debug "Data " + Client$
          If FindMapElement(*Parameter\ClientMap(), Client$)
            *Parameter\ClientMap()\LastSeen = Date()
            Offset = 0
            Repeat
              Size = ReceiveNetworkData(Client, *Parameter\ClientMap()\Buffer + Offset, MemorySize(*Parameter\ClientMap()\Buffer))
              If Size = -1
                DeleteMapElement(*Parameter\ClientMap())
                Break
              Else
                If Size = MemorySize(*Parameter\ClientMap()\Buffer)
                  Offset = MemorySize(*Parameter\ClientMap()\Buffer)
                  ReAllocateMemory(*Parameter\ClientMap()\Buffer, Offset + 4096, #PB_Memory_NoClear)
                  Size = 0
                Else
                  Size = Offset + Size
                  *Parameter\ClientMap()\Size = Size
                  *Parameter\ClientMap()\Thread = CreateThread(@WebserverClientThread(), @*Parameter\ClientMap())
                EndIf
              EndIf
            Until Size > 0
          EndIf
          
        Case #PB_NetworkEvent_Disconnect
          Client = EventClient()
          Client$ = Str(Client)
          If FindMapElement(*Parameter\ClientMap(), Client$)
            Debug "Delete " + MapKey(*Parameter\ClientMap()) + " by disconnect"
            FreeMemory(*Parameter\ClientMap()\Buffer)
            DeleteMapElement(*Parameter\ClientMap())
          EndIf
          
        Case #PB_NetworkEvent_None
          Delay(10)  ; Ne pas saturer le CPU / Don't stole the whole CPU !
          ForEach *Parameter\ClientMap()
            If *Parameter\ClientMap()\LastSeen + 60 < Date()
              Debug "Delete " + MapKey(*Parameter\ClientMap()) + " by timeout"
              FreeMemory(*Parameter\ClientMap()\Buffer)
              DeleteMapElement(*Parameter\ClientMap())
            EndIf
          Next
          
      EndSelect
      
    Until *Parameter\Exit
    
    While MapSize(*Parameter\ClientMap())
      ForEach *Parameter\ClientMap()
        If *Parameter\ClientMap()\Thread = 0
          FreeMemory(*Parameter\ClientMap()\Buffer)
          DeleteMapElement(*Parameter\ClientMap())
        EndIf
      Next
    Wend
    
    CloseNetworkServer(*Parameter\Server)
    *Parameter\Server = 0
    
  EndIf
  
EndProcedure



;-Main
Define.i Event, Exit
Define WebServer.WebServer_Structure


WebServer\Port = #Port
WebServer\RootPath$ = GetPathPart(ProgramFilename()) + #WWW


WebServer\Thread = CreateThread(@WebServerThread(), @WebServer)


If OpenWindow(0, 0, 0, 800, 600, "AppComposer", #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_MaximizeGadget | #PB_Window_MinimizeGadget)
  If CreateMenu(0, WindowID(0))  ; Create a regular menu with title and one item
    MenuTitle("File")
    MenuItem(1, "Reload" + #TAB$ + "Ctrl+O")
  EndIf
  
  WebViewGadget(0, 0, 0, 800, 600)
  SetGadgetText(0, "http://localhost:8080")
  
  
  HideWindow(0, #False)
  
  Repeat 
    Event = WaitWindowEvent()
    Select Event 
      Case #PB_Event_Menu
        Select EventMenu()
          Case 1
            SetGadgetText(0, "http://localhost:8080")
        EndSelect  
      Case #PB_Event_CloseWindow
        Exit = #True
    EndSelect   
  Until Exit
  
  If IsThread(WebServer\Thread)
    WebServer\Exit = #True
    If WaitThread(WebServer\Thread, 3000) = 0
      Debug "Should never reached"
      KillThread(WebServer\Thread)
    EndIf
  EndIf
  
EndIf
I thank you infinitely for the help, thank you for sharing your knowledge with me!
I will test it now, thank you so much. I’ll be back soon with the results I get, thank you for your time.
infratec
Always Here
Always Here
Posts: 7766
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Simple HTTP Server crashes on linux/mac on stress test

Post by infratec »

Btw. If you want a TCPv6 Server you need to start an extra server.
And if you want http/https and IPv4/v6 you need 4 server threads running.

The parameters for the server thread can be extended to reflect this.
Then you can start the 'same' server for the different conditions.
Last edited by infratec on Tue Dec 30, 2025 1:46 pm, edited 1 time in total.
infratec
Always Here
Always Here
Posts: 7766
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Simple HTTP Server crashes on linux/mac on stress test

Post by infratec »

Added the possibility for https.
Some more tricks were needed, mainly to fix -2 return value for ReceiveNetworkData() with TLS

The speed with TLS is ... reduced:
curl -k --parallel --parallel-immediate --parallel-max-host 1000 -o nul "https://localhost:8443/portmon.exe?[1-1000]"
DL% UL% Dled Uled Xfers Live Total Current Left Speed
100 -- 430M 0 1000 0 0:00:12 0:00:12 --:--:-- 35.8M
infratec
Always Here
Always Here
Posts: 7766
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Simple HTTP Server crashes on linux/mac on stress test

Post by infratec »

Since I had a bit time, I added the possibility for all variants.

Pittfall: You need to use the NetworkServerEvent() parameter :!:
Else the events get mixed between the servers.
infratec
Always Here
Always Here
Posts: 7766
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Simple HTTP Server crashes on linux/mac on stress test

Post by infratec »

I think the most problem is your stresstest code.

It does not receive the data and at the end it is terminated without closing all correct.

The problem on the server side is, that ther eis no way in PB to detect if a network connection is still valid or not.
So the server tries to send data to a no longer existant connection and run into trouble.
infratec
Always Here
Always Here
Posts: 7766
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Simple HTTP Server crashes on linux/mac on stress test

Post by infratec »

I tested it on linux and added some stuff to avoid endless loops and segmentation faults cause of not correct connections.
The server works in my environment.
Post Reply