| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package IO::Iron::Connector; | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | ## no critic (Documentation::RequirePodAtEnd) | 
| 4 |  |  |  |  |  |  | ## no critic (Documentation::RequirePodSections) | 
| 5 |  |  |  |  |  |  | ## no critic (RegularExpressions::RequireExtendedFormatting) | 
| 6 |  |  |  |  |  |  | ## no critic (RegularExpressions::RequireLineBoundaryMatching) | 
| 7 |  |  |  |  |  |  | ## no critic (RegularExpressions::ProhibitEscapedMetacharacters) | 
| 8 |  |  |  |  |  |  |  | 
| 9 | 3 |  |  | 3 |  | 55 | use 5.010_000; | 
|  | 3 |  |  |  |  | 11 |  | 
| 10 | 3 |  |  | 3 |  | 25 | use strict; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 72 |  | 
| 11 | 3 |  |  | 3 |  | 15 | use warnings; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 111 |  | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | # Global creator | 
| 14 | 3 |  |  |  |  | 247 | BEGIN { | 
| 15 | 3 |  |  | 3 |  | 18 | use parent qw( IO::Iron::ConnectorBase );    # Inheritance | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 119 |  | 
| 16 |  |  |  |  |  |  | } | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | # Global destructor | 
| 19 |  |  |  | 3 |  |  | END { | 
| 20 |  |  |  |  |  |  | } | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | # ABSTRACT: REST API Connector, HTTP interface class. | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | our $VERSION = '0.14'; # VERSION: generated by DZP::OurPkgVersion | 
| 25 |  |  |  |  |  |  |  | 
| 26 | 3 |  |  | 3 |  | 10 | use Log::Any qw{$log}; | 
|  | 3 |  |  |  |  | 20 |  | 
|  | 3 |  |  |  |  | 1171 |  | 
| 27 |  |  |  |  |  |  | require JSON::MaybeXS; | 
| 28 | 3 |  |  | 3 |  | 746 | use Data::UUID (); | 
|  | 3 |  |  |  |  | 81 |  | 
|  | 3 |  |  |  |  | 16 |  | 
| 29 | 3 |  |  | 3 |  | 48 | use Hash::Util 0.06 qw{lock_keys lock_keys_plus unlock_keys legal_keys}; | 
|  | 3 |  |  |  |  | 16 |  | 
|  | 3 |  |  |  |  | 201 |  | 
| 30 | 3 |  |  | 3 |  | 6 | use Carp::Assert; | 
|  | 3 |  |  |  |  | 18 |  | 
|  | 3 |  |  |  |  | 341 |  | 
| 31 | 3 |  |  | 3 |  | 16 | use Carp::Assert::More; | 
|  | 3 |  |  |  |  | 557 |  | 
|  | 3 |  |  |  |  | 22 |  | 
| 32 | 3 |  |  | 3 |  | 8 | use Carp; | 
|  | 3 |  |  |  |  | 188 |  | 
|  | 3 |  |  |  |  | 23 |  | 
| 33 | 3 |  |  | 3 |  | 7 | use English '-no_match_vars'; | 
|  | 3 |  |  |  |  | 31 |  | 
|  | 3 |  |  |  |  | 3152 |  | 
| 34 | 3 |  |  | 3 |  | 164301 | use REST::Client (); | 
|  | 3 |  |  |  |  | 122 |  | 
|  | 3 |  |  |  |  | 25 |  | 
| 35 | 3 |  |  | 3 |  | 5 | use URI::Escape  qw{uri_escape_utf8}; | 
|  | 3 |  |  |  |  | 159 |  | 
|  | 3 |  |  |  |  | 22 |  | 
| 36 | 3 |  |  | 3 |  | 9 | use Try::Tiny; | 
|  | 3 |  |  |  |  | 147 |  | 
|  | 3 |  |  |  |  | 19 |  | 
| 37 | 3 |  |  | 3 |  | 7 | use Scalar::Util qw{blessed looks_like_number}; | 
|  | 3 |  |  |  |  | 156 |  | 
|  | 3 |  |  |  |  | 609 |  | 
| 38 |  |  |  |  |  |  | use Exception::Class ( | 
| 39 | 3 |  |  |  |  | 1334 | 'IronHTTPCallException' => { | 
| 40 |  |  |  |  |  |  | fields => [ 'status_code', 'response_message' ], | 
| 41 |  |  |  |  |  |  | } | 
| 42 | 3 |  |  | 3 |  | 9270 | ); | 
|  | 3 |  |  |  |  | 76 |  | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | # CONSTANTS | 
| 45 |  |  |  |  |  |  |  | 
| 46 | 3 |  |  | 3 |  | 8 | use Const::Fast; | 
|  | 3 |  |  |  |  | 40 |  | 
|  | 3 |  |  |  |  | 9 |  | 
| 47 |  |  |  |  |  |  | const my $HTTP_CODE_OK_MIN              => 200; | 
| 48 |  |  |  |  |  |  | const my $HTTP_CODE_OK_MAX              => 299; | 
| 49 |  |  |  |  |  |  | const my $HTTP_CODE_SERVICE_UNAVAILABLE => 503; | 
| 50 |  |  |  |  |  |  | const my $HTTP_CONTENT_TYPE_JSON        => q{application/json; charset=utf-8}; | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | sub new { | 
| 53 | 3 |  |  | 3 | 1 | 19 | my ($class) = @_; | 
| 54 | 3 |  |  |  |  | 268 | $log->tracef( 'Entering new(%s)', $class ); | 
| 55 | 3 |  |  |  |  | 7 | my $self = IO::Iron::ConnectorBase->new(); | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | # Add more keys to the self hash. | 
| 58 |  |  |  |  |  |  | my @self_keys = ( | 
| 59 |  |  |  |  |  |  | 'client',           # REST client timeout (for REST calls accessing Iron services). | 
| 60 |  |  |  |  |  |  | 'mime_boundary',    # The boundary string separating parts in multipart REST messages. | 
| 61 | 3 |  |  |  |  | 16 | legal_keys( %{$self} ), | 
|  | 3 |  |  |  |  | 24 |  | 
| 62 |  |  |  |  |  |  | ); | 
| 63 | 3 |  |  |  |  | 14 | unlock_keys( %{$self} ); | 
|  | 3 |  |  |  |  | 29 |  | 
| 64 | 3 |  |  |  |  | 5 | bless $self, $class; | 
| 65 | 3 |  |  |  |  | 17 | lock_keys( %{$self}, @self_keys ); | 
|  | 3 |  |  |  |  | 132 |  | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | # Set up REST client | 
| 68 | 3 |  |  |  |  | 16572 | my $client = REST::Client->new(); | 
| 69 | 3 |  |  |  |  | 984 | $self->{'client'} = $client; | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | # Create MIME multipart message boundary string | 
| 72 | 3 |  |  |  |  | 681 | my $ug    = Data::UUID->new(); | 
| 73 | 3 |  |  |  |  | 50 | my $uuid1 = $ug->create(); | 
| 74 | 3 |  |  |  |  | 41 | $self->{'mime_boundary'} = 'MIME_BOUNDARY_' . ( substr $ug->to_string($uuid1), 1, 20 );    ## no critic (ValuesAndExpressions::ProhibitMagicNumbers) | 
| 75 |  |  |  |  |  |  |  | 
| 76 | 3 |  |  |  |  | 192 | $log->infof('Iron Connector created with REST::Client as HTTP user agent.'); | 
| 77 | 3 |  |  |  |  | 44559 | $log->tracef( 'Exiting new: %s', $self ); | 
| 78 | 0 |  |  |  |  |  | return $self; | 
| 79 |  |  |  |  |  |  | } | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | # TODO check why previous message (coded content) is in the next message! | 
| 82 |  |  |  |  |  |  | sub perform_iron_action {    ## no critic (Subroutines::ProhibitExcessComplexity) | 
| 83 | 0 |  |  | 0 | 1 |  | my ( $self, $iron_action, $params ) = @_; | 
| 84 | 0 | 0 |  |  |  |  | if ( !defined $params ) { | 
| 85 | 0 |  |  |  |  |  | $params = {}; | 
| 86 |  |  |  |  |  |  | } | 
| 87 | 0 |  |  |  |  |  | $log->tracef( 'Entering Connector:perform_iron_action(%s, %s)', $iron_action, $params ); | 
| 88 |  |  |  |  |  |  |  | 
| 89 | 0 |  |  |  |  |  | my $href         = $iron_action->{'href'}; | 
| 90 | 0 |  |  |  |  |  | my $action_verb  = $iron_action->{'action'}; | 
| 91 | 0 |  |  |  |  |  | my $return_type  = $iron_action->{'return'}; | 
| 92 | 0 |  |  |  |  |  | my $retry        = $iron_action->{'retry'}; | 
| 93 | 0 |  |  |  |  |  | my $require_body = $iron_action->{'require_body'}; | 
| 94 | 0 | 0 |  |  |  |  | my $paged        = $iron_action->{'paged'}    ? $iron_action->{'paged'}    : 0; | 
| 95 | 0 | 0 |  |  |  |  | my $per_page     = $iron_action->{'per_page'} ? $iron_action->{'per_page'} : 0; | 
| 96 | 0 |  |  |  |  |  | my $url_params   = q{}; | 
| 97 |  |  |  |  |  |  |  | 
| 98 | 0 | 0 | 0 |  |  |  | if ( exists $iron_action->{'url_params'} && ref $iron_action->{'url_params'} eq 'HASH' ) { | 
| 99 | 0 |  |  |  |  |  | foreach ( keys %{ $iron_action->{'url_params'} } ) { | 
|  | 0 |  |  |  |  |  |  | 
| 100 | 0 |  |  |  |  |  | $log->tracef( 'perform_iron_action(): url_param:%s', $_ ); | 
| 101 | 0 | 0 |  |  |  |  | if ( $params->{ '{' . $_ . '}' } ) { | 
| 102 | 0 |  |  |  |  |  | $url_params .= "$_={$_}&"; | 
| 103 |  |  |  |  |  |  | } | 
| 104 |  |  |  |  |  |  | } | 
| 105 | 0 |  |  |  |  |  | $url_params = substr $url_params, 0, ( length $url_params ) - 1; | 
| 106 |  |  |  |  |  |  | } | 
| 107 | 0 | 0 |  |  |  |  | if ($url_params) { | 
| 108 | 0 |  |  |  |  |  | $href .= ( q{?} . $url_params ); | 
| 109 |  |  |  |  |  |  | } | 
| 110 | 0 |  |  |  |  |  | my $content_type = $iron_action->{'content_type'}; | 
| 111 | 0 |  |  |  |  |  | $params->{'content_type'} = $content_type; | 
| 112 | 0 |  |  |  |  |  | $params->{'return_type'}  = $return_type; | 
| 113 | 0 |  |  |  |  |  | $log->tracef( 'href before value substitution:\'%s\'.', $href ); | 
| 114 | 0 |  |  |  |  |  | foreach my $value_key ( sort keys %{$params} ) { | 
|  | 0 |  |  |  |  |  |  | 
| 115 | 0 |  |  |  |  |  | my $value = $params->{$value_key}; | 
| 116 | 0 |  |  |  |  |  | $log->tracef( 'Param key:%s; value=%s;', $value_key, $value ); | 
| 117 | 0 |  |  |  |  |  | $href =~ s/$value_key/$value/gs; | 
| 118 |  |  |  |  |  |  | } | 
| 119 | 0 |  |  |  |  |  | $log->tracef( 'href after value substitution:\'%s\'.', $href ); | 
| 120 |  |  |  |  |  |  |  | 
| 121 | 0 |  |  |  |  |  | my ( $http_status_code, $returned_msg ); | 
| 122 | 0 |  |  |  |  |  | my $keep_on_trying = 1; | 
| 123 | 0 |  |  |  |  |  | while ($keep_on_trying) { | 
| 124 | 0 |  |  |  |  |  | $keep_on_trying = 0; | 
| 125 |  |  |  |  |  |  | try { | 
| 126 |  |  |  |  |  |  | assert(  ( $require_body == 1 && defined $params->{'body'} && ref $params->{'body'} eq 'HASH' ) | 
| 127 | 0 |  | 0 | 0 |  |  | || ( $require_body == 0 && !defined $params->{'body'} ) ); | 
| 128 | 0 |  |  |  |  |  | assert_in( | 
| 129 |  |  |  |  |  |  | $action_verb, | 
| 130 |  |  |  |  |  |  | [ 'GET', 'PATCH', 'PUT', 'POST', 'DELETE', 'OPTIONS', 'HEAD' ], | 
| 131 |  |  |  |  |  |  | 'action_verb is a valid HTTP verb.' | 
| 132 |  |  |  |  |  |  | ); | 
| 133 | 0 |  |  |  |  |  | assert_nonblank( $params->{'{Protocol}'},          'params->{Protocol} is defined and not blank.' ); | 
| 134 | 0 |  |  |  |  |  | assert_nonblank( $params->{'{Port}'},              'params->{Port} is defined and not blank.' ); | 
| 135 | 0 |  |  |  |  |  | assert_nonblank( $params->{'{Host}'},              'params->{Host} is defined and not blank.' ); | 
| 136 | 0 |  |  |  |  |  | assert_nonblank( $params->{'{Project ID}'},        'params->{Project ID} is defined and not blank.' ); | 
| 137 | 0 |  |  |  |  |  | assert_nonblank( $params->{'{API Version}'},       'params->{API Version} is defined and not blank.' ); | 
| 138 | 0 |  |  |  |  |  | assert_nonblank( $params->{'authorization_token'}, 'params->{authorization_token} is defined and not blank.' ); | 
| 139 | 0 |  |  |  |  |  | assert_nonblank( $params->{'http_client_timeout'}, 'params->{http_client_timeout} is defined and not blank.' ); | 
| 140 | 0 | 0 |  |  |  |  | my $url_escape_these_fields = defined $iron_action->{'url_escape'} ? $iron_action->{'url_escape'} : {}; | 
| 141 |  |  |  |  |  |  |  | 
| 142 | 0 |  |  |  |  |  | foreach my $field_name ( keys %{$url_escape_these_fields} ) { | 
|  | 0 |  |  |  |  |  |  | 
| 143 | 0 | 0 |  |  |  |  | if ( defined $params->{$field_name} ) { | 
| 144 | 0 |  |  |  |  |  | $params->{$field_name} = uri_escape_utf8( $params->{$field_name} ); | 
| 145 |  |  |  |  |  |  | } | 
| 146 |  |  |  |  |  |  | } | 
| 147 |  |  |  |  |  |  | # | 
| 148 | 0 | 0 |  |  |  |  | if ($paged) { | 
| 149 | 0 |  |  |  |  |  | $log->debugf('A paged query.'); | 
| 150 | 0 |  |  |  |  |  | my @returned_msgs; | 
| 151 | 0 |  |  |  |  |  | my ( $http_status_code_temp, $returned_msg_temp ); | 
| 152 | 0 |  |  |  |  |  | my $page_number = 0; | 
| 153 | 0 |  |  |  |  |  | while (1) { | 
| 154 | 0 |  |  |  |  |  | my $page_href = $href; | 
| 155 | 0 |  |  |  |  |  | $log->debugf( 'A paged query. Href:\'%s\'', $page_href ); | 
| 156 | 0 | 0 |  |  |  |  | $page_href .= ( $href =~ /\?/gsx ? q{&} : q{?} ) . 'per_page=' . $per_page . '&page=' . $page_number; | 
| 157 | 0 |  |  |  |  |  | ( $http_status_code_temp, $returned_msg_temp ) = | 
| 158 |  |  |  |  |  |  | $self->perform_http_action( $action_verb, $page_href, $params ); | 
| 159 | 0 |  |  |  |  |  | my $return_list = $returned_msg_temp; | 
| 160 | 0 |  |  |  |  |  | my ( $return_type_def, $list_hash_key ) = ( split m/:/s, $return_type ); | 
| 161 | 0 | 0 | 0 |  |  |  | $return_list = $returned_msg_temp->{$list_hash_key} | 
| 162 |  |  |  |  |  |  | if $return_type_def eq 'LIST' && defined $list_hash_key;    ## no critic (ControlStructures::ProhibitPostfixControls) | 
| 163 | 0 |  |  |  |  |  | push @returned_msgs, @{$return_list}; | 
|  | 0 |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  |  | 
| 165 | 0 | 0 | 0 |  |  |  | if ( scalar @{$return_list} == 0 || @{$return_list} < $per_page ) { | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 166 | 0 |  |  |  |  |  | last; | 
| 167 |  |  |  |  |  |  | } | 
| 168 | 0 |  |  |  |  |  | $page_number++; | 
| 169 |  |  |  |  |  |  | } | 
| 170 | 0 |  |  |  |  |  | $http_status_code = $http_status_code_temp; | 
| 171 | 0 |  |  |  |  |  | $returned_msg     = \@returned_msgs; | 
| 172 |  |  |  |  |  |  | } | 
| 173 |  |  |  |  |  |  | else { | 
| 174 | 0 |  |  |  |  |  | ( $http_status_code, $returned_msg ) = $self->perform_http_action( $action_verb, $href, $params ); | 
| 175 |  |  |  |  |  |  | } | 
| 176 |  |  |  |  |  |  | } | 
| 177 |  |  |  |  |  |  | catch { | 
| 178 | 0 |  |  | 0 |  |  | $log->debugf( 'perform_iron_action(): Caught exception:\'%s\'.', $_ ); | 
| 179 | 0 | 0 | 0 |  |  |  | croak $_ unless blessed $_ && $_->can('rethrow');    ## no critic (ControlStructures::ProhibitPostfixControls) | 
| 180 | 0 | 0 |  |  |  |  | if ( $_->isa('IronHTTPCallException') ) { | 
| 181 | 0 | 0 |  |  |  |  | if ( $_->status_code == $HTTP_CODE_SERVICE_UNAVAILABLE ) { | 
| 182 |  |  |  |  |  |  |  | 
| 183 |  |  |  |  |  |  | # 503 Service Unavailable. Clients should implement exponential backoff to retry the request. | 
| 184 | 0 | 0 |  |  |  |  | $keep_on_trying = 1 if ( $retry == 1 );    ## no critic (ControlStructures::ProhibitPostfixControls) | 
| 185 |  |  |  |  |  |  | # TODO Fix this temporary solution for backoff to retry the request. | 
| 186 |  |  |  |  |  |  | } | 
| 187 |  |  |  |  |  |  | else { | 
| 188 | 0 |  |  |  |  |  | $log->debugf( 'perform_iron_action(): rethrow the exception.', $_ ); | 
| 189 | 0 |  |  |  |  |  | $_->rethrow; | 
| 190 |  |  |  |  |  |  | } | 
| 191 |  |  |  |  |  |  | } | 
| 192 |  |  |  |  |  |  | else { | 
| 193 | 0 |  |  |  |  |  | $_->rethrow; | 
| 194 |  |  |  |  |  |  | } | 
| 195 | 0 |  |  |  |  |  | }; | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | # Module::Pluggable here? | 
| 198 |  |  |  |  |  |  | } | 
| 199 | 0 |  |  |  |  |  | $log->tracef( 'Exiting Connector:perform_iron_action(): %s', $returned_msg ); | 
| 200 | 0 |  |  |  |  |  | return $http_status_code, $returned_msg; | 
| 201 |  |  |  |  |  |  | } | 
| 202 |  |  |  |  |  |  |  | 
| 203 |  |  |  |  |  |  | sub perform_http_action { | 
| 204 | 0 |  |  | 0 | 1 |  | my ( $self, $action_verb, $href, $params ) = @_; | 
| 205 | 0 |  |  |  |  |  | my $client = $self->{'client'}; | 
| 206 | 0 |  |  |  |  |  | my $json   = JSON::MaybeXS->new( utf8 => 1, pretty => 1 ); | 
| 207 |  |  |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | # TODO assert href is URL | 
| 209 | 0 |  |  |  |  |  | assert_in( $action_verb, [ 'GET', 'PATCH', 'PUT', 'POST', 'DELETE', 'OPTIONS', 'HEAD' ], 'action_verb is a valid HTTP verb.' ); | 
| 210 | 0 |  |  |  |  |  | assert_exists( | 
| 211 |  |  |  |  |  |  | $params, | 
| 212 |  |  |  |  |  |  | [ 'http_client_timeout', 'authorization_token' ], | 
| 213 |  |  |  |  |  |  | 'params contains items http_client_timeout and authorization_token.' | 
| 214 |  |  |  |  |  |  | ); | 
| 215 | 0 |  |  |  |  |  | assert_integer( $params->{'http_client_timeout'}, 'params->{\'http_client_timeout\'} is integer.' ); | 
| 216 | 0 |  |  |  |  |  | assert_nonblank( $params->{'authorization_token'}, 'params->{\'authorization_token\'} is a non-blank string.' ); | 
| 217 | 0 |  |  |  |  |  | $log->tracef( 'Entering Connector:perform_http_action(%s, %s, %s)', $action_verb, $href, $params ); | 
| 218 |  |  |  |  |  |  | # | 
| 219 |  |  |  |  |  |  | # HTTP request attributes | 
| 220 | 0 |  |  |  |  |  | my $timeout = $params->{'http_client_timeout'}; | 
| 221 | 0 |  |  |  |  |  | my $request_body; | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | # Headers | 
| 224 | 0 | 0 |  |  |  |  | my $content_type  = defined( $params->{'content_type'} ) ? $params->{'content_type'} : $HTTP_CONTENT_TYPE_JSON; | 
| 225 | 0 |  |  |  |  |  | my $authorization = 'OAuth ' . $params->{'authorization_token'}; | 
| 226 |  |  |  |  |  |  | # | 
| 227 | 0 | 0 |  |  |  |  | if ( $content_type =~ /multipart/is ) { | 
| 228 | 0 | 0 |  |  |  |  | my $body_content = $params->{'body'} ? $params->{'body'} : {};    # Else use an empty hash for body. | 
| 229 | 0 |  |  |  |  |  | my $file_as_zip  = $params->{'body'}->{'file'}; | 
| 230 | 0 |  |  |  |  |  | delete $params->{'body'}->{'file'}; | 
| 231 | 0 |  |  |  |  |  | my $encoded_body_content = $json->encode($body_content); | 
| 232 | 0 |  |  |  |  |  | my $boundary             = $self->{'mime_boundary'}; | 
| 233 | 0 |  |  |  |  |  | $content_type = "multipart/form-data; boundary=$boundary"; | 
| 234 | 0 |  |  |  |  |  | my $file_name = $params->{'body'}->{'file_name'} . '.zip'; | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | #$request_body = 'MIME-Version: 1.0' . "\n"; | 
| 237 |  |  |  |  |  |  | #$request_body .= 'Content-Length: ' . $req_content_length . "\n"; | 
| 238 |  |  |  |  |  |  | #$request_body .= 'Content-Type: ' . $req_content_type . "\n"; | 
| 239 | 0 |  |  |  |  |  | $request_body = q{--} . $boundary . "\n"; | 
| 240 | 0 |  |  |  |  |  | $request_body .= 'Content-Disposition: ' . 'form-data; name="data"' . "\n"; | 
| 241 | 0 |  |  |  |  |  | $request_body .= 'Content-Type: ' . 'text/plain; charset=utf-8' . "\n"; | 
| 242 | 0 |  |  |  |  |  | $request_body .= "\n"; | 
| 243 | 0 |  |  |  |  |  | $request_body .= $encoded_body_content . "\n"; | 
| 244 | 0 |  |  |  |  |  | $request_body .= "\n"; | 
| 245 | 0 |  |  |  |  |  | $request_body .= q{--} . $boundary . "\n"; | 
| 246 | 0 |  |  |  |  |  | $request_body .= 'Content-Disposition: ' . 'form-data; name="file"; filename="' . $file_name . q{"} . "\n"; | 
| 247 | 0 |  |  |  |  |  | $request_body .= 'Content-Type: ' . 'application/zip' . "\n"; | 
| 248 | 0 |  |  |  |  |  | $request_body .= 'Content-Transfer-Encoding: base64' . "\n"; | 
| 249 | 0 |  |  |  |  |  | $request_body .= "\n"; | 
| 250 | 0 |  |  |  |  |  | $request_body .= $file_as_zip . "\n"; | 
| 251 | 0 |  |  |  |  |  | $request_body .= q{--} . $boundary . q{--} . "\n"; | 
| 252 |  |  |  |  |  |  | } | 
| 253 |  |  |  |  |  |  | else { | 
| 254 | 0 | 0 |  |  |  |  | my $body_content = $params->{'body'} ? $params->{'body'} : {};    # Else use an empty hash for body. | 
| 255 | 0 |  |  |  |  |  | $log->debugf( 'About to jsonize the body:\'%s\'', $body_content ); | 
| 256 | 0 |  |  |  |  |  | foreach ( keys %{$body_content} ) { | 
|  | 0 |  |  |  |  |  |  | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | # Gimmick to ensure the proper jsonization of numbers | 
| 259 |  |  |  |  |  |  | # Otherwise numbers might end up as strings. | 
| 260 | 0 | 0 |  |  |  |  | $body_content->{$_} += 0 if looks_like_number $body_content->{$_};    ## no critic (ControlStructures::ProhibitPostfixControls) | 
| 261 |  |  |  |  |  |  | } | 
| 262 | 0 |  |  |  |  |  | my $encoded_body_content = $json->encode($body_content); | 
| 263 | 0 |  |  |  |  |  | $log->debugf( 'Jsonized body:\'%s\'', $encoded_body_content ); | 
| 264 | 0 |  |  |  |  |  | $request_body = $encoded_body_content; | 
| 265 |  |  |  |  |  |  | } | 
| 266 | 0 |  |  |  |  |  | $client->setTimeout($timeout); | 
| 267 | 0 |  |  |  |  |  | $log->tracef( 'client: %s; action=%s; href=%s;', $client, $action_verb, $href ); | 
| 268 | 0 |  |  |  |  |  | $log->debugf( 'REST Request: [verb=%s; href=%s; body=%s; Headers: Content-Type=%s; Authorization=%s]', | 
| 269 |  |  |  |  |  |  | $action_verb, $href, $request_body, $content_type, $authorization ); | 
| 270 | 0 |  |  |  |  |  | $client->request( | 
| 271 |  |  |  |  |  |  | $action_verb, | 
| 272 |  |  |  |  |  |  | $href, | 
| 273 |  |  |  |  |  |  | $request_body, | 
| 274 |  |  |  |  |  |  | { | 
| 275 |  |  |  |  |  |  | 'Content-Type'  => $content_type, | 
| 276 |  |  |  |  |  |  | 'Authorization' => $authorization, | 
| 277 |  |  |  |  |  |  | } | 
| 278 |  |  |  |  |  |  | ); | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  | # RETURN: | 
| 281 | 0 |  |  |  |  |  | $log->debugf( 'Returned HTTP response code:%s', $client->responseCode() ); | 
| 282 | 0 |  |  |  |  |  | $log->tracef( 'Returned HTTP response:%s', $client->responseContent() ); | 
| 283 | 0 | 0 | 0 |  |  |  | if ( $client->responseCode() >= $HTTP_CODE_OK_MIN && $client->responseCode() <= $HTTP_CODE_OK_MAX ) { | 
| 284 |  |  |  |  |  |  |  | 
| 285 |  |  |  |  |  |  | # 200 OK: Successful GET; 201 Created: Successful POST | 
| 286 | 0 |  |  |  |  |  | $log->tracef( 'HTTP Response code: %d, %s', $client->responseCode(), 'Successful!' ); | 
| 287 | 0 |  |  |  |  |  | my $decoded_body_content; | 
| 288 | 0 | 0 | 0 |  |  |  | if ( defined $params->{'return_type'} && $params->{'return_type'} eq 'BINARY' ) { | 
|  |  | 0 | 0 |  |  |  |  | 
| 289 | 0 |  |  |  |  |  | $log->tracef( 'Returned HTTP response header Content-Disposition:%s', $client->responseHeader('Content-Disposition') ); | 
| 290 | 0 |  |  |  |  |  | my $filename; | 
| 291 | 0 | 0 |  |  |  |  | if ( $client->responseHeader('Content-Disposition') =~ /filename=(.+)$/s ) { | 
| 292 | 0 | 0 |  |  |  |  | $filename = $1 ? $1 : '[Unknown filename]'; | 
| 293 |  |  |  |  |  |  | } | 
| 294 | 0 |  |  |  |  |  | $decoded_body_content = { 'file' => $client->responseContent(), 'file_name' => $filename }; | 
| 295 |  |  |  |  |  |  | } | 
| 296 |  |  |  |  |  |  | elsif ( defined $params->{'return_type'} && $params->{'return_type'} eq 'PLAIN_TEXT' ) { | 
| 297 | 0 |  |  |  |  |  | $decoded_body_content = $client->responseContent(); | 
| 298 |  |  |  |  |  |  | } | 
| 299 |  |  |  |  |  |  | else { | 
| 300 | 0 |  |  |  |  |  | $decoded_body_content = $json->decode( $client->responseContent() ); | 
| 301 |  |  |  |  |  |  | } | 
| 302 | 0 |  |  |  |  |  | $log->tracef( 'Exiting Connector:perform_http_action(): %s, %s', $client->responseCode(), $decoded_body_content ); | 
| 303 | 0 |  |  |  |  |  | return $client->responseCode(), $decoded_body_content; | 
| 304 |  |  |  |  |  |  | } | 
| 305 |  |  |  |  |  |  | else { | 
| 306 | 0 |  |  |  |  |  | $log->tracef( 'HTTP Response code: %d, %s', $client->responseCode(), 'Failure!' ); | 
| 307 | 0 |  |  |  |  |  | my $decoded_body_content; | 
| 308 |  |  |  |  |  |  | try { | 
| 309 | 0 |  |  | 0 |  |  | $decoded_body_content = $json->decode( $client->responseContent() ); | 
| 310 | 0 |  |  |  |  |  | }; | 
| 311 | 0 | 0 |  |  |  |  | my $response_message = $decoded_body_content ? $decoded_body_content->{'msg'} : $client->responseContent(); | 
| 312 | 0 |  |  |  |  |  | $log->tracef( | 
| 313 |  |  |  |  |  |  | 'Throwing exception in perform_http_action(): status_code=%s, response_message=%s', | 
| 314 |  |  |  |  |  |  | $client->responseCode(), | 
| 315 |  |  |  |  |  |  | $response_message | 
| 316 |  |  |  |  |  |  | ); | 
| 317 | 0 |  |  |  |  |  | IronHTTPCallException->throw( | 
| 318 |  |  |  |  |  |  | status_code      => $client->responseCode(), | 
| 319 |  |  |  |  |  |  | response_message => $response_message, | 
| 320 |  |  |  |  |  |  | error => 'IronHTTPCallException: status_code=' . $client->responseCode() . ' response_message=' . $response_message, | 
| 321 |  |  |  |  |  |  | ); | 
| 322 |  |  |  |  |  |  | } | 
| 323 |  |  |  |  |  |  | return;    # Control does not reach this point. | 
| 324 |  |  |  |  |  |  | } | 
| 325 |  |  |  |  |  |  |  | 
| 326 |  |  |  |  |  |  | 1; | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | __END__ | 
| 329 |  |  |  |  |  |  |  | 
| 330 |  |  |  |  |  |  | =pod | 
| 331 |  |  |  |  |  |  |  | 
| 332 |  |  |  |  |  |  | =encoding UTF-8 | 
| 333 |  |  |  |  |  |  |  | 
| 334 |  |  |  |  |  |  | =head1 NAME | 
| 335 |  |  |  |  |  |  |  | 
| 336 |  |  |  |  |  |  | IO::Iron::Connector - REST API Connector, HTTP interface class. | 
| 337 |  |  |  |  |  |  |  | 
| 338 |  |  |  |  |  |  | =head1 VERSION | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | version 0.14 | 
| 341 |  |  |  |  |  |  |  | 
| 342 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | This package is for internal use of IO::Iron packages. | 
| 345 |  |  |  |  |  |  |  | 
| 346 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | This class object handles the actual http traffic. Parameters are | 
| 349 |  |  |  |  |  |  | passed from the calling object (partly from API class) via Connection | 
| 350 |  |  |  |  |  |  | class object. This class can be mocked and replaced when | 
| 351 |  |  |  |  |  |  | the client objects are created. | 
| 352 |  |  |  |  |  |  |  | 
| 353 |  |  |  |  |  |  | =for stopwords API http Params params Mikko Koivunalho | 
| 354 |  |  |  |  |  |  |  | 
| 355 |  |  |  |  |  |  | =head1 SUBROUTINES/METHODS | 
| 356 |  |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  | =head2 new | 
| 358 |  |  |  |  |  |  |  | 
| 359 |  |  |  |  |  |  | Creator function. | 
| 360 |  |  |  |  |  |  |  | 
| 361 |  |  |  |  |  |  | =head2 perform_iron_action | 
| 362 |  |  |  |  |  |  |  | 
| 363 |  |  |  |  |  |  | =over 8 | 
| 364 |  |  |  |  |  |  |  | 
| 365 |  |  |  |  |  |  | =item Params: action name, params hash. | 
| 366 |  |  |  |  |  |  |  | 
| 367 |  |  |  |  |  |  | =item Return: 1/0 (1 if success, 0 in all failures), | 
| 368 |  |  |  |  |  |  | HTTP return code, hash if success/failed request. | 
| 369 |  |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | =back | 
| 371 |  |  |  |  |  |  |  | 
| 372 |  |  |  |  |  |  | =head2 perform_http_action | 
| 373 |  |  |  |  |  |  |  | 
| 374 |  |  |  |  |  |  | Do the actual "dirty work" of Internet connection. | 
| 375 |  |  |  |  |  |  | This routine is only accessed internally. | 
| 376 |  |  |  |  |  |  |  | 
| 377 |  |  |  |  |  |  | =head1 AUTHOR | 
| 378 |  |  |  |  |  |  |  | 
| 379 |  |  |  |  |  |  | Mikko Koivunalho <mikko.koivunalho@iki.fi> | 
| 380 |  |  |  |  |  |  |  | 
| 381 |  |  |  |  |  |  | =head1 BUGS | 
| 382 |  |  |  |  |  |  |  | 
| 383 |  |  |  |  |  |  | Please report any bugs or feature requests to bug-io-iron@rt.cpan.org or through the web interface at: | 
| 384 |  |  |  |  |  |  | http://rt.cpan.org/Public/Dist/Display.html?Name=IO-Iron | 
| 385 |  |  |  |  |  |  |  | 
| 386 |  |  |  |  |  |  | =head1 COPYRIGHT AND LICENSE | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | This software is copyright (c) 2023 by Mikko Koivunalho. | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | This is free software; you can redistribute it and/or modify it under | 
| 391 |  |  |  |  |  |  | the same terms as the Perl 5 programming language system itself. | 
| 392 |  |  |  |  |  |  |  | 
| 393 |  |  |  |  |  |  | The full text of the license can be found in the | 
| 394 |  |  |  |  |  |  | F<LICENSE> file included with this distribution. | 
| 395 |  |  |  |  |  |  |  | 
| 396 |  |  |  |  |  |  | =cut |