/docs/yolk.txt
Plain Text | 2182 lines | 1296 code | 886 blank | 0 comment | 0 complexity | 40f173b56532aabd2b6c1e4535d4b6f7 MD5 | raw file
Possible License(s): AGPL-1.0
Large files files are truncated, but you can click here to view the full file
- Yolk Manual
- Revised December 11th. 2012
- Table of Contents
- General Information
- 1 Copyright and License
- This document is copyright (C) 2010-, Thomas Løcke. You may copy
- this document, in whole or in part, in any form or by any means,
- as is or with alterations, provided that (1) alterations are
- clearly marked as alterations and (2) this copyright notice is
- included unmodified in any copy.
- Yolk is GPLv3 software. You should have received a copy of the
- GNU General Public License and a copy of the GCC Runtime Library
- Exception along with this program; see the files COPYING3 and
- COPYING.RUNTIME respectively. If not, see [http://www.gnu.org/licenses/||http://www.gnu.org/licenses/]
- .
- 2 What is Yolk?
- Yolk is a collection of packages that aim to help build solid
- web-applications using Ada. Yolk itself doesn't do a whole lot
- that can't be accomplished simply by using [http://libre.adacore.com/libre/tools/aws/||AWS]
- and the [http://libre.adacore.com/libre/tools/gnat-component-collection/||GNAT Component Collection (GNATcoll)]
- , but it does make the job of building complete web-applications
- a bit simpler. Things like changing user for the running
- application, catching POSIX signals such as SIGKILL, sending log
- data to syslogd, adding basic static content handlers, creating
- and starting/stopping an AWS powered HTTP server and building
- Atom syndication XML are all made a bit easier with Yolk.
- A Yolk application is in reality an AWS application, with some
- sugar added, so you're not really building a Yolk
- web-application, as much as you're building an AWS
- web-application. What I'm getting at, is that you need to
- understand how to use AWS, in order for Yolk to make any kind of
- sense. What you get when using Yolk is the little things that AWS
- does not readily provide.
- 2.1 The Yolk demo application
- Reading this manual will of course (I hope!) help you understand
- how to use Yolk, but please consider taking a closer look at the
- Yolk demo application to get a feel for how Yolk is actually
- used. The demo is heavily commented, so it should be fairly easy
- to understand what's going on. The demo application is also very
- suitable as a foundation for other AWS/Yolk applications.
- It is much easier to show how to use Yolk, than it is to write
- down all possible usage scenarios. With the combination of this
- manual, the Yolk source files and the demo application, you
- should be able to make full use of the Yolk packages in your own
- applications.
- 2.2 The source code
- The Yolk source code is the best documentation there is. This
- document is never going to be as comprehensive as the actual
- source, so I'll strongly suggest having the source code available
- as you read this document. What you will find in this document
- are short descriptions of what a package is meant to do and
- perhaps small usage examples, not a complete rundown of every
- type and procedure in a package.
- 2.3 Building and installing Yolk
- See the README and INSTALL files. These are found in the Yolk
- root directory.
- 2.4 The files Yolk depend upon
- When you read this document and the Yolk source code, you'll
- notice that quite a few packages depend on various files being
- available at specified locations. This is for example the case
- with the Yolk.Whoops package that expects its template file to be
- found at the path templates/system/500.tmpl
- All such “dependencies” will of course be noted accordingly as we
- go along, but instead of forgetting one or more in your own
- application, I'd much rather encourage using the demo application
- as a foundation for your own applications, since all these fixed
- paths and files has been properly added to the demo.
- I also recommend compiling and running the demo, to make sure
- your Yolk install is working as intended. Just read the
- demo/README and demo/INSTALL files for instructions on how to get
- it up and running.
- 2.5 The Yolk packages naming
- The Yolk packages are pretty diverse, ranging from process
- control to sending email. I've tried naming them as sensibly as
- possible, in the hope that the package names alone give away
- their function. If I've failed, well, you're just going to have
- to refer to this document or take a look at the source for
- yourself.
- The Yolk Packages
- 3 Yolk
- The Yolk main package currently only contain only a few things:
- The Yolk Version string, a Config_File function to get the
- location of the configuration file and a PID_File function to get
- the location of the PID file. These are used in a few places, for
- example in the directory.tmpl template file (the version string),
- in the Yolk.Configuration package (the Config_File function) and
- in the Yolk.Process_Control package (the PID_File function).
- All Yolk applications accepts two commandline arguments:
- • --yolk-config-file : Defines the location of the configuration
- file. If empty or not set, then use the default location
- configuration/config.ini.
- • --pid-file : Defines the location of the PID file. If empty or
- not set, then don't write a PID file. Note that if you use the
- extras/rc.yolk script to control your application, then this is
- handled for you transparently.
- 4 Yolk.Cache.Discrete_Keys
- If a piece of data doesn't change very often and it is expensive
- to build, then caching it might be worthwhile. Instead of going
- to a file or database on every hit, you simply go to the cache
- and grab the latest version from there. This is very fast, at the
- cost of some memory.
- If you know exactly what you want to cache, the
- Yolk.Cache.Discrete_Keys package might be just what you need.
- 4.1 The generic formal parameters
- These are:
- generic
- type Key_Type is (<>);
- type Element_Type is private;
- Max_Element_Age : Duration := 3600.0;
- package Yolk.Cache.Discrete_Keys is
- ...
- The Max_Element_Age defaults to one hour. You should obviously
- set this to whatever suits your needs. This timer is used for all
- content in the cache. You cannot set this individually for each
- element.
- 4.2 Instantiation
- If for example we have two different sets of data (Foo and Bar)
- that are expensive to build, we can instantiate a Discrete_Keys
- package to handle this:
- type Cache_Keys is (Foo, Bar);
- package My_Cache is new Yolk.Cache.Discrete_Keys
- (Key_Type => Cache_Keys,
- Element_Type => Unbounded_String);
- And that is all. We now have a My_Cache object that can hold two
- objects: Foo and Bar. These are of the type Unbounded_String and
- they have a Max_Element_Age of 3600.0 seconds.
- 4.3 Writing to the cache
- Before we can read something from the cache, we must first write
- something to it:
- declare
- Foo_Value : Unbounded_String := To_Unbounded_String ("Foo");
- begin
- My_Cache.Write (Key => Foo,
- Value => Foo_Value);
- end;
- That is all it takes: “Foo” is now safely tucked away in the
- My_Cache object, and will be so for 3600.0 seconds. Calling Write
- with the Foo key will always overwrite earlier written Foo
- elements, no matter their age.
- 4.4 Reading from the cache
- A cache obviously only makes sense if you intend to read from it.
- In our case we want to get our hands on the previously written “
- Foo” value:
- declare
- Valid : Boolean := False;
- Value : Unbounded_String;
- begin
- My_Cache.Read (Key => Foo,
- Is_Valid => Valid,
- Value => Value);
- if Valid then
- -- do something interesting with the data
- else
- -- the Foo data is invalid.
- end if;
- end;
- In order for an element to be valid (the Is_Valid parameter is
- true), it must:
- 1. have been added to the cache in the first place
- 2. be younger than Max_Element_Age
- If Is_Valid is False, then Value is undefined. Note that if
- Is_Valid is False then Key is removed from the cache, if it
- exists.
- 4.5 Checking if a key is valid
- If you need to check whether a specific key exists in the cache
- and is valid, then you must use the Is_Valid function.
- if My_Cache.Is_Valid (Foo) then
- -- Foo is good!
- else
- -- Foo is bad!
- end if;
- This follows the same rules as the Is_Valid parameter for the
- Read procedure.
- 4.6 Clearing keys and the entire cache
- For clearing of keys and the entire cache we have, naturally, two
- Clear procedures:
- -- First we clear the Foo key
- My_Cache.Clear (Key => Foo);
- -- And then we clear the entire cache
- My_Cache.Clear;
- And that's all it takes.
- 4.7 Cleanup - Getting rid of stale elements
- Calling Cleanup will delete all stale elements from the cache:
- My_Cache.Cleanup;
- Note that this is potentially a very expensive operation if the
- cache is large, as the entire cache is iterated and every element
- tested for its age. Use with care.
- 5 Yolk.Cache.String_Keys
- This package is almost similar to the Yolk.Cache.Discrete_Keys
- package. The biggest difference is that where the Discrete_Keys
- cache package requires that you define a type for the keys, this
- package use regular String as keys.
- The implications of this difference between the two cache
- packages are subtle. Both have the same Read, Write, Is_Valid and
- Clear procedures and functions, so in that sense the two packages
- are the same. The biggest difference lies in the available
- generic formal parameters and the functionality of the Cleanup
- procedure.
- 5.1 The generic formal parameters
- These are:
- generic
- type Element_Type is private;
- Cleanup_Size : Positive := 200;
- Cleanup_On_Write : Boolean := True;
- Max_Element_Age : Duration := 3600.0;
- Reserved_Capacity : Positive := 100;
- package Yolk.Cache.Discrete_Keys is
- ...
- When the amount of elements in the cache >= Cleanup_Size, then
- the Cleanup procedure is called by Write, if Cleanup_On_Write is
- set to Boolean True. Cleanup_Size is a sort of failsafe for this
- cache package. Since we can't know for sure what is being added
- (we don't know the keys beforehand), we need to make sure it
- doesn't gobble up all available resources. Set this number high
- enough that it'll never tricker under normal circumstances, but
- low enough that it'll prevent resource exhaustion in case of
- errors.
- The Max_Element_Age defaults to one hour. You should obviously
- set this to whatever suits your needs. This timer is used for all
- content in the cache. You cannot set this individually for each
- element.
- Reserved_Capacity should be set as close as possible to the
- expected final size of the cache. If your best guestimate is 200
- elements in the cache, then set this to 200. Note that this
- setting has no bearing on the actual size of the cache. The cache
- will happily grow beyond the Reserved_Capacity value.
- 5.2 Instantiation
- Instantiating String_Keys is done like this:
- package My_Cache is new Yolk.Cache.String_Keys
- (Element_Type => Unbounded_String,
- Reserved_Capacity => 200);
- And that is all. We now have a My_Cache object that can hold
- objects of the type Unbounded_String, all of which have a
- Max_Element_Age of 3600.0 seconds. Also we've told the cache to
- set aside at least 200 positions for content.
- 5.3 Writing to the cache
- Before we can read something from the cache, we must first write
- something to it:
- declare
- Value : Unbounded_String := To_Unbounded_String ("42");
- begin
- My_Cache.Write (Key => "Foo",
- Value => Value);
- end;
- “42” is now safely tucked away in the My_Cache object under the
- key “Foo”, and will be so for 3600.0 seconds. Calling Write with
- the “Foo” String will always overwrite earlier written “Foo”
- elements, no matter their age.
- 5.4 Reading from the cache
- A cache obviously only makes sense if you intend to read from it.
- In our case we want to get our hands on the previously written “
- Foo” value:
- declare
- Valid : Boolean := False;
- Value : Unbounded_String;
- begin
- My_Cache.Read (Key => "Foo",
- Is_Valid => Valid,
- Value => Value);
- if Valid then
- -- do something interesting with the data
- else
- -- the Foo data is invalid.
- end if;
- end;
- In order for an element to be valid (the Is_Valid parameter is
- true), it must:
- 1. have been added to the cache in the first place
- 2. be younger than Max_Element_Age
- If Is_Valid is False, then Value contains undefined garbage. Note
- that if Is_Valid is False then Key is removed from the cache, if
- it exists.
- 5.5 Checking if a key is valid
- If you need to check whether a specific key exists in the cache
- and is valid, then you need to use the Is_Valid function.
- if My_Cache.Is_Valid ("Foo") then
- -- Foo is good!
- else
- -- Foo is bad!
- end if;
- This follows the same rules as the Is_Valid parameter for the
- Read procedure.
- 5.6 Clearing keys and the entire cache
- For clearing of keys and the entire cache we have, naturally, two
- Clear procedures:
- -- First we clear the Foo key
- My_Cache.Clear (Key => "Foo");
- -- And then we clear the entire cache
- My_Cache.Clear;
- 5.7 How much is in there?
- With the Discrete_Keys cache we obviously always know the exact
- amount of keys available, since we've defined the keys ourselves.
- This is not the case with the String_Keys cache, where any String
- can be a key. If we need to know how many elements that are
- currently in the cache, we call the Length function:
- if My_Cache.Length > 1000 then
- -- Woa! Lots of stuff in the cache..
- end if;
- Note that Length count both valid and invalid elements.
- 5.8 Cleanup - Keeping cache size in check
- if Cleanup_On_Write is True, then Cleanup is called by Write
- whenever the size of the cache reach Cleanup_Size. It is of
- course also possible to call it manually:
- if My_Cache.Length > 1000 then
- My_Cache.Cleanup;
- end if;
- If you've set Cleanup_On_Write to Boolean False and the String
- keys are coming from outside sources, then you really should make
- sure you call Cleanup on a regular basis.
- 6 Yolk.Command_Line
- This package enables you to fetch the value of a given
- commandline parameter using the Get function:
- function Get
- (Parameter : in String;
- Default : in String := "")
- return String;
- If Parameter is found Get will return the value immediately
- following Parameter. If Parameter isn't found Default is
- returned.
- 7 Yolk.Config_File_Parser
- This package enables you to access KEY/VALUE pairs in
- configuration files that are written in the style:
- # This is a comment
- -- This is also a comment
- KEY VALUE
- Keys are case-insensitive, so FOO, foo and fOo are all the same.
- Blank lines and comments are ignored and so is pre/postfixed
- whitespace. It is not necessary to quote values that contain
- whitespace, to this:
- KEY some value with whitespace
- is perfectly valid, and will return “some value with whitespace”
- when calling Get (KEY). If VALUE is Boolean True or False
- (case-insensitive), then the KEY can be returned as a String or a
- Boolean, depending on the target type. If the target type does
- not match the VALUE and no sensible conversion can be made, then
- a Conversion_Error exception is raised. No dummy values are
- returned at any time.
- To clear a default value, simply add the key to the configuration
- file, with no value set.
- 7.1 The generic formal parameters
- These are:
- generic
- use Ada.Strings.Unbounded;
- type Key_Type is (<>);
- type Defaults_Array_Type is array (Key_Type) of
- Unbounded_String;
- Defaults : in Defaults_Array_Type;
- Config_File : in String;
- package Yolk.Config_File_Parser is
- ...
- Config_File is of course the name and location of the
- configuration file.
- 7.2 Exceptions
- There are 3 different exceptions that can be raised by the
- Yolk.Config_File_Parser package. These are:
- • Unknown_Key. This is raised if an unknown key has been found in
- the configuration file given when instantiating the package or
- when Load_File is called.
- • Cannot_Open_Config_File. This is raised when Config_File cannot
- be read.
- • Conversion_Error. This is raised when a value cannot be
- converted to the target type, ie. the value “42” to a Boolean.
- 7.3 Instantiation
- Yolk.Config_File_Parser is a generic package, so in order to use
- it, you have to instantiate it, like this:
- package My_Configuration is
- type Keys is (Foo, Bar);
- type Defaults_Array is array (Keys) of Unbounded_String;
- Default_Values : constant Defaults_Array :=
- (Foo => To_Unbounded_String ("some foo"),
- Bar => To_Unbounded_String ("some bar"));
-
- package Config is new Yolk.Config_File_Parser
- (Key_Type => Keys,
- Defaults_Array_Type => Defaults_Array,
- Defaults => Default_Value,
- Config_File => "config.ini");
- end My_Configuration;
- Here we instantiate the Config package with config.ini as the
- configuration file. This means that KEY/VALUE pairs found in this
- file will overwrite the default values set in the Default_Values
- array. Setting a default value to Null_Unbounded_String means the
- value is empty.
- Note that the config.ini file does not have to contain all the
- valid keys. It is perfectly fine to only add those keys that have
- non-default values to the configuration file.
- 7.4 Re-loading configuration files
- With the Load_File procedure you can re-load a new configuration
- file into your Config package:
- My_Configuration.Config.Load_File ("new_config.ini");
- Now the KEY/VALUE pairs of new_config.ini will overwrite the ones
- originally found in the config.ini file the package was
- instantiated with. You can do this as many times as you like.
- Note that you cannot change what KEY's are valid, so if the
- new_config.ini file contains unknown keys, Load_File will raise
- the Unknown_Key exception.
- 7.5 Getting values
- With instantiation and loading of configuration files out of the
- way, it is now time to get to the configuration values. To get
- the value of the Foo key, you do:
- My_Configuration.Config.Get (Foo);
- There are Get functions for the following types:
- • Boolean
- • Duration
- • Float
- • Integer
- • String
- • Unbounded_String
- Empty keys simply return an empty String or a
- Null_Unbounded_String, depending on the target type. If a key is
- empty and the target type is not a String or an Unbounded_String,
- then the Conversion_Error exception is raised.
- 7.6 Checking if a KEY has a VALUE
- You can check if a key has a value with the Has_Value function:
- if Has_Value (Foo) then
- Put_Line ("Foo has a value");
- end if;
- Basically all this function does is return Boolean True if the
- value of the given key is not a Null_Unbounded_String.
- 8 Yolk.Configuration
- This package is a bit of an oddball, as all it does is
- instantiate the Yolk.Config_File_Parser generic with the default
- AWS and Yolk configuration values. This is used by Yolk
- internally, but also by the AWS component of your application.
- The instantiation looks like this:
- package Config is new Config_File_Parser
- (Key_Type => Keys,
- Defaults_Array_Type => Defaults_Array,
- Defaults => Default_Values,
- Config_File => Config_File);
- The Config_File function call return either the default Yolk
- configuration file (configuration/config.ini) or a user specified
- configuration file given by the --yolk-config-file command line
- argument, so starting for example the Yolk demo like this:
- ./yolk_demo --yolk-config-file /etc/yolk-config.ini
- will force the demo to look for the /etc/yolk-config.ini
- configuration file.
- There's a fully commented config.ini.dist file available in the
- extras/ directory. I recommend taking a look at the Yolk demo
- application to see how the Yolk.Configuration package is used.
- 8.1 Get the AWS specific configuration settings
- On some occassions it might be necessary to get the AWS
- configuration object. This can easily be accomplished by calling
- the Get_AWS_Configuration function:
- AWS_Config : constant AWS.Config.Object :=
- Yolk.Configuration.Get_AWS_Configuration;
- 9 Yolk.Email
- Using Yolk.Email and the child package Yolk.Email.Composer you
- can build and send more or less any kind of email:
- • Plain text
- • Multipart/Alternative
- • Multipart/Mixed
- The package supports adding multiple SMTP servers, meaning you
- can add as many as you need, and the email will then be send via
- the first one that accepts it.
- The Yolk.Email package define 4 exceptions and 3 types. The
- facilities for actually constructing and sending the email are
- found in Yolk.Email.Composer.
- 9.1 Exceptions
- These are:
- • Attachment_File_Not_Found. Is raised if a file attachment is
- not found at the given path.
- • No_Address_Set. Is raised if the address component of a To,
- Reply-To, From, Bcc/Cc header is missing.
- • No_Sender_Set_With_Multiple_From. Is raised when an email
- contains multiple From headers but no Sender header, as per
- RFC-5322, 3.6.2. http://tools.ietf.org/html/rfc5322
- • No_SMTP_Host_Set. Is raised if the SMTP host list is empty, ie.
- no SMTP host has been set for sending the email.
- 9.2 The Yolk.Email types
- When using Yolk.Email.Composer to build and send emails, three
- types declared in Yolk.Email are central:
- 1. Character_Set
- 2. Recipient_Kind
- 3. Structure
- The Character_Set type define what character set is used when
- data is added to an email Structure object. For example looking
- at the Yolk.Email.Composer.Add_From procedure, we see that the
- Charset parameter defaults to US_ASCII:
- procedure Add_From
- (ES : in out Structure;
- Address : in String;
- Name : in String := "";
- Charset : in Character_Set := US_ASCII);
- This does not mean that Yolk.Email.Composer.Add_From will encode
- Name as US_ASCII, instead it means that the data given in Name
- already is US_ASCII. So if Name had contained an ISO-8859-1
- encoded String, then the call would've looked like this:
- declare
- use Yolk.Email;
- Email : Structure;
- begin
- Composer.Add_From
- (ES => Email,
- Address => "alice@domain.tld",
- Name => "Alice",
- Charset => ISO_8859_1);
- end;
- In this case you will end up with a From header looking like
- this:
- From: =?ISO-8859-1?Q?Alice?= <alice@domain.tld>
- So bear in mind that it is your responsibility to ensure that
- your data, and the Character_Set parameter match.
- The Recipient_Kind type define the kind of recipient that is
- being added to an email. If you've worked with email, these three
- should be familiar to you:
- 1. Bcc
- 2. Cc
- 3. To
- When adding recipients to an email Structure the default is To,
- but since not all recipients are equal, you can change the kind
- of recipient to Bcc or Cc, according to your needs.
- The Structure type is at the core of it all. You declare an
- object to be of the Structure type, and then you use the
- Yolk.Email.Composer facilities to build and send the email.
- 10 Yolk.Email.Composer
- The actual tools for building and sending an email is found in
- this package. Here are tools for building emails from the ground
- up and there are a few convenience procedures if you just need to
- send a simple email with no bells or whistles.
- I'm not going to go through ever procedure in this package,
- instead I'll show an example on how to build an email from the
- ground up and how to use one of the convenience procedures.
- 10.1 Building and sending an email, the easy way
- There are two convenience procedures in Yolk.Email.Composer for
- sending emails without having to do a whole lot of work/thinking.
- They are both named Send and they look like this:
- procedure Send
- (ES : in out Structure;
- From_Address : in String;
- From_Name : in String := "";
- To_Address : in String;
- To_Name : in String := "";
- Subject : in String;
- Text_Part : in String;
- SMTP_Server : in String := "localhost";
- SMTP_Port : in Positive := 25;
- Charset : in Character_Set := US_ASCII);
-
- procedure Send
- (ES : in out Structure;
- From_Address : in String;
- From_Name : in String := "";
- To_Address : in String;
- To_Name : in String := "";
- Subject : in String;
- Text_Part : in String;
- HTML_Part : in String;
- SMTP_Server : in String := "localhost";
- SMTP_Port : in Positive := 25;
- Charset : in Character_Set := US_ASCII);
- As you can see, the only difference between these two is that the
- first one sends plain text emails, while the second one sends
- multipart/alternative with both plain text and HTML parts. Usage
- is as simple as:
- declare
- use Yolk.Email;
- Email : Structure;
- begin
- Composer.Send (ES => Email,
- From_Address => "alice@domain.tld",
- From_Name => "Alice",
- To_Address => "bob@domain.tld",
- To_Name => "Bob",
- Subject => "Is this thing on?",
- Text_Part => "Hey you!",
- Charset => ISO_8859_1);
-
- if Composer.Is_Send (Email) then
- -- Success!
- else
- -- Failure!
- end if;
- end;
- It is possible, and allowed, to call some of the various other
- procedures prior to calling one of the convenience procedures. If
- for example you want to add a custom header, it can be done like
- this:
- declare
- use Yolk.Email;
- Email : Structure;
- begin
- Composer.Add_Custom_Header (ES => Email,
- Name => "User-Agent",
- Value => "My User Agent");
- Composer.Send (ES => Email,
- From_Address => "alice@domain.tld",
- From_Name => "Alice",
- To_Address => "bob@domain.tld",
- To_Name => "Bob",
- Subject => "Is this thing on?",
- Text_Part => "Hey you!",
- Charset => ISO_8859_1);
-
- if Composer.Is_Send (Email) then
- -- Success!
- else
- -- Failure!
- end if;
- end;
- And with that, the header User-Agent: is now added to the email:
- User-Agent: My User Agent
- It hardly gets any easier than that. Lets move on and see how the
- above is accomplished the hard way.
- 10.2 Building and sending email, the hard way
- It is possible to build an email from the ground up, which
- obviously allows for a more fine grained control over what is
- added. It is also a bit more complicated, but not much. Lets try
- and mimick the easy examples, the “hard” way:
- declare
- use Yolk.Email;
- Email : Structure;
- begin
- Composer.Add_Custom_Header (ES => Email,
- Name => "User-Agent",
- Value => "My User Agent");
- Composer.Add_From (ES => Email,
- Address => "alice@domain.tld",
- Name => "Alice",
- Charset => ISO_8859_1);
- Composer.Add_Recipient (ES => Email,
- Address => "bob@domain.tld",
- Name => "Bob");
-
- Composer.Set_Subject (ES => Email,
- Subject => "Is this thing on?");
- Composer.Set_Text_Part (ES => Email,
- Part => "Hey you!");
- Composer.Add_SMTP_Server (ES => Email,
- Host => "localhost");
-
- Composer.Send (ES => Email);
-
- if Composer.Is_Send (Email) then
- -- Success!
- else
- -- Failure!
- end if;
- end;
- Harder yes, but really not all that much more difficult.
- 11 Yolk.Handlers
- Most web applications will need to handle static content, such as
- PNG, HTML and CSS files. Yolk.Handlers helps you accomplish that,
- so you don't have to build your own handlers for these kinds of
- files.
- The following filetypes are supported by Yolk.Handlers:
- • CSS
- • GIF
- • HTML
- • ICO
- • JPG
- • JS
- • PNG
- • SVG
- • XML
- • XSL
- The filetypes that are textual, are compressed according to the
- Yolk.Configuration parameter Compress_Static_Content which
- defaults to False. The regular expressions for identifying these
- filetypes are also defined in Yolk.Configuration by the Handler_*
- parameters. These regular expressions are registered by the
- AWS.Services.Dispatchers.URI.Register_Regexp procedure.
- There's only one procedure in the Yolk.Handlers package:
- procedure Set (RH : out AWS.Services.Dispatchers.URI.Handler);
- You can see an example on how this is used in the demo file
- my_handlers.adb. There's really very little reason not to use
- this package for handling of static content, but it is of course
- not mandatory.
- This package makes use of the Yolk.Static_Content package for the
- actual delivery of the content to the user.
- 12 Yolk.Log
- This package serves two purposes:
- 1. It contains the two callback procedures used to write AWS
- logging data (access and error) to syslogd.
- 2. It creates the SQL, SQL_Cache, SQL_Error, SQL_Select, Alert,
- Critical, Debug, Emergency, Error, Info, Notice and Warning
- trace handles, and activates them according to the values
- defined in the configuration file.
- Out of the box, a Yolk application requires a running syslogd
- daemon, as all log data is sent to syslogd. If for some reason
- you don't want to use syslogd, you're going to have to hack the
- Yolk.Log package, or remove it entirely.
- The two procedures named AWS_* are used by the AWS HTTP(S)
- server. These should not be used for anything else - or rather:
- If you use them for anything else, the Message given is going to
- be written to syslogd with either the AWS access log label or AWS
- error log label. There's absolutely no harm in this, except it
- might be a bit confusing when reading the log data.
- The Trace procedure is the one that you will be using in your
- application. You can send log data to the trace handles defined
- in Yolk.Log.Trace_Handles. All log data is then sent to the
- syslogd daemon using the facilities set in the configuration file
- for the given trace handle.
- Using Trace is about as easy as it gets:
- Yolk.Log.Trace (Handle => Error,
- Message => "Secret sauce to Error!");
- If you haven't activated a trace handle, then calling Trace for
- that handle does nothing, ie. you don't have to remove all your
- Trace calls from the code if you don't use them.
- Note that on many systems the Emergency messages are written to
- console, so be cautious about using this specific level, unless
- you've made certain that you can get to the messages.
- 13 Yolk.Not_Found
- The job of this package is to return a HTTP 404 status code and
- an accompanying simple not found HTML page. It's sole function
- Generate is about as simple as they come:
- function Generate
- (Request : in AWS.Status.Data)
- return AWS.Response.Data;
- It relies on the template file demo/exe/templates/system/404.tmpl
- to generate the generic 404 HTML page, so if you want to use
- Yolk.Not_Found in your own application, then remember to bring
- along this file. Where the 404.tmpl is placed is defined in the
- configuration parameter System_Templates_Path.
- Also worth noting is that the Yolk.Not_Found.Generate function is
- used as the default callback in the demo application. This means
- that all requested resources that doesn't match a registered
- dispatcher, is served by Yolk.Not_Found.Generate ie. a 404 is
- returned. See the demo/src/my_handlers.adb file for more
- information.
- 14 Yolk.Process_Control
- With Yolk.Process_Control you get the ability to control your
- application using the SIGINT, SIGPWR and SIGTERM signals. If you
- give your Yolk application the --pid-file commandline argument
- when starting it, Yolk.Process_Control will create a PID file on
- the given location, if it is allowed to do so. This PID file will
- also be deleted when the application terminates. Note that the
- extras/rc.yolk script handles all this transparently.
- 14.1 Exceptions
- These are:
- • Cannot_Create_PID_File. Is raised if the PID_File cannot be
- created, eg. if the application lacks permissions to write to
- the directory where the PID_File is located.
- • Cannot_Delete_PID_File. Is raised if the PID_file cannot be
- deleted, eg. if the application lacks permissions to write to
- the directory where the PID_File is located, or to the PID_File
- itself.
- • PID_File_Exists. Is raised when the PID_File already exists,
- ie. the application is already running or it was shutdown
- incorrectly.
- 14.2 Using Yolk.Process_Control
- When you use the Yolk.Process_Control package the
- Unreserve_All_Interrupts pragma is used. This means that
- depending on the compiler used one or more interrupt signals may
- be affected. In the case of the GNAT compiler, this is
- specifically mentioned in the source of the Ada.Interrupts.Names
- package:
- -- The pragma Unreserve_All_Interrupts affects the following
- signal(s):
- -- SIGINT: made available for Ada handler
- Since neither SIGPWR or SIGTERM are reserved by the compiler, the
- Yolk.Process_Control package is able to assume control of these
- signals. You can read more about the [http://gcc.gnu.org/onlinedocs/gnat_rm/Pragma-Unreserve_005fAll_005fInterrupts.html||pragma Unreserve_All_Interrupts here]
- . If you compile Yolk with a different compiler than GNAT, then
- please check if one of the affected signals are reserved.
- There are two procedures in the Yolk.Process_Control package:
- procedure Stop;
- procedure Wait;
- When you call the Wait procedure, you basically hang there until
- 1. The Stop procedure is called
- 2. The application receives a SIGINT, SIGPWR or SIGTERM signal
- This is quite handy for applications, that need some sort of loop
- to keep them from terminating. You can see an example on how this
- can be done in the demo/src/yolk_demo.adb file.
- When Wait is called, subsequent calls to Wait are ignored, unless
- a call to Stop has been made or the application has received one
- of the SIGINT, SIGPWR or SIGTERM signals. So it's perfectly valid
- to do:
- Wait;
- -- Stop called from somewhere in the app
- -- Do something...
- Wait;
- -- The app receives a SIGINT signal
- -- Do something...
- Wait;
- Whether or not this is actually useful I don't know, but it is
- possible.
- 15 Yolk.Process_Owner
- When it is necessary to change the owner of a process, the
- Yolk.Process_Owner package is the solution. Obviously this can
- also be done when starting the application, using various shell
- tricks, but I find it it much cleaner to just let the application
- handle it by itself.
- 15.1 Exceptions
- There's only one:
- 1. Username_Does_Not_Exist. This is raised if the given username
- doesn't exist on the system.
- 15.2 Using Yolk.Process_Owner
- There's only a single procedure in this package and its
- specification looks like this:
- procedure Set_User
- (Username : in String);
- -- Set the process owner to Username.
- Please note that when changing the user ID of the application
- with Set_User, the group ID is changed to the first group the
- given user is a member of.
- Usage is as simple as expected:
- declare
- begin
- Set_User (Username => "billybob");
- exception
- when Username_Does_Not_Exist =>
- -- User is missing. Do something!
- end;
- In the file demo/src/yolk_demo.adb you'll find that
- Yolk.Process_Owner.Set_User is used in conjunction with the
- Yolk.Configuration.Yolk_User parameter.
- 16 Yolk.Server
- This is a convenience package to handle creating, starting and
- stopping an AWS HTTP server. The biggest drawback to this package
- is that it can only create and manage one AWS server.
- Note a potential manual operation when using the Yolk.Server
- package : Setting the Cache-Control header. This can become
- necessary if you
- 1. use Yolk.Server to create and start your AWS server AND
- 2. use the Yolk.Handlers package to register dispatchers for
- static content AND
- 3. have the Compress_Static_Content configuration parameter set
- to True AND
- 4. aren't content with the default Cache-Control header
- In that case you can use the
- Yolk.Static_Content.Set_Cache_Options procedure to work your own
- magic on the Cache-Control header sent to clients requesting
- static content. See the Yolk.Static_Content chapter for more
- information.
- 16.1 Yolk.Server.Create
- Creating a server is done by calling the Create function. This
- function accepts one parameter that should be fairly
- self-explanatory.
- type HTTP is tagged limited private;
- function Create
- (Unexpected : in AWS.Exceptions.Unexpected_Exception_Handler)
-
- return HTTP;
- 16.2 Yolk.Server.Start
- procedure Start
- (WS : in out HTTP;
- Dispatchers : in AWS.Dispatchers.Handler'Class);
- This does as expected: Starts the server with Dispatchers.
- When calling Start several things happen:
- 1. If the configuration parameter Load_MIME_Types_File is True
- then the MIME types file given in MIME_Types_File is loaded
- into AWS.
- 2. If the configuration parameter Start_WebSocket_Servers is True
- then the AWS WebSocket servers are started.
- 3. Yolk.Static_Content.Static_Content_Cache_Setup is called with
- its default parameters if the configuration parameter
- Compress_Static_Content is True.
- 4. If session support is turned on for the server and the
- Session_Data_File exists, then the session data is loaded from
- this file.
- 5. The Yolk.Log.AWS_Access_Log_Writer procedure is registered to
- handle all AWS access log data if the configuration parameter
- AWS_Access_Log_Activate is True .
- 6. The Yolk.Log.AWS_Error_Log_Writer procedure is registered to
- handle all AWS error log data if the configuration parameter
- AWS_Error_Log_Activate is True .
- 16.3 Yolk.Server.Stop
- procedure Stop
- (WS : in out HTTP);
- This does as expected: Stops everything that was started when the
- Yolk.Server.Start call was made. Note that if sessions support is
- turned on for the server, then the Dispatchers are switched for a
- plain Not Found dispatcher when Stop is called. This is done to
- prevent session tampering while the server is shutting down.
- 17 Yolk.Static_Content
- Most web applications have a lot of static content, ie. stuff
- that rarely changes. Things like PNG's, HTML, CSS and Javascript.
- These are content types that are common for most websites, so a
- application is going to have to handle these in some way. This is
- where Yolk.Static_Content comes in. Two kinds of files are
- handled by Yolk.Static_Content:
- • Compressable (XML, HTML, CSS, JS and so on)
- • Non-compressable (PNG, JPG, GIF, ICO and so on)
- It is up to you, the user, to decide whether a specific kind of
- file is compressable or not - the package does not make any such
- assumptions. The difference between the two, is that compressable
- files are compressed prior to delivery, if the clients HTTP
- request includes a Accept-Encoding: gzip header. Non-compressable
- files are simply returned as is. For both kinds, a generic 404 is
- returned if the requested file doesn't exist.
- Yolk.Static_Content is affected by five configuration settings:
- • Compress_Static_Content
- • Compress_Static_Content_Minimum_File_Size
- • Compressed_Static_Content_Cache
- • Compressed_Static_Content_Max_Age
- • WWW_Root
- You should carefully read the demo/exe/configuration/config.ini
- file for information on what exactly these do.
- 17.1 Yolk.Static_Content.Static_Content_Cache_Setup
- The configuration parameter Compressed_Static_Content_Cache
- defines where the compressed version of the static content is
- saved. When your application is started, this directory may or
- may not exist, and it may or may not contain various compressed
- files. To make sure that the directory exists and is empty, you
- should call the Static_Content_Cache_Setup procedure:
- procedure Static_Content_Cache_Setup
- (No_Cache : in Boolean := False;
- No_Store : in Boolean := False;
- No_Transform : in Boolean := False;
- Max_Age : in AWS.Messages.Delta_Seconds := 86400;
- S_Max_Age : in AWS.Messages.Delta_Seconds :=
- AWS.Messages.Unset;
- Public : in Boolean := False;
- Must_Revalidate : in Boolean := True;
- Proxy_Revalidate : in Boolean := False);
- This procedure creates the Compressed_Static_Content_Cache
- directory if it doesn't already exists and if it exists, it
- deletes everything in it. So basically you're left with an empty
- directory after a call to this procedure.
- The parameters defines the content of the Cache-Control header
- sent to the user agent when a request for static content is made.
- The default parameters are fairly sane, and will result in a
- cache header looking like this:
- Cache-Control: max-age=86400, must-revalidate
- Read the [http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9||HTTP/1.1 Cache-Control header definition]
- for more information on what the various settings do.
- If you use Yolk.Server to create and start your AWS server, then
- this called is made automatically with it's default parameters.
- If you don't use Yolk.Server and you use the Yolk.Static_Content
- package to serve static content, then you must call this
- procedure before starting the AWS server. Calling it repeatedly
- will simply wipe out the compressed cache directory and reset the
- Control_Cache header according to the given parameters.
- 17.2 Yolk.Static_Content.Compressable
- If your application contains a bunch of compressable files of
- significant size, you can save a lot of bandwidth by using the
- Compressable function to serve them:
- function Compressable
- (Request : in AWS.Status.Data)
- return AWS.Response.Data;
- A natural place to call this function would be where you define
- your content handlers, as seen in this, slightly altered, excerpt
- from the Yolk.Handlers package:
- declare
- package SC renames Yolk.Static_Content;
- Resource_Handlers : AWS.Services.Dispatchers.URI.Handler;
- begin
- AWS.Services.Dispatchers.URI.Register_Regexp
- (Dispatcher => Resource_Handlers,
- URI => "\.css$",
- Action => Create (Callback =>
- SC.Compressable'Access));
- AWS.Services.Dispatchers.URI.Register_Regexp
- (Dispatcher => Resource_Handlers,
- URI => "\.html$",
- Action => Create (Callback =>
- SC.Compressable'Access));
- end;
- Here you can see that we've defined some content handlers for CSS
- and HTML files based on a few simple regular expressions, and
- whenever a resource is requested that matches one of these, the
- corresponding Action callback is called, which in this example is
- the Compressable function. The following steps are then taken:
- 1. Does the requested resource exist and is it an ordinary file?
- (a) If no, then return a 404 message
- (b) if yes, then proceed to 2
- 2. Does the client support compressed content?
- (a) If no, then return the requested resource un-compressed
- (b) if yes, then proceed to 3
- 3. Is there a compressed version of the resource available on
- disk?
- (a) if no, then make one, cache it for future use, and return
- it
- (b) If yes, then proceed to 4
- 4. Is the age of the compressed file <= Compressed_Max_Age?
- (a) If no, then delete the current file, build a new one, cache
- it and return it
- (b) If yes, then return it
- And that's really all there is to it. Note that Compressable
- always looks for requested content in the WWW_Root directory, so
- if the user requests the file /css/index.css, then the path is
- going to be:
- WWW_Root & "/css/index.ss";
- Using the default configuration value for WWW_Root, we'll end up
- with the path:
- static_content/css/index.css
- The compressed version of index.css is saved as:
- Compressed_Static_Content_Cache & "/css/index.css" & ".gz";
- Using the default configuration value for
- Compressed_Static_Content_Cache, we'll end up with the path:
- static_content/compressed_cache/css/index.css.gz
- In these two cases the paths are relative to the executable, but
- you can of course also define WWW_Root and
- Compressed_Static_Content_Cache as absolute paths in the
- configuration file.
- 17.3 Yolk.Static_Content.Non_Compressable
- A lot of static content really doesn't benefit from any further
- compression. This is the case for PNG files, JPEG files and a
- whole lot of other kinds of files. Static content that has
- already been compressed, should be handled by the
- Non_Compressable function:
- function Non_Compressable
- (Request : in AWS.Status.Data)
- return AWS.Response.Data;
- A call to Non_Compressable is much simpler compared to a call to
- it's sibling function Compressable:
- 1. Does the requested resource exist and is it an ordinary file?
- (a) If no, then return a 404 message
- (b) if yes, then return the file
- And that's it. Either the resource is there, or it isn't. If the
- requested resource is /images/foo.png, then the Non_Compressable
- function searches for the resource in:
- WWW_Root & "/images/foo.png";
- Using the default configuration value for WWW_Root, we'll end up
- with the path:
- static_content/images/foo.png
- Just as with Compressable a natural place to use Non_Compressable
- is where you define you content handlers, but it can obviously be
- used wherever your application handles requests for static
- content.
- 17.4 Yolk.Static.Set_Cache_Options
- You can alter the Cache-Control header sent to clients requesting
- static content with a call to Set_Cache_Options. This is
- especially handy if you use the Yolk.Server package to create and
- start your server, since the initial call to
- Static_Content_Cache_Setup is hidden you're forced to live with
- the default values given in the Static_Content_Cache_Setup call,
- resulting in a Cache-Control header looking like this:
- Cache-Control: max-age=86400, must-revalidate
- If this is not what you want, you can use Set_Cache_Options to
- change it:
- procedure Set_Cache_Options
- (No_Cache : in Boolean := False;
- No_Store : in Boolean := False;
- No_Transform : in Boolean := False;
- Max_Age : in AWS.Messages.Delta_Seconds := 86400;
-
- S_Max_Age : in AWS.Messages.Delta_Seconds :=
- AWS.Messages.Unset;
- Public : in Boolean := False;
- Must_Revalidate : in Boolean := True;
- Proxy_Revalidate : in Boolean := False);
- This MUST be called immediately after having called
- Yolk.Server.Start, since the call to Static_Content_Cache_Setup
- in Start will overwrite the cache options.
- 18 Yolk.Syndication
- The Atom Syndication format ([http://tools.ietf.org/html/rfc4287||RFC4287]
- ) is an XML-based document format that describes lists of related
- information known as "feeds". Atom documents are used by many web
- applications as a means of publishing information to users. It's
- not a complicated format, but it does require a bit of work to
- construct a proper Atom feed by hand, and since I try my best to
- avoid work, I made the Yolk.Syndication package. This package
- makes it “easy” to put together an Atom feed document, and have
- it delivered as a string or an XML/Ada DOM object.
- Yolk.Syndication helps you construct the Atom XML - it does not
- check that the resulting XML is valid Atom XML, ie. that the Atom
- rules have been followed, so if the Atom specification says that
- atom:feed elements MUST contain exactly one atom:id element.
- then it is your job to make sure that your Atom feed has exactly
- one such element. So before venturing into the realm of Atom,
- it's probably a good idea to read [http://tools.ietf.org/html/rfc4287||RFC4287]
- for a good understanding of the requirements for this document
- format. A basic example of how to build an Atom feed can be found
- in the Yolk demo application.
- There are two packages in the Yolk.Syndication hierarchy:
- • Yolk.Syndication - Here the necessary types and exceptions are
- defined.
- • Yolk.Syndication.Writer - Here the functions and procedures for
- actually creating an Atom feed are defined.
- When time permits, I'll add a Yolk.Syndication.Reader package for
- extraction of data from an Atom feed.
- 18.1 Exceptions
- There's one exception in this package:
- • Not_Valid_XML. Is raised when some Xhtml content is not valid
- XML. This exception can be raised by all procedures that takes
- Xhtml as content.
- 18.2 The Yolk.Syndication types
- There are 5 important types in this package:
- • Text_Kinds
- • Relation_Kinds
- • Atom_Entry
- • Atom_Entry_Source
- • Atom_Feed
- A few of the procedures in Yolk.Syndication.Writer has parameters
- of the Text_Kinds type. This identifies the kind of data that is
- being added, with possible values being
- • Text
- • Html
- • Xhtml
- Hopefully it's obvious what each of these refers to. Procedures
- that have parameters of the Text_Kinds type always add data to
- the feed that can be interpreted as one of these three kinds of
- text.
- The Relation_Kinds type is used in correlation with links that
- are added to the feed. It identifies how the link relates to the
- current feed/entry. There are 5 possible relations:
- • Alternate: Signifies that the link points to an alternate
- version of the resource described by the containing element.
- • Related: Signifies that the link points to a resource that is
- related to, but not the same as, the resource described by the
- containing element.
- • Self: Sig…
Large files files are truncated, but you can click here to view the full file