This is the crucial feature of PEF::Font.
/submitUserLogin # model/UserLogin.yaml --- params: ip: value: context.ip login: min-size: 1 max-size: 40 password: min-size: 4 max-size: 40 model: User::login result: OK: set-cookie: auth: value: TT response.auth expires: TT response.expires redirect: /me DEFAULT: unset-cookie: auth /ajaxGetArticles # model/GetArticles.yaml --- params: ip: value: context.ip limit: regex: ^\d+$ max-size: 3 offset: regex: ^\d+$ max-size: 10 model: Article::get_articles
For every internal API method there's one YAML-file describing its parameters, handler method, caching and result processing.
There're two sources to call these methods from: templates and HTTP requests.
To call this method from template you write it like this:
[% articles = "get articles".model(offset => articles_offset, limit => 5) %]
To call this method from AJAX you send HTTP request like this:
GET /ajaxGetArticles?offset=0&limit=5
"Normal" method name is lowercased phrase with spaces. Model methods description file name is made up from method name transformed to CamelCase with '.yaml' extension: "get articles" -> "GetArticles.yaml". HTTP request method name is made up concatenating one of prefixes '/ajax', '/submit', '/get' and CamelCase form of method name.
Section "params" describes method input parameters, their sources, types and checks. There're two parameter's attribute to set source: value and default. value means unconditionally set value; default means that value will be set only if it was not set from request query or form data parameter.
Framework automatically decodes input data into internal Perl UTF-8 and automatically encodes output to UTF-8. This is the only supported encoding.
This is the default. There's no difference between parameters from query string and form data. But when the same parameter is in query and form data then value from query has precedence.
There's also special parameter json that has to be encoded JSON value. When it's present, then parameters are overwritten from this decoded JSON.
Request parsing detects JSON or XML content-types and can parse them.
value and default can be string or integer.
--- params: ip: default: 127.0.0.1 is_active: default: 0
context is a hash with some data for handlers. It is created after initial routing processing before template or API method processing. There're some data:
IP address of the client.
Short (ISO 639-1) language code. There's automatic language detection based on URL, HTTP headers and cookies and Geo IP. You can turn it off. It's written to 'lang' cookie.
Hostname of current request.
Current URI path.
Initial URI path.
Method name.
URL scheme. One of: 'http', 'https'.
"Source" of the request. One of: 'app', 'ajax', 'submit', 'get'.
They are also parts of context but they can't be used as values by themselves. They are used in handlers.
session is loaded only if it was used for parameter value.
session
For template processing "method" is replaced with "template" which is template name.
These are additional fields for template processing. time is current UNIX-time, gmtime - 9-element list with the time in GMT, localtime - 9-element list with the time for the local time zone. See perldoc -f for these functions.
time
gmtime
localtime
perldoc -f
Example:
--- params: ip: value: context.ip lang: default: context.lang
By default parameter "param1" is set from "param1" query/form data, but it's possible to set it from another request parameter, like "another_param". form is meant for this.
--- params: ip: value: context.ip lang: default: context.lang login: value: form.username
--- params: ip: value: context.ip back_url: default: headers.referer
--- params: ip: value: context.ip auth: default: cookies.auth
Routing subroutines can set some notes on request object. Notes are just any key-value pairs.
--- params: ip: value: context.ip subsection: default: notes.subsection
From request parameters or from cookies using cfg_session_request_field is possible to automatically load value from session data.
--- params: ip: value: context.ip user_last_name: default: session.user_last_name
You can specify any configuration parameter of framework or your application.
--- params: path: value: config.avatar_images_path
Ther're several types of input data checks.
This is the mostly used method. Regexp::Common with option 'RE_ALL' is already loaded. When you write an expressions as parameter value then it means regexp check.
--- params: positive_integer: ^\d+$ integer_or_empty: ^\d+$|^$ any_integer: ^$RE{num}{int}$ money: ^$RE{num}{decimal}{-places=>"0,2"}$
When you need to add some other attribute then attribute 'regex' is used:
--- params: lang: default: context.lang regex: ^[a-z]{2}$
Atributes can, can_string and can_number describes set of possible values. can and can_string are synonyms.
can
can_string
can_number
--- params: bool: can_number: [0, 1] default: 0 lang: can: [en, de] default: de
Parameter can have type 'array', 'hash' or 'file'. You can specify this with last symbol of the parameter name '@', '%' or '*' or with attribute 'type' as 'array', 'hash' or 'file'. To submit array you can use PHP-syntax:
<!-- HTML --> <select name="select[]" multiple="multiple"> # YAML --- params: select@:
Another way to submit array or hash is to use json form data field or to post JSON or XML content.
json
Array of files has type 'array'.
This checks are working according to parameter type:
Min/max values are included in allowed range.
--- params: limit40str: max-size: 40 limit4_40str: min-size: 4 max-size: 40
Numerical checks. Min/max values are included in allowed range.
--- params: speed: max: 140 min: 20
Captcha validation process usually removes information from captcha database and this check can be done only once. Validation process makes all needed checks.
This works following way. One parameter contains entered captcha code and another parameter contains hashed data of the right captcha code. Attribute captcha specifies parameter with hashed data of the right captcha code. Validator checks whether the code is right. If entered captcha is equal to 'nocheck' then it means to validator 'no need to check captcha' and no check is done. If captcha is required then handler must check that entered captcha code is not equal to 'nocheck'.
captcha
Attribute optional specifies whether parameter is optional. Special value 'empty' means parameter is optional even when passed but contains only empty string.
optional
Filter can be a Perl subroutine, regular expression substitution or array of regular expression substitutions.
--- params: auth: default: cookies.auth max-size: 40 filter: Auth::required comment: max-size: 1000 filter: [ s/</</g, s/>/>/g ]
Recognized substitution operators are: s, tr and y.
s
tr
y
Auth::required actually means subroutine required($value, $context) from your module ${YourApplicationNamespace}::InFilter::Auth.
Auth::required
required($value, $context)
${YourApplicationNamespace}::InFilter::Auth
I.e.:
+ $project_dir/ + $app/ + $Project/ - AppFrontConfig.pm + InFilter/ - Auth.pm
Your subroutine recieves 2 arguments: value of the parameter and request context.
package MyApp::InFilter::Auth; use DBIx::Struct; use MyApp::Common; sub required { my ($value, $context) = @_; my $author = get_author_from_auth($value); die { result => "NEED_LOGIN", answer => 'You have to login for this operation' } unless $author; $value; }
When filter dies for optional parameter then this parameter is deleted from request as if it was not provided. For required fields this means failed validation.
There's special YAML-file -base-.yaml in your cfg_model_dir where you can write all common requests parameters and just use these descriptions in your model methods description files.
-base-.yaml --- params: ip: value: context.ip auth: default: cookies.auth max-size: 40 auth_required: base: auth filter: Auth::required limit: regex: ^\d+$ max-size: 3 offset: regex: ^\d+$ max-size: 10 positive_integer: ^\d+$ integer_or_empty: ^\d+$|^$ any_integer: ^$RE{num}{int}$ bool: can_number: [0, 1] default: 0 money: ^$RE{num}{decimal}{-places=>"0,2"}$
Attribute base allows to specify "inheritance" of the properties for corresponding parameter from -base-.yaml. It works even inside -base-.yaml. When you write an expressions with leading $ as parameter value then it means "inheritance".
base
$
--- params: ip: $ip auth: base: $auth optional: true id_article: $positive_integer id_comment_parent: base: $integer_or_empty filter: Empty::to_undef author: base: $limit40str min-size: 1 filter: Default::auth_to_author
Special key extra_params controls what to do when there're more parameters as required: ignore - all extra parameters will be silently deleted; pass - all extra parameters will be passed without validation; disallow - validation fails when extra parameters passed.
extra_params
--- params: ip: $ip extra_params: pass
Handlers are supposed to return hash like these.
{ result => "OK", }
or
{ result => "SOMEERRCODE", answer => 'Some $1 Error $2 Message $3', answer_args => [$some, $error, $params], }
result is required in every answer, other keys are optional.
result
Following keys have some meaning:
Is symbolic result state.
Application can use its own codes.
Message from application. If calling type is /ajax and answer_no_nls is not present or false then this message will be automatically localized.
/ajax
answer_no_nls
When calling type is /get or /submit then value of this key can be open file handle or code reference. If value of this key is code reference then it is "streaming function". See "Delayed Response and Streaming Body" in PSGI.
/get
/submit
Array of arguments to the message.
Array of key-value pairs that will be set as output HTTP headers and their values. Key-value pairs can be list ($header => $value), array [$header => $value] or hash {$header => $value}.
[ {'X-Hr' => 'x-hr'}, ['X-Ar' => 'x-ar'], 'X-Header' => 'x-value' ]
Array of key-value pairs that will be set as output HTTP cookies and their values. Key-value pairs can be list ($cookie => $value), array [$cookie => $value] or hash {$cookie => $value}.
[ {ch => 'Chv'}, [ca => 'Cav'], Cookie => 'cookie_value' ]
If present and true then answer will be send as is, without localization attempt.
answer
Sets HTTP status code. If answer_status is between 300 and 400 then Location headers specifys new location for redirect.
answer_status
Location
Sets Content-Type header.
Replaces answer hash with answer_data field before encoding to JSON. It can be only ARRAY or HASH reference.
answer_data
Uses supplied HTTP response object as handler response.
When method is called like /ajax then it means JSON format answer. When you need another output format, use /get or /submit type calls.
Some methods can return constant or rarely changing data, it makes perfect sense to cache them. Key cache manages caching for responses. It has to attributes: key - one value or array defining caching key; expires - how long data can be retained in cache. This value is parsed by Time::Duration::Parse.
cache
key
expires
--- params: id_user: $positive_integer cache: key: [method, id_user] expires: 1m
Response key result defines result's section to execute some actions. When no section is found then it looks for DEFAULT section. Following actions are supported:
DEFAULT
Temporary browser redirect (302) for /get or /submit request types. Can be array or values - it finds first non-empty.
Possible attributes: value, expires, domain, path, secure, max-age, httponly.
value
domain
path
secure
max-age
httponly
secure attribute can be calculated automatically from request's scheme when setting or unsetting cookie if not explicitly set in attributes.
Unsets cookie. It can be one cookie name, list of cookies or hash of cookies with their attributes as for set-cookie.
set-cookie
Cookie with attributes can be required when cookie was set with some attributes like domain, path, secure. In this case you have to nail the right cookie.
Usually you don't need to set any attributes and then unset will work automatically.
Specifies output filter.
Sets answer content.
Sets response header. This action ensures that there's only one header with given name in response.
Adds response header. This action allows to have multiple headers with the same name.
--- params: back_url: default: headers.referer result: OK: set-cookie: auth: value: TT response.auth expires: TT response.expires redirect: /me DEFAULT: unset-cookie: auth redirect: TT request.back_url
Prefix TT means "process this expression with Template-Toolkit language". This prefix can be used for any attribute value in result section.
TT
Stash for this processing contains following data fields: response, form, cookies, context, request, result.
response
form
cookies
context
request
There're two additional helper functions: uri_unescape($uri) and session($key). $key is optional.
uri_unescape($uri)
session($key)
It's possible to specify filtering function for sending content. Attribute filter for given result code in result section specifies function that accepts two parameters: ($response, $context).
filter
result code
--- result: OK: filter: Export::toXML
Export::toXML actually means subroutine toXML($response, $context) from your module ${YourApplicationNamespace}::OutFilter::Export.
Export::toXML
toXML($response, $context)
${YourApplicationNamespace}::OutFilter::Export
$response must be modified "in-place", return value of subroutine is ignored.
Intended application is to transform response to external form, like XML, CSV, XLS and so on.
Key model sets calling model handler. Model can be "local" or "remote". "Local" means that handler is right inside loaded application. "Remote" can mean everything, like "call database method". "Remote" model is handled by cfg_model_rpc($model_handler). "Local" is located in some module inside ${YourApplicationNamespace}::Local::$Module.
model
cfg_model_rpc($model_handler)
${YourApplicationNamespace}::Local::$Module
When you need to call some handler outside of "Local::" namespace, prepend its name with '^', like ^Some::Lib::Module::handler.
^Some::Lib::Module::handler
--- params: ip: $ip login: base: limit40str min-size: 1 password: $limit4_40str model: Author::login
# $project_dir/$app/$Project/Local/Author.pm
package MyApp::Local::Author; sub login { my ($req, $context) = @_; my $author = one_row(author => {hash_ref_slice $req, qw(login password)}) or return {result => "PASS"}; my $auth = PEF::Front::Session::_secure_value; new_row( 'author_auth', id_author => $author->id_author, auth => $auth, expires => [\"now() + ?::interval", $expires] ); return { result => "OK", expires => $expires, auth => $auth }; }
Model handler that matches /^\^?\w+::/ is "local" otherwise it is "remote".
By default, cfg_model_rpc($model_handler) calls PEF::Front::Model::chain_links which accepts list of handler functions to execute with optional parameters. These functions receive ($req, $context, $previous_response[, $optional_params]). Return value from last function is the return value from model handler.
PEF::Front::Model::chain_links
($req, $context, $previous_response[, $optional_params])
Model method can be called from AJAX or template. URI path prefix (src in context) defines how method was called and what type of result is expected.
src
Answer will be in JSON format and redirects from result section are ignored.
Answer can be in any format and usually redirects to new location.
Works like /submit but by default parses rest of the path to parameters. See cfg_parse_extra_params($src, $params, $form) in PEF::Front::Config.
Method that is called from templates has src equal to 'app'.
Key allowed_source can restrict call types: template - for templates; submit - for /submit or /get; ajax for /ajax. Really useful distinction is when some method are allowed to be called only from templates.
allowed_source
template
submit
ajax
When this key is absent then there's no restrictions.
It's supposed to be used like this. You make method making captcha images:
Captcha.yaml:
--- params: width: default: 35 height: default: 40 size: default: 5 extra_params: ignore model: PEF::Front::Captcha::make_captcha
You use it in some template like this:
<form method="post" action="/submitSendMessage"> Captcha: [% captcha="captcha".model %] <input type="hidden" name="captcha_hash" value="[% captcha.code %]"> <img src="[% config('www_static_captchas_path') %][% captcha.code %].jpg"> <input type="text" maxlength="5" name="captcha_code"> ... </form>
SendMessage.yaml:
--- params: ip: $ip email: $email lang: $lang captcha_code: min-size: 5 captcha: captcha_hash captcha_hash: min-size: 5 subject: $subject message: $message result: OK: redirect: /appSentIsOk model: Article::add_comment allowed_source: [ajax, submit]
Your MyApp::Local::Article::add_comment subroutine must check that captcha_code is not equal to 'nocheck' if captcha is required. For example, if user is already logged in then it makes little sense to enter captcha.
captcha_code
Capthcha image is made by PEF::Front::Captcha::make_captcha($request, $context) function. This function writes generated images in cfg_www_static_captchas_dir, stores some information in its own database in cfg_captcha_db and returns generated md5 sum. When form is submitted, validator checks the entered code and when it is right, passes to the model method "send message". Captcha code checks are destructive: the code is not valid anymore after successful check.
PEF::Front::Captcha::make_captcha($request, $context)
To change generation image class see cfg_captcha_image_class in PEF::Front::Config.
Uploaded files are stored in some subdirectory in cfg_upload_dir. Corresponding parameter value is object of PEF::Front::File. This parameter can have attribute type 'file' to pass validation. Array of files can have attribute 'array'.
Uploaded file should be copied to some permanent storage. Usually you can just link it to new place. Uploaded files will be deleted after request destruction.
link
This module was written and is maintained by Anton Petrusevich.
Copyright (c) 2016 Anton Petrusevich. Some Rights Reserved.
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
To install PEF::Front, copy and paste the appropriate command in to your terminal.
cpanm
cpanm PEF::Front
CPAN shell
perl -MCPAN -e shell install PEF::Front
For more information on module installation, please visit the detailed CPAN module installation guide.