Problems in Windows with HTTP-REST API Skeleton
-
- User
- Posts: 12
- Joined: Thu Apr 10, 2025 5:17 pm
Problems in Windows with HTTP-REST API Skeleton
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?
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?
-
- User
- Posts: 12
- Joined: Thu Apr 10, 2025 5:17 pm
Re: Problems in Windows with HTTP-REST API Skeleton
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.
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.
Re: Problems in Windows with HTTP-REST API Skeleton
You should enable the Purifier, e.g. here:
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
Code: Select all
Protected size = StringByteLength(response, #PB_UTF8)
Protected *mem = #Null
*mem = AllocateMemory(size)
If *mem
PokeS(*mem, response, -1, #PB_UTF8)
I didn't go on checking, that code is too confusing
{Home}.:|:.{Dialog Design0R}.:|:.{Codes}.:|:.{History Viewer Online}.:|:.{Send a Beer}
-
- User
- Posts: 12
- Joined: Thu Apr 10, 2025 5:17 pm
Re: Problems in Windows with HTTP-REST API Skeleton
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 

Re: Problems in Windows with HTTP-REST API Skeleton
It's usually the other way round windows is generally more forgiving than linux when it comes to threads.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![]()
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
-
- User
- Posts: 12
- Joined: Thu Apr 10, 2025 5:17 pm
Re: Problems in Windows with HTTP-REST API Skeleton
Good Morning,
I can bring it down to "CloseNetworkConnection" in my rest-api-skeleton.pb, cleaning procedure:
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.
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
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.
Re: Problems in Windows with HTTP-REST API Skeleton
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:
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
Re: Problems in Windows with HTTP-REST API Skeleton
Instead of
You should use:
But that is not a reason for the crash (I think)
Code: Select all
If *client : FreeMemory(*client) : EndIf
Code: Select all
If *client : FreeStructure(*client) : EndIf
Re: Problems in Windows with HTTP-REST API Skeleton
Btw:
instead of:
You can simply use:
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
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
-
- User
- Posts: 12
- Joined: Thu Apr 10, 2025 5:17 pm
Re: Problems in Windows with HTTP-REST API Skeleton
Here we go, kicked everything out.Any chance to try to remove all unneeded part while still preserving the issue ?
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
-
- User
- Posts: 12
- Joined: Thu Apr 10, 2025 5:17 pm
Re: Problems in Windows with HTTP-REST API Skeleton
FreeStructure in the Cleanup Procedure leads to crash Invalid Memory access (read error at address ... )infratec wrote: Fri Apr 11, 2025 9:27 am Instead ofYou should use:Code: Select all
If *client : FreeMemory(*client) : EndIf
But that is not a reason for the crash (I think)Code: Select all
If *client : FreeStructure(*client) : EndIf
-
- User
- Posts: 12
- Joined: Thu Apr 10, 2025 5:17 pm
Re: Problems in Windows with HTTP-REST API Skeleton
this is my last try to clean up everything:
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
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
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
Re: Problems in Windows with HTTP-REST API Skeleton
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
Peter
Re: Problems in Windows with HTTP-REST API Skeleton
Is it Windows 11 problem?
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
Re: Problems in Windows with HTTP-REST API Skeleton
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:
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)