ISAPI / FastCGI

Für allgemeine Fragen zur Programmierung mit PureBasic.
Benutzeravatar
uweb
Beiträge: 461
Registriert: 13.07.2005 08:39

ISAPI / FastCGI

Beitrag von uweb »

Ich bin per Zufall im englischen Forum (http://forums.purebasic.com/english/vie ... php?t=4325)
auf ein Listing von freak gestoßen. Da ich das für eine echte Perle halte und dort keine Resonanz war,
will ich das Thema hier noch einmal aufgreifen.

Zunächst einmal 10.000 Dank an freak !

ISAP ist etwas vereinfacht gesagt wie FastCGI eine Möglichkeit auf dem
Webserver sprachenunabhängig compilierte "Skripte" die permanent
im Speicher bleiben laufen zu lassen.
ISAP kann aber auch als Filter arbeiten und dadurch etwas mehr.
Mit FastCGI in PureBasic beschäftige ich mich auch gerade aber da bin ich noch nicht so weit.

Da es bei mir mit der Methode POST zu Serverabstürzen kam habe ich mich mal etwas im Internet umgesehen.
Unter http://people.freenet.de/j-hummel/progr ... /index.htm habe ich einiges gefunden.
Ich habe zwar noch nie etwas mit Pascal gemacht, aber die Erklärungen dort sind auch so verständlich.

Ich denke die Abstürze kamen durch

Code: Alles auswählen

PeekS(*ECB\cbTotalBytes)
und

Code: Alles auswählen

PeekS(*ECB\cbAvailable)
Ich habe mir erlaubt das Listing etwas zu bearbeiten und in den Bemerkungen j-hummel zu zitieren.
Auch an ihn : Vielen Dank !

>>> Über Feedback ob das sonst noch jemanden interessiert bzw
>>> ob vielleicht jemand etwas zum Thema ISAPI-Filter
>>> oder FastCGI beitragen kann würde ich mich sehr freuen.

Code: Alles auswählen

;- ISAPI Constants

#HSE_VERSION_MAJOR                       =   4      ;/* major version of this spec */
#HSE_VERSION_MINOR                       =   0      ;/* minor version of this spec */
#HSE_LOG_BUFFER_LEN                      =  80
#HSE_MAX_EXT_DLL_NAME_LEN                = 256

#HSE_VERSION = #HSE_VERSION_MINOR + #HSE_VERSION_MAJOR << 16

#HSE_STATUS_SUCCESS                      = 1
#HSE_STATUS_SUCCESS_AND_KEEP_CONN        = 2
#HSE_STATUS_PENDING                      = 3
#HSE_STATUS_ERROR                        = 4

#HSE_REQ_BASE                            = 0
#HSE_REQ_SEND_URL_REDIRECT_RESP          = #HSE_REQ_BASE + 1
#HSE_REQ_SEND_URL                        = #HSE_REQ_BASE + 2
#HSE_REQ_SEND_RESPONSE_HEADER            = #HSE_REQ_BASE + 3
#HSE_REQ_DONE_WITH_SESSION               = #HSE_REQ_BASE + 4
#HSE_REQ_END_RESERVED                    = 1000

#HSE_REQ_MAP_URL_TO_PATH                 = #HSE_REQ_END_RESERVED +  1
#HSE_REQ_GET_SSPI_INFO                   = #HSE_REQ_END_RESERVED +  2
#HSE_APPEND_LOG_PARAMETER                = #HSE_REQ_END_RESERVED +  3
#HSE_REQ_SEND_URL_EX                     = #HSE_REQ_END_RESERVED +  4
#HSE_REQ_IO_COMPLETION                   = #HSE_REQ_END_RESERVED +  5
#HSE_REQ_TRANSMIT_FILE                   = #HSE_REQ_END_RESERVED +  6
#HSE_REQ_REFRESH_ISAPI_ACL               = #HSE_REQ_END_RESERVED +  7
#HSE_REQ_IS_KEEP_CONN                    = #HSE_REQ_END_RESERVED +  8
#HSE_REQ_ASYNC_READ_CLIENT               = #HSE_REQ_END_RESERVED + 10
#HSE_REQ_GET_IMPERSONATION_TOKEN         = #HSE_REQ_END_RESERVED + 11
#HSE_REQ_MAP_URL_TO_PATH_EX              = #HSE_REQ_END_RESERVED + 12
#HSE_REQ_ABORTIVE_CLOSE                  = #HSE_REQ_END_RESERVED + 14
#HSE_REQ_GET_CERT_INFO_EX                = #HSE_REQ_END_RESERVED + 15
#HSE_REQ_SEND_RESPONSE_HEADER_EX         = #HSE_REQ_END_RESERVED + 16

#HSE_TERM_ADVISORY_UNLOAD                = $00000001
#HSE_TERM_MUST_UNLOAD                    = $00000002

#HSE_IO_SYNC                             = $00000001   ;/* For WriteClient */
#HSE_IO_ASYNC                            = $00000002   ;/* For WriteClient/TF*/
#HSE_IO_DISCONNECT_AFTER_SEND            = $00000004   ;/* For TF   */
#HSE_IO_SEND_HEADERS                     = $00000008   ;/* For TF   


;- ISAPI Structures

Structure HSE_VERSION_INFO
  dwExtensionVersion.l        ; store #HSE_VERSION here
  lpszExtensionDesc.b[#HSE_MAX_EXT_DLL_NAME_LEN] ; store a description string of your dll here
EndStructure


Structure EXTENSION_CONTROL_BLOCK
  cbSize.l                    ; Size of this structure.
  dwVersion.l                 ; Version info of this specification.
  ConnID.l                    ; Context number not to be modified!
  dwHttpStatusCode.l          ; HTTP Status code.
  lpszLogData.b[#HSE_LOG_BUFFER_LEN]; Null-terminated log info.
  lpszMethod.l                ; REQUEST_METHOD 
  lpszQueryString.l           ; QUERY_STRING 
  lpszPathInfo.l              ; PATH_INFO 
  lpszPathTranslated.l        ; PATH_TRANSLATED 
  cbTotalBytes.l              ; Total bytes indicated from client.
  cbAvailable.l               ; Available number of bytes.
  lpbData.l                   ; Pointer to cbAvailable bytes.
  lpszContentType.l           ; Content type of client data. 
  GetServerVariable.l         ; Pointers to the Server callback functions
  WriteClient.l
  ReadClient.l
  ServerSupportFunction.l
EndStructure


; The *ECB pointer is passed by the Server to the main dll function. We make it
; global for the callback wrappers to access.
;
Global *ECB.EXTENSION_CONTROL_BLOCK

; A mutex needed to synchronize any threaded acces
;
Global AccessMutex


;
; Wrappers to the Server callbacks for easier access
;

Procedure.s GetServerVariable(Name$)
  ; BOOL WINAPI GetServerVariable(HCONN hConn, LPSTR lpszVariableName,LPVOID lpvBuffer,LPDWORD lpdwSizeofBuffer);
 
  ResultBuffer$ = Space(1000)
  BufferLength    = 1000
  CallFunctionFast(*ECB\GetServerVariable, *ECB\ConnID, @Name$, @ResultBuffer$, @BufferLength)

  ProcedureReturn ResultBuffer$ 
EndProcedure


Procedure ReadClient(*Buffer, *BufferSize)
  ; BOOL ReadClient(HCONN hConn, LPVOID lpvBuffer, LPDWORD lpdwSize);

  ProcedureReturn CallFunctionFast(*ECB\ReadClient, *ECB\ConnID, *Buffer, *BufferSize)
EndProcedure



Procedure WriteClient(*Buffer, *BufferSize)
  ; BOOL WriteClient(HCONN ConnID, LPVOID Buffer, LPDWORD lpdwBytes, DWORD dwSync);

  ProcedureReturn CallFunctionFast(*ECB\WriteClient, *ECB\ConnID, *Buffer, *BufferSize, #HSE_IO_SYNC)
EndProcedure



Procedure ServerSupportFunction(RequestID, *Buffer, *BufferSize, *DataType)
  ; BOOL ServerSupportFunction(HCONN ConnID, DWORD dwHSERRequest, LPVOID lpvBuffer, LPDWORD lpdwSize, LPDWORD lpdwDataType);

  ProcedureReturn CallFunctionFast(*ECB\ServerSupportFunction, *ECB\ConnID, RequestID, *Buffer, *BufferSize, *DataType)
EndProcedure


;
;- Some helper functions to make live even easier. they are just the same callback functions
;  called with strings as parameter
;

Procedure.s ReceiveString(MaxLength)
 
  ResultBuffer$ = Space(MaxLength)
  CallFunctionFast(*ECB\ReadClient, *ECB\ConnID, @ResultBuffer$, @MaxLength)
 
  ProcedureReturn PeekS(@ResultBuffer$, MaxLength) ; cut the string shorter if MaxLength was changed
EndProcedure


Procedure SendString(String$)

  Length = Len(String$)
  ProcedureReturn CallFunctionFast(*ECB\WriteClient, *ECB\ConnID, @String$, @Length, #HSE_IO_SYNC)
EndProcedure


Procedure SendHeader(Header$)
 
  Length = Len(Header$)
  ProcedureReturn CallFunctionFast(*ECB\ServerSupportFunction, *ECB\ConnID, #HSE_REQ_SEND_RESPONSE_HEADER, "200 OK", @Length, @Header$)
 
EndProcedure


;
;- Required DLL Functions
;


; This function is called once the dll is loaded
; you must return #TRUE to tell that the dll can be used
; you must also fill in the HSE_VERSION_INFO structure

ProcedureDLL GetExtensionVersion(*pVer.HSE_VERSION_INFO)

  PokeS(@*pVer\lpszExtensionDesc[0], "pure-isapi-001.dll")
  *pVer\dwExtensionVersion = #HSE_VERSION
 
  ; also initialize the mutex to syncronize the threads
  ;
  AccessMutex = CreateMutex_(0, 0, 0)
 
  ProcedureReturn #True
EndProcedure


; this is the main function called for each request
; the EXTENSION_CONTROL_BLOCK provides the needed info

ProcedureDLL HttpExtensionProc(*lpECB.EXTENSION_CONTROL_BLOCK)

  If WaitForSingleObject_(AccessMutex, 5000) = 0
    ; wait succeeded, so anything can be done here now without worrying
    ; about threads
   
    ; make the control structure pointer global for better access
    *ECB = *lpECB
   
    PokeS(@*ECB\lpszLogData[0],"PureBasic-DLL")
    PokeW(@*ECB\dwHttpStatusCode,200)

    ; ok, we can now process the request...
    ; lets simply output the input stuff + some more information
    ;
   
    ; send the header first
    ;
       
    Output$ + "<html><body><br><h1><u>PureBasic ISAPI extension example</u></h1><br><br>"
;********    
    Output$ + "ReceiveString:<br>" + ReceiveString(4096) + "<br><br>"
;********    
   
    Output$ + "<b>Information in the extension control block structure:</b><table><br><br>"
    Output$ + "<tr><td><i>LogData:</i></td><td>" + PeekS(@*ECB\lpszLogData[0]) + "</td></tr>"
    Output$ + "<tr><td><i>REQUEST_METHOD</i></td><td>" + PeekS(*ECB\lpszMethod) + "</td></tr>"
    Output$ + "<tr><td><i>QUERY_STRING</i></td><td>" + PeekS(*ECB\lpszQueryString) + "</td></tr>"
    Output$ + "<tr><td><i>PATH_INFO</i></td><td>" + PeekS(*ECB\lpszPathInfo) + "</td></tr>"
    Output$ + "<tr><td><i>PATH_TRANSLATED</i></td><td>" + PeekS(*ECB\lpszPathTranslated) + "</td></tr>"
    If PeekS(*ECB\lpszMethod) = "GET" Or PeekS(*ECB\lpszMethod) = "Get" Or PeekS(*ECB\lpszMethod) = "get"
      Output$ + "<tr><td><i>Total bytes by client</i></td><td>" + PeekS(*ECB\cbTotalBytes) + "</td></tr>"
      Output$ + "<tr><td><i>available bytes</td></i><td>" + PeekS(*ECB\cbAvailable) + "</td></tr>"
;     sind bei GET leer :
      Output$ + "<tr><td><i>Contenttype of data</i></td><td>" + PeekS(*ECB\lpszContentType) + "</td></tr>"           
      Output$ + "<tr><td><i>lpbData</i></td><td>" + PeekS(*ECB\lpbData) + "</td></tr>"           
    Else
;      vermutlich 4 Byte aber ohne Vorzeichen - max 4 GB -> warten auf PB Version 4.x    
;      Output$ + "<tr><td><i>Total bytes by client</i></td><td>" + PeekS(*ECB\cbTotalBytes) + "</td></tr>"
;      Output$ + "<tr><td><i>available bytes</td></i><td>" + PeekS(*ECB\cbAvailable) + "</td></tr>"

      Output$ + "<tr><td><i>Contenttype of data</i></td><td>" + PeekS(*ECB\lpszContentType) + "</td></tr>"           
      Output$ + "<tr><td><i>lpbData</i></td><td>" + PeekS(*ECB\lpbData) + "</td></tr>"           
    EndIf
    Output$ + "</table><br><br>"
   
    Output$ + "<b>Output of the available data</b><table><br><br>"
    
    Output$ + "<tr><td><i>AUTH_TYPE:</i></td><td>" + GetServerVariable("AUTH_TYPE") + "</td></tr>"
    Output$ + "<tr><td><i>CONTENT_LENGTH:</i></td><td>" + GetServerVariable("CONTENT_LENGTH") + "</td></tr>"
    Output$ + "<tr><td><i>CONTENT_TYPE:</i></td><td>" + GetServerVariable("CONTENT_TYPE") + "</td></tr>"
    Output$ + "<tr><td><i>PATH_INFO:</i></td><td>" + GetServerVariable("PATH_INFO") + "</td></tr>"
    Output$ + "<tr><td><i>PATH_TRANSLATED:</i></td><td>" + GetServerVariable("PATH_TRANSLATED") + "</td></tr>"
    Output$ + "<tr><td><i>QUERY_STRING:</i></td><td>" + GetServerVariable("QUERY_STRING") + "</td></tr>"
    Output$ + "<tr><td><i>REMOTE_ADDR:</i></td><td>" + GetServerVariable("REMOTE_ADDR") + "</td></tr>"
    Output$ + "<tr><td><i>REMOTE_HOST:</i></td><td>" + GetServerVariable("REMOTE_HOST") + "</td></tr>"
    Output$ + "<tr><td><i>REMOTE_USER:</i></td><td>" + GetServerVariable("REMOTE_USER") + "</td></tr>"
    Output$ + "<tr><td><i>UNMAPPED_REMOTE_USER:</i></td><td>" + GetServerVariable("UNMAPPED_REMOTE_USER") + "</td></tr>"

    Output$ + "<tr><td><i>REQUEST_METHOD:</i></td><td>" + GetServerVariable("REQUEST_METHOD") + "</td></tr>"
    Output$ + "<tr><td><i>SCRIPT_NAME:</i></td><td>" + GetServerVariable("SCRIPT_NAME") + "</td></tr>"
    Output$ + "<tr><td><i>SERVER_NAME:</i></td><td>" + GetServerVariable("SERVER_NAME") + "</td></tr>"
    Output$ + "<tr><td><i>SERVER_PORT:</i></td><td>" + GetServerVariable("SERVER_PORT") + "</td></tr>"
    Output$ + "<tr><td><i>SERVER_PORT_SECURE:</i></td><td>" + GetServerVariable("SERVER_PORT_SECURE") + "</td></tr>"
    Output$ + "<tr><td><i>SERVER_PROTOCOL:</i></td><td>" + GetServerVariable("SERVER_PROTOCOL") + "</td></tr>"
    Output$ + "<tr><td><i>SERVER_SOFTWARE:</i></td><td>" + GetServerVariable("SERVER_SOFTWARE") + "</td></tr>"
    Output$ + "<tr><td><i>ALL_HTTP:</i></td><td>" + GetServerVariable("ALL_HTTP") + "</td></tr>"
    Output$ + "<tr><td><i>HTTP_ACCEPT:</i></td><td>" + GetServerVariable("HTTP_ACCEPT") + "</td></tr>"
    Output$ + "<tr><td><i>URL:</i></td><td>" + GetServerVariable("URL") + "</td></tr>"

    Output$ + "</table><br><br>"

    
    Output$ + "</body></html>"
    SendHeader("Content-Type: text/html"+Chr(13)+Chr(10)+"Content-Length: "+Str(Len(Output$))+Chr(13)+Chr(10)+Chr(13)+Chr(10))
   


    SendString(Output$)
   
    ; make sure the mutex gets released
    ReleaseMutex_(AccessMutex)   
   
    ProcedureReturn #HSE_STATUS_SUCCESS
   
  Else
    ; wait somehow failed..
   
    ProcedureReturn #HSE_STATUS_ERROR 
  EndIf

EndProcedure



; this function will be called just before unloading the dll, so lets free
; the mutex again.

ProcedureDLL TerminateExtension(dwFlags)

  CloseHandle_(AccessMutex)

EndProcedure 

; Ausschnitte von http://people.freenet.de/j-hummel/programmierung/isapi/index.htm :
; ***********************

;Strukturelement	      Beschreibung
;
;cbSize(IN) 	          Die Größe der Record-Struktur ECB.
;
;dwVersion(IN) 	        Die Versionsnummer dieser Struktur: Versionsinformation des 
;                       ISAPI-Standards, auf die der entsprechende ECB aufbaut; 
;                       das HIWORD repräsentiert die major Versionsnummer, 
;                       das LOWORD die minor Versionsnummer, die unterstützt wird.
;
;ConnID(IN) 	          Eine eindeutige Identifikations-Nummer für die aktuelle Anforderung, 
;                       die vom HTTP Server zugewiesen wird. Sie darf nicht verändert werden.
;
;dwHttpStatusCode(OUT)  Der Status der aktuellen Transaktion, wenn die Anforderung fertig ist.
;
;lpszLogData(OUT) 	    Puffer der Größe HSE_LOG_BUFFER_LEN (derzeit 80 Zeichen). 
;                       Er wird mit einem null-terminierten Log-Informations-String der aktuellen 
;                       Transaktion gefüllt. Diese Log-Information wird im HTTP-Server-Log 
;                       eingetragen. Das ist ein gemeinsames Log-File, das HTTP-Server und 
;                       ISAPI-Anwendungen zu administrativen Zwecken dient.
;
;lpszMethod(IN) 	      Die Methode, mit der die Anforderung getätigt wurde.
;
;lpszQueryString(IN) 	  Ein null-terminierter String, der die Abfrage-Information des Klient enthält.
;
;lpszPathInfo(IN)       Ein null-terminierter String, der zusätzliche Pfadinformationen enthält, 
;                       die vom Klient gekommen sind.
;
;lpszPathTranslated(IN) Ein null-terminierter String, der den übersetzten Pfad enthält.
;
;cbTotalBytes(IN) 	    Die Anzahl der Bytes, die vom Klient gesendet wurden. Einen Sonderfall 
;                       stellt der Wert 0xffffffff dar: Es liegen dann mehr als 4 GigaByte Daten vor.
;                       In diesem Fall ist die Funktion ReadClient so oft aufzurufen, bis keine 
;                       Daten mehr anstehen.
;
;cbAvailable(IN) 	      Die Anzahl der verfügbaren Bytes, die sich im Puffer befinden; 
;                       Auf diesen Puffer zeigt der Zeiger lpbData. Ist der Wert von cbTotalbytes 
;                       gleich dem Wert cbAvailable, dann zeigt der Zeiger lpbData auf einen Puffer, 
;                       der die gesamten gesendeten Daten enthält. Ansonsten enthält cbTotalbytes die 
;                       Anzahl der gesendeten Bytes. Die ISAPI-Anwendung benötigt die 
;                       Callback-Funktion ReadClient, um den Rest der Daten zu lesen 
;                       (beginnend bei einem Offset von cbAvailable).
;
;lpbData(IN) 	          Zeiger auf den Puffer der Größe cbAvailable, welcher die gesendeten Daten 
;                       des Klient enthält. Es werden maximal die ersten 48 KBytes geliefert.
;
;pszContenttype(IN) 	  Ein null-terminierter String, der den Content-Typ der Daten enthält, 
;                       die der Klient gesendet hat.

; ***********************

;Servervariable	      Beschreibung
;
;AUTH_TYPE 	          Beinhaltet den zur Beglaubigung verwendeten Typ. 
;                     Bei elementarer Beglaubigung wird der Wert "basic" sein. 
;                     Für NT-Anforderungen kann das "NTLM" sein. 
;                     Bei leerem Rückgabewert wird keine Beglaubigung verwendet.
;
;CONTENT_LENGTH       Anzahl der Bytes, die das Programm als Eingabedaten vom Klient erhalten hat.
:
;CONTENT_TYPE 	      Der Content-Typ der Informationen, die aus einem Formular 
;                     bei Verwendung der Methode "POST" empfangen wurden.
;
;PATH_INFO 	          Zusätzliche Pfad-Informationen, die der Klient übermittelt. 
;                     Sie besteht aus einen Zusatzteil der URL nach dem DLL-Namen 
;                     bis zum QueryString, sofern ein solcher existiert.
;
;PATH_TRANSLATED 	    Eine Pfadangabe zum virtuellen Scriptverzeichnis, 
;                     jedoch als real übersetztes Verzeichnis auf dem Server-Computer.
;
;QUERY_STRING 	      Die Information, die dem "?" in der URL beim Aufruf des Programmes folgt.
;
;REMOTE_ADDR 	        Die IP-Adresse des Klient. Falls der Klient hinter einer Firewall oder 
;                     über einen Agenten arbeitet beinhaltet REMOTE_ADDR nur deren Adressen oder 
;                     ist leer.
;
;REMOTE_HOST 	        Der Hostname des Klient oder dessen Agenten, von dem die Anfrage eintrifft.
;
;REMOTE_USER 	        Beinhaltet den Nutzernamen, wie er vom Klient eingespeist wird. 
;                     Allerdings nur bei Beglaubigung durch den Server. 
;                     Anderenfalls zählt der Nutzer als anonym und es ist ein leerer String.
;
;UNMAPPED_REMOTE_USER Benutzername, bevor etwa vorhandene ISAPI-Filter den Nutzer kartiert haben.
;
;REQUEST_METHOD 	    Die HTTP-Anforderungs-Methode.
;
;SCRIPT_NAME 	        Der Name des Programmes, das gerade ausgeführt wird.
;
;SERVER_NAME 	        Der Hostname des Servers oder notfalls dessen IP-Adresse.
;
;SERVER_PORT 	        Der TCP/IP-Port, über den die Anfrage eingetroffen ist.
;
;SERVER_PORT_SECURE 	Ein String mit dem Wert 0 oder 1. 
;                     Wenn die Anfrage über einen geschützten Port eintrifft, dann 1. 
;                     Anderenfalls 0.
;
;SERVER_PROTOCOL 	    Der Standard und die Version des Abruf-Protokolls der Anfrage. 
;                     Üblicherweise ist das HTTP/1.0.
;
;SERVER_SOFTWARE 	    Name und Version des Webservers, 
;                     auf dem die ISAPI-DLL als Servererweiterung läuft.
;
;ALL_HTTP 	          Alle HTTP-Angaben, die bisher noch nicht in einer der vorherigen Variablen 
;                     übergeben wurden. Alle diese Angaben haben die Form HTTP_<FeldName>. 
;                     Es ist ein null-terminierter String. 
;                     Die Einzelteile sind durch Zeilenvorschübe voneinander getrennt.
;
;HTTP_ACCEPT 	        Spezieller HTTP-Kopf mit Werten der vom Klient akzeptierten Datentypen. 
;                     Die einzelnen Angaben sind durch Komma getrennt.
;
;URL (ab V.2.0) 	    Liefert den Basis-Teil der URL.


;**********************************************************************************************
; ReadClient
; Mit ServerSupportFunction kann das Programm jede Serverfunktion ausführen
; ... Server zu veranlassen mit Hilfe des Extension Control Blocks eine response zu erzeugen
; HttpExtensionProc erfolgreich war, so wird das dem Server mit dem Ausgabewert SUCCESS mitgeteilt.
; PENDING ERROR
; TerminateExtension
P.S. Ich bin mir nicht sicher ob das eher in APIs gehört. Bitte ggf. verschieben. Danke.

edit :
ISAPI-Programme müssen als DLL compiliert und in den Webserver
eingebunden werden. Ich denke sie stellen eine echte Alternative zu
einem selbstgeschriebenen Server da.