Compare commits

...

55 Commits

Author SHA1 Message Date
6c51590369 Updated installation location of openid and http_authorization in ISE package.
Added iron package file for ewsgi.
2015-08-24 16:12:25 +02:00
jvelilla
c824f707cf Fixed typo: Aug instead of Aou. 2015-08-06 10:42:15 +02:00
47c5b798b3 Cosmetic true -> True 2015-08-04 13:24:03 +02:00
8651ff6e1e Fixing script_url' that wrongly used path_info' instead of `percent_encoded_path_info'.
(issue on script_url when path info contains unicode character).
2015-08-04 13:03:51 +02:00
629edea991 Merge remote-tracking branch 'javier/ewf_html_form' into v1 2015-08-04 13:00:08 +02:00
jvelilla
1e10ce8518 Updated set_value for WSF_FORM_SELECTABLE_INPUT (for example a checkbox).
Call the feature set_checked_by_value iff the the current value exist in the
list of values, in other case set checked in Flase.
If we call set_checked_by_value without filter, previous checked values will be
set in False.
2015-07-31 11:55:23 -03:00
4f8f17ad48 Fixed various compilation issues.
Ensure the obsolete/v0 ecf has new UUID.
2015-07-03 20:02:13 +02:00
148518984e Added the possibility to provide the sendmail location in NOTIFICATION_SENDMAIL_MAILER.
Added NOTIFICATION_STORAGE_MAILER which allow to store the email in a storage (could be just output, file, database ...)
Added SMTP implementation, based on EiffelNet SMTP_PROTOCOL.
   note: it is possible to exclude this by setting ecf variable "smtp_notification_email_disabled" to "True"
   this way help to manage dependencies, since the Eiffel Net library would not be included neither.
Fixed Date header value computation.
2015-07-03 10:02:56 +02:00
33150e34d6 Reverted previous changed related to redefinition of set_status_code which was against existing assertions. 2015-07-02 15:11:33 +02:00
af60a5719e Updated eiffelstudio locations for EWF libraries. 2015-07-02 13:06:38 +02:00
31557cfc33 Fixed WGI_HTTPD_REQUEST_HANDLER.process_rescue
Fixed WGI_STANDALONE_OUTPUT_STREAM.is_available
Added WGI_STANDALONE_RESPONSE_STREAM.is_persistent_connection_supported
2015-07-02 10:50:41 +02:00
78c0cd5b0d Merge branch 'v1' of https://github.com/EiffelWebFramework/EWF into v1 2015-07-01 21:48:47 +02:00
412534d0be Fixed compilation of all*-safe.ecf files.
Corrected a few comments.
2015-07-01 21:43:54 +02:00
jvelilla
0f6aa8d7ae Merge branch 'jvelilla-ewf_v1_workbook' into v1 2015-06-30 09:58:40 -03:00
jvelilla
2c745c63d3 Updated workbook: generating response, handling cookies and headers documents. 2015-06-30 09:21:12 -03:00
jvelilla
efd80c1287 Updated workbook form document 2015-06-30 09:00:47 -03:00
jvelilla
01f649fd88 Updated: workbook headers document.
Removed: unnecessary files.
2015-06-30 08:57:00 -03:00
jvelilla
f23aeb6412 Updated Workbook basic documentation. 2015-06-30 08:42:29 -03:00
jvelilla
1a4596c79b Merge branch 'ewf_v1_workbook' of https://github.com/jvelilla/EWF into ewf_v1_workbook 2015-06-29 19:05:25 -03:00
jvelilla
b16e4aa570 Updated basic documentation 2015-06-29 19:03:54 -03:00
Javier Velilla
5255b15fa9 Update basics.md 2015-06-29 18:36:44 -03:00
jvelilla
57048373f4 Update basic document 2015-06-29 18:30:11 -03:00
Javier Velilla
9e06fb2ab8 Update basics.md 2015-06-29 16:24:09 -03:00
Javier Velilla
f2405e0ccd Update basics.md 2015-06-29 16:22:01 -03:00
Javier Velilla
6e3a7deb6e Update workbook.md 2015-06-29 16:20:19 -03:00
jvelilla
f254b599c0 Update basic document to the new EWF concurrent design 2015-06-29 16:17:43 -03:00
99a05b95ba Improved code related to cookie management (avoid duplicated cookie). 2015-06-22 22:06:16 +02:00
54dd43c38a Synchronized wsf-safe.ecf and wsf.ecf 2015-06-18 14:53:19 +02:00
903f925a79 Changed the way SSL is supported with standalone connector (httpd lib).
Now by default, SSL is not supported,
  and if an application wants the SSL support,
  the related .ecf has to set custom variable "httpd_ssl_enabled" to "true"
2015-06-17 17:22:59 +02:00
80709578d6 Updated workbook Eiffel code to follow new EWF concurrent design. 2015-06-16 20:34:57 +02:00
c0d5b7c968 Added make_from_execution procedure to ease implementing various use cases. 2015-06-16 15:02:17 +02:00
7bea163f46 Updated ecf from obsolete v0 folder to include the "_v0" suffix in the library names. 2015-06-14 21:34:41 +02:00
8992dbc515 Added wsf_html in the obsolete v0 folder.
mostly because it is also dependent on "wsf", so it has to be using the obsolete v0 ecf.
2015-06-12 19:01:35 +02:00
c2d3ea6138 Simplified file names, and harmonized with estudio wizards. 2015-06-12 12:56:46 +02:00
9e336deb49 Updated EWF estudio wizard. 2015-06-12 12:43:54 +02:00
0160ce05dd Updated wizard template ecf to take into account current limitation, or known issue related to libcurl and ssl. 2015-06-12 12:00:54 +02:00
7d089a88c2 Made compilable without SSL enabled
(i.e when variable named "httpd_ssl_disabled" is set to "true")
2015-06-11 10:08:44 +02:00
ab0bc7b314 Marked most of the *_with_request_methods procedure obsolete by the same feature name without the "_with_request_methods".
Added argument passing request methods to feature without the _with_request_methods.
Prefer "thread" concurrency for now in examples.
2015-06-10 17:52:26 +02:00
0e3e97a7fd Added a few example based on the obsolete libraries (v0).
Updated the tutorial example.
Added WSF_MESSAGE_EXECUTION.
2015-06-10 16:49:23 +02:00
b790c7fd21 cosmetic, cleaning. 2015-06-10 10:59:24 +02:00
9424b1e369 Merge branch 'master' into v1 2015-06-08 10:10:51 +02:00
64463df552 Fixed various compilation error or warning. 2015-06-05 12:17:53 +02:00
jvelilla
61f90bba38 Merge branch 'jvelilla-ewf_wsf_html5' 2015-05-29 15:38:30 -03:00
jvelilla
fac3dd3946 Merge branch 'ewf_wsf_html5' of https://github.com/jvelilla/EWF into jvelilla-ewf_wsf_html5 2015-05-29 15:38:08 -03:00
jvelilla
e1b583a2b3 Updated code based on comments 2015-05-28 10:07:40 -03:00
jvelilla
8c8dfdd4a3 Updated code based on review 2015-05-28 10:04:13 -03:00
jvelilla
857397e226 Updated code inherit from SHARED_HTML_ENCODER instead of creating new objects. 2015-05-28 09:39:14 -03:00
jvelilla
94340c1c01 Updated html5 classes based on review 2015-05-28 09:19:21 -03:00
8b60ab08e3 Added WSF_FILE_UTILITIES.new_file (p: PATH): detachable G
in order to provide non existing file, but not only for temporary files purpose.
2015-05-22 22:27:28 +02:00
jvelilla
28e51cc314 Initial import HTML5 support for attributes and input types.
Attributes
- Added support for new HTML5 attributes.
missing support for : form, list, and multiple attributes.

Input types: added the all the new input types.

Added test cases, still in progress.
2015-05-21 12:32:08 -03:00
jvelilla
a7c8d40b3e Moved EWF workbook from ewf_example to EWF main repository. 2015-05-18 11:06:04 -03:00
d4c0ff03b4 Added package.iron for nino library. 2015-05-12 19:04:36 +02:00
7fbfda3a66 Refactored wsf router dispatching implementation.
Now the path to take into account during dispatching is computed once
  in WSF_ROUTER.path_to_dispatch (req: WSF_REQUEST): READABLE_STRING_8
  And this function could be redefined in descendant of WSF_ROUTER.
2015-05-12 18:37:25 +02:00
9e467689df improved nino port number validation 2015-05-12 18:25:22 +02:00
dd5c89e31c Fixed compilation of SSL_TCP_STREAM_SOCKET with recent do_accept changes. 2015-05-07 10:44:38 +02:00
236 changed files with 10383 additions and 689 deletions

View File

@@ -1,38 +1,7 @@
History for Eiffel-Web-Framework
[2015-06-10]
* Updated EWF design to better support concurrency, including SCOOP via
the new standalone connector.
[2011-09-23] Jocelyn
* library "ewsgi":
- NEW simple autotest cases using Nino web server
-fixed issue with RAW_POST_DATA being added in form_data_parameters
instead of meta_variables ...
- Implemented WGI_VALUE for parameter's type (query_parameter,
form_data_parameter, item ...)
* Nino connector: added feature to shutdown the server from the WGI application
* NEW library "http_client": a new library to perform simple http requests
such as get, head, post, put, ... (currently implemented with Eiffel cURL)
* NEW library "http_authorization": added simple library to support
HTTP_AUTHORIZATION. For now only "Basic" auth type is supported ..
[2011-09-22] Javier
* NEW Example: added partial Restbuck example
[2011-09-21] Jocelyn
* Nino connector: fixed an issue with missing value for Content-Type and Content-Length
[2011-09-13] Jocelyn
* library "router": now using a generic design to allow customization of
request handler context class.
* NEW library "server/request/rest": first attempt to provide a library to
help building RESTful application (the interfaces are likely to change
soon) EXPERIMENTAL
[2011-09-09] Jocelyn
* library "uri-template": better support for {/vars} and {?vars}
[2011-09-07] Jocelyn
* library "router": now routing depends on uri (or uri template) and request methods
* Nino connector: Fixed issue where HTTP_ prefix were missing for header meta variable.
[2011-09-07] Jocelyn
* changelog: starting to write down changelogs file
[Previous ] Many significant changes in v0

View File

@@ -1,4 +1,4 @@
Date: 2015-mar-31
Date: 2015-june
# Goal:
=======
@@ -7,7 +7,7 @@ Date: 2015-mar-31
# Status:
=========
- The current version of EWF has mainly 3 connectors: CGI, libFCGI, and nino.
- The version v0 of EWF has mainly 3 connectors: CGI, libFCGI, and nino.
- CGI and libFCGI connectors does not need any concurrency support.
- But the nino connector had a pseudo concurrency support with Thread, however one could do write code that result in hasardeous concurrency execution.

View File

@@ -0,0 +1,14 @@
package nino
project
nino = "nino-safe.ecf"
nino = "nino.ecf"
note
title: Eiffel Nino Web Server
description: Simple HTTPd server written in Eiffel
tags: web, httpd, server
license: Eiffel Forum v2
copyright: Javier Velilla, Jocelyn Fiat.
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,206 @@
Nav: [Workbook](../workbook.md) | [Handling Requests: Form/Query Parameter](/doc/workbook/handling_request/form.md)
## EWF basic service
##### Table of Contents
- [Basic Structure](#structure)
- [Service to Generate Plain Text](#text)
- [Source code](#source_1)
- [Service to Generate HTML](#html)
- [Source code](#source_2)
<a name="structure"/>
## EWF service structure
The following code describes the basic structure of an EWF basic service that handles HTTP requests. We will need to define a Service Launcher and a Request Execution implementation.
```eiffel
class
APPLICACTION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
create
make_and_launch
end
```
The class ```APPLICATION``` inherit from
```WSF_DEFAULT_SERVICE [G ->WSF_EXECUTION create make end]``` it will be responsible to launch the service and set optional options.
The class ```APPLICATION_EXECUTION``` is an implementation of ```WSF_EXECUTION``` interface, which is instantiated for each incoming request.
```eiffel
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
do
-- To read incoming HTTP request, we need to use `req'
-- May require talking to databases or other services.
-- To send a response we need to setup, the status code and
-- the response headers and the content we want to send out our client
end
end
```
When using the "nino" connector or the new "standalone" connector, by default the service listens on port 80, but often this port is already used by other applications, so it is recommended to use another port.
To define another port, redefine the feature `initialize' and set up a new port number using the service options (see below).
```eiffel
class
APPLICATION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
set_service_option ("port", 9090)
end
end
```
The **WSF_REQUEST** gives access to the incoming data; the class provides features to get information such as request method, form data, query parameters, uploaded files, HTTP request headers, and hostname of the client among others.
The **WSF_RESPONSE** provides features to define the response with information such as HTTP status codes (10x,20x, 30x, 40x, and 50x), response headers (Content-Type, Content-Length, etc.) and obviously the body of the message itself.
**APPLICATION** is the root class of our example, it launches the application, using the corresponding connector, Which connector? this depends how you want to run it cgi, fcgi,nino or standalone. For development is recommended to use a standalone web server written in Eiffel, and run the execution within the EiffelStudio debugger. For production fcgi (or cgi) using Apache or another popular web server.
![Launcher Hierarchy](/doc/workbook/basics/Launcher Hierarchy.png "Launcher Hierarchy")
**WS_LAUNCHABLE_SERVICE** inherit from **WS_SERVICE** class, which is a marker interface in EWF. And also provides a way to launch our application using different kind of connectors. The class **WSF_DEFAULT_SERVICE_I**, inherit from **WS_LAUNCHABLE_SERVICE** and has a formal generic that should conform to **WSF_SERVICE_LAUNCHER [WSF_EXECUTION]**. Below a [BON diagram] (http://www.bon-method.com/index_normal.htm) showing one of the possible options.
![Standalone Launcher](/doc/workbook/basics/WSF_SERVICE_LAUNCHER_STANDALONE.png "Standalone Hierarchy")
Other connectors:
**WSF_STANDALONE_SERVICE_LAUNCHER**
**WSF_CGI_SERVICE_LAUNCHER**
**WSF_NINO_SERVICE_LAUNCHER**
**WSF_LIBFCGI_SERVICE_LAUNCHER**
A basic EWF service inherits from **WSF_DEFAULT_SERVICE**, which has a formal generic that should conform to **WSF_EXECUTION** class with a `make' creation procedure, in our case the class **APPLICATION_EXECUTION**.
The **APPLICATION_EXECUTION** class inherits from **WSF_EXECUTION** interface, which is instantiated for each incoming request. **WSF_EXECUTION** inherit from **WGI_EXECUTION** which is the low level entry point in EWF, handling each incoming request with a single procedure ```execute (req: WSF_REQUEST; res: WSF_RESPONSE) ...```.
In the **APPLICATION_EXECUTION** class class you will need to implement implement the **execute** feature, get data from the request *req* and write the response in *res*.
![Execution Hierarchy](/doc/workbook/basics/APPLICATION_EXECUTION.png "Application Execution ")
The WSF_EXECUTION instance, in this case ```APPLICATION_EXECUTION``` is created per request, with two main attributes request: ```WSF_REQUEST``` and response: ```WSF_RESPONSE```.
<a name="text"/>
## A simple Service to Generate Plain Text.
Before to continue, it is recommended to review the getting started guided. In the example we will only shows the implementation of the WSF_EXECUTION interface.
```eiffel
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute
-- Execute the incomming request
do
-- To send a response we need to setup, the status code and
-- the response headers.
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/plain"], ["Content-Length", "11"]>>)
response.put_string ("Hello World")
end
end
```
<a name="source_1"></a>
##### Source code
The source code is available on Github. You can get it by running the command:
```git clone https://github.com/EiffelWebFramework/ewf.git```
The example of simple service that generate plain text response is located in the directory $PATH/ewd/doc/workbook/basics/simple, where $PATH is where you run ```git clone``` . Just double click on the simple.ecf file and select the simple_nino target or if you prefer the command line, run the command:
```estudio -config simple.ecf -target simple_nino```
<a name="html"></a>
## A Service to Generate HTML.
To generate HTML, it's needed
1. Change the Content-Type : "text/html"
2. Build an HTML page
```eiffel
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute
-- Execute the incomming request
do
-- To send a response we need to setup, the status code and
-- the response headers.
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", web_page.count.out]>>)
response.put_string (web_page)
end
web_page: STRING = "[
<!DOCTYPE html>
<html>
<head>
<title>Resume</title>
</head>
<body>
Hello World
</body>
</html>
]"
end
```
##### Source code
The source code is available on Github. You can get it by running the command:
```git clone https://github.com/EiffelWebFramework/ewf.git```
The example of the service that generates HTML is located in the directory $PATH/ewf/doc/workbook/basics/simple_html, where $PATH is where you run ```git clone``` . Just double click on the simple_html.ecf file and select the simple_html_nino target or if you prefer the command line, run the command:
```estudio -config simple_html.ecf -target simple_html_nino```
Nav: [Workbook](../workbook.md) | [Handling Requests: Form/Query Parameter](/doc/workbook/handling_request/form.md)

View File

@@ -0,0 +1,24 @@
note
description: "Basic Service launcher"
class
APPLICATION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
set_service_option ("port", 9090)
end
end

View File

@@ -0,0 +1,25 @@
note
description : "Basic Service that Generates Plain Text"
date : "$Date$"
revision : "$Revision$"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute
-- Execute the incomming request
do
-- To send a response we need to setup, the status code and
-- the response headers.
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/plain"], ["Content-Length", "11"]>>)
response.put_string ("Hello World")
end
end

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-13-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-13-0 http://www.eiffel.com/developers/xml/configuration-1-13-0.xsd" name="simple" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486" library_target="simple">
<target name="common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" full_class_checking="false" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
</target>
<target name="simple_nino" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_nino" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\nino-safe.ecf"/>
<cluster name="simple" location=".\" recursive="true"/>
</target>
<target name="simple_cgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\cgi-safe.ecf"/>
<cluster name="simple" location=".\" recursive="true"/>
</target>
<target name="simple_libfcgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\libfcgi-safe.ecf"/>
<cluster name="simple" location=".\" recursive="true"/>
</target>
<target name="simple_standalone" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_standalone" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\standalone-safe.ecf"/>
<cluster name="simple" location=".\" recursive="true"/>
</target>
<target name="simple" extends="simple_nino">
</target>
</system>

View File

@@ -0,0 +1,52 @@
##Run simple_html example on Apache with FCGI on Windows.
####Prerequisites
* This tutorial was written for people working under Windows environment, and using Apache Server with FCGI connector
* Compile the ewf application from command line.
* Assuming you have installed Apache Server under C:/home/server/Apache24.
* Assuming you have placed your current project under C:/home/server/Apache24/fcgi-bin.
* Assuming you have setted the Listen to 8888, the defautl value is 80 .
####FCGI module
If you don't have the FCGI module installed, you can get it from https://www.apachelounge.com/download/, download the module based on your platform [modules-2.4-win64-VC11.zip](https://www.apachelounge.com/download/VC11/modules/modules-2.4-win64-VC11.zip) or [modules-2.4-win32-VC11.zip](https://www.apachelounge.com/download/VC11/modules/modules-2.4-win32-VC11.zip), uncompress it
and copy the _mod_fcgid.so_ to C:/home/server/Apache24/modules
####Compile the project simple_html using the fcgi connector.
ec -config simple_html.ecf -target simple_html_fcgi -finalize -c_compile -project_path .
Copy the genereted exe to C:/home/server/Apache24/fcgi-bin folder.
Check if you have _libfcgi.dll_ in your PATH.
####Apache configuration
Add to httpd.conf the content, you can get the configuration file [here](config.conf)
```
LoadModule fcgid_module modules/mod_fcgid.so
<IfModule mod_fcgid.c>
<Directory "C:/home/server/Apache24/fcgi-bin">
SetHandler fcgid-script
Options +ExecCGI +Includes +FollowSymLinks -Indexes
AllowOverride All
Require all granted
</Directory>
ScriptAlias /simple "C:/home/server/Apache24/fcgi-bin/simple_html.exe"
</IfModule>
```
Test if your httpd.conf is ok
>httpd -t
Luanch the server
>httpd
Check the application
>http://localhost:8888/simple

View File

@@ -0,0 +1,12 @@
LoadModule fcgid_module modules/mod_fcgid.so
<IfModule mod_fcgid.c>
<Directory "C:/home/server/Apache24/fcgi-bin">
SetHandler fcgid-script
Options +ExecCGI +Includes +FollowSymLinks -Indexes
AllowOverride All
Require all granted
</Directory>
ScriptAlias /simple "C:/home/server/Apache24/fcgi-bin/simple_html.exe"
</IfModule>

View File

@@ -0,0 +1,24 @@
note
description: "Basic Service launcher"
class
APPLICATION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
set_service_option ("port", 9090)
end
end

View File

@@ -0,0 +1,66 @@
note
description : "Basic Service that Generate HTML"
date : "$Date$"
revision : "$Revision$"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute
-- Execute the incomming request
do
-- To send a response we need to setup, the status code and
-- the response headers.
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", web_page.count.out]>>)
response.put_string (web_page)
end
web_page: STRING = "[
<!DOCTYPE html>
<html>
<head>
<title>Resume</title>
</head>
<body>
<div id="header">
<p id="name">Your Name Here</p>
<a href="mailto:you@yourdomain.com"><p id="email">you@yourdomain.com</p></a>
</div>
<div class="left"></div>
<div class="right">
<h4>Objective</h4>
<p>To take a position as a software engineer.</p>
<h4>Experience</h4>
<p>Junior Developer, Software Company (2010 - Present)</p>
<ul>
<li>Designed and implemented end-user features for Flagship Product</li>
<li>Wrote third-party JavaScript and Eiffel libraries</li>
</ul>
<h4>Skills</h4>
<p>Languages: C#, JavaScript, Python, Ruby, Eiffel</p>
<p>Frameworks: .NET, Node.js, Django, Ruby on Rails, EWF</p>
<h4>Education</h4>
<p>BS, Economics, My University</p>
<ul>
<li>Award for best senior thesis</li>
<li>GPA: 3.8</li>
</ul>
</div>
<div id="footer">
<p>123 Your Street, Anytown, State 12345-6789 | Tel: (555) 555-5555</p>
</div>
</body>
</html>
]"
end

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="simple_html" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486" library_target="simple_html">
<target name="common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
</target>
<target name="simple_html_nino" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_nino" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\nino-safe.ecf"/>
<cluster name="simple_html" location=".\" recursive="true"/>
</target>
<target name="simple_html_cgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\cgi-safe.ecf"/>
<cluster name="simple_html" location=".\" recursive="true"/>
</target>
<target name="simple_html_libfcgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\libfcgi-safe.ecf"/>
<cluster name="simple_html" location=".\" recursive="true"/>
</target>
<target name="simple_html" extends="simple_html_nino">
</target>
</system>

View File

@@ -0,0 +1,6 @@
#include <windows.h>
STRINGTABLE
BEGIN
1 "This Program was made using EiffelStudio using Visual Studio C++"
END

View File

@@ -0,0 +1,24 @@
note
description: "Basic Service launcher"
class
APPLICATION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
set_service_option ("port", 9090)
end
end

View File

@@ -0,0 +1,36 @@
note
description : "Basic Service that show how to use common Status Code"
date : "$Date$"
revision : "$Revision$"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute
-- Execute the incomming request
do
-- To send a response we need to setup, the status code and
-- the response headers.
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/csv"],["Content-Disposition","attachment;filename=Report.xls"],["Content-Length", sheet.count.out]>>)
response.put_string (sheet)
end
-- ,["Content-Disposition","attachment;filename=Report.xls"]
sheet: STRING ="[
Q1 Q2 Q3 Q4 Total
Cherries 78 87 92 29 =SUM(B2:E2)
Grapes 77 86 93 30 =SUM(B3:E3)
]"
end

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="exel" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486" library_target="exel">l
<target name="common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
</target>
<target name="exel_nino" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_nino" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\nino-safe.ecf"/>
<cluster name="exel" location=".\" recursive="true"/>
</target>
<target name="exel_cgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\cgi-safe.ecf"/>
<cluster name="exel" location=".\" recursive="true"/>
</target>
<target name="exel_libfcgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\libfcgi-safe.ecf"/>
<cluster name="exel" location=".\" recursive="true"/>
</target>
<target name="exel" extends="exel_nino">
</target>
</system>

View File

@@ -0,0 +1,999 @@
Nav: [Workbook](../workbook.md) | [Handling Requests: Header Fields](/doc/workbook/handling_request/headers.md) | [Handling Cookies](/doc/workbook/handling_cookies/handling_cookies.md)
## EWF Generating Response
##### Table of Contents
- [Format of the HTTP response](#format)
- [How to set status code](#status_set)
- [How to redirect to a particular location.](#redirect)
- [HTTP Status codes](#status)
- [Example Staus Codes](#example_1)
- [Generic Search Engine](#example_2)
- [Response Header Fields](#header_fields)
<a name="format"/>
## Format of the HTTP response
As we saw in the previous documents, a request from a user-agent (browser or other client) consists of an HTTP command (usually GET or POST), zero or more request headers (one or more in HTTP 1.1, since Host is required), a blank line, and only in the case of POST/PUT requests, payload data. A typical request looks like the following.
```
GET /url[query_string] HTTP/1.1
Host: ...
Header2: ...
...
HeaderN:
(Blank Line)
```
When a Web server responds to a request, the response typically consists of a status line, some response headers, a blank line, and the document. A typical response
looks like this:
```
HTTP/1.1 200 OK
Content-Type: text/html
Header2: ...
...
HeaderN: ...
(Blank Line)
<!DOCTYPE ...>
<HTML>
<HEAD>...</HEAD>
<BODY>
...
</BODY>
</HTML>
```
The status line consists of the HTTP version (HTTP/1.1 in the preceding example), a status code (an integer 200 in the example), and a very short message corresponding to the status code (OK in the example). In most cases, the headers are optional except for Content-Type, which specifies the MIME type of the document that follows. Although most responses contain a document, some dont. For example, responses to HEAD requests should never include a document, and various status codes essentially indicate failure or redirection (and thus either dont include a document or include only a short error-message document).
<a name="status_set"/>
## How to set the status code
If you need to set an arbitrary status code, you can use the ```WSF_RESPONSE.put_header``` feature or the ```WSF_RESPONSE.set_status_code``` feature. An status code of 200 is a default value. See below examples using the mentioned features.
### Using the WSF_RESPONSE.put_header feature.
In this case you provide the status code with a collection of headers.
```eiffel
put_header (a_status_code: INTEGER_32; a_headers: detachable ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]])
-- Put headers with status `a_status', and headers from `a_headers'
require
a_status_code_valid: a_status_code > 0
status_not_committed: not status_committed
header_not_committed: not header_committed
ensure
status_code_set: status_code = a_status_code
status_set: status_is_set
message_writable: message_writable
Example
res.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", output_size]>>)
res.put_string (web_page)
```
### Using the WSF_RESPONSE.set_status code
```eiffel
custom_response (req: WSF_REQUEST; res: WSF_RESPONSE; output: STRING)
local
h: HTTP_HEADER
l_msg: STRING
do
create h.make
create l_msg.make_from_string (output)
h.put_content_type_text_html
h.put_content_length (l_msg.count)
h.put_current_date
res.set_status_code ({HTTP_STATUS_CODE}.ok)
res.put_header_text (h.string)
res.put_string (l_msg)
end
```
Both features takes an INTEGER (the status code) as an formal argument, you can use 200, 300, 500 etc directly, but instead of using explicit numbers, it's recommended to use the constants defined in the class [HTTP_STATUS_CODE](). The name of each constant is based from the standard [HTTP 1.1](https://httpwg.github.io/).
<a name="redirect"/>
## How to redirect to a particular location.
To redirect the response to a new location, we need to send a 302 status code, to do that we use ```{HTTP_STATUS_CODE}.found```
> The 302 (Found) status code indicates that the target resource resides temporarily under a different URI. Since the redirection might be altered on occasion, the client ought to continue to use the effective request URI for future requests.
Another way to do redirection is with 303 status code
> The 303 (See Other) status code indicates that the server is redirecting the user agent to a different resource, as indicated by a URI in the Location header field, which is intended to provide an indirect response to the original request.
The next code show a custom feature to write a redirection, you can use found or see_other based on your particular requirements.
```eiffel
send_redirect (req: WSF_REQUEST; res: WSF_RESPONSE; a_location: READABLE_STRING_32)
-- Redirect to `a_location'
local
h: HTTP_HEADER
do
create h.make
h.put_content_type_text_html
h.put_current_date
h.put_location (a_location)
res.set_status_code ({HTTP_STATUS_CODE}.found)
res.put_header_text (h.string)
end
```
The class [WSF_RESPONSE]() provide features to work with redirection
```eiffel
redirect_now (a_url: READABLE_STRING_8)
-- Redirect to the given url `a_url'
require
header_not_committed: not header_committed
redirect_now_custom (a_url: READABLE_STRING_8; a_status_code: INTEGER_32; a_header: detachable HTTP_HEADER; a_content: detachable TUPLE [body: READABLE_STRING_8; type: READABLE_STRING_8])
-- Redirect to the given url `a_url' and precise custom `a_status_code', custom header and content
-- Please see http://www.faqs.org/rfcs/rfc2616 to use proper status code.
-- if `a_status_code' is 0, use the default {HTTP_STATUS_CODE}.temp_redirect
require
header_not_committed: not header_committed
redirect_now_with_content (a_url: READABLE_STRING_8; a_content: READABLE_STRING_8; a_content_type: READABLE_STRING_8)
-- Redirect to the given url `a_url'
```
The ```WSF_RESPONSE.redirect_now``` feature use the status code ```{HTTP_STATUS_CODE}.found```,the other redirect features enable customize the status code and content based on your requirements.
Using a similar approach we can build features to answer a bad request (400), internal server error (500), etc. We will build a simple example showing the most common HTTP status codes.
<a name="status"/>
## [HTTP 1.1 Status Codes](https://httpwg.github.io/specs/rfc7231.html#status.codes)
The status-code element is a three-digit integer code giving the result of the attempt to understand and satisfy the request. The first digit of the status-code defines the class of response.
General categories:
* [1xx](https://httpwg.github.io/specs/rfc7231.html#status.1xx) Informational: The 1xx series of response codes are used only in negotiations with the HTTP server.
* [2xx](https://httpwg.github.io/specs/rfc7231.html#status.2xx) Sucessful: The 2xx error codes indicate that an operation was successful.
* [3xx](https://httpwg.github.io/specs/rfc7231.html#status.3xx) Redirection: The 3xx status codes indicate that the client needs to do some extra work to get what it wants.
* [4xx](https://httpwg.github.io/specs/rfc7231.html#status.4xx) Client Error: These status codes indicate that something is wrong on the client side.
* [5xx](https://httpwg.github.io/specs/rfc7231.html#status.5xx) Server Error: These status codes indicate that something is wrong on the server side.
Note: use ```res.set_status_code({HTTP_STATUS_CODE}.bad_request)``` rather than ```res.set_status_code(400)```.
<a name="example_1"/>
### Example Staus Codes
Basic Service that builds a simple web page to show the most common status codes
```eiffel
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute the incomming request
local
l_message: STRING
do
-- To send a response we need to setup, the status code and
-- the response headers.
if req.is_get_request_method then
if req.path_info.same_string ("/") then
res.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", web_page.count.out]>>)
res.put_string (web_page)
elseif req.path_info.same_string ("/redirect") then
send_redirect (req, res, "https://httpwg.github.io/")
-- res.redirect_now (l_engine_url)
elseif req.path_info.same_string ("/bad_request") then
-- Here you can do some logic for example log, send emails to register the error, before to send the response.
create l_message.make_from_string (message_template)
l_message.replace_substring_all ("$title", "Bad Request")
l_message.replace_substring_all ("$status", "Bad Request 400")
res.put_header ({HTTP_STATUS_CODE}.bad_request, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>)
res.put_string (l_message)
elseif req.path_info.same_string ("/internal_error") then
-- Here you can do some logic for example log, send emails to register the error, before to send the response.
create l_message.make_from_string (message_template)
l_message.replace_substring_all ("$title", "Internal Server Error")
l_message.replace_substring_all ("$status", "Internal Server Error 500")
res.put_header ({HTTP_STATUS_CODE}.internal_server_error, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>)
res.put_string (l_message)
else
create l_message.make_from_string (message_template)
l_message.replace_substring_all ("$title", "Resource not found")
l_message.replace_substring_all ("$status", "Resource not found 400")
res.put_header ({HTTP_STATUS_CODE}.not_found, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>)
res.put_string (l_message)
end
else
create l_message.make_from_string (message_template)
l_message.replace_substring_all ("$title", "Method Not Allowed")
l_message.replace_substring_all ("$status", "Method Not Allowed 405")
-- Method not allowed
res.put_header ({HTTP_STATUS_CODE}.method_not_allowed, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>)
res.put_string (l_message)
end
end
feature -- Home Page
send_redirect (req: WSF_REQUEST; res: WSF_RESPONSE; a_location: READABLE_STRING_32)
-- Redirect to `a_location'
local
h: HTTP_HEADER
do
create h.make
h.put_content_type_text_html
h.put_current_date
h.put_location (a_location)
res.set_status_code ({HTTP_STATUS_CODE}.see_other)
res.put_header_text (h.string)
end
web_page: STRING = "[
<!DOCTYPE html>
<html>
<head>
<title>Example showing common status codes</title>
</head>
<body>
<div id="header">
<p id="name">Use a tool to see the request and header details, for example (Developers tools in Chrome or Firebugs in Firefox)</p>
</div>
<div class="left"></div>
<div class="right">
<h4>This page is an example of Status Code 200</h4>
<h4> Redirect Example </h4>
<p> Click on the following link will redirect you to the HTTP Specifcation, we can do the redirect from the HTML directly but
here we want to show you an exmaple, where you can do something before to send a redirect <a href="/redirect">Redirect</a></p>
<h4> Bad Request </h4>
<p> Click on the following link, the server will answer with a 400 error, check the status code <a href="/bad_request">Bad Request</a></p>
<h4> Internal Server Error </h4>
<p> Click on the following link, the server will answer with a 500 error, check the status code <a href="/internal_error">Internal Error</a></p>
<h4> Resource not found </h4>
<p> Click on the following link or add to the end of the url something like /1030303 the server will answer with a 404 error, check the status code <a href="/not_foundd">Not found</a></p>
</div>
<div id="footer">
<p>Useful links for status codes <a href="httpstat.us">httpstat.us</a> and <a href="httpbing.org">httpbin.org</a></p>
</div>
</body>
</html>
]"
feature -- Generic Message
message_template: STRING="[
<!DOCTYPE html>
<html>
<head>
<title>$title</title>
</head>
<body>
<div id="header">
<p id="name">Use a tool to see the request and header details, for example (Developers tools in Chrome or Firebugs in Firefox)</p>
</div>
<div class="left"></div>
<div class="right">
<h4>This page is an example of $status</h4>
<div id="footer">
<p><a href="/">Back Home</a></p>
</div>
</body>
</html>
]"
end
```
<a name="example_2"/>
### Example Generic Search Engine
The following example shows a basic EWF service that builds a generic front end for the most used search engines. This example shows how
redirection works, and we will use a tools to play with the API to show differents responses.
```eiffel
note
description : "Basic Service that build a generic front end for the most used search engines."
date : "$Date$"
revision : "$Revision$"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute the incomming request
local
l_message: STRING
do
-- To send a response we need to setup, the status code and
-- the response headers.
if req.is_get_request_method then
if req.path_info.same_string ("/") then
res.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", web_page.count.out]>>)
res.put_string (web_page)
else
send_resouce_not_found (req, res)
end
elseif req.is_post_request_method then
if req.path_info.same_string ("/search") then
if attached {WSF_STRING} req.form_parameter ("query") as l_query then
if attached {WSF_STRING} req.form_parameter ("engine") as l_engine then
if attached {STRING} map.at (l_engine.value) as l_engine_url then
l_engine_url.append (l_query.value)
send_redirect (req, res, l_engine_url)
else
send_bad_request (req, res, " <strong>search engine: " + l_engine.value + "</strong> not supported,<br> try with Google or Bing")
end
else
send_bad_request (req, res, " <strong>search engine</strong> not selected")
end
else
send_bad_request (req, res, " form_parameter <strong>query</strong> is not present")
end
else
send_resouce_not_found (req, res)
end
else
create l_message.make_from_string (message_template)
l_message.replace_substring_all ("$title", "Method Not Allowed")
l_message.replace_substring_all ("$status", "Method Not Allowed 405")
-- Method not allowed
res.put_header ({HTTP_STATUS_CODE}.method_not_allowed, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>)
res.put_string (l_message)
end
end
feature -- Engine Map
map : STRING_TABLE[STRING]
do
create Result.make (2)
Result.put ("http://www.google.com/search?q=", "Google")
Result.put ("http://www.bing.com/search?q=", "Bing")
end
feature -- Redirect
send_redirect (req: WSF_REQUEST; res: WSF_RESPONSE; a_location: READABLE_STRING_32)
-- Redirect to `a_location'
local
h: HTTP_HEADER
do
create h.make
h.put_content_type_text_html
h.put_current_date
h.put_location (a_location)
res.set_status_code ({HTTP_STATUS_CODE}.see_other)
res.put_header_text (h.string)
end
feature -- Bad Request
send_bad_request (req: WSF_REQUEST; res: WSF_RESPONSE; description: STRING)
local
l_message: STRING
do
create l_message.make_from_string (message_template)
l_message.replace_substring_all ("$title", "Bad Request")
l_message.replace_substring_all ("$status", "Bad Request" + description)
res.put_header ({HTTP_STATUS_CODE}.bad_request, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>)
res.put_string (l_message)
end
feature -- Resource not found
send_resouce_not_found (req: WSF_REQUEST; res: WSF_RESPONSE)
local
l_message: STRING
do
create l_message.make_from_string (message_template)
l_message.replace_substring_all ("$title", "Resource not found")
l_message.replace_substring_all ("$status", "Resource" + req.request_uri + "not found 404")
res.put_header ({HTTP_STATUS_CODE}.not_found, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>)
res.put_string (l_message)
end
feature -- Home Page
web_page: STRING = "[
<!DOCTYPE html>
<html>
<head>
<title>Generic Search Engine</title>
</head>
<body>
<div class="right">
<h2>Generic Search Engine</h2>
<form method="POST" action="/search" target="_blank">
<fieldset>
Search: <input type="search" name="query" placeholder="EWF framework"><br>
<div>
<input type="radio" name="engine" value="Google" checked><img src="http://ebizmba.ebizmbainc.netdna-cdn.com/images/logos/google.gif" height="24" width="42">
</div>
<div>
<input type="radio" name="engine" value="Bing"><img src="http://ebizmba.ebizmbainc.netdna-cdn.com/images/logos/bing.gif" height="24" width="42">
</div><br>
</fieldset>
<input type="submit">
</form>
</div>
<div id="footer">
<p><a href="http://www.ebizmba.com/articles/search-engines">Top 15 Most Popular Search Engines | March 2015</a></p>
</div>
</body>
</html>
]"
feature -- Generic Message
message_template: STRING="[
<!DOCTYPE html>
<html>
<head>
<title>$title</title>
</head>
<body>
<div id="header">
<p id="name">Use a tool to see the request and header details, for example (Developers tools in Chrome or Firebugs in Firefox)</p>
</div>
<div class="left"></div>
<div class="right">
<h4>This page is an example of $status</h4>
<div id="footer">
<p><a href="/">Back Home</a></p>
</div>
</body>
</html>
]"
end
```
Using cURL to test the application
In the first call we use the ```res.redirect_now (l_engine_url)``` feature
```
#>curl -i -H -v -X POST -d "query=Eiffel&engine=Google" http://localhost:9090/search
HTTP/1.1 302 Found
Location: http://www.google.com/search?q=Eiffel
Content-Length: 0
Connection: close
```
Here we use our custom send_redirect feature call.
```
#>curl -i -H -v -X POST -d "query=Eiffel&engine=Google" http://localhost:9090/search
HTTP/1.1 303 See Other
Content-Type: text/html
Date: Fri, 06 Mar 2015 14:37:33 GMT
Location: http://www.google.com/search?q=Eiffel
Connection: close
```
#### Engine Ask Not supported
```
#>curl -i -H -v -X POST -d "query=Eiffel&engine=Ask" http://localhost:9090/search
HTTP/1.1 400 Bad Request
Content-Type: text/html
Content-Length: 503
Connection: close
<!DOCTYPE html>
<html>
<head>
<title>Bad Request</title>
</head>
<body>
<div id="header">
<p id="name">Use a tool to see the request and header details, for example (Developers tools in Chrome or Firebugs in Firefox)</p>
</div>
<div class="left"></div>
<div class="right">
<h4>This page is an example of Bad Request <strong>search engine: Ask</strong> not supported,<br> try with Google or Bing</h4>
<div id="footer">
<p><a href="/">Back Home</a></p>
</div>
</body>
</html>
```
#### Missing query form parameter
```
#>curl -i -H -v -X POST -d "engine=Google" http://localhost:9090/search
HTTP/1.1 400 Bad Request
Content-Type: text/html
Content-Length: 477
Connection: close
<!DOCTYPE html>
<html>
<head>
<title>Bad Request</title>
</head>
<body>
<div id="header">
<p id="name">Use a tool to see the request and header details, for example (Developers tools in Chrome or Firebugs in Firefox)</p>
</div>
<div class="left"></div>
<div class="right">
<h4>This page is an example of Bad Request form_parameter <strong>query</strong> is not present</h4>
<div id="footer">
<p><a href="/">Back Home</a></p>
</div>
</body>
</html>
```
#### Resource searchs not found
```
#>curl -i -H -v -X POST -d "query=Eiffel&engine=Google" http://localhost:9090/searchs
HTTP/1.1 404 Not Found
Content-Type: text/html
Content-Length: 449
Connection: close
<!DOCTYPE html>
<html>
<head>
<title>Resource not found</title>
</head>
<body>
<div id="header">
<p id="name">Use a tool to see the request and header details, for example (Developers tools in Chrome or Firebugs in Firefox)</p>
</div>
<div class="left"></div>
<div class="right">
<h4>This page is an example of Resource /searchs not found 404</h4>
<div id="footer">
<p><a href="/">Back Home</a></p>
</div>
</body>
</html>
```
<a name="header_fields"/>
## [Response Header Fields](https://httpwg.github.io/specs/rfc7231.html#response.header.fields)
The response header fields allow the server to pass additional information about the response beyond what is placed in the status-line. These header fields give information about the server, about further access to the target resource, or about related resources. We can specify cookies, page modification date (for caching), reload a page after a designated period of time, size of the document.
### How to set response headers.
HTTP allows multiple occurrences of the same header name, the features ```put_XYZ``` replace existing headers with the same name and
features ```add_XYZ``` add headers that can lead to duplicated entries.
```eiffel
add_header_line (h: READABLE_STRING_8)
-- Add header `h'
-- This can lead to duplicated header entries
require
header_not_committed: not header_committed
add_header_text (a_text: READABLE_STRING_8)
-- Add the multiline header `a_text'
-- Does not replace existing header with same name
-- This could leads to multiple header with the same name
require
header_not_committed: not header_committed
a_text_ends_with_single_crlf: a_text.count > 2 implies not a_text.substring (a_text.count - 2, a_text.count).same_string ("%R%N")
a_text_does_not_end_with_double_crlf: a_text.count > 4 implies not a_text.substring (a_text.count - 4, a_text.count).same_string ("%R%N%R%N")
ensure
status_set: status_is_set
message_writable: message_writable
put_header_line (h: READABLE_STRING_8)
-- Put header `h'
-- Replace any existing value
require
header_not_committed: not header_committed
put_header_text (a_text: READABLE_STRING_8)
-- Put the multiline header `a_text'
-- Overwite potential existing header
require
header_not_committed: not header_committed
a_text_ends_with_single_crlf: a_text.count > 2 implies not a_text.substring (a_text.count - 2, a_text.count).same_string ("%R%N")
a_text_does_not_end_with_double_crlf: a_text.count > 4 implies not a_text.substring (a_text.count - 4, a_text.count).same_string ("%R%N%R%N")
ensure
message_writable: message_writable
helpers
add_header (a_status_code: INTEGER_32; a_headers: detachable ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]])
-- Put headers with status `a_status', and headers from `a_headers'
require
a_status_code_valid: a_status_code > 0
status_not_committed: not status_committed
header_not_committed: not header_committed
ensure
status_code_set: status_code = a_status_code
status_set: status_is_set
message_writable: message_writable
add_header_lines (a_lines: ITERABLE [READABLE_STRING_8])
-- Add headers from `a_lines'
require
header_not_committed: not header_committed
put_header (a_status_code: INTEGER_32; a_headers: detachable ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]])
-- Put headers with status `a_status', and headers from `a_headers'
require
a_status_code_valid: a_status_code > 0
status_not_committed: not status_committed
header_not_committed: not header_committed
ensure
status_code_set: status_code = a_status_code
status_set: status_is_set
message_writable: message_writable
put_header_lines (a_lines: ITERABLE [READABLE_STRING_8])
-- Put headers from `a_lines'
require
header_not_committed: not header_committed
```
The other way to build headers is using the class [HTTP_HEADER](), that provide routines to build a header. It's recomended to
take a look at constants classes such as [HTTP_MIME_TYPES](),[HTTP_HEADER_NAMES](),[HTTP_STATUS_CODE](),[HTTP_REQUEST_METHODS](), or
[HTTP_CONSTANTS]() which groups them for convenience.
```eiffel
custom_answer (req: WSF_REQUEST; res: WSF_RESPONSE; output: STRING)
local
h: HTTP_HEADER
l_msg: STRING
do
create h.make
create l_msg.make_from_string (output)
h.put_content_type_text_html
h.put_content_length (l_msg.count)
h.put_current_date
res.set_status_code ({HTTP_STATUS_CODE}.bad_gateway)
res.put_header_text (h.string)
res.put_string (l_msg)
end
```
The class [HTTP_HEADER]() also supplies a number of convenience routines for specifying common headers, in fact the features are inherited from the class [HTTP_HEADER_MODIFIER].
```eiffel
deferred class interface
HTTP_HEADER_MODIFIER
feature -- Access
date_to_rfc1123_http_date_format (dt: DATE_TIME): STRING_8
-- String representation of `dt' using the RFC 1123
item alias "[]" (a_header_name: READABLE_STRING_8): detachable READABLE_STRING_8 assign force
-- First header item found for `a_name' if any
feature -- Status report
has (a_name: READABLE_STRING_8): BOOLEAN
-- Has header item for `n'?
-- Was declared in HTTP_HEADER_MODIFIER as synonym of has_header_named.
has_content_length: BOOLEAN
-- Has header "Content-Length"
has_content_type: BOOLEAN
-- Has header "Content-Type"
has_header_named (a_name: READABLE_STRING_8): BOOLEAN
-- Has header item for `n'?
-- Was declared in HTTP_HEADER_MODIFIER as synonym of has.
has_transfer_encoding_chunked: BOOLEAN
-- Has "Transfer-Encoding: chunked" header
feature -- Access: deferred
new_cursor: INDEXABLE_ITERATION_CURSOR [READABLE_STRING_8]
-- Fresh cursor associated with current structure.
feature -- Authorization
put_authorization (a_authorization: READABLE_STRING_8)
-- Put `a_authorization' with "Authorization" header
-- The Authorization header is constructed as follows:
-- 1. Username and password are combined into a string "username:password".
-- 2. The resulting string literal is then encoded using Base64.
-- 3. The authorization method and a space, i.e. "Basic " is then put before the encoded string.
-- ex: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
feature -- Content related header
add_content_type (a_content_type: READABLE_STRING_8)
-- same as put_content_type, but allow multiple definition of "Content-Type"
add_content_type_with_charset (a_content_type: READABLE_STRING_8; a_charset: READABLE_STRING_8)
-- Same as put_content_type_with_charset, but allow multiple definition of "Content-Type".
add_content_type_with_name (a_content_type: READABLE_STRING_8; a_name: READABLE_STRING_8)
-- same as put_content_type_with_name, but allow multiple definition of "Content-Type"
add_content_type_with_parameters (a_content_type: READABLE_STRING_8; a_params: detachable ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]])
-- Add header line "Content-Type:" + type `a_content_type' and extra paramaters `a_params'.
put_content_disposition (a_type: READABLE_STRING_8; a_params: detachable READABLE_STRING_8)
-- Put "Content-Disposition" header
put_content_encoding (a_encoding: READABLE_STRING_8)
-- Put "Content-Encoding" header of value `a_encoding'.
put_content_language (a_lang: READABLE_STRING_8)
-- Put "Content-Language" header of value `a_lang'.
put_content_length (a_length: INTEGER_32)
-- Put "Content-Length:" + length `a_length'.
put_content_transfer_encoding (a_mechanism: READABLE_STRING_8)
-- Put "Content-Transfer-Encoding" header with `a_mechanism'
put_content_type (a_content_type: READABLE_STRING_8)
-- Put header line "Content-Type:" + type `a_content_type'
put_content_type_with_charset (a_content_type: READABLE_STRING_8; a_charset: READABLE_STRING_8)
-- Put content type `a_content_type' with `a_charset' as "charset" parameter.
put_content_type_with_name (a_content_type: READABLE_STRING_8; a_name: READABLE_STRING_8)
-- Put content type `a_content_type' with `a_name' as "name" parameter.
put_content_type_with_parameters (a_content_type: READABLE_STRING_8; a_params: detachable ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]])
-- Put header line "Content-Type:" + type `a_content_type' and extra paramaters `a_params'
put_transfer_encoding (a_encoding: READABLE_STRING_8)
-- Put "Transfer-Encoding" header with `a_encoding' value.
put_transfer_encoding_binary
-- Put "Transfer-Encoding: binary" header
put_transfer_encoding_chunked
-- Put "Transfer-Encoding: chunked" header
feature -- Content-type helpers
put_content_type_application_javascript
put_content_type_application_json
put_content_type_application_pdf
put_content_type_application_x_www_form_encoded
put_content_type_application_zip
put_content_type_image_gif
put_content_type_image_jpg
put_content_type_image_png
put_content_type_image_svg_xml
put_content_type_message_http
put_content_type_multipart_alternative
put_content_type_multipart_encrypted
put_content_type_multipart_form_data
put_content_type_multipart_mixed
put_content_type_multipart_related
put_content_type_multipart_signed
put_content_type_text_css
put_content_type_text_csv
put_content_type_text_html
put_content_type_text_javascript
put_content_type_text_json
put_content_type_text_plain
put_content_type_text_xml
put_content_type_utf_8_text_plain
feature -- Cookie
put_cookie (key, value: READABLE_STRING_8; expiration, path, domain: detachable READABLE_STRING_8; secure, http_only: BOOLEAN)
-- Set a cookie on the client's machine
-- with key 'key' and value 'value'.
-- Note: you should avoid using "localhost" as `domain' for local cookies
-- since they are not always handled by browser (for instance Chrome)
require
make_sense: (key /= Void and value /= Void) and then (not key.is_empty and not value.is_empty)
domain_without_port_info: domain /= Void implies domain.index_of (':', 1) = 0
put_cookie_with_expiration_date (key, value: READABLE_STRING_8; expiration: DATE_TIME; path, domain: detachable READABLE_STRING_8; secure, http_only: BOOLEAN)
-- Set a cookie on the client's machine
-- with key 'key' and value 'value'.
require
make_sense: (key /= Void and value /= Void) and then (not key.is_empty and not value.is_empty)
feature -- Cross-Origin Resource Sharing
put_access_control_allow_all_origin
-- Put "Access-Control-Allow-Origin: *" header.
put_access_control_allow_credentials (b: BOOLEAN)
-- Indicates whether or not the response to the request can be exposed when the credentials flag is true.
-- When used as part of a response to a preflight request, this indicates whether or not the actual request can be made using credentials.
-- Note that simple GET requests are not preflighted, and so if a request is made for a resource with credentials,
-- if this header is not returned with the resource, the response is ignored by the browser and not returned to web content.
-- ex: Access-Control-Allow-Credentials: true | false
put_access_control_allow_headers (a_headers: READABLE_STRING_8)
-- Put "Access-Control-Allow-Headers" header. with value `a_headers'
-- Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request.
-- ex: Access-Control-Allow-Headers: <field-name>[, <field-name>]*
put_access_control_allow_iterable_headers (a_fields: ITERABLE [READABLE_STRING_8])
-- Put "Access-Control-Allow-Headers" header. with value `a_headers'
-- Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request.
-- ex: Access-Control-Allow-Headers: <field-name>[, <field-name>]*
put_access_control_allow_methods (a_methods: ITERABLE [READABLE_STRING_8])
-- If `a_methods' is not empty, put `Access-Control-Allow-Methods' header with list `a_methods' of methods
-- `a_methods' specifies the method or methods allowed when accessing the resource.
-- This is used in response to a preflight request.
-- ex: Access-Control-Allow-Methods: <method>[, <method>]*
put_access_control_allow_origin (a_origin: READABLE_STRING_8)
-- Put "Access-Control-Allow-Origin: " + `a_origin' header.
-- `a_origin' specifies a URI that may access the resource
feature -- Date
put_current_date
-- Put current date time with "Date" header
put_date (a_date: READABLE_STRING_8)
-- Put "Date: " header
put_last_modified (a_utc_date: DATE_TIME)
-- Put UTC date time `dt' with "Last-Modified" header
put_utc_date (a_utc_date: DATE_TIME)
-- Put UTC date time `a_utc_date' with "Date" header
-- using RFC1123 date formating.
feature -- Header change: deferred
add_header (h: READABLE_STRING_8)
-- Add header `h'
-- if it already exists, there will be multiple header with same name
-- which can also be valid
require
h_not_empty: h /= Void and then not h.is_empty
put_header (h: READABLE_STRING_8)
-- Add header `h' or replace existing header of same header name
require
h_not_empty: h /= Void and then not h.is_empty
feature -- Header change: general
add_header_key_value (a_header_name, a_value: READABLE_STRING_8)
-- Add header `a_header_name:a_value'.
-- If it already exists, there will be multiple header with same name
-- which can also be valid
ensure
added: has_header_named (a_header_name)
force (a_value: detachable READABLE_STRING_8; a_header_name: READABLE_STRING_8)
-- Put header `a_header_name:a_value' or replace existing header of name `a_header_name'.
put_header_key_value (a_header_name, a_value: READABLE_STRING_8)
-- Add header `a_header_name:a_value', or replace existing header of same header name/key
ensure
added: has_header_named (a_header_name)
put_header_key_values (a_header_name: READABLE_STRING_8; a_values: ITERABLE [READABLE_STRING_8]; a_separator: detachable READABLE_STRING_8)
-- Add header `a_header_name: a_values', or replace existing header of same header values/key.
-- Use Comma_space as default separator if `a_separator' is Void or empty.
ensure
added: has_header_named (a_header_name)
feature -- Method related
put_allow (a_methods: ITERABLE [READABLE_STRING_8])
-- If `a_methods' is not empty, put `Allow' header with list `a_methods' of methods
feature -- Others
put_cache_control (a_cache_control: READABLE_STRING_8)
-- Put "Cache-Control" header with value `a_cache_control'
put_expires (a_seconds: INTEGER_32)
-- Put "Expires" header to `a_seconds' seconds
put_expires_date (a_utc_date: DATE_TIME)
-- Put "Expires" header with UTC date time value
-- formatted following RFC1123 specification.
put_expires_string (a_expires: STRING_8)
-- Put "Expires" header with `a_expires' string value
put_pragma (a_pragma: READABLE_STRING_8)
-- Put "Pragma" header with value `a_pragma'
put_pragma_no_cache
-- Put "Pragma" header with "no-cache" a_pragma
feature -- Redirection
put_location (a_uri: READABLE_STRING_8)
-- Tell the client the new location `a_uri'
-- using "Location" header.
require
a_uri_valid: not a_uri.is_empty
put_refresh (a_uri: READABLE_STRING_8; a_timeout_in_seconds: INTEGER_32)
-- Tell the client to refresh page with `a_uri' after `a_timeout_in_seconds' in seconds
-- using "Refresh" header.
require
a_uri_valid: not a_uri.is_empty
end -- class HTTP_HEADER_MODIFIER
```
## HTTP 1.1 Response Headers
There are four categories for response header fields:
- [Control Data](https://httpwg.github.io/specs/rfc7231.html#response.control.data) : Supply control data that supplements the status code, directs caching, or instructs the client where to go next.
- Age,Cache-Control,Expires,Date,Location,Retry-After,Vary,Warning.
- [Validator](https://httpwg.github.io/specs/rfc7231.html#response.validator): Validator header fields convey metadata about the selected representation. In responses to safe requests, validator fields describe the selected representation chosen by the origin server while handling the response.
- [Authentication Challenges](https://httpwg.github.io/specs/rfc7231.html#response.auth): Indicate what mechanisms are available for the client to provide authentication credentials in future requests.
- [Response Context](https://httpwg.github.io/specs/rfc7231.html#response.context): Provide more information about the target resource for potential use in later requests.
Nav: [Workbook](../workbook.md) | [Handling Requests: Header Fields](/doc/workbook/handling_request/headers.md) | [Handling Cookies](/doc/workbook/handling_cookies/handling_cookies.md)

View File

@@ -0,0 +1,24 @@
note
description: "Basic Service launcher"
class
APPLICATION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
set_service_option ("port", 9090)
end
end

View File

@@ -0,0 +1,57 @@
note
description : "Basic Service that build a generic front end for the most used search engines."
date : "$Date$"
revision : "$Revision$"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute
-- Execute the incomming request
local
l_message: STRING
do
-- (1) To send a response we need to setup, the status code and the response headers.
-- response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", web_page.count.out]>>)
-- response.put_string (web_page)
-- (2) Using put_header_line
-- response.set_status_code ({HTTP_STATUS_CODE}.ok)
-- response.put_header_line ("Content-Type:text/html")
response.put_header_line ("Content-Length:"+ web_page.count.out)
response.put_header_line ("Content-Type:text/plain")
response.put_string (web_page)
end
feature -- Home Page
web_page: STRING = "[
<!DOCTYPE html>
<html>
<head>
<title>EWF Headers Responses</title>
</head>
<body>
<div class="right">
<h2>Example Header Response</h2>
<p>Response headers</p>
</div>
<div id="footer">
<p>EWF Response Header</p>
</div>
</body>
</html>
]"
end

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="headers" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486" library_target="headers">
<target name="common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
</target>
<target name="headers_nino" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_nino" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\nino-safe.ecf"/>
<cluster name="headers" location=".\" recursive="true"/>
</target>
<target name="headers_cgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\cgi-safe.ecf"/>
<cluster name="headers" location=".\" recursive="true"/>
</target>
<target name="headers_libfcgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\libfcgi-safe.ecf"/>
<cluster name="headers" location=".\" recursive="true"/>
</target>
<target name="headers" extends="headers_nino">
</target>
</system>

View File

@@ -0,0 +1,24 @@
note
description: "Basic Service launcher"
class
APPLICATION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
set_service_option ("port", 9090)
end
end

View File

@@ -0,0 +1,172 @@
note
description : "Basic Service that build a generic front end for the most used search engines."
date : "$Date$"
revision : "$Revision$"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute
-- Execute the incomming request
local
l_message: STRING
do
-- To send a response we need to setup, the status code and
-- the response headers.
if request.is_get_request_method then
if request.path_info.same_string ("/") then
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", web_page.count.out]>>)
response.put_string (web_page)
else
send_resouce_not_found (request, response)
end
elseif request.is_post_request_method then
if request.path_info.same_string ("/search") then
if attached {WSF_STRING} request.form_parameter ("query") as l_query then
if attached {WSF_STRING} request.form_parameter ("engine") as l_engine then
if attached {STRING} map.at (l_engine.value) as l_engine_url then
l_engine_url.append (l_query.value)
send_redirect (request, response, l_engine_url)
-- response.redirect_now (l_engine_url)
else
send_bad_request (request, response, " <strong>search engine: " + l_engine.value + "</strong> not supported,<br> try with Google or Bing")
end
else
send_bad_request (request, response, " <strong>search engine</strong> not selected")
end
else
send_bad_request (request, response, " form_parameter <strong>query</strong> is not present")
end
else
send_resouce_not_found (request, response)
end
else
create l_message.make_from_string (message_template)
l_message.replace_substring_all ("$title", "Method Not Allowed")
l_message.replace_substring_all ("$status", "Method Not Allowed 405")
-- Method not allowed
response.put_header ({HTTP_STATUS_CODE}.method_not_allowed, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>)
response.put_string (l_message)
end
end
feature -- Engine Map
map : STRING_TABLE[STRING]
do
create Result.make (2)
Result.put ("http://www.google.com/search?q=", "Google")
Result.put ("http://www.bing.com/search?q=", "Bing")
end
feature -- Redirect
send_redirect (req: WSF_REQUEST; res: WSF_RESPONSE; a_location: READABLE_STRING_32)
-- Redirect to `a_location'
local
h: HTTP_HEADER
do
create h.make
h.put_content_type_text_html
h.put_current_date
h.put_location (a_location)
res.set_status_code ({HTTP_STATUS_CODE}.see_other)
res.put_header_text (h.string)
end
feature -- Bad Request
send_bad_request (req: WSF_REQUEST; res: WSF_RESPONSE; description: STRING)
local
l_message: STRING
do
create l_message.make_from_string (message_template)
l_message.replace_substring_all ("$title", "Bad Request")
l_message.replace_substring_all ("$status", "Bad Request" + description)
res.put_header ({HTTP_STATUS_CODE}.bad_request, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>)
res.put_string (l_message)
end
feature -- Resource not found
send_resouce_not_found (req: WSF_REQUEST; res: WSF_RESPONSE)
local
l_message: STRING
do
create l_message.make_from_string (message_template)
l_message.replace_substring_all ("$title", "Resource not found")
l_message.replace_substring_all ("$status", "Resource " + req.request_uri + " not found 404")
res.put_header ({HTTP_STATUS_CODE}.not_found, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>)
res.put_string (l_message)
end
feature -- Home Page
web_page: STRING = "[
<!DOCTYPE html>
<html>
<head>
<title>Generic Search Engine</title>
</head>
<body>
<div class="right">
<h2>Generic Search Engine</h2>
<form method="POST" action="/search" target="_blank">
<fieldset>
Search: <input type="search" name="query" placeholder="EWF framework"><br>
<div>
<input type="radio" name="engine" value="Google" checked><img src="http://ebizmba.ebizmbainc.netdna-cdn.com/images/logos/google.gif" height="24" width="42">
</div>
<div>
<input type="radio" name="engine" value="Bing"><img src="http://ebizmba.ebizmbainc.netdna-cdn.com/images/logos/bing.gif" height="24" width="42">
</div><br>
</fieldset>
<input type="submit">
</form>
</div>
<div id="footer">
<p><a href="http://www.ebizmba.com/articles/search-engines">Top 15 Most Popular Search Engines | March 2015</a></p>
</div>
</body>
</html>
]"
feature -- Generic Message
message_template: STRING="[
<!DOCTYPE html>
<html>
<head>
<title>$title</title>
</head>
<body>
<div id="header">
<p id="name">Use a tool to see the request and header details, for example (Developers tools in Chrome or Firebugs in Firefox)</p>
</div>
<div class="left"></div>
<div class="right">
<h4>This page is an example of $status</h4>
<div id="footer">
<p><a href="/">Back Home</a></p>
</div>
</body>
</html>
]"
end

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="search" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486" library_target="search">
<target name="common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
</target>
<target name="search_nino" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_nino" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\nino-safe.ecf"/>
<cluster name="search" location=".\" recursive="true"/>
</target>
<target name="search_cgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\cgi-safe.ecf"/>
<cluster name="search" location=".\" recursive="true"/>
</target>
<target name="search_libfcgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\libfcgi-safe.ecf"/>
<cluster name="search" location=".\" recursive="true"/>
</target>
<target name="search" extends="search_nino">
</target>
</system>

View File

@@ -0,0 +1,24 @@
note
description: "Basic Service launcher"
class
APPLICATION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
set_service_option ("port", 9090)
end
end

View File

@@ -0,0 +1,138 @@
note
description : "Basic Service that a simple web page to show the most common status codes"
date : "$Date$"
revision : "$Revision$"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute
-- Execute the incomming request
local
l_message: STRING
do
-- To send a response we need to setup, the status code and
-- the response headers.
if request.is_get_request_method then
if request.path_info.same_string ("/") then
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", web_page.count.out]>>)
response.put_string (web_page)
elseif request.path_info.same_string ("/redirect") then
send_redirect (request, response, "https://httpwg.github.io/")
elseif request.path_info.same_string ("/bad_request") then
-- Here you can do some logic for example log, send emails to register the error, before to send the response.
create l_message.make_from_string (message_template)
l_message.replace_substring_all ("$title", "Bad Request")
l_message.replace_substring_all ("$status", "Bad Request 400")
response.put_header ({HTTP_STATUS_CODE}.bad_request, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>)
response.put_string (l_message)
elseif request.path_info.same_string ("/internal_error") then
-- Here you can do some logic for example log, send emails to register the error, before to send the response.
create l_message.make_from_string (message_template)
l_message.replace_substring_all ("$title", "Internal Server Error")
l_message.replace_substring_all ("$status", "Internal Server Error 500")
response.put_header ({HTTP_STATUS_CODE}.internal_server_error, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>)
response.put_string (l_message)
else
create l_message.make_from_string (message_template)
l_message.replace_substring_all ("$title", "Resource not found")
l_message.replace_substring_all ("$status", "Resource not found 400")
response.put_header ({HTTP_STATUS_CODE}.not_found, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>)
response.put_string (l_message)
end
else
create l_message.make_from_string (message_template)
l_message.replace_substring_all ("$title", "Method Not Allowed")
l_message.replace_substring_all ("$status", "Method Not Allowed 405")
-- Method not allowed
response.put_header ({HTTP_STATUS_CODE}.method_not_allowed, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>)
response.put_string (l_message)
end
end
feature -- Home Page
send_redirect (req: WSF_REQUEST; res: WSF_RESPONSE; a_location: READABLE_STRING_32)
-- Redirect to `a_location'
local
h: HTTP_HEADER
do
create h.make
h.put_content_type_text_html
h.put_current_date
h.put_location (a_location)
res.set_status_code ({HTTP_STATUS_CODE}.see_other)
res.put_header_text (h.string)
end
web_page: STRING = "[
<!DOCTYPE html>
<html>
<head>
<title>Example showing common status codes</title>
</head>
<body>
<div id="header">
<p id="name">Use a tool to see the request and header details, for example (Developers tools in Chrome or Firebugs in Firefox)</p>
</div>
<div class="left"></div>
<div class="right">
<h4>This page is an example of Status Code 200</h4>
<h4> Redirect Example </h4>
<p> Click on the following link will redirect you to the HTTP Specifcation, we can do the redirect from the HTML directly but
here we want to show you an exmaple, where you can do something before to send a redirect <a href="/redirect">Redirect</a></p>
<h4> Bad Request </h4>
<p> Click on the following link, the server will answer with a 400 error, check the status code <a href="/bad_request">Bad Request</a></p>
<h4> Internal Server Error </h4>
<p> Click on the following link, the server will answer with a 500 error, check the status code <a href="/internal_error">Internal Error</a></p>
<h4> Resource not found </h4>
<p> Click on the following link or add to the end of the url something like /1030303 the server will answer with a 404 error, check the status code <a href="/not_foundd">Not found</a></p>
</div>
<div id="footer">
<p>Useful links for status codes <a href="httpstat.us">httpstat.us</a> and <a href="httpbing.org">httpbin.org</a></p>
</div>
</body>
</html>
]"
feature -- Generic Message
message_template: STRING="[
<!DOCTYPE html>
<html>
<head>
<title>$title</title>
</head>
<body>
<div id="header">
<p id="name">Use a tool to see the request and header details, for example (Developers tools in Chrome or Firebugs in Firefox)</p>
</div>
<div class="left"></div>
<div class="right">
<h4>This page is an example of $status</h4>
<div id="footer">
<p><a href="/">Back Home</a></p>
</div>
</body>
</html>
]"
end

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="status" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486" library_target="status">l
<target name="common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
</target>
<target name="status_nino" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_nino" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\nino-safe.ecf"/>
<cluster name="status" location=".\" recursive="true"/>
</target>
<target name="status_cgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\cgi-safe.ecf"/>
<cluster name="status" location=".\" recursive="true"/>
</target>
<target name="status_libfcgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\libfcgi-safe.ecf"/>
<cluster name="status" location=".\" recursive="true"/>
</target>
<target name="status" extends="status_nino">
</target>
</system>

View File

@@ -0,0 +1,24 @@
note
description: "Basic Service launcher"
class
APPLICATION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
set_service_option ("port", 9090)
end
end

View File

@@ -0,0 +1,141 @@
note
description : "Basic Service that build a generic front to demonstrate the use of Cookies"
date : "$Date$"
revision : "$Revision$"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute
-- Execute the incomming request
local
l_message: STRING
l_header: HTTP_HEADER
l_time: HTTP_DATE
l_cookies: STRING
l_answer: STRING
do
-- all the cookies
create l_cookies.make_empty
across request.cookies as ic loop
l_cookies.append (ic.item.name)
l_cookies.append("<br>")
end
if request.path_info.same_string ("/") then
create l_header.make
create l_answer.make_from_string (web_page)
if request.cookie ("_EWF_Cookie") = Void then
-- First access the the home page, find a cookie with specific name `_EWF_Cookie'
l_answer.replace_substring_all ("$header_title", "Hey, thanks for access our cool site, this is your first acess")
l_answer.replace_substring_all ("$cookies", l_cookies)
create l_time.make_now_utc
l_time.date_time.day_add (40)
l_header.put_cookie_with_expiration_date ("_EWF_Cookie", "EXAMPLE",l_time.date_time, "", Void, False, True)
else
-- No a new access
l_answer.replace_substring_all ("$header_title", "Welcome back, please check all the new things we have!!!")
l_answer.replace_substring_all ("$cookies", l_cookies)
end
l_header.put_content_type_text_html
l_header.put_content_length (l_answer.count)
response.put_header_text (l_header.string)
response.put_string (l_answer)
elseif request.path_info.same_string ("/visitors") then
create l_header.make
create l_answer.make_from_string (visit_page)
if request.cookie ("_visits") = Void then
-- First access the the visit page, find a cookie with specific name `_visits'
l_answer.replace_substring_all ("$visit", "1")
l_answer.replace_substring_all ("$cookies", l_cookies)
create l_time.make_now_utc
l_time.date_time.day_add (40)
l_header.put_cookie_with_expiration_date ("_visits", "1",l_time.date_time, "/visitors", Void, False, True)
else
if attached {WSF_STRING} request.cookie ("_visits") as l_visit then
create l_time.make_now_utc
l_time.date_time.day_add (40)
l_answer.replace_substring_all ("$visit", (l_visit.value.to_integer + 1).out )
l_answer.replace_substring_all ("$cookies", l_cookies)
l_header.put_cookie_with_expiration_date ("_visits", (l_visit.value.to_integer + 1).out,l_time.date_time, "/visitors", Void, False, True)
end
end
create l_time.make_now_utc
l_time.date_time.second_add (120)
l_header.put_content_type_text_html
-- This cookie expires in 120 seconds, its valid for 120 seconds
l_header.put_cookie_with_expiration_date ("_Framework", "EWF",l_time.date_time, "/", Void, False, True)
-- This is a session cookie, valid only to the current browsing session.
l_header.put_cookie ("Session", "Cookie",Void, "/", Void, False, True)
l_header.put_content_length (l_answer.count)
response.add_header_text (l_header.string)
response.put_string (l_answer)
end
end
feature -- Home Page
web_page: STRING = "[
<!DOCTYPE html>
<html>
<head>
<title>EWF Handling Cookies</title>
</head>
<body>
<div class="right">
<h2>$header_title</h2>
</div>
<div class="right">
<a href="/visitors">Visitors</a>
</div>
<div>
<h3>Cookies for the home page</h3>
$cookies
</div>
</body>
</html>
]"
visit_page: STRING = "[
<!DOCTYPE html>
<html>
<head>
<title>EWF Handling Visit Page</title>
</head>
<body>
<div class="right">
<h2>The number of visits is $visit</h2>
</div>
<div>
<h3>Cookies for the Visit page</h3>
$cookies
</div>
</br>
<div>
Back to <a href="/"> Home </a>
</div>
<div id="footer">
<p>EWF Example Cookies</p>
</div>
</body>
</html>
]"
end

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-13-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-13-0 http://www.eiffel.com/developers/xml/configuration-1-13-0.xsd" name="example" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486" library_target="example">
<target name="common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" full_class_checking="false" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
</target>
<target name="example_nino" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_nino" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\nino-safe.ecf"/>
<cluster name="example" location=".\" recursive="true"/>
</target>
<target name="example_cgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\cgi-safe.ecf"/>
<cluster name="example" location=".\" recursive="true"/>
</target>
<target name="example_libfcgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\libfcgi-safe.ecf"/>
<cluster name="example" location=".\" recursive="true"/>
</target>
<target name="example" extends="example_nino">
</target>
</system>

View File

@@ -0,0 +1,288 @@
Nav: [Workbook](../workbook.md) | [Generating Responses](/doc/workbook/generating_response/generating_response.md)
# Handling Cookies
- [Cookie](#cookie)
- [Cookie Porperties](#properties)
- [Write and Read Cookies](#set_get)
- [How to set a cookie](#set_cookie)
- [How to read a cookie](#read_cookie)
- [Examples](#examples)
<a name="cookie"/>
## [Cookie](http://httpwg.github.io/specs/rfc6265.html)
A cookie is a piece of data that can be stored in a browser's cache. If you visit a web site and then revisit it, the cookie data can be used to identify you as a return visitor. Cookies enable state information, such as an online shopping cart, to be remembered. A cookie can be short term, holding data for a single web session, that is, until you close the browser, or a cookie can be longer term, holding data for a week or a year.
Cookies are used a lot in web client-server communication.
- HTTP State Management With Cookies
- Personalized response to the client based on their preference, for example we can set background color as cookie in client browser and then use it to customize response background color, image etc.
Server send cookies to the client
>Set-Cookie: _Framework=EWF; Path=/; Expires=Tue, 10 Mar 2015 13:28:10 GMT; HttpOnly%R
Client send cookies to server
>Cookie: _Framework=EWF
<a name="properties"/>
### Cookie properties
- Comment: describe the purpose of the cookie. Note that server doesnt receive this information when client sends cookie in request header.
- Domain: domain name for the cookie.
- Expiration/MaxAge: Expiration time of the cookie, we could also set it in seconds. (At the moment Max-Age attribute is not supported)
- Name: name of the cookie.
- Path: path on the server to which the browser returns this cookie. Path instruct the browser to send cookie to a particular resource.
- Secure: True, if the browser is sending cookies only over a secure protocol, False in other case.
- Value: Value of th cookie as string.
- HttpOnly: Checks whether this Cookie has been marked as HttpOnly.
- Version:
<a name="set_get"/>
## Write and Read Cookies.
To send a cookie to the client we should use the [HTTP_HEADER] class, and call ```h.put_cookie``` feature or
```h.put_cookie_with_expiration_date``` feature, see [How to set Cookies]() to learn the details, and the set it to response object [WSF_RESPONSE] as we saw previously.
We will show an example.
To Read incomming cookies we can read all the cookies with
```
cookies: ITERABLE [WSF_VALUE]
-- All cookies.
```
which return an interable of WSF_VALUE objects corresponding to the cookies the browser has associated with the web site.
We can also check if a particular cookie by name using
```
WSF_REQUEST.cookie (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE
-- Field for name `a_name'.
```
feature.
<a name="set_cookie"/>
### How to set Cookies
Here we have the feature definitions to set cookies
```eiffel
deferred class interface
HTTP_HEADER_MODIFIER
feature -- Cookie
put_cookie (key, value: READABLE_STRING_8; expiration, path, domain: detachable READABLE_STRING_8; secure, http_only: BOOLEAN)
-- Set a cookie on the client's machine
-- with key 'key' and value 'value'.
-- Note: you should avoid using "localhost" as `domain' for local cookies
-- since they are not always handled by browser (for instance Chrome)
require
make_sense: (key /= Void and value /= Void) and then (not key.is_empty and not value.is_empty)
domain_without_port_info: domain /= Void implies domain.index_of (':', 1) = 0
put_cookie_with_expiration_date (key, value: READABLE_STRING_8; expiration: DATE_TIME; path, domain: detachable READABLE_STRING_8; secure, http_only: BOOLEAN)
-- Set a cookie on the client's machine
-- with key 'key' and value 'value'.
require
make_sense: (key /= Void and value /= Void) and then (not key.is_empty and not value.is_empty)
```
Example of use:
```eiffel
response_with_cookies (res: WSF_RESPONSE)
local
l_message: STRING
l_header: HTTP_HEADER
l_time: HTTP_DATE
do
create l_header.make
create l_time.make_now_utc
l_time.date_time.day_add (40)
l_header.put_content_type_text_html
l_header.put_cookie_with_expiration_date ("EWFCookie", "EXAMPLE",l_time.date_time, "/", Void, False, True)
res.put_header_text (l_header.string)
res.put_string (web_page)
end
```
<a name="read_cookie"/>
### How to read Cookies
Reading a particular cookie
```eiffel
if req.cookie ("EWFCookie") = Void then
do_something
end
````
Reading all the cookies
```Eiffel
across req.cookies as ic loop
print (ic.item.name)
end
```
<a name="examples"/>
### Example
The following EWF service shows a basic use of cookies.
1. It display a message to first-time visitors.
2. Display a welcome back message if a visitor return.
3. A visitor page, counting the number of visits to the page (track user access counts).
4. A cookie with an expiration of 120 seconds.
5. A cookie with an session level, valid in browser session.
```eiffel
note
description : "Basic Service that build a generic front to demonstrate the use of Cookies"
date : "$Date$"
revision : "$Revision$"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute the incomming request
local
l_message: STRING
l_header: HTTP_HEADER
l_time: HTTP_DATE
l_cookies: STRING
l_answer: STRING
do
-- all the cookies
create l_cookies.make_empty
across req.cookies as ic loop
l_cookies.append (ic.item.name)
l_cookies.append("<br>")
end
if req.path_info.same_string ("/") then
create l_header.make
create l_answer.make_from_string (web_page)
if req.cookie ("_EWF_Cookie") = Void then
-- First access the the home page, find a cookie with specific name `_EWF_Cookie'
l_answer.replace_substring_all ("$header_title", "Hey, thanks for access our cool site, this is your first acess")
l_answer.replace_substring_all ("$cookies", l_cookies)
create l_time.make_now_utc
l_time.date_time.day_add (40)
l_header.put_cookie_with_expiration_date ("_EWF_Cookie", "EXAMPLE",l_time.date_time, "", Void, False, True)
else
-- No a new access
l_answer.replace_substring_all ("$header_title", "Welcome back, please check all the new things we have!!!")
l_answer.replace_substring_all ("$cookies", l_cookies)
end
l_header.put_content_type_text_html
l_header.put_content_length (l_answer.count)
res.put_header_text (l_header.string)
res.put_string (l_answer)
elseif req.path_info.same_string ("/visitors") then
create l_header.make
create l_answer.make_from_string (visit_page)
if req.cookie ("_visits") = Void then
-- First access the the visit page, find a cookie with specific name `_visits'
l_answer.replace_substring_all ("$visit", "1")
l_answer.replace_substring_all ("$cookies", l_cookies)
create l_time.make_now_utc
l_time.date_time.day_add (40)
l_header.put_cookie_with_expiration_date ("_visits", "1",l_time.date_time, "/visitors", Void, False, True)
else
if attached {WSF_STRING} req.cookie ("_visits") as l_visit then
create l_time.make_now_utc
l_time.date_time.day_add (40)
l_answer.replace_substring_all ("$visit", (l_visit.value.to_integer + 1).out )
l_answer.replace_substring_all ("$cookies", l_cookies)
l_header.put_cookie_with_expiration_date ("_visits", (l_visit.value.to_integer + 1).out,l_time.date_time, "/visitors", Void, False, True)
end
end
create l_time.make_now_utc
l_time.date_time.second_add (120)
l_header.put_content_type_text_html
-- This cookie expires in 120 seconds, its valid for 120 seconds
l_header.put_cookie_with_expiration_date ("_Framework", "EWF",l_time.date_time, "/", Void, False, True)
-- This is a session cookie, valid only to the current browsing session.
l_header.put_cookie ("Session", "Cookie",Void, "/", Void, False, True)
l_header.put_content_length (l_answer.count)
res.add_header_text (l_header.string)
res.put_string (l_answer)
end
end
feature -- Home Page
web_page: STRING = "[
<!DOCTYPE html>
<html>
<head>
<title>EWF Handling Cookies</title>
</head>
<body>
<div class="right">
<h2>$header_title</h2>
</div>
<div class="right">
<a href="/visitors">Visitors</a>
</div>
<div>
<h3>Cookies for the home page</h3>
$cookies
</div>
</body>
</html>
]"
visit_page: STRING = "[
<!DOCTYPE html>
<html>
<head>
<title>EWF Handling Visit Page</title>
</head>
<body>
<div class="right">
<h2>The number of visits is $visit</h2>
</div>
<div>
<h3>Cookies for the Visit page</h3>
$cookies
</div>
</br>
<div>
Back to <a href="/"> Home </a>
</div>
<div id="footer">
<p>EWF Example Cookies</p>
</div>
</body>
</html>
]"
end
```
Nav: [Workbook](../workbook.md) | [Generating Responses](/doc/workbook/generating_response/generating_response.md)

View File

@@ -0,0 +1,307 @@
Nav: [Workbook](../workbook.md) | [Basic Concepts] (/doc/workbook/basics/basics.md) | [Handling Requests: Header Fields](/doc/workbook/handling_request/headers.md)
#Handling Requests: Form/Query Data
##### Table of Contents
- [Reading Form Data](#read)
- [Query Parameters](#query)
- [Form Parameters](#form)
- [Uniform Read](#uniform)
- [Reading Parameters and Values](#reading_pv)
- [How to read all parameters names](#all_names)
- [How to read single values](#single_values)
- [How to read multiple values](#multiple_values)
- [How to read table values](#table_values)
- [Reading raw data](#raw_data)
- [Upload Files](#upload)
- [Examples](#examples)
An HTML Form can handle GET and POST requests.
When we use a form with method GET, the data is attached at the end of the url for example:
>http://wwww.example.com?key1=value1&...keyn=valuen
If we use the method POST, the data is sent to the server in a different line.
Extracting form data from the server side is one of the most tedious parts. If you do it by hand, you will need
to parse the input, you'll have to URL-decode the value.
Here we will show you how to read input submitted by a user using a Form (GET and POST).
* How to handle missing values:
* client side validattion, server side validations, set default if it's a valid option.
* How to populate Eiffel objects from the request data.
<a name="read"/>
## Reading Form Data
EWF [WSF_REQUEST]() class, provides features to handling this form parsing automatically.
<a name="query"/>
### Query Parameters
WSF_REQUEST.query_parameters: ITERABLE [WSF_VALUE]
-- All query parameters
WSF_REQUEST.query_parameter (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE
-- Query parameter for name `a_name'.
<a name="form"/>
### Form Parameters
WSF_REQUEST.form_parameters: ITERABLE [WSF_VALUE]
-- All form parameters sent by a POST
WSF_REQUEST.form_parameter (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE
-- Field for name `a_name'.
The values supplied to form_parameter and query_parameter are case sensitive.
<a name="uniform"/>
### Read Data
The previous features, let you read the data one way for GET request and a different way for POST request. WSF_REQUEST provide a feature to read all the data in a uniform way.
WSF_REQUEST.item (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE
-- Variable named `a_name' from any of the variables container
-- and following a specific order: form_, query_ and path_ parameters
So, you use **WSF_REQUEST.item** feature exactly the same way for GET and POST request.
>Note: if a query parameter has the same name as a form paramenter req.item will retrieve the form paramenter. Remember the precedence: form > query > path
<a name="reading_pv">
## Reading Parameters and Values
Suppose we have the following HTML5 form using Method POST. This HTML5 form has client side form validation using the new HTML5 attribute, you can do the same using Javascript. So in this case if the user does not fill the fields as expected the form will not be submitted to the server.
>Note: You want to validate on the server side because you can protect against the malicious user, who can easily bypass your JavaScript and submit dangerous input to the server.
```
<h1> EWF Handling Client Request: Form example </h1>
<form action="/" method="POST">
<fieldset>
<legend>Personal details</legend>
<div>
<label>First Name
<input id="given-name" name="given-name" type="text" placeholder="First name only" required autofocus>
</label>
</div>
<div>
<label>Last Name
<input id="family-name" name="family-name" type="text" placeholder="Last name only" required autofocus>
</label>
</div>
<div>
<label>Email
<input id="email" name="email" type="email" placeholder="example@domain.com" required>
</label>
</div>
<div>
<label>Languages
<input type="checkbox" name="languages" value="Spanish"> Spanish
<input type="checkbox" name="languages" value="English"> English
</label>
</div>
</fieldset>
<fieldset>
<div>
<button type=submit>Submit Form</button>
</div>
</fieldset>
</form>
```
<a name="all_names">
### How to read all parameter names
To read all the parameters names we simple call WSF_REQUEST.form_parameters.
```
req: WSF_REQUEST
across req.form_parameters as ic loop show_parameter_name (ic.item.key) end
```
<a name="single_values">
### How to read single values
To read a particular parameter, a single value, for example `given-name', we simple call WSF_REQUEST.form_parameter (a_name) and we check if it's attached to WSF_STRING (represents a String parameter)
```
req: WSF_REQUEST
if attached {WSF_STRING} req.form_paramenter ('given-name') as l_given_name then
-- Work with the given parameter, for example populate an USER object
-- the argument is case sensitive
else
-- Value missing, check the name against the HTML form
end
```
<a name="multiple_values">
### How to read multiple values
To read multiple values, for example in the case of `languages', we simple call WSF_REQUEST.form_parameter (a_name) and we check if it's attached to WSF_MULTIPLE_STRING (represents a String parameter)
```
req: WSF_REQUEST
idioms: LIST[STRING]
-- the argument is case sensitive
if attached {WSF_MULTIPLE_STRING} req.form_paramenter ('languages') as l_languages then
-- Work with the given parameter, for example populate an USER object
-- Get all the associated values
create {ARRAYED_LIST[STRING]} idioms.make (2)
across l_languages as ic loop idioms.force (ic.item.value) end
elseif attached {WSF_STRING} req.form_paramenter ('languages') as l_language then
-- Value missing, check the name against the HTML form
create {ARRAYED_LIST[STRING]} idioms.make (1)
idioms.force (l_language.value)
else
-- Value missing
end
```
In this case we are handling strings values, but in some cases you will need to do a conversion, betweend the strings that came from the request to map them to your domain model.
<a name="table_values">
### How to read table values
This is particularly useful when you have a request with the following format
``` <a href="/link?tab[a]=1&tab[b]=2&tab[c]=foo"> ```
To read table values, for example in the case of `tab', we simple call WSF_REQUEST.form_parameter (a_name) and we check if it's attached to WSF_TABLE.
```
if attached {WSF_TABLE} req.query_parameter ("tab") as l_tab then
l_parameter_names.append ("<br>")
l_parameter_names.append (l_tab.name)
from
l_tab.values.start
until
l_tab.values.after
loop
l_parameter_names.append ("<br>")
l_parameter_names.append (l_tab.values.key_for_iteration)
if attached {WSF_STRING} l_tab.value (l_tab.values.key_for_iteration) as l_value then
l_parameter_names.append ("=")
l_parameter_names.append (l_value.value)
end
l_tab.values.forth
end
end
```
<a name="raw_data">
## Reading Raw Data
You can also access the data in raw format, it means you will need to parse and url-decode it, and also you will not be able to use the previous features, by default, to enable that you need to call `req.set_raw_input_data_recorded (True)'. This feature (reading raw data) is useful if you are reading POST data with JSON or XML formats, but it's not convinient for HTML forms.
To read raw data you need to do this
```
l_raw_data:STRING
req.set_raw_input_data_recorded (True) --
create l_raw_data.make_empty
req.read_input_data_into (l_raw_data)
```
> given-name=testr&family-name=test&dob=1976-08-26&email=test%40gmail.com&url=http%3A%2F%2Fwww.eiffelroom.com&phone=455555555555&languages=Spanish&languages=English
<a name=upload></a>
## Upload Files
How can we read data when the date come from an uploaded file/s?.
HTML supports a form element ```<input type="File" ... > ``` to upload a single file and ```<input type="File" ... multiplr> ``` to upload multiple files.
So supose we have the following form
```
<!DOCTYPE html>
<html>
<head>
<title>EWF Handling Client Request: File Upload Example</title>
</head>
<body>
<h1> EWF Handling Client Request: File Upload Example</h1>
<form action="/upload" enctype="multipart/form-data" method="POST">
<fieldset>
<legend>Upload file/s</legend>
<div>
<label>File
<input name="file-name[]" type="file" multiple>
</label>
<fieldset>
<div>
<button type=submit>Send</button>
</div>
</fieldset>
</form>
</body>
</html>
```
The class WSF_REQUEST has defines mechanism to work with uploaded files. We can call the query
```
WSF_REQUEST.has_uploaded_file: BOOLEAN
-- Has any uploaded file?
```
to check if the request form parameters has any uploaded file, and we can call the feature
```
WSF_REQUEST.uploaded_files: ITERABLE [WSF_UPLOADED_FILE]
-- uploaded files values
--| filename: original path from the user
--| type: content type
--| tmp_name: path to temp file that resides on server
--| tmp_base_name: basename of `tmp_name'
--| error: if /= 0 , there was an error : TODO ...
--| size: size of the file given by the http request
```
to iterate over the uploaded files if any, and the details in the class [WSF_UPLOADED_FILE].
The following snipet code show how to work with Uploaded files using EWF [WSF_REQUEST] class, in the example
we build a simple html answer with basic information, if there is not uploaded files, we send a 400 status code
and a simple message.
```eiffel
if req.path_info.same_string ("/upload") then
-- Check if we have an uploaded file
if req.has_uploaded_file then
-- iterate over all the uploaded files
create l_answer.make_from_string ("<h1>Uploaded File/s</h1><br>")
across req.uploaded_files as ic loop
l_answer.append ("<strong>FileName:</strong>")
l_answer.append (ic.item.filename)
l_answer.append ("<br><strong>Size:</strong>")
l_answer.append (ic.item.size.out)
l_answer.append ("<br>")
end
res.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-type","text/html"],["Content-lenght", l_answer.count.out]>>)
res.put_string (l_answer)
else
-- Here we should handle unexpected errors.
create l_answer.make_from_string ("<strong>No uploaded files</strong><br>")
create l_answer.append ("Back to <a href='/'>Home</a>")
res.put_header ({HTTP_STATUS_CODE}.bad_request, <<["Content-type","text/html"],["Content-lenght", l_answer.count.out]>>)
res.put_string (l_answer)
end
else
-- Handle error
end
```
The source code is available on Github. You can get it by running the command:
```git clone https://github.com/EiffelWebFramework/ewf.git```
The example is located in the directory $PATH/ewf/doc/workbook/upload_file where $PATH is where you run git clone.
<a name=examples>
## Examples
The source code is available on Github. You can get it by running the command:
```git clone https://github.com/EiffelWebFramework/ewf.git```
The GET example is located in the directory $PATH/ewf/doc/workbook/form/get, and the post example is located in the directory $PATH/ewf_examples/workbook/form/post where $PATH is where you run git clone . To run open it using Eiffel Studio or just run theg following command
```estudio -config <ecf_name>.ecf -target <target_name>```
>Note: replace <ecf_name> and<target_name> with the corresponding values.
Nav: [Workbook](../workbook.md) | [Basic Concepts] (/doc/workbook/basics/basics.md) | [Handling Requests: Header Fields](/doc/workbook/handling_request/headers.md)

View File

@@ -0,0 +1,24 @@
note
description: "Basic Service launcher"
class
APPLICATION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
set_service_option ("port", 9090)
end
end

View File

@@ -0,0 +1,101 @@
note
description : "Basic Service that show how to handle a GET request"
date : "$Date$"
revision : "$Revision$"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute
-- Execute the incomming request
local
file: WSF_FILE_RESPONSE
l_parameter_names: STRING
l_answer: STRING
idioms: LIST[STRING]
l_raw_data: STRING
do
if request.is_get_request_method then
if request.path_info.same_string ("/") then
create file.make_html ("form.html")
response.send (file)
elseif request.path_info.same_string ("/search") then
-- (1) the parameter is case sensitive
if not (attached request.query_parameter ("GIVEN-NAME")) then
-- Wrong `GIVEN-NAME' need to be in lower case.
end
-- (2) Multiple values
if attached {WSF_MULTIPLE_STRING} request.query_parameter ("languages") as l_languages then
-- Get all the associated values
create {ARRAYED_LIST[STRING]} idioms.make (2)
across l_languages as ic loop idioms.force (ic.item.value) end
elseif attached {WSF_STRING} request.query_parameter ("languages") as l_language then
-- Single value
print (l_language.value)
else
-- Value Missing
end
-- Read the all parameters names and his values.
create l_parameter_names.make_from_string ("<h2>Parameters Names</h2>")
l_parameter_names.append ("<br>")
create l_answer.make_from_string ("<h2>Parameter Names and Values</h2>")
l_answer.append ("<br>")
across request.query_parameters as ic loop
l_parameter_names.append (ic.item.key)
l_parameter_names.append ("<br>")
l_answer.append (ic.item.key)
l_answer.append_character ('=')
if attached {WSF_STRING} request.query_parameter (ic.item.key) as l_value then
l_answer.append_string (l_value.value)
end
l_answer.append ("<br>")
end
l_parameter_names.append ("<br>")
l_parameter_names.append_string (l_answer)
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_parameter_names.count.out]>>)
response.put_string (l_parameter_names)
elseif request.path_info.same_string ("/link") then
-- WSF_TABLE example
create l_parameter_names.make_from_string ("<h2>Parameters Name</h2>")
if attached {WSF_TABLE} request.query_parameter ("tab") as l_tab then
l_parameter_names.append ("<br>")
l_parameter_names.append (l_tab.name)
from
l_tab.values.start
until
l_tab.values.after
loop
l_parameter_names.append ("<br>")
l_parameter_names.append (l_tab.values.key_for_iteration)
if attached {WSF_STRING} l_tab.value (l_tab.values.key_for_iteration) as l_value then
l_parameter_names.append ("=")
l_parameter_names.append (l_value.value)
end
l_tab.values.forth
end
l_parameter_names.append ("<br>")
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_parameter_names.count.out]>>)
response.put_string (l_parameter_names)
end
else
-- Here we should handle unexpected errors.
end
end
end
end

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="form" library_target="form">
<target name="common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
</target>
<target name="form_nino" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_nino" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\nino-safe.ecf"/>
<cluster name="form" location=".\" recursive="true"/>
</target>
<target name="form_cgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\cgi-safe.ecf"/>
<cluster name="form" location=".\" recursive="true"/>
</target>
<target name="form_libfcgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\libfcgi-safe.ecf"/>
<cluster name="form" location=".\" recursive="true"/>
</target>
<target name="form" extends="form_nino">
</target>
</system>

View File

@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<title>EWF Handling Client Request: Search Form example </title>
</head>
<body>
<h1> EWF Handling Search Request: Form example </h1>
<form action="/search" method="GET">
<fieldset>
<legend>Search by</legend>
<div>
<label>First Name
<input id="given-name" name="given-name" type="text" placeholder="First name only" autofocus>
</label>
</div>
<div>
<label>Last Name
<input id="family-name" name="family-name" type="text" placeholder="Last name only" autofocus>
</label>
</div>
<div>
<label>Date of Birth
<input id="dob" name="dob" type="date">
</label>
</div>
<div>
<label>Email
<input id="email" name="email" type="email" placeholder="example@domain.com">
</label>
</div>
<div>
<label>Languages
<input type="checkbox" name="languages" value="Spanish"> Spanish
<input type="checkbox" name="languages" value="English"> English
</label>
</div>
</fieldset>
<fieldset>
<div>
<button type=submit>Submit Form</button>
</div>
</fieldset>
</form>
<h2> Example link </h2>
<div>
<a href="/link?tab[a]=1&tab[b]=2&tab[c]=foo">Link</a>
</div>
</body>
</html>

View File

@@ -0,0 +1,24 @@
note
description: "Basic Service launcher"
class
APPLICATION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
set_service_option ("port", 9090)
end
end

View File

@@ -0,0 +1,83 @@
note
description : "Reading Parameters from a HTML FORM (method POST) "
date : "$Date$"
revision : "$Revision$"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute
-- Execute the incomming request
local
file: WSF_FILE_RESPONSE
l_parameter_names: STRING
l_answer: STRING
idioms: LIST[STRING]
l_raw_data: STRING
do
if request.is_get_request_method then
create file.make_html ("form.html")
response.send (file)
elseif request.is_post_request_method then
request.set_raw_input_data_recorded (True)
-- (3) Read Raw Data
create l_raw_data.make_empty
request.read_input_data_into (l_raw_data)
-- (1) the parameter is case sensitive
if not (attached request.form_parameter ("GIVEN-NAME")) then
-- Wrong `GIVEN-NAME' need to be in lower case.
end
-- (2) Multiple values
if attached {WSF_MULTIPLE_STRING} request.form_parameter ("languages") as l_languages then
-- Get all the associated values
create {ARRAYED_LIST[STRING]} idioms.make (2)
across l_languages as ic loop idioms.force (ic.item.value) end
elseif attached {WSF_STRING} request.form_parameter ("langauges") as l_language then
-- Single value
print (l_language.value)
else
-- Value Missing
end
-- Read the all parameters names and his values.
create l_parameter_names.make_from_string ("<h2>Parameters Names</h2>")
l_parameter_names.append ("<br>")
create l_answer.make_from_string ("<h2>Parameter Names and Values</h2>")
l_answer.append ("<br>")
across request.form_parameters as ic loop
l_parameter_names.append (ic.item.key)
l_parameter_names.append ("<br>")
l_answer.append (ic.item.key)
l_answer.append_character ('=')
if attached {WSF_STRING} request.form_parameter (ic.item.key) as l_value then
l_answer.append_string (l_value.value)
end
l_answer.append ("<br>")
end
l_parameter_names.append ("<br>")
l_parameter_names.append_string (l_answer)
l_parameter_names.append ("<br>")
l_parameter_names.append ("<h2>Raw content</h2>")
l_parameter_names.append (l_raw_data)
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_parameter_names.count.out]>>)
response.put_string (l_parameter_names)
else
-- Here we should handle unexpected errors.
end
end
end

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="form" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486" library_target="form">
<target name="common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
</target>
<target name="form_nino" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_nino" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\nino-safe.ecf"/>
<cluster name="form" location=".\" recursive="true"/>
</target>
<target name="form_cgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\cgi-safe.ecf"/>
<cluster name="form" location=".\" recursive="true"/>
</target>
<target name="form_libfcgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\libfcgi-safe.ecf"/>
<cluster name="form" location=".\" recursive="true"/>
</target>
<target name="form" extends="form_nino">
</target>
</system>

View File

@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<title>EWF Handling Client Request: Form example </title>
</head>
<body>
<h1> EWF Handling Client Request: Form example </h1>
<form action="/" method="POST">
<fieldset>
<legend>Personal details</legend>
<div>
<label>First Name
<input id="given-name" name="given-name" type="text" placeholder="First name only" required autofocus>
</label>
</div>
<div>
<label>Last Name
<input id="family-name" name="family-name" type="text" placeholder="Last name only" required autofocus>
</label>
</div>
<div>
<label>Date of Birth
<input id="dob" name="dob" type="date" required>
</label>
</div>
<div>
<label>Email
<input id="email" name="email" type="email" placeholder="example@domain.com" required>
</label>
</div>
<div>
<label>URL
<input id="url" name="url" type="url" placeholder="http://mysite.com">
</label>
</div>
<div>
<label>Telephone
<input id="phone" name="phone" type="tel" placeholder="Eg. +447000 000000" required>
</label>
</div>
<div>
<label>Languages
<input type="checkbox" name="languages" value="Spanish"> Spanish
<input type="checkbox" name="languages" value="English"> English
</label>
</div>
</fieldset>
<fieldset>
<div>
<button type=submit>Submit Form</button>
</div>
</fieldset>
</form>
</body>
</html>

View File

@@ -0,0 +1,438 @@
Nav: [Workbook](../workbook.md) | [Handling Requests: Form/Query parameters] (/doc/workbook/handling_request/form.md) | [Generating Responses](/doc/workbook/generating_response/generating_response.md)
#Handling Requests: Headers
##### Introduction
- The [HTTP request header fields (also known as "headers")](https://httpwg.github.io/specs/rfc7231.html#request.header.fields) are set by the client (usually web browser) and sent in the header of the http request text (see http protocol), as opposed to form or query parameters [Form Data]().
- Query parameters are encoded in the URL [GET requests](https://httpwg.github.io/specs/rfc7230.html#http.message).
- Form parameters are encoded in the request message for [POST/PUT requests.](https://httpwg.github.io/specs/rfc7230.html#http.message).
A request usually includes the header fields [Accept, Accept-Encoding, Connection, Cookie, Host, Referer, and User-Agent](https://httpwg.github.io/specs/rfc7231.html#request.header), defining important information about how the server should process the request. And then, the server needs to read the request header fields to use those informations.
##### Table of Contents
- [Reading HTTP Header fields](#read_header)
- [Reading HTTP Request line](#read_line)
- [Understanding HTTP header fields](#understand)
- [Accept](#accept)
- [Accept-Charset](#accept_charset)
- [Accept-Encoding](#accept_encoding)
- [Accept-Language](#accept_language)
- [Connection](#connection)
- [Authorization](#authorization)
- [Content-length](#content-length)
- [Cookie](#cookie)
- [Host](#host)
- [If-Modified-Since](#if-modified-since)
- [If-Unmodified-Since](#if-unmodified-since)
- [Referer](#referer)
- [User-Agent](#user-agent)
- [Example: Request Headers](#example)
- [Example: How to compress pages](#compress)
- [Example: Detecting Browser Types](#browser-types)
- [Example: CGI Variables](#cgi-variables)
That section explains how to read HTTP information sent by the browser via the request header fields. Mostly by defining the most important HTTP request header fields, for more information, read [HTTP 1.1 specification](https://httpwg.github.io/specs/).
## Prerequisites
The Eiffel Web Framework is using the traditional Common Gateway Interface (CGI) programming interface to access the header fields, query and form parameters.
Among other, this means the header fields are exposed with associated CGI field names:
- the header field name are uppercased, and any dash "-" replaced by underscore "_".
- and also prefixed by "HTTP_" except for CONTENT_TYPE and CONTENT_LENGTH.
- For instance `X-Server` will be known as `HTTP_X_SERVER`.
<a name="read_header"></a>
## Reading HTTP Header fields
EWF [WSF_REQUEST]() class provides features to access HTTP headers.
Reading most headers is straightforward by calling:
- the corresponding `http_*` functions such as `http_accept` for header "Accept".
- or indirectly using the `meta_string_variable (a_name)` function by passing the associated CGI field name.
In both cases, if the related header field is supplied by the request, the result is a string value, otherwise it is Void.
Note: always check if the result of those functions is non-void before using it.
* Cookies:
- To iterate on all cookies valued, use `cookies: ITERABLE [WSF_VALUE]`
- To retrieve a specific cookie value, use `cookie (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE`
* Authorization
- To read the Authorization header, first check its type with: `auth_type: detachable READABLE_STRING_8`
- And its value via `http_authorization: detachable READABLE_STRING_8 --Contents of the Authorization: header from the current wgi_request, if there is one.`
* Content_length
- If supplied, get the content length as an string value: `content_length: detachable READABLE_STRING_8`
- or directly as a natural value with `content_length_value: NATURAL_64`
* Content_type
- If supplied, get the content type as an string value with `content_type: detachable HTTP_CONTENT_TYPE`
Due to CGI compliance, the original header names are not available, however the function `raw_header_data` may return the http header data as a string value (warning: this may not be available, depending on the underlying connector). Apart from very specific cases (proxy, debugging, ...), it should not be useful.
Note: CGI variables are information about the current request (and also about the server). Some are based on the HTTP request line and headers (e.g., form parameters, query parameters), others are derived from the socket itself (e.g., the name and IP address of the requesting host), and still others are taken from server installation parameters (e.g., the mapping of URLs to actual paths).
<a name="read_line"></a>
####Retrieve information from the Request Line
For convenience, the following sections refer to a request starting with line:
```
GET http://eiffel.org/search?q=EiffelBase HTTP/1.1
```
Overview of the features
* HTTP method
- The function `request_method: READABLE_STRING_8` gives access to the HTTP request method, (usually GET or POST in conventional Web Applications), but with the raise of REST APIs other methods are also frequently used such as HEAD, PUT, DELETE, OPTIONS, or TRACE.
A few functions helps determining quickly the nature of the request method:
- `is_get_request_method: BOOLEAN -- Is Current a GET request method?`
- `is_put_request_method: BOOLEAN -- Is Current a PUT request method?`
- `is_post_request_method: BOOLEAN -- Is Current a POST request method?`
- `is_delete_request_method: BOOLEAN -- Is Current a DELETE request method?`
In our example the request method is `GET`
* Query String
- The query string for the example is `q=EiffelBase`
- `query_string: READABLE_STRING_8`
* Protocol
- The feature return the third part of the request line, which is generally HTTP/1.0 or HTTP/1.1.
- `server_protocol: READABLE_STRING_8`
In the example the request method is `HTTP/1.1`
<a name="understand"></a>
#### Understanding HTTP 1.1 Request Headers
Access to the request headers permits the web server applications or APIs to perform optimizations and provide behavior that would not be possible without them for instance such as adapting the response according to the browser preferences.
This section summarizes the headers most often used; for more information, see the [HTTP 1.1 specification](https://httpwg.github.io/specs/), note that [RFC 2616 is dead](https://www.mnot.net/blog/2014/06/07/rfc2616_is_dead).
<a name="accept"></a>
* [Accept](https://httpwg.github.io/specs/rfc7231.html#header.accept)
- The "Accept" header field can be used by user agents (browser or other clients) to define response media types that are acceptable. Accept header fields can be used to indicate that the request is limited to a small set of desired types, as in the case of a request for an inline image.
For example, assume an APIs Learn4Kids can respond with XML or JSON data (JSON format have some advantages over XML, readability, parsing etc...), a client can define its preference using "Accept: application/json" to request data in JSON format, or "Accept: application/xml" to get XML format. In other case the server sends a not acceptable response. Note that the client can define an ordered list of accepted content types, including "*", the client will get the response and know the content type via the response header field "Content-Type". Related [Content-Negotiation]()
<a name="accept_charset"></a>
* [Accept-Charset](https://httpwg.github.io/specs/rfc7231.html#header.accept-charset)
- The "Accept-Charset" header field can be sent by a user agent (browser or other clients) to indicate which charsets are acceptable in textual response content (e.g., ISO-8859-1).
<a name="accept_encoding"></a>
* [Accept-Encoding](https://httpwg.github.io/specs/rfc7231.html#header.accept-encoding)
- The "Accept-Encoding" header field can be used by user agents (browser or other clients) to indicate which response content-codings (`gzip`, `compress`) are acceptable in the response. An "identity" token is used as a synonym for "no encoding" in order to communicate when no encoding is preferred. If the server receives this header, it is free to encode the page by using one of the content-encodings specified (usually to reduce transmission time), sending the `Content-Encoding` response header to indicate that it has done so.
<a name="accept_language"></a>
* [Accept-Language](https://httpwg.github.io/specs/rfc7231.html#header.accept-language)
- The "Accept-Language" header field can be used by user agents (browser or other client) to indicate the set of natural languages that are preferred in the response in case the server can produce representation in more than one language. The value of the header should be one of the standard language codes such as en, en-us, da, etc. See RFC 1766 for details (start at http://www.rfc-editor.org/ to get a current list of the RFC archive sites).
<a name="connection"></a>
* [Connection](https://httpwg.github.io/specs/rfc7230.html#header.connection)
- The "Connection" header field allows the sender to indicate desired control options for the current connection, for example if it can hanlde persistent HTTP connections.
By default HTTP/1.1 uses "persistent connections", allowing multiple requests and responses to be carried over a single connection. The "close" connection option is used to signal that a connection will not persist after the current request/response.
<a name="authorization"></a>
* [Authorization](https://httpwg.github.io/specs/rfc7235.html#header.authorization)
- The header is used by user agents to authenticate themselves when accessing password protected resources.
<a name="content-length"></a>
* [Content-Length](https://httpwg.github.io/specs/rfc7230.html#header.content-length)
- For messages that includes a payload body, the Content-Length field-value provides the framing information necessary to determine where the body (and message) ends.
<a name="cookie"></a>
* [Cookie](https://httpwg.github.io/specs/rfc6265.html)
- The Cookie header contains cookies received by the user agent in previous Set-Cookie headers. The origin server is free to ignore the Cookie header or use its contents for an application-specific purpose. (Related State Management).
<a name="host"></a>
* [Host](https://httpwg.github.io/specs/rfc7230.html#header.host)
- The "Host" header field provides the host and port information from the target URI, enabling the origin server to distinguish among resources while serving requests for multiple host names on a single IP address. In HTTP 1.1, browsers and other clients are required to specify this header, which indicates the host and port as given in the original URL.
<a name="if-modified-since"></a>
* [If-Modified-Since](https://httpwg.github.io/specs/rfc7232.html#header.if-modified-since)
- The "If-Modified-Since" header field makes a GET or HEAD request method conditional on the selected representation's modification date being more recent than the date provided in the field-value. Transfering of the selected representation's data is avoided if that data has not changed. So, indicates that the user agents wants the page only if it has been changes after the specified date. The server sends a 304 resource not modified if not has a newer result representation available.
<a name="if-unmodified-since"></a>
* [If-Unmodified-Since](https://httpwg.github.io/specs/rfc7232.html#header.if-unmodified-since)
- The "If-Unmodified-Since" header field makes the request method conditional on the selected representation's last modification date being earlier than or equal to the date provided in the field-value. The operation should succeed only if the document is older than the specified date.
Generally, If-Modified-Since is used for GET requests (“give me the document only if it is newer than my cached version”), whereas If-Unmodified-Since is used for PUT requests (“update this document only if nobody else has changed it since I generated it”).
<a name="referer"></a>
* [Referer](https://httpwg.github.io/specs/rfc7231.html#header.referer)
- The "Referer" header field allows the user agent to specify a URI reference for the resource from which the target URI was obtained (i.e., the "referrer", though the field name is misspelled). A user agent MUST NOT include the fragment and userinfo components of the URI reference [RFC3986], if any, when generating the Referer field value. This header indicates the URL of the referring Web page.
For example, if you are at Web page A and click on a link to Web page B, the URL of Web page A is
included in the Referer header when the browser requests Web page B.
<a name="user-agent"></a>
* [User-Agent](https://httpwg.github.io/specs/rfc7231.html#header.user-agent)
- The "User-Agent" header field contains information about the user agent of the request, which is often used by servers to help identify the scope of reported interoperability problems, to work around or tailor responses to avoid particular user agent limitations, and for analytics regarding browser or operating system use or device.
**Note**: the example shows the **WSF_EXECUTION** implementation, that will be used by the service launcher.
<a name="example"></a>
#### Building a Table of All Request Headers
The following [EWF service](/doc/workbook/handling_request/headers/header_fields/application.e) code simply uses an ```html_template``` to fill a table (names and values) with all the headers fields it receives.
The service accomplishes this task by calling ```req.meta_variables``` feature to get an ```ITERABLE [WSF_STRING]```, an structure that can be iterated over using ```across...loop...end```, then it checks if the name has the prefix ```HTTP_``` and if it is true, put the header name and value in a row. (the name in the left cell, the value in the right cell).
The service also writes three components of the main request line (method, URI, and protocol), and also the raw header.
```eiffel
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute the incomming request
local
l_raw_data: STRING
l_page_response: STRING
l_rows: STRING
do
create l_page_response.make_from_string (html_template)
if req.path_info.same_string ("/") then
-- HTTP method
l_page_response.replace_substring_all ("$http_method", req.request_method)
-- URI
l_page_response.replace_substring_all ("$uri", req.path_info)
-- Protocol
l_page_response.replace_substring_all ("$protocol", req.server_protocol)
-- Fill the table rows with HTTP Headers
create l_rows.make_empty
across req.meta_variables as ic loop
if ic.item.name.starts_with ("HTTP_") then
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append (ic.item.name)
l_rows.append ("</td>")
l_rows.append ("<td>")
l_rows.append (ic.item.value)
l_rows.append ("</td>")
l_rows.append ("</tr>")
end
end
l_page_response.replace_substring_all ("$rows", l_rows)
-- Reading the raw header
if attached req.raw_header_data as l_raw_header then
l_page_response.replace_substring_all ("$raw_header", l_raw_header)
end
res.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_page_response.count.out]>>)
res.put_string (l_page_response)
end
end
html_template: STRING = "[
<!DOCTYPE html>
<html>
<head>
<style>
thead {color:green;}
tbody {color:blue;}
table, th, td {
border: 1px solid black;
}
</style>
</head>
<body>
<h1>EWF service example: Showing Request Headers</h1>
<strong>HTTP METHOD:</strong>$http_method<br/>
<strong>URI:</strong>$uri<br>
<strong>PROTOCOL:</strong>$protocol<br/>
<strong>REQUEST TIME:</strong>$time<br/>
<br>
<table>
<thead>
<tr>
<th>Header Name</th>
<th>Header Value</th>
</tr>
</thead>
<tbody>
$rows
</tbody>
</table>
<h2>Raw header</h2>
$raw_header
</body>
</html>
]"
end
```
<a name="compress"></a>
#### How to compress pages
To be completed.
<a name="browser-types"></a>
#### Detecting Browser Types
The User-Agent header identifies the specific browser/client that is sending the request. The following code shows a [EWF service](/doc/workbook/handling_request/headers/browser_name/application.e) that sends browser-specific responses.
The examples uses the ideas based on the [Browser detection using the user agent](https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent) article.
Basically the code check if the header user_agent exist and then call the ```browser_name (a_user_agent: READABLE_STRING_8): READABLE_STRING_32```
feature to retrieve the current browser name or Unknown in other case.
```eiffel
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute the incomming request
local
l_raw_data: STRING
l_page_response: STRING
l_rows: STRING
do
create l_page_response.make_from_string (html_template)
if req.path_info.same_string ("/") then
-- retrieve the user-agent
if attached req.http_user_agent as l_user_agent then
l_page_response.replace_substring_all ("$user_agent", l_user_agent)
l_page_response.replace_substring_all ("$browser", browser_name (l_user_agent))
else
l_page_response.replace_substring_all ("$user_agent", "[]")
l_page_response.replace_substring_all ("$browser", "Unknown, the user-agent was not present.")
end
res.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_page_response.count.out]>>)
res.put_string (l_page_response)
end
end
feature -- Browser utility
browser_name (a_user_agent: READABLE_STRING_8): READABLE_STRING_32
-- Browser name.
-- Must contain Must not contain
-- Firefox Firefox/xyz Seamonkey/xyz
-- Seamonkey Seamonkey/xyz
-- Chrome Chrome/xyz Chromium/xyz
-- Chromium Chromium/xyz
-- Safari Safari/xyz Chrome/xyz
-- Chromium/xyz
-- Opera OPR/xyz [1]
-- Opera/xyz [2]
-- Internet Explorer ;MSIE xyz; Internet Explorer doesn't put its name in the BrowserName/VersionNumber format
do
if
a_user_agent.has_substring ("Firefox") and then
not a_user_agent.has_substring ("Seamonkey")
then
Result := "Firefox"
elseif a_user_agent.has_substring ("Seamonkey") then
Result := "Seamonkey"
elseif a_user_agent.has_substring ("Chrome") and then not a_user_agent.has_substring ("Chromium")then
Result := "Chrome"
elseif a_user_agent.has_substring ("Chromium") then
Result := "Chromiun"
elseif a_user_agent.has_substring ("Safari") and then not (a_user_agent.has_substring ("Chrome") or else a_user_agent.has_substring ("Chromium")) then
Result := "Safari"
elseif a_user_agent.has_substring ("OPR") or else a_user_agent.has_substring ("Opera") then
Result := "Opera"
elseif a_user_agent.has_substring ("MSIE") or else a_user_agent.has_substring ("Trident")then
Result := "Internet Explorer"
else
Result := "Unknown"
end
end
html_template: STRING = "[
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<h1>EWF service example: Showing Browser Dectection Using User-Agent</h1> <br>
<strong>User Agent:</strong> $user_agent <br>
<h2>Enjoy using $browser </h2>
</body>
</html>
]"
end
```
Let see some results, we will show the html returned
**Internet Explorer**
---
```
<h1>EWF service example: Showing Browser Dectection Using User-Agent</h1></br>
<strong>User Agent:</strong> Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; MDDCJS; rv:11.0) like Gecko <br>
<h2> Enjoy using Internet Explorer </h2>
```
**Chrome**
---
```
<h1>EWF service example: Showing Browser Dectection Using User-Agent</h1></br>
<strong>User Agent:</strong> Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.91 Safari/537.36 <br>
<h2> Enjoy using Chrome </h2>
```
As an exercise, try to write a similar service to retrieve the OS family using the User-Agent information.
<a name="cgi-variables"></a>
[Meta-variables](https://tools.ietf.org/html/rfc3875#section-4.1) contains data about the request, they are identified by case-insensitive names. In this section, the purpose is to show a similar example to HEADERS FIELDS, but in this case building a table showing the standard CGI variables.
* [AUTH_TYPE](https://tools.ietf.org/html/rfc3875#section-4.1.1).
* [CONTENT_LENGTH](https://tools.ietf.org/html/rfc3875#section-4.1.2)
* [CONTENT_TYPE](https://tools.ietf.org/html/rfc3875#section-4.1.3)
* [GATEWAY_INTERFACE](https://tools.ietf.org/html/rfc3875#section-4.1.4)
* [PATH_INFO](https://tools.ietf.org/html/rfc3875#section-4.1.5)
* [PATH_TRANSLATED](https://tools.ietf.org/html/rfc3875#section-4.1.6)
* [QUERY_STRING](https://tools.ietf.org/html/rfc3875#section-4.1.7)
* [REMOTE_ADDR](https://tools.ietf.org/html/rfc3875#section-4.1.8)
* [REMOTE_HOST](https://tools.ietf.org/html/rfc3875#section-4.1.9)
* [REMOTE_IDENT](https://tools.ietf.org/html/rfc3875#section-4.1.10)
* [REMOTE_USER](https://tools.ietf.org/html/rfc3875#section-4.1.11)
* [REQUEST_METHOD](https://tools.ietf.org/html/rfc3875#section-4.1.12)
* [SCRIPT_NAME](https://tools.ietf.org/html/rfc3875#section-4.1.13)
* [SERVER_NAME](https://tools.ietf.org/html/rfc3875#section-4.1.14)
* [SERVER_PROTOCOL](https://tools.ietf.org/html/rfc3875#section-4.1.15)
* [SERVER_SOFTWARE](https://tools.ietf.org/html/rfc3875#section-4.1.16)
**Example**
An [EWF service](/doc/workbook/handling_request/headers/cgi_variables/application.e) that shows the CGI variables, creates a table showing the values of all the CGI variables.
Nav: [Workbook](../workbook.md) | [Handling Requests: Form/Query parameters] (/doc/workbook/handling_request/form.md) | [Generating Responses](/doc/workbook/generating_response/generating_response.md)

View File

@@ -0,0 +1,24 @@
note
description: "Basic Service launcher"
class
APPLICATION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
set_service_option ("port", 9090)
end
end

View File

@@ -0,0 +1,97 @@
note
description : "Basic Service that Read a Request, a "
date : "$Date$"
revision : "$Revision$"
EIS: "name=Browser detection using user agent","src=https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent", "protocol=url"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute
-- Execute the incomming request
local
l_raw_data: STRING
l_page_response: STRING
l_rows: STRING
do
create l_page_response.make_from_string (html_template)
if request.path_info.same_string ("/") then
-- retrieve the user-agent
if attached request.http_user_agent as l_user_agent then
l_page_response.replace_substring_all ("$user_agent", l_user_agent)
l_page_response.replace_substring_all ("$browser", browser_name (l_user_agent))
else
l_page_response.replace_substring_all ("$user_agent", "[]")
l_page_response.replace_substring_all ("$browser", "Unknown, the user-agent was not present.")
end
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_page_response.count.out]>>)
response.put_string (l_page_response)
end
end
feature -- Browser utility
browser_name (a_user_agent: READABLE_STRING_8): STRING_8
-- Must contain Must not contain
-- Firefox Firefox/xyz Seamonkey/xyz
-- Seamonkey Seamonkey/xyz
-- Chrome Chrome/xyz Chromium/xyz
-- Chromium Chromium/xyz
-- Safari Safari/xyz Chrome/xyz
-- Chromium/xyz
-- Opera OPR/xyz [1]
-- Opera/xyz [2]
-- Internet Explorer ;MSIE xyz; Internet Explorer doesn't put its name in the BrowserName/VersionNumber format
do
if
a_user_agent.has_substring ("Firefox") and then
not a_user_agent.has_substring ("Seamonkey")
then
Result := "Firefox"
elseif a_user_agent.has_substring ("Seamonkey") then
Result := "Seamonkey"
elseif a_user_agent.has_substring ("Chrome") and then not a_user_agent.has_substring ("Chromium")then
Result := "Chrome"
elseif a_user_agent.has_substring ("Chromium") then
Result := "Chromiun"
elseif a_user_agent.has_substring ("Safari") and then not (a_user_agent.has_substring ("Chrome") or else a_user_agent.has_substring ("Chromium")) then
Result := "Safari"
elseif a_user_agent.has_substring ("OPR") or else a_user_agent.has_substring ("Opera") then
Result := "Opera"
elseif a_user_agent.has_substring ("MSIE") or else a_user_agent.has_substring ("Trident")then
Result := "Internet Explorer"
else
Result := "Unknown"
end
end
html_template: STRING = "[
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<h1>EWF service example: Showing Browser Dectection Using User-Agent</h1> <br>
<strong>User Agent:</strong> $user_agent <br>
<h2>Enjoy using $browser </h2>
</body>
</html>
]"
end

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="browsers" library_target="browsers">
<target name="common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
</target>
<target name="browsers_nino" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_nino" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\nino-safe.ecf"/>
<cluster name="browsers" location=".\" recursive="true"/>
</target>
<target name="browsers_cgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\cgi-safe.ecf"/>
<cluster name="browsers" location=".\" recursive="true"/>
</target>
<target name="browsers_libfcgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\libfcgi-safe.ecf"/>
<cluster name="browsers" location=".\" recursive="true"/>
</target>
<target name="browsers" extends="browsers_nino">
</target>
</system>

View File

@@ -0,0 +1,24 @@
note
description: "Basic Service launcher"
class
APPLICATION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
set_service_option ("port", 9090)
end
end

View File

@@ -0,0 +1,303 @@
note
description : "Basic Service that shows the standard CGI variables"
date : "$Date$"
revision : "$Revision$"
EIS: "name=CGI specification","src=(https://tools.ietf.org/html/rfc3875", "protocol=url"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute
-- Execute the incomming request
local
l_raw_data: STRING
l_page_response: STRING
l_rows: STRING
do
create l_page_response.make_from_string (html_template)
if request.path_info.same_string ("/") then
-- HTTP method
l_page_response.replace_substring_all ("$http_method", request.request_method)
-- URI
l_page_response.replace_substring_all ("$uri", request.path_info)
-- Protocol
l_page_response.replace_substring_all ("$protocol", request.server_protocol)
-- Fill the table rows with CGI standard variables
create l_rows.make_empty
-- Auth_type
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append ("AUTH_TYPE")
l_rows.append ("</td>")
l_rows.append ("<td>")
if attached request.auth_type as l_type then
l_rows.append (l_type)
else
l_rows.append ("Not present")
end
l_rows.append ("</td>")
l_rows.append ("</tr>")
-- Content length
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append ("CONTENT_LENGTH")
l_rows.append ("</td>")
l_rows.append ("<td>")
if attached request.content_length as l_content_length then
l_rows.append (l_content_length)
else
l_rows.append ("Not present")
end
l_rows.append ("</td>")
l_rows.append ("</tr>")
-- Content length
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append ("CONTENT_TYPE")
l_rows.append ("</td>")
l_rows.append ("<td>")
if attached request.content_type as l_content_type then
l_rows.append (l_content_type.string)
else
l_rows.append ("Not present")
end
l_rows.append ("</td>")
l_rows.append ("</tr>")
-- Gateway interface
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append ("GATEWAY_INTERFACE")
l_rows.append ("</td>")
l_rows.append ("<td>")
if attached request.gateway_interface as l_gateway_interface then
l_rows.append (l_gateway_interface)
else
l_rows.append ("Not present")
end
l_rows.append ("</td>")
l_rows.append ("</tr>")
-- Path info
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append ("PATH_INFO")
l_rows.append ("</td>")
l_rows.append ("<td>")
if attached request.path_info as l_path_info then
l_rows.append (l_path_info)
else
l_rows.append ("Not present")
end
l_rows.append ("</td>")
l_rows.append ("</tr>")
-- Path translated
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append ("PATH_TRANSLATED")
l_rows.append ("</td>")
l_rows.append ("<td>")
if attached request.path_translated as l_path_translated then
l_rows.append (l_path_translated)
else
l_rows.append ("Not present")
end
l_rows.append ("</td>")
l_rows.append ("</tr>")
-- Query string
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append ("QUERY_STRING")
l_rows.append ("</td>")
l_rows.append ("<td>")
if attached request.query_string as l_query_string then
l_rows.append (l_query_string)
else
l_rows.append ("Not present")
end
l_rows.append ("</td>")
l_rows.append ("</tr>")
-- Remote addr
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append ("REMOTE_ADDR")
l_rows.append ("</td>")
l_rows.append ("<td>")
l_rows.append (request.remote_addr)
l_rows.append ("</td>")
l_rows.append ("</tr>")
-- Remote host
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append ("REMOTE_HOST")
l_rows.append ("</td>")
l_rows.append ("<td>")
if attached request.remote_host as l_remote_host then
l_rows.append (l_remote_host)
else
l_rows.append ("Not present")
end
l_rows.append ("</td>")
l_rows.append ("</tr>")
-- Remote ident
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append ("REMOTE_IDENT")
l_rows.append ("</td>")
l_rows.append ("<td>")
if attached request.remote_ident as l_remote_ident then
l_rows.append (l_remote_ident)
else
l_rows.append ("Not present")
end
l_rows.append ("</td>")
l_rows.append ("</tr>")
-- Remote user
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append ("REMOTE_USER")
l_rows.append ("</td>")
l_rows.append ("<td>")
if attached request.remote_user as l_remote_user then
l_rows.append (l_remote_user)
else
l_rows.append ("Not present")
end
l_rows.append ("</td>")
l_rows.append ("</tr>")
-- Request method
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append ("REQUEST_METHOD")
l_rows.append ("</td>")
l_rows.append ("<td>")
l_rows.append (request.request_method)
l_rows.append ("</td>")
l_rows.append ("</tr>")
-- Script name
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append ("SCRIPT_NAME")
l_rows.append ("</td>")
l_rows.append ("<td>")
l_rows.append (request.script_name)
l_rows.append ("</td>")
l_rows.append ("</tr>")
-- Server name
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append ("SERVER_NAME")
l_rows.append ("</td>")
l_rows.append ("<td>")
l_rows.append (request.server_name)
l_rows.append ("</td>")
l_rows.append ("</tr>")
-- Server protocol
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append ("SERVER_PROTOCOL")
l_rows.append ("</td>")
l_rows.append ("<td>")
l_rows.append (request.server_protocol)
l_rows.append ("</td>")
l_rows.append ("</tr>")
-- Server software
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append ("SERVER_SOFTWARE")
l_rows.append ("</td>")
l_rows.append ("<td>")
l_rows.append (request.server_software)
l_rows.append ("</td>")
l_rows.append ("</tr>")
l_page_response.replace_substring_all ("$rows", l_rows)
-- Reading the raw header
if attached request.raw_header_data as l_raw_header then
l_page_response.replace_substring_all ("$raw_header", l_raw_header)
end
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_page_response.count.out]>>)
response.put_string (l_page_response)
end
end
html_template: STRING = "[
<!DOCTYPE html>
<html>
<head>
<style>
thead {color:green;}
tbody {color:blue;}
table, th, td {
border: 1px solid black;
}
</style>
</head>
<body>
<h1>EWF service example: Showing Standard CGI Variables</h1>
<strong>HTTP METHOD:</strong>$http_method<br>
<strong>URI:</strong>$uri<br>
<strong>PROTOCOL:</strong>$protocol<br>
<br>
<table>
<thead>
<tr>
<th>CGI Name</th>
<th>Value</th>
</tr>
</thead>
<tbody>
$rows
</tbody>
</table>
<h2>Raw header</h2>
$raw_header
</body>
</html>
]"
end

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="cgi_variables" library_target="cgi_variables">
<target name="common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
</target>
<target name="cgi_variables_nino" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_nino" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\nino-safe.ecf"/>
<cluster name="cgi_variables" location=".\" recursive="true"/>
</target>
<target name="cgi_variables_cgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\cgi-safe.ecf"/>
<cluster name="cgi_variables" location=".\" recursive="true"/>
</target>
<target name="cgi_variables_libfcgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\libfcgi-safe.ecf"/>
<cluster name="cgi_variables" location=".\" recursive="true"/>
</target>
<target name="cgi_variables" extends="cgi_variables_nino">
</target>
</system>

View File

@@ -0,0 +1,24 @@
note
description: "Basic Service launcher"
class
APPLICATION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
set_service_option ("port", 9090)
end
end

View File

@@ -0,0 +1,105 @@
note
description : "Basic Service that Read Request Headers"
date : "$Date$"
revision : "$Revision$"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute
-- Execute the incomming request
local
l_raw_data: STRING
l_page_response: STRING
l_rows: STRING
do
create l_page_response.make_from_string (html_template)
if request.path_info.same_string ("/") then
-- HTTP method
l_page_response.replace_substring_all ("$http_method", request.request_method)
-- URI
l_page_response.replace_substring_all ("$uri", request.path_info)
-- Protocol
l_page_response.replace_substring_all ("$protocol", request.server_protocol)
-- Fill the table rows with HTTP Headers
create l_rows.make_empty
across request.meta_variables as ic loop
if ic.item.name.starts_with ("HTTP_") then
l_rows.append ("<tr>")
l_rows.append ("<td>")
l_rows.append (ic.item.name)
l_rows.append ("</td>")
l_rows.append ("<td>")
l_rows.append (ic.item.value)
l_rows.append ("</td>")
l_rows.append ("</tr>")
end
end
l_page_response.replace_substring_all ("$rows", l_rows)
-- Reading the raw header
if attached request.raw_header_data as l_raw_header then
l_page_response.replace_substring_all ("$raw_header", l_raw_header)
end
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_page_response.count.out]>>)
response.put_string (l_page_response)
end
end
html_template: STRING = "[
<!DOCTYPE html>
<html>
<head>
<style>
thead {color:green;}
tbody {color:blue;}
table, th, td {
border: 1px solid black;
}
</style>
</head>
<body>
<h1>EWF service example: Showing Request Headers</h1>
<strong>HTTP METHOD:</strong>$http_method<br>
<strong>URI:</strong>$uri<br>
<strong>PROTOCOL:</strong>$protocol<br>
<strong>REQUEST TIME:</strong>$time<br>
<br>
<table>
<thead>
<tr>
<th>Header Name</th>
<th>Header Value</th>
</tr>
</thead>
<tbody>
$rows
</tbody>
</table>
<h2>Raw header</h2>
$raw_header
</body>
</html>
]"
end

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="header_fields" library_target="header_fields">
<target name="common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
</target>
<target name="header_fields_nino" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_nino" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\nino-safe.ecf"/>
<cluster name="header_fields" location=".\" recursive="true"/>
</target>
<target name="header_fields_cgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\cgi-safe.ecf"/>
<cluster name="header_fields" location=".\" recursive="true"/>
</target>
<target name="header_fields_libfcgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\libfcgi-safe.ecf"/>
<cluster name="header_fields" location=".\" recursive="true"/>
</target>
<target name="header_fields" extends="header_fields_nino">
</target>
</system>

View File

@@ -0,0 +1,24 @@
note
description: "Basic Service launcher"
class
APPLICATION
inherit
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
set_service_option ("port", 9090)
end
end

View File

@@ -0,0 +1,58 @@
note
description : "Basic Service that show how to Upload a file"
date : "$Date$"
revision : "$Revision$"
class
APPLICATION_EXECUTION
inherit
WSF_EXECUTION
create
make
feature -- Basic operations
execute
-- Execute the incomming request
local
file: WSF_FILE_RESPONSE
l_answer: STRING
do
if request.is_get_request_method then
if request.path_info.same_string ("/") then
create file.make_html ("upload.html")
response.send (file)
else
-- Here we should handle unexpected errors.
end
elseif request.is_post_request_method then
if request.path_info.same_string ("/upload") then
-- Check if we have an uploaded file
if request.has_uploaded_file then
-- iterate over all the uploaded files
create l_answer.make_from_string ("<h1>Uploaded File/s</h1><br>")
across request.uploaded_files as ic loop
l_answer.append ("<strong>FileName:</strong>")
l_answer.append (ic.item.filename)
l_answer.append ("<br><strong>Size:</strong>")
l_answer.append (ic.item.size.out)
l_answer.append ("<br>")
end
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-type","text/html"],["Content-lenght", l_answer.count.out]>>)
response.put_string (l_answer)
else
-- Here we should handle unexpected errors.
create l_answer.make_from_string ("<strong>No uploaded files</strong><br>")
l_answer.append ("Back to <a href='/'>Home</a>")
response.put_header ({HTTP_STATUS_CODE}.bad_request, <<["Content-type","text/html"],["Content-lenght", l_answer.count.out]>>)
response.put_string (l_answer)
end
else
-- Handle error
end
end
end
end

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="upload" library_target="upload">
<target name="common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
</target>
<target name="upload_nino" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_nino" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\nino-safe.ecf"/>
<cluster name="upload" location=".\" recursive="true"/>
</target>
<target name="upload_cgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\cgi-safe.ecf"/>
<cluster name="upload" location=".\" recursive="true"/>
</target>
<target name="upload_libfcgi" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="default_libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\libfcgi-safe.ecf"/>
<cluster name="upload" location=".\" recursive="true"/>
</target>
<target name="upload" extends="upload_nino">
</target>
</system>

View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<title>EWF Handling Client Request: File Upload Example</title>
</head>
<body>
<h1> EWF Handling Client Request: File Upload Example</h1>
<form action="/upload" enctype="multipart/form-data" method="POST">
<fieldset>
<legend>Upload file/s</legend>
<div>
<label>File
<input name="file-name[]" type="file" multiple>
</label>
<fieldset>
<div>
<button type=submit>Send</button>
</div>
</fieldset>
</form>
</body>
</html>

13
doc/workbook/readme.md Normal file
View File

@@ -0,0 +1,13 @@
Introduction
Basic Concepts
Generating Plain Text
Generation HTML
Handling Client Request:
Form Data
Request Heders
Query Parameters.

38
doc/workbook/workbook.md Normal file
View File

@@ -0,0 +1,38 @@
# EWF Workbook
##### Table of Contents
* [EWF Core](#core)
* [EWF Introduction](#introduction)
* [Handling Requests: Form/Query Parameter](#form_query_parameters)
* [Handling Requests: Header Fields](#header_fields)
* [Generating Responses](/workbook/generating_response/generating_response.md)
* [Handling Cookies](/workbook/handling_cookies/handling_cookies.md)
*
<a name="core"></a>
# EWF Core
Before reading (or walking throught) the workbook, to get a quick overview of EWF, it is recommended to read the following articles:
* [Getting Started with EWF](http://eiffelwebframework.github.io/EWF/getting-started/)
* [EWF Documentation](http://eiffelwebframework.github.io/EWF/wiki/Documentation/)
* [EWF Application Lifecyle](https://github.com/EiffelWebFramework/ewf_examples/wiki/Application-Lifecycle)
<a name="introduction"></a>
## Introduction
[Basic Concepts] (/doc/workbook/basics/basics.md).
<a name="form_query_parameters"></a>
## Handling Requests: Form/Query Parameter
[Handling Requests: Form/Query Parameter] (/doc/workbook/handling_request/form.md).
<a name="header_fields"></a>
## Handling Requests: Header Fields
[Handling Requests: Header Fields](/doc/workbook/handling_request/headers.md).
<a name="header_fields"></a>
## Generating Response
[Generating Responses](/doc/workbook/generating_response/generating_response.md)
## Handling Cookies
[Handling Cookies](/doc/workbook/handling_cookies/handling_cookies.md)

View File

@@ -38,14 +38,14 @@ feature -- Router and Filter
-- NOTE: you could put all those files in a specific folder, and use WSF_FILE_SYSTEM_HANDLER with "/"
-- this way, it handles the caching and so on
router.handle_with_request_methods ("/assets", create {WSF_FILE_SYSTEM_HANDLER}.make_hidden ("assets"), router.methods_GET)
router.handle ("/assets", create {WSF_FILE_SYSTEM_HANDLER}.make_hidden ("assets"), router.methods_GET)
end
feature -- Helper: mapping
map_agent_uri (a_uri: READABLE_STRING_8; a_action: like {WSF_URI_AGENT_HANDLER}.action; rqst_methods: detachable WSF_REQUEST_METHODS)
do
router.map_with_request_methods (create {WSF_URI_MAPPING}.make (a_uri, create {WSF_URI_AGENT_HANDLER}.make (a_action)), rqst_methods)
router.map (create {WSF_URI_MAPPING}.make (a_uri, create {WSF_URI_AGENT_HANDLER}.make (a_action)), rqst_methods)
end
feature -- Execution

View File

@@ -18,16 +18,17 @@
</target>
<target name="debug_any" extends="common">
<root class="EWF_DEBUG_SERVER" feature="make_and_launch"/>
<setting name="concurrency" value="scoop"/>
<setting name="concurrency" value="thread"/>
<library name="cgi" location="..\..\library\server\wsf\connector\cgi-safe.ecf" readonly="false"/>
<library name="standalone" location="..\..\library\server\wsf\connector\standalone-safe.ecf" readonly="false"/>
<library name="libfcgi" location="..\..\library\server\wsf\connector\libfcgi-safe.ecf" readonly="false"/>
<library name="nino" location="..\..\library\server\wsf\connector\nino-safe.ecf" readonly="false"/>
<library name="standalone" location="..\..\library\server\wsf\connector\standalone-safe.ecf" readonly="false"/>
<cluster name="launcher" location=".\launcher\any\" recursive="true"/>
<cluster name="src" location=".\src\" recursive="true"/>
</target>
<target name="debug_standalone" extends="common">
<root class="EWF_DEBUG_SERVER" feature="make_and_launch"/>
<setting name="concurrency" value="thread"/>
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone-safe.ecf" readonly="false"/>
<cluster name="launcher" location=".\launcher\default\" recursive="true"/>
<cluster name="src" location=".\src\" recursive="true"/>

View File

@@ -7,7 +7,7 @@
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true"/>
</option>
<setting name="concurrency" value="scoop"/>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="ewsgi" location="..\..\library\server\ewsgi\ewsgi-safe.ecf"/>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread-safe.ecf"/>

View File

@@ -45,11 +45,11 @@ feature -- Execution
req := request
create router.make (3)
router.handle ("/test/{var}", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent handle_test))
router.handle ("/env", create {WSF_URI_AGENT_HANDLER}.make (agent handle_env))
router.handle ("/exit", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent handle_exit))
router.handle ("/test/{var}", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent handle_test), Void)
router.handle ("/env", create {WSF_URI_AGENT_HANDLER}.make (agent handle_env), Void)
router.handle ("/exit", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent handle_exit), Void)
create fs.make_with_path ((create {EXECUTION_ENVIRONMENT}).current_working_path.extended ("files"))
router.handle ("/files", fs)
router.handle ("/files", fs, Void)
create sess
router.dispatch (req, response, sess)
if not sess.dispatched then

View File

@@ -27,7 +27,7 @@
</target>
<target name="filter_standalone" extends="common">
<root class="FILTER_SERVER" feature="make"/>
<setting name="concurrency" value="scoop"/>
<setting name="concurrency" value="thread"/>
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone-safe.ecf" readonly="true"/>
<cluster name="filter" location="src\" recursive="true"/>
</target>

View File

@@ -59,7 +59,7 @@ feature {NONE} -- Initialization
create l_methods
l_methods.enable_options
l_methods.enable_get
router.handle_with_request_methods ("/user/{userid}", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent l_options_filter.execute), l_methods)
router.handle ("/user/{userid}", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent l_options_filter.execute), l_methods)
end
initialize_json

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-9-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-9-0 http://www.eiffel.com/developers/xml/configuration-1-9-0.xsd" name="filter" uuid="52FF4B77-0614-4D8B-9B96-C07EC852793E" library_target="filter">
<target name="common">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/\.git$</exclude>
<exclude>/\.svn$</exclude>
</file_rule>
<option debug="true" warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
<debug name="nino" enabled="true"/>
<assertions precondition="true" postcondition="true" invariant="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf" readonly="true"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf" readonly="true"/>
<library name="http" location="../../../../library/network/protocol/http/http-safe.ecf" readonly="true"/>
<library name="json" location="..\..\..\..\contrib\library\text\parser\json\library\json-safe.ecf" readonly="true"/>
<library name="wsf" location="..\..\..\..\library\server\obsolete\v0\wsf\wsf-safe.ecf" readonly="true"/>
<library name="wsf_router_context" location="..\..\..\..\library\server\obsolete\v0\wsf\wsf_router_context-safe.ecf" readonly="true"/>
<library name="wsf_extension" location="..\..\..\..\library\server\obsolete\v0\wsf\wsf_extension-safe.ecf" readonly="true"/>
<library name="http_authorization" location="..\..\..\..\library\server\authentication\http_authorization\http_authorization-safe.ecf" readonly="true"/>
</target>
<target name="filter_nino" extends="common">
<root class="FILTER_SERVER" feature="make"/>
<library name="default_nino" location="..\..\..\..\library\server\obsolete\v0\wsf\default\nino-safe.ecf" readonly="true"/>
<cluster name="filter" location="src\" recursive="true"/>
</target>
<target name="filter_fcgi" extends="common">
<root class="FILTER_SERVER" feature="make"/>
<library name="default_libfcgi" location="..\..\..\..\library\server\obsolete\v0\wsf\default\libfcgi-safe.ecf"/>
<cluster name="filter" location="src\" recursive="true"/>
</target>
<target name="filter" extends="filter_nino">
</target>
</system>

View File

@@ -0,0 +1,6 @@
#include <windows.h>
STRINGTABLE
BEGIN
1 "This Program was made using EiffelStudio using Visual Studio C++"
END

View File

@@ -0,0 +1,4 @@
${NOTE_KEYWORD}
copyright: "2011-${YEAR}, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"

View File

@@ -0,0 +1,4 @@
Filter example
To test the example, you can just run in a terminal:
> curl -u foo:bar http://localhost:9090/user/1 -v

View File

@@ -0,0 +1,57 @@
note
description: "Summary description for {DATABASE_API}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
DATABASE_API
create
make
feature -- Initialization
make
local
l_user: USER
do
create users.make (10)
create l_user.make (1, "foo", "bar")
users.put (l_user, l_user.id)
create l_user.make (2, "demo", "demo")
users.put (l_user, l_user.id)
end
feature -- Access
user (a_id: INTEGER; a_name: detachable READABLE_STRING_GENERAL): detachable USER
-- User with id `a_id' or name `a_name'.
require
a_id > 0 xor a_name /= Void
local
n: like {USER}.name
do
if a_id > 0 then
Result := users.item (a_id)
elseif a_name /= Void then
n := a_name.as_string_8
across
users as c
until
Result /= Void
loop
if attached c.item as u and then u.name.same_string (n) then
Result := u
end
end
end
ensure
Result /= Void implies ((a_id > 0 and then Result.id = a_id) xor (a_name /= Void and then Result.name.same_string_general (a_name)))
end
users: HASH_TABLE [USER, INTEGER]
;note
copyright: "2011-2012, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,20 @@
note
description: "Summary description for {SHARED_DATABASE_API}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
SHARED_DATABASE_API
feature -- Access
db_access: DATABASE_API
once
create Result.make
end
note
copyright: "2011-2012, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,52 @@
note
description: "JSON user converter."
author: "Olivier Ligot"
date: "$Date$"
revision: "$Revision$"
class
JSON_USER_CONVERTER
inherit
JSON_CONVERTER
create
make
feature {NONE} -- Initialization
make
do
create object.make (0, "", "")
end
feature -- Access
object: USER
value: detachable JSON_OBJECT
feature -- Conversion
from_json (j: attached like value): detachable like object
-- Convert from JSON value.
do
end
to_json (o: like object): like value
-- Convert to JSON value.
do
create Result.make
Result.put (json.value (o.id), id_key)
Result.put (json.value (o.name), name_key)
end
feature {NONE} -- Implementation
id_key: STRING = "id"
name_key: STRING = "name"
note
copyright: "2011-2012, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,59 @@
note
description: "User."
author: ""
date: "$Date$"
revision: "$Revision$"
class
USER
inherit
ANY
redefine
is_equal
end
create
make
feature {NONE} -- Initialization
make (an_id: INTEGER; a_name, a_password: STRING)
do
id := an_id
name := a_name
password := a_password
ensure
id_set: id = an_id
name_set: name = a_name
password_set: password = a_password
end
feature -- Access
id: INTEGER
-- Identifier
name: STRING
-- Name
password: STRING
-- Password
feature -- Comparison
is_equal (other: like Current): BOOLEAN
-- Is `other' attached to an object considered
-- equal to current object?
do
if Current = other then
Result := True
else
Result := (id = other.id) and (name = other.name) and (password = other.password)
end
end
;note
copyright: "2011-2012, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,65 @@
note
description: "Authentication filter."
author: "Olivier Ligot"
date: "$Date$"
revision: "$Revision$"
class
AUTHENTICATION_FILTER
inherit
WSF_FILTER
WSF_URI_TEMPLATE_HANDLER
SHARED_DATABASE_API
SHARED_EJSON
feature -- Basic operations
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute the filter
local
l_auth: detachable HTTP_AUTHORIZATION
do
if attached req.http_authorization as l_http_authorization then
create l_auth.make (l_http_authorization)
end
if
l_auth /= Void and then
l_auth.is_basic and then
attached l_auth.login as l_auth_login and then
attached Db_access.user (0, l_auth_login) as l_user and then
l_auth_login.same_string (l_user.name) and then
attached l_auth.password as l_auth_password and then
l_auth_password.same_string (l_user.password)
then
req.set_execution_variable ("user", l_user)
execute_next (req, res)
else
handle_unauthorized ("Unauthorized", req, res)
end
end
feature {NONE} -- Implementation
handle_unauthorized (a_description: STRING; req: WSF_REQUEST; res: WSF_RESPONSE)
-- Handle forbidden.
local
h: HTTP_HEADER
do
create h.make
h.put_content_type_text_plain
h.put_content_length (a_description.count)
h.put_current_date
h.put_header_key_value ({HTTP_HEADER_NAMES}.header_www_authenticate, "Basic realm=%"User%"")
res.set_status_code ({HTTP_STATUS_CODE}.unauthorized)
res.put_header_text (h.string)
res.put_string (a_description)
end
note
copyright: "2011-2014, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,34 @@
note
description: "Summary description for {FILTER_HANDLER_CONTEXT}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
FILTER_HANDLER_CONTEXT
inherit
WSF_HANDLER_CONTEXT
create
make
feature -- Access
user: detachable USER
-- Authenticated user
feature -- Element change
set_user (a_user: USER)
-- Set `user' to `a_user'
do
user := a_user
ensure
user_set: user = a_user
end
note
copyright: "2011-2012, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,111 @@
note
description : "Filter example."
author : "Olivier Ligot"
date : "$Date$"
revision : "$Revision$"
class
FILTER_SERVER
inherit
ANY
WSF_DEFAULT_SERVICE
WSF_ROUTED_SERVICE
undefine
execute
end
WSF_FILTERED_SERVICE
SHARED_EJSON
create
make
feature {NONE} -- Initialization
make
local
l_message: STRING
l_factory: INET_ADDRESS_FACTORY
do
initialize_router
initialize_filter
initialize_json
set_service_option ("port", port)
create l_message.make_empty
l_message.append_string ("Launching filter server at ")
create l_factory
l_message.append_string (l_factory.create_localhost.host_name)
l_message.append_string (" port ")
l_message.append_integer (port)
io.put_string (l_message)
io.put_new_line
make_and_launch
end
create_filter
-- Create `filter'
do
create {WSF_CORS_FILTER} filter
end
setup_filter
-- Setup `filter'
local
l_routing_filter: WSF_ROUTING_FILTER
l_logging_filter: WSF_LOGGING_FILTER
do
create l_routing_filter.make (router)
l_routing_filter.set_execute_default_action (agent execute_default)
filter.set_next (l_routing_filter)
create l_logging_filter
l_routing_filter.set_next (l_logging_filter)
end
setup_router
-- Setup `router'
local
l_options_filter: WSF_CORS_OPTIONS_FILTER
l_authentication_filter: AUTHENTICATION_FILTER
l_user_filter: USER_HANDLER
l_methods: WSF_REQUEST_METHODS
do
create l_options_filter.make (router)
create l_authentication_filter
create l_user_filter
l_options_filter.set_next (l_authentication_filter)
l_authentication_filter.set_next (l_user_filter)
create l_methods
l_methods.enable_options
l_methods.enable_get
router.handle_with_request_methods ("/user/{userid}", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent l_options_filter.execute), l_methods)
end
initialize_json
-- Initialize `json'.
do
json.add_converter (create {JSON_USER_CONVERTER}.make)
end
feature {NONE} -- Implementation
port: INTEGER = 9090
-- Port number
note
copyright: "2011-2014, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,97 @@
note
description: "User handler."
author: "Olivier Ligot"
date: "$Date$"
revision: "$Revision$"
class
USER_HANDLER
inherit
WSF_FILTER
WSF_URI_TEMPLATE_HANDLER
WSF_RESOURCE_HANDLER_HELPER
redefine
do_get
end
SHARED_DATABASE_API
SHARED_EJSON
feature -- Basic operations
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute request handler
do
execute_methods (req, res)
execute_next (req, res)
end
do_get (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Using GET to retrieve resource information.
-- If the GET request is SUCCESS, we response with
-- 200 OK, and a representation of the user
-- If the GET request is not SUCCESS, we response with
-- 404 Resource not found
require else
authenticated_user_attached: attached {USER} req.execution_variable ("user")
local
id : STRING
do
if attached req.orig_path_info as orig_path then
id := get_user_id_from_path (orig_path)
if attached retrieve_user (id) as l_user then
if l_user ~ req.execution_variable ("user") then
compute_response_get (req, res, l_user)
elseif attached {USER} req.execution_variable ("user") as l_auth_user then
-- Trying to access another user that the authenticated one,
-- which is forbidden in this example...
handle_forbidden ("You try to access the user " + id.out + " while authenticating with the user " + l_auth_user.id.out, req, res)
end
else
handle_resource_not_found_response ("The following resource " + orig_path + " is not found ", req, res)
end
end
end
feature {NONE} -- Implementation
compute_response_get (req: WSF_REQUEST; res: WSF_RESPONSE; l_user : USER)
local
h: HTTP_HEADER
l_msg : STRING
do
create h.make
h.put_content_type_application_json
if attached {JSON_VALUE} json.value (l_user) as jv then
l_msg := jv.representation
h.put_content_length (l_msg.count)
if attached req.request_time as time then
h.put_utc_date (time)
end
res.set_status_code ({HTTP_STATUS_CODE}.ok)
res.put_header_text (h.string)
res.put_string (l_msg)
end
end
get_user_id_from_path (a_path: READABLE_STRING_32): STRING
do
Result := a_path.split ('/').at (3)
end
retrieve_user (id: STRING) : detachable USER
-- Retrieve the user by id if it exist, in other case, Void
do
if id.is_integer and then Db_access.users.has (id.to_integer) then
Result := db_access.users.item (id.to_integer)
end
end
note
copyright: "2011-2013, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,3 @@
The current example has a main target for the server: "restbucks"
But we also provide "policy_driven_restbucks" target which is using the
policy-driven framework than help coder fulfill HTTP expectations.

View File

@@ -0,0 +1,11 @@
Make sure to have the Clib generated in the related cURL library
- if you use EiffelStudio >= 7.0
check %ISE_LIBRARY%\library\cURL\spec\%ISE_C_COMPILER%\$ISE_PLATFORM
or $ISE_LIBRARY/library/cURL/spec/$ISE_PLATFORM
- otherwise if you use earlier version
check under ext/ise_library/curl/spec/...
And on Windows, be sure to get the libcurl.dll from %ISE_LIBRARY%\studio\spec\%ISE_PLATFORM%\bin\libcurl.dll

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="client" uuid="D0059CEB-5F5C-4D21-8C71-842BD0F88468" library_target="client">
<target name="client">
<root class="RESTBUCK_CLIENT" feature="make"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="standard">
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http_client" location="..\..\..\..\..\library\network\http_client\http_client-safe.ecf" readonly="false"/>
<library name="json" location="..\..\..\..\..\contrib\library\text\parser\json\library\json-safe.ecf" readonly="false"/>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread-safe.ecf"/>
<cluster name="src" location=".\src\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-8-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-8-0 http://www.eiffel.com/developers/xml/configuration-1-8-0.xsd" name="client" uuid="D0059CEB-5F5C-4D21-8C71-842BD0F88468">
<target name="client">
<root class="RESTBUCK_CLIENT" feature="make"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" full_class_checking="true" void_safety="none" syntax="standard">
</option>
<setting name="concurrency" value="thread"/>
<library name="http_client" location="../../../../../library/network/http_client/http_client.ecf"/>
<library name="json" location="..\..\..\..\..\contrib\library\text\parser\json\library\json.ecf" readonly="false"/>
<library name="base" location="$ISE_LIBRARY/library/base/base.ecf"/>
<library name="thread" location="$ISE_LIBRARY/library/thread/thread.ecf"/>
<cluster name="src" location="./src" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,6 @@
#include <windows.h>
STRINGTABLE
BEGIN
1 "This Program was made using EiffelStudio using Visual Studio C++"
END

View File

@@ -0,0 +1,154 @@
note
description : "Objects that ..."
author : "$Author$"
date : "$Date$"
revision : "$Revision$"
class
RESTBUCK_CLIENT
create
make
feature {NONE} -- Initialization
make
-- Initialize `Current'.
local
h: LIBCURL_HTTP_CLIENT
sess: HTTP_CLIENT_SESSION
resp : detachable HTTP_CLIENT_RESPONSE
l_location : detachable READABLE_STRING_8
body : STRING
do
create h.make
sess := h.new_session ("http://127.0.0.1:9090")
-- Uncomment the following 2 lines, if you use fiddler2 web debugging tool
-- sess.set_is_debug (True)
-- sess.set_proxy ("127.0.0.1", 8888)
-- Create Order
print ("%N Create Order %N")
resp := create_order (sess)
-- Read the Order
print ("%N Read Order %N")
l_location := resp.header ("Location")
resp := read_order (sess, l_location)
-- Update the Order
if resp /= Void and then attached resp.body as l_body then
body := l_body.as_string_8
body.replace_substring_all ("takeAway", "in Shop")
print ("%N Update Order %N")
resp := update_order (sess, l_location, body)
end
end
update_order ( sess: HTTP_CLIENT_SESSION; uri : detachable READABLE_STRING_8; a_body : STRING): detachable HTTP_CLIENT_RESPONSE
local
context : HTTP_CLIENT_REQUEST_CONTEXT
do
if attached uri as l_uri then
sess.set_base_url (l_uri)
create context.make
context.headers.put ("application/json", "Content-Type")
Result := sess.put ("", context, a_body )
-- Show headers
across
Result.headers as l_headers
loop
print (l_headers.item.name)
print (":")
print (l_headers.item.value)
io.put_new_line
end
-- Show body
print (Result.body)
io.put_new_line
end
end
read_order ( sess: HTTP_CLIENT_SESSION; uri : detachable READABLE_STRING_8): detachable HTTP_CLIENT_RESPONSE
do
if attached uri as l_uri then
sess.set_base_url (l_uri)
Result := sess.get ("", Void)
-- Show headers
across
Result.headers as l_headers
loop
print (l_headers.item.name)
print (":")
print (l_headers.item.value)
io.put_new_line
end
-- Show body
print (Result.body)
io.put_new_line
end
end
create_order (sess: HTTP_CLIENT_SESSION) : HTTP_CLIENT_RESPONSE
local
s: READABLE_STRING_8
j: JSON_PARSER
id: detachable STRING
context : HTTP_CLIENT_REQUEST_CONTEXT
do
s := "[
{
"location":"takeAway",
"items":[
{
"name":"Late",
"option":"skim",
"size":"Small",
"quantity":1
}
]
}
]"
create context.make
context.headers.put ("application/json", "Content-Type")
Result := sess.post ("/order", context, s)
-- Show the Headers
across
Result.headers as l_headers
loop
print (l_headers.item.name)
print (":")
print (l_headers.item.value)
io.put_new_line
end
-- Show the Response body
if attached Result.body as m then
create j.make_with_string (m)
j.parse_content
if j.is_valid and then attached j.parsed_json_object as j_o then
if attached {JSON_STRING} j_o.item ("id") as l_id then
id := l_id.item
end
print (m)
io.put_new_line
end
end
end
feature {NONE} -- Implementation
invariant
-- invariant_clause: True
end

View File

@@ -0,0 +1,4 @@
${NOTE_KEYWORD}
copyright: "2011-${YEAR}, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"

View File

@@ -0,0 +1,297 @@
Restbuck Eiffel Implementation based on the book of REST in Practice
====================================================================
This is an implementation of CRUD pattern for manipulate resources, this is the first step to use
the HTTP protocol as an application protocol instead of a transport protocol.
Restbuck Protocol
-----------------
<table>
<TR><TH>Verb</TH> <TH>URI or template</TH> <TH>Use</TH></TR>
<TR><TD>POST</TD> <TD>/order</TD> <TD>Create a new order, and upon success, receive a Locationheader specifying the new order's URI.</TD></TR>
<TR><TD>GET</TD> <TD>/order/{orderId}</TD> <TD>Request the current state of the order specified by the URI.</TD></TR>
<TR><TD>PUT</TD> <TD>/order/{orderId}</TD> <TD>Update an order at the given URI with new information, providing the full representation.</TD></TR>
<TR><TD>DELETE</TD> <TD>/order/{orderId}</TD> <TD>Logically remove the order identified by the given URI.</TD></TR>
</table>
Resource Represenation
----------------------
The previous tables shows a contrat, the URI or URI template, allows us to indentify resources, now we will chose a
representacion, for this particular case we will use JSON.
Note: <br/>
1. *A resource can have multiple URIs*.<br/>
2. *A resource can have multiple Representations*.<br/>
RESTBUCKS_SERVER
----------------
This class implement the main entry of our REST CRUD service, we are using a default connector (Nino Connector,
using a WebServer written in Eiffel).
We are inheriting from URI_TEMPLATE_ROUTED_SERVICE, this allows us to map our service contrat, as is shown in the previous
table, the mapping is defined in the feature setup_router, this also show that the class ORDER_HANDLER will be encharge
of to handle different type of request to the ORDER resource.
class
RESTBUCKS_SERVER
inherit
ANY
URI_TEMPLATE_ROUTED_SERVICE
DEFAULT_SERVICE
-- Here we are using a default connector using the default Nino Connector,
-- but it's possible to use other connector (CGI or FCGI).
create
make
feature {NONE} -- Initialization
make
-- Initialize the router (this will have the request handler and
-- their context).
do
initialize_router
make_and_launch
end
create_router
do
create router.make (2)
end
setup_router
local
order_handler: ORDER_HANDLER [REQUEST_URI_TEMPLATE_HANDLER_CONTEXT]
do
create order_handler
router.map_with_request_methods ("/order", order_handler, <<"POST">>)
router.map_with_request_methods ("/order/{orderid}", order_handler, <<"GET", "DELETE", "PUT">>)
end
feature -- Execution
execute_default (req: WSF_REQUEST; res: WSF_RESPONSE)
-- I'm using this method to handle the method not allowed response
-- in the case that the given uri does not have a corresponding http method
-- to handle it.
local
h : HTTP_HEADER
l_description : STRING
l_api_doc : STRING
do
if req.content_length_value > 0 then
req.input.read_string (req.content_length_value.as_integer_32)
end
create h.make
h.put_status ({HTTP_STATUS_CODE}.method_not_allowed)
h.put_content_type_text_plain
l_api_doc := "%NPlease check the API%NURI:/order METHOD: POST%NURI:/order/{orderid} METHOD: GET, PUT, DELETE%N"
l_description := req.request_method + req.request_uri + " is not allowed" + "%N" + l_api_doc
h.put_content_length (l_description.count)
h.put_current_date
res.set_status_code ({HTTP_STATUS_CODE}.method_not_allowed)
res.write_header_text (h.string)
res.write_string (l_description)
end
end
How to Create an order with POST
--------------------------------
Here is the convention that we are using:
POST is used for creation and the server determines the URI of the created resource.
If the request POST is SUCCESS, the server will create the order and will response with
201 CREATED, the Location header will contains the newly created order's URI,
if the request POST is not SUCCESS, the server will response with
400 BAD REQUEST, the client send a bad request or
500 INTERNAL_SERVER_ERROR, when the server can deliver the request.
POST /order HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Content-Length: 196
Origin: chrome-extension://fhjcajmcbmldlhcimfajhfbgofnpcjmb
Content-Type: application/json
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: es-419,es;q=0.8,en;q=0.6
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
{
"location":"takeAway",
"items":[
{
"name":"Late",
"option":"skim",
"size":"Small",
"quantity":1
}
]
}
Response success
HTTP/1.1 201 Created
Status 201 Created
Content-Type application/json
Content-Length 123
Location http://localhost:8080/order/1
Date FRI,09 DEC 2011 20:34:20.00 GMT
{
"location" : "takeAway",
"status" : "submitted",
"items" : [ {
"name" : "late",
"size" : "small",
"quantity" : 1,
"option" : "skim"
} ]
}
note:
curl -vv http://localhost:9090/order -H "Content-Type: application/json" -d "{\"location\":\"takeAway\",\"items\":[{\"name\":\"Late\",\"option\":\"skim\",\"size\":\"Small\",\"quantity\":1}]}" -X POST
How to Read an order with GET
-----------------------------
Using GET to retrieve resource information.
If the GET request is SUCCESS, we response with 200 OK, and a representation of the order
If the GET request is not SUCCESS, we response with 404 Resource not found
If is a Conditional GET and the resource does not change we send a 304, Resource not modifed
GET /order/1 HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: es-419,es;q=0.8,en;q=0.6
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
If-None-Match: 6542EF270D91D3EAF39CFB382E4CEBA7
Response
HTTP/1.1 200 OK
Status 200 OK
Content-Type application/json
Content-Length 123
Date FRI,09 DEC 2011 20:53:46.00 GMT
etag 2ED3A40954A95D766FC155682DC8BB52
{
"location" : "takeAway",
"status" : "submitted",
"items" : [ {
"name" : "late",
"size" : "small",
"quantity" : 1,
"option" : "skim"
} ]
}
note:
curl -vv http://localhost:9090/order/1
How to Update an order with PUT
-------------------------------
A successful PUT request will not create a new resource, instead it will change the state of the resource identified by the current uri.
If success we response with 200 and the updated order.
404 if the order is not found
400 in case of a bad request
500 internal server error
If the request is a Conditional PUT, and it does not mat we response 415, precondition failed.
Suposse that we had created an Order with the values shown in the _How to create an order with POST_
But we change our decision and we want to stay in the shop.
PUT /order/1 HTTP/1.1
Content-Length: 122
Content-Type: application/json; charset=UTF-8
Host: localhost:8080
Connection: Keep-Alive
Expect: 100-Continue
{
"location" : "in shop",
"status" : "submitted",
"items" : [ {
"name" : "late",
"size" : "small",
"quantity" : 1,
"option" : "skim"
} ]
}
Response success
HTTP/1.1 200 OK
Status 200 OK
Content-Type application/json
Date FRI,09 DEC 2011 21:06:26.00 GMT
etag 8767F900674B843E1F3F70BCF3E62403
Content-Length 122
{
"location" : "in shop",
"status" : "submitted",
"items" : [ {
"name" : "late",
"size" : "small",
"quantity" : 1,
"option" : "skim"
} ]
}
How to Delete an order with DELETE
----------------------------------
Here we use DELETE to cancel an order, if that order is in state where it can still be canceled.
204 if is ok
404 Resource not found
405 if consumer and service's view of the resouce state is inconsisent
500 if we have an internal server error
DELETE /order/1 HTTP/1.1
Host: localhost:8080
Connection: Keep-Alive
Response success
HTTP/1.1 204 No Content
Status 204 No Content
Content-Type application/json
Date FRI,09 DEC 2011 21:10:51.00 GMT
If we want to check that the resource does not exist anymore we can try to retrieve a GET /order/1 and we will receive a
404 No Found
GET /order/1 HTTP/1.1
Host: localhost:8080
Connection: Keep-Alive
Response
HTTP/1.1 404 Not Found
Status 404 Not Found
Content-Type application/json
Content-Length 44
Date FRI,09 DEC 2011 21:14:17.79 GMT
The following resource/order/1 is not found
References
----------
1. [How to get a cup of coffe](http://www.infoq.com/articles/webber-rest-workflow)
2. [Rest in Practice] (http://restinpractice.com/default.aspx)

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="restbucks" uuid="2773FEAA-448F-410E-BEDE-9298C4749066" library_target="restbucks">
<target name="restbucks_common">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/\.git$</exclude>
<exclude>/\.svn$</exclude>
</file_rule>
<option full_class_checking="false" void_safety="all">
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="connector_nino" location="..\..\..\..\library\server\obsolete\v0\ewsgi\connectors\nino\nino-safe.ecf" readonly="false">
<option debug="true">
<debug name="nino" enabled="true"/>
</option>
</library>
<library name="conneg" location="..\..\..\..\library\network\protocol\content_negotiation\conneg-safe.ecf"/>
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto-safe.ecf" readonly="false"/>
<library name="default_nino" location="..\..\..\..\library\server\obsolete\v0\wsf\default\nino-safe.ecf" readonly="false"/>
<library name="encoder" location="..\..\..\..\library\text\encoder\encoder-safe.ecf" readonly="false"/>
<library name="http" location="..\..\..\..\library\network\protocol\http\http-safe.ecf" readonly="false"/>
<library name="json" location="..\..\..\..\contrib\library\text\parser\json\library\json-safe.ecf" readonly="false"/>
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
<library name="uri_template" location="..\..\..\..\library\text\parser\uri_template\uri_template-safe.ecf" readonly="false"/>
<library name="wsf" location="..\..\..\..\library\server\obsolete\v0\wsf\wsf-safe.ecf" readonly="false"/>
<library name="wsf_extension" location="..\..\..\..\library\server\obsolete\v0\wsf\wsf_extension-safe.ecf" readonly="false"/>
</target>
<target name="restbucks" extends="restbucks_common">
<root class="RESTBUCKS_SERVER" feature="make"/>
<option debug="true" warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="transitional" syntax="provisional">
<debug name="nino" enabled="true"/>
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<cluster name="src" location="src\" recursive="true">
<file_rule>
<exclude>/policy_driven_resource$</exclude>
</file_rule>
</cluster>
</target>
<target name="policy_driven_restbucks" extends="restbucks_common">
<root class="RESTBUCKS_SERVER" feature="make"/>
<option debug="true" warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="transitional" syntax="provisional">
<debug name="nino" enabled="true"/>
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="wsf_policy_driven" location="..\..\..\..\library\server\obsolete\v0\wsf\wsf_policy_driven-safe.ecf" readonly="false"/>
<cluster name="src" location="src\" recursive="true">
<file_rule>
<exclude>/resource$</exclude>
</file_rule>
</cluster>
</target>
</system>

View File

@@ -0,0 +1,6 @@
#include <windows.h>
STRINGTABLE
BEGIN
1 "This Program was made using EiffelStudio using Visual Studio C++"
END

View File

@@ -0,0 +1,26 @@
note
description: "Summary description for {DATABASE_API}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
DATABASE_API
create
make
feature -- Initialization
make
do
create orders.make (10)
end
feature -- Access
orders: HASH_TABLE [ORDER, STRING]
;note
copyright: "2011-2012, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,19 @@
note
description: "Summary description for {SHARED_DATABASE_API}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
SHARED_DATABASE_API
feature -- Access
db_access: DATABASE_API
once
create Result.make
end
note
copyright: "2011-2012, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,90 @@
note
description: "Summary description for {ITEM}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
ITEM
inherit
ITEM_CONSTANTS
create
make
feature -- Initialization
make ( a_name : STRING_32 ; a_size:STRING_32; a_option: STRING_32; a_quantity:INTEGER_8)
do
set_name (a_name)
set_size (a_size)
set_option (a_option)
set_quantity (a_quantity)
end
feature -- Access
name : STRING
-- product name type of Coffee(Late, Cappuccino, Expresso)
option : STRING
-- customization option Milk (skim, semi, whole)
size : STRING
-- small, mediumm large
quantity :INTEGER
feature -- Element Change
set_name (a_name: STRING)
require
valid_name: is_valid_coffee_type (a_name)
do
name := a_name
ensure
name_assigned : name.same_string(a_name)
end
set_size (a_size: STRING)
require
valid_size : is_valid_size_option (a_size)
do
size := a_size
ensure
size_assigned : size.same_string(a_size)
end
set_option (an_option: STRING)
require
valid_option : is_valid_milk_type (an_option)
do
option := an_option
ensure
option_assigned : option.same_string (an_option)
end
set_quantity (a_quantity: INTEGER)
require
valid_quantity : a_quantity > 0
do
quantity := a_quantity
ensure
quantity_assigned : quantity = a_quantity
end
feature -- Report
hash_code: INTEGER
--Hash code value
do
Result := option.hash_code + name.hash_code + size.hash_code + quantity.hash_code
end
invariant
valid_size : is_valid_size_option (size)
valid_coffe : is_valid_coffee_type (name)
valid_customization : is_valid_milk_type (option)
valid_quantity : quantity > 0
note
copyright: "2011-2012, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,54 @@
note
description: "Summary description for {ITEM_CONSTANTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
ITEM_CONSTANTS
feature -- Access
is_valid_coffee_type (a_type: STRING) : BOOLEAN
--is `a_type' a valid coffee type
do
a_type.to_lower
coffe_types.compare_objects
Result := coffe_types.has (a_type)
end
Coffe_types : ARRAY[STRING]
-- List of valid Coffee types
once
Result := <<"late","cappuccino", "expresso">>
end
is_valid_milk_type (a_type: STRING) : BOOLEAN
--is `a_type' a valid milk type
do
a_type.to_lower
milk_types.compare_objects
Result := milk_types.has (a_type)
end
Milk_types : ARRAY[STRING]
-- List of valid Milk types
once
Result := <<"skim","semi", "whole">>
end
is_valid_size_option (an_option: STRING) : BOOLEAN
--is `an_option' a valid size option
do
an_option.to_lower
size_options.compare_objects
Result := size_options.has (an_option)
end
Size_options : ARRAY[STRING]
-- List of valid Size_options
once
Result := <<"small","mediumn", "large">>
end
note
copyright: "2011-2012, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,176 @@
note
description: "Summary description for {JSON_ORDER_CONVERTER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
JSON_ORDER_CONVERTER
inherit
JSON_CONVERTER
create
make
feature -- Initialization
make
do
create object.make ("","","")
end
feature -- Access
object : ORDER
value : detachable JSON_OBJECT
feature -- Conversion
from_json (j: attached like value): detachable like object
-- Convert from JSON value. Returns Void if unable to convert
local
s_id, s_location, s_status: detachable STRING_32
q: INTEGER_8
o: ORDER
i : ITEM
l_array : detachable ARRAYED_LIST [JSON_VALUE]
is_valid_from_json : BOOLEAN
do
is_valid_from_json := True
if attached {STRING_32} json.object (j.item (id_key), Void) as l_id then
s_id := s_id
end
if attached {STRING_32} json.object (j.item (location_key), Void) as l_location then
s_location := l_location
end
if attached {STRING_32} json.object (j.item (status_key), Void) as l_status then
s_status := l_status
end
create o.make ("", s_location, s_status)
if attached {JSON_ARRAY} j.item (items_key) as l_val then
l_array := l_val.array_representation
from
l_array.start
until
l_array.after
loop
if attached {JSON_OBJECT} l_array.item_for_iteration as jv then
if attached {INTEGER_8} json.object (jv.item (quantity_key), Void) as l_integer then
q := l_integer
else
q := 0
end
if
attached {STRING_32} json.object (jv.item (name_key), Void) as s_name and then
attached {STRING_32} json.object (jv.item (size_key), Void) as s_key and then
attached {STRING_32} json.object (jv.item (option_key), Void) as s_option
then
if is_valid_item_customization (s_name, s_key, s_option,q) then
create i.make (s_name, s_key, s_option, q)
o.add_item (i)
else
is_valid_from_json := False
end
else
is_valid_from_json := False
end
end
l_array.forth
end
end
if not is_valid_from_json or o.items.is_empty then
Result := Void
else
Result := o
end
end
to_json (o: like object): like value
-- Convert to JSON value
local
ja : JSON_ARRAY
i : ITEM
jv: JSON_OBJECT
do
create Result.make
-- Result.put (json.value (o.id), id_key)
Result.put (json.value (o.location), location_key)
Result.put (json.value (o.status), status_key)
from
create ja.make_empty
o.items.start
until
o.items.after
loop
i := o.items.item_for_iteration
create jv.make
jv.put (json.value (i.name), name_key)
jv.put (json.value (i.size),size_key)
jv.put (json.value (i.quantity), quantity_key)
jv.put (json.value (i.option), option_key)
ja.add (jv)
o.items.forth
end
Result.put (ja, items_key)
end
feature {NONE} -- Implementation
id_key: JSON_STRING
once
create Result.make_from_string ("id")
end
location_key: JSON_STRING
once
create Result.make_from_string ("location")
end
status_key: JSON_STRING
once
create Result.make_from_string ("status")
end
items_key : JSON_STRING
once
create Result.make_from_string ("items")
end
name_key : JSON_STRING
once
create Result.make_from_string ("name")
end
size_key : JSON_STRING
once
create Result.make_from_string ("size")
end
quantity_key : JSON_STRING
once
create Result.make_from_string ("quantity")
end
option_key : JSON_STRING
once
create Result.make_from_string ("option")
end
feature -- Validation
is_valid_item_customization ( name : STRING_32; size: STRING_32; option : STRING_32; quantity : INTEGER_8 ) : BOOLEAN
local
ic : ITEM_CONSTANTS
do
create ic
Result := ic.is_valid_coffee_type (name) and ic.is_valid_milk_type (option) and ic.is_valid_size_option (size) and quantity > 0
end
note
copyright: "2011-2015, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,114 @@
note
description: "Summary description for {ORDER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
ORDER
create
make
feature -- Initialization
make ( an_id : detachable STRING_32; a_location: detachable STRING_32; a_status: detachable STRING_32)
do
create {ARRAYED_LIST [ITEM]} items.make (10)
if an_id /= Void then
set_id (an_id)
else
set_id ("")
end
if a_location /= Void then
set_location (a_location)
else
set_location ("")
end
if a_status /= Void then
set_status (a_status)
else
set_status ("")
end
revision := 0
end
feature -- Access
id : STRING_32
location : STRING_32
items: LIST[ITEM]
status : STRING_32
revision : INTEGER
feature -- element change
set_id (an_id : STRING_32)
do
id := an_id
ensure
id_assigned : id.same_string (an_id)
end
set_location (a_location : STRING_32)
do
location := a_location
ensure
location_assigned : location.same_string (a_location)
end
set_status (a_status : STRING_32)
do
status := a_status
ensure
status_asigned : status.same_string (a_status)
end
add_item (a_item : ITEM)
require
valid_item: a_item /= Void
do
items.force (a_item)
ensure
has_item : items.has (a_item)
end
add_revision
do
revision := revision + 1
ensure
revision_incremented : old revision + 1 = revision
end
feature -- Etag
etag : STRING_32
-- Etag generation for Order objects
do
Result := hash_code.out + revision.out
end
feature -- Output
feature -- Report
hash_code: INTEGER_32
-- Hash code value
do
from
items.start
Result := items.item.hash_code
until
items.off
loop
Result:= ((Result \\ 8388593) |<< 8) + items.item.hash_code
items.forth
end
if items.count > 1 then
Result := Result \\ items.count
end
end
note
copyright: "2011-2012, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,56 @@
note
description: "Summary description for {ORDER_TRANSITIONS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
ORDER_VALIDATION
feature -- Access
is_valid_status_state (a_status: STRING) : BOOLEAN
--is `a_status' a valid coffee order state
do
a_status.to_lower
Order_states.compare_objects
Result := Order_states.has (a_status)
end
Order_states : ARRAY[STRING]
-- List of valid status states
once
Result := <<"submitted","pay","payed", "cancel","canceled","prepare","prepared","deliver","completed">>
end
is_valid_transition (order:ORDER a_status : STRING) :BOOLEAN
-- Given the current order state, determine if the transition is valid
do
a_status.to_lower
if order.status.same_string ("submitted") then
Result := a_status.same_string ("pay") or a_status.same_string ("cancel") or order.status.same_string (a_status)
elseif order.status.same_string ("pay") then
Result := a_status.same_string ("payed") or order.status.same_string (a_status)
elseif order.status.same_string ("cancel") then
Result := a_status.same_string ("canceled") or order.status.same_string (a_status)
elseif order.status.same_string ("payed") then
Result := a_status.same_string ("prepared") or order.status.same_string (a_status)
elseif order.status.same_string ("prepared") then
Result := a_status.same_string ("deliver") or order.status.same_string (a_status)
elseif order.status.same_string ("deliver") then
Result := a_status.same_string ("completed") or order.status.same_string (a_status)
end
end
is_state_valid_to_update ( a_status : STRING) : BOOLEAN
-- Given the current state `a_status' of an order, is possible to update the order?
do
if a_status.same_string ("submitted") or else a_status.same_string ("pay") or else a_status.same_string ("payed") then
Result := true
end
end
note
copyright: "2011-2012, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,19 @@
note
description: "Summary description for {SHARED_ORDER_VALIDATION}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
SHARED_ORDER_VALIDATION
feature
order_validation : ORDER_VALIDATION
once
create Result
end
note
copyright: "2011-2012, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,574 @@
note
description: "{ORDER_HANDLER} handle the resources that we want to expose"
author: ""
date: "$Date$"
revision: "$Revision$"
class ORDER_HANDLER
inherit
WSF_SKELETON_HANDLER
SHARED_DATABASE_API
SHARED_EJSON
REFACTORING_HELPER
SHARED_ORDER_VALIDATION
WSF_RESOURCE_HANDLER_HELPER
rename
execute_options as helper_execute_options,
handle_internal_server_error as helper_handle_internal_server_error
end
create
make_with_router
feature -- Execution variables
Order_execution_variable: STRING = "ORDER"
-- Execution variable used by application
Generated_content_execution_variable: STRING = "GENERATED_CONTENT"
-- Execution variable used by application
Extracted_order_execution_variable: STRING = "EXTRACTED_ORDER"
-- Execution variable used by application
feature -- Documentation
description: READABLE_STRING_GENERAL
-- General description for self-generated documentation;
-- The specific URI templates supported will be described automatically
do
Result := "Create, Read, Update or Delete an ORDER."
end
feature -- Access
is_chunking (req: WSF_REQUEST): BOOLEAN
-- Will the response to `req' using chunked transfer encoding?
do
-- No.
end
includes_response_entity (req: WSF_REQUEST): BOOLEAN
-- Does the response to `req' include an entity?
-- Method will be DELETE, POST, PUT or an extension method.
do
Result := False
-- At present, there is no support for this except for DELETE.
end
conneg (req: WSF_REQUEST): SERVER_CONTENT_NEGOTIATION
-- Content negotiatior for all requests
once
create Result.make ({HTTP_MIME_TYPES}.application_json, "en", "UTF-8", "identity")
end
mime_types_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept header that `Current' can serve
do
create {ARRAYED_LIST [STRING]} Result.make_from_array (<<{HTTP_MIME_TYPES}.application_json>>)
Result.compare_objects
end
languages_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept-Language header that `Current' can serve
do
create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"en">>)
Result.compare_objects
end
charsets_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept-Charset header that `Current' can serve
do
create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"UTF-8">>)
Result.compare_objects
end
encodings_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept-Encoding header that `Current' can serve
do
create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"identity">>)
Result.compare_objects
end
max_age (req: WSF_REQUEST): NATURAL
-- Maximum age in seconds before response to `req` is considered stale;
-- This is used to generate a Cache-Control: max-age header.
-- Return 0 to indicate already expired.
-- Return Never_expires to indicate never expires.
do
-- All our responses are considered stale.
end
is_freely_cacheable (req: WSF_REQUEST): BOOLEAN
-- Should the response to `req' be freely cachable in shared caches?
-- If `True', then a Cache-Control: public header will be generated.
do
-- definitely not!
end
private_headers (req: WSF_REQUEST): detachable LIST [READABLE_STRING_8]
-- Header names intended for a single user.
-- If non-Void, then a Cache-Control: private header will be generated.
-- Returning an empty list prevents the entire response from being served from a shared cache.
do
create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (0)
end
non_cacheable_headers (req: WSF_REQUEST): detachable LIST [READABLE_STRING_8]
-- Header names that will not be sent from a cache without revalidation;
-- If non-Void, then a Cache-Control: no-cache header will be generated.
-- Returning an empty list prevents the response being served from a cache
-- without revalidation.
do
create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (0)
end
is_sensitive (req: WSF_REQUEST): BOOLEAN
-- Is the response to `req' of a sensitive nature?
-- If `True' then a Cache-Control: no-store header will be generated.
do
Result := True
-- since it's commercial data.
end
allowed_cross_origins (req: WSF_REQUEST): detachable STRING
-- Value for Access-Control-Allow-Origin header;
-- If supplied, should be a single URI, or the values "*" or "null".
-- This is currently supported only for GET requests, and POSTs that functions as GET.
do
if req.is_get_head_request_method then
Result := "*"
end
end
matching_etag (req: WSF_REQUEST; a_etag: READABLE_STRING_32; a_strong: BOOLEAN): BOOLEAN
-- Is `a_etag' a match for resource requested in `req'?
-- If `a_strong' then the strong comparison function must be used.
local
l_id: STRING
l_etag_util: ETAG_UTILS
do
l_id := order_id_from_request (req)
if db_access.orders.has_key (l_id) then
check attached db_access.orders.item (l_id) as l_order then
-- postcondition of `has_key'
create l_etag_util
Result := a_etag.same_string (l_etag_util.md5_digest (l_order.out).as_string_32)
end
end
end
etag (req: WSF_REQUEST): detachable READABLE_STRING_8
-- Optional Etag for `req' in the requested variant
local
l_etag_utils: ETAG_UTILS
do
create l_etag_utils
if attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then
Result := l_etag_utils.md5_digest (l_order.out)
end
end
last_modified (req: WSF_REQUEST): detachable DATE_TIME
-- When representation of resource selected in `req' was last modified;
-- SHOULD be set whenever it can reasonably be determined.
do
end
modified_since (req: WSF_REQUEST; a_date_time: DATE_TIME): BOOLEAN
-- Has resource requested in `req' been modified since `a_date_time' (UTC)?
do
-- We don't track this information. It is safe to always say yes.
Result := True
end
feature -- Measurement
content_length (req: WSF_REQUEST): NATURAL
-- Length of entity-body of the response to `req'
do
check attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) as l_response then
-- postcondition generated_content_set_for_get_head of `ensure_content_available'
-- We only call this for GET/HEAD in this example.
Result := l_response.count.as_natural_32
end
end
allow_post_to_missing_resource (req: WSF_REQUEST): BOOLEAN
-- The resource named in `req' does not exist, and this is a POST. Do we allow it?
do
-- No.
end
feature -- Status report
finished (req: WSF_REQUEST): BOOLEAN
-- Has the last chunk been generated for `req'?
do
-- precondition is never met
end
feature -- Execution
check_resource_exists (req: WSF_REQUEST; a_helper: WSF_METHOD_HELPER)
-- Call `a_helper.set_resource_exists' to indicate that `req.path_translated'
-- is the name of an existing resource.
-- We also put the order into `req.execution_variable (Order_execution_variable)' for GET or HEAD responses.
local
l_id: STRING
do
if req.is_post_request_method then
a_helper.set_resource_exists
-- because only /order is defined to this handler for POST
else
-- the request is of the form /order/{orderid}
l_id := order_id_from_request (req)
if db_access.orders.has_key (l_id) then
a_helper.set_resource_exists
if req.is_get_head_request_method then
check attached db_access.orders.item (l_id) as l_order then
-- postcondition `item_if_found' of `has_key'
req.set_execution_variable (Order_execution_variable, l_order)
end
end
end
end
ensure then
order_saved_only_for_get_head: attached {ORDER} req.execution_variable (Order_execution_variable) implies req.is_get_head_request_method
end
feature -- GET/HEAD content
ensure_content_available (req: WSF_REQUEST)
-- Commence generation of response text (entity-body).
-- If not chunked, then this will create the entire entity-body so as to be available
-- for a subsequent call to `content'.
-- If chunked, only the first chunk will be made available to `next_chunk'. If chunk extensions
-- are used, then this will also generate the chunk extension for the first chunk.
-- We save the text in `req.execution_variable (Generated_content_execution_variable)'
-- We ignore the results of content negotiation, as there is only one possible combination.
do
check attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then
-- precondition get_or_head and postcondition order_saved_only_for_get_head of `check_resource_exists' and
if attached {JSON_VALUE} json.value (l_order) as jv then
req.set_execution_variable (Generated_content_execution_variable, jv.representation)
else
req.set_execution_variable (Generated_content_execution_variable, "")
end
end
ensure then
generated_content_set_for_get_head: req.is_get_head_request_method implies
attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable)
end
content (req: WSF_REQUEST): READABLE_STRING_8
-- Non-chunked entity body in response to `req';
-- We only call this for GET/HEAD in this example.
do
check attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) as l_response then
-- postcondition generated_content_set_for_get_head of `ensure_content_available'
Result := l_response
end
end
next_chunk (req: WSF_REQUEST): TUPLE [a_chunk: READABLE_STRING_8; a_extension: detachable READABLE_STRING_8]
-- Next chunk of entity body in response to `req';
-- The second field of the result is an optional chunk extension.
do
-- precondition `is_chunking' is never met, but we need a dummy `Result'
-- to satisfy the compiler in void-safe mode
Result := ["", Void]
end
generate_next_chunk (req: WSF_REQUEST)
-- Prepare next chunk (including optional chunk extension) of entity body in response to `req'.
-- This is not called for the first chunk.
do
-- precondition `is_chunking' is never met
end
feature -- DELETE
delete (req: WSF_REQUEST)
-- Delete resource named in `req' or set an error on `req.error_handler'.
local
l_id: STRING
do
l_id := order_id_from_request (req)
if db_access.orders.has_key (l_id) then
if is_valid_to_delete (l_id) then
delete_order (l_id)
else
req.error_handler.add_custom_error ({HTTP_STATUS_CODE}.method_not_allowed, "DELETE not valid",
"There is conflict while trying to delete the order, the order could not be deleted in the current state")
end
else
req.error_handler.add_custom_error ({HTTP_STATUS_CODE}.not_found, "DELETE not valid",
"There is no such order to delete")
end
end
delete_queued (req: WSF_REQUEST): BOOLEAN
-- Has resource named by `req' been queued for deletion?
do
-- No
end
feature -- PUT/POST
is_entity_too_large (req: WSF_REQUEST): BOOLEAN
-- Is the entity stored in `req.execution_variable (Request_entity_execution_variable)' too large for the application?
do
-- No. We don't care for this example.
end
check_content_headers (req: WSF_REQUEST)
-- Check we can support all content headers on request entity.
-- Set `req.execution_variable (Content_check_code_execution_variable)' to {NATURAL} zero if OK, or 415 or 501 if not.
do
-- We don't bother for this example. Note that this is equivalent to setting zero.
end
create_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Create new resource in response to a PUT request when `check_resource_exists' returns `False'.
-- Implementor must set error code of 200 OK or 500 Server Error.
do
-- We don't support creating a new resource with PUT. But this can't happen
-- with our router mappings, so we don't bother to set a 500 response.
end
append_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Create new resource in response to a POST request.
-- Implementor must set error code of 200 OK or 204 No Content or 303 See Other or 500 Server Error.
do
if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then
save_order (l_order)
compute_response_post (req, res, l_order)
else
handle_bad_request_response ("Not a valid order", req, res)
end
end
check_conflict (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Check we can support all content headers on request entity.
-- Set `req.execution_variable (Conflict_check_code_execution_variable)' to {NATURAL} zero if OK, or 409 if not.
-- In the latter case, write the full error response to `res'.
do
if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then
if not is_valid_to_update (l_order) then
req.set_execution_variable (Conflict_check_code_execution_variable, {NATURAL} 409)
handle_resource_conflict_response (l_order.out +"%N There is conflict while trying to update the order, the order could not be update in the current state", req, res)
end
else
req.set_execution_variable (Conflict_check_code_execution_variable, {NATURAL} 409)
--| This ought to be a 500, as if attached should probably be check attached. But as yet I lack a proof.
handle_resource_conflict_response ("There is conflict while trying to update the order, the order could not be update in the current state", req, res)
end
end
check_request (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Check that the request entity is a valid request.
-- The entity is available as `req.execution_variable (Conflict_check_code_execution_variable)'.
-- Set `req.execution_variable (Request_check_code_execution_variable)' to {NATURAL} zero if OK, or 400 if not.
-- In the latter case, write the full error response to `res'.
local
l_order: detachable ORDER
l_id: STRING
do
if attached {READABLE_STRING_8} req.execution_variable (Request_entity_execution_variable) as l_request then
l_order := extract_order_request (l_request)
if req.is_put_request_method then
l_id := order_id_from_request (req)
if l_order /= Void and then db_access.orders.has_key (l_id) then
l_order.set_id (l_id)
req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 0)
req.set_execution_variable (Extracted_order_execution_variable, l_order)
else
req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 400)
handle_bad_request_response (l_request +"%N is not a valid ORDER, maybe the order does not exist in the system", req, res)
end
else
req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 0)
req.set_execution_variable (Extracted_order_execution_variable, l_order)
end
else
req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 400)
handle_bad_request_response ("Request is not a valid ORDER", req, res)
end
end
update_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Perform the update requested in `req'.
-- Write a response to `res' with a code of 204 or 500.
do
if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then
update_order (l_order)
compute_response_put (req, res, l_order)
else
handle_internal_server_error (res)
end
end
feature -- HTTP Methods
compute_response_put (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
local
h: HTTP_HEADER
joc : JSON_ORDER_CONVERTER
etag_utils : ETAG_UTILS
do
create h.make
create joc.make
create etag_utils
json.add_converter(joc)
create h.make
h.put_content_type_application_json
if attached req.request_time as time then
h.add_header ("Date:" +time.formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT")
end
h.add_header ("etag:" + etag_utils.md5_digest (l_order.out))
if attached {JSON_VALUE} json.value (l_order) as jv then
h.put_content_length (jv.representation.count)
res.set_status_code ({HTTP_STATUS_CODE}.ok)
res.put_header_text (h.string)
res.put_string (jv.representation)
end
end
compute_response_post (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
local
h: HTTP_HEADER
l_msg : STRING
l_location : STRING
joc : JSON_ORDER_CONVERTER
do
create h.make
create joc.make
json.add_converter(joc)
h.put_content_type_application_json
if attached {JSON_VALUE} json.value (l_order) as jv then
l_msg := jv.representation
h.put_content_length (l_msg.count)
if attached req.http_host as host then
l_location := "http://" + host + req.request_uri + "/" + l_order.id
h.put_location (l_location)
end
if attached req.request_time as time then
h.put_utc_date (time)
end
res.set_status_code ({HTTP_STATUS_CODE}.created)
res.put_header_text (h.string)
res.put_string (l_msg)
end
end
feature {NONE} -- URI helper methods
order_id_from_request (req: WSF_REQUEST): STRING
-- Value of "orderid" template URI variable in `req'
require
req_attached: req /= Void
do
if attached {WSF_VALUE} req.path_parameter ("orderid") as l_value then
Result := l_value.as_string.value.as_string_8
else
Result := ""
end
end
feature {NONE} -- Implementation Repository Layer
retrieve_order ( id : STRING) : detachable ORDER
-- get the order by id if it exist, in other case, Void
do
Result := db_access.orders.item (id)
end
save_order (an_order: ORDER)
-- save the order to the repository
local
i : INTEGER
do
from
i := 1
until
not db_access.orders.has_key ((db_access.orders.count + i).out)
loop
i := i + 1
end
an_order.set_id ((db_access.orders.count + i).out)
an_order.set_status ("submitted")
an_order.add_revision
db_access.orders.force (an_order, an_order.id)
end
is_valid_to_delete ( an_id : STRING) : BOOLEAN
-- Is the order identified by `an_id' in a state whre it can still be deleted?
do
if attached retrieve_order (an_id) as l_order then
if order_validation.is_state_valid_to_update (l_order.status) then
Result := True
end
end
end
is_valid_to_update (an_order: ORDER) : BOOLEAN
-- Check if there is a conflict while trying to update the order
do
if attached retrieve_order (an_order.id) as l_order then
if order_validation.is_state_valid_to_update (l_order.status) and then order_validation.is_valid_status_state (an_order.status) and then
order_validation.is_valid_transition (l_order, an_order.status) then
Result := True
end
end
end
update_order (an_order: ORDER)
-- update the order to the repository
do
an_order.add_revision
db_access.orders.force (an_order, an_order.id)
end
delete_order (an_order: STRING)
-- update the order to the repository
do
db_access.orders.remove (an_order)
end
extract_order_request (l_post : STRING) : detachable ORDER
-- extract an object Order from the request, or Void
-- if the request is invalid
local
parser : JSON_PARSER
joc : JSON_ORDER_CONVERTER
do
create joc.make
json.add_converter(joc)
create parser.make_with_string (l_post)
parser.parse_content
if parser.is_valid and then attached parser.parsed_json_value as jv then
if attached {like extract_order_request} json.object (jv, "ORDER") as res then
Result := res
end
end
end
note
copyright: "2011-2015, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,403 @@
note
description: "{ORDER_HANDLER} handle the resources that we want to expose"
author: ""
date: "$Date$"
revision: "$Revision$"
class ORDER_HANDLER
inherit
WSF_URI_TEMPLATE_HANDLER
WSF_RESOURCE_HANDLER_HELPER
redefine
do_get,
do_post,
do_put,
do_delete
end
SHARED_DATABASE_API
SHARED_EJSON
REFACTORING_HELPER
SHARED_ORDER_VALIDATION
WSF_SELF_DOCUMENTED_HANDLER
create
make_with_router
feature {NONE} -- Initialization
make_with_router (a_router: WSF_ROUTER)
-- Initialize `router'.
require
a_router_attached: a_router /= Void
do
router := a_router
ensure
router_aliased: router = a_router
end
feature -- Router
router: WSF_ROUTER
-- Associated router that could be used for advanced strategy
feature -- Execute
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute request handler
do
execute_methods (req, res)
end
feature -- API DOC
api_doc : STRING = "URI:/order METHOD: POST%N URI:/order/{orderid} METHOD: GET, PUT, DELETE%N"
feature -- Documentation
mapping_documentation (m: WSF_ROUTER_MAPPING; a_request_methods: detachable WSF_REQUEST_METHODS): WSF_ROUTER_MAPPING_DOCUMENTATION
do
create Result.make (m)
if a_request_methods /= Void then
if a_request_methods.has_method_post then
Result.add_description ("URI:/order METHOD: POST")
elseif
a_request_methods.has_method_get
or a_request_methods.has_method_put
or a_request_methods.has_method_delete
then
Result.add_description ("URI:/order/{orderid} METHOD: GET, PUT, DELETE")
end
end
end
feature -- HTTP Methods
do_get (req: WSF_REQUEST; res: WSF_RESPONSE)
-- <Precursor>
local
id: STRING
do
if attached req.path_info as l_path_info then
id := get_order_id_from_path (l_path_info)
if attached retrieve_order (id) as l_order then
if is_conditional_get (req, l_order) then
handle_resource_not_modified_response ("The resource" + l_path_info + "does not change", req, res)
else
compute_response_get (req, res, l_order)
end
else
handle_resource_not_found_response ("The following resource" + l_path_info + " is not found ", req, res)
end
end
end
is_conditional_get (req : WSF_REQUEST; l_order : ORDER) : BOOLEAN
-- Check if If-None-Match is present and then if there is a representation that has that etag
-- if the representation hasn't changed, we return TRUE
-- then the response is a 304 with no entity body returned.
local
etag_util : ETAG_UTILS
do
if attached req.meta_string_variable ("HTTP_IF_NONE_MATCH") as if_none_match then
create etag_util
if if_none_match.same_string (etag_util.md5_digest (l_order.out).as_string_32) then
Result := True
end
end
end
compute_response_get (req: WSF_REQUEST; res: WSF_RESPONSE; l_order: ORDER)
local
h: HTTP_HEADER
l_msg : STRING
etag_utils : ETAG_UTILS
do
create h.make
create etag_utils
h.put_content_type_application_json
if attached {JSON_VALUE} json.value (l_order) as jv then
l_msg := jv.representation
h.put_content_length (l_msg.count)
if attached req.request_time as time then
h.add_header ("Date:" + time.formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT")
end
h.add_header ("etag:" + etag_utils.md5_digest (l_order.out))
res.set_status_code ({HTTP_STATUS_CODE}.ok)
res.put_header_text (h.string)
res.put_string (l_msg)
end
end
do_put (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Updating a resource with PUT
-- A successful PUT request will not create a new resource, instead it will
-- change the state of the resource identified by the current uri.
-- If success we response with 200 and the updated order.
-- 404 if the order is not found
-- 400 in case of a bad request
-- 500 internal server error
-- If the request is a Conditional PUT, and it does not mat we response
-- 415, precondition failed.
local
l_put: STRING
l_order : detachable ORDER
id : STRING
do
if attached req.path_info as l_path_info then
id := get_order_id_from_path (l_path_info)
l_put := retrieve_data (req)
l_order := extract_order_request(l_put)
if l_order /= Void and then db_access.orders.has_key (id) then
l_order.set_id (id)
if is_valid_to_update(l_order) then
if is_conditional_put (req, l_order) then
update_order( l_order)
compute_response_put (req, res, l_order)
else
handle_precondition_fail_response ("", req, res)
end
else
--| FIXME: Here we need to define the Allow methods
handle_resource_conflict_response (l_put +"%N There is conflict while trying to update the order, the order could not be update in the current state", req, res)
end
else
handle_bad_request_response (l_put +"%N is not a valid ORDER, maybe the order does not exist in the system", req, res)
end
end
end
is_conditional_put (req : WSF_REQUEST; order : ORDER) : BOOLEAN
-- Check if If-Match is present and then if there is a representation that has that etag
-- if the representation hasn't changed, we return TRUE
local
etag_util : ETAG_UTILS
do
if attached retrieve_order (order.id) as l_order then
if attached req.meta_string_variable ("HTTP_IF_MATCH") as if_match then
create etag_util
if if_match.same_string (etag_util.md5_digest (l_order.out).as_string_32) then
Result := True
end
else
Result := True
end
end
end
compute_response_put (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
local
h: HTTP_HEADER
joc : JSON_ORDER_CONVERTER
etag_utils : ETAG_UTILS
do
create h.make
create joc.make
create etag_utils
json.add_converter(joc)
create h.make
h.put_content_type_application_json
if attached req.request_time as time then
h.add_header ("Date:" +time.formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT")
end
h.add_header ("etag:" + etag_utils.md5_digest (l_order.out))
if attached {JSON_VALUE} json.value (l_order) as jv then
h.put_content_length (jv.representation.count)
res.set_status_code ({HTTP_STATUS_CODE}.ok)
res.put_header_text (h.string)
res.put_string (jv.representation)
end
end
do_delete (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Here we use DELETE to cancel an order, if that order is in state where
-- it can still be canceled.
-- 200 if is ok
-- 404 Resource not found
-- 405 if consumer and service's view of the resouce state is inconsisent
-- 500 if we have an internal server error
local
id: STRING
do
if attached req.path_info as l_path_info then
id := get_order_id_from_path (l_path_info)
if db_access.orders.has_key (id) then
if is_valid_to_delete (id) then
delete_order( id)
compute_response_delete (req, res)
else
--| FIXME: Here we need to define the Allow methods
handle_method_not_allowed_response (l_path_info + "%N There is conflict while trying to delete the order, the order could not be deleted in the current state", req, res)
end
else
handle_resource_not_found_response (l_path_info + " not found in this server", req, res)
end
end
end
compute_response_delete (req: WSF_REQUEST; res: WSF_RESPONSE)
local
h : HTTP_HEADER
do
create h.make
h.put_content_type_application_json
if attached req.request_time as time then
h.put_utc_date (time)
end
res.set_status_code ({HTTP_STATUS_CODE}.no_content)
res.put_header_text (h.string)
end
do_post (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Here the convention is the following.
-- POST is used for creation and the server determines the URI
-- of the created resource.
-- If the request post is SUCCESS, the server will create the order and will response with
-- HTTP_RESPONSE 201 CREATED, the Location header will contains the newly created order's URI
-- if the request post is not SUCCESS, the server will response with
-- HTTP_RESPONSE 400 BAD REQUEST, the client send a bad request
-- HTTP_RESPONSE 500 INTERNAL_SERVER_ERROR, when the server can deliver the request
local
l_post: STRING
do
l_post := retrieve_data (req)
if attached extract_order_request (l_post) as l_order then
save_order (l_order)
compute_response_post (req, res, l_order)
else
handle_bad_request_response (l_post +"%N is not a valid ORDER", req, res)
end
end
compute_response_post (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
local
h: HTTP_HEADER
l_msg : STRING
l_location : STRING
joc : JSON_ORDER_CONVERTER
do
create h.make
create joc.make
json.add_converter(joc)
h.put_content_type_application_json
if attached {JSON_VALUE} json.value (l_order) as jv then
l_msg := jv.representation
h.put_content_length (l_msg.count)
if attached req.http_host as host then
l_location := "http://" + host + req.request_uri + "/" + l_order.id
h.put_location (l_location)
end
if attached req.request_time as time then
h.put_utc_date (time)
end
res.set_status_code ({HTTP_STATUS_CODE}.created)
res.put_header_text (h.string)
res.put_string (l_msg)
end
end
feature {NONE} -- URI helper methods
get_order_id_from_path (a_path: READABLE_STRING_32) : STRING
do
Result := a_path.split ('/').at (3)
end
feature {NONE} -- Implementation Repository Layer
retrieve_order ( id : STRING) : detachable ORDER
-- get the order by id if it exist, in other case, Void
do
Result := db_access.orders.item (id)
end
save_order (an_order: ORDER)
-- save the order to the repository
local
i : INTEGER
do
from
i := 1
until
not db_access.orders.has_key ((db_access.orders.count + i).out)
loop
i := i + 1
end
an_order.set_id ((db_access.orders.count + i).out)
an_order.set_status ("submitted")
an_order.add_revision
db_access.orders.force (an_order, an_order.id)
end
is_valid_to_delete ( an_id : STRING) : BOOLEAN
-- Is the order identified by `an_id' in a state whre it can still be deleted?
do
if attached retrieve_order (an_id) as l_order then
if order_validation.is_state_valid_to_update (l_order.status) then
Result := True
end
end
end
is_valid_to_update (an_order: ORDER) : BOOLEAN
-- Check if there is a conflict while trying to update the order
do
if attached retrieve_order (an_order.id) as l_order then
if order_validation.is_state_valid_to_update (l_order.status) and then order_validation.is_valid_status_state (an_order.status) and then
order_validation.is_valid_transition (l_order, an_order.status) then
Result := True
end
end
end
update_order (an_order: ORDER)
-- update the order to the repository
do
an_order.add_revision
db_access.orders.force (an_order, an_order.id)
end
delete_order (an_order: STRING)
-- update the order to the repository
do
db_access.orders.remove (an_order)
end
extract_order_request (l_post : STRING) : detachable ORDER
-- extract an object Order from the request, or Void
-- if the request is invalid
local
parser : JSON_PARSER
joc : JSON_ORDER_CONVERTER
do
create joc.make
json.add_converter(joc)
create parser.make_with_string (l_post)
parser.parse_content
if
parser.is_valid and then
attached parser.parsed_json_value as jv
then
if attached {like extract_order_request} json.object (jv, "ORDER") as res then
Result := res
end
end
end
note
copyright: "2011-2015, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

Some files were not shown because too many files have changed in this diff Show More