File Coverage

blib/lib/Plack/Middleware/PeriAHS/ParseRequest.pm
Criterion Covered Total %
statement 15 17 88.2
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 21 23 91.3


line stmt bran cond sub pod time code
1             package Plack::Middleware::PeriAHS::ParseRequest;
2              
3             our $DATE = '2016-03-16'; # DATE
4             our $VERSION = '0.60'; # VERSION
5              
6 2     2   1645 use 5.010;
  2         7  
7 2     2   11 use strict;
  2         5  
  2         38  
8 2     2   12 use warnings;
  2         4  
  2         57  
9 2     2   845 use Log::Any '$log';
  2         15197  
  2         19  
10              
11 2     2   5247 use Perinci::AccessUtil qw(insert_riap_stuffs_to_res decode_args_in_riap_req);
  2         3635  
  2         121  
12 2     2   929 use Perinci::Access::Base::Patch::PeriAHS;
  0            
  0            
13              
14             use parent qw(Plack::Middleware);
15             use Plack::Request;
16             use Plack::Util::Accessor qw(
17             riap_uri_prefix
18             server_host
19             server_port
20             server_path
21              
22             match_uri
23             match_uri_errmsg
24             parse_form
25             parse_reform
26             parse_path_info
27             accept_yaml
28              
29             riap_client
30             use_tx
31             custom_tx_manager
32              
33             php_clients_ua_re
34             deconfuse_php_clients
35             );
36              
37             use Perinci::Access::Schemeless;
38             use Perinci::Sub::GetArgs::Array qw(get_args_from_array);
39             use Plack::Util::PeriAHS qw(errpage);
40             use Scalar::Util qw(blessed);
41             use URI::Escape;
42              
43             # retun ($success?, $errmsg, $res)
44             sub __parse_json {
45             require Data::Clean::FromJSON;
46             require JSON::MaybeXS;
47              
48             my $str = shift;
49              
50             state $json = JSON::MaybeXS->new->allow_nonref;
51              
52             # to rid of those JSON::XS::Boolean objects which currently choke
53             # Data::Sah-generated validator code. in the future Data::Sah can be
54             # modified to handle those, or we use a fork of JSON::XS which doesn't
55             # produce those in the first place (probably only when performance is
56             # critical).
57             state $cleanser = Data::Clean::FromJSON->get_cleanser;
58              
59             my $res;
60             eval { $res = $json->decode($str); $cleanser->clean_in_place($res) };
61             my $e = $@;
62             return (!$e, $e, $res);
63             }
64              
65             sub __parse_yaml {
66             require YAML::Syck;
67              
68             my $str = shift;
69              
70             local $YAML::Syck::ImplicitTyping = 1;
71             my $res;
72             eval { $res = YAML::Syck::Load($str) };
73             my $e = $@;
74             return (!$e, $e, $res);
75             }
76              
77             sub get_server_url {
78             my ($self, $env) = @_;
79             my $host = $self->{server_host};
80             unless (defined $host) {
81             $host = $env->{HTTP_HOST} =~ /(.+):(.+)/ ? $1 : $env->{HTTP_HOST};
82             }
83             my $port = $self->{server_port};
84             unless (defined $port) {
85             $port = $env->{HTTP_HOST} =~ /(.+):(.+)/ ? $2 :
86             ($env->{HTTPS} ? 443 : 80);
87             }
88             join("",
89             ($env->{HTTPS} ? "https" : "http"), "://",
90             $host,
91             $port == ($env->{HTTPS} ? 443:80) ? "" : ":$port",
92             $self->{server_path},
93             "/",
94             );
95             }
96              
97             sub prepare_app {
98             my $self = shift;
99              
100             $self->{riap_uri_prefix} //= '';
101             $self->{server_host} //= undef;
102             $self->{server_port} //= undef;
103             $self->{server_path} //= '/api';
104             $self->{server_path} =~ s!/\z!!;
105             $self->{get_http_request_url} //= sub {
106             my ($self, $env, $rreq) = @_;
107             my $uri = $rreq->{uri};
108             return unless $uri =~ m!^/! || $uri =~ s/^pl://;
109             $uri =~ s/\A\Q$self->{riap_uri_prefix}\E//;
110             $uri =~ s!^/!!;
111             join("",
112             $self->get_server_url($env),
113             $uri
114             );
115             };
116              
117             $self->{match_uri} //= qr/(?<uri>[^?]*)/;
118             $self->{accept_yaml} //= 0;
119             $self->{parse_form} //= 1;
120             $self->{parse_reform} //= 0;
121             $self->{parse_path_info} //= 0;
122             $self->{use_tx} //= 0;
123             $self->{custom_tx_manager} //= undef;
124              
125             $self->{riap_client} //= Perinci::Access::Schemeless->new(
126             load => 0,
127             set_function_properties => {
128             #timeout => 300,
129             },
130             use_tx => $self->{use_tx},
131             custom_tx_manager => $self->{custom_tx_manager},
132             );
133              
134             $self->{php_clients_ua_re} //= qr(Phinci|/php|php/)i;
135             $self->{deconfuse_php_clients} //= 1;
136              
137             $log->tracef("Prepared PeriAHS::ParseRequest middleware: %s", $self);
138             }
139              
140             sub call {
141             my ($self, $env) = @_;
142             $log->tracef("=> PeriAHS::ParseRequest middleware");
143              
144             my $rreq = $env->{"riap.request"} //= {};
145              
146             # put Riap client for later phases
147             $env->{"periahs.riap_client"} = $self->{riap_client};
148              
149             # first determine the default output format (fmt), so we can return error
150             # page in that format
151             my $acp = $env->{HTTP_ACCEPT} // "";
152             my $ua = $env->{HTTP_USER_AGENT} // "";
153             my $fmt;
154             if ($acp =~ m!^text/(?:x-)?yaml$!) {
155             $fmt = "yaml";
156             } elsif ($acp eq 'application/json') {
157             $fmt = "json";
158             } elsif ($acp eq 'text/plain') {
159             $fmt = "text";
160             } elsif ($ua =~ m!Wget/|curl/!) {
161             $fmt = "text";
162             } elsif ($ua =~ m!Mozilla/!) {
163             $fmt = "json";
164             # XXX enable json->html templating
165             } else {
166             $fmt = "json";
167             }
168             $env->{"periahs.default_fmt"} = $fmt;
169              
170             my ($decsuc, $decerr); # json/yaml decoding success status & error message
171              
172             # parse Riap request keys from HTTP headers (required by spec)
173             for my $k0 (keys %$env) {
174             next unless $k0 =~ /\AHTTP_X_RIAP_(.+?)(_J_)?\z/;
175             my $v = $env->{$k0};
176             my ($k, $encj) = (lc($1), $2);
177             # already ensured by Plack
178             #$k =~ /\A\w+\z/ or return errpage(
179             # $env, [400, "Invalid Riap request key syntax in HTTP header $k0"]);
180             if ($encj) {
181             ($decsuc, $decerr, $v) = __parse_json($v);
182             return errpage(
183             $env, [400, "Invalid JSON in HTTP header $k0: $decerr"])
184             if !$decsuc;
185             }
186             $rreq->{$k} = $v;
187             }
188              
189             # parse args from request body (required by spec)
190             my $preq = Plack::Request->new($env);
191             unless (exists $rreq->{args}) {
192             {
193             my $ct = $env->{CONTENT_TYPE};
194             last unless $ct;
195             last if $ct eq 'application/x-www-form-urlencoded';
196             return errpage(
197             $env, [400, "Unsupported request content type '$ct'"])
198             unless $ct eq 'application/json' ||
199             $ct eq 'text/yaml' && $self->{accept_yaml};
200             if ($ct eq 'application/json') {
201             #$log->trace('Request body is JSON');
202             ($decsuc,$decerr, $rreq->{args}) = __parse_json($preq->content);
203             return errpage(
204             $env, [400, "Invalid JSON in request body: $decerr"])
205             if !$decsuc;
206             #} elsif ($ct eq 'application/vnd.php.serialized') {
207             # #$log->trace('Request body is PHP serialized');
208             # request PHP::Serialization;
209             # eval { $args = PHP::Serialization::unserialize($body) };
210             # return errpage(
211             # $env, [400, "Invalid PHP serialized data in request body"])
212             # if $@;
213             } elsif ($ct eq 'text/yaml') {
214             ($decsuc,$decerr, $rreq->{args}) = __parse_yaml($preq->content);
215             return errpage(
216             $env, [400, "Invalid YAML in request body: $decerr"])
217             if !$decsuc;
218             }
219             }
220             }
221              
222             # special handling for php clients #1
223             my $rcua = $rreq->{ua};
224             if ($self->{deconfuse_php_clients} &&
225             $rcua && $rcua =~ $self->{php_clients_ua_re}) {
226             if (ref($rreq->{args}) eq 'ARRAY' && !@{ $rreq->{args} }) {
227             $rreq->{args} = {};
228             }
229             }
230              
231             return errpage(
232             $env, [400, "Riap request key 'args' must be hash"])
233             unless !defined($rreq->{args}) || ref($rreq->{args}) eq 'HASH'; # sanity
234              
235             # get uri from 'match_uri' config
236             my $mu = $self->{match_uri};
237             if ($mu && !exists($rreq->{uri})) {
238             my $uri = $env->{REQUEST_URI};
239             my %m;
240             if (ref($mu) eq 'ARRAY') {
241             $uri =~ $mu->[0] or return errpage(
242             $env, [404, $self->{match_uri_errmsg} //
243             "Request does not match match_uri[0] $mu->[0]"]);
244             %m = %+;
245             $mu->[1]->($env, \%m);
246             } else {
247             $uri =~ $mu or return errpage(
248             $env, [404, $self->{match_uri_errmsg} //
249             "Request does not match match_uri $mu"]);
250             %m = %+;
251             for (keys %m) {
252             $rreq->{$_} //= $m{$_};
253             }
254             }
255             if (defined $rreq->{uri}) {
256             $rreq->{uri} =~ s!\A\Q$self->{server_path}!!;
257             }
258             }
259              
260             # get riap request key from form variables (optional)
261             if ($self->{parse_form}) {
262             my $form = $preq->parameters;
263             $env->{'periahs._form_cache'} = $form;
264              
265             # special name 'callback' is for jsonp
266             if (($rreq->{fmt} // $env->{"periahs.default_fmt"}) eq 'json' &&
267             defined($form->{callback})) {
268             return errpage(
269             $env, [400, "Invalid callback syntax, please use ".
270             "a valid JS identifier"])
271             unless $form->{callback} =~ /\A[A-Za-z_]\w*\z/;
272             $env->{"periahs.jsonp_callback"} = $form->{callback};
273             delete $form->{callback};
274             }
275              
276             while (my ($k, $v) = each %$form) {
277             if ($k =~ /(.+):j$/) {
278             $k = $1;
279             #$log->trace("CGI parameter $k (json)=$v");
280             ($decsuc, $decerr, $v) = __parse_json($v);
281             return errpage(
282             $env, [400, "Invalid JSON in query parameter $k: $decerr"])
283             if !$decsuc;
284             } elsif ($k =~ /(.+):y$/) {
285             $k = $1;
286             #$log->trace("CGI parameter $k (yaml)=$v");
287             return errpage($env, [400, "YAML form variable unacceptable"])
288             unless $self->{accept_yaml};
289             ($decsuc, $decerr, $v) = __parse_yaml($v);
290             return errpage(
291             $env, [400, "Invalid YAML in query parameter $k: $decerr"])
292             if !$decsuc;
293             #} elsif ($k =~ /(.+):p$/) {
294             # $k = $1;
295             # #$log->trace("PHP serialized parameter $k (php)=$v");
296             # return errpage($env, [400, "PHP serialized form variable ".
297             # "unacceptable"])
298             # unless $self->{accept_phps};
299             # require PHP::Serialization;
300             # eval { $v = PHP::Serialization::unserialize($v) };
301             # return errpage(
302             # $env, [400, "Invalid PHP serialized data in ".
303             # "query parameter $k: $@") if $@;
304             }
305             if ($k =~ /\A-riap-([\w-]+)/) {
306             my $rk = lc $1; $rk =~ s/-/_/g;
307             return errpage(
308             $env, [400, "Invalid Riap request key `$rk` (from form)"])
309             unless $rk =~ /\A\w+\z/;
310             $rreq->{$rk} = $v unless exists $rreq->{$rk};
311             } else {
312             $rreq->{args}{$k} = $v unless exists $rreq->{args}{$k};
313             }
314             }
315             }
316              
317             if ($self->{parse_reform} && $env->{'periahs._form_cache'} &&
318             $env->{'periahs._form_cache'}{'-submit'}) {
319             {
320             last unless $rreq->{uri};
321             my $res = $env->{'periahs._meta_res_cache'} //
322             $self->{riap_client}->request(meta => $rreq->{uri});
323             return errpage($env, [$res->[0], $res->[1]])
324             unless $res->[0] == 200;
325             $env->{'periahs._meta_res_cache'} //= $res;
326             my $meta = $res->[2];
327             last unless $meta;
328             last unless $meta->{args};
329              
330             require ReForm::HTML;
331             require Perinci::Sub::To::ReForm;
332             my $rf = ReForm::HTML->new(
333             spec => Perinci::Sub::To::ReForm::gen_form_spec_from_rinci_meta(
334             meta => $meta,
335             )
336             );
337             $res = $rf->get_data(psgi_env => $env);
338             return errpage($env, [$res->[0], $res->[1]])
339             unless $res->[0] == 200;
340             $rreq->{args} = $res->[2];
341             }
342             }
343              
344             if ($self->{parse_path_info}) {
345             {
346             last unless $rreq->{uri};
347             my $res = $env->{'periahs._meta_res_cache'} //
348             $self->{riap_client}->request(meta => $rreq->{uri});
349             return errpage($env, [$res->[0], $res->[1]])
350             unless $res->[0] == 200;
351             $env->{'periahs._meta_res_cache'} //= $res;
352             my $meta = $res->[2];
353             last unless $meta;
354             last unless $meta->{args};
355              
356             my $pi = $env->{PATH_INFO} // "";
357             $pi =~ s!^/+!!;
358             my @pi = map {uri_unescape($_)} split m!/+!, $pi;
359             $res = get_args_from_array(array=>\@pi, meta=>$meta);
360             return errpage(
361             $env, [500, "Bad metadata for function $rreq->{uri}: ".
362             "Can't get arguments: $res->[0] - $res->[1]"])
363             unless $res->[0] == 200;
364             for my $k (keys %{$res->[2]}) {
365             $rreq->{args}{$k} //= $res->[2]{$k};
366             }
367             }
368             }
369              
370             # defaults
371             $rreq->{v} //= 1.1;
372             $rreq->{fmt} //= $env->{"periahs.default_fmt"};
373             if (!$rreq->{action}) {
374             if ($rreq->{uri} =~ m!/$!) {
375             $rreq->{action} = 'list';
376             $rreq->{detail} //= 1;
377             } else {
378             $rreq->{action} = 'call';
379             }
380             }
381              
382             # sanity: check required keys
383             for (qw/uri v action/) {
384             defined($rreq->{$_}) or return errpage(
385             $env, [500, "Required Riap request key '$_' has not been defined"]);
386             }
387              
388             # add uri prefix
389             $rreq->{uri} = "$self->{riap_uri_prefix}$rreq->{uri}";
390              
391             # special handling for php clients #2
392             {
393             last unless $self->{deconfuse_php_clients} &&
394             $rcua && $rcua =~ $self->{php_clients_ua_re};
395             my $rargs = $rreq->{args};
396             last unless $rargs;
397              
398             # XXX this is repetitive, must refactor
399             my $res = $env->{'periahs._meta_res_cache'} //
400             $self->{riap_client}->request(meta => $rreq->{uri});
401             return errpage($env, [$res->[0], $res->[1]])
402             unless $res->[0] == 200;
403             $env->{'periahs._meta_res_cache'} //= $res;
404             my $meta = $res->[2];
405              
406             if ($meta->{args}) {
407             for my $arg (keys %$rargs) {
408             my $argm = $meta->{args}{$arg};
409             if ($argm && $argm->{schema}) {
410             # convert {} -> [] if function expects array
411             if (ref($rargs->{$arg}) eq 'HASH' &&
412             !keys(%{$rargs->{$arg}}) &&
413             $argm->{schema}[0] eq 'array') {
414             $rargs->{$arg} = [];
415             }
416             # convert [] -> {} if function expects hash
417             if (ref($rargs->{$arg}) eq 'ARRAY' &&
418             !@{$rargs->{$arg}} &&
419             $argm->{schema}[0] eq 'hash') {
420             $rargs->{$arg} = {};
421             }
422             }
423             }
424             }
425             }
426              
427             # Riap 1.2: decode base64-encoded args
428             decode_args_in_riap_req($rreq);
429              
430             $log->tracef("Riap request: %s", $rreq);
431              
432             # expose configuration for other middlewares
433             $env->{"middleware.PeriAHS.ParseRequest"} = $self;
434              
435             # continue to app
436             $self->app->($env);
437             }
438              
439             1;
440             # ABSTRACT: Parse Riap request from HTTP request
441              
442             __END__
443              
444             =pod
445              
446             =encoding UTF-8
447              
448             =head1 NAME
449              
450             Plack::Middleware::PeriAHS::ParseRequest - Parse Riap request from HTTP request
451              
452             =head1 VERSION
453              
454             This document describes version 0.60 of Plack::Middleware::PeriAHS::ParseRequest (from Perl distribution Perinci-Access-HTTP-Server), released on 2016-03-16.
455              
456             =head1 SYNOPSIS
457              
458             # in your app.psgi
459             use Plack::Builder;
460              
461             builder {
462             enable "PeriAHS::ParseRequest",
463             match_uri => m!^/api(?<uri>/[^?]*)!;
464             };
465              
466             =head1 DESCRIPTION
467              
468             This middleware's task is to parse Riap request from HTTP request (PSGI
469             environment) and should normally be the first middleware put in the stack.
470              
471             =head2 Parsing result
472              
473             The result of parsing will be put in C<< $env->{"riap.request"} >> hashref.
474              
475             Aside from that, this middleware also sets these for convenience of later
476             middlewares:
477              
478             =over 4
479              
480             =item * $env->{'periahs.default_fmt'} => STR
481              
482             Default output format, will be used for response if C<fmt> is not specified in
483             Rinci request. Determined using some simple heuristics, i.e. graphical browser
484             like Firefox or Chrome will get 'HTML', command-line browser like Wget or Curl
485             will get 'Text', others will get 'json'.
486              
487             =item * $env->{'periahs.jsonp_callback'} => STR
488              
489             From form variable C<callback>.
490              
491             =item * $env->{'periahs.riap_client'} => OBJ
492              
493             Store the Riap client (by default instance of L<Perinci::Access::Schemeless>).
494              
495             =back
496              
497             =head2 Parsing process
498              
499             B<From HTTP header and request body>. First parsing is done as per L<Riap::HTTP>
500             specification's requirement. All C<X-Riap-*> request headers are parsed for Riap
501             request key. When an unknown header is found, HTTP 400 error is returned. Then,
502             request body is read for C<args>. C<application/json> document type is accepted,
503             and also C<text/yaml> (if C<accept_yaml> configuration is enabled).
504              
505             Additionally, the following are also done:
506              
507             B<From URI>. Request URI is checked against B<match_uri> configuration (This
508             step will be skipped if B<match_uri> configuration is not set or empty). If URI
509             doesn't match this regex, a 404 error response is returned. It is a convenient
510             way to check for valid URLs as well as set Riap request keys, like:
511              
512             qr!^/api/(?<fmt>json|yaml)/!;
513              
514             The default C<match_uri> is qr/(?<uri>[^?]*)/.
515              
516             B<From form variables>. If B<parse_form> configuration is enabled, C<args>
517             request key will be set (or added) from GET/POST request variables, for example:
518             http://host/api/foo/bar?a=1&b:j=[2] will set arguments C<a> and C<b> (":j"
519             suffix means value is JSON-encoded; ":y" is also accepted if the C<accept_yaml>
520             configurations are enabled). In addition, request variables C<-riap-*> are also
521             accepted for setting other Riap request keys. Unknown Riap request key or
522             encoding suffix will result in 400 error.
523              
524             If request format is JSON and form variable C<callback> is defined, then it is
525             assumed to specify callback for JSONP instead part of C<args>. "callback(json)"
526             will be returned instead of just "json".
527              
528             B<From form variables (2, ReForm)>. PeriAHS has support for L<ReForm>. If
529             B<parse_reform> configuration is set to true and form variable C<-submit> is
530             also set to true, then the resulting C<args> from previous step will be further
531             fed to ReForm object. See the "parse_reform" in the configuration documentation.
532              
533             C<From URI (2, path info)>. If C<parse_path_info> configuration is enabled, and
534             C<uri> Riap request key has been set (so metadata can be retrieved), C<args>
535             will be set (or added) from URI path info. See "parse_path_info" in the
536             configuration documentation.
537              
538             http://host/api/v1/Module::Sub/func/a1/a2/a3
539              
540             will result in ['a1', 'a2', 'a3'] being fed into
541             L<Perinci::Sub::GetArgs::Array>. An unsuccessful parsing will result in HTTP 400
542             error.
543              
544             =for Pod::Coverage .*
545              
546             =head1 CONFIGURATIONS
547              
548             =over 4
549              
550             =item * riap_uri_prefix => STR (default: '')
551              
552             If set, Riap request C<uri> will be prefixed by this. For example, you are
553             exposing Perl modules at C<YourApp::API::*> (e.g. C<YourApp::API::Module1>. You
554             want to access this module via Riap request uri C</Module1/func> instead of
555             C</YourApp/API/Module1/func>. In that case, you can set B<riap_uri_prefix> to
556             C</YourApp/API/> (notice the ending slash).
557              
558             =item * server_host => STR
559              
560             Set server host. Used by B<get_http_request_url>. The default will be retrieved
561             from PSGI environment C<HTTP_HOST>.
562              
563             =item * server_port => STR
564              
565             Set server port. Used by B<get_http_request_url>. The default will be retrieved
566             from PSGI environment C<HTTP_HOST>.
567              
568             =item * server_path => STR (default: '/api')
569              
570             Set server URI path. Used by C<get_http_request_url>.
571              
572             =item * get_http_request_url => CODE (default: code)
573              
574             Should be set to code that returns HTTP request URL. Code will be passed
575             C<($self, $env, $rreq)>, where C<$rreq> is the Riap request hash. The default
576             code will return something like:
577              
578             http(s)://<SERVER_HOST>:<SERVER_PORT><SERVER_PATH><RIAP_REQUEST_URI>
579              
580             for example:
581              
582             https://cpanlists.org/api/get_list
583              
584             This code is currently used by the B<PeriAHS::Respond> middleware to print
585             text hints.
586              
587             Usually you do not need to customize this, you can already do some customization
588             by setting B<server_path> or B<riap_uri_prefix>, unless you have a more custom
589             URL scheme.
590              
591             =item * match_uri => REGEX or [REGEX, CODE] (default qr/.?/)
592              
593             This provides an easy way to extract Riap request keys (typically C<uri>) from
594             HTTP request's URI. Put named captures inside the regex and it will set the
595             corresponding Riap request keys, e.g.:
596              
597             qr!^/api(?<uri>/[^?]*)!
598              
599             If you need to do some processing, you can also specify a 2-element array
600             containing regex and code. When supplied this, the middleware will NOT
601             automatically set Riap request keys with the named captures; instead, your code
602             should do it. Code will be supplied ($env, \%match) and should set
603             $env->{'riap.request'} as needed. An example:
604              
605             match_uri => [
606             qr!^/api
607             (?: /(?<module>[\w.]+)?
608             (?: /(?<func>[\w+]+) )?
609             )?!x,
610             sub {
611             my ($env, $match) = @_;
612             if (defined $match->{module}) {
613             $match->{module} =~ s!\.!/!g;
614             $env->{'riap.request'}{uri} = "/$match->{module}/" .
615             ($match->{func} // "");
616             }
617             }];
618              
619             Given URI C</api/Foo.Bar/baz>, C<uri> Riap request key will be set to
620             C</Foo/Bar/baz>.
621              
622             =item * match_uri_errmsg => STR
623              
624             Show custom error message when URI does not match C<match_uri>.
625              
626             =item * accept_yaml => BOOL (default 0)
627              
628             Whether to accept YAML-encoded data in HTTP request body and form for C<args>
629             Riap request key. If you only want to deal with JSON, keep this off.
630              
631             =item * parse_form => BOOL (default 1)
632              
633             Whether to parse C<args> keys and Riap request keys from form (GET/POST)
634             variable of the name C<-x-riap-*> (notice the prefix dash). If an argument is
635             already defined (e.g. from request body) or request key is already defined (e.g.
636             from C<X-Riap-*> HTTP request header), it will be skipped.
637              
638             =item * parse_reform => BOOL (default 0)
639              
640             Whether to parse arguments in C<args> request key using L<ReForm>. Even if
641             enabled, will only be done if C<-submit> form variable is set to true.
642              
643             This configuration is used only if you render forms using ReForm and want to
644             process the submitted form.
645              
646             Form specification will be created (converted) from C<args> property in function
647             metadata, which means that a C<meta> Riap request to the backend will be
648             performed first to get this metadata.
649              
650             =item * parse_path_info => BOOL (default 0)
651              
652             Whether to parse arguments from $env->{PATH_INFO}. Note that this will require a
653             Riap C<meta> request to the backend, to get the specification for function
654             arguments. You'll also most of the time need to prepare the PATH_INFO first.
655             Example:
656              
657             parse_path_info => 1,
658             match_uri => [
659             qr!^/ga/(?<mod>[^?/]+)(?:
660             /?(?:
661             (?<func>[^?/]+)?:
662             (<pi>/?[^?]*)
663             )
664             )!x,
665             sub {
666             my ($env, $m) = @_;
667             $m->{mod} =~ s!::!/!g;
668             $m->{func} //= "";
669             $env->{'riap.request'}{uri} = "/$m->{mod}/$m->{func}";
670             $env->{PATH_INFO} = $m->{pi};
671             },
672             ]
673              
674             =item * riap_client => OBJ
675              
676             By default, a L<Perinci::Access::Schemeless> object will be instantiated (and
677             later put into C<$env->{'periahs.riap_client'}> for the next middlewares) to
678             perform Riap requests. You can supply a custom object here, for example the more
679             general L<Perinci::Access> object to allow requesting from remote URLs.
680              
681             =item * use_tx => BOOL (default 0)
682              
683             Will be passed to L<Perinci::Access::Schemeless> constructor.
684              
685             =item * custom_tx_manager => STR|CODE
686              
687             Will be passed to L<Perinci::Access::Schemeless> constructor.
688              
689             =item * php_clients_ua_re => REGEX (default: qr(Phinci|/php|php/)i)
690              
691             What regex should be used to identify PHP Riap clients. Riap clients often
692             (should) send C<ua> key identifying itself, e.g. C<Phinci/20130308.1>,
693             C<Perinci/0.12>, etc.
694              
695             =item * deconfuse_php_clients => BOOL (default: 1)
696              
697             Whether to do special handling for PHP Riap clients (identified by
698             C<php_clients_ua_re>). PHP clients often confuse empty array C<[]> with empty
699             hash C<{}>, since both are C<Array()> in PHP. If this setting is turned on, the
700             server makes sure C<args> becomes C<{}> when client sends C<[]>, and C<{}>
701             arguments become C<[]> or vice versa according to hint provided by function
702             metadata.
703              
704             =back
705              
706             =head1 HOMEPAGE
707              
708             Please visit the project's homepage at L<https://metacpan.org/release/Perinci-Access-HTTP-Server>.
709              
710             =head1 SOURCE
711              
712             Source repository is at L<https://github.com/sharyanto/perl-Perinci-Access-HTTP-Server>.
713              
714             =head1 BUGS
715              
716             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Perinci-Access-HTTP-Server>
717              
718             When submitting a bug or request, please include a test-file or a
719             patch to an existing test-file that illustrates the bug or desired
720             feature.
721              
722             =head1 SEE ALSO
723              
724             L<Perinci::Access::HTTP::Server>
725              
726             =head1 AUTHOR
727              
728             perlancar <perlancar@cpan.org>
729              
730             =head1 COPYRIGHT AND LICENSE
731              
732             This software is copyright (c) 2016 by perlancar@cpan.org.
733              
734             This is free software; you can redistribute it and/or modify it under
735             the same terms as the Perl 5 programming language system itself.
736              
737             =cut