Skip to content

Writing Your First Client

Andrew Yates edited this page Aug 20, 2014 · 5 revisions

Writing a REST client is easy. If you can query our ping service you can write a client to work against any endpoint. We will write a sample client in Perl to send a request to REST using HTTP::Tiny, JSON, look at the response headers from the service and make sure that we respect any rate limits or responses back from the server. To see the full client skip to the end of this page.

Starting Out

First we will setup a basic structure of a Perl script and import the relevant modules:

use strict;
use warnings;
use HTTP::Tiny;
use Time::HiRes qw/sleep/;
use JSON qw/decode_json/;

We have turned on Perl's strict mode (we need to declare variables) and turned on its warnings system. We also imported three modules. HTTP::Tiny is our HTTP communication library. JSON is a module for converting JSON strings into Perl data structures. Time::HiRes provides a sub-second sleep function.

Producing a URL and Headers

my $server = 'http://rest.ensembl.org/';
my $ping_endpoint = '/info/ping';
my $url = $server.$ping_endpoint;
my $headers = { accept => 'application/json' };

Here we have set a base server URL to point to the Ensembl REST server and decided we will query the ping endpoint. We have also created an anonymous hash to hold any HTTP headers we want to send to the server. We are going to send one which is the accept header with the JSON mime type. This tells the server we want JSON formatted data back.

Writing the Basic Request

sub rest_request {
  my ($url, $headers, $attempts) = @_;
  $attempts //= 0;
  my $http = HTTP::Tiny->new();
  my $response = $http->get($url, {headers => $headers});
  if($response->{success}) {
    my $content = $response->{content};
    my $json = decode_json($content);
    return $json;
  }
  # more to come to deal with bad requests
  return;
}

The basic structure of a requests involves creating an instance of our HTTP library, passing it both the URL and headers and then inspecting the response object to see if the request was a success. If it was then we grab the content, pass it into the JSON module using decode_json() and return the resulting data structure. Since we are using the ping endpoint we should expect the data structure { "ping" : 1 }.

Dealing with Failure

Face facts. At some point your requests will fail for a variety of reasons. Building some form of recovery and error handling is essential to a reliable client.

$attempts++;
my $reason = $response->{reason};
if($attempts > 3) {
  warn 'Failure with request '.$reason;
  die "Attempted to submit the URL $url more than 3 times without success";
}
my $response_code = $response->{status};
# we were rate limited
if($response_code == 429) {
  my $sleep_time = $response->{headers}->{'retry-after'};
  sleep($sleep_time);
  return rest_request($url, $headers, $attempts);
}

die "Cannot do request because of HTTP reason: '${reason}' (${response_code})";

When we encounter an unsuccessful request we can look at the HTTP response code and see if it is a 429 which means we have been rate limited. If we have been then we need to sleep for the time specified in the Retry-After header (this is REST attempting to help you out as much as possible). We then retry after the sleep and increment the number of attempts we have made. Once we exceed 3 attempts then our code will die. All other unsuccessful responses will result in death. You probably don't want this in your own code and should deal with bad requests gracefully.

Using the response

my $response = rest_request($url, $headers);
my $status = ($response->{ping}) ? 'up' : 'down';
print "Service is ${status}\n";

Finally we look at the hash's content and print an up/down message depending on the truth of the boolean.

The Full Client

use strict;
use warnings;
use HTTP::Tiny;
use Time::HiRes qw/sleep/;
use JSON qw/decode_json/;

my $server = 'http://rest.ensembl.org/';
my $ping_endpoint = '/info/ping';
my $url = $server.$ping_endpoint;
my $headers = { accept => 'application/json' };

my $response = rest_request($url, $headers);
my $status = ($response->{ping}) ? 'up' : 'down';
print "Service is ${status}\n";

sub rest_request {
  my ($url, $headers, $attempts) = @_;
  $attempts //= 0;
  my $http = HTTP::Tiny->new();
  my $response = $http->get($url, {headers => $headers});
  if($response->{success}) {
    my $content = $response->{content};
    my $json = decode_json($content);
    return $json;
  }
  $attempts++;
  my $reason = $response->{reason};
  if($attempts > 3) {
    warn 'Failure with request '.$reason;
    die "Attempted to submit the URL $url more than 3 times without success";
  }
  my $response_code = $response->{status};
  # we were rate limited
  if($response_code == 429) {
    my $sleep_time = $response->{headers}->{'retry-after'};
    sleep($sleep_time);
    return rest_request($url, $headers, $attempts);
  }

  die "Cannot do request because of HTTP reason: '${reason}' (${response_code})";
}