The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

PEF::Front model methods description

This is the crucial feature of PEF::Font.

SYNOPSIS

  /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


 

DESCRIPTION

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.

INPUT PARAMETERS

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.

Possible sources

from query string or form data

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.

Direct value

value and default can be string or integer.

  ---
  params:
    ip:
      default: 127.0.0.1
    is_active:
      default: 0
from context data

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

IP address of the client.

lang

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

Hostname of current request.

path

Current URI path.

path_info

Initial URI path.

method

Method name.

scheme

URL scheme. One of: 'http', 'https'.

src

"Source" of the request. One of: 'app', 'ajax', 'submit', 'get'.

form, headers, cookies, session and request

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.

template

For template processing "method" is replaced with "template" which is template name.

time, gmtime, localtime

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.

Example:

  ---
  params:
    ip:
      value: context.ip
    lang:
      default: context.lang
from request parameters

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
from headers
  ---
  params:
    ip:
      value: context.ip
    back_url:
      default: headers.referer
from cookies
  ---
  params:
    ip:
      value: context.ip
    auth:
      default: cookies.auth
from request notes

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 session data

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
from configuration parameter

You can specify any configuration parameter of framework or your application.

  ---
  params:
    path:
      value: config.avatar_images_path

Checks

Ther're several types of input data checks.

Perl regular expressions

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}$
Possible values

Atributes can, can_string and can_number describes set of possible values. can and can_string are synonyms.

  ---
  params:
    bool:
      can_number: [0, 1]
      default: 0
    lang:
      can: [en, de]
      default: de
Type

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.

Array of files has type 'array'.

Maximum or minimum size

This checks are working according to parameter type:

string length for scalars
array size for arrays
file size for files

Min/max values are included in allowed range.

  ---
  params:
    limit40str:
      max-size: 40
    limit4_40str:
      min-size: 4
      max-size: 40
Maximum or minimum value

Numerical checks. Min/max values are included in allowed range.

  ---
  params:
    speed:
      max: 140
      min: 20
captcha

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'.

Optional flag

Attribute optional specifies whether parameter is optional. Special value 'empty' means parameter is optional even when passed but contains only empty string.

Custom filter

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/</&lt;/g, s/>/&gt;/g ]

Recognized substitution operators are: s, tr and y.

Auth::required actually means subroutine required($value, $context) from your module ${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.

Kind of inheritance

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".

  ---
  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

Extra parameters

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.

  ---
  params:
    ip: $ip
  extra_params: pass

Output result

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.

Following keys have some meaning:

result

Is symbolic result state.

"OK" means everything is good
"INTERR" means some internal application error
"OAUTHERR" means Oauth protocol error
"BADPARAM" means validation error

Application can use its own codes.

answer

Message from application. If calling type is /ajax and answer_no_nls is not present or false then this message will be automatically localized.

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.

answer_args

Array of arguments to the message.

answer_headers

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'
  ]
answer_cookies

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'
  ]
answer_no_nls

If present and true then answer will be send as is, without localization attempt.

answer_status

Sets HTTP status code. If answer_status is between 300 and 400 then Location headers specifys new location for redirect.

answer_content_type

Sets Content-Type header.

answer_data

Replaces answer hash with answer_data field before encoding to JSON. It can be only ARRAY or HASH reference.

answer_http_response

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.

Response caching

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.

  ---
  params:
    id_user: $positive_integer
  cache:
    key: [method, id_user]
    expires: 1m

Result processing

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:

redirect

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.

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.

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.

filter

Specifies output filter.

answer

Sets answer content.

set-header

Sets response header. This action ensures that there's only one header with given name in response.

add-header

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.

Stash for this processing contains following data fields: response, form, cookies, context, request, result.

There're two additional helper functions: uri_unescape($uri) and session($key). $key is optional.

Output filter

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).

  ---
  result:
    OK:
      filter: Export::toXML

Export::toXML actually means subroutine toXML($response, $context) from your module ${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.

Model handler

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.

When you need to call some handler outside of "Local::" namespace, prepend its name with '^', like ^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.

Method call types

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.

/ajax

Answer will be in JSON format and redirects from result section are ignored.

/submit

Answer can be in any format and usually redirects to new location.

/get

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.

When this key is absent then there's no restrictions.

Captcha

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.

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.

To change generation image class see cfg_captcha_image_class in PEF::Front::Config.

File upload

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.

AUTHOR

This module was written and is maintained by Anton Petrusevich.

Copyright and License

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.