If someone needs it too, I'll share my progress here :
Progress :
Code18/12/07 : Cookie and string format
Added : Cookie support, see the example to learn how to add a cookie to a response and how to read a cookie value from a request. /!\ The GetCookie() procedure won't work with space in you cookie value! /!\ That's good enough for my need though.
Changed : you can now set the string format, UTF-8 is used by default.
18/12/07 : Padding size fix
The padding was calculated but not included in the packet.
18/12/04 : POC.
It only implements a small fraction of the FCGI protocol and it's prone to memory leak. It's a good enough start to learn about the FCGI protocol.
Code: Select all
DeclareModule FastCGI
; Server
Declare Open(Port, *Callback, BindedIP.s = "") ;Create a FCGI Application on the given port. Return a Server object if succeed or 0 otherwise. Callback format : Callback(Request)
Declare Close(*Server) ;Close the given Server.
; Request
Declare.s GetParameter(*Request, Parameter.s) ;Return the value of the give parameter if it exists.
Declare WriteResponseHeader(*Request, Header.s, Value.s) ;Write a header to the response
Declare WriteResponseString(*Request, String.s) ;Write a string to the response
Declare FinishReponse(*Request) ;Send the response
EndDeclareModule
Module FastCGI
EnableExplicit
;{ private variables declaration
#__VERSION = 1
#__ROLE_RESPONDER = 1
#__ROLE_AUTHORIZER = 2
#__ROLE_FILTER = 3
#__TYPE_BEGIN = 1
#__TYPE_ABORT = 2
#__TYPE_END = 3
#__TYPE_PARAMS = 4
#__TYPE_STDIN = 5
#__TYPE_STDOUT = 6
#__TYPE_STDERR = 7
#__TYPE_DATA = 8
#__TYPE_GETVALUES = 9
#__TYPE_GETVALUES_RESULT = 10
#__TYPE_UNKOWNTYPE = 11
Structure Server
ServerID.i
Thread.i
Stop.i
*Callback
EndStructure
Structure Record
Version.a
Type.a
RequestIdB1.a
RequestIdB0.a
ContentLengthB1.a
ContentLengthB0.a
PaddingLength.a
Reserved.a
EndStructure
Structure Request
ClientID.i
RequestIdB1.a
RequestIdB0.a
*Thread
Map Variable.s()
Map Response.s()
List ResponseData.i()
EndStructure
#__HEADER_SIZE = SizeOf(Record)
;}
;{ Private procedures declaration
Declare ServerThread(Server)
Declare ProcessPairs(*Data, *Request.Request ,Lenght)
;}
;{ Public procedures
;- Server
Procedure Close(*Server.Server)
; Wow, no mutex? Well... I feel like living dangerously! (Also, I need to test : I don't think it can be a problem...)
*Server\Stop = #True
EndProcedure
Procedure Open(Port, *Callback, BindedIP.s = "")
Protected Server, *ServerData.Server
Server = CreateNetworkServer(#PB_Any,Port,#PB_Network_TCP,BindedIP)
If Server
*ServerData.Server = AllocateMemory(SizeOf(Server))
*ServerData\ServerID = Server
*ServerData\Thread = CreateThread(@ServerThread(),*ServerData)
*ServerData\Callback = *Callback
EndIf
ProcedureReturn *ServerData
EndProcedure
;- Request
Procedure.s GetParameter(*Request.Request, Parameter.s)
ProcedureReturn *Request\Variable(Parameter)
EndProcedure
Procedure WriteResponseHeader(*Request.Request, Header.s, Value.s)
*Request\Response(Header) = Value
EndProcedure
Procedure WriteResponseString(*Request.Request, String.s)
AddElement(*Request\ResponseData())
*Request\ResponseData() = AllocateMemory(StringByteLength(String,#PB_Ascii),#PB_Memory_NoClear)
PokeS(*Request\ResponseData(),String,-1,#PB_Ascii|#PB_String_NoZero) ; // Temp
EndProcedure
Procedure FinishReponse(*Request.Request)
Protected Size, PaddingSize, Position
Protected *Record.Record
Protected *Data
; 1- Calculate the packet size
ForEach *Request\Response()
Size + StringByteLength(MapKey(*Request\Response())+*Request\Response(),#PB_Ascii) + 3 ; Le plus 3 se décompose comme ça : 1 = LF, 2 = ": "
Next
ForEach *Request\ResponseData()
Size + MemorySize(*Request\ResponseData())
Next
Size +1 ; le dernier LF
PaddingSize = Bool(Size % 8 > 0) * (8 -Size % 8)
*Data = AllocateMemory(#__HEADER_SIZE + Size + PaddingSize + #__HEADER_SIZE + 8)
*Record = *Data
*Record\ContentLengthB0 = Size
*Record\ContentLengthB1 = Size >> 8
*Record\RequestIdB0 = *Request\RequestIdB0
*Record\RequestIdB1 = *Request\RequestIdB1
*Record\Type = #__TYPE_STDOUT
*Record\Version = #__VERSION
*Record\PaddingLength = PaddingSize
Position = #__HEADER_SIZE
ForEach *Request\Response()
Position + PokeS(*Data + Position,MapKey(*Request\Response())+": "+*Request\Response()+#LF$,-1,#PB_Ascii|#PB_String_NoZero)
Next
Position + PokeS(*Data + Position,#LF$,-1,#PB_Ascii|#PB_String_NoZero)
ForEach *Request\ResponseData()
CopyMemory(*Request\ResponseData(),*Data + Position,MemorySize(*Request\ResponseData()))
Position + MemorySize(*Request\ResponseData())
FreeMemory(*Request\ResponseData())
DeleteElement(*Request\ResponseData())
Next
Position + PaddingSize
*Record = *Data + Position
*Record\ContentLengthB0 = 8
*Record\ContentLengthB1 = 0
*Record\RequestIdB0 = *Request\RequestIdB0
*Record\RequestIdB1 = *Request\RequestIdB1
*Record\Type = #__TYPE_END
*Record\Version = #__VERSION
*Record\PaddingLength = 0
SendNetworkData(*Request\ClientID,*Data,MemorySize(*Data))
FreeMemory(*Data)
EndProcedure
;}
;{ Private procedures
Procedure ServerThread(*ServerData.Server)
Protected Lenght, ContentLenght
Protected NewMap ClientMap.Request(), *Request.Request, *Record.Record = AllocateMemory(#__HEADER_SIZE,#PB_Memory_NoClear), *Data = AllocateMemory(65535,#PB_Memory_NoClear)
Repeat
Select NetworkServerEvent(*ServerData\ServerID)
Case #PB_NetworkEvent_None
Delay(1)
Case #PB_NetworkEvent_Connect
*Request = AddMapElement(ClientMap(), Str(EventClient()),#PB_Map_NoElementCheck)
*Request\ClientID = EventClient()
Case #PB_NetworkEvent_Data
*Request = FindMapElement(ClientMap(), Str(EventClient()))
While ReceiveNetworkData(*Request\ClientID,*Record,#__HEADER_SIZE) > 0
ContentLenght = (*Record\contentLengthB1 <<8 + *Record\contentLengthB0)
Select *Record\type
Case #__TYPE_BEGIN
*Request\RequestIdB0 = *Record\RequestIdB0
*Request\RequestIdB1 = *Record\RequestIdB1
If ContentLenght
Lenght = ReceiveNetworkData(*Request\ClientID,*Data,ContentLenght)
ProcessPairs(*Data, *Request,Lenght)
EndIf
Case #__TYPE_ABORT
Case #__TYPE_END
Case #__TYPE_PARAMS
If ContentLenght
Lenght = ReceiveNetworkData(*Request\ClientID,*Data,ContentLenght)
ProcessPairs(*Data, *Request,Lenght)
EndIf
Case #__TYPE_STDIN
If ContentLenght
Lenght = ReceiveNetworkData(*Request\ClientID,*Data,ContentLenght)
ProcessPairs(*Data, *Request,Lenght)
EndIf
*Request\Thread = CreateThread(*ServerData\Callback,*Request)
Case #__TYPE_STDOUT
Case #__TYPE_STDERR
Case #__TYPE_DATA
Case #__TYPE_GETVALUES
Case #__TYPE_GETVALUES_RESULT
Case #__TYPE_UNKOWNTYPE
EndSelect
If *Record\PaddingLength
ReceiveNetworkData(*Request\ClientID,*Data,*Record\PaddingLength)
EndIf
Wend
Case #PB_NetworkEvent_Disconnect
DeleteMapElement(ClientMap(),Str(EventClient()))
EndSelect
If *ServerData\Stop
Break
EndIf
ForEver
CloseNetworkServer(*ServerData\ServerID)
ProcedureReturn #Null
EndProcedure
Procedure ProcessPairs(*Data, *Request.Request ,Lenght)
EnableExplicit
Protected Progress, namelenght, valuelenght,Name.s, Value.s
While Progress < lenght
namelenght = PeekA(*data + Progress)
If namelenght = 128
namelenght = ((PeekA(*data + Progress) & $7f) << 24) + (PeekA(*data + Progress + 1) << 16) + (PeekA(*data + Progress + 2) << 8) + PeekA(*data + Progress + 3);
Progress + 3
EndIf
Progress + 1
valuelenght = PeekA(*data + Progress)
If valuelenght = 128
valuelenght = ((PeekA(*data + Progress) & $7f) << 24) + (PeekA(*data + Progress + 1) << 16) + (PeekA(*data + Progress + 2) << 8) + PeekA(*data + Progress + 3);
Progress + 3
EndIf
Progress + 1
Name = PeekS(*data + Progress,namelenght,#PB_Ascii)
If Len(Name)
AddMapElement(*Request\Variable(),PeekS(*data + Progress,namelenght,#PB_Ascii),#PB_Map_NoElementCheck)
Progress + namelenght
*Request\Variable() = PeekS(*data + Progress,valuelenght,#PB_Ascii)
Progress +valuelenght
;Debug MapKey(*Request\Variable()) + " : "+ *Request\Variable()
EndIf
Wend
DisableExplicit
EndProcedure
;}
EndModule
CompilerIf #PB_Compiler_IsMainFile
; Demo
Procedure Handler_FCGIRequest(Request)
FastCGI::WriteResponseHeader(Request,#PB_CGI_HeaderContentType,"text/html")
FastCGI::WriteResponseString(Request,"<html><title>PureBasic - FastCGI</title><body>" +
"Hello from PureBasic FastCGI !<br>" +
"Actual time: <b>"+FormatDate("%hh:%ii", Date()) + "</b>" +
"</body></html>")
FastCGI::FinishReponse(Request)
EndProcedure
OpenConsole("fastCGI Demo")
InitNetwork()
Server = FastCGI::Open(5600,@Handler_FCGIRequest())
Input()
FastCGI::Close(Server)
PrintN("Server closed")
Input()
End
CompilerEndIf