Archives
-
Session Tracker Native Module for IIS 7.0
The Session Tracker is a native C++ module for IIS 7.0 that can be used to obtain information about user-activity on a site using cookies.
-
Getting started with writing a FastCGI Client application (FastCGI Test Client)
I worked on the FastCGI for IIS 6.0 RTM release a while ago and during that time, I worked on developing a FastCGI Client application, in order to test FastCGI at a protocol level. I just decided to blog about it, just in case any of you are interested in getting started with writing a FastCGI client application from scratch.
You can download the sample client application along with the source code from here. This application can work both over TCP and Named Pipes.
The FastCGI Client application is a generic application that will be used to communicate with the Web Server (FastCGI handler) through FastCGI protocol either over Named Pipes or TCP. The FastCGI test client communicates in the form of messages with the FastCGI Server. All these messages are in the form of STDIN, STDOUT and STDERR. The Web Server (FastCGI Handler) creates a Named Pipe or a TCP stream for communication and duplicates these handles onto the stdout, stdin and stderr (parent processes can override the stdin, stdout and stderr of child). By this, the application just has to write it’s protocol messages to stdin, stdout and stderr and the details of the TCP/Named Pipe communication is abstracted out.
As a first step, you can determine if the application was invoked using CGI or FastCGI by calling getpeername on the STD_INPUT_HANDLE. You should get a socket error if the client was invoked using a FastCGI application. Depending on whether your server uses TCP or Named Pipes, your WSAGetLastError() could return WASENOTCONN or WSAENOTSOCK respectively.
bool IsApplicationInvokedUsingFastCGI( HANDLE s ) { // Determine if this application was invoked using CGI or FastCGI struct sockaddr nameOfConnectedSocket; int nameLenOfConnectedSocket; HRESULT hr = S_OK; int errs; errs = getpeername( (SOCKET)s, &nameOfConnectedSocket, &nameLenOfConnectedSocket ); if ( errs == SOCKET_ERROR ) { hr = HRESULT_FROM_WIN32( WSAGetLastError() ); if( hr == HRESULT_FROM_WIN32( WSAENOTCONN ) ) { //LogMessage( TRACE_MESSAGE, "Application was invoked using FastCGI over TCP, got a WSAENOTCONN on a call to getpeername()" ); return TRUE; } else if( hr == HRESULT_FROM_WIN32( WSAENOTSOCK ) ) { //LogMessage( TRACE_MESSAGE, "Application was invoked using FastCGI over Named Pipe, got a WSAENOTSOCK on a call to getpeername()" ); return TRUE; } else { return FALSE; } } else { // Quit, this app is not compatible with FastCGI //LogMessage( TRACE_MESSAGE, "Application was invoked using CGI, getpeername() successful" ); return FALSE; } }
Next step to writing a client is to try and determine if the communication with the Web Server should happen over Named Pipes or TCP. Do an accept() socket call on the STDIN and if it succeeds, use TCP, else use Named Pipes.
bool IsNamedPipeOrSocket( HANDLE hStdin, FASTCGI_PROTOCOL * useNamedPipeOrSocket ) { HRESULT hr = S_OK; try { acceptSocket = accept( (SOCKET)hStdin, NULL, NULL ); if( acceptSocket == INVALID_SOCKET ) { (*useNamedPipeOrSocket) = FcgiConfigProtocolNamedPipe; } else { (*useNamedPipeOrSocket) = FcgiConfigProtocolTcp; } } catch(...) { //LogMessage( TRACE_EXCEPTION, "Exception is IsNamedPipeOrSocket() function" ); return FALSE; } return TRUE; }
Then Initialize the transport connection and receive and send messages from and to the FastCGI Server respectively. For more information on this exchange please refer the FastCGI protocol specification.
// Initialize the transport connection based on whether the Server is using Named Pipes or TCP. if( namePipeOrSocket == FcgiConfigProtocolNamedPipe ) { pTransport = new TRANSPORT_NAMEDPIPE(); } else { pTransport = new TRANSPORT_TCP(); }
class TRANSPORT { public: TRANSPORT() {} ~TRANSPORT() {} HRESULT Send( PBYTE pBuffer, DWORD dwSize) { return SendReceive( TRUE, pBuffer, dwSize ); } HRESULT Receive( PBYTE pBuffer, DWORD dwSize) { return SendReceive( FALSE, pBuffer, dwSize ); } protected: virtual HRESULT SendReceive( bool fSend, PBYTE pBuffer, DWORD dwSize ) = 0; };
class TRANSPORT_NAMEDPIPE : public TRANSPORT {
}
class TRANSPORT_TCP : public TRANSPORT {
}
class TRANSPORT_NAMEDPIPE : public TRANSPORT { public: TRANSPORT_NAMEDPIPE() {} ~TRANSPORT_NAMEDPIPE() {} protected: HRESULT SendReceive( bool fSend, PBYTE pBuffer, DWORD dwSize ) { BOOL fRet = FALSE; DWORD dwLastError = 0; DWORD nNumberOfBytesRead = 0; if( fSend == TRUE ) { fRet = WriteFile( hStdin, pBuffer, dwSize, &nNumberOfBytesRead, NULL ); } else { fRet = ReadFile( hStdin, pBuffer, dwSize, &nNumberOfBytesRead, NULL ); } if(!fRet) { dwLastError = GetLastError(); } return HRESULT_FROM_WIN32( dwLastError ); } }; class TRANSPORT_TCP : public TRANSPORT { public: TRANSPORT_TCP() {} ~TRANSPORT_TCP() {} protected: HRESULT SendReceive( bool fSend, PBYTE pBuffer, DWORD dwSize ) { int iRet = 0; WSABUF winsockBuffer; DWORD dwBytesTransferred = 0; DWORD dwFlags = 0; DWORD dwLastError = 0; winsockBuffer.len = dwSize; winsockBuffer.buf = (char *)pBuffer; if( fSend == TRUE ) { iRet = WSASend( acceptSocket, &winsockBuffer, 1, &dwBytesTransferred, 0, NULL, NULL ); } else { iRet = WSARecv( acceptSocket, &winsockBuffer, 1, &dwBytesTransferred, &dwFlags, NULL, NULL ); } if( SOCKET_ERROR == iRet ) { dwLastError = WSAGetLastError(); } return HRESULT_FROM_WIN32( dwLastError ); } };
A while ago, Rick who was the developer of the FastCGI core module, published a fake fastcgi server that works only over Named Pipes. To quickly see the working of this client application, you can run it with the fake server as below. The highlighted messages are returned by the client application TestClient.exe.
D:\fake>fakefcgi.exe d:\php\hello.php TestClient.exe
Fake FastCGI web server
FCGI_PARAMS sent
FCGI_STDIN sent
Launching receive loop
FCGI_STDOUT: FCGIClient : Obtained FCGI_BEGIN_REQUEST message from Server
FCGI_STDOUT: FCGIClient : Obtained FCGI_PARAMS from Server
FCGI_STDOUT: FCGIClient : Obtained FCGI_STDIN from Server
FCGI_STDOUT: FCGIClient : Sending FCGI_END_REQUEST to Server
FCGI_END_REQUEST received
killing app
FastCGI process exited with 0