File Coverage

blib/lib/Hypersonic/UA/Response.pm
Criterion Covered Total %
statement 15 78 19.2
branch n/a
condition n/a
subroutine 6 28 21.4
pod 0 21 0.0
total 21 127 16.5


line stmt bran cond sub pod time code
1             package Hypersonic::UA::Response;
2              
3 1     1   221335 use strict;
  1         2  
  1         30  
4 1     1   3 use warnings;
  1         2  
  1         38  
5 1     1   13 use 5.010;
  1         5  
6              
7             our $VERSION = '0.15';
8              
9             use constant {
10 1         117 SLOT_STATUS => 0,
11             SLOT_STATUS_TEXT => 1,
12             SLOT_HEADERS => 2,
13             SLOT_BODY => 3,
14             SLOT_CONTENT_TYPE => 4,
15             SLOT_CONTENT_LENGTH => 5,
16             SLOT_HTTP_VERSION => 6,
17             SLOT_REQUEST => 7,
18             SLOT_REDIRECTS => 8,
19             SLOT_TIMING => 9,
20             SLOT_JSON => 10,
21             SLOT_RAW_HEADERS => 11,
22             SLOT_COUNT => 12,
23 1     1   4 };
  1         1  
24              
25 1     1   4 use constant MAX_RESPONSES => 65536;
  1         1  
  1         1498  
26              
27             sub generate_c_code {
28 0     0 0 0 my ($class, $builder, $opts) = @_;
29              
30 0         0 $class->gen_slot_constants($builder);
31 0         0 $class->gen_xs_new($builder);
32 0         0 $class->gen_xs_from_raw($builder);
33 0         0 $class->gen_xs_status($builder);
34 0         0 $class->gen_xs_status_text($builder);
35 0         0 $class->gen_xs_body($builder);
36 0         0 $class->gen_xs_headers($builder);
37 0         0 $class->gen_xs_header($builder);
38 0         0 $class->gen_xs_content_type($builder);
39 0         0 $class->gen_xs_content_length($builder);
40 0         0 $class->gen_xs_raw_headers($builder);
41 0         0 $class->gen_xs_is_success($builder);
42 0         0 $class->gen_xs_is_redirect($builder);
43 0         0 $class->gen_xs_is_error($builder);
44 0         0 $class->gen_xs_is_client_error($builder);
45 0         0 $class->gen_xs_is_server_error($builder);
46 0         0 $class->gen_xs_is_json($builder);
47 0         0 $class->gen_xs_json($builder);
48 0         0 $class->gen_xs_location($builder);
49             }
50              
51             sub get_xs_functions {
52             return {
53 1     1 0 1925 'Hypersonic::UA::Response::new' => { source => 'xs_response_new', is_xs_native => 1 },
54             'Hypersonic::UA::Response::from_raw' => { source => 'xs_response_from_raw', is_xs_native => 1 },
55             'Hypersonic::UA::Response::status' => { source => 'xs_response_status', is_xs_native => 1 },
56             'Hypersonic::UA::Response::status_text' => { source => 'xs_response_status_text', is_xs_native => 1 },
57             'Hypersonic::UA::Response::body' => { source => 'xs_response_body', is_xs_native => 1 },
58             'Hypersonic::UA::Response::headers' => { source => 'xs_response_headers', is_xs_native => 1 },
59             'Hypersonic::UA::Response::header' => { source => 'xs_response_header', is_xs_native => 1 },
60             'Hypersonic::UA::Response::content_type' => { source => 'xs_response_content_type', is_xs_native => 1 },
61             'Hypersonic::UA::Response::content_length' => { source => 'xs_response_content_length', is_xs_native => 1 },
62             'Hypersonic::UA::Response::raw_headers' => { source => 'xs_response_raw_headers', is_xs_native => 1 },
63             'Hypersonic::UA::Response::is_success' => { source => 'xs_response_is_success', is_xs_native => 1 },
64             'Hypersonic::UA::Response::is_redirect' => { source => 'xs_response_is_redirect', is_xs_native => 1 },
65             'Hypersonic::UA::Response::is_error' => { source => 'xs_response_is_error', is_xs_native => 1 },
66             'Hypersonic::UA::Response::is_client_error' => { source => 'xs_response_is_client_error', is_xs_native => 1 },
67             'Hypersonic::UA::Response::is_server_error' => { source => 'xs_response_is_server_error', is_xs_native => 1 },
68             'Hypersonic::UA::Response::is_json' => { source => 'xs_response_is_json', is_xs_native => 1 },
69             'Hypersonic::UA::Response::json' => { source => 'xs_response_json', is_xs_native => 1 },
70             'Hypersonic::UA::Response::location' => { source => 'xs_response_location', is_xs_native => 1 },
71             };
72             }
73              
74             sub gen_slot_constants {
75 0     0 0   my ($class, $builder) = @_;
76              
77 0           $builder->line('#define RESP_SLOT_STATUS ' . SLOT_STATUS)
78             ->line('#define RESP_SLOT_STATUS_TEXT ' . SLOT_STATUS_TEXT)
79             ->line('#define RESP_SLOT_HEADERS ' . SLOT_HEADERS)
80             ->line('#define RESP_SLOT_BODY ' . SLOT_BODY)
81             ->line('#define RESP_SLOT_CONTENT_TYPE ' . SLOT_CONTENT_TYPE)
82             ->line('#define RESP_SLOT_CONTENT_LENGTH ' . SLOT_CONTENT_LENGTH)
83             ->line('#define RESP_SLOT_HTTP_VERSION ' . SLOT_HTTP_VERSION)
84             ->line('#define RESP_SLOT_REQUEST ' . SLOT_REQUEST)
85             ->line('#define RESP_SLOT_REDIRECTS ' . SLOT_REDIRECTS)
86             ->line('#define RESP_SLOT_TIMING ' . SLOT_TIMING)
87             ->line('#define RESP_SLOT_JSON ' . SLOT_JSON)
88             ->line('#define RESP_SLOT_RAW_HEADERS ' . SLOT_RAW_HEADERS)
89             ->line('#define RESP_SLOT_COUNT ' . SLOT_COUNT)
90             ->blank;
91              
92 0           $builder->line('static const char* resp_status_text(int status) {')
93             ->line(' switch (status) {')
94             ->line(' case 100: return "Continue";')
95             ->line(' case 101: return "Switching Protocols";')
96             ->line(' case 200: return "OK";')
97             ->line(' case 201: return "Created";')
98             ->line(' case 202: return "Accepted";')
99             ->line(' case 204: return "No Content";')
100             ->line(' case 206: return "Partial Content";')
101             ->line(' case 301: return "Moved Permanently";')
102             ->line(' case 302: return "Found";')
103             ->line(' case 303: return "See Other";')
104             ->line(' case 304: return "Not Modified";')
105             ->line(' case 307: return "Temporary Redirect";')
106             ->line(' case 308: return "Permanent Redirect";')
107             ->line(' case 400: return "Bad Request";')
108             ->line(' case 401: return "Unauthorized";')
109             ->line(' case 403: return "Forbidden";')
110             ->line(' case 404: return "Not Found";')
111             ->line(' case 405: return "Method Not Allowed";')
112             ->line(' case 408: return "Request Timeout";')
113             ->line(' case 409: return "Conflict";')
114             ->line(' case 410: return "Gone";')
115             ->line(' case 413: return "Payload Too Large";')
116             ->line(' case 415: return "Unsupported Media Type";')
117             ->line(' case 422: return "Unprocessable Entity";')
118             ->line(' case 429: return "Too Many Requests";')
119             ->line(' case 500: return "Internal Server Error";')
120             ->line(' case 501: return "Not Implemented";')
121             ->line(' case 502: return "Bad Gateway";')
122             ->line(' case 503: return "Service Unavailable";')
123             ->line(' case 504: return "Gateway Timeout";')
124             ->line(' default: return "Unknown";')
125             ->line(' }')
126             ->line('}')
127             ->blank;
128             }
129              
130             sub gen_xs_new {
131 0     0 0   my ($class, $builder) = @_;
132              
133 0           $builder->comment('Create new response from parsed data')
134             ->xs_function('xs_response_new')
135             ->xs_preamble
136             ->line('int status;')
137             ->line('HV* opts;')
138             ->line('AV* self;')
139             ->line('SV** val;')
140             ->line('SV* obj;')
141             ->blank
142             ->line('if (items < 2) croak("Usage: new(class, status, ...)");')
143             ->blank
144             ->line('status = (int)SvIV(ST(1));')
145             ->line('opts = (items > 2 && SvROK(ST(2))) ? (HV*)SvRV(ST(2)) : NULL;')
146             ->blank
147             ->line('self = newAV();')
148             ->line('av_extend(self, RESP_SLOT_COUNT - 1);')
149             ->blank
150             ->line('av_store(self, RESP_SLOT_STATUS, newSViv(status));')
151             ->line('av_store(self, RESP_SLOT_STATUS_TEXT, newSVpv(resp_status_text(status), 0));')
152             ->blank
153             ->if('opts')
154             ->line('val = hv_fetchs(opts, "headers", 0);')
155             ->line('av_store(self, RESP_SLOT_HEADERS, val && *val ? newSVsv(*val) : newRV_noinc((SV*)newHV()));')
156             ->line('val = hv_fetchs(opts, "body", 0);')
157             ->line('av_store(self, RESP_SLOT_BODY, val && *val ? newSVsv(*val) : newSVpvn("", 0));')
158             ->line('val = hv_fetchs(opts, "content_type", 0);')
159             ->line('av_store(self, RESP_SLOT_CONTENT_TYPE, val && *val ? newSVsv(*val) : newSVpvn("", 0));')
160             ->line('val = hv_fetchs(opts, "content_length", 0);')
161             ->line('av_store(self, RESP_SLOT_CONTENT_LENGTH, val && *val ? newSVsv(*val) : newSViv(-1));')
162             ->line('val = hv_fetchs(opts, "raw_headers", 0);')
163             ->line('av_store(self, RESP_SLOT_RAW_HEADERS, val && *val ? newSVsv(*val) : newSVpvn("", 0));')
164             ->else
165             ->line('av_store(self, RESP_SLOT_HEADERS, newRV_noinc((SV*)newHV()));')
166             ->line('av_store(self, RESP_SLOT_BODY, newSVpvn("", 0));')
167             ->line('av_store(self, RESP_SLOT_CONTENT_TYPE, newSVpvn("", 0));')
168             ->line('av_store(self, RESP_SLOT_CONTENT_LENGTH, newSViv(-1));')
169             ->line('av_store(self, RESP_SLOT_RAW_HEADERS, newSVpvn("", 0));')
170             ->endif
171             ->blank
172             ->line('av_store(self, RESP_SLOT_HTTP_VERSION, newSVpvn("1.1", 3));')
173             ->line('av_store(self, RESP_SLOT_REQUEST, &PL_sv_undef);')
174             ->line('av_store(self, RESP_SLOT_REDIRECTS, newRV_noinc((SV*)newAV()));')
175             ->line('av_store(self, RESP_SLOT_TIMING, newRV_noinc((SV*)newHV()));')
176             ->line('av_store(self, RESP_SLOT_JSON, &PL_sv_undef);')
177             ->blank
178             ->line('obj = sv_bless(newRV_noinc((SV*)self), gv_stashpv("Hypersonic::UA::Response", GV_ADD));')
179             ->line('ST(0) = sv_2mortal(obj);')
180             ->xs_return('1')
181             ->xs_end
182             ->blank;
183             }
184              
185             sub gen_xs_from_raw {
186 0     0 0   my ($class, $builder) = @_;
187              
188 0           $builder->comment('Create response from raw HTTP response')
189             ->xs_function('xs_response_from_raw')
190             ->xs_preamble
191             ->line('STRLEN raw_len;')
192             ->line('const char* raw;')
193             ->line('int http_minor;')
194             ->line('int status;')
195             ->line('const char* body_start;')
196             ->line('size_t headers_len;')
197             ->line('size_t body_len;')
198             ->line('HV* headers;')
199             ->line('AV* self;')
200             ->line('SV** ct;')
201             ->line('SV** cl;')
202             ->line('SV* obj;')
203             ->comment('Parse using HTTP1 helpers (must be linked)')
204             ->line('extern int http1_parse_status_line(const char*, size_t, int*);')
205             ->line('extern const char* http1_find_body_start(const char*, size_t);')
206             ->line('extern HV* http1_parse_headers_into_hv(const char*, size_t);')
207             ->blank
208             ->line('if (items != 2) croak("Usage: from_raw(class, raw)");')
209             ->blank
210             ->line('raw = SvPV(ST(1), raw_len);')
211             ->blank
212             ->line('status = http1_parse_status_line(raw, raw_len, &http_minor);')
213             ->if('status < 0')
214             ->line('ST(0) = &PL_sv_undef;')
215             ->line('XSRETURN(1);')
216             ->endif
217             ->blank
218             ->line('body_start = http1_find_body_start(raw, raw_len);')
219             ->if('!body_start')
220             ->line('ST(0) = &PL_sv_undef;')
221             ->line('XSRETURN(1);')
222             ->endif
223             ->blank
224             ->line('headers_len = body_start - raw;')
225             ->line('body_len = raw_len - headers_len;')
226             ->blank
227             ->line('headers = http1_parse_headers_into_hv(raw, headers_len);')
228             ->blank
229             ->line('self = newAV();')
230             ->line('av_extend(self, RESP_SLOT_COUNT - 1);')
231             ->blank
232             ->line('av_store(self, RESP_SLOT_STATUS, newSViv(status));')
233             ->line('av_store(self, RESP_SLOT_STATUS_TEXT, newSVpv(resp_status_text(status), 0));')
234             ->line('av_store(self, RESP_SLOT_HEADERS, newRV_noinc((SV*)headers));')
235             ->line('av_store(self, RESP_SLOT_BODY, newSVpvn(body_start, body_len));')
236             ->blank
237             ->line('ct = hv_fetchs(headers, "content_type", 0);')
238             ->line('av_store(self, RESP_SLOT_CONTENT_TYPE, ct && *ct ? newSVsv(*ct) : newSVpvn("", 0));')
239             ->blank
240             ->line('cl = hv_fetchs(headers, "content_length", 0);')
241             ->line('av_store(self, RESP_SLOT_CONTENT_LENGTH, cl && *cl ? newSViv(atoi(SvPV_nolen(*cl))) : newSViv(-1));')
242             ->blank
243             ->line('av_store(self, RESP_SLOT_HTTP_VERSION, newSVpvn("1.1", 3));')
244             ->line('av_store(self, RESP_SLOT_REQUEST, &PL_sv_undef);')
245             ->line('av_store(self, RESP_SLOT_REDIRECTS, newRV_noinc((SV*)newAV()));')
246             ->line('av_store(self, RESP_SLOT_TIMING, newRV_noinc((SV*)newHV()));')
247             ->line('av_store(self, RESP_SLOT_JSON, &PL_sv_undef);')
248             ->line('av_store(self, RESP_SLOT_RAW_HEADERS, newSVpvn(raw, headers_len));')
249             ->blank
250             ->line('obj = sv_bless(newRV_noinc((SV*)self), gv_stashpv("Hypersonic::UA::Response", GV_ADD));')
251             ->line('ST(0) = sv_2mortal(obj);')
252             ->xs_return('1')
253             ->xs_end
254             ->blank;
255             }
256              
257             sub gen_xs_status {
258 0     0 0   my ($class, $builder) = @_;
259 0           $class->_gen_slot_accessor($builder, 'status', 'RESP_SLOT_STATUS');
260             }
261              
262             sub gen_xs_status_text {
263 0     0 0   my ($class, $builder) = @_;
264 0           $class->_gen_slot_accessor($builder, 'status_text', 'RESP_SLOT_STATUS_TEXT');
265             }
266              
267             sub gen_xs_body {
268 0     0 0   my ($class, $builder) = @_;
269 0           $class->_gen_slot_accessor($builder, 'body', 'RESP_SLOT_BODY');
270             }
271              
272             sub gen_xs_content_type {
273 0     0 0   my ($class, $builder) = @_;
274 0           $class->_gen_slot_accessor($builder, 'content_type', 'RESP_SLOT_CONTENT_TYPE');
275             }
276              
277             sub gen_xs_content_length {
278 0     0 0   my ($class, $builder) = @_;
279 0           $class->_gen_slot_accessor($builder, 'content_length', 'RESP_SLOT_CONTENT_LENGTH');
280             }
281              
282             sub gen_xs_raw_headers {
283 0     0 0   my ($class, $builder) = @_;
284 0           $class->_gen_slot_accessor($builder, 'raw_headers', 'RESP_SLOT_RAW_HEADERS');
285             }
286              
287             sub _gen_slot_accessor {
288 0     0     my ($class, $builder, $name, $slot) = @_;
289              
290 0           $builder->comment("Get $name")
291             ->xs_function("xs_response_$name")
292             ->xs_preamble
293             ->line('if (items != 1) croak("Usage: ' . $name . '(self)");')
294             ->line('SV** ary = AvARRAY((AV*)SvRV(ST(0)));')
295             ->line("ST(0) = ary[$slot];")
296             ->xs_return('1')
297             ->xs_end
298             ->blank;
299             }
300              
301             sub gen_xs_headers {
302 0     0 0   my ($class, $builder) = @_;
303              
304 0           $builder->comment('Get headers hashref')
305             ->xs_function('xs_response_headers')
306             ->xs_preamble
307             ->line('if (items != 1) croak("Usage: headers(self)");')
308             ->line('SV** ary = AvARRAY((AV*)SvRV(ST(0)));')
309             ->line('ST(0) = ary[RESP_SLOT_HEADERS];')
310             ->xs_return('1')
311             ->xs_end
312             ->blank;
313             }
314              
315             sub gen_xs_header {
316 0     0 0   my ($class, $builder) = @_;
317              
318 0           $builder->comment('Get single header (normalized)')
319             ->xs_function('xs_response_header')
320             ->xs_preamble
321             ->line('if (items != 2) croak("Usage: header(self, name)");')
322             ->line('SV** ary = AvARRAY((AV*)SvRV(ST(0)));')
323             ->line('HV* headers = (HV*)SvRV(ary[RESP_SLOT_HEADERS]);')
324             ->blank
325             ->line('STRLEN klen;')
326             ->line('const char* key = SvPV(ST(1), klen);')
327             ->blank
328             ->line('char norm_key[256];')
329             ->line('size_t i;')
330             ->line('if (klen > 255) klen = 255;')
331             ->line('for (i = 0; i < klen; i++) {')
332             ->line(' char c = key[i];')
333             ->line(' if (c >= \'A\' && c <= \'Z\') c += 32;')
334             ->line(' else if (c == \'-\') c = \'_\';')
335             ->line(' norm_key[i] = c;')
336             ->line('}')
337             ->blank
338             ->line('SV** val = hv_fetch(headers, norm_key, klen, 0);')
339             ->line('ST(0) = (val && *val) ? *val : &PL_sv_undef;')
340             ->xs_return('1')
341             ->xs_end
342             ->blank;
343             }
344              
345             sub gen_xs_is_success {
346 0     0 0   my ($class, $builder) = @_;
347 0           $class->_gen_status_check($builder, 'is_success', 'status >= 200 && status < 300');
348             }
349              
350             sub gen_xs_is_redirect {
351 0     0 0   my ($class, $builder) = @_;
352 0           $class->_gen_status_check($builder, 'is_redirect', 'status >= 300 && status < 400');
353             }
354              
355             sub gen_xs_is_error {
356 0     0 0   my ($class, $builder) = @_;
357 0           $class->_gen_status_check($builder, 'is_error', 'status >= 400');
358             }
359              
360             sub gen_xs_is_client_error {
361 0     0 0   my ($class, $builder) = @_;
362 0           $class->_gen_status_check($builder, 'is_client_error', 'status >= 400 && status < 500');
363             }
364              
365             sub gen_xs_is_server_error {
366 0     0 0   my ($class, $builder) = @_;
367 0           $class->_gen_status_check($builder, 'is_server_error', 'status >= 500');
368             }
369              
370             sub _gen_status_check {
371 0     0     my ($class, $builder, $name, $condition) = @_;
372              
373 0           $builder->comment("Check $name")
374             ->xs_function("xs_response_$name")
375             ->xs_preamble
376             ->line('SV** ary = AvARRAY((AV*)SvRV(ST(0)));')
377             ->line('IV status = SvIV(ary[RESP_SLOT_STATUS]);')
378             ->line("ST(0) = ($condition) ? &PL_sv_yes : &PL_sv_no;")
379             ->xs_return('1')
380             ->xs_end
381             ->blank;
382             }
383              
384             sub gen_xs_is_json {
385 0     0 0   my ($class, $builder) = @_;
386              
387 0           $builder->comment('Check if content type is JSON')
388             ->xs_function('xs_response_is_json')
389             ->xs_preamble
390             ->line('SV** ary = AvARRAY((AV*)SvRV(ST(0)));')
391             ->line('SV* ct = ary[RESP_SLOT_CONTENT_TYPE];')
392             ->if('!SvOK(ct)')
393             ->line('ST(0) = &PL_sv_no;')
394             ->line('XSRETURN(1);')
395             ->endif
396             ->line('STRLEN ct_len;')
397             ->line('const char* ct_str = SvPV(ct, ct_len);')
398             ->line('ST(0) = (strstr(ct_str, "application/json") || strstr(ct_str, "+json")) ? &PL_sv_yes : &PL_sv_no;')
399             ->xs_return('1')
400             ->xs_end
401             ->blank;
402             }
403              
404             sub gen_xs_json {
405 0     0 0   my ($class, $builder) = @_;
406              
407 0           $builder->comment('Get parsed JSON (lazy, cached)')
408             ->xs_function('xs_response_json')
409             ->xs_preamble
410             ->line('SV** ary = AvARRAY((AV*)SvRV(ST(0)));')
411             ->blank
412             ->comment('Return cached if available')
413             ->line('SV* cached = ary[RESP_SLOT_JSON];')
414             ->if('SvOK(cached)')
415             ->line('ST(0) = cached;')
416             ->line('XSRETURN(1);')
417             ->endif
418             ->blank
419             ->comment('Parse JSON using Cpanel::JSON::XS')
420             ->line('dSP;')
421             ->line('PUSHMARK(SP);')
422             ->line('XPUSHs(ary[RESP_SLOT_BODY]);')
423             ->line('PUTBACK;')
424             ->line('int count = call_pv("Cpanel::JSON::XS::decode_json", G_SCALAR | G_EVAL);')
425             ->line('SPAGAIN;')
426             ->blank
427             ->if('SvTRUE(ERRSV)')
428             ->line('POPs;')
429             ->line('ST(0) = &PL_sv_undef;')
430             ->line('XSRETURN(1);')
431             ->endif
432             ->blank
433             ->line('SV* result = POPs;')
434             ->line('SvREFCNT_inc(result);')
435             ->line('av_store((AV*)SvRV(ST(0)), RESP_SLOT_JSON, result);')
436             ->line('ST(0) = result;')
437             ->xs_return('1')
438             ->xs_end
439             ->blank;
440             }
441              
442             sub gen_xs_location {
443 0     0 0   my ($class, $builder) = @_;
444              
445 0           $builder->comment('Get Location header')
446             ->xs_function('xs_response_location')
447             ->xs_preamble
448             ->line('SV** ary = AvARRAY((AV*)SvRV(ST(0)));')
449             ->line('HV* headers = (HV*)SvRV(ary[RESP_SLOT_HEADERS]);')
450             ->line('SV** val = hv_fetchs(headers, "location", 0);')
451             ->line('ST(0) = (val && *val) ? *val : &PL_sv_undef;')
452             ->xs_return('1')
453             ->xs_end
454             ->blank;
455             }
456              
457             1;
458              
459             __END__