NAME

Restish::Client - A lightweight client for JSON REST APIs

VERSION

Version 1.10

SYNOPSIS

use Restish::Client;

my $client = Restish::Client->new(
    uri_host            => 'https://api.example.com/v1/',
    head_params_default => { Authorization => "Bearer $token" },
);

# GET /v1/users?status=active
my $users = $client->GET(
    uri          => 'users',
    query_params => { status => 'active' },
);

# POST a JSON body
my $user = $client->POST(
    uri         => 'users',
    body_params => { name => 'Alice', email => 'alice@example.com' },
);

# DELETE /v1/users/42, with the id escaped into the path
$client->DELETE(
    uri             => 'users/%(id)s',
    template_params => { id => $user->{id} },
);

die sprintf "Request failed (%s): %s\n",
    $client->response_code, $client->response_body
    unless $client->is_success;

DESCRIPTION

Restish::Client wraps LWP::UserAgent for APIs that speak JSON over HTTP. Give it a base URL once, then make requests with relative paths. Request bodies are encoded as JSON, JSON responses are decoded into Perl data, and the last response is kept so you can inspect its status, headers and body.

CONSTRUCTOR

new

my $client = Restish::Client->new(%options);

A client using a Vault token and a client certificate:

my $client = Restish::Client->new(
    uri_host            => 'https://vault.example.com/',
    head_params_default => { 'X-Vault-Token' => $token },
    require_https       => 1,
    agent_options       => { timeout => 10 },
    ssl_opts            => {
        SSL_cert_file => '/etc/ssl/certs/client.pem',
        SSL_key_file  => '/etc/ssl/private/client.key',
    },
);

Proxy settings are always read from the environment (https_proxy, no_proxy and so on).

MAKING REQUESTS

request

my $data = $client->request(
    method          => 'POST',
    uri             => 'users/%(id)s/keys',
    template_params => { id => 42 },
    query_params    => { notify => 1 },
    body_params     => { key => $public_key },
    head_params     => { 'X-Request-Id' => $request_id },
);

Send a request and return the response data.

An unknown argument name is fatal, so a typo such as query_param fails loudly instead of sending an unfiltered request.

Returns:

Invalid JSON in a successful response is fatal. Because a successful request can return a false value, check "is_success" when you need to be certain.

Uploading a file:

$client->POST(
    uri          => 'uploads',
    query_params => { filename => 'report.pdf' },
    raw_body     => $pdf_data,
    content_type => 'application/pdf',
);

GET, POST, PUT, PATCH, DELETE, LIST

my $user = $client->GET( uri => 'users/42' );

$client->PUT(
    uri         => 'users/42',
    body_params => { name => 'Bob' },
);

Shortcuts for "request" with method already set. They take the same arguments. LIST is a non-standard method used by some APIs, such as HashiCorp Vault.

thin_request

my $res = $client->thin_request($method, $uri, \%query, @lwp_args);

Send a request through the matching LWP::UserAgent method (get, post, put, patch or delete). Use it for endpoints that take form-encoded bodies instead of JSON.

Returns the decoded data if the response has a JSON Content-Type, otherwise the body as a string. Returns 0 if the request failed or the JSON could not be decoded.

# POST form fields
my $res = $client->thin_request('POST', 'public/auth', undef,
    { user => $user, pass => $pass });

# GET /servers?status=active
my $servers = $client->thin_request('GET', 'servers',
    { status => 'active' });

# GET with an extra header
my $res = $client->thin_request('GET', 'servers', undef,
    'X-Request-Id' => $id);

# PUT /servers/web1?notify=1 with form fields
$client->thin_request('PUT', 'servers/web1', { notify => 1 },
    { status => 'down' });

INSPECTING THE RESPONSE

The client keeps the response from the most recent request. Each method below returns undef until a request has been made.

is_success

if ($client->is_success) { ... }

True if the last response had a 2xx status.

response_code

my $status = $client->response_code;    # e.g. 404

The HTTP status code of the last response.

response_header

my $type = $client->response_header('Content-Type');

The value of one header from the last response.

response_body

warn "Error: ", $client->response_body unless $client->is_success;

The body of the last response as a decoded string. Useful for error details, since a failed "request" returns only 0.

ATTRIBUTES

Each attribute can be passed to "new" or changed later through its accessor. A change applies from the next request on.

head_params_default

$client->head_params_default({ 'X-Vault-Token' => $token });

Hashref of headers sent with every request. Setting it replaces the whole set. Accept: application/json is also sent, unless this hashref sets Accept itself.

ssl_opts

$client->ssl_opts({ SSL_ca_file => '/etc/ssl/certs/internal-ca.pem' });

Hashref passed to "ssl_opts" in LWP::UserAgent, such as a CA file or a client certificate.

$client->cookie_jar(1);                        # in memory
$client->cookie_jar('/var/tmp/cookies.txt');   # saved to a file

Keep cookies between requests. With a file path, cookies (including session cookies) are loaded from and saved to that file. The jar is created on the first request, so set this before making any.

debug

$client->debug({});                      # print everything
$client->debug({ trim_tokens => 1 });    # leave tokens out
$client->debug(undef);                   # off (the default)

When set to a hashref, "request" prints the agent's default headers, the request and the response to STDERR. With trim_tokens, the X-Auth-Token, X-Subject-Token and X-Vault-Token headers are left out. "thin_request" prints nothing.

ERRORS

Invalid arguments are fatal: a missing uri_host or uri, an unknown method or argument name, a non-hashref where a hashref is expected, or a path that does not form a valid URL. The client throws these with "croak" in Carp.

An HTTP error is not fatal. "request" and "thin_request" return 0, and the response methods tell you what went wrong.

SUBCLASSING

Restish::Client is a Moo class, so subclasses can use extends and method modifiers.

error

sub error {
    my ($self, $message) = @_;
    My::Exception->throw($message);
}

Called with the message for each error raised while making a request. The default croaks. Errors from constructor and attribute validation always croak.

_get_agent

package My::Client;
use Moo;
extends 'Restish::Client';

around _get_agent => sub {
    my ($orig, $self) = @_;
    my $ua = $self->$orig;
    $ua->agent('My::Client/1.0');
    return $ua;
};

Returns the LWP::UserAgent for a request. A new agent is built for each request from the client's attributes. Wrap it to add handlers or change settings.

TESTING

Set $Restish::Client::CANONICAL to 1 to encode body_params with sorted keys, so request bodies can be compared as strings in tests.

SEE ALSO

LWP::UserAgent, HTTP::Request::Common, Text::Sprintf::Named, Moo

BUGS

Please report bugs at https://github.com/thend20/perl-restish-client/issues.

AUTHOR

Tim H thend20@pair.com

LICENSE

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License, version 3, as published by the Free Software Foundation. See the LICENSE file distributed with it, or https://www.gnu.org/licenses/gpl-3.0.html.