File Coverage

blib/lib/Binance/API/Request.pm
Criterion Covered Total %
statement 88 110 80.0
branch 39 54 72.2
condition 20 24 83.3
subroutine 13 16 81.2
pod 4 4 100.0
total 164 208 78.8


line stmt bran cond sub pod time code
1             package Binance::API::Request;
2              
3             # MIT License
4             #
5             # Copyright (c) 2017 Lari Taskula
6             #
7             # Permission is hereby granted, free of charge, to any person obtaining a copy
8             # of this software and associated documentation files (the "Software"), to deal
9             # in the Software without restriction, including without limitation the rights
10             # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11             # copies of the Software, and to permit persons to whom the Software is
12             # furnished to do so, subject to the following conditions:
13             #
14             # The above copyright notice and this permission notice shall be included in all
15             # copies or substantial portions of the Software.
16             #
17             # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18             # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19             # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20             # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21             # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22             # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23             # SOFTWARE.
24              
25 3     3   616 use strict;
  3         7  
  3         135  
26 3     3   16 use warnings;
  3         6  
  3         109  
27              
28 3     3   15 use base 'LWP::UserAgent';
  3         6  
  3         2222  
29              
30 3     3   155266 use Digest::SHA qw( hmac_sha256_hex );
  3         10361  
  3         291  
31 3     3   2308 use JSON;
  3         32287  
  3         20  
32 3     3   3099 use Time::HiRes;
  3         4605  
  3         32  
33 3     3   371 use URI;
  3         9  
  3         109  
34 3     3   2369 use URI::QueryParam;
  3         2345  
  3         116  
35              
36 3     3   23 use Binance::Constants qw( :all );
  3         16  
  3         399  
37              
38 3     3   1531 use Binance::Exception::Parameter::Required;
  3         9  
  3         3430  
39              
40             =head1 NAME
41              
42             Binance::API::Request -- LWP::UserAgent wrapper for L
43              
44             =head1 DESCRIPTION
45              
46             This module provides a wrapper for LWP::UserAgent. Generates required parameters
47             for Binance API requests.
48              
49             =cut
50              
51             sub new {
52 5     5 1 11 my $class = shift;
53 5         25 my %params = @_;
54              
55             my $self = {
56             apiKey => $params{'apiKey'},
57             secretKey => $params{'secretKey'},
58             recvWindow => $params{'recvWindow'},
59 5         23 logger => $params{'logger'},
60             };
61              
62 5         18 bless $self, $class;
63             }
64              
65             sub get {
66 9     9 1 27 my ($self, $url, $params) = @_;
67              
68 9         25 my ($path, %data) = $self->_init($url, $params);
69 9         34 return $self->_exec('get', $path, %data);
70             }
71              
72             sub post {
73 0     0 1 0 my ($self, $url, $params) = @_;
74              
75 0         0 my ($path, %data) = $self->_init($url, $params);
76 0         0 return $self->_exec('post', $path, %data);
77             }
78              
79             sub delete {
80 0     0 1 0 my ($self, $url, $params) = @_;
81              
82 0         0 my ($path, %data) = $self->_init($url, $params);
83 0         0 return $self->_exec('delete', $path, %data);
84             }
85              
86             sub _exec {
87 0     0   0 my ($self, $method, $url, %data) = @_;
88              
89 0         0 $self->{logger}->debug("New request: $url");
90 0         0 $method = "SUPER::$method";
91 0         0 my $response;
92 0 0       0 if (keys %data > 0) {
93 0         0 $response = $self->$method($url, %data);
94             } else {
95 0         0 $response = $self->$method($url);
96             }
97 0 0       0 if ($response->is_success) {
98 0         0 $response = eval { decode_json($response->decoded_content); };
  0         0  
99 0 0       0 if ($@) {
100             $self->{logger}->error(
101 0 0       0 "Error decoding response. \nStatus => " . $response->code . ",\n"
102             . 'Content => ' . ($response->content ? $response->content : '')
103             );
104             }
105             } else {
106             $self->{logger}->error(
107 0 0       0 "Unsuccessful request. \nStatus => " . $response->code . ",\n"
108             . 'Content => ' . ($response->content ? $response->content : '')
109             );
110             }
111 0         0 return $response;
112             }
113              
114             sub _init {
115 18     18   3672 my ($self, $path, $params) = @_;
116              
117 18 50       46 unless ($path) {
118 0         0 Binance::Exception::Parameter::Required->throw(
119             error => 'Parameter "path" required',
120             parameters => ['path']
121             );
122             }
123              
124 18         31 my $timestamp = $params->{'timestamp'};
125 18         31 delete $params->{'timestamp'};
126             # Delete undefined query parameters
127 18         32 my $query = $params->{'query'};
128 18         58 foreach my $param (keys %$query) {
129 31 100       73 delete $query->{$param} unless defined $query->{$param};
130             }
131              
132             # Delete undefined body parameters
133 18         38 my $body = $params->{'body'};
134 18         45 foreach my $param (keys %$body) {
135 14 50       30 delete $body->{$param} unless defined $body->{$param};
136             }
137              
138 18         27 my $recvWindow;
139 18 100       42 if ($params->{signed}) {
140             $recvWindow = $query->{'recvWindow'} // $body->{'recvWindow'} //
141 6 50 100     39 defined $self->{'recvWindow'} ? $self->{'recvWindow'} : undef;
      66        
142             }
143              
144 18 100 66     62 $timestamp //= int Time::HiRes::time * 1000 if $params->{'signed'};
145              
146 18         107 my $uri = URI->new( BASE_URL . $path );
147 18         19336 my $full_path = $uri->as_string;
148              
149 18         216 my %data;
150             # Mixed request (both query params & body params)
151 18 100 100     139 if (keys %$body && keys %$query) {
    100 66        
    50 100        
152 3 100 100     23 if (!defined $body->{'recvWindow'} && defined $recvWindow) {
    100 66        
153 1         3 $query->{'recvWindow'} = $recvWindow;
154             }
155             elsif (!defined $query->{'recvWindow'} && defined $recvWindow) {
156 1         2 $body->{'recvWindow'} = $recvWindow;
157             }
158              
159             # First, generate escaped parameter sets
160 3         10 my $tmp = $uri->clone;
161 3         28 $tmp->query_form($query);
162 3         317 my $query_params = $tmp->query();
163 3         34 $tmp->query_form($body);
164 3         249 my $body_params = $tmp->query();
165              
166             # Add timestamp to the end of body
167 3 100       36 $body_params .= "×tamp=$timestamp" if defined $timestamp;
168              
169             # Combine query and body parameters so that we can sign it.
170             # Binance documentation states that mixed content signature
171             # generation should not add the '&' character between query
172             # and body parameter sets.
173 3         7 my $to_sign = $query_params . $body_params;
174              
175 3         36 $self->{logger}->debug("Generating signature from: '$to_sign'");
176              
177             $body_params .= '&signature='.hmac_sha256_hex(
178             $to_sign, $self->{secretKey}
179 3 100       38 ) if $params->{signed};
180              
181 3         9 $full_path .= "?$query_params";
182 3         14 $data{'Content'} = $body_params;
183             }
184             # Query parameters only
185             elsif (keys %$query || !keys %$query && !keys %$body) {
186 12 100       31 $query->{'recvWindow'} = $recvWindow if $recvWindow;
187              
188 12         36 my $tmp = $uri->clone;
189 12         100 $tmp->query_form($query);
190 12         950 my $query_params = $tmp->query();
191              
192             # Add timestamp to the end of query
193 12 100       135 $query_params .= "×tamp=$timestamp" if defined $timestamp;
194              
195 12 100       107 $self->{logger}->debug("Generating signature from: '$query_params'")
196             if $query_params;
197              
198             $query_params .= '&signature='.hmac_sha256_hex(
199             $query_params, $self->{secretKey}
200 12 100       50 ) if $params->{signed};
201              
202 12 100       62 $full_path .= "?$query_params" if $query_params;
203             }
204             # Body parameters only
205             elsif (keys %$body) {
206 3 100       9 $body->{'recvWindow'} = $recvWindow if $recvWindow;
207              
208 3         8 $full_path = $uri->as_string;
209              
210 3         15 my $tmp = $uri->clone;
211 3         23 $tmp->query_form($body);
212 3         320 my $body_params = $tmp->query();
213              
214             # Add timestamp to the end of body
215 3 100       37 $body_params .= "×tamp=$timestamp" if defined $timestamp;
216              
217 3         22 $self->{logger}->debug("Generating signature from: '$body_params'");
218              
219             $body_params .= '&signature='.hmac_sha256_hex(
220             $body_params, $self->{secretKey}
221 3 100       36 ) if $params->{signed};
222              
223 3         9 $data{'Content'} = $body_params;
224             }
225              
226 18 50       69 if (defined $self->{apiKey}) {
227 0         0 $data{'X_MBX_APIKEY'} = $self->{apiKey};
228             }
229              
230 18         97 return ($full_path, %data);
231             }
232              
233             1;