2009-03-28 00:59 UTC+0100 Francesco Saverio Giudice (info/at/fsgiudice.com)

* harbour/contrib/examples/uhttpd/uhttpd.prg
    + added #define FIXED_THREADS that disable (temporarly) dynamic threads
      support, until I will find a correct way to implement.
    * changed default GT in Linux from GTXWC to GTTRM
    + added Actions and Handlers support
      rewrote handlers part, now separated and similar to Apache
    * changed internal ScripAlias Hash name from Aliases to ScriptAliases
    + added Alias support
    + added PATH_INFO and PATH_TRANSLATED support
      this means that parts of urls longer than the real file url are passed to
      file as parameters. Actually valid for any file.
    * fixed CGIKill() to correct handle killing of not responding CGI apps
    * formatting and minor changes
    ; NOTE: not tested under Linux, more tests to do
    ; TOFIX: in case of error of cgi executables error output is missing,
             this I have to check better.
  * harbour/contrib/examples/uhttpd/uhttpd.ini
    * changed [ALIASES] section name to [SCRIPTALIASES]
    + added [ALIASES] correct section
  * harbour/contrib/examples/uhttpd/session.prg
    + added retrying in case of error in reading session files.
      to check it better
  ; NOTE: still work in progress.
    Any comments and suggestions are welcome
This commit is contained in:
Francesco Saverio Giudice
2009-03-28 00:00:43 +00:00
parent 142a03c102
commit 72ffa7c225
4 changed files with 546 additions and 171 deletions

View File

@@ -8,6 +8,32 @@
2009-12-31 13:59 UTC+0100 Foo Bar (foo.bar foobar.org)
*/
2009-03-28 00:59 UTC+0100 Francesco Saverio Giudice (info/at/fsgiudice.com)
* harbour/contrib/examples/uhttpd/uhttpd.prg
+ added #define FIXED_THREADS that disable (temporarly) dynamic threads
support, until I will find a correct way to implement.
* changed default GT in Linux from GTXWC to GTTRM
+ added Actions and Handlers support
rewrote handlers part, now separated and similar to Apache
* changed internal ScripAlias Hash name from Aliases to ScriptAliases
+ added Alias support
+ added PATH_INFO and PATH_TRANSLATED support
this means that parts of urls longer than the real file url are passed to
file as parameters. Actually valid for any file.
* fixed CGIKill() to correct handle killing of not responding CGI apps
* formatting and minor changes
; NOTE: not tested under Linux, more tests to do
; TOFIX: in case of error of cgi executables error output is missing,
this I have to check better.
* harbour/contrib/examples/uhttpd/uhttpd.ini
* changed [ALIASES] section name to [SCRIPTALIASES]
+ added [ALIASES] correct section
* harbour/contrib/examples/uhttpd/session.prg
+ added retrying in case of error in reading session files.
to check it better
; NOTE: still work in progress.
Any comments and suggestions are welcome
2009-03-27 18:30 UTC+0100 Viktor Szakats (harbour.01 syenar hu)
* config/win/poccce.cf
* config/win/pocc.cf

View File

@@ -143,6 +143,8 @@ CLASS uhttpd_Session
DATA bWrite //INIT {|cID, cData| ::SessionWrite( cID, cData ) }
DATA bDestroy //INIT {|cID| ::SessionDestroy( cID ) }
DATA bGC //INIT {|nMaxLifeTime| ::SessionGC( nMaxLifeTime ) }
DATA nFileRetry INIT 10 // How many time try to open / write / delete file in case of error
DATA nFileWait INIT 500 // How many milliseconds have to wait before retry
DATA nActiveSessions INIT 0
@@ -579,22 +581,36 @@ METHOD SessionRead( cID ) CLASS uhttpd_Session
LOCAL cFile
LOCAL nFileSize
LOCAL cBuffer
LOCAL nRetry := 0
LOCAL nFError := 0
DEFAULT cID TO ::cSID
cFile := ::cSavePath + "/" + ::cName + "_" + cID
cFile := ::cSavePath + HB_OSPathSeparator() + ::cName + "_" + cID
//TraceLog( "SessionRead: cFile", cFile )
IF File( cFile )
IF ( nH := FOpen( cFile, FO_READ ) ) <> -1
nFileSize := FSeek( nH, 0, FS_END )
FSeek( nH, 0, FS_SET )
cBuffer := Space( nFileSize )
IF ( FRead( nH, @cBuffer, nFileSize ) ) <> nFileSize
uhttpd_Die( "ERROR: On reading session file : " + cFile + ", File error : " + hb_cStr( FError() ) )
DO WHILE nRetry++ <= ::nFileRetry
IF ( nH := FOpen( cFile, FO_READ + FO_DENYWRITE ) ) <> -1
nRetry := 0
DO WHILE nRetry++ <= ::nFileRetry
nFileSize := FSeek( nH, 0, FS_END )
FSeek( nH, 0, FS_SET )
cBuffer := Space( nFileSize )
IF ( FRead( nH, @cBuffer, nFileSize ) ) <> nFileSize
//uhttpd_Die( "ERROR: On reading session file : " + cFile + ", File error : " + hb_cStr( FError() ) )
hb_idleSleep( ::nFileWait / 1000 )
LOOP
ENDIF
FClose( nH )
EXIT
ENDDO
ELSE
FClose( nH )
//uhttpd_Die( "ERROR: On opening session file : " + cFile + ", File error : " + hb_cStr( FError() ) )
hb_idleSleep( ::nFileWait / 1000 )
LOOP
ENDIF
ELSE
uhttpd_Die( "ERROR: On opening session file : " + cFile + ", file not exist." )
ENDIF
EXIT
ENDDO
ENDIF
//TraceLog( "SessionRead() - cID, cFile, nFileSize, cBuffer", cID, cFile, nFileSize, cBuffer )
RETURN cBuffer
@@ -611,7 +627,7 @@ METHOD SessionWrite( cID, cData ) CLASS uhttpd_Session
nFileSize := Len( cData )
cFile := ::cSavePath + "/" + ::cName + "_" + cID
cFile := ::cSavePath + HB_OSPathSeparator() + ::cName + "_" + cID
//TraceLog( "SessionWrite() - cFile", cFile )
IF nFileSize > 0
IF ( nH := FCreate( cFile, FC_NORMAL ) ) <> -1
@@ -619,16 +635,16 @@ METHOD SessionWrite( cID, cData ) CLASS uhttpd_Session
uhttpd_Die( "ERROR: On writing session file : " + cFile + ", File error : " + hb_cStr( FError() ) )
ELSE
lOk := TRUE
FClose( nH )
ENDIF
FClose( nH )
ELSE
uhttpd_Die( "ERROR: On WRITING session file. I can not create session file : " + cFile + ", File error : " + hb_cStr( FError() ) )
ENDIF
ELSE
// If session data is empty, I will delete the file if exist
IF File( cFile )
FErase( cFile )
ENDIF
//IF File( cFile )
// FErase( cFile )
//ENDIF
// Return that all is ok
lOk := TRUE
ENDIF
@@ -644,7 +660,7 @@ METHOD SessionDestroy( cID ) CLASS uhttpd_Session
::oCookie:DeleteCookie( ::cName )
//TraceLog( "SessionDestroy() - cID, oCGI:h_Session", cID, DumpValue( oCGI:h_Session ) )
cFile := ::cSavePath + "/" + ::cName + "_" + cID
cFile := ::cSavePath + HB_OSPathSeparator() + ::cName + "_" + cID
IF !( lOk := ( FErase( cFile ) == 0 ) )
uhttpd_Die( "ERROR: On deleting session file : " + cFile + ", File error : " + hb_cStr( FError() ) )
ELSE
@@ -661,14 +677,14 @@ METHOD SessionGC( nMaxLifeTime ) CLASS uhttpd_Session
LOCAL aDir, aFile
DEFAULT nMaxLifeTime TO ::nGc_MaxLifeTime
aDir := Directory( ::cSavePath + "/" + ::cName + "_*.*" )
aDir := Directory( ::cSavePath + HB_OSPathSeparator() + ::cName + "_*.*" )
FOR EACH aFile IN aDir
nSecs := TimeDiffAsSeconds( aFile[ F_DATE ], Date(), aFile[ F_TIME ], Time() )
//TraceLog( "GC: aFile[ F_NAME ], aFile[ F_DATE ], Date(), aFile[ F_TIME ], Time(), nSecs, nMaxLifeTime", ;
// aFile[ F_NAME ], aFile[ F_DATE ], Date(), aFile[ F_TIME ], Time(), nSecs, nMaxLifeTime )
IF nSecs > nMaxLifeTime
FErase( ::cSavePath + "/" + aFile[ F_NAME ] )
FErase( ::cSavePath + HB_OSPathSeparator() + aFile[ F_NAME ] )
ENDIF
NEXT

View File

@@ -1,6 +1,17 @@
#
# $Id$
#
# ------------------------------------
# Harbour Project source code:
# uHTTPD (Micro HTTP server) ini file
#
# Copyright 2009 Francesco Saverio Giudice <info / at / fsgiudice.com>
# www - http://www.harbour-project.org
# ------------------------------------
#
# uHTTPD ini file (defaults are commented)
#
# ------------------------------------
# --- server listen port
#Port = 8082
@@ -20,6 +31,7 @@
# --- how many threads have to run always
#start_num = 4
start_num = 10
# --- how many threads can be added to initial threads
# (over this number server replies with BUSY error)
@@ -32,11 +44,23 @@
# --- path for error log
#error = logs\error.log
[SCRIPTALIASES]
# --- here put script aliases to real path
# you can use following macros:
# $(DOCROOT_DIR) = to refer to document_root path
# $(APP_DIR) = to refer to application path
# otherwise it will be a full filesystem path
/info = $(DOCROOT_DIR)/cgi-bin/info.hrb
/cookie = $(DOCROOT_DIR)/cgi-bin/cookie.hrb
[ALIASES]
# --- here put aliases to real path
# (under document_root path defined above)
/info = /cgi-bin/info.hrb
/cookie = /cgi-bin/cookie.hrb
# --- here put path aliases to real path
# you can use following macros:
# $(DOCROOT_DIR) = to refer to document_root path
# $(APP_DIR) = to refer to application path
# otherwise it will be a full filesystem path
# example:
#/images = $(APP_DIR)/images
# end

View File

@@ -72,12 +72,16 @@
- optimize code
- add SSL support
- check CGIExec() better
- fix dynamic threads (now locked to fixed number)
*/
// remove comment to activate hb_toOutDebug()
//#define DEBUG_ACTIVE
#define FIXED_THREADS // This force application to use fixed number of running threads and no service threads
#ifdef __XHARBOUR__
#include "hbcompat.ch"
#xtranslate hb_StrFormat( [<x,...>] ) => hb_sprintf( <x> )
@@ -118,14 +122,23 @@
#stdout "Mindaugas socket"
#endif
#ifdef FIXED_THREADS
#define APP_DT_SUPPORT "_FIXED_THREADS"
#stdout "Fixed # of threads"
#else
#define APP_DT_SUPPORT ""
#stdout "Dynamic # of threads"
#endif
#define APP_NAME "uhttpd"
#define APP_VER_NUM "0.4.1"
#define APP_VERSION APP_VER_NUM + APP_GD_SUPPORT + APP_INET_SUPPORT
#define APP_VER_NUM "0.4.2"
#define APP_VERSION APP_VER_NUM + APP_GD_SUPPORT + APP_INET_SUPPORT + APP_DT_SUPPORT
#define AF_INET 2
// default values - they can changes using line command switch
#define START_RUNNING_THREADS 4 // Start threads to serve connections
// default values - they can changes using line command switch or ini file
#define START_RUNNING_THREADS 6 // Start threads to serve connections
#define MAX_RUNNING_THREADS 20 // Max running threads
#define START_SERVICE_THREADS 0 // Initial number for service connections
@@ -140,7 +153,7 @@
#define PAGE_STATUS_REFRESH 5
#define THREAD_MAX_WAIT ( 30 ) // How much time thread has to wait a new connection before finish - IN SECONDS
#define CGI_MAX_EXEC_TIME 30
#define CGI_MAX_EXEC_TIME 30 // in seconds
// TOCHECK: Caching of HRB modules (Is this faster than loading HRBBody from file where OS will cache ?)
#define HRB_ACTIVATE_CACHE .F. // if .T. caching of HRB modules will be enabled. (NOTE: changes of files will not be loaded until server is active)
@@ -155,7 +168,7 @@
REQUEST HB_GT_WIN
REQUEST HB_GT_NUL
#else
REQUEST HB_GT_XWC_DEFAULT
REQUEST HB_GT_TRM_DEFAULT
REQUEST HB_GT_NUL
#endif
#define THREAD_GT hb_gtVersion()
@@ -172,7 +185,7 @@
DYNAMIC HRBMAIN
STATIC s_hmtxQueue, s_hmtxServiceThreads, s_hmtxRunningThreads, s_hmtxLog, s_hmtxConsole, s_hmtxBusy
STATIC s_hmtxHRB, s_hmtxCGIKill
STATIC s_hmtxHRB
STATIC s_hfileLogAccess, s_hfileLogError, s_cDocumentRoot, s_lIndexes, s_lConsole, s_nPort
STATIC s_cSessionPath
@@ -189,17 +202,41 @@ STATIC s_aDirectoryIndex
STATIC s_cLocalAddress, s_nLocalPort
#endif
// ALIASES: now read from ini file
STATIC s_hActions := { ;
/*"default-handler" => @Handler_Default() ,*/; // default handler
/*"send-as-is" => @Handler_SendAsIs() ,*/;
"cgi-script" => @Handler_CgiScript() ,;
"hrb-script" => @Handler_HrbScript() ,;
/*"server-info" => @Handler_ServerInfo() ,*/;
"server-status" => @Handler_ServerStatus() ;
}
STATIC s_hHandlers := { ;
"hrb" => "hrb-script" ,;
"exe" => "cgi-script" ,;
"/serverstatus" => "server-status" ;
}
//STATIC s_lAcceptPathInfo := TRUE
// SCRIPTALIASES: now read from ini file
//STATIC s_hScriptAliases := { "/info" => "/cgi-bin/info.hrb" }
STATIC s_hScriptAliases := { => }
STATIC s_hAliases := { => }
THREAD STATIC t_cResult, t_nStatusCode, t_aHeader, t_cErrorMsg, t_oSession
THREAD STATIC t_hProc
MEMVAR _SERVER, _GET, _POST, _COOKIE, _SESSION, _REQUEST, _HTTP_REQUEST, m_cPost
ANNOUNCE ERRORSYS
// ----------------------------------------
//
// M A I N
//
// ----------------------------------------
FUNCTION MAIN( ... )
LOCAL nPort, hListen, hSocket, aRemote, cI, xVal
LOCAL aThreads, nStartThreads, nMaxThreads, nStartServiceThreads
@@ -231,7 +268,7 @@ FUNCTION MAIN( ... )
nStartServiceThreads := START_SERVICE_THREADS
// Check GT version - if I have started app with //GT:NUL then I have to disable
// console
// console and application will start in hidden way.
cGT := HB_GTVERSION()
IF ( cGT == "NUL" )
lConsole := FALSE
@@ -310,17 +347,26 @@ FUNCTION MAIN( ... )
nStartThreads := hDefault[ "THREADS" ][ "START_NUM" ]
nMaxThreads := hDefault[ "THREADS" ][ "MAX_NUM" ]
// ATTENTION: aliases can be in mixed case
// ATTENTION: script aliases can be in mixed case
// i.e. we can have /info or /Info that will be different unless lScriptAliasMixedCase will be FALSE
FOR EACH xVal IN hDefault[ "ALIASES" ]
FOR EACH xVal IN hDefault[ "SCRIPTALIASES" ]
IF HB_ISSTRING( xVal )
hb_HSet( s_hScriptAliases, IIF( lScriptAliasMixedCase, xVal:__enumKey(), Upper( xVal:__enumKey() ) ), xVal )
ENDIF
NEXT
// ATTENTION: path aliases cannnot be in mixed case
// i.e. we can have /info or /Info that will be different
FOR EACH xVal IN hDefault[ "ALIASES" ]
IF HB_ISSTRING( xVal )
hb_HSet( s_hAliases, xVal:__enumKey(), xVal )
ENDIF
NEXT
//hb_ToOutDebug( "cLogAccess = %s, cLogError = %s\n\r", cLogAccess, cLogError )
//hb_ToOutDebug( "hDefault = %s\n\r", hb_ValToExp( hDefault ) )
//hb_ToOutDebug( "s_hScriptAliases = %s\n\r", hb_ValToExp( s_hScriptAliases ) )
//hb_ToOutDebug( "s_hAliases = %s\n\r", hb_ValToExp( s_hAliases ) )
// ------------------- Parameters forced from command line ----------------
@@ -434,7 +480,6 @@ FUNCTION MAIN( ... )
s_hmtxRunningThreads := hb_mutexCreate()
s_hmtxServiceThreads := hb_mutexCreate()
s_hmtxHRB := hb_mutexCreate()
s_hmtxCGIKill := hb_mutexCreate()
WriteToConsole( "--- Starting " + APP_NAME + " ---" )
@@ -500,10 +545,12 @@ FUNCTION MAIN( ... )
hb_DispOutAt( 7, 5, "Max Connections : " + Transform( s_nMaxConnections, "9999999999" ) )
hb_DispOutAt( 8, 5, "Total Connections : " + Transform( s_nTotConnections, "9999999999" ) )
#ifndef FIXED_THREADS
hb_DispOutAt( 5, 37, "ServiceThreads : " + Transform( s_nServiceThreads, "9999999999" ) )
hb_DispOutAt( 6, 37, "Connections : " + Transform( s_nServiceConnections, "9999999999" ) )
hb_DispOutAt( 7, 37, "Max Connections : " + Transform( s_nMaxServiceConnections, "9999999999" ) )
hb_DispOutAt( 8, 37, "Total Connections : " + Transform( s_nTotServiceConnections, "9999999999" ) )
#endif // FIXED_THREADS
hb_DispOutAt( 10, 40, "Memory: " + hb_ntos( memory( HB_MEM_USED ) ) )
hb_mutexUnlock( s_hmtxBusy )
ENDIF
@@ -514,7 +561,7 @@ FUNCTION MAIN( ... )
// Wait a connection
#ifdef USE_HB_INET
IF HB_InetDataReady( hListen, 250 ) > 0
IF HB_InetDataReady( hListen, 100 ) > 0
#else
IF socket_select( { hListen },,, 50 ) > 0
#endif
@@ -662,7 +709,8 @@ STATIC FUNCTION AcceptConnections()
lCanNotify := FALSE
// If I have no more threads to use ...
#ifndef FIXED_THREADS
// If I have no more running threads to use ...
IF nConnections > nMaxThreads
// If I have no more of service threads to use ... (DOS attack ?)
@@ -693,7 +741,7 @@ STATIC FUNCTION AcceptConnections()
LOOP
// If I have no running threads in use ...
// If I have no free running threads to use ...
ELSEIF nConnections >= nThreads
// Add one more
IF hb_mutexLock( s_hmtxBusy )
@@ -708,8 +756,11 @@ STATIC FUNCTION AcceptConnections()
// Otherwise I send connection to running thread queue
//hb_ToOutDebug( "Len( s_aRunningThreads ) = %i\n\r", Len( s_aRunningThreads ) )
IF lCanNotify
#endif // FIXED_THREADS
hb_mutexNotify( s_hmtxRunningThreads, hSocket )
#ifndef FIXED_THREADS
ENDIF
#endif // FIXED_THREADS
ENDDO
@@ -760,6 +811,7 @@ STATIC FUNCTION ProcessConnection()
EXIT
ELSEIF hSocket == NIL // no socket received, thread can graceful quit, but ...
#ifndef FIXED_THREADS
IF hb_mutexLock( s_hmtxBusy )
// .. not if under minimal number of starting threads
IF s_nThreads <= s_nStartThreads
@@ -769,6 +821,9 @@ STATIC FUNCTION ProcessConnection()
hb_mutexUnlock( s_hmtxBusy )
ENDIF
EXIT
#else // FIXED_THREADS
LOOP
#endif // FIXED_THREADS
ENDIF
// Connection accepted
@@ -810,7 +865,6 @@ STATIC FUNCTION ProcessConnection()
IF ParseRequest( cRequest )
//hb_ToOutDebug( "_SERVER = %s,\n\r _GET = %s,\n\r _POST = %s,\n\r _REQUEST = %s,\n\r _HTTP_REQUEST = %s\n\r", hb_ValToExp( _SERVER ), hb_ValToExp( _GET ), hb_ValToExp( _POST ), hb_ValToExp( _REQUEST ), hb_ValToExp( _HTTP_REQUEST ) )
define_Env( _SERVER )
cSend := uproc_default()
ELSE
uhttpd_SetStatusCode( 400 )
@@ -1134,6 +1188,8 @@ STATIC FUNCTION ParseRequest( cRequest )
_SERVER[ "GATEWAY_INTERFACE" ] := "CGI/1.1"
_SERVER[ "SCRIPT_URL" ] := _SERVER["SCRIPT_NAME"]
_SERVER[ "SCRIPT_URI" ] := "http://" + _HTTP_REQUEST[ "HOST" ] + _SERVER["SCRIPT_NAME"]
_SERVER[ "PATH_INFO" ] := NIL
_SERVER[ "PATH_TRANSLATED" ] := NIL
//hb_ToOutDebug( "_SERVER = %s\n\r", hb_ValToExp( _SERVER ) )
//hb_ToOutDebug( "_GET = %s\n\r", hb_ValToExp( _GET ) )
@@ -1301,24 +1357,28 @@ STATIC PROCEDURE WriteToLog( cRequest )
RETURN
STATIC FUNCTION CGIExec( cProc, /*@*/ cOutPut )
LOCAL hOut
LOCAL hOut, hErr
LOCAL cData, nLen
LOCAL nErrorLevel
LOCAL nErrorLevel := 0, nKillExit := 0
LOCAL pThread
LOCAL hProc
LOCAL hmtxCGIKill := hb_mutexCreate()
LOCAL cError
IF HB_ISSTRING( cProc )
//hb_toOutDebug( "Launching process: %s\n\r", cProc )
// No hIn, hErr == hOut
t_hProc := hb_processOpen( cProc, , @hOut, @hOut, .T. ) // .T. = Detached Process (Hide Window)
hProc := hb_processOpen( cProc, , @hOut, @hErr, .T. ) // .T. = Detached Process (Hide Window)
IF t_hProc > -1
//hb_toOutDebug( "Process handler: %s\n\r", t_hProc )
IF hProc > -1
//hb_toOutDebug( "Process handler: %s\n\r", hProc )
//hb_toOutDebug( "Error: %s\n\r", FError() )
pThread := hb_threadStart( @CGIKill() )
pThread := hb_threadStart( @CGIKill(), hProc, hmtxCGIKill )
hb_mutexNotify( s_hmtxCGIKill, .T. )
hb_mutexNotify( hmtxCGIKill, { hProc, .T. } )
// Nothing sent to process
//hb_toOutDebug( "Sending: %s\n\r", cSend )
@@ -1333,25 +1393,38 @@ STATIC FUNCTION CGIExec( cProc, /*@*/ cOutPut )
cData := Space( 1000 )
ENDDO
//? "Reading errors"
//hb_toOutDebug( "Reading errors\n\r" )
//cData := Space( 1000 )
//nLen := Fread( hErr, @cData, 1000 )
//hb_toOutDebug( "Received: %i bytes\n\r", nLen )
//IF nLen >0 .and. nLen < 200
// hb_toOutDebug( "Dumping them: %s\n\r", SubStr( cData, 1, nLen ) )
//ENDIF
cData := Space( 1000 )
cError := ""
DO WHILE ( nLen := Fread( hErr, @cData, Len( cData ) ) ) > 0
cError += SubStr( cData, 1, nLen )
cData := Space( 1000 )
ENDDO
cOutPut += cError
//hb_toOutDebug( "Received: cOutPut = %s\n\r", cOutPut )
//? "Waiting for process termination"
// Return value
nErrorLevel := HB_ProcessValue( t_hProc )
nErrorLevel := HB_ProcessValue( hProc )
//hb_toOutDebug( "CGIExec HB_ProcessValue nErrorLevel = %s\n\r", nErrorLevel )
// Notify to CGIKill to terminate
hb_mutexNotify( s_hmtxCGIKill, .F. )
hb_threadJoin( pThread )
hb_mutexNotify( hmtxCGIKill, { hProc, .F. } )
hb_threadJoin( pThread, @nKillExit )
FClose( t_hProc )
//hb_toOutDebug( "CGIExec quitting CGI, nErrorLevel = %s\n\r", nKillExit )
IF nKillExit != 0
// retrieving last command from
nErrorLevel := nKillExit
ENDIF
FClose( hProc )
FClose( hOut )
FClose( hErr )
//hb_toOutDebug( "CGIExec closed handles\n\r" )
ENDIF
@@ -1361,16 +1434,37 @@ STATIC FUNCTION CGIExec( cProc, /*@*/ cOutPut )
ENDIF
hmtxCGIKill := NIL
RETURN nErrorLevel
STATIC PROCEDURE CGIKill()
STATIC FUNCTION CGIKill( hProc, hmtxCGIKill )
LOCAL lWait
LOCAL nStartTime := hb_milliseconds()
LOCAL nErrorLevel := 0
LOCAL aValue, hRecProc
LOCAL hCurProc := hProc
//hb_toOutDebug( "CGIKill() Started. nStartTime = %s\n\r", nStartTime )
// Kill process after MAX_PROCESS_EXEC_TIME
DO WHILE .T.
hb_mutexSubscribe( s_hmtxCGIKill, 10, @lWait ) // 10 seconds
aValue := NIL
lWait := NIL
hb_mutexSubscribe( hmtxCGIKill, 1, @aValue ) // 10 seconds
IF HB_ISARRAY( aValue )
hRecProc := aValue[ 1 ]
lWait := aValue[ 2 ]
// if Process requested is different from this, sending request again in the queue
IF !( hRecProc == hCurProc )
lWait := NIL
ENDIF
ENDIF
//hb_toOutDebug( "CGIKill() lWait = %s, time := %s\n\r", lWait, hb_milliseconds() - nStartTime )
IF HB_ISLOGICAL( lWait )
IF lWait
@@ -1380,16 +1474,20 @@ STATIC PROCEDURE CGIKill()
ENDIF
ENDIF
IF ( hb_milliseconds() - nStartTime ) > CGI_MAX_EXEC_TIME
IF ( hb_milliseconds() - nStartTime ) > CGI_MAX_EXEC_TIME * 1000
//hb_toOutDebug( "CGIKill() Killing Process hCurProc = %s\n\r", hCurProc )
// Killing process if still exists
IF t_hProc != NIL
HB_ProcessClose( t_hProc )
IF hCurProc != NIL
HB_ProcessClose( hCurProc )
nErrorLevel := 1
ENDIF
EXIT
ENDIF
ENDDO
RETURN
RETURN nErrorLevel
INIT PROCEDURE SocketInit()
#ifdef USE_HB_INET
@@ -1402,7 +1500,7 @@ INIT PROCEDURE SocketInit()
RETURN
EXIT PROCEDURE Socketxit()
EXIT PROCEDURE SocketExit()
#ifdef USE_HB_INET
hb_InetCleanup()
#else
@@ -1594,139 +1692,165 @@ FUNCTION uhttpd_join( cSeparator, aData )
RETURN cRet
STATIC FUNCTION uproc_default()
LOCAL cFileName, nI, cI
LOCAL cExt
LOCAL cScript
LOCAL cFileName, nI
LOCAL cExt, cHandler, xAction, nPos
LOCAL cBaseFile
LOCAL cPathInfo, lFound, cFile
// Starting from Script Name request
cScript := _SERVER[ "SCRIPT_NAME" ]
//cFileName := STRTRAN(cRoot + _SERVER["SCRIPT_NAME"], "//", "/")
cFileName := _SERVER[ "SCRIPT_FILENAME" ]
cFileName := NIL
cPathInfo := ""
//hb_ToOutDebug( "cFileName = %s, uhttpd_OSFileName( cFileName ) = %s,\n\r _SERVER = %s\n\r", cFileName, uhttpd_OSFileName( cFileName ), hb_ValToExp( _SERVER ) )
DO WHILE TRUE
IF HB_HHasKey( s_hScriptAliases, _SERVER[ "SCRIPT_NAME" ] )
cFileName := _SERVER[ "DOCUMENT_ROOT" ] + hb_hGet( s_hScriptAliases, _SERVER[ "SCRIPT_NAME" ] )
ENDIF
#ifdef DEBUG_ACTIVE
//hb_ToOutDebug( "cFileName = %s, cScript = %s\n\r", cFileName, cScript )
#endif
// Security
IF ".." $ cFileName
uhttpd_SetStatusCode( 403 )
t_cErrorMsg := "Characters not allowed"
RETURN MakeResponse()
ENDIF
IF cFileName == NIL
//hb_toOutDebug( "cFileName = %s, uhttpd_OSFileName( cFileName ) = %s,\n\r s_hScriptAliases = %s\n\r", cFileName, uhttpd_OSFileName( cFileName ), hb_ValToExp( s_hScriptAliases ) )
// Special script names
IF Upper( cScript ) == "/SERVERSTATUS"
cFileName := "/serverstatus"
cExt := "/serverstatus" // special extension
ENDIF
// Server status request
IF Upper( _SERVER[ "SCRIPT_NAME" ] ) == "/SERVERSTATUS"
ENDIF
ShowServerStatus()
IF cFileName == NIL
// real file request
ELSEIF HB_FileExists( uhttpd_OSFileName( cFileName ) )
cFileName := FileUnAlias( cScript )
IF ( nI := RAT( ".", cFileName ) ) > 0
SWITCH ( cExt := LOWER( SUBSTR( cFileName, nI + 1 ) ) )
// First filters
CASE "hrb" ; EXIT
CASE "exe" ; EXIT
ENDIF
// Then other files
CASE "css" ; cI := "text/css"; EXIT
CASE "htm" ; CASE "html"; cI := "text/html"; EXIT
CASE "txt" ; CASE "text"; CASE "asc"
CASE "c" ; CASE "h"; CASE "cpp"
CASE "hpp" ; CASE "log"; cI := "text/plain"; EXIT
CASE "rtf" ; cI := "text/rtf"; EXIT
CASE "xml" ; cI := "text/xml"; EXIT
CASE "xsl" ; cI := "text/xsl"; EXIT
CASE "bmp" ; cI := "image/bmp"; EXIT
CASE "gif" ; cI := "image/gif"; EXIT
CASE "jpg" ; CASE "jpe"; CASE "jpeg"; cI := "image/jpeg"; EXIT
CASE "png" ; cI := "image/png"; EXIT
CASE "tif" ; CASE "tiff"; cI := "image/tiff"; EXIT
CASE "djv" ; CASE "djvu"; cI := "image/vnd.djvu"; EXIT
CASE "ico" ; cI := "image/x-icon"; EXIT
CASE "xls" ; cI := "application/excel"; EXIT
CASE "doc" ; cI := "application/msword"; EXIT
CASE "pdf" ; cI := "application/pdf"; EXIT
CASE "ps" ; CASE "eps"; cI := "application/postscript"; EXIT
CASE "ppt" ; cI := "application/powerpoint"; EXIT
CASE "bz2" ; cI := "application/x-bzip2"; EXIT
CASE "gz" ; cI := "application/x-gzip"; EXIT
CASE "tgz" ; cI := "application/x-gtar"; EXIT
CASE "js" ; cI := "application/x-javascript"; EXIT
CASE "tar" ; cI := "application/x-tar"; EXIT
CASE "tex" ; cI := "application/x-tex"; EXIT
CASE "zip" ; cI := "application/zip"; EXIT
CASE "midi"; cI := "audio/midi"; EXIT
CASE "mp3" ; cI := "audio/mpeg"; EXIT
CASE "wav" ; cI := "audio/x-wav"; EXIT
CASE "qt" ; CASE "mov"; cI := "video/quicktime"; EXIT
CASE "avi" ; cI := "video/x-msvideo"; EXIT
OTHERWISE
cI := "application/octet-stream"
ENDSWITCH
// if filename is still NIL I set it
IF cFileName == NIL
cFileName := _SERVER[ "SCRIPT_FILENAME" ]
ENDIF
// Starting Filters
IF cExt == "hrb"
#ifdef DEBUG_ACTIVE
//hb_ToOutDebug( "cFileName = %s, uhttpd_OSFileName( cFileName ) = %s,\n\r", cFileName, uhttpd_OSFileName( cFileName ) )
#endif
// Starting HRB module
RETURN Filter_HRB( cFileName )
// Security
IF ".." $ cFileName
uhttpd_SetStatusCode( 403 )
t_cErrorMsg := "Characters not allowed"
RETURN MakeResponse()
ENDIF
ELSEIF cExt == "exe"
//hb_toOutDebug( "cFileName = %s, uhttpd_OSFileName( cFileName ) = %s,\n\r s_hScriptAliases = %s\n\r", cFileName, uhttpd_OSFileName( cFileName ), hb_ValToExp( s_hScriptAliases ) )
// Starting CGI application
RETURN Filter_EXE( cFileName )
// checking extension
IF cExt == NIL
// checking if file exists
IF HB_FileExists( uhttpd_OSFileName( cFileName ) )
// extract extension
IF ( nI := RAT( ".", cFileName ) ) > 0
cExt := LOWER( SUBSTR( cFileName, nI + 1 ) )
ENDIF
// is it a directory ?
ELSEIF HB_DirExists( uhttpd_OSFileName( cFileName ) )
// if it exists as folder and it is missing trailing slash I add it and redirect to it
IF RIGHT( cFileName, 1 ) != "/"
uhttpd_AddHeader( "Location", "http://" + _SERVER[ "HTTP_HOST" ] + _SERVER[ "SCRIPT_NAME" ] + "/" )
RETURN MakeResponse()
ENDIF
// Search for directory index file, i.e.: index.html
IF ASCAN( s_aDirectoryIndex, ;
{|x| IIF( HB_FileExists( uhttpd_OSFileName( cFileName + X ) ), ( cFileName += X, .T. ), .F. ) } ) > 0
// I have to check filename again (behaviour changes on extension file name)
// resetting extension
cExt := NIL
LOOP
ENDIF
ELSE
// Standard file
// Check for PATH_INFO: I will search if there is a physical file removing parts from right
cBaseFile := cScript
lFound := FALSE
DO WHILE !Empty( cBaseFile )
//hb_toOutDebug( "cBaseFile = %s, cPathInfo = %s\n\r", cBaseFile, cPathInfo )
IF ( nPos := RAT( "/", cBaseFile ) ) > 0
cPathInfo := SubStr( cBaseFile, nPos ) + cPathInfo
cBaseFile := Left( cBaseFile, nPos - 1 )
ELSE
EXIT
ENDIF
IF HB_FileExists( uhttpd_OSFileName( _SERVER[ "DOCUMENT_ROOT" ] + cBaseFile ) )
cFileName := uhttpd_OSFileName( _SERVER[ "DOCUMENT_ROOT" ] + cBaseFile )
lFound := TRUE
EXIT
ENDIF
cFile := FileUnAlias( cBaseFile )
IF cFile != NIL .AND. HB_FileExists( uhttpd_OSFileName( cFile ) )
cFileName := uhttpd_OSFileName( cFile )
lFound := TRUE
EXIT
ENDIF
ENDDO
//hb_toOutDebug( "Uscita: cBaseFile = %s, cPathInfo = %s\n\r", cBaseFile, cPathInfo )
// Found a script file name
IF lFound .AND. !Empty( cPathInfo )
// Store PATH_INFO
_SERVER[ "PATH_INFO" ] := cPathInfo
_SERVER[ "PATH_TRANSLATED" ] := cFileName
// Restart
LOOP
ENDIF
uhttpd_AddHeader( "Content-Type", cI )
uhttpd_Write( HB_MEMOREAD( uhttpd_OSFileName( cFileName ) ) )
ENDIF
ELSE
// Unknown file type
cI := "application/octet-stream"
uhttpd_AddHeader( "Content-Type", cI )
uhttpd_Write( HB_MEMOREAD( uhttpd_OSFileName( cFileName ) ) )
ENDIF
// Directory content request
ELSEIF HB_DirExists( uhttpd_OSFileName( cFileName ) )
// Ok, now I have to see what action I have to take
// if it exists as folder, add trailing slash and redirect to it
IF RIGHT( cFileName, 1 ) != "/"
uhttpd_AddHeader( "Location", "http://" + _SERVER[ "HTTP_HOST" ] + _SERVER[ "SCRIPT_NAME" ] + "/" )
RETURN MakeResponse()
//hb_toOutDebug( "cExt = %s\n\r", cExt )
// Begin to search Handlers
IF cExt != NIL
cHandler := uhttpd_HGetValue( s_hHandlers, cExt )
ENDIF
// Search for directory index file, i.e.: index.html
IF ASCAN( s_aDirectoryIndex, ;
{|x| IIF( HB_FileExists( uhttpd_OSFileName( cFileName + X ) ), ( cFileName += X, .T. ), .F. ) } ) > 0
uhttpd_AddHeader( "Content-Type", "text/html" )
uhttpd_Write( HB_MEMOREAD( uhttpd_OSFileName( cFileName ) ) )
RETURN MakeResponse()
//hb_toOutDebug( "cHandler = %s\n\r", cHandler )
IF cHandler != NIL
xAction := uhttpd_HGetValue( s_hActions, cHandler )
ENDIF
// If I'm here it's means that I have no page, so, if it is defined, I will display content folder
IF !s_lIndexes
uhttpd_SetStatusCode( 403 )
t_cErrorMsg := "Display file list not allowed"
RETURN MakeResponse()
//hb_toOutDebug( "xAction = %s\n\r", xAction )
IF xAction == NIL
xAction := @Handler_Default()
ENDIF
// ----------------------- display folder content -------------------------------------
ShowFolder( cFileName )
//hb_toOutDebug( "xAction = %s\n\r", xAction )
ELSE
// Setting CGI vars
define_Env( _SERVER )
// We cannot handle request
// Eval Action
RETURN hb_ExecFromArray( xAction, { cFileName } )
uhttpd_SetStatusCode( 404 )
t_cErrorMsg := "File does not exist: " + cFileName
ENDIF
ENDDO
RETURN MakeResponse()
@@ -1747,6 +1871,7 @@ STATIC PROCEDURE ShowServerStatus()
uhttpd_AddHeader( "Content-Type", "text/html" )
uhttpd_Write( '<html><head>' )
uhttpd_Write( '<META HTTP-EQUIV="Refresh" CONTENT="' + LTrim( Str( PAGE_STATUS_REFRESH ) ) + ';URL=/ServerStatus">' )
uhttpd_Write( '<META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">' )
uhttpd_Write( '<title>Server Status</title><body><h1>Server Status</h1><pre>')
//uhttpd_Write( '<table border="0">')
@@ -1762,6 +1887,7 @@ STATIC PROCEDURE ShowServerStatus()
cThreads := "{ " + IIF( !Empty( cThreads ), Left( cThreads, Len( cThreads ) - 1 ), "<empty>" ) + " }"
uhttpd_Write( '<br>Running Threads: ' + cThreads )
#ifndef FIXED_THREADS
uhttpd_Write( '<br>Service Thread: ' + Str( s_nServiceThreads ) )
uhttpd_Write( '<br>Service Connections: ' + Str( s_nServiceConnections ) )
uhttpd_Write( '<br>Max Service Connections: ' + Str( s_nMaxServiceConnections ) )
@@ -1770,6 +1896,8 @@ STATIC PROCEDURE ShowServerStatus()
aEval( s_aServiceThreads, {|e| cThreads += LTrim( Str( hb_threadId( e ) ) ) + "," } )
cThreads := "{ " + IIF( !Empty( cThreads ), Left( cThreads, Len( cThreads ) - 1 ), "<empty>" ) + " }"
uhttpd_Write( '<br>Service Threads: ' + cThreads )
#endif // FIXED_THREADS
hb_mutexUnlock( s_hmtxBusy )
ENDIF
uhttpd_Write( '<br>Time: ' + Time() )
@@ -2003,7 +2131,8 @@ STATIC FUNCTION ParseIni( cConfig )
"START_NUM" => START_RUNNING_THREADS ,;
"MAX_NUM" => MAX_RUNNING_THREADS ;
},;
"ALIASES" => { => } ;
"SCRIPTALIASES" => { => } ,;
"ALIASES" => { => } ;
}, FALSE )
//hb_ToOutDebug( "hDefault = %s\n\r", hb_ValToExp( hDefault ) )
@@ -2029,7 +2158,13 @@ STATIC FUNCTION ParseIni( cConfig )
//hb_ToOutDebug( "cKey = %s\n\r", cKey )
IF cSection == "ALIASES"
IF cSection == "SCRIPTALIASES"
xVal := hSect[ cKey ]
IF xVal <> NIL
hDefault[ cSection ][ cKey ] := xVal
ENDIF
ELSEIF cSection == "ALIASES"
xVal := hSect[ cKey ]
IF xVal <> NIL
hDefault[ cSection ][ cKey ] := xVal
@@ -2243,7 +2378,7 @@ STATIC FUNCTION ErrorMessage( oError )
RETURN cMessage
// ------------------ FILTERS ---------------------
STATIC FUNCTION Filter_HRB( cFileName )
STATIC FUNCTION Handler_HrbScript( cFileName )
LOCAL xResult
LOCAL cHRBBody, pHRB, oError
@@ -2300,7 +2435,7 @@ STATIC FUNCTION Filter_HRB( cFileName )
RETURN MakeResponse()
STATIC FUNCTION Filter_EXE( cFileName )
STATIC FUNCTION Handler_CgiScript( cFileName )
LOCAL xResult
IF ( CGIExec( uhttpd_OSFileName(cFileName), @xResult ) ) == 0
@@ -2312,8 +2447,182 @@ STATIC FUNCTION Filter_EXE( cFileName )
ELSE
uhttpd_AddHeader( "Content-Type", "text/html" )
uhttpd_Write( "CGI Error" )
IF !Empty( xResult )
uhttpd_Write( xResult )
ELSE
uhttpd_Write( "CGI Error" )
ENDIF
ENDIF
RETURN MakeResponse()
// ----------------------------------------------------------------------------------
// HANDLERS
// ----------------------------------------------------------------------------------
// This handler handle static files
STATIC FUNCTION Handler_Default( cFileName )
LOCAL cMime
LOCAL cExt, nI
LOCAL hMimeTypes := LoadMimeTypes()
// If file exists
IF HB_FileExists( uhttpd_OSFileName( cFileName ) )
IF ( nI := RAT( ".", cFileName ) ) > 0
cExt := LOWER( SUBSTR( cFileName, nI + 1 ) )
cMime := uhttpd_HGetValue( hMimeTypes, cExt )
ENDIF
IF cMime == NIL
// Unknown file type
cMime := "application/octet-stream"
ENDIF
uhttpd_AddHeader( "Content-Type", cMime )
uhttpd_Write( HB_MEMOREAD( uhttpd_OSFileName( cFileName ) ) )
// Directory content request
ELSEIF HB_DirExists( uhttpd_OSFileName( cFileName ) )
// If I'm here it's means that I have no page, so, if it is defined, I will display content folder
IF !s_lIndexes
uhttpd_SetStatusCode( 403 )
t_cErrorMsg := "Display file list not allowed"
ELSE
// ----------------------- display folder content -------------------------------------
ShowFolder( cFileName )
ENDIF
ELSE
// We cannot handle request
uhttpd_SetStatusCode( 404 )
t_cErrorMsg := "File does not exist: " + cFileName
ENDIF
RETURN MakeResponse()
// This handler handle server status
STATIC FUNCTION Handler_ServerStatus()
LOCAL cThreads
uhttpd_AddHeader( "Content-Type", "text/html" )
uhttpd_Write( '<html><head>' )
uhttpd_Write( '<META HTTP-EQUIV="Refresh" CONTENT="' + LTrim( Str( PAGE_STATUS_REFRESH ) ) + ';URL=/ServerStatus">' )
uhttpd_Write( '<META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">' )
uhttpd_Write( '<title>Server Status</title><body><h1>Server Status</h1><pre>')
//uhttpd_Write( '<table border="0">')
uhttpd_Write( 'SERVER: ' + _SERVER[ "SERVER_SOFTWARE" ] + " Server at " + _SERVER[ "SERVER_NAME" ] + " Port " + _SERVER[ "SERVER_PORT" ] )
uhttpd_Write( '<br>' )
IF hb_mutexLock( s_hmtxBusy )
uhttpd_Write( '<br>Thread: ' + Str( s_nThreads ) )
uhttpd_Write( '<br>Connections: ' + Str( s_nConnections ) )
uhttpd_Write( '<br>Max Connections: ' + Str( s_nMaxConnections ) )
uhttpd_Write( '<br>Total Connections: ' + Str( s_nTotConnections ) )
cThreads := ""
aEval( s_aRunningThreads, {|e| cThreads += LTrim( Str( hb_threadId( e ) ) ) + "," } )
cThreads := "{ " + IIF( !Empty( cThreads ), Left( cThreads, Len( cThreads ) - 1 ), "<empty>" ) + " }"
uhttpd_Write( '<br>Running Threads: ' + cThreads )
#ifndef FIXED_THREADS
uhttpd_Write( '<br>Service Thread: ' + Str( s_nServiceThreads ) )
uhttpd_Write( '<br>Service Connections: ' + Str( s_nServiceConnections ) )
uhttpd_Write( '<br>Max Service Connections: ' + Str( s_nMaxServiceConnections ) )
uhttpd_Write( '<br>Total Service Connections: ' + Str( s_nTotServiceConnections ) )
cThreads := ""
aEval( s_aServiceThreads, {|e| cThreads += LTrim( Str( hb_threadId( e ) ) ) + "," } )
cThreads := "{ " + IIF( !Empty( cThreads ), Left( cThreads, Len( cThreads ) - 1 ), "<empty>" ) + " }"
uhttpd_Write( '<br>Service Threads: ' + cThreads )
#endif // FIXED_THREADS
hb_mutexUnlock( s_hmtxBusy )
ENDIF
uhttpd_Write( '<br>Time: ' + Time() )
//uhttpd_Write( '</table>')
uhttpd_Write( "<hr></pre></body></html>" )
RETURN MakeResponse()
STATIC FUNCTION LoadMimeTypes()
LOCAL hMimeTypes
// TODO: load mime types from file
hMimeTypes := { ;
"css" => "text/css" ,;
"htm" => "text/html" ,;
"html" => "text/html" ,;
"txt" => "text/plain" ,;
"text" => "text/plain" ,;
"asc" => "text/plain" ,;
"c" => "text/plain" ,;
"h" => "text/plain" ,;
"cpp" => "text/plain" ,;
"hpp" => "text/plain" ,;
"log" => "text/plain" ,;
"rtf" => "text/rtf" ,;
"xml" => "text/xml" ,;
"xsl" => "text/xsl" ,;
"bmp" => "image/bmp" ,;
"gif" => "image/gif" ,;
"jpg" => "image/jpeg" ,;
"jpe" => "image/jpeg" ,;
"jpeg" => "image/jpeg" ,;
"png" => "image/png" ,;
"tif" => "image/tiff" ,;
"tiff" => "image/tiff" ,;
"djv" => "image/vnd.djvu" ,;
"djvu" => "image/vnd.djvu" ,;
"ico" => "image/x-icon" ,;
"xls" => "application/excel" ,;
"doc" => "application/msword" ,;
"pdf" => "application/pdf" ,;
"ps" => "application/postscript" ,;
"eps" => "application/postscript" ,;
"ppt" => "application/powerpoint" ,;
"bz2" => "application/x-bzip2" ,;
"gz" => "application/x-gzip" ,;
"tgz" => "application/x-gtar" ,;
"js" => "application/x-javascript" ,;
"tar" => "application/x-tar" ,;
"tex" => "application/x-tex" ,;
"zip" => "application/zip" ,;
"midi" => "audio/midi" ,;
"mp3" => "audio/mpeg" ,;
"wav" => "audio/x-wav" ,;
"qt" => "video/quicktime" ,;
"mov" => "video/quicktime" ,;
"avi" => "video/x-msvideo" ;
}
RETURN hMimeTypes
STATIC FUNCTION FileUnAlias( cScript )
LOCAL cFileName, x
// Checking if the request contains a Script Alias
IF HB_HHasKey( s_hScriptAliases, cScript )
// in this case I have to substitute the alias with the real file name
cFileName := hb_hGet( s_hScriptAliases, cScript )
// substitute macros
cFileName := StrTran( cFileName, "$(DOCROOT_DIR)", _SERVER[ "DOCUMENT_ROOT" ] )
cFileName := StrTran( cFileName, "$(APP_DIR)" , Exe_Path() )
ENDIF
IF cFileName == NIL
// Checking if the request contains an alias
FOR EACH x IN s_hAliases
IF x:__enumKey() == Left( cScript, Len( x:__enumKey() ) )
cFileName := x:__enumValue() + SubStr( cScript, Len( x:__enumKey() ) + 1 )
// substitute macros
cFileName := StrTran( cFileName, "$(DOCROOT_DIR)", _SERVER[ "DOCUMENT_ROOT" ] )
cFileName := StrTran( cFileName, "$(APP_DIR)" , Exe_Path() )
EXIT
ENDIF
NEXT
ENDIF
RETURN cFileName