LP#1541559: HTTPClient: a utility for sending HTTP requests and handling responses
authorJeff Davis <jdavis@sitka.bclibraries.ca>
Fri, 20 Nov 2015 21:54:36 +0000 (13:54 -0800)
committerKathy Lussier <klussier@masslnc.org>
Mon, 20 Feb 2017 23:54:37 +0000 (18:54 -0500)
The intent of this package is to provide basic tools for communicating
with third-party APIs.  It is a dependency of the open-ils.ebook_api
service.

Signed-off-by: Jeff Davis <jdavis@sitka.bclibraries.ca>
Signed-off-by: Kathy Lussier <klussier@masslnc.org>
Open-ILS/examples/opensrf.xml.example
Open-ILS/src/perlmods/MANIFEST
Open-ILS/src/perlmods/lib/OpenILS/Utils/HTTPClient.pm [new file with mode: 0644]
Open-ILS/src/perlmods/t/14-OpenILS-Utils.t

index 9205229..dd128bd 100644 (file)
@@ -232,6 +232,39 @@ vim:et:ts=4:sw=4:
         instructions on mapping the old XML entries to database tables.
         -->
 
+        <http_client>
+            <!--
+            These settings are used by the OpenILS::Utils::HTTPClient module
+            when communicating with external services (e.g. third-party APIs)
+            over HTTP.  Values are passed along to LWP::UserAgent.
+            -->
+
+            <!-- custom useragent for HTTP requests
+            <useragent>Evergreen</useragent>
+            -->
+
+            <!-- default timeout value (in seconds) -->
+            <default_timeout>60</default_timeout>
+
+            <ssl_opts>
+                <!--
+                When using HTTPS, verify that the external server has a valid
+                SSL certificate matching the expected hostname.  (Set to 0 to
+                disable verification, 1 to enable it.)
+                -->
+                <verify_hostname>1</verify_hostname>
+
+                <!--
+                If verify_hostname is enabled, you may need to specify a path
+                for CA certificates installed on your system.  Use ONE of the
+                following settings.  See LWP::UserAgent docs for details.
+                <SSL_ca_path>/etc/ssl/certs</SSL_ca_path>
+                <SSL_ca_file>/etc/ssl/certs/ca-certificates.crt</SSL_ca_file>
+                -->
+            </ssl_opts>
+
+        </http_client>
+
         <added_content>
             <!-- load the OpenLibrary added content module -->
             <module>OpenILS::WWW::AddedContent::OpenLibrary</module>
index f8a77a4..216c40d 100644 (file)
@@ -132,6 +132,7 @@ lib/OpenILS/Utils/Cronscript.pm
 lib/OpenILS/Utils/Cronscript.pm.in
 lib/OpenILS/Utils/CStoreEditor.pm
 lib/OpenILS/Utils/Fieldmapper.pm
+lib/OpenILS/Utils/HTTPClient.pm
 lib/OpenILS/Utils/ISBN.pm
 lib/OpenILS/Utils/Lockfile.pm
 lib/OpenILS/Utils/MFHD.pm
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Utils/HTTPClient.pm b/Open-ILS/src/perlmods/lib/OpenILS/Utils/HTTPClient.pm
new file mode 100644 (file)
index 0000000..6474586
--- /dev/null
@@ -0,0 +1,131 @@
+package OpenILS::Utils::HTTPClient;
+
+use strict;
+use warnings;
+
+use OpenSRF::Utils::SettingsClient;
+use OpenSRF::Utils::Logger qw($logger);
+use OpenSRF::Utils::JSON;
+use LWP::UserAgent;
+use HTTP::Request;
+
+sub new {
+    my $class = shift;
+
+    my $self = {};
+    bless $self, $class;
+
+    $self->_initialize();
+
+    return $self;
+}
+
+sub _initialize {
+    my $self = shift;
+
+    # pull settings from opensrf.xml config
+    my $conf = OpenSRF::Utils::SettingsClient->new();
+    my $settings = $conf->config_value('http_client');
+
+    if ($settings->{useragent}) {
+        $self->{useragent} = $settings->{useragent};
+    }
+    if ($settings->{default_timeout}) {
+        $self->{default_timeout} = $settings->{default_timeout};
+    }
+
+    # SSL handling options. When communicating over HTTPS, LWP::UserAgent
+    # falls back to the environment variables whose values are set here.
+    # See LWP::UserAgent docs for details.
+    foreach my $opt (keys %{$settings->{ssl_opts}}) {
+        # check for a valid SSL cert?
+        if ($opt eq 'verify_hostname') {
+            $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = $settings->{ssl_opts}->{verify_hostname};
+        # path to directory for CA certificate files
+        } elsif ($opt eq 'SSL_ca_path') {
+            $ENV{PERL_LWP_SSL_CA_PATH} = $settings->{ssl_opts}->{SSL_ca_path};
+        # path to CA certificate file
+        } elsif ($opt eq 'SSL_ca_file') {
+            $ENV{PERL_LWP_SSL_CA_FILE} = $settings->{ssl_opts}->{SSL_ca_file};
+        }
+    }
+
+    return $self;
+}
+
+# request(): Send an HTTP request.
+#
+# Params:
+#   $method - HTTP method (GET, POST, PUT, DELETE)
+#   $uri - URI of resource to be requested
+#   $header - hashref containing HTTP headers
+#   $content - content of request
+#   $request_timeout - timeout value in seconds; defaults to 60s
+#   $useragent - user agent string; defaults to SameOrigin/1.0
+#
+# Returns an HTTP::Response object, or undef if the request failed/timed out.
+# Use $res->content to get response content.
+#
+sub request {
+    my ($self, $method, $uri, $headers, $content, $request_timeout, $useragent) = @_;
+    my $ua = new LWP::UserAgent;
+
+    $request_timeout = $request_timeout || $self->{default_timeout} || 60;
+    $ua->timeout($request_timeout);
+
+    $useragent = $useragent || $self->{useragent} || 'SameOrigin/1.0';
+    $ua->agent($useragent);
+
+    my $h = HTTP::Headers->new();
+    foreach my $k (keys %$headers) {
+        $h->header($k => $headers->{$k});
+    }
+
+    my $req = HTTP::Request->new(
+        $method,
+        $uri,
+        $h,
+        $content
+    );
+    my $res;
+
+    eval {
+        $logger->info("HTTPClient: sending HTTP $method request to $uri");
+        $res = $ua->request($req);
+    } or do {
+        $logger->info("HTTPClient: execution error");
+        return undef;
+    };
+
+    if ($res->status_line =~ /timeout/) {
+        $logger->info("HTTPClient: timeout error: " . $res->status_line);
+        return undef;
+    }
+
+    # TODO handle HTTP response status codes
+
+    return $res;
+}
+
+# Wrappers for request() using specific HTTP methods (GET, POST etc).
+sub get {
+    my $self = shift;
+    return $self->request('GET', @_);
+}
+
+sub post {
+    my $self = shift;
+    return $self->request('POST', @_);
+}
+
+sub put {
+    my $self = shift;
+    return $self->request('PUT', @_);
+}
+
+sub delete {
+    my $self = shift;
+    return $self->request('DELETE', @_);
+}
+
+1;
index 180686f..548bea3 100644 (file)
@@ -1,6 +1,6 @@
 #!perl -T
 
-use Test::More tests => 29;
+use Test::More tests => 30;
 use Test::Warn;
 use utf8;
 
@@ -20,6 +20,7 @@ use_ok( 'OpenILS::Utils::PermitHold' );
 use_ok( 'OpenILS::Utils::RemoteAccount' );
 use_ok( 'OpenILS::Utils::ZClient' );
 use_ok( 'OpenILS::Utils::EDIReader' );
+use_ok( 'OpenILS::Utils::HTTPClient' );
 
 # LP 800269 - Test MFHD holdings for records that only contain a caption field
 my $co_marc = MARC::Record->new();