2

I'm using a third party API service to get a country code from IP address, and to limit the number of requests I'm storing the country code in a local database table, and doing a lookup to that first. If the record doesn't exist, it goes to get the country code and stores it for the next lookup.

However, when looking in the database table, I'm seeing the same IP address and countrycode row that's being added about 20 times.

What's going on? I'm reading through this, and I can't understand why it would do several adds to the table, it makes no sense.

This is causing massive disruption with the API service who are telling us we are abusing their request limit. How can I resolve this?

function get_country_code() {


    // Check if site is local dev
    if(get_bloginfo('url') == '###') {
        
        global $wpdb;
    
        $ip         = $_SERVER['REMOTE_ADDR'];
        $access_key = '###';
        $record     = $wpdb->get_row("SELECT * FROM wp_geoip WHERE ip = '$ip'");
        
        if($record) {

            // Return stored country code
            return $record->country_code;
            
        } else {
            
            // Make request to ipapi and store country code into table
            $ch = curl_init('http://api.ipapi.com/'.$ip.'?access_key='.$access_key.'');
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            $json = curl_exec($ch);
            curl_close($ch);
            $api_result = json_decode($json, true);
            $wpdb->insert(
                'wp_geoip',
                array(
                    'ip'           => $ip,
                    'country_code' => $api_result['country_code']
                ),
                array(
                    '%s',
                    '%s'
                )
            );
            
            // Return country code
            return $api_result['country_code'];
            
        }
        
    } else {
        
        return 'GB';
        
    }

}
halfer
  • 19,824
  • 17
  • 99
  • 186
Lee
  • 4,187
  • 6
  • 25
  • 71
  • debug / print_r($record), to see why is false in your if statement – Salines Apr 05 '19 at 15:30
  • It's not false... I haven't said it's false. $record is returning the DB row. – Lee Apr 05 '19 at 15:35
  • 1
    If this executes for a page as well as any resources within that page you may have a race condition as the requests are concurrent. – Alex K. Apr 05 '19 at 15:37
  • Not sure I understand what you're saying @AlexK., but obviously this runs whenever the function is called, which is multiple times on a page, but the first time the function is run, that's when it shoudl create the record. Then subsequent times, the function should find the row and get the local value. Perhaps, it's not.. but why isn't it? – Lee Apr 05 '19 at 15:39
  • Because a server can receive and process multiple requests at once, one of the requests can have already checked the database, found nothing, and is now calling the API. Meanwhile, another request can also run, check the database, also find nothing (siince the record hasn't been inserted by the other request), and also call the API. – g-dg Apr 05 '19 at 15:42
  • If it runs twice on a page then both requests from the same IP run the function at approximately the same time when the row does not exist, both then query the API and both then add the row so you have 2 rows. – Alex K. Apr 05 '19 at 15:42
  • Hmm okay, well how would I overcome that then? I would've thought surely the function can't run at the same time, PHP can only run through one thing at a time... – Lee Apr 05 '19 at 15:43
  • To partially fix this, insert a record with just the IP (no country code) just after finding out that the IP address is not in the database. To more fully fix this, make sure it only runs on the main page (not any other resources). – g-dg Apr 05 '19 at 15:46
  • PHP can only run through one thing at a time *per request* - If a PHP page returns HTML that calls more PHP - to return an image/script/css resource etc - then the browser will run another requests in parallel to fetch it. – Alex K. Apr 05 '19 at 15:48
  • Hmm okay, do you have any idea which Wordpress hook should be used as the initial call then? I can then leave the function as is, and strictly tell it to get the local value then and go nowhere near the API for the rest of the pages / areas. – Lee Apr 05 '19 at 15:51
  • Sorry, no idea about Wordpress. – Alex K. Apr 05 '19 at 16:24
  • You can do it at the `init` hook, as no data will have been sent to the browser yet so it won't know what resources to request. Note however, that this may slow things down a bit, especially for people who are visiting for the first time from that IP address as then it will have to query the API. – g-dg Apr 05 '19 at 16:30

0 Answers0