Re use authenticated HTTP connection or cUrl handle

We have a bespoke WordPress plugin, which adds a meta box to the edit post page. The plugin accesses data from a semi-private HTTP REST API.

The connection is authenticated (DIGEST) and accessed regularly – every person writing a post will interact with it several times (usually in tight bursts) and there are > 100 such people. So, we would like to ensure the HTTP calls are working efficiently. We see two problems:

Read More
  • No evidence of Keep-Alive being used to stop the TCP socket opening
    and closing constantly.
  • Every request is done twice for the DIGEST authentication. Since it is the plugin that is authenticated (not the user) this is > 99% waste. Apache have called their solution for this pre-emptive authentication.

We can see latency in the meta box as users interact with it, so this is a real issue.

I hear some of these problems can be solved by re-using the cURL handle, but cannot find any best practice advice around implementing that in a WordPress plugin specifically. One issue we have with that is that WordPRess effectively re-initialises the plugin on a per request basis, as far as we can tell.

Related posts

Leave a Reply

2 comments

  1. It is not possible, at least not within reason, to solve either of these issues due to fundamental limitations of PHP in a web envionment (mod_php).

    Class variables do not persist between web requests, so the Keep-Alive part of the problem cannot be fixed because all the resources (i.e. curl handle, tcp socket) will be destroyed at the end of the request. Re-using the cUrl handle seem to be mostly relevant to batch scripts or the rare web-script that needs to access multiple URLs in one go. The kind of performance optimizations considered standard for Java developers are therefore unavailable to PHP developers.

    One could try to implement digest authentication by sticking the values for the Authorization header into session, but that is not cheap to implement (high dev time) and not something that many enterprises want to dedicate time to. This might be a nice 3rd year university project for example.

    Hopefully, at some point, someone will release an add-on product for PHP in Apache that somehow channels HTTP requests through to a TCP connection pool external to the process. That would probably make money, given that it will knock a lot of latency off upstream HTTP requests from PHP.

  2. Inspecting the cURL request

    Even if inspecting and debugging a cURL request isn’t something unique to WordPress, you need to know a good portion of the WP HTTP API internals to get around it. I’ve written myself a plugin to do that for me, which I’ve modified/stripped down and attached for you to use. It will output cURL object details in the shutdown hook, both in the admin UI as well as in the Front-End/Theme.

    What you can do, is use the exact same hook http_api_curl and catch the cURL object. Then save it into a class variable, attach your next calls to it and go on. I haven’t done anything like this myself before, but it should be doable.

    Edit

    As the number of edits here is limited, I moved the further development of this plugin to my GitHub account. If you have notes, comments or suggestions, leave a comment there.

    The plugin below is fully functional. Still a much more advanced version is on GitHub.

    <?php
    /** 
     * Plugin Name: (#81791) Dump cURL Request & Response 
     * Author:      Franz Josef Kaiser
     */
    add_action( 'plugins_loaded', array( 'WPSE81791_cURL', 'init' ) );
    class WPSE81791_cURL
    {
        protected static $instance;
    
        public static $dump;
    
        public static function init()
        {
            null === self :: $instance AND self :: $instance = new self;
            return self :: $instance;
        }
    
        public function __construct()
        {
            add_action( 'http_api_curl', array( $this, 'dump_curl' ) );
            add_action( 'shutdown', array( $this, 'do_dump' ) );
        }
    
        /**
         * Debug the response in the middle.
         * Catches the cURL object during the request.
         * @param  cURL $handle
         * @return void
         */
        public function dump_curl( &$handle )
        {
            curl_setopt( $handle, CURLINFO_HEADER_OUT, 1 );
            curl_setopt( $handle, CURLOPT_HEADER, 0 );
            curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'dump_curl_buffer_cb' ) );
            curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, 'dump_curl_buffer_cb' ) );
            curl_exec( $handle );
            $this->add_dump(
                 curl_getinfo( $handle, CURLINFO_HEADER_OUT )
                .$this->dump_curl_buffer_cb( null )
                .'<br />Nr. of Errors: '.curl_errno( $handle )
                .'<br />Errors: '.curl_error( $handle )
            );
        }
    
        /**
         * Callback for cURL dump method
         * @param  object $curl
         * @param  null   $data
         * @return int
         */
        public function dump_curl_buffer_cb( $curl, $data = null )
        {
            static $buffer = '';
            if ( is_null( $curl ) )
            {
                $r = $buffer;
                $buffer = '';
                return $r;
            }
            $buffer .= $data;
            return strlen( $data );
        }
    
        /**
         * Adds data to the static data stack
         * @param  
         * @return void
         */
        public function add_dump( $data )
        {
            self :: $dump[] = $data;
        }
    
        /**
         * Dumps the data stack for debug
         * @param  
         * @return void
         */
        public function do_dump()
        {
            printf(
                 '<pre>%s</pre>'
                ,var_export( implode( "<br />", self :: $dump ), true ) 
            );
        }
    }