Files
aws/docs/source/high_level_services.rst
2021-11-15 12:39:41 -06:00

1657 lines
50 KiB
ReStructuredText

.. _High_level_services:
.. highlight:: ada
*******************
High level services
*******************
Here you will find a description of high level services. These services are
ready to use with AWS and can be used together with user's callbacks.
Refer to the Ada spec for a complete API and usage description.
.. _Directory_browser:
Directory browser
=================
.. index:: Directory browser
This service will help building a Web directory browser. It has a lot
of options to sort directory entries and is based on the templates
interface :ref:`AWS.Templates`. This means that you can use the
default directory template or provide your own.
see :ref:`AWS.Services.Directory` for the complete specification and description of this service.
.. _Dispatchers:
Dispatchers
===========
.. index:: Dispatchers
In many AWS applications it is necessary to process the URI to give the
right answer. This means that part of the application is a big
**if/elsif** procedure. Also, in the standard callback it is not possible
to have user data. Both of these restrictions are addressed with the
Dispatcher facilities.
Working with a dispatcher is quite easy:
* Create a new dispatcher by inheriting from the service you want
to build.
* Register a set of action based on rules (strings or regular
expressions depending on the service)
.. _Callback_dispatcher:
Callback dispatcher
-------------------
.. index:: Dispatchers callback
.. index:: Callback, dispatcher
This is a wrapper around the standard callback procedure. It is needed
to mix dispatcher based callback and access to procedure
callback. Note that it is not in the `AWS.Services.Dispatchers`
hierarchy but instead in `AWS.Dispatchers.Callback` because this is a
basic service needed for the server itself. It is referenced here for
documentation purposes but an AWS server can be built without using it.
see :ref:`AWS.Dispatchers.Callback` for the complete specification.
.. _Method_dispatcher:
Method dispatcher
-----------------
.. index:: Dispatchers method
.. index:: method, dispatcher
This is a dispatcher based on the request method. A different callback
procedure can be registered for the supported request methods: GET,
POST, PUT, HEAD.
see :ref:`AWS.Services.Dispatchers.Method` for the complete specification.
.. _URI_dispatcher:
URI dispatcher
--------------
.. index:: Dispatchers URI
.. index:: URI, dispatcher
This is a dispatcher based on the request resource. A different callback
procedure can be registered for specific resources. The resource is
described either by its full name (string) or a regular expression.
see :ref:`AWS.Services.Dispatchers.URI` for the complete specification.
.. _Virtual_host_dispatcher:
Virtual host dispatcher
-----------------------
.. index:: Dispatchers virtual host
.. index:: virtual host, dispatcher
This is a dispatcher based on the hostname. A different callback
procedure can be registered for specific hostnames. This enables support for
virtual hosting.
The same computer can be registered into the DNS with different
names. So all names point to the same machine. But in fact you want
each name to be seen as a different Web server. This is called virtual
hosting. This service will just do that, call different **callback**
procedures or redirect to some **machine/port** based on the hostname
in the client's request.
see :ref:`AWS.Services.Dispatchers.Virtual_Host` for the complete specification.
.. _Transient_pages_dispatcher:
Transient pages dispatcher
--------------------------
.. index:: Dispatchers Transient pages
.. index:: transient pages, dispatcher
This is a dispatcher that calls a user's callback and if the resource
requested is not found (i.e. the user's callback returns status code
404) it checks if this resource is known as a transient
page. see :ref:`Transient_Pages`.
.. _Timer_dispatcher:
Timer dispatcher
----------------
.. index:: Dispatchers Timer
.. index:: timer, dispatcher
A timer dispatcher can be used to call different callback routines
depending on the current date and time. Such a dispatcher is composed of
a set of activation `Period`s . When the current date and time is
inside a `Period` the corresponding callback is called. A
`Period` can eventually be repeated. Here are the different kinds
of `Period`s supported by `AWS`:
*Once*
A unique period in time. The boundaries are fully described using a
year, month, day, hour, minute and second.
*Yearly*
A period that repeats each year. The boundaries are described using a
month, day, hour, minute and second.
*Monthly*
A period that repeats each month. The boundaries are described using a
day, hour, minute and second.
*Weekly*
A period that repeats each week. The boundaries are described using a
day name, hour, minute and second.
*Daily*
A period that repeats each day. The boundaries are described using an
hour, minute and second.
*Hourly*
A period that repeats each hour. The boundaries are described using a
minute and second.
*Minutely*
A period that repeats each minute. The boundaries are described using
a second.
.. _Linker_dispatcher:
Linker dispatcher
-----------------
.. index:: Dispatchers Linker
.. index:: linker, dispatcher
A dispatcher that can be used to chain two dispatchers. The response
of the first dispatcher is returned except if it is a 404 (Not Found)
error. In this case, the response of the second dispatcher is returned.
.. _SOAP_dispatcher:
SOAP dispatcher
---------------
.. index:: Dispatchers SOAP
.. index:: SOAP, dispatcher
`AWS` also provides a `SOAP` specific dispatcher. This is a way to
automatically route HTTP requests or `SOAP` requests to different
callback routines.
see :ref:`SOAP_helpers` for more information.
see :ref:`SOAP.Dispatchers.Callback` for the complete specification.
.. _Static_Page_server:
Static Page server
==================
.. index:: Static Page server
.. index:: Simple Page server
.. index:: Page server
This service is a ready to use static page server callback. It
is possible to use this service to build a simple static page server;
this is as simple as::
with AWS.Server;
with AWS.Services.Page_Server;
procedure WPS is
WS : AWS.Server.HTTP;
begin
AWS.Server.Start
(WS, "Simple Page Server demo",
Port => 8080,
Callback => AWS.Services.Page_Server.Callback'Access,
Max_Connection => 5);
AWS.Server.Wait (AWS.Server.Q_Key_Pressed);
AWS.Server.Shutdown (WS);
end WPS;
Build this program and execute it to serve `HTML` pages and images
in the current directory.
It is possible to activate the directory browsing facility of this
simple page server. This is not activated by default. This feature
is based on the directory browsing service see :ref:`Directory_browser`.
Note that this service uses two template files:
*aws_directory.thtml*
The template page used for directory browsing. See
see :ref:`AWS.Services.Directory` for a full description of this
template usage.
*404.thtml*
The Web page returned if the requested page is not found. This is a
template with a single tag variable named PAGE. It will be replaced by
the ressource which was not found.
Note that on Microsoft IE this page will be displayed only if the total
page size is bigger than 512 bytes or it includes at least one image.
see :ref:`AWS.Services.Page_Server` for the complete specification.
.. _Transient_Pages:
Transient Pages
===============
.. index:: transient pages
.. index:: pages, transient
A transient page is a resource that has a certain life time on the
server. After this time the resource will be released and will not be
accessible anymore.
Sometimes you want to reference, in a Web page, a resource that is built
in memory by the server. This resource may or may not be requested by the
client (by clicking on the corresponding link), in either case the page must
be released after a certain amount of time to free the associated memory.
This is exactly what the transient pages high level service does
automatically. Each transient page must be registered into the
service, then a specific routine named `Get_URI` can be used to create
a unique `URI` on this server. see :ref:`AWS.Services.Transient_Pages`.
A transient pages dispatcher can be used to build a server supporting
transient pages. see :ref:`Transient_pages_dispatcher`.
.. _Split_pages:
Split pages
===========
.. index:: split pages
.. index:: pages, split
It not not very convenient to send back a Web page with a large
table. In such a case it is better to split the table into chunks (20
lines or so) and to send only the first page. This first page reference the
next pages and can also contain an index of all the pages.
The `AWS`'s split page feature can automatically do this for
you. Given template `Translate_Table` or `Translate_Set` and the
max line per page it returns the first page and creates a set of
transient pages for all other pages. A set of template tags are used
to reference the previous and next page and also to build the page index.
There are different ways to split a set of pages and ready-to-use
splitters are available:
*Alpha*
Split into (at most) 28 pages, one for empty fields, one for all fields
that start with a digit, and one for each different initial letter.
see :ref:`AWS.Services.Split_Pages.Alpha`.
*Alpha.Bounded*
Same as the alpha splitter, but pages larger than a Max_Per_Page value
are further split.
A secondary index is generated that gives the various pages for a given
letter. see :ref:`AWS.Services.Split_Pages.Alpha.Bounded`.
*Uniform*
Split into pages of length Max_Per_Page (except the last one). This
corresponds to the default service in the Split_Pages package.
see :ref:`AWS.Services.Split_Pages.Uniform`.
*Uniform.Alpha*
Same as the uniform splitter, but additionally builds an alphabetical
secondary index from a key field.
see :ref:`AWS.Services.Split_Pages.Uniform.Alpha`.
*Uniform.Overlapping*
Same as the uniform splitter, but pages (except the first one)
repeat Overlap lines from the previous page in addition to the
Max_Per_Page lines. see :ref:`AWS.Services.Split_Pages.Uniform.Overlapping`.
Using the spliter abstract interface it is possible to build a
customized splitter algorithm. see :ref:`AWS.Services.Split_Pages`.
.. _Download_Manager:
Download Manager
================
.. index:: Download Manager
A server that needs to handle lot of large downloads can run out of
connections to answer the standard Web pages. One solution is to increase the
number of simultaneous connections, but this is not really efficient
as a task is created for each connection and does not ensure that all
the connections will be used for the downloads anyway.
The download manager can be used for that, and provides the following
feature:
* use a single task for all downloads
* can be configured to limit the number of simultaneous connections
* downloads past this limit are queued
* send messages to the client with the position in the waiting line
* send messages to the client when the download is about to start
The server must be configured to use dispatchers (standard callbacks
are not supported, note that it is possible to create a dispatcher for
standard callbacks. see :ref:`AWS.Dispatchers.Callback`).
To start the download manager you need to pass the main server
dispatcher object. The start routine will return a new dispatcher,
linked with the download server specific dispatcher, that must be used
to start the standard Web server. See the comments in
see :ref:`AWS.Services.Download`.
To queue a download request in the download manager you just need to
create a stream object (can be any kind of stream, see
`AWS.Resources.Streams.*`) for the resource to download.
The download manager needs two templates files:
*aws_download_manager_waiting.thtml*
This template is used for sending a message to the client when the
request is on the waiting line. The tags defined in this template file
are:
*NAME*
the name of the resource to download (the filename), this is the
default filename used for the client side save dialog.
*RES_URI*
the URI used to access the resource.
*POSITION*
the position in the waiting line (not counting the clients being currently
served).
*aws_download_manager_start.thtml*
This template is used for sending a message to the client when the
download is about to start (the request is out of the waiting
line). The tags defined in this template file are:
*NAME*
as above
*RES_URI*
as above
.. highlight:: xml
It is important to note that those templates must be reloaded
periodically. The best way to do that in the context of an `HTML`
document is to use a meta-tag. For example to refresh the page every
two seconds::
<meta http-equiv="refresh" content="2">
The templates could look like:
*aws_download_manager_waiting.thtml*
::
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="refresh" content="2">
<title>Download Manager - waiting</title>
</head>
<body>
<p>Waiting for downloading @_NAME_@
<p>Position in the waiting line @_POSITION_@
</body>
</html>
*aws_download_manager_start.thtml*
::
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="refresh" content="2">
<title>Download Manager - waiting</title>
</head>
<body>
<p>Waiting for downloading @_NAME_@
<p>The download will start in a moment
</body>
</html>
.. _Web_Elements:
Web Elements
============
.. index:: Web Elements
`AWS` provides some components to help in creating nice looking Web
interfaces. It is possible to browse these Web Elements using the
`web_elements` demo. Just launch this Web application from the
demos directory and turn your Web browser to
`http://localhost:2400 <http://localhost:2400>`_.
Currently `AWS` provides:
* Notebooks (based on CSS)
* CSS Menu
* Rounded boxes
* Ajax
All of them are based on templates that can be easily reused in other
applications. The first three are best described by the Web Elements
demos as they are 100% design. The `Ajax` one is a bit more complex, we
will present its use in the following section.
.. _Installation:
Installation
------------
.. index:: we_icons
.. index:: we_js
To ease integration we have used the following design:
* Sub-directories found in the `AWS`'s web_elements directory
are self contained. The content must be copied into the project. Note
that the icons and javascripts directories contain the
icons and javascripts code shared by all web elements and must also be
copied, see below.
* Each graphic elements (icons) is referenced in the templates with the
alias `/we_icons/<icon_name>`. So users must provide the right alias
("`/we_icons/`") in the Web server.
* Each JavaScripts code is referenced in the templates with the
alias `/we_js/<script>`. So users must provide the right alias
(`"/we_js/"`) in the Web server.
.. _Ajax:
Ajax
----
.. index:: Ajax
.. index:: aws_action_replace.tjs
.. index:: aws_action_clear.tjs
First of all, `Ajax` stand for
*Asynchronous JavaScript language and XML*, and is not well defined
at the moment. `Ajax` is on one side able to send HTTP requests
to the Web server and on the other side able to manipulate directly the Web
browser's `DOM` tree. On the `DOM` it can add, remove or
replace `XML` nodes. So, it is possible to change the content of
a Web page without reloading it from the server.
Most importantly, `Ajax` changes the way Web applications are
thought from **page** based to **event** based.
As implemented in `AWS`, `Ajax` support comes as a set of
`JavaScript` templates. Using those templates there is no need to
know `JavaScript` (except for the `JavaScript` event names) and it
makes `Ajax` programming easier. Two actions are provided,
one for replacing and another for clearing part of a web page's content.
.. _Steps_to_do_Ajax:
Steps to do Ajax
^^^^^^^^^^^^^^^^
What are the steps to do `Ajax` ?
Remember, do not think about a Web page but rather a specific widget
(`HTML` fragments) with an associated event and action.
* Include the AWS/Ajax support file
This is the `AWS/Ajax` runtime, it contains `JavaScript`
code needed for the `AWS/Ajax` support.
* Create the Web widgets/forms
There is nothing special here, use your favorite Web designer tool.
* Create the Web area
Using some `HTML` <div> tags we create areas where we will place
`HTML` fragments later. For example when clicking on a button
(described above) in our Web interface we want to display a new form
in this area.
* Name the widgets/forms/area using the id="name" attribute
Give a different name to the widgets using id="name". This name will
later be used to identify the widgets on which the event and
corresponding action must be placed. We do not want to clutter the Web
design with `JavaScript` code like `onclick="dothis()"` or
`onchange="dothat()"`.
* Add the proper event/action to the widgets using the AWS/Ajax templates
This is the interresting part. At this point we link events/actions
to the widgets and specify in which area the results sent by the
server will be placed.
This is not the only way to do `Ajax`, but a simple
approach that works well with the `AWS/Ajax` templates.
.. _Basic_Ajax_support:
Basic Ajax support
^^^^^^^^^^^^^^^^^^
This section describes the `AWS/Ajax` support where the answer from the
server is an `HTML` fragment. This basic support is designed to
be used for migration of a Web server to `Ajax`. For new
applications, it is worth considering using the XML based Ajax support,
see :ref:`XML_based_Ajax`.
Let's have a very simple example:
* The AWS/Ajax runtime support
::
@@INCLUDE@@@ aws.tjs
Must be included into every Web page's `<head>` tag.
* The widget: a button
::
<input id="clickme" type="button" value="Clik Me">
* The result area: a div
::
<div id="placeholder">... result here ...</div>
* The AWS/Ajax
::
@@INCLUDE@@ aws_action_replace.tjs onclick clickme placeholder
Basically it places an **onclick** attribute (the event) in the `HTML`
`<input>` identified as **clickme** (the action) above. Here is
what happens when the button is clicked:
* the "/onclick$clickme" HTTP request is sent to the server
* asynchronously waits for the answer, when received places the
message body into the `<div>` **placeholder**.
.. highlight:: ada
On the server side the code would look like this::
function Callback (Request : in Status.Data) return Response.Data is
URI : constant String := Status.URI (Request);
begin
if URI = "/clickme" then
return Response.Build (MIME.Text_HTML, "you click me!");
...
So when the button is clicked the string **"you click me!"** will replace
the **"... result here ..."** string of the place holder div above.
This is a simple and very limited example as there is no parameter
passed to the `HTTP` request. In real Web applications it is necessary
to send a context with the request. This can be either the value of
other widgets or all values of widgets' form.
.. highlight:: xml
References to widgets or forms can be passed to the
:file:`aws_action_replace.tjs` template starting with the 5th
parameter::
<input id="field" type="text" value="default value">
...
@@INCLUDE@@ aws_action_replace.tjs (onclick clickme placeholder 5=>field)
or::
<form id="small_form" name="small_form">
...
</form>
@@INCLUDE@@ aws_action_replace.tjs (onclick clickme placeholder 5=>*mall_form)
Note that the `onclick` event is only one of the possible
`JavaScript` event on a `button`. It is possible to used
any supported event, for example on an `HTML` `<select>` widget
it is common to map the action to the `onchange` event.
`AWS` also provides support for clearing an area or a widget
content (like an input)::
@@INCLUDE@@ aws_action_clear.tjs (onclick, clear, field)
This simple action adds the **onclick** event to the **clear** button
to erase the content of the **field** widget.
.. _XML_based_Ajax:
XML based Ajax
^^^^^^^^^^^^^^
In many cases you'll want to update and/or clear multiple areas in your
Web interface. With the templates above only a single action is
possible. `AWS` provides support for `XML` based answers. In
this `XML` documents it is possible to:
* replace an area with a new content::
<replace id="item_id">new text</replace>
* clear an area::
<clear id="item_id"/>
* add an item into a select widget::
<select action="add" id="item_id"
option_value="value" option_content="content"/>
* remove an item from a select widget::
<select action="delete" id="item_id" option_value="value"/>
* select a specific item in a select widget::
<select action="select" id="item_id" option_value="value"/>
* clear a select widget (remove all items)::
<select action="clear" id="item_id"/>
* select a radio button::
<radio action="select" id="item_id"/>
* check a checkbox::
<check action="select" id="item_id"/>
* clear a checkbox::
<check action="clear" id="item_id"/>
* call another URL::
<get url="http://thishost/action">
<parameters value="name=Ajax"/>
<field id="input1"/>
</get>
This will send the following request::
http://thishost/action?name=Ajax&input1=<val_input1>
Where **val_input1** is the current value of the **input1** input
widget. The result must be an `XML/Ajax` document that will be parsed.
* make a list sortable::
<make_sortable>
<list id="firstlist"/>
<list id="secondlist"/>
</make_sortable>
Here **firstlist** and **secondlist** are **id** of `UL` elements. It is
possible to specified as many list id as needed. A drag and drop is
then possible for all elements in those lists. It is then possible to
reference such list by passing the list id as a field to the
template. Items on those list will be serialized and passed to the `AWS`
callback. Note that for the serialization to work properly, each
`LI` elements must be given the id of the list and then the value
we want to pass::
<ul id="firstlist">
<li id="firstlist_red">Red</li>
<li id="firstlist_green">Green</li>
<li id="firstlist_blue">Blue</li>
</ul>
The serialization will send each value on this list using a
multi-valued parameter named **firstlist[]**::
http://server?firstlist[]=red&firstlist[]=green&firstlist[]=blue
* make a list not sortable::
<destroy_sortable>
<list id="firstlist"/>
<list id="secondlist"/>
</destroy_sortable>
Remove the sortable properly from the specified lists.
* redirect to another URL::
<location url="http://thishost/go_there"/>
Redirect the browser to the specified URL.
* refresh the current page::
<refresh/>
Refresh the current page as if the Web Browser refresh button was pressed.
* add a CSS style to a given node::
<apply_style id="node_id">
<attribute id="display" value="none"/>
</apply_style>
Add the CSS style `display:none` to the **node_id** element. It
is possible to specify multiple attributes if needed.
* make an entry disabled or enabled::
<disabled id="item_id" value="true/false"/>
* make an entry read-only or writable::
<read_only id="item_id" value="true/false"/>
* reset a form::
<reset id="form_id"/>
Here is an example of such XML document::
<?xml version="1.0" encoding="UTF-8" ?>
<response>
<replace id="xml_status_bar">Fill Widgets...</replace>
<replace id="text1">Response from XML</replace>
<replace id="text2">Another response for text2</replace>
<replace id="input1">tag is input1</replace>
<replace id="input2">tag is input2</replace>
<select action="add" id="xmlsel" option_value="one" option_content="1"/>
<select action="add" id="xmlsel" option_value="two" option_content="2"/>
<select action="add" id="xmlsel" option_value="three" option_content="3"/>
<select action="select" id="xmlsel" option_value="two"/>
<radio action="select" id="radio1"/>
<check action="select" id="check1"/>
<check action="select" id="check3"/>
<check action="clear" id="check2"/>
</response>
To register an `Ajax` action to a specific tag id a macro can be
used. It is named `JS_ACTION` and defined in :file:`ajax_api.tjs`.
The usage is similar to what is described in the previous section
(see :ref:`Basic_Ajax_support`) except that in this case we use a macro
instead of an include file and we do not have to pass the placeholder.
Let's revisit the first example above to use the `XML`
`Ajax` support.
* The AWS/Ajax runtime support::
@@INCLUDE@@@ aws.tjs
Must be included in every Web page's `<head>` tag.
* The AWS/Ajax API::
@@INCLUDE@@@ ajax_api.tjs
Must be included at least once during an application life-time. It
gives access to the `JS_ACTION` macro.
* The widget: a button::
<input id="clickme" type="button" value="Clik Me">
* The result area: a div::
<div id="placeholder">... result here ...</div>
* The AWS/Ajax::
@_JS_ACTION(onclick, clickme)_@
Basically it places an **onclick** attribute (the event) in the `HTML`
`<input>` identified as **clickme** (the action) above. Here is
what happens when the button is clicked:
* the "/onclick$clickme" HTTP request is sent to the server
* asynchronously waits for the XML answer, when received parses the
answer and perform the actions according to the `XML` content.
To set the placeholder with "**new text**", the `XML` document
returned by the server must be::
<?xml version="1.0" encoding="UTF-8" ?>
<response>
<replace id="placeholder">new text</replace>
</response>
If we want also to clear the input field named **field** and to select the
radio button named **radio1** we must return::
<?xml version="1.0" encoding="UTF-8" ?>
<response>
<replace id="placeholder">new text</replace>
<clear id="field"/>
<radio action="select" id="radio1"/>
</response>
This is by far the most flexible solution as it is possible to return, from the
server, a structured answer.
A final comment, if the text returned by the server to replace a
specific area is an `HTML` fragment, the content must be placed into a
`CDATA` tag::
<?xml version="1.0" encoding="UTF-8" ?>
<response>
<replace id="item_id">
<![CDATA[ *HTML CODE HERE* ]]>
</replace>
</response>
.. _Advanced_Ajax:
Advanced Ajax
^^^^^^^^^^^^^
Finally, if this is not enough because you need to use some specific
`JavaScript` code, `AWS` provides a macro named
`BIND_JS` to add an event to a specific widget, the action being
the name of a `JavaScript` routine.
This macro together with the :file:`aws_func_replace.tjs`,
:file:`aws_func_clear.tjs` templates and the :file:`JS_ACTION` macro can
be used to chain multiple actions. Those templates are the function
body used by the corresponding templates :file:`aws_action_replace.tjs`,
:file:`aws_action_clear.tjs`.
Suppose you want to clear a widget, change the content of another one
and call one of your specific `JavaScript` routines when clicking on
a button. It is not possible to have mutiple `onclick` events on
the same widget, the solution is the following:
* Create the JavaScript routine to do the job
For this in the the body of the `clear_replace()` JavaScript
routine we place::
function clear_replace()
{
@@INCLUDE@@ aws_func_replace.tjs (clickme placeholder 4=>field)
@@INCLUDE@@ aws_func_clear.tjs (area)
call_this_routine();
}
Then to add the event on the widget::
@_BIND_JS(onclick, clickme clear_replace)_@
Furthermore, it is possible to pass (as the parameter number 20) a
routine to call after a specific action to all templates and to the
`JS_ACTION` macro. This is another way to chain multiple actions
for a single event.
Note that all `AWS/Ajax` templates and the :file:`ajax_api.tjs`
file have a set of comments at the start explaining in details the
usage of each parameter.
.. _Web_Blocks:
Web Blocks
==========
.. index:: Web Blocks
The `AWS.Services.Web_Block` hierarchy contains an API useful for
keeping context on Web pages. It has been designed to be able to split
a Web application into a set of independent blocks that can be put
together in the same Web page. The context is then useful as it is
passed and known by each individual block. Note that this is different
than the session as a session is global to the current Web browser
whereas the context can be different for each individual web pages
opened.
Instead of parsing a whole page using the `AWS.Templates` API the web blocks
are registered independently using `AWS.Services.Web_Block.Registry`.
The block is registered together with its templates and the callback to use
to get user's data for this specific block with the given context.
So using this API, instead of having a set of callbacks returning an
`AWS.Response.Data` and where the final rendering is to be done
by the client code, we have a set of callbacks that returns a
`Translate_Set`. The client just has to fill the set with the
data corresponding to the actual request and possibly using the
context. The final rendering is done by the provided services in
`Web_Block.Registry`.
Note that all Web pages must also be registered into the registry
to ensure that the context identification is properly kept. The context
identification is injected into the Web pages transparently for the
end-user when using `Ajax`.
.. _Web_Block_example:
Web Block example
-----------------
Let's have a simple example, a page containing a single block with a
tag (@_COUNTER_@) which is incremented by one each time it is
used. The code can be found in :file:`demos/web_block`.
First create the following HTML fragment and place it into
:file:`counter.thtml`::
<p>@_COUNTER_@</p>
Then create the main page and place it into :file:`page.thtml`. The
important part is the @_CTX_WB_@ tag which is passed to the link. This
tag is the context identifier, it must be passed to each request. Note
that this is automatically done when using the `Ajax` framework
(see :ref:`Web_Block_and_Ajax`)::
<html>
<head>
<title>Main Page</title>
</head>
<body>
<p>This is the main page, bellow is a simple counter</p>
<p>@_COUNTER_@</p>
<a href="/?CTX_WB=@_CTX_WB_@>Next</a>
</body>
</html>
.. highlight:: ada
The `Web_Callbacks` package contains the application callbacks::
with AWS.Response;
with AWS.Status;
with AWS.Templates;
with AWS.Services.Web_Block.Context;
package Web_Callbacks is
use AWS;
use AWS.Services;
function Main (Request : in Status.Data) return Response.Data;
-- Main callback which handle the home page
procedure Counter
(Request : in Status.Data;
Context : not null access Web_Block.Context.Object;
Translations : in out Templates.Translate_Set);
-- The callback handling the counter web block
end Web_Callbacks;
Last part is to actually implement the `Counter` callback. Here
is a possible implementation making use of the context to keep the
counter state::
with AWS.Utils;
with AWS.Messages;
with AWS.MIME;
with AWS.Services.Web_Block.Registry;
package body Web_Callbacks is
-------------
-- Counter --
-------------
procedure Counter
(Request : in Status.Data;
Context : not null access Web_Block.Context.Object;
Translations : in out Templates.Translate_Set)
is
N : Natural := 0;
begin
if Context.Exist ("N") then
N := Natural'Value (Context.Get_Value ("N"));
end if;
N := N + 1;
Context.Set_Value ("N", Utils.Image (N));
Templates.Insert
(Translations, AWS.Templates.Assoc ("COUNTER", N));
end Counter;
----------
-- Main --
----------
function Main (Request : in Status.Data) return Response.Data is
URI : constant String := Status.URI (Request);
begin
return Web_Block.Registry.Build
(Key => URI,
Request => Request,
Translations => Set);
end Main;
end Web_Callbacks;
Finally, we write the main procedure::
with Ada.Text_IO;
with AWS.Server;
with AWS.Services.Web_Block.Registry;
with Web_Callbacks;
procedure Web_Block is
use Ada;
use AWS;
use AWS.Services;
HTTP : AWS.Server.HTTP;
begin
-- First we register the main page and the counter block
Services.Web_Block.Registry.Register ("/", "page.thtml", null);
Services.Web_Block.Registry.Register
("COUNTER", "counter.thtml",
Web_Callbacks.Counter'Access, Context_Required => True);
-- Then we just start the server
Server.Start (HTTP, "web_block", Web_Callbacks.Main'Access);
Text_IO.Put_Line ("Press Q to terminate.");
Server.Wait (Server.Q_Key_Pressed);
Server.Shutdown (HTTP);
end Web_Block;
Compile and run the server. Then connect to the server and click on
next. The counter will be incremented by one each time.
.. _Web_Block_and_Ajax:
Web Block and Ajax
------------------
The Web Block framework has really been designed to be used with
`Ajax`. It is the only way to gain the full power of the Web Block
framework.
For the complete code, see `demos/web_block_ajax`.
.. highlight:: xml
When using `Ajax` it is not needed to explicitly pass the context
identification to every link. This is done automatically by the
framework. So the main page will look like this::
@@INCLUDE@@ ../../web_elements/javascripts/ajax_api.tjs
<html>
<head>
<title>Main Page</title>
@@INCLUDE@@ ../../web_elements/javascripts/aws.tjs
</head>
<body>
<p>This is the main page, bellow is a simple counter</p>
@_WIDGET_COUNTER_@
</body>
</html>
The counter widget is on :file:`widget_counter.thtml`::
<!-- implementation of a simple counter widget -->
<p><div id="counter">@_COUNTER_@</div></p>
<a id="next" href="/">Next</a>
@_JS_ACTION(onclick, next)_@
For the `Ajax` part, see :ref:`Ajax`.
.. highlight:: ada
We now have one more register call for registering the `next` button
`Ajax` callback, and a callback named `Widget_Counter` for
displaying the block::
Services.Web_Block.Registry.Register
("WIDGET_COUNTER", "widget_counter.thtml",
Web_Callbacks.Widget_Counter'Access);
Services.Web_Block.Registry.Register
("/onclick$next", "r_widget_counter.txml",
Web_Callbacks.Onclick_Next'Access,
Content_Type => MIME.Text_XML,
Context_Required => True);
.. highlight:: xml
The `next` `Ajax` button is using an XML based response which
is defined in :file:`r_widget_counter.txml`::
<?xml version="1.0" encoding="UTF-8" ?>
<response>
<replace id="counter">@_COUNTER_@</replace>
</response>
.. highlight:: ada
The `Widget_Counter` callbacks just have to set the
`COUNTER` tag variable to the corresponding value. This is used to
display the block. The `Ajax` callback `Onclick_Next` has to
increment the counter and set the `COUNTER` tag variable, a simple
implementation is::
procedure Onclick_Next
(Request : in Status.Data;
Context : not null access Web_Block.Context.Object;
Translations : in out Templates.Translate_Set)
is
N : Natural := 0;
begin
if Context.Exist ("N") then
N := Natural'Value (Context.Get_Value ("N"));
end if;
N := N + 1;
Context.Set_Value ("N", Utils.Image (N));
Templates.Insert
(Translations, Templates.Assoc ("COUNTER", N));
end Onclick_Next;
The framework will then call `Onclick_Next` when pressing the
`Next` button. This routine increments N by one sending back a
response based on `r_widget_counter.txml`. Finally, the client
browser will parse this XML response and do the corresponding actions.
.. _Web_Block_and_templates2ada:
Web Block and templates2ada
---------------------------
For the complete code, see `demos/web_block_ajax_templates`.
It is possible to use the `Templates_Parser's templates2ada` tool for
generating the callbacks register calls. This ensures that all tags on the
application Web Pages have a corresponding callback.
.. highlight:: xml
The code is almost identical to the standard `Ajax` example above. The
main difference is that we need to use a naming convention for the
blocks. This way we can generate automatically the corresponding
callbacks using a template. A common convention is to add `LAZY_` as
prefix for the name of the blocks. With this convention the main page
template is::
@@INCLUDE@@ ../../web_elements/javascripts/ajax_api.tjs
<html>
<head>
<title>Main Page</title>
@@INCLUDE@@ ../../web_elements/javascripts/aws.tjs
</head>
<body>
<p>This is the main page, bellow is a simple counter</p>
@_LAZY_WIDGET_COUNTER_@
</body>
</html>
.. highlight:: ada
We need also modify the standard :file:`templates.tads` as distributed
with the `Templates_Parser`. Here is the interesting part::
@@SET@@ PACKAGE = WBlocks
...
with AWS.MIME;
with AWS.Services.Web_Block.Registry;
with Web_Callbacks;
@@TABLE@@
with @_PACKAGE_@.@_CAPITALIZE:REPLACE_ALL(\\./_):BASENAME_@;
@@END_TABLE@@
package body @_PACKAGE_@ is
use AWS;
package body Lazy is
--------------
-- Register --
--------------
procedure Register is
use AWS.Services;
begin
-- Register blocks
@@TABLE@@
@@IF@@ @_UPPER:SLICE(1..5):VARIABLE_LIST_@ = "LAZY_"
Web_Block.Registry.Register
("@_VARIABLE_LIST_@",
"@_LOWER:REPLACE_ALL(LAZY_/):VARIABLE_LIST_@.thtml",
Web_Callbacks.@_CAPITALIZE:REPLACE_ALL(LAZY_/):VARIABLE_LIST_@'Access);
@@END_IF@@
@@END_TABLE@@
-- Register Ajax
@@TABLE@@
@@TABLE@@
@@IF@@ not @_IS_EMPTY:AJAX_EVENT_@
Services.Web_Block.Registry.Register
("/@_AJAX_EVENT_@$@_AJAX_ACTION_@",
@_PACKAGE_@.R_@_CAPITALIZE:REPLACE_ALL(\\./_):AJAX_FILE_@.Template,
Web_Callbacks.@_CAPITALIZE:AJAX_EVENT_@@_UNDERSCORE_@@_CAPITALIZE:AJAX_ACTION_@'Access,
Content_Type => MIME.Text_XML,
Context_Required => True);
@@END_IF@@
@@END_TABLE@@
@@END_TABLE@@
end Register;
end Lazy;
end @_PACKAGE_@;
Basically this is to write a register call for every template's
tag starting with `LAZY_`. The second section is to write a
register call for every `Ajax` event. All callbacks are expected to be in
a package named `Web_Callbacks`. It is of course possible to change
this template to reference callbacks for blocks and `Ajax` in separate
packages. The use of a template here is very flexible.
Now let's parse the application HTML and XML templates and create the
corresponding Ada specs and register calls::
$ templates2ada -d . -o code.ada -t templates.tada -e .thtml -e .txml
$ gnatchop code.ada
Look at the generated code below, it properly register the
`Widget_Counter` callback to be used for rendering
`LAZY_WIDGET_COUNTER` using the :file:`widget_counter.thtml`. So
we have a tight coupling between the code and the template file. If
the tag is renamed in the template file the application will not
compile anymore. The same is true for `Ajax` callbacks, every
`Ajax` action put in a template file needs a corresponding
callback in Ada. This greatly helps keeping the application code
synchronized::
procedure Register is
use AWS.Services;
begin
Web_Block.Registry.Register
("LAZY_WIDGET_COUNTER",
"widget_counter.thtml",
Web_Callbacks.Widget_Counter'Access);
Services.Web_Block.Registry.Register
("/onclick$next",
WBlocks.R_Widget_Counter.Template,
Web_Callbacks.Onclick_Next'Access,
Content_Type => MIME.Text_XML,
Context_Required => True);
end Register;
In the main, it is just now required to register the Web pages and to
call the generated `Register` procedure::
Services.Web_Block.Registry.Register ("/", "page.thtml", null);
WBlocks.Lazy.Register;
Moreover, an Ada spec containing reference for the tag names is
generated for every HTML and XML template file. All tags can be
referenced using those specs, it is not needed to use string
literal in the application. Again, this ensures that a tag which is
renamed or deleted is detected at compilation time. For example the
`Widget_Counter` callback can be rewritten as follow::
procedure Widget_Counter
(Request : in Status.Data;
Context : not null access Web_Block.Context.Object;
Translations : in out Templates.Translate_Set)
is
N : Natural := 0;
begin
if Context.Exist ("N") then
N := Natural'Value (Context.Get_Value ("N"));
end if;
Templates.Insert
(Translations, Templates.Assoc (WBlocks.Widget_Counter.COUNTER, N));
end Widget_Counter;
.. _Web_Cross-References:
Web Cross-References
====================
.. index:: webxref
.. index:: web cross-references
When building an `Ajax` Web applications it is required to give ids to
web elements to be able to reference them. It is also quite common to
use CSS to give such and such item a specific style. After some time
it is quite difficult to keep track of all those ids. This services helps
answer if they are all used, or we reference an id that does not exist
anymore.
`webxref` has been designed to help finding such problems.
The files kinds handled are:
*.css*, *.tcss*
A CSS (or template CSS file). Ids and classes inside are recorded as
CSS definitions.
*.xml*, *.html*, *.thtml*
A meta-language document. Ids and classes inside are recorded as
referencing a CSS definition and meta-language definition.
*.txml*
An `Ajax` response file. Ids declared inside are recorded as referencing
a meta-language definition.
The features are:
*cross-references*
By default `webxref` output all the references to ids and classes.
*finding unused items*
Output the ids/classes that are defined but not used. For example an
id declared in a CSS but never referenced into an HTML document or an
HTML id never referenced in an `Ajax` response file :file:`.txml` document.
*finding undeclared items*
Output ids/classes that are referenced but never defined. This is for
example an id inside an `Ajax` response file which is never defined into
an HTML document.
*enforcing a naming scheme for ids and classes*
It can enforce a specific prefix for ids and classes. The id prefix
can be based on the filename (using filename's first character and all
character before an underscore). This make it less likely to find the
same id on multiple files.
Note that all references are in a format recognized by tools like `GPS`
and `Emacs`. It is then possible to navigate inside them easily.
All `webxref` options are listed using the `-h` option.
.. _WebSockets:
WebSockets
==========
.. index:: websockets
.. index:: web sockets
.. _Introduction_to_WebSockets:
Introduction to WebSockets
--------------------------
WebSockets are part of HTML5, the API is being standardized by the W3C
and the protocol by the IETF (see RFC-6455). It is a bidirectional and
full-duplex communication channel between the client and the
server. Most Web Browsers are now supporting (at least part) of the
WebSocket recommendation. On the client side, the WebSockets are
programmed in JavaScript as done for Ajax for example.
A WebSocket is always opened at the request of a client. This can be
done on the same port as the main HTTP protocol. This is possible because
the initial handshake to open a WebSocket is done in pure HTTP protocol. Past
this initial handshake the socket is switching protocol from HTTP to the one
called WebSocket protocol.
It is not necessary to know the protocol used is WebSockets, AWS comes with
some high level services on the server side and also on the client side.
.. _WebSockets_on_the_client:
WebSockets on the client (javascript)
-------------------------------------
The WebSocket is created on the client side. As there is some differences
between Web browsers, AWS provides a wrapper routine to create a
WebSocket::
ws = AWS.WebSocket.open('ws://localhost:8080/echo');
This basically creates a WebSocket and contacts the local server using
port 8080.
This method is declared in :file:`aws.tjs` which must be included::
@@INCLUDE@@@ aws.tjs
A WebSocket Javascript's object has four method's callbacks:
*onopen*
Called when the WebSocket has been opened. This means that the
initial handshake with the server has been accepted. At this point the
WebSocket is ready to send and receive messages.
*onmessage*
Called for every incoming message. This callback receives a single
parameter which is the event. The actual message data can be found in
**e.data**.
*onclose*
Called when the WebSocket is closing. This means that the server has
sent a close request. After this event it is not possible to send nor
receive messages through this WebSocket.
*onerror*
Called when an error has occurred. This can be a lost connection for
example. This callback takes a single parameter which is the error
message.
AWS comes with default implementations of these callbacks. With the
two optional WebSocket constructor parameters it can be configured to
fit most needs::
ws = AWS.WebSocket.open('ws://localhost:8080/echo', message_id, status_id);
*message_id*
The id of the HTML element which will be used to display the incoming
messages. This is most of the time the id of a `p` or `div` HTML
element.
*status_id*
The id of the HTML element which will be used to display the status
and error messages. For example when a connection is closed.
When those default callbacks are not what is needed it is always
possible to redefine them::
ws.onmessage = function (e) {
code there
};
Likewise for the other events.
.. _WebSockets_on_the_server:
WebSockets on the client (Ada)
------------------------------
AWS also supports writing websocket clients directly in Ada. Here is an
example::
type MySocket is new AWS.Net.WebSocket.Object with null record;
overriding procedure On_Message (Self : in out MySocket; Str : String);
-- You would likely also override On_Error and On_Close
overriding procedure On_Message (Self : in out MySocket; Str : String) is
begin
Ada.Text_IO.Put_Line ("++ Got message '" & Str & "'");
end On_Message;
declare
Socket : MySocket;
begin
AWS.Net.WebSocket.Connect (Socket, "ws://localhost:8765");
-- Send one message
Socket.Send ("some message");
-- Then wait for any number of messages from the server. Give up if
-- no message is available for 2s. If messages become available, the
-- procedure On_Message will be called.
while Socket.Poll (Timeout => 2.0) loop
null;
end loop;
Socket.Close ("");
end;
You are responsible for checking regularly whether any message has been
received from the server.
WebSockets on the server
------------------------
The first step is to setup the server to dispatch the incoming
messages to the proper WebSocket object. For this one needs to inherit
from `AWS.Net.WebSocket.Object` and redefine at least two methods
`Create` and `On_Message`:
*Create*
This is the constructor that will be used by the server to handle some
WebSockets. This constructor will be associated to some URI, see below::
function Create
(Socket : Socket_Access;
Request : AWS.Status.Data) return Object'Class;
The default constructor creates a WebSocket of type
`AWS.Net.WebSocket.Object`. It is not possible to receive events
(close, open, error) using such object it is only possible to
send messages to the clients.
Here is an example on a custom socket::
type MySocket is new Net.WebSocket.Object with null record;
function Create
(Socket : Socket_Access;
Request : AWS.Status.Data) return AWS.Net.WebSocket.Object'Class
is
-- Note the call to the other version of Create*
return MySocket'
(AWS.Net.WebSocket.Object
(AWS.Net.WebSocket.Create (Socket, Request)) with null record);
end Create;
It is also possible to deny the handshake by returning an object from
AWS.Net.WebSocket.Handshake_Error.
*On_Open*
This is the callback that will be called when the WebSocket is opened::
procedure On_Open
(Socket : in out Object; Message : String) is null;
*On_Message*
This is the callback that will be called for every message sent by the
client on the corresponding WebSocket::
procedure On_Message
(Socket : in out Object; Message : String);
The first parameter is the WebSocket itself, it is possible to send a
message directly by using the associated `Send` method. Note that
the default implementation supports the XML based Ajax actions.
See see :ref:`XML_based_Ajax` and can be used to redirect simple message
to an HTML widget given it's id.
*On_Close*
This is the callback that will be called when the WebSocket is closed::
procedure On_Close
(Socket : in out Object; Message : String) is null;
*On_Error*
This is the callback that will be called when an error occurs on the
WebSocket::
procedure On_Error
(Socket : in out Object; Message : String) is null;
When this is done, the constructor declared above needs to be
registered to handle some WebSocket designated by the URI. For example
to have this WebSocket handling all URI named `/echo`::
Net.WebSocket.Registry.Register ("/echo", CB.Create'Access);
Where `CB.Create` is the constructor redefined for the new
WebSocket class.
The last step is to start the WebSocket server which are needed to
handle the incoming messages::
Net.WebSocket.Registry.Control.Start;
At this point all is setup to have AWS supports WebSockets. Sending
messages can be done to a single client or by broadcasting to all
clients for a specific URI. To send a message one need to create a
`Net.WebSocket.Registry.Recipient` object. For example to
broadcast a message to all Web clients having opened the `/echo`
WebSocket::
Rcp : Net.WebSocket.Registry.Recipient :=
Net.WebSocket.Registry.Create (URI => "/echo");
Net.WebSocket.Registry.Send (Rcp, "A simple message");
As we have seen before, this will send a message to clients which will
in turn trigger the `onmessage` Javascript method.
It is also possible to send a message to clients from a specific
origin by using the `Origin` information::
Rcp : Net.WebSocket.Registry.Recipient :=
Net.WebSocket.Registry.Create (URI => "/echo"; Origin => ".*\\.fr");
Net.WebSocket.Registry.Send (Rcp, "A simple message");
The above recipent targets all WebSockets whose URI is `"/echo"`
and that have been created from a Web page originating from a Web server
running in the `.fr` domain. Note that `URI` and the
`Origin` are regular expressions.
The `Origin` value can be used by a server to handle only
WebSockets originating from it's own domain. Restricting the origin of
the WebSockets can be done with the `WEBSOCKET_ORIGIN` config
parameter, see :ref:`WebSocket_Origin`.