Yaminij's blog

  • SNI (Server Name Indication) Readiness Tool

    The SNI Readiness tool can be used by server admins to parse incoming HTTP traffic from the IIS W3C logs on the IIS server in order to determine the percentage requests that come from SNI capable clients. This should enable server admins to make an informed decision on enabling SNI-Centric features on IIS 8.0 for Windows 8.

  • 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