Working with PHP Sockets and cURL

I was recently asked to release OneLobby's source code. It's an unfinished product but I think it would be a good idea. However, before I do that I'm going to showcase some of the useful classes I made for it. Four of these classes happen to be interfaces for dealing with PHP's socket and cURL functions. Take a look...

<?php

/*
OneLobby
Copyright (C) 2007 Peter Goodman, Geoffrey Goodman

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
of the License, 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 program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

//-------------------------------------------------
// The user Agent and version of this OneLobby remote
// XML caller.
//-------------------------------------------------

define ('REMOTE_XML_USERAGENT''OneLobby [RemoteXML v.1.0]');

//-------------------------------------------------
// Out little extension class.. it's not really needed,
// but it's convenient to choose REST or RPC.
//-------------------------------------------------

class RemoteXML extends FAExtension
{
    function 
RemoteXML()
    {
        
// moo :P
    
}
    
    function &
getFinder($type 'REST')
    {
        
$class $type .'_Request';
        
$ret NULL;
        
        if(
class_exists($class))
        {
            
$ret = &new $class();
        }
        
        return 
$ret;
    }
}

//-------------------------------------------------
// Base wrapper class for REST and RPC calls. Deal with
// opening connections using sockets or cURL, sending
// and receiving data, etc.
//-------------------------------------------------

class RemoteXMLRequest
{
    
//-------------------------------------------------
    // Some variables related to RPC and REST requests.
    //-------------------------------------------------
    
    
var $_initial_request FALSE;
    var 
$_initial_put FALSE;
    
    
//-------------------------------------------------
    // Get a cURL or Socket connection.
    //-------------------------------------------------
    
    
function &getConnection()
    {
        
//-------------------------------------------------
        // If the cURL extension isn't loaded, try to load
        // it. The PHP_SHLIB_SUFFIX is for checking if we are
        // on Windows or UNIX.
        //-------------------------------------------------

        
if(!extension_loaded('curl'))
        {
            
$prefix = (PHP_SHLIB_SUFFIX === 'dll') ? 'php_' '';
            @
dl($prefix 'curl.'PHP_SHLIB_SUFFIX);
        }
        
        if(
function_exists('curl_init'))
        {
            
$conn = &new cURL_Connection;
        }
        else
        {
            
$conn = &new Socket_Connection;
        }
        
        return 
$conn;
    }
    
    
//-------------------------------------------------
    // Interface methods...
    //-------------------------------------------------
    
    
function getInfo($url)
    {
        
assert(FALSE);
    }
    
    function 
putInfo($url$xml)
    {
        
assert(FALSE);
    }
}

//-------------------------------------------------
// Deal with REST (representational state transfers)
// requests.
//-------------------------------------------------

class REST_Request extends RemoteXMLRequest
{
    
//-------------------------------------------------
    // Do the initial request of data for this REST session.
    //-------------------------------------------------
    
    
function getInfo($url)
    {    
        
//-------------------------------------------------
        // Get out socket/cURL connection and make the request.
        //-------------------------------------------------
        
        
$conn = &$this->getConnection();
        
$conn->createRequest('GET'$url);
        
$data $conn->getReturnData();
        
$conn->closeRequest();
        
        
$this->_initial_request TRUE;
        
        return 
$data;
    }
    
    
//-------------------------------------------------
    // Put info to the server.
    //-------------------------------------------------
    
    
function putInfo($url$xml)
    {
        
//-------------------------------------------------
        // Make sure we're doing things the REST way.
        //-------------------------------------------------
        
        
if(!$this->_initial_request)
        {
            
trigger_error("[ERROR] This is a REST application, not RPC.");
        }
        
        
//-------------------------------------------------
        // Use a socket connection for this for the sake of
        // simplicity.
        //-------------------------------------------------
        
        
$conn = &new Socket_Connection;
        
        
$conn->createRequest('PUT'$url);
        
$conn->setPutData($xml);
        
$data $conn->getReturnData();
        
$conn->closeRequest();
        
        return 
$data;
    }
}

//-------------------------------------------------
// Deal with RPC (remote procedure call)
// requests.
//-------------------------------------------------

class RPC_Request extends RemoteXMLRequest
{    
    
//-------------------------------------------------
    // Put info to the server.
    //-------------------------------------------------
    
    
function putInfo($url$xml)
    {    
        
//-------------------------------------------------
        // Use a socket connection for this for the sake of
        // simplicity.
        //-------------------------------------------------
        
        
$conn = &new Socket_Connection;
        
        
$conn->createRequest('PUT'$url);
        
$conn->setPutData($xml);
        
$data $conn->getReturnData();
        
$conn->closeRequest();
        
        
$this->_initial_put TRUE;
        
        return 
$data;
    }
    
    
//-------------------------------------------------
    // Get the information after out inital PUT
    //-------------------------------------------------
    
    
function getInfo($url)
    {    
        
//-------------------------------------------------
        // Make sure we're doing things the RPC way.
        //-------------------------------------------------
        
        
if(!$this->_initial_put)
        {
            
trigger_error("[ERROR] This is a REST application, not RPC."E_USER_ERROR);
        }
        
        
//-------------------------------------------------
        // Get out socket/cURL connection and make the request.
        //-------------------------------------------------
        
        
$conn = &$this->getConnection();
        
$conn->createRequest('GET'$url);
        
$data $conn->getReturnData();
        
$conn->closeRequest();
        
        return 
$data;
    }
}

//-------------------------------------------------
// Interface for cURL and socket connections.
//-------------------------------------------------

class RemoteXMLConnection
{
    var 
$_conn;
    var 
$_conn_closed FALSE;
    
    
//-------------------------------------------------
    // Some nice variables
    //-------------------------------------------------
    
    
var $_scheme;
    var 
$_server;
    var 
$_path;
    var 
$_port 80;
    var 
$_timeout 5;
    
    var 
$_put FALSE;
    var 
$_head FALSE;
    
    function 
createRequest($type 'GET'$server ''$path '/'$port 80)
    {
        
assert(FALSE);
    }
    
    function 
closeRequest()
    {
        
assert(FALSE);
    }
    
    function 
getReturnData()
    {
        
assert(FALSE);
    }
    
    function 
setFile($file_name)
    {
        
assert(FALSE);
    }
    
    function 
errorNo()
    {
        
assert(FALSE);
    }
    
    function 
showAnyErrors()
    {
        
assert(FALSE);
    }
    
    function 
setXMLData(&$data)
    {
        if(!
is_a($data'RemoteXMLDocument'))
        {
            
trigger_error("[ERROR] The data must be an instance of the RemoteXMLDocument object.");
        }
    }
    
    function 
_parseUrl($url)
    {
        
//-------------------------------------------------
        // Parse the passed in URL.
        //-------------------------------------------------
        
        
$url parse_url($url);
        
        
$this->_scheme strtoupper($url['scheme']);
        
$this->_server $url['host'];
        
$this->_path = isset($url['path']) ? $url['path'] : '/';
        
$this->_port = isset($url['port']) ? $url['port'] : 80;
        
$this->_timeout = isset($url['fragment']) ? $url['fragment'] : 5;
    }
}

//-------------------------------------------------
// A cURL layer for doing what we need to do :D
//-------------------------------------------------

class cURL_Connection extends RemoteXMLConnection
{
    var 
$_called_exec FALSE;
    
    
//-------------------------------------------------
    // Create a request.
    //-------------------------------------------------
    
    
function createRequest($method 'GET'$url)
    {    
        
//-------------------------------------------------
        // Parse the url to get the connection data.
        //-------------------------------------------------
        
        
$this->_parseUrl($url);
        
        
//-------------------------------------------------
        // Check to see if the cURL extension is even enabled.
        //-------------------------------------------------
        
        
if(function_exists('curl_init'))
        {
            
//-------------------------------------------------
            // Start connecting and do stuff.
            //-------------------------------------------------
            
            
$this->_conn = &curl_init();
            
            
$this->showAnyErrors();
            
            
curl_setopt($this->_connCURLOPT_URL$this->_server $this->_path);
            
curl_setopt($this->_connCURLOPT_HTTP_VERSION1.0);
            
curl_setopt($this->_connCURLOPT_USERAGENTREMOTE_XML_USERAGENT);
            
            
$this->showAnyErrors();
            
            
//-------------------------------------------------
            // Figure out what connection method we are using.
            //-------------------------------------------------
            
            
$return_headers FALSE;
            
            if(
$method == 'GET')
            {
                
$method CURLOPT_HTTPGET;
            }
            else if(
$method == 'POST')
            {
                
$method CURLOPT_POST;
            }
            else if(
$method == 'HEAD')
            {
                
$method 'GET';
                
$return_headers TRUE;
            }
            else
            {
                
$method CURLOPT_PUT;
                
$this->_put TRUE;
            }
            
            
curl_setopt($this->_conn$method1);
            
            
//-------------------------------------------------
            // Should we display the headers?
            //-------------------------------------------------
            
            
curl_setopt($this->_connCURLOPT_HEADER$return_headers);
            
            
//-------------------------------------------------
            // If the request fails (error code > 400), fail silently.
            //-------------------------------------------------
            
            
curl_setopt($this->_connCURLOPT_FAILONERROR1);
            
            
//-------------------------------------------------
            // Follow header(Location: )'s, but only a max of 5
            // of them.
            //-------------------------------------------------
            
            
curl_setopt($this->_connCURLOPT_FOLLOWLOCATION1);
            
curl_setopt($this->_connCURLOPT_MAXREDIRS5);
            
            
//-------------------------------------------------
            // Make sure to close the request.
            //-------------------------------------------------

            
register_shutdown_function(array(&$this'closeRequest'));
        }
        else
        {
            
trigger_error("[ERROR] The cURL extension cannot be found.");
        }
    }
    
    
//-------------------------------------------------
    // Get the return data from the execution.
    //-------------------------------------------------
    
    
function getReturnData()
    {
        
curl_setopt($this->_connCURLOPT_RETURNTRANSFER1);
        
$data curl_exec($this->_conn);
        
        
$this->_called_exec TRUE;
        
        return 
$data;
    }
    
    
//-------------------------------------------------
    // Close the connection
    //-------------------------------------------------
    
    
function closeRequest()
    {
        if(!
$this->_conn_closed)
        {
            if(!
$this->_called_exec)
            {
                
curl_exec($this->_conn);
            }
        
            
curl_close($this->_conn);
        }
    }
    
    
//-------------------------------------------------
    // If we're using HTTP PUT, we need to 'put' a file to
    // their server, so, open up a file pointer to the local
    // file that we want to put and send it to their server.
    //-------------------------------------------------
    
    
function setFile($file_name)
    {
        if(
$this->_put)
        {
            
//-------------------------------------------------
            // Do some nice error checking..
            //-------------------------------------------------
            
            
if(!file_exists($file_name))
            {
                
trigger_error("[ERROR] File [$file_name] does not exist.");
            }
            
            if(!
is_readable($file_name))
            {
                
trigger_error("[ERROR] File [$file_name] cannot be read.");
            }
            
            
//-------------------------------------------------
            // Get and put the file.
            //-------------------------------------------------
            
            
$file_fp fopen($file_name"r");
            
$file_size filesize($file_name);
            
            
curl_setopt($this->_connCURLOPT_INFILE$file_fp);
            
curl_setopt($this->_connCURLOPT_INFILESIZE$file_size);
            
            
$this->showAnyErrors();
        }
    }
    
    
//--------------------------------------------
    // Send XML data through the curl session.
    //--------------------------------------------
    
    
function setPutData($xml)
    {
        if(
$this->_put)
        {
            
// TODO:{[Should I make this create a file only to pass it to cURL only to delete it after?[setPutData[cURL_Connection}
        
}
    }
    
    
//-------------------------------------------------
    // Return the last error code of the curl operation.
    //-------------------------------------------------
    
    
function errorNo()
    {
        return 
curl_errno($this->_conn);
    }
    
    
//-------------------------------------------------
    // If there are any errors, show them.
    //-------------------------------------------------
    
    
function showAnyErrors()
    {
        if(
$this->errorNo() > 0)
        {
            
trigger_error(curl_error($this->_conn), E_USER_ERROR);
        }
    }
}

//-------------------------------------------------
// Do everything that the above cURL stuff does, but
// manually with the socket.
//-------------------------------------------------

class Socket_Connection extends RemoteXMLConnection
{
    
//-------------------------------------------------
    // Open a socket to the url on the specified port.
    //-------------------------------------------------
    
    
function createRequest($method 'GET'$url)
    {
        
//-------------------------------------------------
        // Parse the url to get the connection data.
        //-------------------------------------------------
        
        
$this->_parseUrl();
        
        
//-------------------------------------------------
        // Connect...
        //-------------------------------------------------
        
        
$scheme $this->_scheme == 'HTTPS' 'ssl' $this->_scheme;
        
        
$this->_conn fsockopen($scheme .'://'$this->_server$this->_port$error_number$error_string$this->_timeout);
        
        
//-------------------------------------------------
        // Error check.
        //-------------------------------------------------
        
        
if(!$this->_conn)
        {
            
trigger_error("[ERROR] Could not open the socket."E_USER_ERROR);
        }
        
        
$data stream_get_meta_data($this->_conn);
        
        if(
$data['timed_out'])
        {
            
trigger_error("[ERROR] Socket times out."E_USER_ERROR);
        }
        
        
//-------------------------------------------------
        // Make sure it times out.
        //-------------------------------------------------
        
        
stream_set_timeout($this->_conn$this->_timeout);
        
stream_set_blocking($this->_connFALSE);
        
        
//-------------------------------------------------
        // Figure out what connection method we are using.
        //-------------------------------------------------
        
        
if($method == 'PUT')
        {
            
$this->_put TRUE;
        }
        else if(
$method == 'HEAD')
        {
            
$this->_head TRUE;
        }
        
        
//-------------------------------------------------
        // Set the headers.
        //-------------------------------------------------
        
        
if($this->_scheme == 'HTTPS')
        {
            
$this->_scheme 'HTTP';
        }
        
        if(
$this->_scheme != 'UDP')
        {
            
$this->_socketCommand("{$method} {$this->_path} {$this->_scheme}");
        }
        
        
$this->_socketCommand("Host: {$this->_server}");
        
$this->_socketCommand("User-Agent: "REMOTE_XML_USERAGENT);
        
$this->_socketCommand("X-Requested-With: "REMOTE_XML_USERAGENT);
        
$this->_socketCommand("Connection: Close");
        
$this->_socketCommand("");
        
        
//-------------------------------------------------
        // Make sure to close the request.
        //-------------------------------------------------
        
        
register_shutdown_function(array(&$this'closeRequest'));
    }
    
    
//-------------------------------------------------
    // Close the socket.
    //-------------------------------------------------
    
    
function closeRequest()
    {
        if(!
$this->_conn_closed)
        {
            
fclose($this->_conn);
            
$this->_conn_closed TRUE;
        }
    }
    
    
//-------------------------------------------------
    // Get the data that the socket returned.
    //-------------------------------------------------
    
    
function getReturnData()
    {
        
$data "";
        
        
$first_line TRUE;
        
$getting_headers TRUE;
        
        
//-------------------------------------------------
        // Loop through the data returned.
        //-------------------------------------------------
        
        
while(!feof($this->_conn))
        {
            
$line fgets($this->_conn4096);
            
            
//-------------------------------------------------
            // This is the first line, check for the 200 code 
            // (everything's a-okay)
            //-------------------------------------------------
            
            
if($first_line)
            {
                if(
strlen($line) != 0)
                {
                    
//-------------------------------------------------
                    // Did something go wrong?
                    //-------------------------------------------------
                    
                    
if(strstr($line" 200 ") === FALSE)
                    {
                        
trigger_error("[ERROR] An error occured. HTTP status code was not 200.");
                    }
                
                    
$first_line FALSE;
                    
                    
//-------------------------------------------------
                    // If we only want to get the headers, make them part
                    // of the data.
                    //-------------------------------------------------
                    
                    
if($this->_head)
                    {
                        
$data .= $line;
                    }
                }
            }
            
            
//-------------------------------------------------
            // If we're not getting headers anymore, add this line to the data.
            //-------------------------------------------------
            
            
if(!$getting_headers && !$this->_head)
            {
                
$data .= $line;
            }
            
            
//-------------------------------------------------
            // If we're still getting headers and this line is equal
            // to CR\LF, tell the script to start recording data for
            // the next loop.
            //-------------------------------------------------
            
            
if($getting_headers && $line == "\r\n")
            {
                
$getting_headers FALSE;
                
                
//-------------------------------------------------
                // So, we only want to return the headers.
                //-------------------------------------------------
                
                
if($this->_head)
                {
                    break;
                }
            }
        }
        
        return 
$data;
    }
    
    
//--------------------------------------------
    // Send data through the socket.
    //--------------------------------------------
    
    
function setPutData($xml)
    {
        if(
$this->_put)
        {
            
fputs($this->_conn$xmlstrlen($xml));
        }
    }
    
    
//--------------------------------------------
    // Send a command to the socket.
    //--------------------------------------------
    
    
function _socketCommand($command)
    {
        
$command $command ."\r\n";
        
fputs($this->_conn$commandstrlen($command));
    }
}

?>

Well, you should be able to figure out how to use them, they're pretty straightforward. Have fun with them!

Comments

thank you.

very helpful.

    by andy on Jan 20, 2008 @ 10:44pm

Add a Comment