1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
use crate::geocoding::{
    error::Error,
    forward::ForwardRequest,
    response::Response,
    response::status::Status,
}; // use
use crate::request_rate::api::Api;
use log::{info, warn};

impl<'a> ForwardRequest<'a> {

    /// Performs the HTTP get request and returns the response to the caller.
    ///
    /// ## Arguments:
    ///
    /// This method accepts no arguments.

    pub fn get(&mut self) -> Result<Response, Error> {

        // Build the URI stem for the HTTP get request:

        const SERVICE_URI: &str = "https://maps.googleapis.com/maps/api/geocode";
        const OUTPUT_FORMAT: &str = "json"; // json or xml
        let mut uri = format!("{}/{}?", SERVICE_URI, OUTPUT_FORMAT);

        match &self.query {
            // If query string built, append it to the URL stem.
            Some(query) => uri.push_str(query.as_ref()),
            // If query string not built, return an error.
            None => return Err(Error::QueryNotBuilt),
        } // match

        self.client_settings.rate_limit.limit(Api::All);
        self.client_settings.rate_limit.limit(Api::Geocoding);

        info!("HTTP GET: {}", uri);

        // Initialize variables:
        let mut counter = 0;
        let mut wait_time_in_ms = 0;
        // Retries the get request until successful, an error ineligible for
        // retries is returned, or we have reached the maximum retries:
        loop {
            // Increment retry counter:
            counter += 1;
            // Query the Google Cloud Maps Platform using using an HTTP get
            // request, and return result to caller:
            let response = reqwest::blocking::get(&*uri);
            // Check response from the HTTP client:
            match response {
                Ok(response) => {
                    // HTTP client was successful getting a response from the
                    // server. Check the HTTP status code:
                    if response.status().is_success() {
                        // If the HTTP GET request was successful, deserialize
                        // the JSON response into a Rust data structure:
                        let deserialized: Response = serde_json::from_str(&response.text()?)?;
                        // If the response JSON was successfully parsed, check
                        // the Google API status before returning it to the
                        // caller:
                        if deserialized.status == Status::Ok {
                            // If Google's response was "Ok" return the
                            // struct deserialized from JSON:
                            return Ok(deserialized)
                        } else if
                            // Only Google's "Unknown Error" is eligible for
                            // retries
                            deserialized.status != Status::UnknownError ||
                            counter > self.client_settings.max_retries {
                                // If there is a Google API error other than
                                // "Unknown Error," return the error and do not
                                // retry the request. Also, if we have reached
                                // the maximum retry count, do not retry
                                // further:
                                let error = Error::GoogleMapsService(deserialized.status, deserialized.error_message);
                                warn!("{}", error);
                                return Err(error)
                        } // if
                    // We got a response from the server but it was not success:
                    } else if
                        !response.status().is_server_error() || // Only HTTP "500 Server Errors", and
                        response.status() != 429 || // HTTP "429 Too Many Requests" are eligible for retries.
                        counter > self.client_settings.max_retries {
                            // If the HTTP request error was not a 500 Server
                            // error or "429 Too Many Requests" error, return
                            // the error and do not retry the request. Also, if
                            // we have reached the maximum retry count, do not
                            // retry anymore:
                            warn!("HTTP client returned: `{}`.", response.status());
                            return Err(Error::HttpUnsuccessful(counter, response.status().to_string()))
                    } // if
                } // case
                Err(response) => {
                    // HTTP client did not get a response from the server:
                    warn!("HTTP client returned: `{}`", response);
                    if counter > self.client_settings.max_retries {
                        // If we have reached the maximum retry count, do not
                        // retry anymore. Return the last HTTP client error:
                        return Err(Error::Reqwest(response))
                    } // if
                } // case
            }; // match

            // Truncated exponential backoff is a standard error handling
            // strategy for network applications in which a client periodically
            // retries a failed request with increasing delays between requests.
            if wait_time_in_ms < self.client_settings.max_backoff {
                // Wait Time = 2^N + Random
                //
                // A random number of milliseconds less than or equal to 255.
                // This helps to avoid cases where many clients get synchronized
                // by some situation and all retry at once, sending requests in
                // synchronized waves.
                wait_time_in_ms = 2_u32.pow(counter as u32) + (rand::random::<u8>() as u32);
                // Maximum backoff is typically 32 or 64 seconds. The
                // appropriate value depends on the use case.
                //
                // It's okay to continue retrying once you reach the
                // max_backoff time. Retries after this point do not need to
                // continue increasing backoff time. For example, if a client
                // uses an max_backoff time of 64 seconds, then after
                // reaching this value, the client can retry every 64 seconds.
                // At some point, clients should be prevented from retrying
                // infinitely.
                if wait_time_in_ms > self.client_settings.max_backoff {
                    wait_time_in_ms = self.client_settings.max_backoff
                } // if
            } // if

            info!("Could not successfully query the Google Maps Platform. Sleeping for {} milliseconds before retry #{} of {}.", wait_time_in_ms, counter, self.client_settings.max_retries);

            std::thread::sleep(std::time::Duration::from_millis(wait_time_in_ms as u64));

        }; // loop

    } // fn

} // impl