developing ISAPI dlls in PB?

Everything else that doesn't fall into one of the other PB categories.
ricardo
Addict
Addict
Posts: 2438
Joined: Fri Apr 25, 2003 7:06 pm
Location: Argentina

developing ISAPI dlls in PB?

Post by ricardo »

Hello

Did anybody coded any ISAPI dll with PB?
Any code, help, etc?

Thanks in advance
ARGENTINA WORLD CHAMPION
ricardo
Addict
Addict
Posts: 2438
Joined: Fri Apr 25, 2003 7:06 pm
Location: Argentina

Post by ricardo »

Here is a Power Basic sample code

Code: Select all

 removed copyrighted code
ARGENTINA WORLD CHAMPION
freak
PureBasic Team
PureBasic Team
Posts: 5940
Joined: Fri Apr 25, 2003 5:21 pm
Location: Germany

Post by freak »

I did this quite a while ago in PB, but it was quite some work due to stuff like the unsafety of threads.
It should be fairly easy though with PB4 now.

I will see if i can find some of these old codes and port it to PB4.
quidquid Latine dictum sit altum videtur
ricardo
Addict
Addict
Posts: 2438
Joined: Fri Apr 25, 2003 7:06 pm
Location: Argentina

Post by ricardo »

freak wrote:I did this quite a while ago in PB, but it was quite some work due to stuff like the unsafety of threads.
It should be fairly easy though with PB4 now.

I will see if i can find some of these old codes and port it to PB4.
Excellent!! I will be waiting!! :D
ARGENTINA WORLD CHAMPION
uweb
User
User
Posts: 98
Joined: Wed Mar 15, 2006 9:40 am
Location: Germany

Post by uweb »

some month ago i did a thread in german forum about freaks source and my problems with post.
http://www.purebasic.fr/german/viewtopi ... ight=isapi
It was just a try. I will also be waiting.
freak
PureBasic Team
PureBasic Team
Posts: 5940
Joined: Fri Apr 25, 2003 5:21 pm
Location: Germany

Post by freak »

I am sorry this took so long. I had forgotten about it for a while :oops:

PB4 makes this really much easier than before with stuff like
Prototypes, Fixed length strings and such.

Here is my PB4 Isapi include file:

Code: Select all

;- ISAPI Constants

#HSE_VERSION_MAJOR                       =   6      ;/* 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   

#HSE_URL_FLAGS_READ          = $00000001    ;// Allow For Read
#HSE_URL_FLAGS_WRITE         = $00000002    ;// Allow For Write
#HSE_URL_FLAGS_EXECUTE       = $00000004    ;// Allow For Execute
#HSE_URL_FLAGS_SSL           = $00000008    ;// Require SSL
#HSE_URL_FLAGS_DONT_CACHE    = $00000010    ;// Don't cache (vroot only)
#HSE_URL_FLAGS_NEGO_CERT     = $00000020    ;// Allow client SSL certs
#HSE_URL_FLAGS_REQUIRE_CERT  = $00000040    ;// Require client SSL certs
#HSE_URL_FLAGS_MAP_CERT      = $00000080    ;// Map SSL cert To NT account
#HSE_URL_FLAGS_SSL128        = $00000100    ;// Require 128 bit SSL
#HSE_URL_FLAGS_SCRIPT        = $00000200    ;// Allow For Script execution

#HSE_URL_FLAGS_MASK          = $000003ff

#HSE_EXEC_URL_NO_HEADERS                     = $02
#HSE_EXEC_URL_IGNORE_CURRENT_INTERCEPTOR     = $04
#HSE_EXEC_URL_IGNORE_VALIDATION_AND_RANGE    = $10
#HSE_EXEC_URL_DISABLE_CUSTOM_ERROR           = $20
#HSE_EXEC_URL_SSI_CMD                        = $40

#HSE_VECTOR_ELEMENT_TYPE_MEMORY_BUFFER      = 0
#HSE_VECTOR_ELEMENT_TYPE_FILE_HANDLE        = 1

#HSE_APP_FLAG_IN_PROCESS   = 0
#HSE_APP_FLAG_ISOLATED_OOP = 1
#HSE_APP_FLAG_POOLED_OOP   = 2
          

;- ISAPI Structures

; Prototypes for the EXTENSION_CONTROL_BLOCK structure
;
Prototype GetServerVariable(ConnID, lpszVariableName.s, *lpvBuffer, *lpdwSizeofBuffer.LONG)
Prototype WriteClient(ConnID, *Buffer, *lpdwSizeofBuffer.LONG, dwSync)
Prototype ReadClient(ConnID, *lpvBuffer, *lpdwSize.LONG)
Prototype ServerSupportFunction(ConnID, dwServerSupportFunction, *lpvBuffer, *lpdwSizeofBuffer.LONG, *lpdwDataType.LONG)


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.s{#HSE_LOG_BUFFER_LEN}; Null-terminated log info. (why call it lpsz when it is not a pointer !?)
  lpszMethod.s                ; REQUEST_METHOD  
  lpszQueryString.s           ; QUERY_STRING  
  lpszPathInfo.s              ; PATH_INFO  
  lpszPathTranslated.s        ; PATH_TRANSLATED  
  cbTotalBytes.l              ; Total bytes indicated from client.
  cbAvailable.l               ; Available number of bytes.
  lpbData.l                   ; Pointer to cbAvailable bytes.
  lpszContentType.s           ; Content type of client data.  
  GetServerVariable.GetServerVariable ; Server callback functions
  WriteClient.WriteClient
  ReadClient.ReadClient
  ServerSupportFunction.ServerSupportFunction
EndStructure

Structure HSE_VERSION_INFO
  dwExtensionVersion.l
  lpszExtensionDescription.s{#HSE_MAX_EXT_DLL_NAME_LEN}
EndStructure

Structure HSE_URL_MAPEX_INFO 
  lpszPath.s{#MAX_PATH}; // Physical path root mapped to
  dwFlags.l;            // Flags associated with this URL path
  cchMatchingPath.l;    // Number of matching characters in physical path
  cchMatchingURL.l;     // Number of matching characters in URL
  dwReserved1.l;
  dwReserved2.l;
EndStructure

Structure HSE_UNICODE_URL_MAPEX_INFO
  lpszPath.w[#MAX_PATH]; // Physical path root mapped to (unicode version)
  dwFlags.l;            // Flags associated with this URL path
  cchMatchingPath.l;    // Number of matching characters in physical path
  cchMatchingURL.l;     // Number of matching characters in URL
EndStructure

Prototype PFN_HSE_IO_COMPLETION(*lpECP.EXTENSION_CONTROL_BLOCK, *pContect, cbIO, dwError)

Structure HSE_TF_INFO 
  pfnHseIO.PFN_HSE_IO_COMPLETION 
  pContext.l
  hFile.l
  pszStatusCode.s; // HTTP Status Code  eg: "200 OK"
  BytesToWrite.l;  // special value of "0" means write entire file.
  Offset.l;        // offset value within the file to start from
  pHead.l;         // Head buffer to be sent before file data
  HeadLength.l;    // header length
  pTail.l;         // Tail buffer to be sent after file data
  TailLength.l;    // tail length
  dwFlags.l;       // includes HSE_IO_DISCONNECT_AFTER_SEND, ...
EndStructure

Structure HSE_SEND_HEADER_EX_INFO
  pszStatus.s;  // HTTP status code  eg: "200 OK"
  pszHeader.s;  // HTTP header
  
  cchStatus.l;  // number of characters in status code
  cchHeader.l;  // number of characters in header

  fKeepConn.l;  // keep client connection alive?
EndStructure

Structure HSE_EXEC_URL_USER_INFO
  hImpersonationToken.l
  pszCustomUserName.s
  pszCustomAuthType.s
EndStructure

Structure HSE_EXEC_URL_ENTITY_INFO 
  cbAvailable.l
  lpbData.l
EndStructure

Structure HSE_EXEC_URL_STATUS
  uHttpStatusCode.w
  uHttpSubStatus.w
  dwWin32Error.l
EndStructure

Structure HSE_EXEC_URL_INFO
  pszUrl.s;                       // URL to execute
  pszMethod.s;                    // Method
  pszChildHeaders.s;              // Request headers for child
  *pUserInfo.HSE_EXEC_URL_USER_INFO; // User for new request
  *pEntity.HSE_EXEC_URL_ENTITY_INFO; // Entity body for new request
  dwExecUrlFlags.l;               // Flags
EndStructure

Structure HSE_EXEC_UNICODE_URL_USER_INFO
  hImpersonationToken.l
  pszCustomUserName.s
  pszCustomAuthType.s
EndStructure

Structure HSE_EXEC_UNICODE_URL_INFO
  pszUrl.s;                              // URL to execute
  pszMethod.s;                           // Method
  pszChildHeaders.s;                     // Request headers for child
  *pUserInfo.HSE_EXEC_UNICODE_URL_USER_INFO; // User for new request
  *pEntity.HSE_EXEC_URL_ENTITY_INFO;         // Entity body for new request
  dwExecUrlFlags.l;                      // Flags
EndStructure

Structure HSE_CUSTOM_ERROR_INFO 
  pszStatus.s
  uHttpSubError.w
  fAsync.l
EndStructure 

Structure HSE_VECTOR_ELEMENT
  ElementType.l;  // Type of element (buffer/file/fragment etc)
  pvContext.l;    // The context representing the element to be sent
  cbOffset.q; // Offset from the start of hFile
  cbSize.q;   // Number of bytes to send
EndStructure

Structure HSE_RESPONSE_VECTOR
  dwFlags.l;                          // combination of HSE_IO_* flags
  pszStatus.s;                        // Status line to send like "200 OK"
  pszHeaders.s;                       // Headers to send
  nElementCount.l;                    // Number of HSE_VECTOR_ELEMENT's
  *lpElementArray.HSE_VECTOR_ELEMENT;    // Pointer to those elements
EndStructure

Structure HSE_TRACE_INFO
  fTraceRequest.l
  TraceContextId.b[16]
  dwReserved1.l
  dwReserved2.l
EndStructure

Here is a simple example extension:

Code: Select all

XIncludeFile "Isapi.pb"


; GetExtensionVersion is called when the dll is loaded
;
ProcedureDLL GetExtensionVersion(*pver.HSE_VERSION_INFO)

  *pver\dwExtensionVersion       = #HSE_VERSION                        ; set the version info (6.0)
  *pver\lpszExtensionDescription = "ISAPI extension test in PureBasic" ; Its a fixed length string so we can just assign the text
    
  ProcedureReturn #True  ; return success. returning False will cause the dll to be unloaded again
EndProcedure

; This is just a small helper function to wrap the GetServerVariable call
; for easier access
;
Procedure.s ServerVariable(*pECB.EXTENSION_CONTROL_BLOCK, Name$)
  *Buffer = AllocateMemory(1000)
  Size    = 1000
  Value$  = ""
  
  If *pECB\GetServerVariable(*pECB\ConnID, Name$, *Buffer, @Size) = #True
    Value$ = PeekS(*Buffer, Size)
  EndIf
    
  FreeMemory(*Buffer)
  ProcedureReturn Value$
EndProcedure

; This is the main function that is called for each client request
; *pECB contains all needed data
;
ProcedureDLL HttpExtensionProc(*pECB.EXTENSION_CONTROL_BLOCK)

  ; Lets build some HTML code to send back
  ;
  Html$ = "<html><head><title>ISAPI extension test in PureBasic</title></head><body>"
  Html$ + "<br><hr><center>ISAPI extension test in PureBasic</center><hr>"
  Html$ + "<br><br>"
  
  ; Some information is directly provided through the structure, other stuff
  ; can be read with the GetServerVariable callback:
  ;
  Html$ + "Some examples for input data:<hr>"
  Html$ + "<b>REQUEST METHOD:</b> "  + *pECB\lpszMethod         + "<br>"
  Html$ + "<b>QUERY_STRING:</b> "    + *pECB\lpszQueryString    + "<br>"
  Html$ + "<b>PATH_INFO:</b> "       + *pECB\lpszPathInfo       + "<br>"
  Html$ + "<b>PATH_TRANSLATED:</b> " + *pECB\lpszPathTranslated + "<br>"
  Html$ + "<b>CONTENT TYPE:</b> "    + *pECB\lpszContentType    + "<br>"    
  Html$ + "<b>REMOTE_ADDR:</b> "     + ServerVariable(*pECB, "REMOTE_ADDR") + "<br>"  
  Html$ + "<br><br>"
  
  Html$ + "DATA sent from the client:(" + Str(*pECB\cbTotalBytes) + " Bytes)<hr>"    
  Html$ + "<pre>"
  
  ; An important note on POST data from the client:
  ;
  ; 'cbTotalBytes' indicates the total number of bytes available from the client.
  ; 'cbAvailable' indicates the number of bytes directly available in the 'lpbData'
  ; Buffer. These two may differ (for larger number of datas). So the correct way is 
  ; to read anything inside 'lpbData', and then call the ReadClient() callback as shown
  ; below until all data is read from the client, if there is more than 'cbAvailable' bytes available
  ;
  If *pECB\cbAvailable > 0    
    Html$ + PeekS(*pECB\lpbData, *pECB\cbAvailable)    
  EndIf
  
  If *pECB\cbTotalBytes > *pECB\cbAvailable
    BytesRead = *pECB\cbAvailable
    *Buffer = AllocateMemory(1000)
    
    If *Buffer
      While BytesRead < *pECB\cbTotalBytes
        Size = 1000
        *pECB\ReadClient(*pECB\ConnID, *Buffer, @Size)
        Html$ + PeekS(*Buffer, Size)
        BytesRead + Size
      Wend
      
      FreeMemory(*Buffer)
    EndIf
  EndIf
    
  Html$ + "</pre>"
  Html$ + "<br><br>"
  Html$ + "Test: Enter something and it will be sent to the dll via POST method.<hr>"
  Html$ + "<form action="+Chr(34)+ServerVariable(*pECB, "URL")+Chr(34)+" method=POST>"
  Html$ + "<textarea name="+Chr(34)+"text"+Chr(34)+" rows=10 cols=60>Enter text</textarea><br>"
  Html$ + "<input type=submit>"
  Html$ + "</form>"
  Html$ + "</body></html>"
  
  ; Send the response header with the ServerSupportFunction() callback
  ;        
  Header.HSE_SEND_HEADER_EX_INFO
  Header\pszStatus = "200 OK"
  Header\pszHeader = "Content-type: text/html" + Chr(13)+Chr(10)+Chr(13)+Chr(10)
  Header\cchStatus = Len(Header\pszStatus)
  Header\cchHeader = Len(Header\pszHeader)
  Header\fKeepConn = 0  
  *pECB\ServerSupportFunction(*pECB\ConnID, #HSE_REQ_SEND_RESPONSE_HEADER_EX, @Header, 0, 0)
  
  ; Send the html data with the WriteClient() callback:
  ;
  Length = Len(Html$)
  *pECB\WriteClient(*pECB\ConnID, @Html$, @Length, 0)

  ; return success
  ;
  ProcedureReturn #HSE_STATUS_SUCCESS
EndProcedure

; TerminateExtension is called before unloading the dll. 
;
ProcedureDLL TerminateExtension(dwFlags)
  ProcedureReturn #True
EndProcedure
Some notes:

Threads:
ISAPI is per definition threaded. When multiple connections are incomming, each one
will be handled inside a separate thread. So you should compile your extension
with the threadsafe switch turned on.

But: During the testing of this with PB4, i discovered some bigger issues with threaded dll's.
We will try to fix this with the next beta (beta10). Until then, you will just have to
compile this without the threadsafe switch and make sure you do not bomb
out your server with simultanous requests.
Running this with the threadsafe option right now is guaranteed to produce a crash sooner or later.

Unicode:
To ease the use, i defined all string pointers in the include file as PB strings,
and all in-structure strings as fixed length strings. This however means
that it cannot be easily compiled in unicode mode.
To compile a unicode extension, you will need to create a different includefile.
quidquid Latine dictum sit altum videtur
Shannara
Addict
Addict
Posts: 1808
Joined: Thu Oct 30, 2003 11:19 pm
Location: Emerald Cove, Unformed

Post by Shannara »

You are officially a Hero, freak! Looking forward to Beta 10! If this is combined with DracScript, it is possible to have a PB like web scripting language!
Booger
Enthusiast
Enthusiast
Posts: 134
Joined: Tue Sep 04, 2007 2:18 pm

Post by Booger »

Very nice work Freak.

I am trying to expand on this using it with apache.

But from this dll, I cannot open a console. Cannot open a window. Cannot open msgbox. Can't runprogram to pipe data.

Does anyone know how I can realtime output data to a console and get around apache seemingly hooking these calls?

I really dont want to serve stats to a browser if at all possible.

Thanks for the help.
Post Reply