-
Notifications
You must be signed in to change notification settings - Fork 56
/
HTTPClient.pm
170 lines (131 loc) · 4.13 KB
/
HTTPClient.pm
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package SyTest::HTTPClient;
use strict;
use warnings;
# A subclass of NaHTTP that stores a URI base, and has convenient JSON
# encoding/decoding wrapper methods
use Carp;
use base qw( Net::Async::HTTP );
Net::Async::HTTP->VERSION( '0.36' ); # PUT content bugfix
use JSON;
my $json = JSON->new->convert_blessed(1)->utf8(1);
use Future 0.33; # ->catch
use List::Util qw( any );
use Net::SSLeay 1.59; # TLSv1.2
use Scalar::Util qw( blessed );
use SyTest::JSONSensible;
use constant MIME_TYPE_JSON => "application/json";
sub _init
{
my $self = shift;
my ( $params ) = @_;
# Turn off pipelining because it gets in the way of longpolls
$params->{pipeline} = 0;
$self->SUPER::_init( $params );
}
sub configure
{
my $self = shift;
my %params = @_;
foreach (qw( uri_base restrict_methods server_name )) {
$self->{$_} = delete $params{$_} if exists $params{$_};
}
$self->SUPER::configure( %params );
}
sub server_name
{
my $self = shift;
return $self->{server_name};
}
sub full_uri_for
{
my $self = shift;
my %params = @_;
my $uri;
if( defined $self->{uri_base} ) {
$uri = URI->new( $self->{uri_base} );
if( !defined $params{full_uri} ) {
$uri->path( $uri->path . $params{uri} ); # In case of '#room' fragments
}
elsif( $params{full_uri} =~ m/^http/ ) {
$uri = URI->new( $params{full_uri} );
}
else {
$uri->path( $params{full_uri} );
}
}
else {
$uri = URI->new( $params{uri} );
}
$uri->query_form( %{ $params{params} } ) if $params{params};
return $uri;
}
sub do_request
{
my $self = shift;
my %params = @_;
my $uri = $self->full_uri_for( %params );
# Also set verify_mode = 0 to not complain about self-signed SSL certs
$params{SSL_verify_mode} = 0;
$params{SSL_cipher_list} = "HIGH";
if( $self->{restrict_methods} ) {
any { $params{method} eq $_ } @{ $self->{restrict_methods} } or
croak "This HTTP client is not allowed to perform $params{method} requests";
}
$self->SUPER::do_request(
%params,
uri => $uri,
)->then( sub {
my ( $response ) = @_;
unless( $response->code == 200 ) {
my $message = $response->message;
$message =~ s/\r?\n?$//; # because HTTP::Response doesn't do this
return Future->fail( "HTTP Request failed ( ${\$response->code} $message $uri )",
http => $response, $response->request );
}
my $content = $response->content;
if( $response->header( "Content-type" ) eq MIME_TYPE_JSON ) {
$content = wrap_numbers( $json->decode( $content ) );
}
Future->done( $content, $response );
})->catch_with_f( http => sub {
my ( $f, $message, $name, @args ) = @_;
return $f unless my $response = $args[0];
return $f unless $response->content_type eq MIME_TYPE_JSON;
# Most HTTP failures from synapse contain more detailed information in a
# JSON-encoded response body.
# Full URI is going to be long and messy because of query params; trim them
my $uri_without_query = join "", $uri->scheme, "://", $uri->authority, $uri->path, "?...";
return Future->fail( "$message from $params{method} $uri_without_query\n" . $response->decoded_content, $name => @args );
});
}
sub do_request_json
{
my $self = shift;
my %params = @_;
if( defined( my $content = $params{content} ) ) {
!blessed $content and ( ref $content eq "HASH" or ref $content eq "ARRAY" ) or
croak "->do_request_json content must be a plain HASH or ARRAY reference";
$params{content} = $json->encode( $content );
$params{content_type} //= MIME_TYPE_JSON;
}
$self->do_request( %params );
}
# A terrible internals hack that relies on the dualvar nature of the ^ operator
sub SvPOK { ( $_[0] ^ $_[0] ) ne "0" }
sub wrap_numbers
{
my ( $d ) = @_;
if( defined $d and !ref $d and !SvPOK $d ) {
return JSON::number( $d );
}
elsif( ref $d eq "ARRAY" ) {
return [ map wrap_numbers($_), @$d ];
}
elsif( ref $d eq "HASH" ) {
return { map { $_, wrap_numbers( $d->{$_} ) } keys %$d };
}
else {
return $d;
}
}
1;