Problems in Windows with HTTP-REST API Skeleton

Just starting out? Need help? Post your questions and find answers here.
jakobssystems
User
User
Posts: 12
Joined: Thu Apr 10, 2025 5:17 pm

Problems in Windows with HTTP-REST API Skeleton

Post by jakobssystems »

Hi there,

this is my first post and my first purebasic steps, please be polite when I am missing someting

I've written an small skeleton for an http rest-api server in purebasic:
https://codeberg.org/tomas-jakobs/rest-api-skeleton

In GNU/Linux (Debian sid and stable) everything works fine and is rock solid but when I try to use this in Windows after the first request/response I end up with with memory overflow.

I am aware of differences between the platforms, but I've used clean Purebasic code and really tried hard to prevent mem overflows and issues. Do I miss something in Compiler settings or something else?
jakobssystems
User
User
Posts: 12
Joined: Thu Apr 10, 2025 5:17 pm

Re: Problems in Windows with HTTP-REST API Skeleton

Post by jakobssystems »

Now checked against PureBasic 6.21 Beta on Windows 10 baremetal and virtualized Windows 2019 - no success.
Checked my code again, tried multiple compiler options, added more defensive checks (cause I expect broken requesting clients) but again, no success.
For me the NetworkServer of PureBasic is the suspect. It seems working differently in windows than in GNU/Linux.
User avatar
HeX0R
Addict
Addict
Posts: 1204
Joined: Mon Sep 20, 2004 7:12 am
Location: Hell

Re: Problems in Windows with HTTP-REST API Skeleton

Post by HeX0R »

You should enable the Purifier, e.g. here:

Code: Select all

Protected size = StringByteLength(response, #PB_UTF8)
    Protected *mem = #Null
    *mem = AllocateMemory(size)
    
    If *mem
      PokeS(*mem, response, -1, #PB_UTF8)
PokeS() adds the 0 byte of the string (when not using #PB_String_NoZero flag), which means, you are poking behind your reserved memory block.
I didn't go on checking, that code is too confusing
jakobssystems
User
User
Posts: 12
Joined: Thu Apr 10, 2025 5:17 pm

Re: Problems in Windows with HTTP-REST API Skeleton

Post by jakobssystems »

thank you for your quick check and suggesttion. Poking behind is prevented with alloc size +1 everywhere. In adition to this I eleminated a race condition in my Main loop when creating threads on PB_NetworkEvent_Connect. This works now with charme handling many parallel threads on GNU/LInux. But still not on Winows :-(
User avatar
idle
Always Here
Always Here
Posts: 5897
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Problems in Windows with HTTP-REST API Skeleton

Post by idle »

jakobssystems wrote: Fri Apr 11, 2025 1:13 am thank you for your quick check and suggesttion. Poking behind is prevented with alloc size +1 everywhere. In adition to this I eleminated a race condition in my Main loop when creating threads on PB_NetworkEvent_Connect. This works now with charme handling many parallel threads on GNU/LInux. But still not on Winows :-(
It's usually the other way round windows is generally more forgiving than linux when it comes to threads.

I don't see you handling #PB_NetworkEvent_Data and your not handling RecieveNetworkData properly plus you will force multiple client retries when the actual error is an eagain or WsaWouldBlock. This isn't your fault, its just that PB doesn't have an error checking function which complicates it.
I think the main problem might be that your continuing on 0 bytes received but that's supposed to be telling you the connection has closed
https://learn.microsoft.com/en-us/windo ... nsock-recv

you can try these functions which try to catch the errors I haven't tested them on linux yet

Code: Select all

;-Extra functions 
#PB_Network_Error_Fatal = -1 
#PB_Network_Error_timeout = -2 
#PB_Network_Error_Dropped = -3 
#PB_Network_Error_Memory = -4 

Procedure Atomic_Server_NetworkErrorContinue(ID,val=0) 
  Protected ret,error.l
  
  #WSA_IO_INCOMPLETE = 996
  #WSA_IO_PENDING = 997
  
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows 
    #WSA_IO_INCOMPLETE = 996
    #WSA_IO_PENDING = 997
    #WSAEINTR = 10004
    #WSAEMFILE = 10024
    #WSAEWOULDBLOCK = 10035
    #WSAEINPROGRESS = 10036
    #WSAEALREADY = 10037
  CompilerElse 
    #WSAEINTR = 4 ;EINTR 
    #WSAEMFILE = 17 ;2  ;ENOFILE ENOENT 2 
    #WSAEWOULDBLOCK = 11 ;Eagain  
    #WSAEINPROGRESS = 115 ;EINPROGRESS
    #WSAEALREADY = 114   ;EALREADY 
  CompilerEndIf 
   
  
  #TLS_WANT_POLLIN  = -2
  #TLS_WANT_POLLOUT = -3
    
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows 
    error = WSAGetLastError_()
  CompilerElse 
    CompilerIf #PB_Compiler_Backend = #PB_Backend_C
      !#include "errno.h"
      !extern int errno;
      !v_error=errno;
    CompilerElse
      error = PeekL(__errno_location()) 
    CompilerEndIf 
  CompilerEndIf
  
  If val = #TLS_WANT_POLLIN
     Debug "#TLS_WANT_POLLIN"
    ProcedureReturn  1
  EndIf   
  If val = #TLS_WANT_POLLOUT  
     Debug "#TLS_WANT_POLLOUT"
    ProcedureReturn 1 
  EndIf   
  
  Select error 
    Case 0 
      ret = 0
       Debug "None"
    Case  #WSAEWOULDBLOCK  
      ret = 1 
      Debug "#WSAEWOULDBLOCK"
    Case  #WSAEINPROGRESS   
      Debug "#WSAEINPROGRESS"
      ret = 1  
    Case  #WSAEALREADY  
      Debug "#WSAEALREADY"
      ret = 1  
    Case  #WSA_IO_INCOMPLETE
      Debug "#WSA_IO_INCOMPLETE"
      ret =1   
    Case  #WSA_IO_PENDING
      Debug "#WSA_IO_PENDING"
      ret = 1  
    Case  #WSAEMFILE 
      ret =1 
      Debug "#WSAEMFILE"
    Default 
      Debug error   
  EndSelect   
  
  ProcedureReturn ret 
  
EndProcedure  

Procedure Atomic_Server_ReceiveNetworkDataEx(clientId,len,timeout=15000,mutex=0,*error.Integer=0) 
  
  Protected result,recived,recvTimeout,tlen,bfirst=1
  
  If len > 0 
    Protected *buffer = AllocateMemory(len)
    If *buffer 
      
      recvTimeout=ElapsedMilliseconds()+timeout   
      
      Repeat
        If result > 0
           *buffer = ReAllocateMemory(*buffer, recived + len) 
        EndIf 
        If *buffer 
          If mutex 
            Repeat 
              If TryLockMutex(mutex)
                Result = ReceiveNetworkData(clientId,*buffer+recived, len) 
                If result < 0 
                  If Atomic_Server_NetworkErrorContinue(clientId,result) 
                    Delay(10)
                  Else 
                    
                    UnlockMutex(mutex)
                    FreeMemory(*buffer)
                    If *error 
                      *error\i = #PB_Network_Error_Fatal
                    EndIf   
                    ProcedureReturn 0
                  EndIf 
                EndIf   
                UnlockMutex(mutex) 
               
                Break 
              Else 
                Delay(10)
              EndIf   
            Until  ElapsedMilliseconds() > recvTimeout  
          Else       
            Result = ReceiveNetworkData(clientId,*buffer+recived, len)
            If result < 0 
              If Atomic_Server_NetworkErrorContinue(clientId,result) 
                Delay(10)
                Continue 
              Else 
                FreeMemory(*buffer)
                If *error 
                  *error\i = #PB_Network_Error_Fatal
                EndIf   
                Delay(10)
                ProcedureReturn 0
              EndIf 
            EndIf   
          EndIf   
          
          If result > 0 
            recived+result  
            recvTimeout = ElapsedMilliseconds() + timeout
          ElseIf result = 0 
            FreeMemory(*buffer)
            If *error 
              *error\i = #PB_Network_Error_Dropped 
            EndIf   
            ProcedureReturn 0
          EndIf   
        Else 
          If *error 
            *error\i = #PB_Network_Error_Memory 
          EndIf   
          ProcedureReturn 0
        EndIf   
        
        If ElapsedMilliseconds() > recvTimeout    
          FreeMemory(*buffer)
          If *error 
            *error\i = #PB_Network_Error_timeout 
          EndIf   
          ProcedureReturn 0
        EndIf 
        Delay(0) 
      Until result <> len   
      
      ProcedureReturn *buffer
      
    EndIf 
  EndIf 
  
EndProcedure   

Procedure Atomic_Server_SendNetworkDataEX(clientId,*buffer,len,timeout=15000,mutex=0,*error.Integer=0) 
  
  Protected  totalSent,tryLen,sendLen,sendTimeout
  
  sendTimeout = ElapsedMilliseconds() + timeout
  Repeat
    
    tryLen = len - totalSent
    If tryLen > len 
      tryLen = len 
    EndIf
    If mutex 
      Repeat 
        If TryLockMutex(mutex)  
          sendLen = SendNetworkData(clientId, *Buffer+totalSent,tryLen)
          If sendLen < 0 
            If Atomic_Server_NetworkErrorContinue(clientId,sendLen) 
              Delay(10) 
            Else 
            If *error 
               *error\i = #PB_Network_Error_Fatal
            EndIf   
             Debug Str(totalsent) + " " + Str(trylen) + " " + Str(len) 
             UnlockMutex(mutex)
             ProcedureReturn 0
            EndIf 
          EndIf 
          UnlockMutex(mutex) 
          Break 
        Else 
          Delay(10)
        EndIf 
      Until ElapsedMilliseconds() > sendTimeout 
    Else 
      sendLen = SendNetworkData(clientId, *Buffer+totalSent,tryLen)
      If sendLen < 0 
        If Atomic_Server_NetworkErrorContinue(clientId,sendLen) 
          Delay(10) 
        Else 
          If *error 
            *error\i = #PB_Network_Error_Fatal
          EndIf   
           Debug Str(totalsent) + " " + Str(trylen) + " " + Str(len) 
          ProcedureReturn 0
        EndIf 
      EndIf 
    EndIf   
    
    If sendLen > 0
      totalSent + sendLen
      sendLen = 0 
      sendTimeout = ElapsedMilliseconds() + timeout
    ElseIf sendLen = 0 
      If *error 
        *error\i = #PB_Network_Error_Dropped  
      EndIf   
      ProcedureReturn 0 
    EndIf 
    
    If ElapsedMilliseconds() > sendTimeout
      If *error 
        *error\i = #PB_Network_Error_timeout 
      EndIf   
      ProcedureReturn 0
    EndIf 
    
    Delay(1) 
    
  Until totalSent >= len 
  
  ProcedureReturn totalSent 
  
EndProcedure   
jakobssystems
User
User
Posts: 12
Joined: Thu Apr 10, 2025 5:17 pm

Re: Problems in Windows with HTTP-REST API Skeleton

Post by jakobssystems »

Good Morning,

I can bring it down to "CloseNetworkConnection" in my rest-api-skeleton.pb, cleaning procedure:

Code: Select all

; Clean Up ClientHandling Routine
Procedure CleanupClient(clientID.i, *buffer, *client.ClientData, *tempbuffer)
  
  ; Check first before trying to close Connections and free memory
  If *buffer : FreeMemory(*buffer) : EndIf
  If *tempbuffer : FreeMemory(*tempbuffer) : EndIf
  If *client :  FreeMemory(*client) : EndIf
  If clientID : CloseNetworkConnection(clientID) : EndIf ; <---- if commented out, it doesn't crash in Windows anymore

EndProcedure
When I comment to whole line, nothing crahes in Windows anymore.
Next I've put the CloseNetworkConnection inside a Mutex but again it crashed.

But I have to close the Connection inside the thread 'cause every new connection is handled in its own thread in Main()

To me it seems there is a race condition in NetworkConnection in Winows when threaded.
As said before, in GNU/LInux everywhing works fine.
Fred
Administrator
Administrator
Posts: 18220
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Problems in Windows with HTTP-REST API Skeleton

Post by Fred »

I quickly checked the code but it's too big to analyze fully. Any chance to try to remove all unneeded part while still preserving the issue ?
Just wondering, why did you reimplement the base64 stuff ?
You should also not using several ProcedureReturn, as PB code isn't not fully scope managed. For example in the file config.pbi, line 150 the file isn't closed if the AllocateMemory() fail. You could rewrite it like that:

Code: Select all

Procedure.s ReadBinaryFileInBase64(filePath.s = "")
    
  Protected result.s
  
  If filePath
      
    Protected fileSize.q = FileSize(filePath)
    If fileSize > 0
        
      Protected file = ReadFile(#PB_Any, filePath)
      If file
          
        Protected *buffer = AllocateMemory(fileSize)
        If *buffer
            
          Protected bytesRead.q = ReadData(file, *buffer, fileSize)

          If bytesRead = fileSize
            result.s = base64::EncodeFromMemory(*buffer, fileSize)
          EndIf

          FreeMemory(*buffer)
            
        EndIf
          
        CloseFile(file)
      EndIf
        
    EndIf
      
  EndIf

  ProcedureReturn result
  
EndProcedure
infratec
Always Here
Always Here
Posts: 7618
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Problems in Windows with HTTP-REST API Skeleton

Post by infratec »

Instead of

Code: Select all

If *client :  FreeMemory(*client) : EndIf
You should use:

Code: Select all

If *client :  FreeStructure(*client) : EndIf
But that is not a reason for the crash (I think)
infratec
Always Here
Always Here
Posts: 7618
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Problems in Windows with HTTP-REST API Skeleton

Post by infratec »

Btw:

instead of:

Code: Select all

; Send full HTTP-Response to client
Procedure SendHttpResponse(clientID.i, body.s, contentType.s = #CONTENTTYPE)
  
  Protected response.s = BuildHttpResponse(body, contentType)
  Protected size = StringByteLength(response, #PB_UTF8)
  Protected *mem = #Null
  *mem = AllocateMemory(size + 1)
  If *mem = #Null : ProcedureReturn : EndIf
  
  If *mem
    PokeS(*mem, response, -1, #PB_UTF8)
    SendNetworkData(clientID, *mem, size)
    FreeMemory(*mem)
  EndIf
  
EndProcedure
You can simply use:

Code: Select all

; Send full HTTP-Response to client
Procedure.i SendHttpResponse(clientID.i, body.s, contentType.s = #CONTENTTYPE)
  
  Protected response.s = BuildHttpResponse(body, contentType)
  ProcedureReturn SendNetworkString(clientID, response)
  
EndProcedure
jakobssystems
User
User
Posts: 12
Joined: Thu Apr 10, 2025 5:17 pm

Re: Problems in Windows with HTTP-REST API Skeleton

Post by jakobssystems »

Any chance to try to remove all unneeded part while still preserving the issue ?
Here we go, kicked everything out.
first request is served... 2nd or 3rd request crashes ... in Windows only... in the Cleanup Procedure when it is closed in CloseNetworkConnection
I am checking if client disconnected... trying to be as defensive as possible... w/o success...

Code: Select all

EnableExplicit

XIncludeFile "httpresponse.pbi"

Structure ClientData
  ClientID.i
EndStructure

Declare HandleRoutes(clientID.i, method.s, path.s)
Declare CleanupClient(clientID.i, *buffer, *client.ClientData, *tempbuffer, clientConnected = #True)
Declare HandleClient(*client.ClientData)
Declare Main()

Main()


; Path Routing in HandleClient Requests
Procedure.i HandleRoutes(ClientID.i, Method.s, Path.s)
  
  Debug "Start HandleRoutes..."
  
  If UCase(Method) = "GET" And Path = "/"
    HttpResponse::SendHttpResponse(ClientID, "Hello!")
    Debug "SendHttpResponse(ClientID, Hello!)"
    ProcedureReturn #True
    
  ElseIf UCase(Method) = "GET" And LCase(Path) = "/api/ping"
    HttpResponse::SendHttpResponse(ClientID, "pong")
    Debug "SendHttpResponse(ClientID, pong)"
    ProcedureReturn #True
      
  ElseIf UCase(Method) = "GET" And Len(Path) 
    HttpResponse::SendHttpError(ClientID, 403, "Forbidden")
    Debug "SendHttpError(ClientID, 403, Forbidden)"
    ProcedureReturn #True
      
  EndIf

  ProcedureReturn #False
  Debug "End HandleRoutes = false"
  
EndProcedure

Procedure HandleClient(*client.ClientData)
  
  Protected clientID.i = *client\ClientID
  Protected bufferSize = 4096
  Protected clientConnected = #True
  Protected received, totalReceived = 0, headerEndPos = -1
  Protected Header.s, Body.s, Method.s
  Protected authHeader.s = "Authorization: Basic "
  Protected i = 0, contentLength = 0
  Protected startTime = ElapsedMilliseconds()
  Protected HeaderMaxSizeInBytes.i = 8192
  Protected HeaderTimeoutInMs.i = 5000
  Protected DelayInMs.i = 10

  ; Create Buffer
  ; HTTP 500 when Allocating Error (e.g. memory exhausted)
  Protected *tempbuffer = #Null
  Protected *buffer = #Null
  *buffer = AllocateMemory(bufferSize)

  If *buffer = #Null
    HttpResponse::SendHttpError(clientID, 500, "Memory Allocation Failed")
    CleanupClient(clientID, *buffer, *client, *tempbuffer, clientConnected)
    Debug "SendHttpError(ClientID, 500, alloc failed)"
    ProcedureReturn
  EndIf    
  
  ; Read Binary-safe complete HTTP Header
  ; Limit Header Size and Timeouts
  ;
  ; and answer with RFC compliant HTTP Status Codes
  Repeat

    ; Client gone away prematurely
    If NetworkClientEvent(clientID) = #PB_NetworkEvent_Disconnect
      clientConnected = #False
      Debug "Client disconnected"
      Break
    EndIf
    
    ; Timeout-Check
    ; HTTP 408 Status when HeaderTimeoutInMs exceeded
    If ElapsedMilliseconds() - startTime > HeaderTimeoutInMs
      HttpResponse::SendHttpError(clientID, 408, "Request Timeout")
      clientConnected = #False
      Debug "SendHttpError(clientID, 408, Request Timeout)"
      Break
    EndIf

    received = ReceiveNetworkData(clientID, *buffer + totalReceived, bufferSize - totalReceived - 1)

    If received < 0
      ; Unkown Error occured when negative
      ; answer with HTTP 400 Status Code
      HttpResponse::SendHttpError(clientID, 400, "Bad Request")
      Debug "SendHttpError(clientID, 400, Bad Request)"
      Break
      
    ElseIf received = 0
      ; Free CPU Cycles
      Delay(DelayInMs) 
      Continue
    EndIf

    totalReceived + received

    ; Limit Headersize
    ; HTTP 431 Status when HeaderMaxSizeInBytes exceeded
    If totalReceived > HeaderMaxSizeInBytes
      HttpResponse::SendHttpError(clientID, 431, "Request Header Fields Too Large")
      Debug "SendHttpError(clientID, 431, Header Fields too large)"
      Break
    EndIf

    ; Search UTF-safe End of Header (CRLF CRLF)
    ; Break out when Reading Header finished
    For i = 0 To totalReceived - 4
      If PeekA(*buffer + i) = 13 And PeekA(*buffer + i + 1) = 10 And
        PeekA(*buffer + i + 2) = 13 And PeekA(*buffer + i + 3) = 10
        headerEndPos = i
        Break 2
      EndIf
    Next

    ; Increase Buffer
    ; HTTP 500 when ReAllocating Error (e.g. memory exhausted)
    If totalReceived >= bufferSize - 1
      bufferSize + 4096
      *buffer = ReAllocateMemory(*buffer, bufferSize +1)
      If *buffer = 0
        HttpResponse::SendHttpError(clientID, 500, "Server Memory Error")
        Debug "SendHttpError(clientID, 500, alloc error)"
        Break
      EndIf
    EndIf

  Until clientConnected = #False 
  
  ; Process only if Client is still there to recieve an answer
  If clientConnected
    
    ; Set Nullterminator
    PokeA(*buffer + totalReceived, 0)
  
    ; Break without Header 
    If headerEndPos = -1
      CleanupClient(clientID, *buffer, *client, *tempbuffer, clientConnected)
      ProcedureReturn
    EndIf

    ; Parse Header, Body and Method
    Header = PeekS(*buffer, headerEndPos, #PB_UTF8)
    Body = PeekS(*buffer + headerEndPos + 4, totalReceived - headerEndPos - 4, #PB_UTF8)
    Method = StringField(Header, 1, " ")
  
    ; Handle OPTIONS
    If UCase(Method) = "OPTIONS"
      HttpResponse::SendHttpResponse(clientID,"")
      Debug "OPTIONS sent"
      CleanupClient(clientID, *buffer, *client, *tempbuffer, clientConnected)    
      ProcedureReturn
    EndIf
  
    ; For Routing get RequestLine and uriPath
    Protected RequestLine.s = StringField(Header, 1, #CRLF$)
    Protected uriPath.s = StringField(RequestLine, 2, " ")
  
    ; Route-Handling
    ; Checks on existing routes. if routing then process and break
    If HandleRoutes(clientID, Method, uriPath)
      CleanupClient(clientID, *buffer, *client, *tempbuffer, clientConnected)
      ProcedureReturn
    EndIf  
  
    ; Handle POST ---
    If UCase(Method) = "POST"

      Protected contentPos = FindString(UCase(Header), "CONTENT-LENGTH:")
      If contentPos
        contentLength = Val(Trim(StringField(Mid(Header, contentPos + 15), 1, #CRLF$)))

        ; Get chunked Body part until ContentLenght reached
        While StringByteLength(Body, #PB_UTF8) < contentLength
        
          ; Use temp Buffer
          Protected restSize = contentLength - StringByteLength(Body, #PB_UTF8)
          *tempBuffer = AllocateMemory(restSize +1)
        
          ; HTTP 500 when Allocating Error (e.g. memory exhausted)
          If *tempBuffer = #Null
            HttpResponse::SendHttpError(clientID, 500, "Server Memory Error")
            Debug "SendHttpError(clientID, 500, alloc)"
            CleanupClient(clientID, *buffer, *client, *tempbuffer, clientConnected)
            ProcedureReturn
          EndIf
          
          ; Client gone away prematurely
          If NetworkClientEvent(clientID) = #PB_NetworkEvent_Disconnect
            Debug "client gone away"
            clientConnected = #False
            Break
          EndIf
    
          received = ReceiveNetworkData(clientID, *tempBuffer, restSize)
          If received > restSize : received = restSize : EndIf
        
          ; Set Nullterminator
          PokeA(*tempBuffer + received, 0)
        
          ; HTTP 400 for incomplete Body post        
          If received <= 0
            HttpResponse::SendHttpError(clientID, 400, "Incomplete POST Body")
            Debug "SendHttpError(clientID, 400, incomplete body)"
            CleanupClient(clientID, *buffer, *client, *tempbuffer, clientConnected)
            ProcedureReturn
          EndIf

          body + PeekS(*tempBuffer, received, #PB_UTF8)
        
          ; Free CPU Cycles
          Delay(DelayInMs)
        
        Wend
      
        If clientConnected
          HttpResponse::SendHttpResponse(clientID, "POST Body: " + Body)
          Debug "send body back"
        EndIf

      Else
      
        ; Exception when missing Content-Length, which is required by RFC
        HttpResponse::SendHttpError(clientID, 411, "Length Required")
        Debug "SendHttpError(clientID, 411, Length Required)"
      
      EndIf

    Else
    
      ; Handle any unsupported methods or missing Auth
      ; only process when Client is still there
      If clientConnected
        HttpResponse::SendHttpError(clientID, 405, "Method Not Allowed")
        Debug "SendHttpError(clientID, 405, Method not allowed)"
      EndIf
    
    EndIf
        
  EndIf
  
  ; Everything finished
  ; Cleanup everything to close thread
  CleanupClient(clientID, *buffer, *client, *tempbuffer, clientConnected)
  ProcedureReturn

EndProcedure


; Clean Up ClientHandling Routine
Procedure CleanupClient(clientID.i, *buffer, *client.ClientData, *tempbuffer, clientConnected = #True)
  
  Debug "cleanup started..."

  ; Check first before trying to close Connections and free memory
  If *buffer : FreeMemory(*buffer) : EndIf
  If *tempbuffer : FreeMemory(*tempbuffer) : EndIf
  If *client :  FreeMemory(*client) : EndIf
  If clientConnected
    If clientID : CloseNetworkConnection(clientID) : EndIf
  EndIf
  
  Debug "...cleanup stopped."

  
EndProcedure



; Main server loop
; Initialize HTTP Server on given Port
; creates new Thread on each new Connections
Procedure Main()
  
  Debug "Main startet..."
  If CreateNetworkServer(#PB_Any, 8080)
    
   Protected DelayInMs.i = 10

   Repeat
      
      Select NetworkServerEvent()

        Case #PB_NetworkEvent_Connect
          
          Protected connectionID = EventClient()
          Debug "new Client connecting"
          
          If connectionID >= 0
            
            Protected *client.ClientData = #Null
            *client = AllocateMemory(SizeOf(ClientData))
            
            Debug "Creating new thread..."

            If *client
              *client\ClientID = connectionID
              If CreateThread(@HandleClient(), *client) = 0
                Debug "Failed to create thread aborting..."
                CloseNetworkConnection(connectionID)
                FreeMemory(*client)
              EndIf
            Else
               CloseNetworkConnection(connectionID)
               Debug "Failed to create thread aborting..."
            EndIf
            
            Debug "...Thread created."
  
          EndIf
        

        Case #PB_NetworkEvent_None
          
          ;Free CPU Cycles
          Delay(DelayInMs)

      EndSelect
      
    ForEver

  EndIf

EndProcedure

only one module left:
but this is clean...

Code: Select all


EnableExplicit

; Declares
DeclareModule HttpResponse
  
  ; Constants
  #CONTENTTYPE = "text/plain; charset=UTF-8"

  Declare.s BuildHttpResponse(body.s, contentType.s = #CONTENTTYPE)
  Declare SendHttpResponse(clientID.i, body.s, contentType.s = #CONTENTTYPE)
  Declare SendHttpError(clientID.i, code.i, message.s)
  
EndDeclareModule

Module HttpResponse

  ; Create a complete HTTP 200 OK response string
  Procedure.s BuildHttpResponse(body.s, contentType.s = #CONTENTTYPE)
    
    Protected length = StringByteLength(body, #PB_UTF8)
    Protected header.s = "HTTP/1.1"
    
    ; OPTIONS without Body
    If length > 0 
      header + " 200 OK" + #CRLF$
    Else
      header + " 204 No Content" + #CRLF$      
    EndIf
    header + "Content-Type: " + contentType + #CRLF$
    header + "Content-Length: " + Str(length) + #CRLF$
    header + "Connection: close" + #CRLF$ + #CRLF$
    ProcedureReturn header + body
    
  EndProcedure
    

  ; Send full HTTP-Response to client
  Procedure SendHttpResponse(clientID.i, body.s, contentType.s = #CONTENTTYPE)
    
    Protected response.s = BuildHttpResponse(body, contentType)
    Protected size = StringByteLength(response, #PB_UTF8)
    Protected *mem = #Null
    *mem = AllocateMemory(size + 1)
    If *mem = #Null : ProcedureReturn : EndIf
    
    If *mem
      PokeS(*mem, response, -1, #PB_UTF8)
      SendNetworkData(clientID, *mem, size)
      FreeMemory(*mem)
    EndIf
    
  EndProcedure


  ; Send HTTP Errors
  Procedure SendHttpError(clientID.i, code.i, message.s)
    
    Protected body.s = Str(code) + " " + message
    Protected length = StringByteLength(body, #PB_UTF8)
    Protected header.s = "HTTP/1.1 " + Str(code) + " " + message + #CRLF$
    header + "Content-Type: " + #CONTENTTYPE + #CRLF$
    header + "Content-Length: " + Str(length) + #CRLF$
    header + "Connection: close" + #CRLF$ + #CRLF$
    
    Protected response.s = header + body
    Protected size = StringByteLength(response, #PB_UTF8)
    
    Protected *mem = #Null
    *mem = AllocateMemory(size + 1)
    If *mem = #Null : ProcedureReturn : EndIf
    
    If *mem
      PokeS(*mem, response, -1, #PB_UTF8)
      SendNetworkData(clientID, *mem, size)
      FreeMemory(*mem)  
    EndIf
    
  EndProcedure

EndModule
jakobssystems
User
User
Posts: 12
Joined: Thu Apr 10, 2025 5:17 pm

Re: Problems in Windows with HTTP-REST API Skeleton

Post by jakobssystems »

infratec wrote: Fri Apr 11, 2025 9:27 am Instead of

Code: Select all

If *client :  FreeMemory(*client) : EndIf
You should use:

Code: Select all

If *client :  FreeStructure(*client) : EndIf
But that is not a reason for the crash (I think)
FreeStructure in the Cleanup Procedure leads to crash Invalid Memory access (read error at address ... )
jakobssystems
User
User
Posts: 12
Joined: Thu Apr 10, 2025 5:17 pm

Re: Problems in Windows with HTTP-REST API Skeleton

Post by jakobssystems »

this is my last try to clean up everything:

Code: Select all

Structure ClientData
  ClientID.i
  AlreadyCleanedUp.i
EndStructure

Code: Select all

 If *client
    
    ; Check If already cleaned up somewhere Else
    If *client\AlreadyCleanedUp = 1
      ProcedureReturn
    Else
      
      ;Set Flag to true in order to prevent 2x ClosingNetworkConnection
      *client\AlreadyCleanedUp = 1
  
      ; Further checks before freeing memory
      If *buffer : FreeMemory(*buffer) : EndIf
      If *tempbuffer : FreeMemory(*tempbuffer) : EndIf
      If *client :  FreeMemory(*client) : EndIf
      If clientID : CloseNetworkConnection(clientID) : EndIf
      
    EndIf
    
  EndIf
it still crashes in windows.

Bottomline:

1. NetworkConnection on Windows acts differently and less gracefully than on Linux
2. A Race Condition in NetworkConnection (-> CloseNetworkConnection) on Windows is blocking any threaded use.

as soon as I comment it out, it works. in terms of, that it doesn't crash anymore.
Please prove me wrong
PeDe
Enthusiast
Enthusiast
Posts: 284
Joined: Sun Nov 26, 2017 3:13 pm

Re: Problems in Windows with HTTP-REST API Skeleton

Post by PeDe »

Your code, even with the last change, works here with Windows 7 x64 and PB v6.21b4 without any problems. I have 10 tabs open in the browser, which update themselves every second. The code does not crash.

Peter
User avatar
skywalk
Addict
Addict
Posts: 4218
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: Problems in Windows with HTTP-REST API Skeleton

Post by skywalk »

Is it Windows 11 problem?
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
infratec
Always Here
Always Here
Posts: 7618
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Problems in Windows with HTTP-REST API Skeleton

Post by infratec »

It does not crash here at the CloseConnection() line (WIn10 x64 PB 621b2 x86)

But it crashed once in Select NetworkServerEvent()

So I modified it a bit and then it runs smooth:

Code: Select all

Procedure Main()
  
  Protected.i Server
  
  Debug "Main startet..."
  Server = CreateNetworkServer(#PB_Any, 8080)
  If Server
    
   Protected DelayInMs.i = 10

   Repeat
      
      Select NetworkServerEvent(Server)
Post Reply