PureBasic

Forums PureBasic
Nous sommes le Lun 17/Déc/2018 10:44

Heures au format UTC + 1 heure




Poster un nouveau sujet Répondre au sujet  [ 4 messages ] 
Auteur Message
 Sujet du message: [WIP] FastCGI
MessagePosté: Mar 04/Déc/2018 21:58 
Hors ligne
Avatar de l’utilisateur

Inscription: Sam 31/Juil/2004 22:32
Messages: 1131
L'implémentation du Fast CGI dans PB est très limitée, elle est en fait plus proche du SCGI que du FCGI. J'ai besoin d'écrire une application FCGI, je me suis donc demandé si je devais passer à du C ou si je pouvais réimplémenter le protocole en PB...
Il se trouve que c'était assez rapide à faire : le Fast CGI date de 1996 et est donc plutôt simple, et il ne m'a fallu que quelques heures de travail pour arriver à un POC.
Si quelqu'un d'autre en a besoin, je partage mon travail ici :

Avancement :
Citation:
18/12/10 : Répondre autre chose que du texte.
Ajout de WriteResponseData(). Pour la prochaine étape, je vais devoir me pencher sur la découpe des réponses trop grosse en plusieurs records : un record fcgi est en effet limité à 2^16 octets.
18/12/09 : Moar cookies
Ajout de la fonction FastCGI::GetCookie(), pour ne pas avoir besoin de bidouiller avec les paramètres pour récupérer la valeur d'un cookie. /!\ Elle va déconner plein tube si il y a un espace dans le cookie, faudrait écrire une regex mais je suis pas habitué à PCRE et, en l'état, ça suffit pour mes besoins.
18/12/08 : Cookies & string format
Ajout du support des cookies, ajout d'une options d'encodage des strings (par défaut en UTF8)
18/12/07 : Padding size fix
Fix une erreur grossière avec le padding des packets : Il était calculé mais pas inclus dans les packets... Par un hasard malheureux, tous mes packets de tests avaient une taille multiple de 8, le bug était donc passé sous le radar.
18/12/04 : POC.
Seule une petite partie du protocole FCGI est implémentée et il y a pas mal de risque de memory leaks, mais les bases sont suffisantes pour qui veut étudier ou s'amuser avec le FCGI

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 FinishReponse(*Request)                                                                  ;Send the response
   Declare.s GetCookie(*Request, Cookie.s)                                                      ;Return the value of the given
   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 WriteResponseData(*Request,*Buffer, Lenght)                                    ;Add data to the response
   Declare WriteResponseString(*Request, String.s, Format = #PB_UTF8)            ;Add a string to 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
      Alive.a
      Responded.a
      Map Variable.s()
      Map Response.s()
      List Cookie.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 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
      
      ForEach *Request\Cookie()
         Size + StringByteLength(*Request\Cookie(),#PB_Ascii) + 13 ; 12 is for "Set-Cookie: ", 1 = LF
      Next
      
      Size +1 ; le dernier LF
      PaddingSize = Bool(Size % 8 > 0) * (8 -Size % 8)
      
      ; 2- Allocate memory and fill the needed records
      *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
      
      ForEach *Request\Cookie()
         Position + PokeS(*Data + Position,"Set-Cookie: " + *Request\Cookie()+#LF$,-1,#PB_Ascii)
      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
      
      ; 3- write the END record
      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
      
      *Request\Responded = #True
      
      SendNetworkData(*Request\ClientID,*Data,MemorySize(*Data))
      FreeMemory(*Data)     
   EndProcedure
   
   Procedure.s GetCookie(*Request, Cookie.s)
      Protected.s Parameter = FastCGI::GetParameter(*Request,"HTTP_COOKIE"), Result
      Protected Position1,Position2
      
      Cookie + "="
      Position1 = FindString(Parameter, Cookie)
      
      If Position1
         Position1 + Len(Cookie)
         Position2 = FindString(Parameter, " ",Position1)
         
         If Position2 = 0
            Position2 = Len(Parameter)
         EndIf
         
         Result = Mid(Parameter,Position1,Position2-Position1+1)
         
      EndIf
      
      ProcedureReturn Result
   EndProcedure
   
   Procedure.s GetParameter(*Request.Request, Parameter.s)
      ProcedureReturn *Request\Variable(Parameter)
   EndProcedure
   
   Procedure WriteResponseHeader(*Request.Request, Header.s, Value.s)
      If Header = #PB_CGI_HeaderSetCookie
         AddElement(*Request\Cookie())
         *Request\Cookie() =Value
      Else
         *Request\Response(Header) = Value
      EndIf
   EndProcedure
   
   Procedure WriteResponseString(*Request.Request, String.s, Format = #PB_UTF8)
      AddElement(*Request\ResponseData())
      *Request\ResponseData() = AllocateMemory(StringByteLength(String,Format),#PB_Memory_NoClear)
      PokeS(*Request\ResponseData(),String,-1,Format|#PB_String_NoZero) ; // Temp
   EndProcedure
   
   Procedure WriteResponseData(*Request.Request,*Buffer, Lenght)
      AddElement(*Request\ResponseData())
      *Request\ResponseData() = AllocateMemory(Lenght,#PB_Memory_NoClear)
      CopyMemory(*Buffer,*Request\ResponseData(),Lenght)
   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()
               *Request\Alive = #True
            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
                        
                        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
               *Request = FindMapElement(ClientMap(), Str(EventClient()))
               If *Request\Responded
                  DeleteMapElement(ClientMap(),Str(EventClient()))
               Else
                  *Request\Alive = #False
               EndIf
         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)
      Debug FastCGI::GetCookie(Request,"acookie")
      FastCGI::WriteResponseHeader(Request,#PB_CGI_HeaderContentType,"text/html")
      FastCGI::WriteResponseString(Request,~"<head><meta charset=\"UTF-8\"></head>" +
                                           "<html><title>PureBasic - FastCGI</title><body>" +
                                           "Hello from PureBasic Re-FCGI くも!<br>" +
                                           "Actual time: <b>"+FormatDate("%hh:%ii", Date()) + "</b>" +
                                           "</body></html>")
      FastCGI::WriteResponseHeader(Request,#PB_CGI_HeaderSetCookie,"acookie=avalue")
      FastCGI::FinishReponse(Request)
   EndProcedure
   
   OpenConsole("fastCGI Demo")
   
   InitNetwork()
   Server = FastCGI::Open(5600,@Handler_FCGIRequest())
   
   Input()
   FastCGI::Close(Server)
   PrintN("Server closed")
   
   Input()
   End
CompilerEndIf


Dernière édition par poshu le Lun 10/Déc/2018 13:42, édité 8 fois.

Haut
 Profil  
Répondre en citant le message  
 Sujet du message: Re: [WIP] FastCGI
MessagePosté: Mar 04/Déc/2018 22:58 
Hors ligne

Inscription: Jeu 07/Juin/2007 22:54
Messages: 206
Merci pour le partage, il va falloir faire des essais avec cette lib ;)

Euh entre ton EndDeclareModule et ton Module FastCGI y'a un "WriteCGIString(" qui traine pas sur qu'il ai grand chose à foutre ici :p


Haut
 Profil  
Répondre en citant le message  
 Sujet du message: Re: [WIP] FastCGI
MessagePosté: Ven 07/Déc/2018 9:54 
Hors ligne
Avatar de l’utilisateur

Inscription: Sam 31/Juil/2004 22:32
Messages: 1131
boby a écrit:
Merci pour le partage, il va falloir faire des essais avec cette lib ;)

Euh entre ton EndDeclareModule et ton Module FastCGI y'a un "WriteCGIString(" qui traine pas sur qu'il ai grand chose à foutre ici :p

... En effet. L’incompétence, ça ne se soigne pas é___è;

J'en profite pour fixe un petit bug aussi... Qui était lui aussi une belle gamelle de ma part (⁄ ⁄•⁄ω⁄•⁄ ⁄)


Haut
 Profil  
Répondre en citant le message  
 Sujet du message: Re: [WIP] FastCGI
MessagePosté: Sam 08/Déc/2018 16:01 
Hors ligne
Avatar de l’utilisateur

Inscription: Sam 31/Juil/2004 22:32
Messages: 1131
Petite maj, avec les cookies et le support des characters internationaux.


Haut
 Profil  
Répondre en citant le message  
Afficher les messages postés depuis:  Trier par  
Poster un nouveau sujet Répondre au sujet  [ 4 messages ] 

Heures au format UTC + 1 heure


Qui est en ligne

Utilisateurs parcourant ce forum: Aucun utilisateur enregistré et 3 invités


Vous ne pouvez pas poster de nouveaux sujets
Vous ne pouvez pas répondre aux sujets
Vous ne pouvez pas éditer vos messages
Vous ne pouvez pas supprimer vos messages

Rechercher:
Aller à:  

 


Powered by phpBB © 2008 phpBB Group | Traduction par: phpBB-fr.com
subSilver+ theme by Canver Software, sponsor Sanal Modifiye