Files
harbour-core/harbour/examples/httpsrv/uhttpd.prg
Francesco Saverio Giudice a66d739f24 2009-07-31 10:16 UTC+0200 Francesco Saverio Giudice (info/at/fsgiudice.com)
* harbour/Changelog
    * fixed wrong month in previous updates
  * harbour/examples/httpsrv/uhttpd.prg
    * Fixed readRequest() and Handler_HrbScript() functions as per
      Przemek suggestions. Thank you.
2009-07-31 08:20:13 +00:00

2973 lines
95 KiB
Plaintext

/*
* $Id$
*/
/*
* Harbour Project source code:
* uHTTPD (Micro HTTP server)
*
* Copyright 2009 Francesco Saverio Giudice <info / at / fsgiudice.com>
* Copyright 2008 Mindaugas Kavaliauskas (dbtopas at dbtopas.lt)
* www - http://www.harbour-project.org
*
* Credits:
* Based on first version posted from Mindaugas Kavaliauskas on
* developers NG on December 15th, 2008 whom give my thanks to have
* shared initial work.
* Francesco.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA (or visit the web site http://www.gnu.org/).
*
* As a special exception, the Harbour Project gives permission for
* additional uses of the text contained in its release of Harbour.
*
* The exception is that, if you link the Harbour libraries with other
* files to produce an executable, this does not by itself cause the
* resulting executable to be covered by the GNU General Public License.
* Your use of that executable is in no way restricted on account of
* linking the Harbour library code into it.
*
* This exception does not however invalidate any other reasons why
* the executable file might be covered by the GNU General Public License.
*
* This exception applies only to the code released by the Harbour
* Project under the name Harbour. If you copy code from other
* Harbour Project or Free Software Foundation releases into a copy of
* Harbour, as the General Public License permits, the exception does
* not apply to the code that you add in this way. To avoid misleading
* anyone as to the status of such modified files, you must delete
* this exception notice from them.
*
* If you write modifications of your own for Harbour, it is your choice
* whether to permit this exception to apply to your modifications.
* If you do not wish that, delete this exception notice.
*
*/
/*
* A simple HTTP server.
*
* More description to come.
*
*
*/
/*
TODO:
- security check
verify to launch .hrb and .exe *only* from cgi-bin
- optimize code
- add SSL support
- fix dynamic threads (now locked to fixed number)
- add full mime type handling
- add cgi exec on linux
- add .htaccess support
- fix binding to address
*/
// 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> )
#else
/* TRY / CATCH / FINALLY / END */
#xcommand TRY => BEGIN SEQUENCE WITH {|oErr| Break( oErr )}
#xcommand CATCH [<!oErr!>] => RECOVER [USING <oErr>] <-oErr->
#xcommand FINALLY => ALWAYS
#endif
#include "fileio.ch"
#include "common.ch"
#include "inkey.ch"
#include "error.ch"
#include "hbmemory.ch"
#include "hbgtinfo.ch"
#include "hbextern.ch" // need this to use with HRB
#ifdef GD_SUPPORT
// adding GD support
REQUEST GDIMAGE, GDIMAGECHAR, GDCHART
#define APP_GD_SUPPORT "_GD"
#stdout "Lib GD support enabled"
#else
#define APP_GD_SUPPORT ""
#stdout "Lib GD support disabled"
#endif
#ifdef USE_HB_INET
#define APP_INET_SUPPORT "_INET"
#stdout "inet socket API"
#else
#define APP_INET_SUPPORT ""
#stdout "custom socket API"
#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.4"
#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 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
#define MAX_SERVICE_THREADS 3 // Max running threads
#define LISTEN_PORT 8082 // differs from standard 80 port for tests in case
// anyone has a apache/IIS installed
#define FILE_STOP ".uhttpd.stop"
#define FILE_ACCESS_LOG "logs" + HB_OSPathSeparator() + "access.log"
#define FILE_ERROR_LOG "logs" + HB_OSPathSeparator() + "error.log"
#define DIRECTORYINDEX_ARRAY { "index.html", "index.htm" }
#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 // 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)
#define CR_LF (CHR(13)+CHR(10))
#define HB_IHASH() HB_HSETCASEMATCH( {=>}, FALSE )
#ifndef __XHARBOUR__
#ifdef __PLATFORM__WINDOWS
REQUEST HB_GT_WVT_DEFAULT
REQUEST HB_GT_WIN
REQUEST HB_GT_NUL
#else
REQUEST HB_GT_TRM_DEFAULT
REQUEST HB_GT_NUL
#endif
#define THREAD_GT hb_gtVersion()
#else
REQUEST HB_GT_WVT
REQUEST HB_GT_WIN
REQUEST HB_GT_NUL
#endif
// dynamic call for HRB support
DYNAMIC HRBMAIN
STATIC s_lQuitRequest := FALSE
STATIC s_hmtxQueue, s_hmtxServiceThreads, s_hmtxRunningThreads, s_hmtxLog, s_hmtxConsole, s_hmtxBusy
STATIC s_hmtxHRB
STATIC s_hfileLogAccess, s_hfileLogError, s_cApplicationRoot, s_cDocumentRoot, s_lIndexes, s_lConsole, s_nPort
STATIC s_cSessionPath
STATIC s_nThreads, s_nStartThreads, s_nMaxThreads
STATIC s_nServiceThreads, s_nStartServiceThreads, s_nMaxServiceThreads
STATIC s_nConnections, s_nMaxConnections, s_nTotConnections
STATIC s_nServiceConnections, s_nMaxServiceConnections, s_nTotServiceConnections
STATIC s_aRunningThreads := {}
STATIC s_aServiceThreads := {}
STATIC s_hHRBModules := {=>}
STATIC s_aDirectoryIndex
#ifdef USE_HB_INET
STATIC s_cLocalAddress, s_nLocalPort
#endif
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
MEMVAR _SERVER, _GET, _POST, _COOKIE, _SESSION, _REQUEST, _HTTP_REQUEST, _HTTP_RESPONSE, m_cPost
ANNOUNCE ERRORSYS
// ----------------------------------------
//
// M A I N
//
// ----------------------------------------
FUNCTION MAIN( ... )
LOCAL nPort, hListen, hSocket, aRemote, cI, xVal
LOCAL aThreads, nStartThreads, nMaxThreads, nStartServiceThreads
LOCAL i, cPar, lStop
LOCAL cGT, cApplicationRoot, cDocumentRoot, lIndexes, cConfig
LOCAL lConsole, lScriptAliasMixedCase, aDirectoryIndex
LOCAL nProgress := 0
LOCAL hDefault, cLogAccess, cLogError, cSessionPath
LOCAL cCmdPort, cCmdApplicationRoot, cCmdDocumentRoot, lCmdIndexes, nCmdStartThreads, nCmdMaxThreads
LOCAL nConsoleRows, nConsoleCols
LOCAL nCmdConsoleRows, nCmdConsoleCols
IF !HB_MTVM()
? "I need multhread support. Please, recompile me!"
WAIT
RETURN 2
ENDIF
// ----------------------- Initializations ---------------------------------
SysSettings()
ErrorBlock( { | oError | uhttpd_DefError( oError ) } )
// ----------------------- Parameters defaults -----------------------------
// defaults not changeble via ini file
lStop := FALSE
cConfig := EXE_Path() + hb_OSPathSeparator() + APP_NAME + ".ini"
lConsole := TRUE
nStartServiceThreads := START_SERVICE_THREADS
// Check GT version - if I have started app with //GT:NUL then I have to disable
// console and application will start in hidden way.
cGT := HB_GTVERSION()
IF ( cGT == "NUL" )
lConsole := FALSE
ELSE
hb_gtInfo( HB_GTI_NOTIFIERBLOCK, {|nEvent, ...| GT_notifier( nEvent, ... ) } )
ENDIF
// TOCHECK: now not force case insensitive
//HB_HSETCASEMATCH( s_hScriptAliases, FALSE )
// ----------------- Line command parameters checking ----------------------
i := 1
DO WHILE ( i <= PCount() )
cPar := hb_PValue( i++ )
DO CASE
CASE cPar == "--port" .OR. cPar == "-p"
cCmdPort := hb_PValue( i++ )
CASE cPar == "--approot" .OR. cPar == "-a"
cCmdApplicationRoot := hb_PValue( i++ )
CASE cPar == "--docroot" .OR. cPar == "-d"
cCmdDocumentRoot := hb_PValue( i++ )
CASE cPar == "--indexes" .OR. cPar == "-i"
lCmdIndexes := TRUE
CASE cPar == "--stop" .OR. cPar == "-s"
lStop := TRUE
CASE cPar == "--config" .OR. cPar == "-c"
cConfig := hb_PValue( i++ )
CASE cPar == "--start-threads" .OR. cPar == "-ts"
nCmdStartThreads := Val( hb_PValue( i++ ) )
CASE cPar == "--max-threads" .OR. cPar == "-tm"
nCmdMaxThreads := Val( hb_PValue( i++ ) )
CASE cPar == "--console-rows" .OR. cPar == "-cr"
nCmdConsoleRows := Val( hb_PValue( i++ ) )
CASE cPar == "--console-cols" .OR. cPar == "-cc"
nCmdConsoleCols := Val( hb_PValue( i++ ) )
CASE cPar == "--help" .OR. Lower( cPar ) == "-h" .OR. cPar == "-?"
help()
RETURN 0
OTHERWISE
help()
RETURN 0
ENDCASE
ENDDO
// -------------------- checking STOP request -------------------------------
IF lStop
HB_MEMOWRIT( FILE_STOP, "" )
RETURN 0
ELSE
FERASE( FILE_STOP )
ENDIF
// ----------------- Parse ini file ----------------------------------------
//hb_ToOutDebug( "cConfig = %s\n\r", cConfig )
hDefault := ParseIni( cConfig )
// ------------------- Parameters changeable from ini file ----------------
// All key values MUST be in uppercase
nPort := hDefault[ "MAIN" ][ "PORT" ]
cApplicationRoot := hDefault[ "MAIN" ][ "APPLICATION_ROOT" ]
cDocumentRoot := hDefault[ "MAIN" ][ "DOCUMENT_ROOT" ]
lIndexes := hDefault[ "MAIN" ][ "SHOW_INDEXES" ]
lScriptAliasMixedCase := hDefault[ "MAIN" ][ "SCRIPTALIASMIXEDCASE" ]
cSessionPath := hDefault[ "MAIN" ][ "SESSIONPATH" ]
aDirectoryIndex := hDefault[ "MAIN" ][ "DIRECTORYINDEX" ]
nConsoleRows := hDefault[ "MAIN" ][ "CONSOLE-ROWS" ]
nConsoleCols := hDefault[ "MAIN" ][ "CONSOLE-COLS" ]
cLogAccess := hDefault[ "LOGFILES" ][ "ACCESS" ]
cLogError := hDefault[ "LOGFILES" ][ "ERROR" ]
nStartThreads := hDefault[ "THREADS" ][ "START_NUM" ]
nMaxThreads := hDefault[ "THREADS" ][ "MAX_NUM" ]
// 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[ "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 ----------------
IF cCmdPort != NIL
nPort := Val( cCmdPort )
ENDIF
IF cCmdApplicationRoot != NIL
cApplicationRoot := cCmdApplicationRoot
ENDIF
IF cCmdDocumentRoot != NIL
cDocumentRoot := cCmdDocumentRoot
ENDIF
IF lCmdIndexes != NIL
lIndexes := lCmdIndexes
ENDIF
IF nCmdStartThreads != NIL
nStartThreads := nCmdStartThreads
ENDIF
IF nCmdMaxThreads != NIL
nMaxThreads := nCmdMaxThreads
ENDIF
IF nCmdConsoleRows != NIL
nConsoleRows := nCmdConsoleRows
ENDIF
IF nCmdConsoleCols != NIL
nConsoleCols := nCmdConsoleCols
ENDIF
// -------------------- adjusting MACROS values ----------------------------
// cApplicationRoot can be only ExePath() or a correct full path
cDocumentRoot := StrTran( cDocumentRoot, "$(APP_DIR)", cApplicationRoot )
cSessionPath := StrTran( cSessionPath , "$(APP_DIR)", cApplicationRoot )
cLogAccess := StrTran( cLogAccess , "$(APP_DIR)", cApplicationRoot )
cLogError := StrTran( cLogError , "$(APP_DIR)", cApplicationRoot )
// -------------------- checking starting values ----------------------------
IF nPort <= 0 .OR. nPort > 65535
? "Invalid port number:", nPort
WAIT
RETURN 1
ENDIF
IF HB_ISSTRING( cApplicationRoot )
cI := cApplicationRoot
IF HB_DirExists( cI )
IF RIGHT( cI, 1 ) == "/" .AND. LEN(cI) > 2 .AND. SUBSTR( cI, LEN( cI ) - 2, 1 ) != ":"
s_cApplicationRoot := LEFT( cI, LEN( cI ) - 1 )
ELSE
s_cApplicationRoot := cI
ENDIF
ELSE
? "Invalid application root:", cI
WAIT
RETURN 3
ENDIF
ELSE
? "Invalid application root"
WAIT
RETURN 3
ENDIF
#ifdef DEBUG_ACTIVE
hb_ToOutDebug( "s_cDocumentRoot = %s, cDocumentRoot = %s\n\r", s_cDocumentRoot, cDocumentRoot )
#endif
IF HB_ISSTRING( cDocumentRoot )
//cI := STRTRAN( SUBSTR( cDocumentRoot, 2 ), "\", "/" )
cI := cDocumentRoot
IF HB_DirExists( cI )
IF RIGHT( cI, 1 ) == "/" .AND. LEN(cI) > 2 .AND. SUBSTR( cI, LEN( cI ) - 2, 1 ) != ":"
s_cDocumentRoot := LEFT( cI, LEN( cI ) - 1 )
ELSE
s_cDocumentRoot := cI
ENDIF
ELSE
? "Invalid document root:", cI
WAIT
RETURN 3
ENDIF
ELSE
? "Invalid document root"
WAIT
RETURN 3
ENDIF
#ifdef DEBUG_ACTIVE
hb_ToOutDebug( "s_cDocumentRoot = %s, cDocumentRoot = %s\n\r", s_cDocumentRoot, cDocumentRoot )
#endif
IF nMaxThreads <= 0
nMaxThreads := MAX_RUNNING_THREADS
ENDIF
IF nStartThreads < 0
nStartThreads := 0
ELSEIF nStartThreads > nMaxThreads
nStartThreads := nMaxThreads
ENDIF
IF nConsoleRows < 1 //.OR. nConsoleRows > MaxRow() + 1
nConsoleRows := MaxRow()
ENDIF
IF nConsoleCols < 1 //.OR. nConsoleCols > MaxCol() + 1
nConsoleCols := MaxCol()
ENDIF
// -------------------- assign STATIC values --------------------------------
s_lIndexes := lIndexes
s_lConsole := lConsole
s_nPort := nPort
s_nThreads := 0
s_nStartThreads := nStartThreads
s_nMaxThreads := nMaxThreads
s_nServiceThreads := 0
s_nStartServiceThreads := nStartServiceThreads
s_nMaxServiceThreads := MAX_SERVICE_THREADS
s_nConnections := 0
s_nMaxConnections := 0
s_nTotConnections := 0
s_nServiceConnections := 0
s_nMaxServiceConnections := 0
s_nTotServiceConnections := 0
s_cSessionPath := cSessionPath
s_aDirectoryIndex := aDirectoryIndex
// --------------------- Open log files -------------------------------------
IF ( s_hfileLogAccess := FOPEN( cLogAccess, FO_CREAT + FO_WRITE ) ) == -1
? "Can't open access log file"
WAIT
RETURN 1
ENDIF
FSEEK( s_hfileLogAccess, 0, FS_END )
IF ( s_hfileLogError := FOPEN( cLogError, FO_CREAT + FO_WRITE ) ) == -1
? "Can't open error log file"
WAIT
RETURN 1
ENDIF
FSEEK( s_hfileLogError, 0, FS_END )
// --------------------- MAIN PART ------------------------------------------
IF s_lConsole
SET CURSOR OFF
SetMode( nConsoleRows, nConsoleCols )
//hb_toOutDebug( "nConsoleRows = %s, nConsoleCols = %s", nConsoleRows, nConsoleCols )
//hb_toOutDebug( "nCmdConsoleRows = %s, nCmdConsoleCols = %s", nCmdConsoleRows, nCmdConsoleCols )
ENDIF
// --------------------- define mutexes -------------------------------------
s_hmtxQueue := hb_mutexCreate()
s_hmtxLog := hb_mutexCreate()
s_hmtxConsole := hb_mutexCreate()
s_hmtxBusy := hb_mutexCreate()
s_hmtxRunningThreads := hb_mutexCreate()
s_hmtxServiceThreads := hb_mutexCreate()
s_hmtxHRB := hb_mutexCreate()
WriteToConsole( "--- Starting " + APP_NAME + " ---" )
// --------------------------------------------------------------------------
// SOCKET CREATION
// --------------------------------------------------------------------------
#ifdef USE_HB_INET
hListen := hb_InetServer( nPort )
IF hb_InetErrorCode( hListen ) != 0
? "Bind Error"
WAIT
ELSE
s_nLocalPort := hb_InetPort( hListen )
s_cLocalAddress := hb_InetAddress( hListen )
hb_InetTimeOut( hListen, 3000 )
#else
hListen := socket_create()
IF socket_bind( hListen, { AF_INET, "0.0.0.0", nPort } ) == -1
? "bind() error", socket_error()
ELSEIF socket_listen( hListen ) == -1
? "listen() error", socket_error()
ELSE
#endif
// --------------------------------------------------------------------------------- //
// Starting Accept connection thread
// --------------------------------------------------------------------------------- //
WriteToConsole( "Starting AcceptConnection Thread" )
aThreads := {}
AADD( aThreads, hb_threadStart( @AcceptConnections() ) )
#ifdef DEBUG_ACTIVE
hb_ToOutDebug( "Len( aThreads ) = %i\n\r", Len( aThreads ) )
#endif
// --------------------------------------------------------------------------------- //
// main loop
// --------------------------------------------------------------------------------- //
WriteToConsole( "Starting main loop" )
IF s_lConsole
hb_DispOutAt( 1, 5, APP_NAME + " - web server - v. " + APP_VERSION )
hb_DispOutAt( 4, 5, "Server listening (Port: " + LTrim( Str( nPort ) ) + ") : ..." )
hb_DispOutAt( 10, 9, "Waiting." )
ENDIF
DO WHILE .T.
#ifdef __PLATFORM__WINDOWS
// windows resource releasing - 1 millisecond wait
IF WIN_SYSREFRESH( 1 ) != 0
EXIT
ENDIF
#endif
IF hb_mutexLock( s_hmtxBusy )
IF s_lQuitRequest
hb_mutexUnlock( s_hmtxBusy )
EXIT
ENDIF
hb_mutexUnlock( s_hmtxBusy )
ENDIF
IF s_lConsole
// Show application infos
IF hb_mutexLock( s_hmtxBusy )
hb_DispOutAt( 5, 5, "Threads : " + Transform( s_nThreads, "9999999999" ) )
hb_DispOutAt( 6, 5, "Connections : " + Transform( s_nConnections, "9999999999" ) )
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
// Show progress
Progress( @nProgress )
ENDIF
// Wait a connection
#ifdef USE_HB_INET
IF HB_InetDataReady( hListen, 100 ) > 0
#else
IF socket_select( { hListen },,, 50 ) > 0
#endif
// reset remote values
aRemote := NIL
// Accept a remote connection
#ifdef USE_HB_INET
hSocket := hb_InetAccept( hListen )
#else
hSocket := socket_accept( hListen, @aRemote )
#endif
IF hSocket == NIL
#ifdef USE_HB_INET
WriteToConsole( hb_StrFormat( "accept() error" ) )
#else
WriteToConsole( hb_StrFormat( "accept() error: %s", socket_error() ) )
#endif
ELSE
// Send accepted connection to AcceptConnections() thread
hb_mutexNotify( s_hmtxQueue, hSocket )
ENDIF
ELSE
// Checking if I have to quit
IF HB_FileExists( FILE_STOP )
FERASE( FILE_STOP )
EXIT
ENDIF
ENDIF
// Memory release
//hb_GCAll( TRUE )
ENDDO
WriteToConsole( "Waiting threads" )
// Send to threads that they have to stop
AEVAL( aThreads, {|| hb_mutexNotify( s_hmtxQueue, NIL ) } )
// Wait threads to end
AEVAL( aThreads, {|h| hb_threadJoin( h ) } )
ENDIF
WriteToConsole( "--- Quitting " + APP_NAME + " ---" )
// Close socket
#ifdef USE_HB_INET
hb_InetClose( hListen )
#else
socket_close( hListen )
#endif
// Close log files
FCLOSE( s_hfileLogAccess )
FCLOSE( s_hfileLogError )
SET CURSOR ON
RETURN 0
// --------------------------------------------------------------------------------- //
// THREAD FUNCTIONS
// --------------------------------------------------------------------------------- //
STATIC FUNCTION AcceptConnections()
LOCAL hSocket
LOCAL n
#ifndef FIXED_THREADS
LOCAL nConnections, nThreads, nMaxThreads
LOCAL nServiceConnections, nServiceThreads, nMaxServiceThreads
LOCAL lCanNotify
#endif
LOCAL pThread
LOCAL lQuitRequest := FALSE
ErrorBlock( { | oError | uhttpd_DefError( oError ) } )
WriteToConsole( "Starting AcceptConnections()" )
IF hb_mutexLock( s_hmtxBusy )
// Starting initial running threads
FOR n := 1 TO s_nStartThreads
pThread := hb_threadStart( @ProcessConnection() )
AADD( s_aRunningThreads, pThread )
NEXT
// Starting initial service threads
FOR n := 1 TO s_nStartServiceThreads
pThread := hb_threadStart( @ServiceConnection() )
AADD( s_aServiceThreads, pThread )
NEXT
hb_mutexUnlock( s_hmtxBusy )
ENDIF
// Main AcceptConnections loop
DO WHILE .T.
// reset socket
hSocket := NIL
#ifdef __PLATFORM__WINDOWS
// releasing resources
IF WIN_SYSREFRESH( 1 ) != 0
lQuitRequest := TRUE
ENDIF
#endif
IF hb_mutexLock( s_hmtxBusy )
IF s_lQuitRequest
hb_mutexUnlock( s_hmtxBusy )
lQuitRequest := TRUE
ENDIF
hb_mutexUnlock( s_hmtxBusy )
ENDIF
// Waiting a connection from main application loop
IF !lQuitRequest
hb_mutexSubscribe( s_hmtxQueue,, @hSocket )
ENDIF
// I have a QUIT request
IF hSocket == NIL .OR. lQuitRequest
// Requesting to Running threads to quit (using -1 value)
AEVAL( s_aRunningThreads, {|| hb_mutexNotify( s_hmtxRunningThreads, -1 ) } )
#ifndef FIXED_THREADS
// Requesting to Service threads to quit (using -1 value)
AEVAL( s_aServiceThreads, {|| hb_mutexNotify( s_hmtxServiceThreads, -1 ) } )
#endif
// waiting running threads to quit
AEVAL( s_aRunningThreads, {|h| hb_threadJoin( h ) } )
#ifndef FIXED_THREADS
// waiting service threads to quit
AEVAL( s_aServiceThreads, {|h| hb_threadJoin( h ) } )
#endif
IF hb_mutexLock( s_hmtxBusy )
//hb_ToOutDebug( "Len( s_aRunningThreads ) = %i\n\r", Len( s_aRunningThreads ) )
asize( s_aRunningThreads, 0 )
#ifndef FIXED_THREADS
asize( s_aServiceThreads, 0 )
#endif
hb_mutexUnlock( s_hmtxBusy )
ENDIF
EXIT
ENDIF
#ifndef FIXED_THREADS
// Load current state
IF hb_mutexLock( s_hmtxBusy )
nConnections := s_nConnections
nThreads := s_nThreads
nMaxThreads := s_nMaxThreads
nServiceConnections:= s_nServiceConnections
nServiceThreads := s_nServiceThreads
nMaxServiceThreads := s_nMaxServiceThreads
hb_mutexUnlock( s_hmtxBusy )
ENDIF
lCanNotify := FALSE
// If I have no more running threads to use ...
IF nConnections > nMaxThreads
// If I have no more of service threads to use ... (DOS attack ?)
IF nServiceConnections > nMaxServiceThreads
// DROP connection
#ifdef USE_HB_INET
hb_InetClose( hSocket )
#else
socket_shutdown( hSocket )
socket_close( hSocket )
#endif
// If I have no service threads in use ...
ELSEIF nServiceConnections >= nServiceThreads
// Add one more
IF hb_mutexLock( s_hmtxBusy )
pThread := hb_threadStart( @ServiceConnection() )
AADD( s_aServiceThreads, pThread )
lCanNotify := TRUE
hb_mutexUnlock( s_hmtxBusy )
ENDIF
ENDIF
// Otherwise I send connection to current service thread queue
IF lCanNotify
hb_mutexNotify( s_hmtxServiceThreads, hSocket )
ENDIF
LOOP
// If I have no free running threads to use ...
ELSEIF nConnections >= nThreads
// Add one more
IF hb_mutexLock( s_hmtxBusy )
pThread := hb_threadStart( @ProcessConnection() )
AADD( s_aRunningThreads, pThread )
lCanNotify := TRUE
hb_mutexUnlock( s_hmtxBusy )
ENDIF
ELSE
lCanNotify := TRUE
ENDIF
// 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
WriteToConsole( "Quitting AcceptConnections()" )
RETURN 0
// --------------------------------------------------------------------------------- //
// CONNECTIONS
// --------------------------------------------------------------------------------- //
STATIC FUNCTION ProcessConnection()
LOCAL hSocket, nLen, cRequest, cSend
LOCAL nMsecs, nParseTime, nPos, nThreadID
LOCAL lQuitRequest := FALSE
PRIVATE _SERVER, _GET, _POST, _COOKIE, _SESSION, _REQUEST, _HTTP_REQUEST, _HTTP_RESPONSE, m_cPost
nThreadId := hb_threadID()
#ifdef DEBUG_ACTIVE
hb_ToOutDebug( "nThreadId = %s\r\n", nThreadId )
#endif
ErrorBlock( { | oError | uhttpd_DefError( oError ) } )
WriteToConsole( "Starting ProcessConnections() " + hb_CStr( nThreadID ) )
IF hb_mutexLock( s_hmtxBusy )
s_nThreads++
hb_mutexUnlock( s_hmtxBusy )
ENDIF
// ProcessConnection Loop
DO WHILE .T.
// Reset socket
hSocket := NIL
#ifdef __PLATFORM__WINDOWS
// releasing resources
IF WIN_SYSREFRESH( 1 ) != 0
lQuitRequest := TRUE
EXIT
ENDIF
#endif
IF hb_mutexLock( s_hmtxBusy )
IF s_lQuitRequest
hb_mutexUnlock( s_hmtxBusy )
lQuitRequest := TRUE
EXIT
ENDIF
hb_mutexUnlock( s_hmtxBusy )
ENDIF
// Waiting a connection from AcceptConnections() but up to defined time
hb_mutexSubscribe( s_hmtxRunningThreads, THREAD_MAX_WAIT, @hSocket )
// received a -1 value, I have to quit
IF HB_ISNUMERIC( hSocket )
lQuitRequest := TRUE
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
hb_mutexUnlock( s_hmtxBusy )
LOOP
ENDIF
hb_mutexUnlock( s_hmtxBusy )
ENDIF
EXIT
#else // FIXED_THREADS
LOOP
#endif // FIXED_THREADS
ENDIF
// Connection accepted
IF hb_mutexLock( s_hmtxBusy )
s_nConnections++
s_nTotConnections++
s_nMaxConnections := Max( s_nConnections, s_nMaxConnections )
hb_mutexUnlock( s_hmtxBusy )
ENDIF
// Save initial time
nMsecs := hb_milliseconds()
BEGIN SEQUENCE
cRequest := NIL
/* receive query */
nLen := readRequest( hSocket, @cRequest )
#ifdef DEBUG_ACTIVE
hb_ToOutDebug( "cRequest -- BEGIN --\n\r%s\n\rcRequest -- END --\n\r", cRequest )
#endif
IF nLen == -1
#ifdef USE_HB_INET
? "recv() error:", hb_InetErrorCode( hSocket ), hb_InetErrorDesc( hSocket )
#else
? "recv() error:", socket_error()
#endif
ELSEIF nLen == 0 /* connection closed */
ELSE
//hb_ToOutDebug( "cRequest -- BEGIN --\n\r%s\n\rcRequest -- END --\n\r", cRequest )
_SERVER := HB_IHASH(); _GET := HB_IHASH(); _POST := HB_IHASH(); _COOKIE := HB_IHASH()
_SESSION := HB_IHASH(); _REQUEST := HB_IHASH(); _HTTP_REQUEST := HB_IHASH(); _HTTP_RESPONSE := HB_IHASH()
m_cPost := NIL
t_cResult := ""
//t_aHeader := {}
t_nStatusCode := 200
t_cErrorMsg := ""
defineServer( hSocket )
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 _HTTP_RESPONSE = %s\n\r", hb_ValToExp( _SERVER ), hb_ValToExp( _GET ), hb_ValToExp( _POST ), hb_ValToExp( _REQUEST ), hb_ValToExp( _HTTP_REQUEST ), hb_ValToExp( _HTTP_RESPONSE ) )
cSend := uproc_default()
ELSE
//uhttpd_SetStatusCode( 400 )
cSend := MakeResponse()
ENDIF
#ifdef DEBUG_ACTIVE
hb_ToOutDebug( "cSend = %s\n\r", cSend )
#endif
sendReply( hSocket, cSend )
WriteToLog( cRequest )
// Destroy PRIVATE VARIABLES
_SERVER := _GET := _POST := _COOKIE := _SESSION := _REQUEST := _HTTP_REQUEST := _HTTP_RESPONSE := m_cPost := NIL
ENDIF
END SEQUENCE
nParseTime := hb_milliseconds() - nMsecs
WriteToConsole( "Page served in : " + Str( nParseTime/1000, 7, 4 ) + " seconds" )
#ifdef USE_HB_INET
hb_InetClose( hSocket )
hSocket := NIL
#else
socket_shutdown( hSocket )
socket_close( hSocket )
#endif
IF hb_mutexLock( s_hmtxBusy )
s_nConnections--
hb_mutexUnlock( s_hmtxBusy )
ENDIF
// Memory release
hb_GCAll( TRUE )
ENDDO
WriteToConsole( "Quitting ProcessConnections() " + hb_CStr( nThreadId ) )
// Here I remove this thread from thread queue as it is unnecessary, but only if there is not
// an external quit request. In this case application is quitting and I cannot resize array
// here to avoid race condition
IF !lQuitRequest .AND. hb_mutexLock( s_hmtxBusy )
//hb_ToOutDebug( "Len( s_aRunningThreads ) = %i\n\r", Len( s_aRunningThreads ) )
IF ( nPos := aScan( s_aRunningThreads, hb_threadSelf() ) > 0 )
hb_aDel( s_aRunningThreads, nPos, TRUE )
s_nThreads := Len( s_aRunningThreads )
ENDIF
hb_mutexUnlock( s_hmtxBusy )
ENDIF
RETURN 0
STATIC FUNCTION ServiceConnection()
LOCAL hSocket, nLen, cRequest, cSend
LOCAL nMsecs, nParseTime, nPos, nThreadId
LOCAL nError := 500013
LOCAL lQuitRequest := FALSE
PRIVATE _SERVER, _GET, _POST, _COOKIE, _SESSION, _REQUEST, _HTTP_REQUEST, _HTTP_RESPONSE, m_cPost
ErrorBlock( { | oError | uhttpd_DefError( oError ) } )
nThreadId := hb_threadID()
WriteToConsole( "Starting ServiceConnections() " + hb_CStr( nThreadId ) )
IF hb_mutexLock( s_hmtxBusy )
s_nServiceThreads++
hb_mutexUnlock( s_hmtxBusy )
ENDIF
DO WHILE .T.
// Reset socket
hSocket := NIL
#ifdef __PLATFORM__WINDOWS
// releasing resources
IF WIN_SYSREFRESH( 1 ) != 0
lQuitRequest := TRUE
EXIT
ENDIF
#endif
IF hb_mutexLock( s_hmtxBusy )
IF s_lQuitRequest
hb_mutexUnlock( s_hmtxBusy )
lQuitRequest := TRUE
EXIT
ENDIF
hb_mutexUnlock( s_hmtxBusy )
ENDIF
// Waiting a connection from AcceptConnections() but up to defined time
hb_mutexSubscribe( s_hmtxServiceThreads, THREAD_MAX_WAIT, @hSocket )
// received a -1 value, I have to quit
IF HB_ISNUMERIC( hSocket )
lQuitRequest := TRUE
EXIT
ELSEIF hSocket == NIL // no socket received, thread can graceful quit, but ...
IF hb_mutexLock( s_hmtxBusy )
// .. not if under minimal number of starting threads
IF s_nServiceThreads <= s_nStartServiceThreads
hb_mutexUnlock( s_hmtxBusy )
LOOP
ENDIF
hb_mutexUnlock( s_hmtxBusy )
ENDIF
EXIT
ENDIF
// Connection accepted
IF hb_mutexLock( s_hmtxBusy )
s_nServiceConnections++
s_nTotServiceConnections++
s_nMaxServiceConnections := Max( s_nServiceConnections, s_nMaxServiceConnections )
hb_mutexUnlock( s_hmtxBusy )
ENDIF
// Save initial time
nMsecs := hb_milliseconds()
BEGIN SEQUENCE
/* receive query */
nLen := readRequest( hSocket, @cRequest )
IF nLen == -1
#ifdef USE_HB_INET
? "recv() error:", hb_InetErrorCode( hSocket ), hb_InetErrorDesc( hSocket )
#else
? "recv() error:", socket_error()
#endif
ELSEIF nLen == 0 /* connection closed */
ELSE
//hb_ToOutDebug( "cRequest -- INIZIO --\n\r%s\n\rcRequest -- FINE --\n\r", cRequest )
_SERVER := HB_IHASH(); _GET := HB_IHASH(); _POST := HB_IHASH(); _COOKIE := HB_IHASH()
_SESSION := HB_IHASH(); _REQUEST := HB_IHASH(); _HTTP_REQUEST := HB_IHASH(); _HTTP_RESPONSE := HB_IHASH()
m_cPost := NIL
t_cResult := ""
//t_aHeader := {}
t_nStatusCode := 200
t_cErrorMsg := ""
defineServer( hSocket )
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 _HTTP_RESPONSE = %s\n\r", hb_ValToExp( _SERVER ), hb_ValToExp( _GET ), hb_ValToExp( _POST ), hb_ValToExp( _REQUEST ), hb_ValToExp( _HTTP_REQUEST ), hb_ValToExp( _HTTP_RESPONSE ) )
define_Env( _SERVER )
ENDIF
// Error page served
uhttpd_SetStatusCode( nError )
cSend := MakeResponse()
sendReply( hSocket, cSend )
WriteToLog( cRequest )
// Destroy PRIVATE VARIABLES
_SERVER := _GET := _POST := _COOKIE := _SESSION := _REQUEST := _HTTP_REQUEST := _HTTP_RESPONSE := m_cPost := NIL
ENDIF
END SEQUENCE
nParseTime := hb_milliseconds() - nMsecs
WriteToConsole( "Page served in : " + Str( nParseTime/1000, 7, 4 ) + " seconds" )
#ifdef USE_HB_INET
hb_InetClose( hSocket )
hSocket := NIL
#else
socket_shutdown( hSocket )
socket_close( hSocket )
#endif
IF hb_mutexLock( s_hmtxBusy )
s_nServiceConnections--
hb_mutexUnlock( s_hmtxBusy )
ENDIF
// Memory release
hb_GCAll( TRUE )
ENDDO
WriteToConsole( "Quitting ServiceConnections() " + hb_CStr( nThreadId ) )
// Here I remove this thread from thread queue as it is unnecessary, but only if there is not
// an external quit request. In this case application is quitting and I cannot resize array
// here to avoid race condition
IF !lQuitRequest .AND. hb_mutexLock( s_hmtxBusy )
IF ( nPos := aScan( s_aServiceThreads, hb_threadSelf() ) > 0 )
hb_aDel( s_aServiceThreads, nPos, TRUE )
s_nServiceThreads := Len( s_aServiceThreads )
ENDIF
hb_mutexUnlock( s_hmtxBusy )
ENDIF
RETURN 0
STATIC FUNCTION ParseRequest( cRequest )
LOCAL aRequest, aLine, nI, nJ, cI
LOCAL cReq, aVal, cFields, hVars
LOCAL hUrl
// RFC2616
aRequest := uhttpd_split( CR_LF, cRequest )
#ifdef DEBUG_ACTIVE
hb_ToOutDebug( "aRequest = %s\n\r", hb_ValToExp( aRequest ) )
#endif
WriteToConsole( aRequest[1] )
aLine := uhttpd_split( " ", aRequest[1] )
IF LEN( aLine ) != 3 .OR. ;
( aLine[1] != "GET" .AND. aLine[1] != "POST" ) .OR. ; // Sorry, we support GET and POST only
LEFT( aLine[3], 5 ) != "HTTP/"
// Set status code
t_nStatusCode := 501 // Not Implemented
RETURN .F.
ENDIF
// define _SERVER var
_SERVER[ "REQUEST_METHOD" ] := aLine[1]
_SERVER[ "REQUEST_URI" ] := aLine[2]
_SERVER[ "SERVER_PROTOCOL" ] := aLine[3]
hUrl := uhttpd_SplitUrl( _SERVER[ "REQUEST_URI" ] )
_SERVER[ "SCRIPT_NAME" ] := hUrl[ "URI" ]
_SERVER[ "QUERY_STRING" ] := hUrl[ "QUERY" ]
/*
IF ( nI := AT( "?", _SERVER[ "REQUEST_URI" ] ) ) > 0
_SERVER[ "SCRIPT_NAME" ] := LEFT( _SERVER[ "REQUEST_URI" ], nI - 1)
_SERVER[ "QUERY_STRING" ] := SUBSTR( _SERVER[ "REQUEST_URI" ], nI + 1)
ELSE
_SERVER[ "SCRIPT_NAME" ] := _SERVER[ "REQUEST_URI" ]
_SERVER[ "QUERY_STRING" ] := ""
ENDIF
*/
FOR nI := 2 TO LEN( aRequest )
IF aRequest[nI] == ""; EXIT
ELSEIF ( nJ := AT( ":", aRequest[nI] ) ) > 0
cI := LTRIM( SUBSTR( aRequest[nI], nJ + 1))
SWITCH UPPER( LEFT( aRequest[nI], nJ - 1))
CASE "ACCEPT"
CASE "ACCEPT-CHARSET"
CASE "ACCEPT-ENCODING"
CASE "ACCEPT-LANGUAGE"
CASE "CACHE-CONTROL"
CASE "CONNECTION"
CASE "COOKIE"
CASE "KEEP-ALIVE"
CASE "REFERER"
CASE "USER-AGENT"
_SERVER[ "HTTP_" + STRTRAN( UPPER( LEFT( aRequest[nI], nJ - 1 ) ), "-", "_" ) ] := cI
EXIT
CASE "HOST"
//aVal := uhttpd_split( ":", aRequest[ nI ] )
//_SERVER[ "HTTP_" + STRTRAN( UPPER( aVal[ 1 ] ), "-", "_")] := AllTrim( aVal[ 2 ] )
_SERVER[ "HTTP_" + STRTRAN( UPPER( LEFT( aRequest[nI], nJ - 1 ) ), "-", "_" ) ] := cI
EXIT
CASE "CONTENT-TYPE"
CASE "CONTENT-LENGTH"
_SERVER[ STRTRAN( UPPER( LEFT( aRequest[ nI ], nJ - 1 ) ), "-", "_" ) ] := cI
EXIT
ENDSWITCH
ENDIF
NEXT
// Load _HTTP_REQUEST
FOR EACH cReq IN aRequest
IF cReq:__enumIndex() == 1 // GET request
hb_HSet( _HTTP_REQUEST, "HTTP Request", cReq )
ELSEIF Empty( cReq )
EXIT
ELSE
aVal := uhttpd_split( ":", cReq, 1 )
hb_HSet( _HTTP_REQUEST, aVal[ 1 ], IIF( Len( aVal ) == 2, AllTrim( aVal[ 2 ] ), NIL ) )
ENDIF
NEXT
// check if Host field is provided
IF hb_HPos( _HTTP_REQUEST, "Host" ) == 0
// Try to determine Host name
IF !Empty( hUrl[ "HOST" ] )
_HTTP_REQUEST[ "Host" ] := hUrl[ "HOST" ]
ELSE
_HTTP_REQUEST[ "Host" ] := ""
// Set status code
t_nStatusCode := 400 // Bad Request
RETURN .F.
ENDIF
ENDIF
//hb_toOutDebug( "_HTTP_REQUEST: aRequest = %s, _HTTP_REQUEST = %s\n\r", hb_ValToExp( aRequest ), hb_ValToExp( _HTTP_REQUEST ) )
// GET
cFields := _SERVER[ "QUERY_STRING" ]
IF !Empty( cFields )
hVars := uhttpd_GetVars( cFields )
hb_HMerge( _GET, hVars )
hb_HMerge( _REQUEST, hVars )
ENDIF
//hb_toOutDebug( "GET: cFields = %s, hVars = %s, _GET = %s, _REQUEST = %s\n\r", cFields, hb_ValToExp( hVars ), hb_ValToExp( _GET ), hb_ValToExp( _REQUEST ) )
// POST
IF "POST" $ Upper( _SERVER[ 'REQUEST_METHOD' ] )
cFields := aTail( aRequest )
IF !Empty( cFields )
hVars := uhttpd_GetVars( cFields )
hb_HMerge( _POST, hVars )
hb_HMerge( _REQUEST, hVars )
ENDIF
m_cPost := cFields // TOFIX: Who needs this ?
ENDIF
//hb_toOutDebug( "POST: cFields = %s, hVars = %s, _POST = %s, _REQUEST = %s\n\r", cFields, hb_ValToExp( hVars ), hb_ValToExp( _POST ), hb_ValToExp( _REQUEST ) )
// COOKIES
cFields := _SERVER[ 'HTTP_COOKIE' ]
IF !Empty( cFields )
hVars := uhttpd_GetVars( cFields, ";" )
hb_HMerge( _COOKIE, hVars )
hb_HMerge( _REQUEST, hVars )
ENDIF
//hb_toOutDebug( "COOKIE: cFields = %s, hVars = %s, _COOKIE = %s, _REQUEST = %s\n\r", cFields, hb_ValToExp( hVars ), hb_ValToExp( _COOKIE ), hb_ValToExp( _REQUEST ) )
// define _HTTP_RESPONSE
_HTTP_RESPONSE[ "X-Powered-By" ] := Version()
_HTTP_RESPONSE[ "Connection" ] := "Close"
_HTTP_RESPONSE[ "Content-Type" ] := "text/html; charset=UTF-8"
_HTTP_RESPONSE[ "Server" ] := APP_NAME + " " + APP_VERSION
//_HTTP_RESPONSE[ "Transfer-Encoding" ] := "chunked"
// Complete _SERVER
_SERVER[ "SERVER_NAME" ] := uhttpd_split( ":", _HTTP_REQUEST[ "HOST" ], 1 )[ 1 ]
_SERVER[ "SCRIPT_FILENAME" ] := STRTRAN( STRTRAN( _SERVER[ "DOCUMENT_ROOT" ] + _SERVER[ "SCRIPT_NAME" ], "//", "/" ), "\", "/" )
_SERVER[ "SCRIPT_URL" ] := _SERVER[ "SCRIPT_NAME" ]
_SERVER[ "SCRIPT_URI" ] := "http://" + _HTTP_REQUEST[ "HOST" ] + _SERVER[ "SCRIPT_NAME" ]
#ifdef DEBUG_ACTIVE
hb_ToOutDebug( "_SERVER = %s\n\r", hb_ValToExp( _SERVER ) )
hb_ToOutDebug( "_GET = %s\n\r", hb_ValToExp( _GET ) )
hb_ToOutDebug( "_POST = %s\n\r", hb_ValToExp( _POST ) )
hb_ToOutDebug( "_COOKIE = %s\n\r", hb_ValToExp( _COOKIE ) )
hb_ToOutDebug( "_SESSION = %s\n\r", hb_ValToExp( _SESSION ) )
hb_ToOutDebug( "_HTTP_REQUEST = %s\n\r", hb_ValToExp( _HTTP_REQUEST ) )
hb_ToOutDebug( "_HTTP_RESPONSE = %s\n\r", hb_ValToExp( _HTTP_RESPONSE ) )
#endif
// After defined all SERVER vars we can define a session
// SESSION - sessions ID is stored as a cookie value, normally as SESSIONID var name (this can be user defined)
t_oSession := uhttpd_SessionNew( "UHTTPD-SESSION", s_cSessionPath )
t_oSession:Start()
RETURN .T.
STATIC FUNCTION MakeResponse()
LOCAL cRet, cReturnCode, v
//uhttpd_SetHeader( "X-Powered-By", Version() )
//uhttpd_SetHeader( "Connection", "close" )
IF uhttpd_GetHeader( "Location" ) != NIL
t_nStatusCode := 301
ENDIF
IF uhttpd_GetHeader( "Content-Type" ) == NIL
uhttpd_SetHeader( "Content-Type", "text/html" )
ENDIF
cRet := "HTTP/1.1 "
cReturnCode := DecodeStatusCode()
SWITCH t_nStatusCode
CASE 200
EXIT
CASE 301
CASE 400
CASE 401
CASE 402
CASE 403
CASE 404
CASE 405
CASE 500
CASE 501
CASE 502
CASE 503
CASE 504
CASE 505
t_cResult := "<html><body><h1>" + cReturnCode + "</h1></body></html>"
EXIT
// extended error messages - from Microsoft IIS Server
CASE 500013 // error: 500-13 Server too busy
uhttpd_SetHeader( "Retry-After", "60" ) // retry after 60 seconds
t_cResult := "<html><body><h1>500 Server Too Busy</h1></body></html>"
EXIT
CASE 500100 // error: 500-100 Undeclared Variable
OTHERWISE
cReturnCode := "403 Forbidden"
t_cResult := "<html><body><h1>" + cReturnCode + "</h1></body></html>"
ENDSWITCH
//hb_ToOutDebug( "_SESSION = %s\n\r", hb_ValToExp( _SESSION ) )
// Close session - Autodestructor will NOT close it, because t_oSession is destroyed only at end of Thread
IF HB_ISOBJECT( t_oSession )
t_oSession:Close()
ENDIF
// t_oSession := NIL
WriteToConsole( cReturnCode )
cRet += cReturnCode + CR_LF
FOR EACH v IN _HTTP_RESPONSE
cRet += v:__enumKey() + ": " + v:__enumValue() + CR_LF
NEXT
//AEVAL( t_aHeader, {|x| cRet += x[1] + ": " + x[2] + CR_LF } )
cRet += CR_LF
cRet += t_cResult
//hb_ToOutDebug( "_HTTP_RESPONSE = %s\n\rcRet = %s\n\r", hb_ValToExp( _HTTP_RESPONSE ), cRet )
RETURN cRet
STATIC FUNCTION DecodeStatusCode()
LOCAL cReturnCode
SWITCH t_nStatusCode
CASE 200
cReturnCode := "200 OK"
EXIT
CASE 301
cReturnCode := "301 Moved Permanently"
EXIT
CASE 400
cReturnCode := "400 Bad Request"
EXIT
CASE 401
cReturnCode := "401 Unauthorized"
EXIT
CASE 402
cReturnCode := "402 Payment Required"
EXIT
CASE 403
cReturnCode := "403 Forbidden"
EXIT
CASE 404
cReturnCode := "404 Not Found"
EXIT
CASE 405
cReturnCode := "405 Method Not Allowed"
EXIT
CASE 500
cReturnCode := "500 Internal Server Error"
EXIT
CASE 501
cReturnCode := "501 Not Implemented"
EXIT
CASE 502
cReturnCode := "502 Bad Gateway"
EXIT
CASE 503
cReturnCode := "503 Service Unavailable"
EXIT
CASE 504
cReturnCode := "504 Gateway Timeout"
EXIT
CASE 505
cReturnCode := "505 HTTP Version Not Supported"
EXIT
// extended error messages - from Microsoft IIS Server
CASE 500013 // error: 500-13 Server too busy
cReturnCode := "500-13 Server Too Busy"
EXIT
CASE 500100 // error: 500-100 Undeclared Variable
OTHERWISE
cReturnCode := "403 Forbidden"
ENDSWITCH
RETURN cReturnCode
STATIC PROCEDURE WriteToLog( cRequest )
LOCAL cTime, cDate
LOCAL aDays := { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }
LOCAL aMonths := {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
LOCAL cAccess, cError, nDoW, dDate, nDay, nMonth, nYear, nSize, cBias
LOCAL cErrorMsg
LOCAL cReferer
IF hb_mutexLock( s_hmtxLog )
//hb_ToOutDebug( "TIP_TimeStamp() = %s \n\r", TIP_TIMESTAMP() )
cTime := TIME()
dDate := Date()
cDate := DTOS( dDate )
nSize := LEN( t_cResult )
cReferer := _SERVER[ "HTTP_REFERER" ]
cBias := uhttpd_UTCOffset()
cAccess := _SERVER[ "REMOTE_ADDR" ] + " - - [" + RIGHT( cDate, 2 ) + "/" + ;
aMonths[ VAL( SUBSTR( cDate, 5, 2 ) ) ] + ;
"/" + LEFT( cDate, 4 ) + ":" + cTime + ' ' + cBias + '] "' + ;
LEFT( cRequest, AT( CR_LF, cRequest ) - 1 ) + '" ' + ;
LTRIM( STR( t_nStatusCode ) ) + " " + IIF( nSize == 0, "-", LTRIM( STR( nSize ) ) ) + ;
' "' + IIF( Empty( cReferer ), "-", cReferer ) + '" "' + _SERVER[ "HTTP_USER_AGENT" ] + ;
'"' + HB_OSNewLine()
//hb_ToOutDebug( "AccessLog = %s \n\r", cAccess )
FWRITE( s_hfileLogAccess, cAccess )
IF !( t_nStatusCode == 200 ) // ok
nDoW := Dow( dDate )
nDay := Day( dDate )
nMonth := Month( dDate )
nYear := Year( dDate )
cErrorMsg := t_cErrorMsg
cError := "[" + Left( aDays[ nDoW ], 3 ) + " " + aMonths[ nMonth ] + " " + StrZero( nDay, 2 ) + " " + ;
PadL( LTrim( cTime ), 8, "0" ) + " " + StrZero( nYear, 4 ) + "] [error] [client " + _SERVER[ "REMOTE_ADDR" ] + "] " + ;
cErrorMsg + HB_OSNewLine()
//hb_ToOutDebug( "ErrorLog = %s \n\r", cError )
FWRITE( s_hfileLogError, cError )
ENDIF
hb_mutexUnlock( s_hmtxLog )
ENDIF
RETURN
STATIC FUNCTION CGIExec( cProc, /*@*/ cOutPut )
LOCAL hIn, hOut
LOCAL cData, nLen, cSend, v
LOCAL nErrorLevel := 0, nKillExit := 0
LOCAL pThread
LOCAL hProc
LOCAL hmtxCGIKill := hb_mutexCreate()
LOCAL cCurPath
//LOCAL cError
IF HB_ISSTRING( cProc )
//hb_toOutDebug( "Launching process: %s\n\r", cProc )
// No hIn, hErr == hOut
// save current directory
cCurPath := CurDrive() + hb_osDriveSeparator() + HB_OSPathSeparator() + CurDir()
//hb_toOutDebug( "cCurPath: %s\n\r", cCurPath )
// Change dir to document root
DirChange( s_cDocumentRoot )
//hb_toOutDebug( "New Path: %s\n\r", CurDrive() + hb_osDriveSeparator() + HB_OSPathSeparator() + CurDir() )
hProc := hb_processOpen( cProc, @hIn, @hOut, @hOut, .T. ) // .T. = Detached Process (Hide Window)
// return to original folder
DirChange( cCurPath )
//hb_toOutDebug( "New 2 Path: %s\n\r", CurDrive() + hb_osDriveSeparator() + HB_OSPathSeparator() + CurDir() )
IF hProc > -1
//hb_toOutDebug( "Process handler: %s\n\r", hProc )
//hb_toOutDebug( "Error: %s\n\r", FError() )
pThread := hb_threadStart( @CGIKill(), hProc, hmtxCGIKill )
// Sending POST variables to CGI via STD_IN
cSend := ""
FOR EACH v IN _POST
cSend += v:__enumKey() + "=" + LTrim( hb_cStr( v:__enumValue() ) ) + IIF( v:__enumIndex() < Len( _POST ), "&", "" )
NEXT
FWrite( hIn, cSend )
//hb_toOutDebug( "Sending: %s\n\r", cSend )
hb_mutexNotify( hmtxCGIKill, { hProc, .T. } )
//hb_toOutDebug( "Reading output\n\r" )
cData := Space( 1000 )
cOutPut := ""
DO WHILE ( nLen := Fread( hOut, @cData, Len( cData ) ) ) > 0
cOutPut += SubStr( cData, 1, nLen )
cData := Space( 1000 )
ENDDO
/*
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( hProc )
//hb_toOutDebug( "CGIExec HB_ProcessValue nErrorLevel = %s\n\r", nErrorLevel )
// Notify to CGIKill to terminate
hb_mutexNotify( hmtxCGIKill, { hProc, .F. } )
hb_threadJoin( pThread, @nKillExit )
//hb_toOutDebug( "CGIExec quitting CGI, nErrorLevel = %s\n\r", nKillExit )
IF nKillExit != 0
// retrieving last command from
nErrorLevel := nKillExit
ENDIF
FClose( hProc )
FClose( hIn )
FClose( hOut )
//FClose( hErr )
//hb_toOutDebug( "CGIExec closed handles\n\r" )
ENDIF
ELSE
nErrorLevel := -1 // Error: cProc is not a valid string
ENDIF
hmtxCGIKill := NIL
RETURN nErrorLevel
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.
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
nStartTime := hb_milliseconds()
ELSE
EXIT
ENDIF
ENDIF
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 hCurProc != NIL
HB_ProcessClose( hCurProc )
nErrorLevel := 1
ENDIF
EXIT
ENDIF
ENDDO
RETURN nErrorLevel
INIT PROCEDURE SocketInit()
#ifdef USE_HB_INET
hb_InetInit()
#else
IF socket_init() != 0
? "socket_init() error"
ENDIF
#endif
RETURN
EXIT PROCEDURE SocketExit()
#ifdef USE_HB_INET
hb_InetCleanup()
#else
socket_exit()
#endif
RETURN
/********************************************************************
Public helper functions
********************************************************************/
FUNCTION uhttpd_OSFileName( cFileName )
IF HB_OSPathSeparator() != "/"
RETURN STRTRAN( cFileName, "/", HB_OSPathSeparator() )
ENDIF
RETURN cFileName
PROCEDURE uhttpd_SetStatusCode(nStatusCode)
t_nStatusCode := nStatusCode
RETURN
PROCEDURE uhttpd_SetHeader( cType, cValue )
//LOCAL nI
//DEFAULT lReplace TO TRUE // Needed from SetCookie()
hb_HSet( _HTTP_RESPONSE, cType, cValue )
/*
IF lReplace .AND. ( nI := ASCAN( t_aHeader, {|x| UPPER( x[ 1 ] ) == UPPER( cType ) } ) ) > 0
t_aHeader[ nI, 2 ] := cValue
ELSE
AADD( t_aHeader, { cType, cValue } )
ENDIF
*/
RETURN
FUNCTION uhttpd_GetHeader( cType )
RETURN uhttpd_HGetValue( _HTTP_RESPONSE, cType )
/*
DEFAULT nPos TO 1
nPos := hb_HPos( hHash, cKey ))
IF ( nPos := ASCAN( t_aHeader, {|x| UPPER( x[ 1 ] ) == UPPER( cType ) }, nPos ) ) > 0
RETURN t_aHeader[ nPos, 2 ]
ENDIF
RETURN NIL
*/
PROCEDURE uhttpd_DelHeader( cType )
LOCAL nPos := hb_HPos( _HTTP_RESPONSE, cType )
IF nPos > 0
hb_HDelAt( _HTTP_RESPONSE, nPos )
ENDIF
RETURN
/*
LOCAL nI
IF ( nI := ASCAN( t_aHeader, {|x| UPPER( x[ 1 ] ) == UPPER( cType ) } ) ) > 0
hb_aDel( t_aHeader, nI, TRUE )
ENDIF
RETURN
*/
PROCEDURE uhttpd_Write( cString )
t_cResult += cString
RETURN
/********************************************************************
Internal helper functions
********************************************************************/
STATIC FUNCTION readRequest( hSocket, /* @ */ cRequest )
LOCAL cBuf, nLen, nPos
/* receive query */
#ifdef USE_HB_INET
cRequest := hb_InetRecvEndBlock( hSocket, CR_LF + CR_LF, @nLen )
IF nLen > 0
cRequest += CR_LF + CR_LF
ENDIF
#else
cRequest := ""
DO WHILE .T.
nLen := socket_recv( hSocket, @cBuf )
IF nLen <= 0
EXIT
ENDIF
cRequest += cBuf
IF CR_LF + CR_LF $ cRequest
EXIT
ENDIF
ENDDO
#endif
/* receive CONTENT-LENGTH data */
IF nLen > 0
nPos := HB_ATI( CR_LF + "CONTENT-LENGTH:", cRequest )
IF nPos > 0
nPos := Val( Substr( cRequest, nPos + 17, 10 ) )
IF nPos > 0
#ifdef USE_HB_INET
cBuf := Space( nPos )
nLen := hb_InetRecvAll( hSocket, @cBuf, nPos )
IF nLen < 0
nLen := -1
ELSE
cRequest += Left( cBuf, nLen )
ENDIF
#else
/* we have to decrease number of bytes to read by already read
* data after CR_LF + CR_LF
*/
nPos -= Len( cRequest ) - At( CR_LF + CR_LF, cRequest ) - 3
WHILE nPos > 0
nLen := socket_recv( hSocket, @cBuf, nPos )
IF nLen <= 0
EXIT
ENDIF
cRequest += cBuf
nPos -= nLen
ENDDO
#endif
ENDIF
ENDIF
ENDIF
#ifdef DEBUG_ACTIVE
hb_ToOutDebug( "readRequest(): nLen = %i, cRequest = %s \n\r", nLen, cRequest )
#endif
RETURN nLen
STATIC FUNCTION sendReply( hSocket, cSend )
LOCAL nError := 0
LOCAL nLen
#ifdef USE_HB_INET
DO WHILE LEN( cSend ) > 0
IF ( nLen := hb_InetSendAll( hSocket, cSend ) ) == -1
? "send() error:", hb_InetErrorCode( hSocket ), HB_InetErrorDesc( hSocket )
WriteToConsole( hb_StrFormat( "ProcessConnection() - send() error: %s, cSend = %s, hSocket = %s", hb_InetErrorDesc( hSocket ), cSend, hSocket ) )
EXIT
ELSEIF nLen > 0
cSend := SUBSTR( cSend, nLen + 1 )
ENDIF
ENDDO
#else
DO WHILE LEN( cSend ) > 0
IF ( nLen := socket_send( hSocket, cSend ) ) == -1
? "send() error:", socket_error()
WriteToConsole( hb_StrFormat( "ServiceConnection() - send() error: %s, cSend = %s, hSocket = %s", socket_error(), cSend, hSocket ) )
EXIT
ELSEIF nLen > 0
cSend := SUBSTR( cSend, nLen + 1 )
ENDIF
ENDDO
#endif
RETURN nError
STATIC PROCEDURE defineServer( hSocket )
#ifndef USE_HB_INET
LOCAL aI
#endif
// define _SERVER vars (address part)
#ifdef USE_HB_INET
_SERVER[ "REMOTE_ADDR" ] := hb_InetAddress( hSocket )
_SERVER[ "REMOTE_HOST" ] := _SERVER[ "REMOTE_ADDR" ] // no reverse DNS
_SERVER[ "REMOTE_PORT" ] := hb_InetPort( hSocket )
_SERVER[ "SERVER_ADDR" ] := s_cLocalAddress
_SERVER[ "SERVER_PORT" ] := LTrim( Str( s_nLocalPort ) )
#else
IF socket_getpeername( hSocket, @aI ) != -1
_SERVER[ "REMOTE_ADDR" ] := aI[2]
_SERVER[ "REMOTE_HOST" ] := _SERVER[ "REMOTE_ADDR" ] // no reverse DNS
_SERVER[ "REMOTE_PORT" ] := aI[3]
ENDIF
IF socket_getsockname( hSocket, @aI ) != -1
_SERVER[ "SERVER_ADDR" ] := aI[2]
_SERVER[ "SERVER_PORT" ] := LTrim( Str( aI[3] ) )
ENDIF
#endif
// add other _SERVER vars
_SERVER[ "REQUEST_METHOD" ] := NIL
_SERVER[ "REQUEST_URI" ] := NIL
_SERVER[ "SERVER_PROTOCOL" ] := NIL
_SERVER[ "SCRIPT_NAME" ] := NIL
_SERVER[ "QUERY_STRING" ] := NIL
_SERVER[ "HTTP_ACCEPT" ] := NIL
_SERVER[ "HTTP_ACCEPT_CHARSET" ] := NIL
_SERVER[ "HTTP_ACCEPT_ENCODING" ] := NIL
_SERVER[ "HTTP_ACCEPT_LANGUAGE" ] := NIL
_SERVER[ "HTTP_CONNECTION" ] := NIL
_SERVER[ "HTTP_HOST" ] := NIL
_SERVER[ "HTTP_KEEP_ALIVE" ] := NIL
_SERVER[ "HTTP_REFERER" ] := ""
_SERVER[ "HTTP_USER_AGENT" ] := ""
_SERVER[ "HTTP_CACHE_CONTROL" ] := NIL
_SERVER[ "HTTP_COOKIE" ] := NIL
_SERVER[ "SERVER_NAME" ] := ""
_SERVER[ "SERVER_SOFTWARE" ] := APP_NAME + " " + APP_VERSION + " (" + OS() + ")"
_SERVER[ "SERVER_SIGNATURE" ] := "<address>" + _SERVER[ "SERVER_SOFTWARE" ] + " Server at " + _SERVER[ "SERVER_NAME" ] + " Port " + _SERVER[ "SERVER_PORT" ] + "</address>"
_SERVER[ "DOCUMENT_ROOT" ] := s_cDocumentRoot
_SERVER[ "SERVER_ADMIN" ] := "root@localhost" // TOFIX: put real user
_SERVER[ "SCRIPT_FILENAME" ] := NIL
_SERVER[ "GATEWAY_INTERFACE" ] := "CGI/1.1"
_SERVER[ "SCRIPT_URL" ] := NIL
_SERVER[ "SCRIPT_URI" ] := NIL
_SERVER[ "PATH_INFO" ] := NIL
_SERVER[ "PATH_TRANSLATED" ] := NIL
RETURN
FUNCTION uhttpd_split( cSeparator, cString, nMax )
LOCAL aRet := {}, nI
LOCAL nIter := 0
DEFAULT nMax TO 0
DO WHILE ( nI := AT( cSeparator, cString ) ) > 0
AADD( aRet, LEFT( cString, nI - 1 ) )
cString := SUBSTR( cString, nI + LEN( cSeparator ) )
IF nMax > 0 .AND. ++nIter >= nMax
EXIT
ENDIF
ENDDO
AADD( aRet, cString )
RETURN aRet
FUNCTION uhttpd_join( cSeparator, aData )
LOCAL cRet := "", nI
FOR nI := 1 TO LEN( aData )
IF nI > 1; cRet += cSeparator
ENDIF
IF VALTYPE(aData[nI]) $ "CM"; cRet += aData[nI]
ELSEIF VALTYPE(aData[nI]) == "N"; cRet += LTRIM(STR(aData[nI]))
ELSEIF VALTYPE(aData[nI]) == "D"; cRet += IF(!EMPTY(aData[nI]), DTOC(aData[nI]), "")
ELSE
ENDIF
NEXT
RETURN cRet
STATIC FUNCTION uproc_default()
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 := NIL
cPathInfo := ""
DO WHILE TRUE
#ifdef DEBUG_ACTIVE
//hb_ToOutDebug( "cFileName = %s, cScript = %s\n\r", cFileName, cScript )
#endif
IF cFileName == NIL
// Special script names
IF Upper( cScript ) == "/SERVERSTATUS"
cFileName := "/serverstatus"
cExt := "/serverstatus" // special extension
ENDIF
ENDIF
IF cFileName == NIL
cFileName := FileUnAlias( cScript )
ENDIF
// if filename is still NIL I set it
IF cFileName == NIL
cFileName := _SERVER[ "SCRIPT_FILENAME" ]
ENDIF
#ifdef DEBUG_ACTIVE
//hb_ToOutDebug( "cFileName = %s, uhttpd_OSFileName( cFileName ) = %s,\n\r", cFileName, uhttpd_OSFileName( cFileName ) )
#endif
// Security
IF ".." $ cFileName
uhttpd_SetStatusCode( 403 )
t_cErrorMsg := "Characters not allowed"
RETURN MakeResponse()
ENDIF
//hb_toOutDebug( "cFileName = %s, uhttpd_OSFileName( cFileName ) = %s,\n\r s_hScriptAliases = %s\n\r", cFileName, uhttpd_OSFileName( cFileName ), hb_ValToExp( s_hScriptAliases ) )
// 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_SetHeader( "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
// 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
ENDIF
ENDIF
// Ok, now I have to see what action I have to take
//hb_toOutDebug( "cExt = %s\n\r", cExt )
// Begin to search Handlers
IF cExt != NIL
cHandler := uhttpd_HGetValue( s_hHandlers, cExt )
ENDIF
//hb_toOutDebug( "cHandler = %s\n\r", cHandler )
IF cHandler != NIL
xAction := uhttpd_HGetValue( s_hActions, cHandler )
ENDIF
//hb_toOutDebug( "xAction = %s\n\r", xAction )
IF xAction == NIL
xAction := @Handler_Default()
ENDIF
//hb_toOutDebug( "xAction = %s\n\r", xAction )
// Setting CGI vars
define_Env( _SERVER )
// Eval Action
RETURN hb_ExecFromArray( xAction, { cFileName } )
ENDDO
RETURN MakeResponse()
// Define environment SET variables - TODO: Actually only for windows, make multiplatform
STATIC PROCEDURE Define_Env( hmServer )
LOCAL v
FOR EACH v IN hmServer
hb_SetEnv( v:__enumKey(), v:__enumValue() )
NEXT
RETURN
// ------------------------------- DEFAULT PAGES -----------------------------------
STATIC PROCEDURE ShowServerStatus()
LOCAL cThreads
uhttpd_SetHeader( "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
STATIC PROCEDURE ShowFolder( cDir )
LOCAL aDir, aF
LOCAL cParentDir, nPos
uhttpd_SetHeader( "Content-Type", "text/html" )
aDir := DIRECTORY( uhttpd_OSFileName( cDir ), "D" )
IF HB_HHasKey( _GET, "s" )
IF _GET[ "s" ] == "s"
ASORT( aDir,,, {|X,Y| IIF( X[ 5 ] == "D", IIF( Y[ 5 ] == "D", X[ 1 ] < Y[ 1 ], .T. ), ;
IIF( Y[ 5 ] == "D", .F., X[ 2 ] < Y[ 2 ] ) ) } )
ELSEIF _GET[ "s" ] == "m"
ASORT( aDir,,, {|X,Y| IIF( X[ 5 ] == "D", IIF( Y[ 5 ] == "D", X[ 1 ] < Y[ 1 ], .T.), ;
IIF( Y[ 5 ] == "D", .F., DTOS( X[ 3 ] ) + X[ 4 ] < DTOS( Y[ 3 ] ) + Y[ 4 ] ) ) } )
ELSE
ASORT( aDir,,, {|X,Y| IIF( X[ 5 ] == "D", IIF( Y[ 5 ] == "D", X[ 1 ] < Y[ 1 ], .T. ), ;
IIF( Y[ 5 ] == "D", .F., X[ 1 ] < Y[ 1 ] ) ) } )
ENDIF
ELSE
ASORT( aDir,,, {|X,Y| IIF( X[ 5 ] == "D", IIF( Y[ 5 ] == "D", X[ 1 ] < Y[ 1 ], .T. ), ;
IIF( Y[ 5 ] == "D", .F., X[ 1 ] < Y[ 1 ] ) ) } )
ENDIF
uhttpd_Write( '<html><body><h1>Index of ' + _SERVER[ "SCRIPT_NAME" ] + '</h1><pre> ')
uhttpd_Write( '<a href="?s=n">Name</a> ')
uhttpd_Write( '<a href="?s=m">Modified</a> ' )
uhttpd_Write( '<a href="?s=s">Size</a>' + CR_LF + '<hr>' )
// Adding Upper Directory
nPos := RAT( "/", SUBSTR( cDir, 1, Len( cDir ) - 1 ) )
cParentDir := SUBSTR( cDir, 1, nPos )
cParentDir := SUBSTR( cParentDir, Len( _SERVER[ "DOCUMENT_ROOT" ] ) + 1 )
//hb_ToOutDebug( "cDir = %s, nPos = %i, cParentDir = %s\n\r", cDir, nPos, cParentDir )
IF !Empty( cParentDir )
// Add parent directory
hb_aIns( aDir, 1, { "<parent>", 0, "", "", "D" }, .T. )
ENDIF
FOR EACH aF IN aDir
IF aF[ 1 ] == "<parent>"
uhttpd_Write( '[DIR] <a href="' + cParentDir + '">..</a>' + ;
CR_LF )
ELSEIF LEFT( aF[ 1 ], 1 ) == "."
ELSEIF "D" $ aF[ 5 ]
uhttpd_Write( '[DIR] <a href="' + aF[ 1 ] + '/">'+ aF[ 1 ] + '</a>' + SPACE( 50 - LEN( aF[ 1 ] ) ) + ;
DTOC( aF[ 3 ] ) + ' ' + aF[ 4 ] + CR_LF )
ELSE
uhttpd_Write( ' <a href="' + aF[ 1 ] + '">'+ aF[ 1 ] + '</a>' + SPACE( 50 - LEN( aF[ 1 ] ) ) + ;
DTOC( aF[ 3 ]) + ' ' + aF[ 4 ] + STR( aF[ 2 ], 12 ) + CR_LF )
ENDIF
NEXT
uhttpd_Write( "<hr></pre></body></html>" )
RETURN
// ------------------------------- Utility functions --------------------------------
// from Przemek's example, useful to use encrypted HRB module files
STATIC FUNCTION HRB_LoadFromFileEncrypted( cFile, cKey )
LOCAL cHrbBody
cHrbBody := hb_memoread( cFile )
cHrbBody := sx_decrypt( cHrbBody, cKey )
cHrbBody := hb_zuncompress( cHrbBody )
RETURN cHrbBody
/*
// Reverse function to save is:
PROCEDURE HRB_SaveToFileEncrypted( cHrbBody, cKey, cEncFileName )
LOCAL cFile
IF !Empty( cHrbBody )
cHrbBody := hb_zcompress( cHrbBody )
cHrbBody := sx_encrypt( cHrbBody, cKey )
hb_memowrit( cEncFileName, cHrbBody )
ENDIF
RETURN
*/
STATIC FUNCTION HRB_LoadFromFile( cFile )
RETURN hb_memoread( cFile )
STATIC PROCEDURE Help()
//LOCAL cPrg := hb_argv( 0 )
//LOCAL nPos := RAt( "\", cPrg )
//__OutDebug( hb_argv(0) )
//IF nPos > 0
// cPrg := SubStr( cPrg, nPos + 1 )
//ENDIF
?
? "(C) 2009 Francesco Saverio Giudice <info@fsgiudice.com>"
?
? APP_NAME + " - web server - v. " + APP_VERSION
? "Based on original work of Mindaugas Kavaliauskas <dbtopas@dbtopas.lt>"
?
? "Parameters: (all optionals)"
?
? "-p | --port webserver tcp port (default: " + LTrim( Str( LISTEN_PORT ) ) + ")"
? "-c | --config Configuration file (default: " + APP_NAME + ".ini)"
? " It is possibile to define file path"
? "-a | --approot Application root directory (default: <curdir>)"
? "-d | --docroot Document root directory (default: <curdir>\home)"
? "-i | --indexes Allow directory view (default: no)"
? "-s | --stop Stop webserver"
? "-ts | --start-threads Define starting threads (default: " + LTrim( Str( START_RUNNING_THREADS ) ) + ")"
? "-tm | --max-threads Define max threads (default: " + LTrim( Str( MAX_RUNNING_THREADS ) ) + ")"
? "-cr | --console-rows Console rows (default: " + LTrim( Str( MaxRow() + 1 ) ) + ")"
? "-cc | --console-cols Console cols (default: " + LTrim( Str( MaxCol() + 1 ) ) + ")"
? "-h | -? | --help This help message"
?
WAIT
RETURN
STATIC PROCEDURE SysSettings()
SET SCOREBOARD OFF
SET CENTURY ON
SET DATE ITALIAN
SET BELL OFF
SET DELETED ON
SET EXACT OFF
SET CONFIRM ON
SET ESCAPE ON
SET WRAP ON
SET EPOCH TO 2000
//RDDSetDefault( "DBFCDX" )
RETURN
STATIC FUNCTION Exe_Path()
LOCAL cPath := hb_argv( 0 )
LOCAL nPos := RAt( HB_OSPathSeparator(), cPath )
IF nPos == 0
cPath := ""
ELSE
cPath := SubStr( cPath, 1, nPos-1 )
ENDIF
RETURN cPath
STATIC FUNCTION Exe_Name()
LOCAL cPrg := hb_argv( 0 )
LOCAL nPos := RAt( HB_OSPathSeparator(), cPrg )
IF nPos > 0
cPrg := SubStr( cPrg, nPos+1 )
ENDIF
RETURN cPrg
STATIC PROCEDURE Progress( /*@*/ nProgress )
LOCAL cString := "["
DO CASE
CASE nProgress == 0
cString += "-"
CASE nProgress == 1
cString += "\"
CASE nProgress == 2
cString += "|"
CASE nProgress == 3
cString += "/"
ENDCASE
cString += "]"
nProgress++
IF nProgress == 4
nProgress := 0
ENDIF
// using hb_dispOutAt() to avoid MT screen updates problem
hb_dispOutAt( 10, 5, cString )
hb_dispOutAt( 0, 60, "Time: " + Time() )
RETURN
// Show messages in console
#define CONSOLE_FIRSTROW 12
#define CONSOLE_LASTROW MaxRow()
STATIC PROCEDURE WriteToConsole( ... )
LOCAL cMsg
IF hb_mutexLock( s_hmtxConsole )
IF s_lConsole
FOR EACH cMsg IN hb_aParams()
hb_Scroll( CONSOLE_FIRSTROW, 0, CONSOLE_LASTROW, MaxCol(), -1 )
hb_DispOutAt( CONSOLE_FIRSTROW, 0, PadR( "> " + hb_cStr( cMsg ), MaxCol() ) )
#ifdef DEBUG_ACTIVE
hb_ToOutDebug( ">>> %s\n\r", cMsg )
#endif
NEXT
ENDIF
hb_mutexUnlock( s_hmtxConsole )
ENDIF
RETURN
STATIC FUNCTION ParseIni( cConfig )
LOCAL hIni := hb_IniRead( cConfig, TRUE ) // TRUE = load all keys in MixedCase, redundant as it is default, but to remember
LOCAL cSection, hSect, cKey, xVal, cVal, nPos
LOCAL hDefault
//hb_ToOutDebug( "cConfig = %s,\n\rhIni = %s\n\r", cConfig, hb_ValToExp( hIni ) )
// Define here what attributes we can have in ini config file and their defaults
// Please add all keys in uppercase. hDefaults is Case Insensitive
hDefault := hb_HSetCaseMatch( ;
{ ;
"MAIN" => { ;
"PORT" => LISTEN_PORT ,;
"APPLICATION_ROOT" => EXE_Path() ,;
"DOCUMENT_ROOT" => EXE_Path() + HB_OSPathSeparator() + "home" ,;
"SHOW_INDEXES" => FALSE ,;
"SCRIPTALIASMIXEDCASE" => TRUE ,;
"SESSIONPATH" => EXE_Path() + HB_OSPathSeparator() + "sessions" ,;
"DIRECTORYINDEX" => DIRECTORYINDEX_ARRAY ,;
"CONSOLE-ROWS" => MaxRow() + 1 ,;
"CONSOLE-COLS" => MaxCol() + 1 ;
},;
"LOGFILES" => { ;
"ACCESS" => FILE_ACCESS_LOG ,;
"ERROR" => FILE_ERROR_LOG ;
},;
"THREADS" => { ;
"MAX_WAIT" => THREAD_MAX_WAIT ,;
"START_NUM" => START_RUNNING_THREADS ,;
"MAX_NUM" => MAX_RUNNING_THREADS ;
},;
"SCRIPTALIASES" => { => } ,;
"ALIASES" => { => } ;
}, FALSE )
//hb_ToOutDebug( "hDefault = %s\n\r", hb_ValToExp( hDefault ) )
// Now read changes from ini file and modify only admited keys
IF !Empty( hIni )
FOR EACH cSection IN hIni:Keys
cSection := Upper( cSection )
//hb_ToOutDebug( "cSection = %s\n\r", cSection )
IF cSection $ hDefault
hSect := hIni[ cSection ]
//hb_ToOutDebug( "hSect = %s\n\r", hb_ValToExp( hSect ) )
IF HB_IsHash( hSect )
FOR EACH cKey IN hSect:Keys
// Please, below check values MUST be uppercase
//hb_ToOutDebug( "cKey = %s\n\r", cKey )
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
ENDIF
ELSEIF ( cKey := Upper( cKey ) ) $ hDefault[ cSection ] // force cKey to be uppercase
IF ( nPos := hb_HScan( hSect, {|k| Upper( k ) == cKey } ) ) > 0
cVal := hb_HValueAt( hSect, nPos )
//hb_ToOutDebug( "cVal = %s\n\r", cVal )
DO CASE
CASE cSection == "MAIN"
DO CASE
CASE cKey == "PORT"
xVal := Val( cVal )
CASE cKey == "CONSOLE-ROWS"
xVal := Val( cVal )
CASE cKey == "CONSOLE-COLS"
xVal := Val( cVal )
CASE cKey == "APPLICATION_ROOT"
IF !Empty( cVal )
// Change APP_DIR macro with current exe path
xVal := cVal
ENDIF
CASE cKey == "DOCUMENT_ROOT"
IF !Empty( cVal )
// After will change APP_DIR macro with application dir
//xVal := StrTran( cVal, "$(APP_DIR)", Exe_Path() )
xVal := cVal
ENDIF
CASE cKey == "SCRIPTALIASMIXEDCASE"
xVal := cVal
CASE cKey == "SESSIONPATH"
IF !Empty( cVal )
// Change APP_DIR macro with current exe path
//xVal := StrTran( cVal, "$(APP_DIR)", Exe_Path() )
xVal := cVal
ENDIF
CASE cKey == "DIRECTORYINDEX"
IF !Empty( cVal )
xVal := uhttpd_split( " ", AllTrim( cVal ) )
ENDIF
ENDCASE
CASE cSection == "LOGFILES"
DO CASE
CASE cKey == "ACCESS"
xVal := cVal
CASE cKey == "ERROR"
xVal := cVal
ENDCASE
CASE cSection == "THREADS"
DO CASE
CASE cKey == "MAX_WAIT"
xVal := Val( cVal )
CASE cKey == "START_NUM"
xVal := Val( cVal )
CASE cKey == "MAX_NUM"
xVal := Val( cVal )
ENDCASE
ENDCASE
IF xVal <> NIL
hDefault[ cSection ][ cKey ] := xVal
ENDIF
ENDIF
ENDIF
NEXT
ENDIF
ENDIF
NEXT
ENDIF
RETURN hDefault
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)" , s_cApplicationRoot )
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)" , s_cApplicationRoot )
EXIT
ENDIF
NEXT
ENDIF
RETURN cFileName
STATIC FUNCTION uhttpd_DefError( oError )
LOCAL cMessage
LOCAL cCallstack
LOCAL cDOSError
LOCAL aOptions
LOCAL nChoice
LOCAL n
LOCAL cDateTime, cString
LOCAL cNewLine := hb_OSNewLine()
// By default, division by zero results in zero
IF oError:genCode == EG_ZERODIV .AND. ;
oError:canSubstitute
RETURN 0
ENDIF
// By default, retry on RDD lock error failure */
IF oError:genCode == EG_LOCK .AND. ;
oError:canRetry
// oError:tries++
RETURN .T.
ENDIF
// Set NetErr() of there was a database open error
IF oError:genCode == EG_OPEN .AND. ;
oError:osCode == 32 .AND. ;
oError:canDefault
NetErr( .T. )
RETURN .F.
ENDIF
// Set NetErr() if there was a lock error on dbAppend()
IF oError:genCode == EG_APPENDLOCK .AND. ;
oError:canDefault
NetErr( .T. )
RETURN .F.
ENDIF
cMessage := ErrorMessage( oError )
IF ! Empty( oError:osCode )
cDOSError := "(DOS Error " + hb_NToS( oError:osCode ) + ")"
ENDIF
// ;
cCallstack := ""
n := 1
DO WHILE ! Empty( ProcName( ++n ) )
cCallstack += "Called from " + ProcName( n ) + "(" + hb_NToS( ProcLine( n ) ) + ") " + cNewLine
ENDDO
// Build buttons
aOptions := {}
AAdd( aOptions, "Quit" )
IF oError:canRetry
AAdd( aOptions, "Retry" )
ENDIF
IF oError:canDefault
AAdd( aOptions, "Default" )
ENDIF
// Show alert box
#ifdef DEBUG_ACTIVE
hb_ToOutDebug( "ERROR: %s\n\r", cMessage + " " + cCallstack )
#endif
nChoice := 0
DO WHILE nChoice == 0
IF cDOSError == NIL
nChoice := Alert( cMessage + ";" + cCallstack, aOptions )
ELSE
nChoice := Alert( cMessage + " " + cDOSError + ";" + cCallstack, aOptions )
ENDIF
ENDDO
IF ! Empty( nChoice )
DO CASE
CASE aOptions[ nChoice ] == "Break"
Break( oError )
CASE aOptions[ nChoice ] == "Retry"
RETURN .T.
CASE aOptions[ nChoice ] == "Default"
RETURN .F.
ENDCASE
ENDIF
// "Quit" selected
IF cDOSError != NIL
cMessage += " " + cDOSError
ENDIF
OutErr( cNewLine )
OutErr( cMessage )
OutErr( cNewLine )
OutErr( cCallstack )
// Write to errorlog
cDateTime := HB_TTOC( hb_DateTime() )
cString := Replicate( "*", 70 ) + cNewLine + ;
cDateTime + cNewLine + ;
Replicate( "*", 70 ) + cNewLine + ;
cMessage + cNewLine + ;
cCallstack + cNewLine + ;
Replicate( "*", 70 ) + cNewLine
uhttpd_WriteToLogFile( cString, Exe_Path() + "\error.log" )
ErrorLevel( 1 )
QUIT
RETURN .F.
STATIC FUNCTION ErrorMessage( oError )
// start error message
LOCAL cMessage := iif( oError:severity > ES_WARNING, "Error", "Warning" ) + " "
// add subsystem name if available
IF ISCHARACTER( oError:subsystem )
cMessage += oError:subsystem()
ELSE
cMessage += "???"
ENDIF
// add subsystem's error code if available
IF ISNUMBER( oError:subCode )
cMessage += "/" + hb_NToS( oError:subCode )
ELSE
cMessage += "/???"
ENDIF
// add error description if available
IF ISCHARACTER( oError:description )
cMessage += " " + oError:description
ENDIF
// add either filename or operation
DO CASE
CASE !Empty( oError:filename )
cMessage += ": " + oError:filename
CASE !Empty( oError:operation )
cMessage += ": " + oError:operation
ENDCASE
RETURN cMessage
// ----------------------------------------------------------------------------------
// 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_SetHeader( "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_SetHeader( "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 Handler_HrbScript( cFileName )
LOCAL xResult
LOCAL cHRBBody, pHRB, oError
LOCAL cCurPath
TRY
// Lock HRB to avoid MT race conditions
IF !HRB_ACTIVATE_CACHE
cHRBBody := HRB_LoadFromFile( uhttpd_OSFileName( cFileName ) )
ENDIF
IF hb_mutexLock( s_hmtxHRB )
IF HRB_ACTIVATE_CACHE
// caching modules
IF !hb_HHasKey( s_hHRBModules, cFileName )
hb_HSet( s_hHRBModules, cFileName, HRB_LoadFromFile( uhttpd_OSFileName( cFileName ) ) )
ENDIF
cHRBBody := s_hHRBModules[ cFileName ]
ENDIF
WriteToConsole( "Executing: " + cFileName )
IF !EMPTY( pHRB := HB_HRBLOAD( cHRBBody ) )
// save current directory
cCurPath := CurDrive() + hb_osDriveSeparator() + HB_OSPathSeparator() + CurDir()
// Change dir to document root
DirChange( s_cDocumentRoot )
xResult := HRBMAIN()
#ifdef DEBUG_ACTIVE
hb_ToOutDebug( "Handler_HrbScript(): cFileName = %s,\n\rcCurPath = %s,\n\rs_cDocumentRoot = %s,\n\rpHRB = %s,\n\rxResult = %s\n\r", ;
cFileName, cCurPath, s_cDocumentRoot, pHRB, xResult )
#endif
// return to original folder
DirChange( cCurPath )
HB_HRBUNLOAD( pHRB )
ELSE
uhttpd_SetStatusCode( 404 )
t_cErrorMsg := "File does not exist: " + cFileName
ENDIF
hb_mutexUnlock( s_hmtxHRB )
ENDIF
IF HB_ISSTRING( xResult )
uhttpd_SetHeader( "Content-Type", "text/html" )
uhttpd_Write( xResult )
ELSE
// Application in HRB module is responsible to send HTML content
ENDIF
CATCH oError
WriteToConsole( "Error!" )
uhttpd_SetHeader( "Content-Type", "text/html" )
uhttpd_Write( "Error" )
uhttpd_Write( "<br>Description: " + hb_cStr( oError:Description ) )
uhttpd_Write( "<br>Filename: " + hb_cStr( oError:filename ) )
uhttpd_Write( "<br>Operation: " + hb_cStr( oError:operation ) )
uhttpd_Write( "<br>OsCode: " + hb_cStr( oError:osCode ) )
uhttpd_Write( "<br>GenCode: " + hb_cStr( oError:genCode ) )
uhttpd_Write( "<br>SubCode: " + hb_cStr( oError:subCode ) )
uhttpd_Write( "<br>SubSystem: " + hb_cStr( oError:subSystem ) )
uhttpd_Write( "<br>Args: " + hb_cStr( hb_ValToExp( oError:args ) ) )
uhttpd_Write( "<br>ProcName: " + hb_cStr( procname( 0 ) ) )
uhttpd_Write( "<br>ProcLine: " + hb_cStr( procline( 0 ) ) )
END
RETURN MakeResponse()
STATIC FUNCTION Handler_CgiScript( cFileName )
LOCAL xResult
WriteToConsole( "Executing: " + cFileName )
IF ( CGIExec( uhttpd_OSFileName(cFileName), @xResult ) ) == 0
//uhttpd_SetHeader( "Content-Type", cI )
//uhttpd_Write( xResult )
RETURN "HTTP/1.1 200 OK " + CR_LF + xResult
ELSE
uhttpd_SetHeader( "Content-Type", "text/html" )
IF !Empty( xResult )
uhttpd_Write( xResult )
ELSE
uhttpd_Write( "CGI Error" )
ENDIF
ENDIF
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 GT_notifier( nEvent, xParams )
LOCAL nReturn := 0
DO CASE
CASE nEvent == HB_GTE_CLOSE
IF hb_mutexLock( s_hmtxBusy )
s_lQuitRequest := TRUE
nReturn := 1
hb_mutexUnlock( s_hmtxBusy )
ENDIF
ENDCASE
HB_SYMBOL_UNUSED( xParams )
RETURN nReturn
STATIC FUNCTION UHTTPD_UTCOFFSET()
LOCAL nOffset := HB_UTCOFFSET()
RETURN iif( nOffset < 0, "-", "+" ) +;
StrZero( nOffset / 3600, 2, 0 ) +;
StrZero( ( nOffset % 3600 ) / 60, 2, 0 )