10

I would like to make requests to the WordPress API much faster. My API is implemented in a plugin (using register_rest_route to register my routes). However, since this is a plugin, everything is loaded with it (the child-theme and the theme) and basically a query to this API is taking half a second because of all this useless parts loaded.

Doesn't WordPress API can be used in another way? Since most plugin making use of the WP-API doesn't need any other plugins to be loaded, even less a theme... I don't understand how they could miss that.

Is there anyway to do this?

TigrouMeow
  • 1,038
  • 4
  • 19
  • 30
  • `the request doesn't do anything`, what request is that? – brasofilo Apr 03 '16 at 07:05
  • 1
    @brasofilo I have tried to make the question clearer. Basically this is what a call to my API does. Even if it does nothing, it takes time anyway since all the theme and other plugins are loaded. – TigrouMeow Apr 04 '16 at 00:33

4 Answers4

14

Yes, it is possible. In one of my plugins where I need the minimal WordPress core (DB without plugins & themes) here is what I do:

<?php

define('SHORTINIT', true);  // load minimal WordPress

require_once PATH_TO_WORDPRESS . '/wp-load.php'; // WordPress loader

// use $wpdb here, no plugins or themes were loaded

The PATH_TO_WORDPRESS constant I made up; you just need to point that to the correct path. In plugins for example, it might look like:

require_once dirname(__FILE__) . '/../../../wp-load.php'; // backwards 'plugin-dir/plugins/wp-content'

Setting SHORTINIT to true certainly does help performance a bit.

With WP_DEBUG disabled, the time it takes to bootstrap WordPress are as follows:

  • Without SHORTINIT: ~0.045 seconds
  • With SHORTINIT: ~0.0015 seconds

If this is for your own site where you demand performance, you can probably increase this a bit by enabling an OpCache (e.g. APC or PHP OpCache in recent versions).

But I believe the 2 lines of code above to define SHORTINIT and require wp-load.php are what you're looking for.

To clarify, this file is a part of a plugin, but it is called independently of WordPress itself (via Ajax and directly). It never gets included or used by any other parts of the plugin or WP itself.

EDIT: Since the OP is actually concerned with the WP-API, not WordPress in general, I am adding this to address the actual question. I'll leave the original answer content in case it can help someone else.

I did further testing with the WP API and like @David said in his answer, the issue is probably something else.

I loaded up 12 plugins in addition to the rest api, some fairly "large" plugins, and my local install has about 25 themes installed (one active of course). I edited WordPress' index.php file and used microtime(true) to record when everything started, and then edited one of the REST controllers to calculate how long it took from start to getting to the API endpoint.

The result on my system is consistently around 0.0462 - 0.0513 seconds (no PHP OpCache, and no other system load). So it appears bootstrapping all of WordPress has little impact on performance.

If the requests are taking half a second, the bottleneck is elsewhere and cutting out plugins and themes is going to have minimal impact. At least this is what I found.

drew010
  • 68,777
  • 11
  • 134
  • 162
  • That's a very good trick, I actually do some stuff like that and loads wp-load.php to resolve such issues as wp-load.php and where I need callbacks. For my question, it's a bit different since I am using WP-API, I want to make my API available through the WP-API system. Problem is that WP-API is loading the whole WordPress for every single call. With your solution, we are going completely around WP-API :( – TigrouMeow Apr 04 '16 at 11:41
  • My bad, I had been reading the whispers of WP-API in the past months but haven't looked at it. I'll see if I can take a look at it later and revise my answer. Thanks for clearing it up. – drew010 Apr 04 '16 at 15:15
  • I sometimes use this trick in plugins too. In my case, to define `PATH_TO_WORDPRESS` I do: `define('PATH_TO_WORDPRESS', preg_replace('/\/wp-content\/.*/', '', __DIR__) . '/');` – Jordi Nebot Apr 06 '16 at 12:44
  • Hey @TigrouMeow I finally had a chance to look at this again. Edited my answer with what I found, but basically I don't think loading up all of WP is the bottleneck and cutting out plugins won't really help much. Do you have any further details or the ability to profile the requests to see where the most time is being spent? – drew010 Apr 07 '16 at 15:08
  • upvote for the investigation :) the wp_query itself wont be very fast and slower on each post that is added, but autoloaded wp_options can be a huge draw as well. I've often seen 100+ db queries, there is a setting you can enter into wp-config, quick guess `define('SAVEQUERIES', true);` and then you can output all the db queries in a template, `$wpdb->queries` – David Apr 07 '16 at 22:39
  • @drew010 Thank you for your investigation :) However, how can your website be take only 0.0462 - 0.0513 to load all the plugins and the theme? If you query one of your WP-API endpoint, how long it takes to get your reply back? – TigrouMeow Apr 11 '16 at 23:35
  • Running `time curl -o /dev/null -s http://localhost/wordpress/wp-json/wp/v2/posts` yields: `real 0m0.069s` so to fetch the posts it takes about 0.07 seconds total. This is with all plugins active (including buddypress, several contact form plugins etc). Also tried it on a few production sites and the results are all about the same. – drew010 Apr 12 '16 at 00:59
4

I think you might be focusing on the wrong issue.

Loading php files is not nearly as slow as reading from your db and this is likely to be your 500ms load time. You should actually look at reducing this anyway (cache wp-options, etc), but what i suggest to you in relation to the api, is to cache the output using a mu-plugin. Using exit we can load output from file and serve that instantly.

Our Method: 1. Create a folder called mu-plugins in the wp-content folder (may already be there)

  1. create a file called api-cache.php

  2. enter this code into your file:

    function get_api_cache(){
    
        //dont run if we are calling to cache the file (see later in the code)
        if( isset($_GET['cachecall']) && $_GET['cachecall'] === true)
            return;
    
        $url = "$_SERVER[REQUEST_URI]";
    
        //do a little error checking
    
        $uri= explode('/',$url);
    
        //we have a array (1st key is blank)
        if( $uri[1] !== 'wp-json' || $uri[2] !== 'wp' || $uri[3] !== 'v2'){
            return;
        } 
    
        //lock down the possible endpoints we dont want idiots playing with this...
        $allowed_endpoints= array(
            'posts'
        );
    
        $endpoint= array_pop($uri); // not sure if this is valid or not, is there more structure to some api calls?
    
        if( !in_array( $endpoint, $allowed_endpoints) ){
            return;
        } 
    
        //ok reasonably confident its a api call...
    
        $cache_folder= get_stylesheet_directory().'/api_cache/';
    
        // prob best if not within php server but to get you going
        if(! file_exists ( $cache_folder ) ){
            mkdir($cache_folder); //warning 777!!
        }
    
    
        /*
        * Need to choose a method of control for your cached json files
        * you could clear out the folder on update post/ taxonomies etc
        * or cron clear out hourly/weekly whatever freq you want
        */
    
    
        if( file_exists($cache_folder.$endpoint.'.json') ){
            $json= file_get_contents($cache_folder.$endpoint.'.json');
            header('Content-Type: application/json');
            echo $json;
            exit;// we need nothing else from php exit 
        } else {
            //make sure there will be no errors etc..
            $ch = curl_init();
            $url= "http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]?cachecall=true";
            $timeout= 5;
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
            $json = curl_exec($ch);
            curl_close($ch);
            file_put_contents($cache_folder.$endpoint.'.json', $json);  
        }
    
    }
    
    get_api_cache();    
    

Now you should notice a significant difference on your load time on the 2nd load (this first time it is caching the output).

A few disclaimers:

  1. you should read the comments in the code
  2. You need curl
  3. You need to be aware the cache folder is 777, I would strongly suggest you move this away from your theme folder and preferably outside your http accessible files.
  4. There were no catch all hooks to capture the data to be cached, hence i used curl to grab the content, this may change in the future and a hook/filter would improve the process time a bit when creating the cache file.
  5. I have not included a method to update cache files. You need to decide on how often you want to update, a site that gets lots of posts per day and a lot of visits, you might do a cron job to just delete the files (e.g. 3 times a day, hourly, every 10 minutes, etc-- what is a reasonable tradeoff in update time?) or add a hook to save post to only update when your posts change, etc..
  6. add your endpoints to the array for them (you can remove the if statement to allow all endpoints, but then you may have a situation where 404s are being cached!)
David
  • 5,897
  • 3
  • 24
  • 43
1

You should give this a try this. It is a plug-in that allows you to enable/disable certain plug-ins for post-types, pages and other circumstances.

For the theme part, if you wrote it, it would be easy to add something in the function.php to prevent it from attaching any hooks or filters in the case of an API request.

As a sidenote, couldn't you query de DB directly?

Community
  • 1
  • 1
Mathieu de Lorimier
  • 975
  • 3
  • 19
  • 32
0

Sorry for my poor english if this is helpful for you.

Put the plugin folder in root wordpress install.

/public_html/my-plugin/my-plugin.php

and include wordpress main file.

    require dirname( dirname( __FILE__ ) ).'/wp-load.php';

Or in plugin folder directly access

/public_html/wp-content/plugins/my-plugin/my-plugin.php
require_once dirname(__FILE__) . '/../../../wp-load.php';

Before check wp-load.php file included properly and working.

wp-settings.php file load whole core, plugins and themes files. wordpress is load first mu-plugins files (wp-content/mu-plugins/) and provide after action hook muplugins_loaded. Trigger this action to exit whole other files loaded. You can also find which action hook is provide before muplugins_loaded and stop other files and script execution.

if define constant SHORTINIT before include wp-load.php its includes some files provide DB,plugin or basic functions. When we want more load core files and not just want load plugins and theme files in this way found a solution.

// file my-plugin.php
//call before include file wp-load.php
global $wp_filter;
$wp_filter = array( 
    // pass wp hook where to want exit extra wp loaded
    'muplugins_loaded' => array(
     // prority
         1 => array(
            // callback function register
            'wp_extra_loaded_exit' => array( 

              'function' => 'wp_extra_loaded_exit',

              'accepted_args' => 1

              )
         ) 
   )
);



function wp_extra_loaded_exit(){
  exit; 

}
require dirname( dirname( __FILE__ ) ).'/wp-load.php';
// plugin code here.

We check muplugins_loaded hook is define wordpress early you can also find which hook is define before muplugins_loaded then stop this point to after load more wordpress files. -

When you want to test your script open file wp-settings.php and find string muplugins_loaded then echo statement to check.

echo "Wordpress loaded in this point before"; 

do_action( 'muplugins_loaded' );

echo "After this wordpress not loading"; // Output fail bcz we exit 
user5200704
  • 1,689
  • 11
  • 10