It is currently Mon Oct 21, 2019 11:56 am

All times are UTC + 1 hour




Post new topic Reply to topic  [ 1 post ] 
Author Message
 Post subject: [WIP] FastCGI
PostPosted: Tue Dec 04, 2018 9:50 pm 
Offline
Enthusiast
Enthusiast

Joined: Tue Jan 25, 2005 7:01 pm
Posts: 460
Location: Canada
The implementation of FastCGI in PB is very limited (it's actually closer to SCGI than FCGI), and I need to write a real FCGI application... So I gave it a try : FastCGI being 22 years old, it's really simple and it only took me a few hours to have a working proof of concept.
If someone needs it too, I'll share my progress here :

Progress :
Quote:
18/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
Code:
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


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 1 post ] 

All times are UTC + 1 hour


Who is online

Users browsing this forum: blueb and 17 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  

 


Powered by phpBB © 2008 phpBB Group
subSilver+ theme by Canver Software, sponsor Sanal Modifiye