Compare commits

...

48 Commits

Author SHA1 Message Date
d4bbdea5e4 Removed unneeded redefine. 2019-01-23 23:50:26 +01:00
8260336d6c Added support for X-Forwarded-For .., and Forwarded header, for the simple proxy implementation.
Also added the possibility to "keep" the original host name.
Updated related example.
2019-01-23 23:46:35 +01:00
19c14d28c7 Updated EiffelWeb wizard. 2018-11-19 19:10:26 +01:00
f1e8dfa40b Updated to use ARGUMENTS_32 as client. 2018-11-19 18:04:35 +01:00
e73302639d updated changelog. 2018-11-16 19:39:13 +01:00
1c58674523 Updated notes for JWS. 2018-11-16 19:35:34 +01:00
7cfe0cc5ed Removed obsolete v0 code. 2018-11-16 19:31:41 +01:00
4f8341e04e Updated JWT library with class descriptions and better features names.
JWT library fixed to use agorithms names in upper case.
Updated README.
2018-11-16 19:28:46 +01:00
7f36e539f1 Accepts string general in html_encoded_string. 2018-11-16 19:28:05 +01:00
8241c0209a Updated JWT README content. 2018-10-29 15:17:31 +01:00
45179b58a3 Fixed custom error creation, do not create default message, otherwise the info will be duplicated in error output. 2018-10-29 15:14:28 +01:00
31fa31bd53 Updated changelog 2018-10-29 13:22:01 +01:00
a8a3ca5b97 typo. 2018-10-29 13:15:32 +01:00
7c6fe5a04a Added HTTP_COOKIE.set_expiration_from_max_age, to add the "Expiration:" based on the max-age value. 2018-10-29 13:15:14 +01:00
c8e2009638 Use double quotes only when needed for put_content_type_with_parameters. 2018-10-29 13:11:19 +01:00
627ec7aefc removed unneeded inheritance. 2018-10-29 13:04:46 +01:00
e7087bcbc1 Added missing WSF_TIMEOUT_UTILITIES file. 2018-10-29 12:19:36 +01:00
8d881bcd7d Updated changelog.md 2018-10-29 12:16:01 +01:00
ed3ad962d1 Updated a few classes from http_client to use nanoseconds as timeout precision.
Fixed typo in comments.
2018-10-29 12:15:20 +01:00
d3e865cf6c Fixed setting of socket.timeout in httpd (was not currently set before).
Adopted the nanoseconds timeout precision
 - in config file added support for ns, us, ms, s timeout precision (without indication, it uses `seconds` precision).
2018-10-29 11:27:26 +01:00
9fcd30b4e1 removed useless JWT_ENCODER 2018-10-17 14:25:56 +02:00
0baa05cf63 JWT: updated to make JWT algorithm support more flexible, and simple to extend with specific algorithm. 2018-10-17 11:00:20 +02:00
f97f59b703 renamed a png file to avoid blank character. 2018-09-27 22:01:34 +02:00
dc377b84d3 Duplicated images to see expected images embedded in github markdown pages, and also in github web pages (jekyll on eiffelweb.org). 2018-09-27 21:57:16 +02:00
f14431fc05 Update basics.md 2018-09-27 21:50:02 +02:00
9577d7d82a Update basics.md 2018-09-27 21:48:45 +02:00
99f8377721 Include images twices to see them under github pages, and also in markdown pages 2018-09-27 21:47:48 +02:00
73d5555532 test (ignore) 2018-09-27 21:39:58 +02:00
ce3c7ac57a try change for related links. 2018-09-27 21:34:18 +02:00
Javier Velilla
8754c2d67d Update basics.md 2018-09-27 16:22:20 -03:00
5e928b9a47 use image location that works for md files, and also generated web files (jekyll) 2018-09-27 21:10:04 +02:00
Javier Velilla
9cdfbd2538 Update basics.md 2018-09-27 16:08:39 -03:00
e4fcc863ca Updated image locations 2018-09-27 21:05:54 +02:00
Javier Velilla
7c8d6b9eef Updated path to Application execution png file 2018-09-27 15:58:09 -03:00
a97eb4b062 Added missing dependencies. 2018-05-30 19:27:04 +02:00
bd5aba3db6 Updated Windows DOS script to build the libfcgi binary lib files. 2018-05-30 17:30:05 +02:00
d43c4edb7d Updated the default rescue response (i.e when exception or bad internal error occurs).
Factorized the implementation in WGI_RESCUE_EXECUTION, and now by redefining the `WGI_EXECUTION.execute_rescue (...)` procedure, it is possible to have a custom response on such rescued execution.
2018-05-30 17:28:24 +02:00
9cdd676417 Fixed HTTP_HEADER.put_raw_header (..) by ignoring any empty line of the argument value.
(note: "%R" is considered as empty line here.)
2018-05-30 17:25:04 +02:00
cb273c3176 Updated to compile with upcoming EiffelStudio 18.05 (with and without ssl). 2018-05-28 17:21:11 +02:00
ec7d504502 Fixed EOL. 2018-05-28 16:18:59 +02:00
7ed1e815b0 Updated to compile with upcoming EiffelStudio 18.05 . 2018-05-28 16:11:24 +02:00
da2e26f697 Renamed the fcgi executable. 2018-04-26 11:58:00 +02:00
bc169d6b26 Fixed remaining issues with docker setup. 2018-04-26 10:58:01 +02:00
cf2f0f09fa Updated container files. 2018-04-24 15:13:47 +02:00
207a109e44 Updated to match docker expectation. 2018-04-24 10:36:20 +02:00
2f2e2067ba Added an example to run the debug app with apache2+libfcgi inside a docker container. 2018-04-23 22:13:07 +02:00
7aa7bf1ab2 Updated travis CI config.
Updated install dos script to include wsf_security.
2018-02-13 18:49:14 +01:00
8e8c3602c6 Allow (websocket) upgrade even without persistent connection for normal http request.
(note: this allows to use websocket in single-threaded mode, and avoid the keep-alive-timeout delay before websocket begins its execution)
2018-02-13 18:39:47 +01:00
166 changed files with 1473 additions and 5924 deletions

View File

@@ -1,8 +1,8 @@
language: eiffel
before_script:
- export current_dir=$PWD ; echo current_dir=$current_dir ; cd ..
- curl -sSL https://www.eiffel.org/setup/install.sh | bash
- source eiffel_latest.rc
- curl -sSL https://www.eiffel.org/setup/install.sh | bash > eiffel.rc
- source ./eiffel.rc
- echo `ec -version`
- cd $current_dir
- echo Check projects compilation status...

View File

@@ -7,15 +7,58 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
## [Unreleased]
### Added
### Changed
### Deprecated
### Removed
- Removed obsolete v0 code.
### Fixed
- `error`: Fixed custom error creation, do not create default message, otherwise the info will be duplicated in error output.
- `jwt`: updated indexing notes, and readme.
### Security
## [v1.1.0] - 2018-10-29
### Added
- `examples`: Added an example to run the debug app with apache2+libfcgi inside a docker container.
- `wsf`: Updated the default rescue response (i.e when exception or bad internal error occurs). Factorized the implementation in `WGI_RESCUE_EXECUTION`, and now by redefining the `WGI_EXECUTION.execute_rescue (...)` procedure, it is possible to have a custom response on such rescued execution.
- `http`: Added `HTTP_COOKIE.set_expiration_from_max_age`, to add the "Expiration:" based on the max-age value.
### Changed
- `wsf`: Adopted the nanoseconds timeout precision. And in config file added support for ns, us, ms, s timeout precision (without indication, it uses `seconds` precision).
### Deprecated
### Removed
### Fixed
- `websocket`: Allow (websocket) upgrade even without persistent connection for normal http request.
(note: this allows to use websocket in single-threaded mode, and avoid the keep-alive-timeout delay before websocket begins its execution)
- `http`: Fixed `HTTP_HEADER.put_raw_header (..)` by ignoring any empty line of the argument value. (note: "%R" is considered as empty line here.)
- `jwt`: updated to make JWT algorithm support more flexible, and simple to extend with specific algorithm.
- `httpd`, `websocket`: Fixed setting of socket.timeout in httpd (it was not correctly set).
### Security
## [v1.0.6] - 2018-02-05
### Added
- `jwt`: new JSON Web Token (JWT) library (supports for claim exp, iat, nbf, iss, aud).
- `http_client`: added support for ciphers setting in the libcurl implementation only.
- `http_client`: added convenient `get` and `custom` functions on HTTP_CLIENT directly.
- `websocket`: added `on_timer` solution to allow the server to check for external events and send notification to websocket clients.
- `http_client`: added convenient `get` and `custom` functions on `HTTP_CLIENT` directly.
- `wsf`: added `WSF_EXECUTE_HANDLER`, and `WSF_CGI_HANDLER`. Demonstration of `WSF_CGI_HANDLER` in the new `tools/httpd` project.
- `wsf_security`: new security library, providing support for XSS injection protection and similar.
- `wsf`: Support persistent connection, even in single thread mode (i.e concurrency=none).
Warning: as there is no concurrent request handling in single threaded mode, it is recommended to either set the keep_alive_timeout to a small value, or disable persistent connection by setting max_keep_alive_requests to 0.
Accept -1 as value of max_keep_alive_requests to have unlimited number of request in the same persistent connection.
- `wsf_compression`: Introduced `WSF_COMPRESSION` and applied to `WSF_*_WITH_COMPRESSION` classes. Added `simple_compression` example.
- `websocket`: added `on_timer` solution to allow the server to check for external events and send notification to websocket clients.
- `wsf`: Added routing condition mapping.
- `wsf_extension`: added handler to add support for CGI scripts.
- Added a new tool `httpd` which is a basic httpd server product (with file server and CGI handler).
- `wsf_security`: added security protections such as XSS injection protection support.
### Changed
- adopted ecf version 1-16-0 and use a single .ecf file (the -safe.ecf are now redirection to normal .ecf)
- `wsf_html`: Made interface of wsf forms and widgets a bit more flexible by accepting `READABLE_STRING_GENERAL`.
### Deprecated
- removed support for Eiffel version before 17.05 .
- SSL 2 or 3 is obsolete and will raise an exception if used.
@@ -23,9 +66,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
### Fixed
- Removed a few obsolete calls.
- `http_client`: Added support for multiple file in form data. Made clear what is the meaning of `upload_filename`, `upload_data` and `form_data`.
- `authentication`: HTTP_AUTHORIZATION acceps now READABLE_STRING_GENERAL for username and password argument.
- `authentication`: `HTTP_AUTHORIZATION` acceps now `READABLE_STRING_GENERAL` for username and password argument.
- `http_client`: fixed curl implementation by setting `Content-Type` to `x-www-form-urlencoded` (if not set) when POST send data as `x-www-form-urlencoded`.
- `notification_email`: fixed the SMTP support for multiple recipients address.
- `http_network`: use proper ciphers settings for libcurl implementation.
- `http_client`: Improved support of absolute/relative https:// and http:// in `http_client`.
- `json_encoder.e`: Properly JSON encode null character as \u0000 .
### Security

View File

@@ -14,3 +14,9 @@ port: 9000
repo: https://github.com/EiffelWebFramework/EWF
version: v1
download: https://github.com/EiffelWebFramework/EWF/archive/v1.zip
plugins:
- jekyll-relative-links
relative_links:
enabled: true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

View File

@@ -94,11 +94,12 @@ The **WSF_RESPONSE** provides features to define the response with information s
**APPLICATION** is the root class of our example, it launches the application, using the corresponding connector, Which connector? this depends how do you want to run it `cgi`, `fcgi`, `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](./Launcher Hierarchy.png "Launcher Hierarchy")
![Launcher Hierarchy](images/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](./WSF_SERVICE_LAUNCHER_STANDALONE.png "Standalone Hierarchy")
![Standalone Launcher](images/WSF_SERVICE_LAUNCHER_STANDALONE.png "Standalone Hierarchy")
Other connectors:
**WSF_STANDALONE_SERVICE_LAUNCHER**
@@ -111,7 +112,7 @@ The **APPLICATION_EXECUTION** class inherits from **WSF_EXECUTION** interface,
In the **APPLICATION_EXECUTION** class class you will need to implement the **execute** feature, get data from the request *req* and write the response in *res*.
![Execution Hierarchy](./APPLICATION_EXECUTION.png "Application Execution ")
![Execution Hierarchy](images/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```.

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

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: 11 KiB

View File

@@ -0,0 +1,50 @@
FROM debian
#ubuntu:xenial
MAINTAINER Jocelyn Fiat <jfiat@eiffel.com>
LABEL description="EiffelWeb debug example hosted using apache2+libfcgi"
RUN apt-get update \
&& apt-get -y install \
curl bzip2 make gcc git-core \
apache2 libapache2-mod-fcgid libfcgi-dev \
&& rm -rf /var/lib/apt/lists/*
#RUN apt-get update && apt-get -y install tmux git-all vim && rm -rf /var/lib/apt/lists/*
EXPOSE 80
RUN a2enmod rewrite suexec include fcgid
RUN export uid=1000 gid=1000 && \
mkdir -p /home/eifweb && \
echo "eifweb:x:${uid}:${gid}:eifweb,,,:/home/eifweb:/bin/bash" >> /etc/passwd && \
echo "eifweb:x:${uid}:" >> /etc/group && \
chown ${uid}:${gid} -R /home/eifweb
USER eifweb
ENV HOME /home/eifweb
ENV WEBDIR /home/eifweb/www
WORKDIR $HOME
# Create expected folders
RUN mkdir $WEBDIR
#Build the debug EiffelWeb example and copy the executable to $HOME/
COPY files/build_service_fcgi $HOME/build_service_fcgi
USER root
RUN chown eifweb:eifweb $HOME/build_service_fcgi && chmod 700 $HOME/build_service_fcgi
USER eifweb
RUN $HOME/build_service_fcgi $HOME/www
USER root
COPY ./files/httpd.conf /etc/apache2/sites-enabled/000-default.conf
COPY ./files/html/.htaccess $WEBDIR/html/.htaccess
COPY ./files/html/index.html $WEBDIR/html/index.html
RUN echo > $WEBDIR/html/service.ews
RUN chown www-data:www-data -R $WEBDIR
#Setup apache as foreground (for docker purpose)
RUN mkdir -p /etc/service/apache
ADD ./files/apache.sh /etc/service/apache/run
RUN chmod +x /etc/service/apache/run
ENTRYPOINT ["/etc/service/apache/run"]

25
examples/docker/README.md Normal file
View File

@@ -0,0 +1,25 @@
Docker container for EiffelWeb debug example with apache2+libfcgi.
==================================================================
This example demonstrates the use of EiffelWeb with Apache2, using the libfcgi connector.
For that, it is using the Docker solution to show the compilation and configuration steps.
To build the docker image:
```
docker build -t local/ewf-debug-httpd .
```
To run the docker image in a self-destroyed container:
```
docker run --rm -dit -p 8080:80 --name my-ewf-debug local/ewf-debug-httpd
```
Notes:
- `--rm` : to remove the container after the execution
- `-p 8080:80` : to map internal listening port 80 to localhost port 8080.
- on Linux, you may need to use `sudo` to be able to use `docker`
- depending on the docker installation, you may need to add an extra `-e ISE_PLATFORM=linux-x86` to force execution in 32bits.
- This docker example is simple on purpose. For production it should be improved and optimized to keep the image smaller and more customizable.
- For more advanced Docker usage, please refer to official https://www.docker.com/ website.

View File

@@ -0,0 +1,3 @@
#!/bin/sh
exec /usr/sbin/apache2ctl -D FOREGROUND

View File

@@ -0,0 +1,25 @@
#!/bin/bash
web_dir=$1
mkdir $web_dir/bin
mkdir $web_dir/html
# Install latest EiffelStudio suite.
curl -sSL https://www.eiffel.org/setup/install.sh > install_eiffel.sh \
&& bash ./install_eiffel.sh latest > $HOME/eiffel.rc
# Setup Eiffel environment
source $HOME/eiffel.rc
# Get source code
git clone https://github.com/EiffelWebFramework/EWF.git $web_dir/src
# Build executable
eiffel build -v --target debug_libfcgi $web_dir/src/examples/debug/debug.ecf $web_dir/html/service.fcgi
# Clean files
rm -rf $ISE_EIFFEL
rm -rf $web_dir/src
exit $?

View File

@@ -0,0 +1,20 @@
<IfModule mod_fcgid.c>
AddHandler fcgid-script .ews
FcgidWrapper /home/eifweb/www/html/service.fcgi .ews
</IfModule>
Options +ExecCGI +Includes +FollowSymLinks
<IfModule mod_rewrite.c>
RewriteEngine on
#RewriteRule ^.*$ maintenance.html [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^$ service.ews/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteRule ^(.*)$ service.ews/$1 [C]
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
</IfModule>

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>EiffelWeb running on Apache2+libfcgi</title>
</head>
<body>
EiffelWeb debug example hosted on apache2+libfcgi .
<ul>
<li>Send a GET request <a href="get?foo=bar">get?foo=bar</a></li>
<li>Send a POST request <form action="post?foo=bar" method="POST">
<input type="text" name="one" value="value_1"/>
<input type="text" name="two" value="value_2"/>
<input type="submit" value="Submit"/>
</form>
</li>
</ul>
</body>
</html>

View File

@@ -0,0 +1,53 @@
<IfModule mod_fcgid.c>
FcgidIdleTimeout 60
FcgidBusyScanInterval 120
FcgidProcessLifeTime 1800
#7200
FcgidMaxProcesses 10
FcgidMaxProcessesPerClass 100
FcgidMinProcessesPerClass 100
FcgidConnectTimeout 8
FcgidIOTimeout 3000
FcgidBusyTimeout 300
FcgidPassHeader Authorization
FcgidMaxRequestLen 10000000
FcgidMaxRequestsPerProcess 100
</IfModule>
<VirtualHost *:80>
# The ServerName directive sets the request scheme, hostname and port that
# the server uses to identify itself. This is used when creating
# redirection URLs. In the context of virtual hosts, the ServerName
# specifies what hostname must appear in the request's Host: header to
# match this virtual host. For the default virtual host (this file) this
# value is not decisive as it is used as a last resort host regardless.
# However, you must set it for any further virtual host explicitly.
#ServerName www.example.com
ServerAdmin webmaster@localhost
DocumentRoot /home/eifweb/www/html
<Directory /home/eifweb/www/html>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Require all granted
</Directory>
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the loglevel for particular
# modules, e.g.
#LogLevel info ssl:warn
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to
# include a line for only one particular virtual host. For example the
# following line enables the CGI configuration for this host only
# after it has been globally disabled with "a2disconf".
#Include conf-available/serve-cgi-bin.conf
</VirtualHost>

View File

@@ -1,6 +0,0 @@
Obsolete example based on EWF v0
================================
Those examples are using the old EWF v0 interface, including use of deprecated "nino" connector.
They serves as example for existing project based on EWF v0, but willing to use latest EiffelStudio, and latest EWF repository.

View File

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

View File

@@ -1,4 +0,0 @@
${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

@@ -1,4 +0,0 @@
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

@@ -1,57 +0,0 @@
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

@@ -1,20 +0,0 @@
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

@@ -1,52 +0,0 @@
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

@@ -1,59 +0,0 @@
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

@@ -1,65 +0,0 @@
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

@@ -1,34 +0,0 @@
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

@@ -1,111 +0,0 @@
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 ("/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-2017, 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

@@ -1,97 +0,0 @@
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.as_string_32)
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-2017, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,3 +0,0 @@
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

@@ -1,11 +0,0 @@
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

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-16-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-16-0 http://www.eiffel.com/developers/xml/configuration-1-16-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>/\.svn$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true">
</option>
<capability>
<concurrency support="thread" use="thread"/>
<void_safety support="all"/>
</capability>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="http_client" location="..\..\..\..\..\library\network\http_client\http_client.ecf" readonly="false"/>
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json.ecf"/>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread.ecf"/>
<cluster name="src" location=".\src\" recursive="true"/>
</target>
</system>

View File

@@ -1,154 +0,0 @@
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

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

View File

@@ -1,297 +0,0 @@
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

@@ -1,55 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-16-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-16-0 http://www.eiffel.com/developers/xml/configuration-1-16-0.xsd" name="restbucks" uuid="2773FEAA-448F-410E-BEDE-9298C4749066">
<target name="restbucks_common">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/\.git$</exclude>
<exclude>/\.svn$</exclude>
</file_rule>
<capability>
<concurrency support="thread" use="thread"/>
</capability>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="connector_nino" location="..\..\..\..\library\server\obsolete\v0\ewsgi\connectors\nino\nino.ecf" readonly="false">
<option debug="true">
<debug name="nino" enabled="true"/>
</option>
</library>
<library name="conneg" location="..\..\..\..\library\network\protocol\content_negotiation\conneg.ecf"/>
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto.ecf" readonly="false"/>
<library name="default_nino" location="..\..\..\..\library\server\obsolete\v0\wsf\default\nino.ecf" readonly="false"/>
<library name="encoder" location="..\..\..\..\library\text\encoder\encoder.ecf" readonly="false"/>
<library name="http" location="..\..\..\..\library\network\protocol\http\http.ecf" readonly="false"/>
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json.ecf"/>
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
<library name="uri_template" location="..\..\..\..\library\text\parser\uri_template\uri_template.ecf" readonly="false"/>
<library name="wsf" location="..\..\..\..\library\server\obsolete\v0\wsf\wsf.ecf" readonly="false"/>
<library name="wsf_extension" location="..\..\..\..\library\server\obsolete\v0\wsf\wsf_extension.ecf" readonly="false"/>
</target>
<target name="restbucks" extends="restbucks_common">
<root class="RESTBUCKS_SERVER" feature="make"/>
<option debug="true" warning="true">
<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">
<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.ecf" readonly="false"/>
<cluster name="src" location="src\" recursive="true">
<file_rule>
<exclude>/resource$</exclude>
</file_rule>
</cluster>
</target>
</system>

View File

@@ -1,26 +0,0 @@
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

@@ -1,19 +0,0 @@
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

@@ -1,90 +0,0 @@
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

@@ -1,54 +0,0 @@
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

@@ -1,176 +0,0 @@
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

@@ -1,114 +0,0 @@
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

@@ -1,56 +0,0 @@
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

@@ -1,19 +0,0 @@
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

@@ -1,574 +0,0 @@
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

@@ -1,403 +0,0 @@
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

View File

@@ -1,58 +0,0 @@
note
description : "REST Buck server"
date : "$Date$"
revision : "$Revision$"
class RESTBUCKS_SERVER
inherit
WSF_ROUTED_SKELETON_SERVICE
undefine
requires_proxy
end
WSF_URI_TEMPLATE_HELPER_FOR_ROUTED_SERVICE
WSF_HANDLER_HELPER
WSF_DEFAULT_SERVICE
WSF_NO_PROXY_POLICY
create
make
feature {NONE} -- Initialization
make
do
initialize_router
set_service_option ("port", 9090)
make_and_launch
end
setup_router
local
order_handler: ORDER_HANDLER
doc: WSF_ROUTER_SELF_DOCUMENTATION_HANDLER
do
create order_handler.make_with_router (router)
router.handle_with_request_methods ("/order", order_handler, router.methods_POST)
router.handle_with_request_methods ("/order/{orderid}", order_handler, router.methods_GET + router.methods_DELETE + router.methods_PUT)
create doc.make_hidden (router)
router.handle_with_request_methods ("/api/doc", doc, router.methods_GET)
end
note
copyright: "2011-2013, Javier Velilla 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

@@ -1,24 +0,0 @@
note
description: "Summary description for {ETAG_UTILS}."
date: "$Date$"
revision: "$Revision$"
class
ETAG_UTILS
feature -- Access
md5_digest (a_string: STRING): STRING
-- Cryptographic hash function that produces a 128-bit (16-byte) hash value, based on `a_string'
local
md5: MD5
do
create md5.make
md5.update_from_string (a_string)
Result := md5.digest_as_string
end
note
copyright: "2011-2014, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -9,41 +9,98 @@ class
inherit
WSF_EXECUTION
WSF_URI_REWRITER
rename
uri as proxy_uri
end
create
make
feature -- Basic operations
execute
local
l_forwarded: BOOLEAN
do
-- NOTE: please enter the target server uri here
-- replace "http://localhost:8080/foobar"
send_proxy_response ("http://localhost:8080/foobar", Current)
-- NOTE: please edit the proxy.conf file
across
proxy_map as ic
until
l_forwarded
loop
if request.path_info.starts_with_general (ic.key) then
l_forwarded := True
send_proxy_response (ic.key, ic.item, agent proxy_uri (ic.key, ?))
end
end
if not l_forwarded then
response.send (create {WSF_PAGE_RESPONSE}.make_with_body ("EiffelWeb proxy: not forwarded!"))
end
end
send_proxy_response (a_remote: READABLE_STRING_8; a_rewriter: detachable WSF_URI_REWRITER)
proxy_map: HASH_TABLE [STRING, STRING]
-- location => target
local
f: PLAIN_TEXT_FILE
l_line: STRING
p: INTEGER
once ("thread")
create Result.make (1)
-- Load proxy.conf
create f.make_with_name ("proxy.conf")
if f.exists and then f.is_access_readable then
f.open_read
from
until
f.end_of_file or f.exhausted
loop
f.read_line
l_line := f.last_string
if l_line.starts_with ("#") then
-- ignore
else
-- Format:
-- path%Tserver
p := l_line.index_of ('%T', 1)
if p > 0 then
Result.force (l_line.substring (p + 1, l_line.count), l_line.head (p - 1))
end
end
end
f.close
end
end
send_proxy_response (a_location, a_remote: READABLE_STRING_8; a_rewriter: detachable FUNCTION [WSF_REQUEST, STRING])
local
h: WSF_SIMPLE_REVERSE_PROXY_HANDLER
do
create h.make (a_remote)
h.set_uri_rewriter (a_rewriter)
h.set_uri_rewriter (create {WSF_AGENT_URI_REWRITER}.make (agent proxy_uri))
h.set_timeout (30) -- 30 seconds
if a_rewriter /= Void then
h.set_uri_rewriter (create {WSF_AGENT_URI_REWRITER}.make (a_rewriter))
end
h.set_timeout_ns (10_000_000_000) -- 10 seconds
h.set_connect_timeout (5_000) -- milliseconds = 5 seconds
-- Uncomment following, if you want to provide proxy information
-- h.set_header_via (True)
-- h.set_header_forwarded (True)
-- h.set_header_x_forwarded (True)
-- Uncomment following line to keep the original Host value.
-- h.keep_proxy_host (True)
h.execute (request, response)
end
feature -- Helpers
proxy_uri (a_request: WSF_REQUEST): STRING
proxy_uri (a_location: READABLE_STRING_8; a_request: WSF_REQUEST): STRING
-- Request uri rewriten as url.
do
Result := a_request.request_uri
-- If related proxy setting is
-- a_location=/foo -> http://foo.com
-- and if request was http://example.com/foo/bar, it will use http://foo.com/bar
-- so the Result here, is "/bar"
if Result.starts_with (a_location) then
Result.remove_head (a_location.count)
end
end
end

View File

@@ -0,0 +1,2 @@
/google/ http://www.google.com/search?q=eiffel
/ http://localhost:8080/testproxy

View File

@@ -4,10 +4,10 @@ port=9090
max_concurrent_connections=100
max_tcp_clients=100
socket_timeout=30
socket_recv_timeout=5
socket_recv_timeout=100 ms
#Persistent connections
keep_alive_timeout=2
keep_alive_timeout=50 ms
max_keep_alive_requests=-1
#SSL

View File

@@ -151,14 +151,14 @@ feature -- Settings
timeout: INTEGER
-- HTTP transaction timeout in seconds.
--| 0 means it nevers timeout
--| 0 means it never timeouts
do
Result := session.timeout
end
connect_timeout: INTEGER
-- HTTP connection timeout in seconds.
--| 0 means it nevers timeout
-- HTTP connection timeout in milliseconds.
--| 0 means it never timeouts
do
Result := session.connect_timeout
end
@@ -208,7 +208,7 @@ feature -- Settings
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2018, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -64,7 +64,7 @@ feature {NONE} -- Internal
create Result.make_client_by_port (a_port, a_host)
end
Result.set_connect_timeout (connect_timeout)
Result.set_timeout (timeout)
Result.set_timeout_ns (Result.seconds_to_nanoseconds (timeout))
Result.connect
end
end
@@ -887,7 +887,7 @@ feature {NONE} -- Helpers
invariant
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2018, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -27,10 +27,22 @@
<custom name="ssl_enabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="ssl_network" location="$|ssl\" recursive="true">
<cluster name="ssl_network" location="$|ssl\" recursive="false">
<condition>
<custom name="ssl_enabled" value="true"/>
</condition>
<file_rule>
<exclude>/http_stream_secure_socket_ext\.e$</exclude>
<condition>
<version type="compiler" max="18.05.0.0"/>
</condition>
</file_rule>
</cluster>
<cluster name="ssl_network_until_18_05" location="$|ssl\until_18.05" recursive="true">
<condition>
<custom name="ssl_enabled" value="true"/>
<version type="compiler" max="18.05.0.0"/>
</condition>
</cluster>
</cluster>
</target>

View File

@@ -2,22 +2,9 @@ note
description: "[
Extension to HTTP_STREAM_SOCKET to support backward compatibility.
TO BE REMOVED IN THE FUTURE, WHEN 16.05 IS OLD.
]"
deferred class
HTTP_STREAM_SECURE_SOCKET_EXT
feature {NONE} -- SSL bridge
ssl_write (a_ssl: SSL; a_pointer: POINTER; a_byte_count: INTEGER): INTEGER
do
Result := a_ssl.write (a_pointer, a_byte_count)
if a_ssl.was_error then
if Result >= 0 then
Result := -1
end
end
end
end

View File

@@ -0,0 +1,23 @@
note
description: "[
Extension to HTTP_STREAM_SOCKET to support backward compatibility.
TO BE REMOVED IN THE FUTURE, WHEN 16.05 IS OLD.
]"
deferred class
HTTP_STREAM_SECURE_SOCKET_EXT
feature {NONE} -- SSL bridge
ssl_write (a_ssl: SSL; a_pointer: POINTER; a_byte_count: INTEGER): INTEGER
do
Result := a_ssl.write (a_pointer, a_byte_count)
if a_ssl.was_error then
if Result >= 0 then
Result := -1
end
end
end
end

View File

@@ -197,6 +197,24 @@ feature -- Change Element
expiration_set: attached expiration as l_expiration and then l_expiration.same_string (date_to_rfc1123_http_date_format (a_date))
end
set_expiration_from_max_age
-- Set `expiration` value from `max_age`.
local
dt: DATE_TIME
do
if max_age < 0 then
unset_expiration
else
if max_age = 0 then
create dt.make_from_epoch (0)
else
create dt.make_now_utc
dt.second_add (max_age)
end
set_expiration_date (dt)
end
end
set_path (a_path: READABLE_STRING_8)
-- Set `path' to `a_path'.
do

View File

@@ -228,7 +228,9 @@ feature -- Header: merging
if line [line.count] = '%R' then
line.remove_tail (1)
end
put_header (line)
if not line.is_empty then
put_header (line)
end
end
end
end

View File

@@ -213,6 +213,7 @@ feature -- Content related header
--| note: see `put_content_type_with_charset' for examples.
local
s: STRING_8
v: READABLE_STRING_8
do
if a_params /= Void and then not a_params.is_empty then
create s.make_from_string (a_content_type)
@@ -224,16 +225,21 @@ feature -- Content related header
s.append_character (' ')
s.append (nv.name)
s.append_character ('=')
s.append_character ('%"')
s.append (nv.value)
s.append_character ('%"')
v := nv.value
if v.has(' ') or v.has ('%T') or v.has ('=') then
s.append_character ('%"')
s.append (v)
s.append_character ('%"')
else
s.append (v)
end
end
end
put_header_key_value ({HTTP_HEADER_NAMES}.header_content_type, s)
else
put_content_type (a_content_type)
end
end
end
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'.

View File

@@ -2,25 +2,34 @@ JSON Web Token (JWT)
http://jwt.io/
Note: supporting only HS256 and none algorithm for signature.
Note: supporting only HS256 and none algorithm for signature, but could be extend with your own algorithm via `JWT_ALGORITHMS` (see `JWT.algorithms`, and `JWT_LOADER.algorithms`).
# How to use
```eiffel
local
jwt: JWT
do
create jwt
tok := jwt.encoded_string ("[
{"iss":"joe", "exp":1200819380,"http://example.com/is_root":true}
]", "secret", "HS256")
if
attached jwt.decoded_string (tok, "secret", Void) as l_tok_payload and
not jwt.has_error
then
check verified: not jwt.has_unverified_token_error end
check no_error: not jwt.has_error end
print (l_tok_payload)
example
local
jwt: JWS
tok: STRING
l_loader: JWT_LOADER
do
create jwt.make_with_json_payload ("[
{"iss":"joe", "exp":1200819380,"http://example.com/is_root":true}
]")
jwt.set_algorithm_to_hs256
tok := jwt.encoded_string ("my-secret")
create l_loader
if
attached l_loader.token (tok, Void, "my-secret", Void) as l_tok and then
not l_tok.has_error
then
print (l_tok.claimset.string)
check verified: not l_tok.has_unverified_token_error end
check no_error: not l_tok.has_error end
end
end
end
```

View File

@@ -1,7 +1,9 @@
note
description: "Summary description for {JWS}."
description: "JSON Web Signature (JWS)"
date: "$Date$"
revision: "$Revision$"
EIS: "name=JSON Web Signature", "src=https://tools.ietf.org/html/rfc7515", "protocol=uri"
EIS: "name=JSON Web Token (JWT)", "src=https://tools.ietf.org/html/rfc7519", "protocol=uri"
class
JWS
@@ -64,14 +66,16 @@ feature -- Conversion
encoded_string (a_secret: READABLE_STRING_8): STRING
local
alg, sign: READABLE_STRING_8
sign, alg_name: READABLE_STRING_8
alg: JWT_ALG
l_enc_payload, l_enc_header: READABLE_STRING_8
do
reset_error
alg := header.algorithm
if not is_supporting_signature_algorithm (alg) then
report_unsupported_alg_error (alg)
alg := alg_hs256 -- Default ...
alg_name := header.algorithm
alg := algorithms [alg_name]
if alg = Void then
report_unsupported_alg_error (alg_name)
alg := algorithms.hs256 -- Default ...
end
l_enc_header := base64url_encode (header.string)
l_enc_payload := base64url_encode (claimset.string)
@@ -94,12 +98,12 @@ feature -- Element change
set_algorithm_to_hs256
do
set_algorithm (alg_hs256)
set_algorithm (algorithms.hs256.name)
end
set_algorithm_to_none
do
set_algorithm (alg_none)
set_algorithm (algorithms.none.name)
end
end

View File

@@ -2,6 +2,7 @@ note
description: "JSON Web Token"
date: "$Date$"
revision: "$Revision$"
EIS: "name=JSON Web Token (JWT)", "src=https://tools.ietf.org/html/rfc7519", "protocol=uri"
deferred class
JWT
@@ -16,12 +17,15 @@ feature {NONE} -- Initialization
default_create
do
create algorithms
create header
create claimset
end
feature -- Access
algorithms: JWT_ALGORITHMS
header: JWT_HEADER
claimset: JWT_CLAIMSET
@@ -60,7 +64,7 @@ feature -- Status report
if attached claimset.issuer as iss then
Result := a_issuer = Void or else a_issuer.same_string (iss)
else
Result := a_issuer = Void
Result := a_issuer = Void
end
end
@@ -69,7 +73,7 @@ feature -- Status report
if attached claimset.audience as aud then
Result := a_audience = Void or else a_audience.same_string (aud)
else
Result := a_audience = Void
Result := a_audience = Void
end
end
@@ -91,6 +95,11 @@ feature -- status report
Result := attached errors as errs and then across errs as ic some attached {JWT_UNSUPPORTED_ALG_ERROR} ic.item end
end
has_mismatched_alg_error: BOOLEAN
do
Result := attached errors as errs and then across errs as ic some attached {JWT_MISMATCHED_ALG_ERROR} ic.item end
end
has_unverified_token_error: BOOLEAN
do
Result := attached errors as errs and then across errs as ic some attached {JWT_UNVERIFIED_TOKEN_ERROR} ic.item end

View File

@@ -0,0 +1,26 @@
note
description: "JWT signature is based on Current algorithm"
date: "$Date$"
revision: "$Revision$"
deferred class
JWT_ALG
feature -- Access
name: READABLE_STRING_8
deferred
end
encoded_string (a_message: READABLE_STRING_8; a_secret: READABLE_STRING_8): STRING
deferred
end
feature -- Status report
is_none: BOOLEAN
-- Is Current algorithm is "none" ?
do
end
end

View File

@@ -0,0 +1,56 @@
note
description: "JWT signature is based on HS256=HMAC+SHA256 algorithm."
date: "$Date$"
revision: "$Revision$"
class
JWT_ALG_HS256
inherit
JWT_ALG
feature -- Access
name: STRING = "HS256"
encoded_string (a_message: READABLE_STRING_8; a_secret: READABLE_STRING_8): STRING
do
Result := base64_hmacsha256 (a_message, a_secret)
end
feature {NONE} -- Implementation
base64_hmacsha256 (s: READABLE_STRING_8; a_secret: READABLE_STRING_8): STRING_8
local
hs256: HMAC_SHA256
do
create hs256.make_ascii_key (a_secret)
hs256.update_from_string (s)
-- if Version >= EiffelStudio 18.01 then
-- Result := hs256.base64_digest --lowercase_hexadecimal_string_digest
-- else
Result := base64_bytes_encoded_string (hs256.digest)
-- end
end
base64_bytes_encoded_string (a_bytes: SPECIAL [NATURAL_8]): STRING_8
-- Base64 string from `a_bytes`.
--| Note: to be removed when 18.01 is not latest release anymore.
local
s: STRING
i,n: INTEGER
do
from
i := 1
n := a_bytes.count
create s.make (n)
until
i > n
loop
s.append_code (a_bytes[i - 1])
i := i + 1
end
Result := (create {BASE64}).encoded_string (s)
end
end

View File

@@ -0,0 +1,29 @@
note
description: "Object representing algorithm `NONE'"
date: "$Date$"
revision: "$Revision$"
EIS: "name=Algorithm none", "src=https://tools.ietf.org/html/rfc7518#section-3.6", "protocol=uri"
class
JWT_ALG_NONE
inherit
JWT_ALG
redefine
is_none
end
feature -- Access
name: STRING = "none"
encoded_string (a_message: READABLE_STRING_8; a_secret: READABLE_STRING_8): STRING
do
create Result.make_empty
end
feature -- Status report
is_none: BOOLEAN = True
-- Is Current algorithm is "none" ?
end

View File

@@ -0,0 +1,99 @@
note
description: "JSON Web Algorithms (JWA)"
date: "$Date$"
revision: "$Revision$"
EIS: "name= JSON Web Algorithms", "src=https://tools.ietf.org/html/rfc7518", "protocol=uri"
class
JWT_ALGORITHMS
inherit
ANY
redefine
default_create
end
create
default_create
feature {NONE} -- Initialization
default_create
do
create items.make_caseless (2)
register_algorithm (hs256)
register_algorithm (none)
-- TODO: check if this is acceptable default.
set_default_algorithm ({JWT_ALG_HS256}.name)
end
feature -- Access
hs256: JWT_ALG_HS256
do
create Result
end
none: JWT_ALG_NONE
do
create Result
end
feature -- Access
default_algorithm: JWT_ALG
do
if attached internal_default_alg_name as l_alg_name then
Result := algorithm (l_alg_name)
end
if Result = Void then
Result := none
end
end
algorithm alias "[]" (a_name: READABLE_STRING_GENERAL): detachable JWT_ALG
do
Result := items [a_name]
end
feature -- Element change
register_algorithm (alg: attached like algorithm)
do
items [alg.name] := alg
end
unregister_algorithm (a_alg_name: READABLE_STRING_GENERAL)
do
items.remove (a_alg_name)
end
set_default_algorithm (a_alg_name: detachable READABLE_STRING_GENERAL)
do
if
a_alg_name = Void or else
not is_supported_algorithm (a_alg_name)
then
internal_default_alg_name := Void
else
internal_default_alg_name := a_alg_name
end
end
feature -- Status report
is_supported_algorithm (a_name: READABLE_STRING_GENERAL): BOOLEAN
do
Result := items.has (a_name)
end
feature {NONE} -- Implementation
items: STRING_TABLE [attached like algorithm]
internal_default_alg_name: detachable READABLE_STRING_GENERAL
invariant
end

View File

@@ -1,9 +1,8 @@
note
description: "Summary description for {JWT_CLAIMSET}."
author: ""
description: "Object representing a JWT claim set"
date: "$Date$"
revision: "$Revision$"
EIS: "name=JWT claims", "src=https://tools.ietf.org/html/rfc7519#section-4", "protocol=uri"
class
JWT_CLAIMSET

View File

@@ -1,275 +0,0 @@
note
description: "JSON Web Token encoder"
date: "$Date$"
revision: "$Revision$"
class
JWT_ENCODER
feature -- Basic operations
encoded_values (a_values: STRING_TABLE [READABLE_STRING_GENERAL]; a_secret: READABLE_STRING_8; a_algo: READABLE_STRING_8): STRING
local
j: JSON_OBJECT
do
create j.make_with_capacity (a_values.count)
across
a_values as ic
loop
j.put_string (ic.item, ic.key)
end
Result := encoded_json (j, a_secret, a_algo)
end
encoded_json (a_json: JSON_OBJECT; a_secret: READABLE_STRING_8; a_algo: READABLE_STRING_8): STRING
local
vis: JSON_PRETTY_STRING_VISITOR
s: STRING
do
create s.make_empty
create vis.make (s)
vis.visit_json_object (a_json)
Result := encoded_string (s, a_secret, a_algo)
end
encoded_string (a_payload: READABLE_STRING_8; a_secret: READABLE_STRING_8; a_algo: READABLE_STRING_8): STRING
local
alg, sign: STRING_8
l_enc_payload, l_enc_header: READABLE_STRING_8
do
reset_error
if a_algo.is_case_insensitive_equal_general (alg_hs256) then
alg := alg_hs256
elseif a_algo.is_case_insensitive_equal_general (alg_none) then
alg := alg_none
else
report_unsupported_alg_error (a_algo)
alg := alg_hs256 -- Default ...
end
l_enc_header := base64url_encode (header ("JWT", alg))
l_enc_payload := base64url_encode (a_payload)
sign := signature (l_enc_header, l_enc_payload, a_secret, alg)
create Result.make (l_enc_header.count + 1 + l_enc_payload.count + 1 + sign.count)
Result.append (l_enc_header)
Result.append_character ('.')
Result.append (l_enc_payload)
Result.append_character ('.')
Result.append (sign)
end
decoded_string (a_token: READABLE_STRING_8; a_secret: READABLE_STRING_8; a_algo: detachable READABLE_STRING_8): detachable STRING
local
i,j,n: INTEGER
alg, l_enc_payload, l_enc_header, l_signature: READABLE_STRING_8
do
reset_error
n := a_token.count
i := a_token.index_of ('.', 1)
if i > 0 then
j := a_token.index_of ('.', i + 1)
if j > 0 then
l_enc_header := a_token.substring (1, i - 1)
l_enc_payload := a_token.substring (i + 1, j - 1)
l_signature := a_token.substring (j + 1, n)
Result := base64url_decode (l_enc_payload)
alg := a_algo
if alg = Void then
alg := signature_algorithm_from_encoded_header (l_enc_header)
if alg = Void then
-- Use default
alg := alg_hs256
end
end
check alg_set: alg /= Void end
if alg.is_case_insensitive_equal (alg_hs256) then
alg := alg_hs256
elseif alg.is_case_insensitive_equal (alg_none) then
alg := alg_none
else
alg := alg_hs256
report_unsupported_alg_error (alg)
end
if not l_signature.same_string (signature (l_enc_header, l_enc_payload, a_secret, alg)) then
report_unverified_token_error
end
else
report_invalid_token
end
else
report_invalid_token
end
end
feature -- Error status
error_code: INTEGER
-- Last error, if any.
has_error: BOOLEAN
-- Last `encoded_string` reported an error?
do
Result := error_code /= 0
end
has_unsupported_alg_error: BOOLEAN
do
Result := error_code = unsupported_alg_error
end
has_unverified_token_error: BOOLEAN
do
Result := error_code = unverified_token_error
end
has_invalid_token_error: BOOLEAN
do
Result := error_code = invalid_token_error
end
feature {NONE} -- Error reporting
reset_error
do
error_code := 0
end
report_unsupported_alg_error (alg: READABLE_STRING_8)
do
error_code := unsupported_alg_error
end
report_unverified_token_error
do
error_code := unverified_token_error
end
report_invalid_token
do
error_code := invalid_token_error
end
feature {NONE} -- Constants
unsupported_alg_error: INTEGER = -2
unverified_token_error: INTEGER = -4
invalid_token_error: INTEGER = -8
alg_hs256: STRING = "HS256"
-- HMAC SHA256.
alg_none: STRING = "none"
-- for unsecured token.
feature -- Conversion
header (a_type: detachable READABLE_STRING_8; alg: READABLE_STRING_8): STRING
do
create Result.make_empty
Result.append ("{%"typ%":%"")
if a_type /= Void then
Result.append (a_type)
else
Result.append ("JWT")
end
Result.append ("%",%"alg%":%"")
Result.append (alg)
Result.append ("%"}")
end
feature {NONE} -- Conversion
signature_algorithm_from_encoded_header (a_enc_header: READABLE_STRING_8): detachable STRING_8
local
jp: JSON_PARSER
do
create jp.make_with_string (base64url_decode (a_enc_header))
jp.parse_content
if
attached jp.parsed_json_object as jo and then
attached {JSON_STRING} jo.item ("alg") as j_alg
then
Result := j_alg.unescaped_string_8
end
end
feature -- Base64
base64url_encode (s: READABLE_STRING_8): STRING_8
local
urlencoder: URL_ENCODER
base64: BASE64
do
create urlencoder
create base64
Result := urlsafe_encode (base64.encoded_string (s))
end
feature {NONE} -- Implementation
signature (a_enc_header, a_enc_payload: READABLE_STRING_8; a_secret: READABLE_STRING_8; alg: READABLE_STRING_8): STRING_8
local
s: STRING
do
if alg = alg_none then
create Result.make_empty
else
create s.make (a_enc_header.count + 1 + a_enc_payload.count)
s.append (a_enc_header)
s.append_character ('.')
s.append (a_enc_payload)
if alg = alg_hs256 then
Result := base64_hmacsha256 (s, a_secret)
else
Result := base64_hmacsha256 (s, a_secret)
end
Result := urlsafe_encode (Result)
end
end
base64url_decode (s: READABLE_STRING_8): STRING_8
local
urlencoder: URL_ENCODER
base64: BASE64
do
create urlencoder
create base64
Result := base64.decoded_string (urlsafe_decode (s))
end
urlsafe_encode (s: READABLE_STRING_8): STRING_8
do
create Result.make_from_string (s)
Result.replace_substring_all ("=", "")
Result.replace_substring_all ("+", "-")
Result.replace_substring_all ("/", "_")
end
urlsafe_decode (s: READABLE_STRING_8): STRING_8
local
i: INTEGER
do
create Result.make_from_string (s)
Result.replace_substring_all ("-", "+")
Result.replace_substring_all ("_", "/")
from
i := Result.count \\ 4
until
i = 0
loop
i := i - 1
Result.extend ('=')
end
end
base64_hmacsha256 (s: READABLE_STRING_8; a_secret: READABLE_STRING_8): STRING_8
local
ut: JWT_UTILITIES
do
create ut
Result := ut.base64_hmacsha256 (s, a_secret)
end
end

View File

@@ -6,7 +6,8 @@ note
]"
date: "$Date$"
revision: "$Revision$"
EIS: "name=JOSE Header", "src=https://tools.ietf.org/html/rfc7519#section-5", "protocol=uri"
class
JWT_HEADER
@@ -52,6 +53,10 @@ feature -- Access
-- The issuer can freely set an algorithm to verify the signature on the token.
-- However, some supported algorithms are insecure.
private_key_id: detachable READABLE_STRING_8
-- For the kid field in the header, specify your service account's private key ID.
-- You can find this value in the private_key_id field of your service account JSON file.
feature -- Conversion
string: STRING
@@ -67,7 +72,13 @@ feature -- Conversion
end
Result.append (",%"alg%":%"")
Result.append (algorithm)
Result.append ("%"}")
Result.append ("%"")
if attached private_key_id as kid then
Result.append (",%"kid%":%"")
Result.append (kid)
Result.append ("%"")
end
Result.append ("}")
end
feature -- Element change
@@ -84,13 +95,21 @@ feature -- Element change
set_algorithm (alg: detachable READABLE_STRING_8)
do
if alg = Void then
if
alg = Void or else
alg.is_case_insensitive_equal ("none")
then
algorithm := "none"
else
algorithm := alg
algorithm := alg.as_upper
end
end
set_private_key_id (a_id: detachable READABLE_STRING_8)
do
private_key_id := a_id
end
feature -- Element change
import_json (a_json: READABLE_STRING_8)
@@ -111,6 +130,9 @@ feature -- Element change
if attached {JSON_STRING} jo.item ("alg") as j_alg then
set_algorithm (j_alg.unescaped_string_8)
end
if attached {JSON_STRING} jo.item ("kid") as j_kid then
set_private_key_id (j_kid.unescaped_string_8)
end
end
end

View File

@@ -9,6 +9,20 @@ class
inherit
JWT_UTILITIES
redefine
default_create
end
feature {NONE} -- Initialization
default_create
do
create algorithms
end
feature -- Settings
algorithms: JWT_ALGORITHMS
feature -- Access
@@ -18,11 +32,12 @@ feature -- Access
-- WARNING: passing Void for `a_alg` is not safe, as the server should know which alg he used for tokens,
-- leaving the possibility to use the header alg is dangerous as client may use "none" and then bypass verification!
require
a_valid_alg: a_alg /= Void implies is_supporting_signature_algorithm (a_alg)
a_valid_alg: a_alg /= Void implies algorithms.is_supported_algorithm (a_alg)
local
jws: JWS
i,j,n: INTEGER
alg, l_enc_payload, l_enc_header, l_signature: READABLE_STRING_8
alg_encoder: JWT_ALG
do
n := a_token_input.count
i := a_token_input.index_of ('.', 1)
@@ -43,17 +58,18 @@ feature -- Access
else
if alg = Void then
-- Use default
alg := alg_hs256
alg := algorithms.default_algorithm.name
end
end
jws.set_algorithm (alg)
check alg_set: alg /= Void end
if ctx = Void or else not ctx.validation_ignored then
if not is_supporting_signature_algorithm (alg) then
alg_encoder := algorithms [alg]
if alg_encoder = Void then
jws.report_unsupported_alg_error (alg)
alg := alg_hs256
alg_encoder := algorithms.default_algorithm
end
if not l_signature.same_string (signature (l_enc_header, l_enc_payload, a_verification_key, alg)) then
if not l_signature.same_string (signature (l_enc_header, l_enc_payload, a_verification_key, alg_encoder)) then
jws.report_unverified_token_error
end
if

View File

@@ -1,20 +1,11 @@
note
description: "Summary description for {JWT_UTILITIES}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
JWT_UTILITIES
feature -- Constants
alg_hs256: STRING = "HS256"
-- HMAC SHA256.
alg_none: STRING = "none"
-- for unsecured token.
feature -- Encoding
base64url_encode (s: READABLE_STRING_8): STRING_8
@@ -35,61 +26,21 @@ feature -- Encoding
Result.replace_substring_all ("/", "_")
end
signature (a_enc_header, a_enc_payload: READABLE_STRING_8; a_secret: READABLE_STRING_8; alg: READABLE_STRING_8): STRING_8
signature (a_enc_header, a_enc_payload: READABLE_STRING_8; a_secret: READABLE_STRING_8; alg: JWT_ALG): STRING_8
local
s: STRING
do
if alg.is_case_insensitive_equal (alg_none) then
if alg.is_none then
create Result.make_empty
else
create s.make (a_enc_header.count + 1 + a_enc_payload.count)
s.append (a_enc_header)
s.append_character ('.')
s.append (a_enc_payload)
if alg.is_case_insensitive_equal (alg_hs256) then
Result := base64_hmacsha256 (s, a_secret)
else
Result := base64_hmacsha256 (s, a_secret)
end
Result := urlsafe_encode (Result)
Result := urlsafe_encode (alg.encoded_string (s, a_secret))
end
end
base64_hmacsha256 (s: READABLE_STRING_8; a_secret: READABLE_STRING_8): STRING_8
local
hs256: HMAC_SHA256
do
create hs256.make_ascii_key (a_secret)
hs256.update_from_string (s)
-- if Version >= EiffelStudio 17.11 then
-- Result := hs256.base64_digest --lowercase_hexadecimal_string_digest
-- else
Result := base64_bytes_encoded_string (hs256.digest)
-- end
end
feature {NONE} -- Implementation
base64_bytes_encoded_string (a_bytes: SPECIAL [NATURAL_8]): STRING_8
-- Base64 string from `a_bytes`.
--| Note: to be removed when 17.11 is not latest release anymore.
local
s: STRING
i,n: INTEGER
do
from
i := 1
n := a_bytes.count
create s.make (n)
until
i > n
loop
s.append_code (a_bytes[i - 1])
i := i + 1
end
Result := (create {BASE64}).encoded_string (s)
end
feature -- Decoding
base64url_decode (s: READABLE_STRING_8): STRING_8
@@ -119,20 +70,4 @@ feature -- Decoding
end
end
feature -- Signature
supported_signature_algorithms: LIST [READABLE_STRING_8]
-- Supported signature algorithm `alg`?
do
create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (2)
Result.extend (alg_hs256)
Result.extend (alg_none)
end
is_supporting_signature_algorithm (alg: READABLE_STRING_8): BOOLEAN
-- Is supporting signature algorithm `alg`?
do
Result := across supported_signature_algorithms as ic some alg.is_case_insensitive_equal (ic.item) end
end
end

View File

@@ -0,0 +1,22 @@
note
description: "Summary description for {JWT_ALG_TEST}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
JWT_ALG_TEST
inherit
JWT_ALG
feature -- Access
name: STRING = "test"
encoded_string (a_message: READABLE_STRING_8; a_secret: READABLE_STRING_8): STRING
do
Result := "TEST<<"+ a_message + ">>"
end
end

View File

@@ -16,6 +16,30 @@ inherit
feature -- Test
example
local
jwt: JWS
l_loader: JWT_LOADER
tok: STRING
do
create jwt.make_with_json_payload ("[
{"iss":"joe", "exp":1200819380,"http://example.com/is_root":true}
]")
jwt.set_algorithm_to_hs256
tok := jwt.encoded_string ("my-secret")
create l_loader
if
attached l_loader.token (tok, Void, "my-secret", Void) as l_tok and then
not l_tok.has_error
then
print (l_tok.claimset.string)
check verified: not l_tok.has_unverified_token_error end
check no_error: not l_tok.has_error end
end
end
test_jwt_io
local
jwt: JWS
@@ -33,6 +57,22 @@ feature -- Test
assert ("signature", jwt.encoded_string ("secret").same_string ("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.pcHcZspUvuiqIPVB_i_qmcvCJv63KLUgIAKIlXI1gY8"))
end
test_jwt_alg_caseless
local
jwt: JWS
ut: JWT_UTILITIES
do
create jwt
jwt.set_algorithm ("HS256")
assert("HS256", jwt.algorithm.same_string ("HS256"))
create jwt
jwt.set_algorithm ("hs256")
assert("hs256", jwt.algorithm.same_string ("HS256"))
create jwt
jwt.set_algorithm ("None")
assert("None", jwt.algorithm.same_string ("none"))
end
test_jwt
local
jwt: JWS
@@ -185,7 +225,8 @@ feature -- Test
tok := jwt.encoded_string ("secret")
if attached (create {JWT_LOADER}).token (tok, "HS256", "secret", Void) as l_tok then
assert ("no error", not jwt.has_error)
assert ("error", l_tok.has_error)
assert ("has_mismatched_alg_error", l_tok.has_mismatched_alg_error)
assert ("same payload", l_tok.claimset.string.same_string (payload))
end
end
@@ -205,15 +246,50 @@ feature -- Test
tok := jwt.encoded_string ("secret")
if attached (create {JWT_LOADER}).token (tok, "none", "secret", Void) as l_tok then
assert ("no error", not jwt.has_error)
assert ("no error", not l_tok.has_error)
assert ("same payload", l_tok.claimset.string.same_string (payload))
end
if attached (create {JWT_LOADER}).token (tok, Void, "secret", Void) as l_tok then
assert ("no error", not jwt.has_error)
assert ("no error", not l_tok.has_error)
assert ("same payload", l_tok.claimset.string.same_string (payload))
end
end
test_additional_alg
local
jwt: JWS
payload: STRING
tok: STRING
l_loader: JWT_LOADER
do
payload := "[
{"iss":"joe","exp":1300819380,"http://example.com/is_root":true}
]"
create jwt.make_with_json_payload (payload)
jwt.algorithms.register_algorithm (create {JWT_ALG_TEST})
jwt.set_algorithm ({JWT_ALG_TEST}.name)
tok := jwt.encoded_string ("secret")
create l_loader
l_loader.algorithms.register_algorithm (create {JWT_ALG_TEST})
if attached l_loader.token (tok, "test", "secret", Void) as l_tok then
assert ("no error", not l_tok.has_error)
assert ("not has_unsupported_alg_error", not l_tok.has_unsupported_alg_error)
assert ("same payload", l_tok.claimset.string.same_string (payload))
end
if attached l_loader.token (tok, Void, "secret", Void) as l_tok then
assert ("no error", not l_tok.has_error)
assert ("same payload", l_tok.claimset.string.same_string (payload))
end
create l_loader
if attached l_loader.token (tok, "test", "secret", Void) as l_tok then
assert ("has error", l_tok.has_error)
assert ("has_unsupported_alg_error", l_tok.has_unsupported_alg_error)
end
end
feature -- Implementation
duplicated_time (dt: DATE_TIME): DATE_TIME

View File

@@ -38,12 +38,12 @@ feature -- Execution
exec.execute
res.push
exec.clean
elseif exec /= Void then
exec.execute_rescue ((create {EXCEPTION_MANAGER}).last_exception)
exec.clean
else
process_rescue (res)
if exec /= Void then
exec.clean
end
end
(create {WGI_RESCUE_EXECUTION}).execute (req, res, (create {EXCEPTION_MANAGER}).last_exception)
end
rescue
if not rescued then
rescued := True
@@ -51,24 +51,6 @@ feature -- Execution
end
end
process_rescue (res: detachable WGI_RESPONSE)
-- Handle rescued execution of current request.
do
if attached (create {EXCEPTION_MANAGER}).last_exception as e and then attached e.trace as l_trace then
if res /= Void then
if not res.status_is_set then
res.set_status_code ({HTTP_STATUS_CODE}.internal_server_error, Void)
end
if res.message_writable then
res.put_string ("<pre>")
res.put_string (html_encoder.encoded_string (l_trace))
res.put_string ("</pre>")
end
res.push
end
end
end
note
copyright: "2011-2015, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"

View File

@@ -73,12 +73,12 @@ feature -- Execution
exec.execute
res.push
exec.clean
elseif exec /= Void then
exec.execute_rescue ((create {EXCEPTION_MANAGER}).last_exception)
exec.clean
else
process_rescue (res)
if exec /= Void then
exec.clean
end
end
(create {WGI_RESCUE_EXECUTION}).execute (req, res, (create {EXCEPTION_MANAGER}).last_exception)
end
rescue
if not rescued then
rescued := True
@@ -86,24 +86,6 @@ feature -- Execution
end
end
process_rescue (res: detachable WGI_RESPONSE)
-- Handle rescued execution of current request.
do
if attached (create {EXCEPTION_MANAGER}).last_exception as e and then attached e.trace as l_trace then
if res /= Void then
if not res.status_is_set then
res.set_status_code ({HTTP_STATUS_CODE}.internal_server_error, Void)
end
if res.message_writable then
res.put_string ("<pre>")
res.put_string (html_encoder.encoded_string (l_trace))
res.put_string ("</pre>")
end
res.push
end
end
end
feature -- Input/Output
input: WGI_LIBFCGI_INPUT_STREAM

View File

@@ -88,14 +88,16 @@ feature -- Request processing
exec.execute
res.push
exec.clean
else
elseif exec /= Void then
if not has_error then
process_rescue (res)
exec.execute_rescue ((create {EXCEPTION_MANAGER}).last_exception)
end
if exec /= Void then
exec.clean
end
end
exec.clean
elseif not has_error then
(create {WGI_RESCUE_EXECUTION}).execute (req, res, (create {EXCEPTION_MANAGER}).last_exception)
else
-- Bad error.
end
rescue
if l_output = Void or else not l_output.is_available then
report_error ("Missing WGI output")
@@ -106,31 +108,6 @@ feature -- Request processing
end
end
process_rescue (res: detachable WGI_RESPONSE)
local
s: STRING
do
if attached (create {EXCEPTION_MANAGER}).last_exception as e and then attached e.trace as l_trace then
if res /= Void then
if not res.status_is_set then
res.set_status_code ({HTTP_STATUS_CODE}.internal_server_error, Void)
end
create s.make_empty
s.append ("<pre>")
s.append (html_encoder.encoded_string (l_trace))
s.append ("</pre>")
if not res.header_committed then
res.put_header_text ("Content-Type: text/html%R%NContent-Length: " + s.count.out + "%R%N%R%N")
end
if res.message_writable then
res.put_string (s)
end
res.push
end
end
end
httpd_environment (a_socket: HTTPD_STREAM_SOCKET): STRING_TABLE [READABLE_STRING_8]
local
p: INTEGER

View File

@@ -110,7 +110,9 @@ feature -- Header output operation
if
not l_connection.is_case_insensitive_equal_general ("close")
then
s.replace_substring ("Connection: close", i + 1, j - 1)
if not l_connection.is_case_insensitive_equal_general ("upgrade") then
s.replace_substring ("Connection: close", i + 1, j - 1)
end
end
elseif not is_http_version_1_0 then
-- HTTP/1.1: always return "close" since persistent connection is not supported.

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-16-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-16-0 http://www.eiffel.com/developers/xml/configuration-1-16-0.xsd" name="ewsgi_spec" uuid="AA193B9F-02FD-47B9-B60D-C42B9AB35E1C" library_target="ewsgi_spec">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-18-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-18-0 http://www.eiffel.com/developers/xml/configuration-1-18-0.xsd" name="ewsgi_spec" uuid="AA193B9F-02FD-47B9-B60D-C42B9AB35E1C" library_target="ewsgi_spec">
<target name="ewsgi_spec">
<root all_classes="true"/>
<file_rule>
@@ -10,6 +10,8 @@
<option warning="true">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="encoder" location="..\..\text\encoder\encoder.ecf"/>
<library name="http" location="..\..\network\protocol\http\http.ecf"/>
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
<cluster name="specification" location="specification\" recursive="true"/>
</target>

View File

@@ -42,6 +42,14 @@ feature -- Execution
is_valid_end_of_execution: is_valid_end_of_execution
end
feature {WGI_EXPORTER, WGI_CONNECTOR} -- Execution: rescue
execute_rescue (e: detachable EXCEPTION)
-- Execute on rescue.
do
(create {WGI_RESCUE_EXECUTION}).execute (request, response, e)
end
feature -- Status report
is_valid_end_of_execution: BOOLEAN

View File

@@ -0,0 +1,56 @@
note
description: "Execution when an exception occurred."
date: "$Date$"
revision: "$Revision$"
class
WGI_RESCUE_EXECUTION
inherit
WGI_EXPORTER
SHARED_HTML_ENCODER
feature -- Execution
execute (req: detachable WGI_REQUEST; res: detachable WGI_RESPONSE; e: detachable EXCEPTION)
-- Exception or internal error occurred, return the eventual trace
-- as response.
-- `req` and `res` may be available for processing.
local
s: STRING
do
if
res /= Void and then
e /= Void and then
attached e.trace as l_trace
then
if not res.status_is_set then
res.set_status_code ({HTTP_STATUS_CODE}.internal_server_error, Void)
end
create s.make_empty
s.append ("<pre>")
s.append (html_encoder.encoded_string (l_trace))
s.append ("</pre>")
if not res.header_committed then
-- Overwrite any header previously set.
res.put_header_text ("Content-Type: text/html%R%NContent-Length: " + s.count.out + "%R%N%R%N")
end
if res.message_writable then
res.put_string (s)
end
res.push
end
end
note
copyright: "2011-2018, Jocelyn Fiat, Javier Velilla, Eiffel Software 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

@@ -10,9 +10,6 @@ class
inherit
HTTPD_CONNECTION_HANDLER_I
redefine
initialize
end
create
make

View File

@@ -11,6 +11,8 @@ inherit
HTTPD_CONSTANTS
SOCKET_TIMEOUT_UTILITIES
feature {NONE} -- Initialization
make
@@ -18,9 +20,9 @@ feature {NONE} -- Initialization
http_server_port := default_http_server_port
max_concurrent_connections := default_max_concurrent_connections
max_tcp_clients := default_max_tcp_clients
socket_timeout := default_socket_timeout
socket_recv_timeout := default_socket_recv_timeout
keep_alive_timeout := default_keep_alive_timeout
socket_timeout_ns := seconds_to_nanoseconds (default_socket_timeout)
socket_recv_timeout_ns := seconds_to_nanoseconds (default_socket_recv_timeout)
keep_alive_timeout_ns := seconds_to_nanoseconds (default_keep_alive_timeout)
max_keep_alive_requests := default_max_keep_alive_requests
is_secure := False
create secure_certificate.make_empty
@@ -39,12 +41,12 @@ feature -- Access
max_tcp_clients: INTEGER assign set_max_tcp_clients
-- Listen on socket for at most `queue' connections.
socket_timeout: INTEGER assign set_socket_timeout
socket_timeout_ns: NATURAL_64 assign set_socket_timeout_ns
-- Amount of seconds that the server waits for receipts and transmissions during communications.
-- note: with timeout of 0, socket can wait for ever.
-- By default: 60 seconds, which is appropriate for most situations.
socket_recv_timeout: INTEGER assign set_socket_recv_timeout
socket_recv_timeout_ns: NATURAL_64 assign set_socket_recv_timeout_ns
-- Amount of seconds that the server waits for receiving data during communications.
-- note: with timeout of 0, socket can wait for ever.
-- By default: 5 seconds.
@@ -65,7 +67,7 @@ feature -- Access
verbose_level: INTEGER assign set_verbose_level
-- Verbosity of output.
keep_alive_timeout: INTEGER assign set_keep_alive_timeout
keep_alive_timeout_ns: NATURAL_64 assign set_keep_alive_timeout_ns
-- Persistent connection timeout.
-- Number of seconds the server waits after a request has been served before it closes the connection.
-- Timeout unit in Seconds.
@@ -86,9 +88,9 @@ feature -- Access
do
Result.is_verbose := is_verbose
Result.verbose_level := verbose_level
Result.timeout := socket_timeout
Result.socket_recv_timeout := socket_recv_timeout
Result.keep_alive_timeout := keep_alive_timeout
Result.timeout_ns := socket_timeout_ns
Result.socket_recv_timeout_ns := socket_recv_timeout_ns
Result.keep_alive_timeout_ns := keep_alive_timeout_ns
Result.max_keep_alive_requests := max_keep_alive_requests
Result.is_secure := is_secure
end
@@ -166,28 +168,52 @@ feature -- Element change
max_concurrent_connections_set : max_concurrent_connections = v
end
set_socket_timeout (a_nb_seconds: like socket_timeout)
set_socket_timeout_ns (a_nano_seconds: like socket_timeout_ns)
-- Set `socket_timeout_ns' with `a_nano_seconds'.
require
is_valid_timeout_ns: is_valid_timeout_ns (a_nano_seconds)
do
socket_timeout_ns := a_nano_seconds
ensure
socket_timeout_ns_set: socket_timeout_ns = a_nano_seconds
end
set_socket_recv_timeout_ns (a_nano_seconds: like socket_recv_timeout_ns)
-- Set `socket_recv_timeout_ns' with `a_nano_seconds'.
require
is_valid_timeout_ns: is_valid_timeout_ns (a_nano_seconds)
do
socket_recv_timeout_ns := a_nano_seconds
ensure
socket_recv_timeout_ns_set: socket_recv_timeout_ns = a_nano_seconds
end
set_keep_alive_timeout_ns (a_nano_seconds: like keep_alive_timeout_ns)
-- Set `keep_alive_timeout_ns' with `a_nano_seconds'.
require
is_valid_timeout_ns: is_valid_timeout_ns (a_nano_seconds)
do
keep_alive_timeout_ns := a_nano_seconds
ensure
keep_alive_timeout_ns_set: keep_alive_timeout_ns = a_nano_seconds
end
set_socket_timeout (a_nb_seconds: INTEGER)
-- Set `socket_timeout' with `a_nb_seconds'.
do
socket_timeout := a_nb_seconds
ensure
socket_timeout_set: socket_timeout = a_nb_seconds
set_socket_timeout_ns (seconds_to_nanoseconds (a_nb_seconds))
end
set_socket_recv_timeout (a_nb_seconds: like socket_recv_timeout)
set_socket_recv_timeout (a_nb_seconds: INTEGER)
-- Set `socket_recv_timeout' with `a_nb_seconds'.
do
socket_recv_timeout := a_nb_seconds
ensure
socket_recv_timeout_set: socket_recv_timeout = a_nb_seconds
set_socket_recv_timeout_ns (seconds_to_nanoseconds (a_nb_seconds))
end
set_keep_alive_timeout (a_seconds: like keep_alive_timeout)
-- Set `keep_alive_timeout' with `a_seconds'.
set_keep_alive_timeout (a_nb_seconds: INTEGER)
-- Set `keep_alive_timeout' with `a_nb_seconds'.
do
keep_alive_timeout := a_seconds
ensure
keep_alive_timeout_set: keep_alive_timeout = a_seconds
set_keep_alive_timeout_ns (seconds_to_nanoseconds (a_nb_seconds))
end
set_max_keep_alive_requests (nb: like max_keep_alive_requests)

View File

@@ -9,6 +9,9 @@ note
expanded class
HTTPD_REQUEST_SETTINGS
inherit
SOCKET_TIMEOUT_UTILITIES
feature -- Access
is_verbose: BOOLEAN assign set_is_verbose
@@ -20,13 +23,13 @@ feature -- Access
is_secure: BOOLEAN assign set_is_secure
-- Is using secure connection? i.e SSL?
timeout: INTEGER assign set_timeout
timeout_ns: NATURAL_64 assign set_timeout_ns
-- Amount of seconds that the server waits for receipts and transmissions during communications.
socket_recv_timeout: INTEGER assign set_socket_recv_timeout
socket_recv_timeout_ns: NATURAL_64 assign set_socket_recv_timeout_ns
-- Amount of seconds that the server waits for receiving data on socket during communications.
keep_alive_timeout: INTEGER assign set_keep_alive_timeout
keep_alive_timeout_ns: NATURAL_64 assign set_keep_alive_timeout_ns
-- Keep-alive timeout, also known as persistent-connection timeout.
-- Number of seconds the server waits after a request has been served before it closes the connection.
-- Unit in Seconds.
@@ -34,6 +37,29 @@ feature -- Access
max_keep_alive_requests: INTEGER assign set_max_keep_alive_requests
-- Maximum number of requests allowed per persistent connection.
feature -- Access: obsolete
timeout: INTEGER assign set_timeout
obsolete
"Use `timeout_ns` [2018-10-29]"
do
Result := nanoseconds_to_seconds (timeout_ns)
end
socket_recv_timeout: INTEGER assign set_socket_recv_timeout
obsolete
"Use `socket_recv_timeout_ns` [2018-10-29]"
do
Result := nanoseconds_to_seconds (socket_recv_timeout_ns)
end
keep_alive_timeout: INTEGER assign set_keep_alive_timeout
obsolete
"Use `keep_alive_timeout_ns` [2018-10-29]"
do
Result := nanoseconds_to_seconds (keep_alive_timeout_ns)
end
feature -- Change
set_is_verbose (b: BOOLEAN)
@@ -54,24 +80,48 @@ feature -- Change
is_secure := b
end
feature -- Timeout change
set_timeout_ns (a_timeout_in_nanoseconds: NATURAL_64)
-- Set `timeout_ns' to `a_timeout_in_nanoseconds'.
do
timeout_ns := a_timeout_in_nanoseconds
end
set_socket_recv_timeout_ns (a_timeout_in_nanoseconds: NATURAL_64)
-- Set `socket_recv_timeout_ns' to `a_timeout_in_nanoseconds'.
do
socket_recv_timeout_ns := a_timeout_in_nanoseconds
end
set_keep_alive_timeout_ns (a_timeout_in_nanoseconds: NATURAL_64)
-- Set `keep_alive_timeout_ns' to `a_timeout_in_nanoseconds'.
do
keep_alive_timeout_ns := a_timeout_in_nanoseconds
end
feature -- Timeout change (in seconds)
set_timeout (a_timeout_in_seconds: INTEGER)
-- Set `timeout' to `a_timeout_in_seconds'.
do
timeout := a_timeout_in_seconds
set_timeout_ns (seconds_to_nanoseconds (a_timeout_in_seconds))
end
set_socket_recv_timeout (a_timeout_in_seconds: INTEGER)
-- Set `socket_recv_timeout' to `a_timeout_in_seconds'.
do
socket_recv_timeout := a_timeout_in_seconds
set_socket_recv_timeout_ns (seconds_to_nanoseconds (a_timeout_in_seconds))
end
set_keep_alive_timeout (a_timeout_in_seconds: INTEGER)
-- Set `keep_alive_timeout' to `a_timeout_in_seconds'.
do
keep_alive_timeout := a_timeout_in_seconds
set_keep_alive_timeout_ns (seconds_to_nanoseconds (a_timeout_in_seconds))
end
feature -- Change
set_max_keep_alive_requests (nb: like max_keep_alive_requests)
-- Set `max_keep_alive_requests' with `nb'
do
@@ -79,7 +129,7 @@ feature -- Change
end
note
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2018, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -19,9 +19,9 @@ feature {NONE} -- Initialization
do
reset
-- Import global request settings.
timeout := a_request_settings.timeout -- seconds
socket_recv_timeout := a_request_settings.socket_recv_timeout -- seconds
keep_alive_timeout := a_request_settings.keep_alive_timeout -- seconds
timeout_ns := a_request_settings.timeout_ns -- nanoseconds
socket_recv_timeout_ns := a_request_settings.socket_recv_timeout_ns -- nanoseconds
keep_alive_timeout_ns := a_request_settings.keep_alive_timeout_ns -- nanoseconds
max_keep_alive_requests := a_request_settings.max_keep_alive_requests
is_verbose := a_request_settings.is_verbose
@@ -140,7 +140,7 @@ feature -- Settings
is_persistent_connection_supported: BOOLEAN
-- Is persistent connection supported?
do
Result := {HTTPD_SERVER}.is_persistent_connection_supported and then
Result := {HTTPD_SERVER}.is_persistent_connection_supported and then
max_keep_alive_requests /= 0 --| `-1` no limit
end
@@ -148,17 +148,17 @@ feature -- Settings
-- Is next persistent connection supported?
-- note: it is relevant only if `is_persistent_connection_supported' is True.
timeout: INTEGER -- seconds
-- Amount of seconds that the server waits for receipts and transmissions during communications.
timeout_ns: NATURAL_64 -- nanoseconds
-- Amount of nanoseconds that the server waits for receipts and transmissions during communications.
socket_recv_timeout: INTEGER -- seconds
-- Amount of seconds that the server waits for receiving data on socket during communications.
socket_recv_timeout_ns: NATURAL_64 -- nanoseconds
-- Amount of nanoseconds that the server waits for receiving data on socket during communications.
max_keep_alive_requests: INTEGER
-- Maximum number of requests allowed per persistent connection.
keep_alive_timeout: INTEGER -- seconds
-- Number of seconds for persistent connection timeout.
keep_alive_timeout_ns: NATURAL_64 -- nanoseconds
-- Number of nanoseconds for persistent connection timeout.
feature -- Status report
@@ -173,7 +173,7 @@ feature -- Status change
has_error := True
if m /= Void and then is_verbose then
log (m.as_string_8, debug_level)
end
end
end
reset_error
@@ -226,6 +226,10 @@ feature -- Execution
do
l_socket := client_socket
-- Set to expected `timeout_ns`.
l_socket.set_timeout_ns (timeout_ns)
l_socket.set_recv_timeout_ns (socket_recv_timeout_ns)
-- Compute remote info once for the persistent connection.
create l_remote_info
if attached l_socket.peer_address as l_addr then
@@ -299,13 +303,13 @@ feature -- Execution
end
-- Try to get request header.
-- If the request is reusing persistent connection, use `keep_alive_timeout',
-- otherwise `socket_recv_timeout'.
-- If the request is reusing persistent connection, use `keep_alive_timeout_ns',
-- otherwise `socket_recv_timeout_ns'.
get_request_header (l_socket, a_is_reusing_connection)
if has_error then
if a_is_reusing_connection and then request_header.is_empty then
-- Close persistent connection, since no new connection occurred in the delay `keep_alive_timeout'.
-- Close persistent connection, since no new connection occurred in the delay `keep_alive_timeout_ns'.
debug ("dbglog")
dbglog ("execute_request socket=" + l_socket.descriptor.out + "} close persistent connection.")
end
@@ -407,9 +411,9 @@ feature -- Parsing
a_socket.readable
then
if a_is_reusing_connection then
a_socket.set_recv_timeout (keep_alive_timeout) -- in seconds!
a_socket.set_recv_timeout_ns (keep_alive_timeout_ns) -- in nanoseconds!
else
a_socket.set_recv_timeout (socket_recv_timeout) -- FIXME: return a 408 Request Timeout response ..
a_socket.set_recv_timeout_ns (socket_recv_timeout_ns) -- FIXME: return a 408 Request Timeout response ..
end
if
@@ -424,7 +428,7 @@ feature -- Parsing
if not has_error then
if a_is_reusing_connection then
-- Restore normal recv timeout!
a_socket.set_recv_timeout (socket_recv_timeout) -- FIXME: return a 408 Request Timeout response ..
a_socket.set_recv_timeout_ns (socket_recv_timeout_ns) -- FIXME: return a 408 Request Timeout response ..
end
from
line := next_line (a_socket)
@@ -646,7 +650,7 @@ invariant
request_header_attached: request_header /= Void
note
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2018, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -109,9 +109,9 @@ feature -- Execution
log (" - port = " + configuration.http_server_port.out)
log (" - max_tcp_clients = " + configuration.max_tcp_clients.out)
log (" - max_concurrent_connections = " + configuration.max_concurrent_connections.out)
log (" - socket_timeout = " + configuration.socket_timeout.out + " seconds")
log (" - socket_recv_timeout = " + configuration.socket_recv_timeout.out + " seconds")
log (" - keep_alive_timeout = " + configuration.keep_alive_timeout.out + " seconds")
log (" - socket_timeout = " + timeout_representation (configuration.socket_timeout_ns))
log (" - socket_recv_timeout = " + timeout_representation (configuration.socket_recv_timeout_ns))
log (" - keep_alive_timeout = " + timeout_representation (configuration.keep_alive_timeout_ns))
log (" - max_keep_alive_requests = " + configuration.max_keep_alive_requests.out)
if configuration.has_secure_support then
if configuration.is_secure then
@@ -366,8 +366,25 @@ feature -- Output
end
end
timeout_representation (a_ns: NATURAL_64): STRING
do
if 1_000 * (a_ns // 1_000) = a_ns then
if 1_000_000 * (a_ns // 1_000_000) = a_ns then
if 1_000_000_000 * (a_ns // 1_000_000_000) = a_ns then
Result := (a_ns // 1_000_000_000).out + " seconds"
else
Result := (a_ns // 1_000_000).out + " milliseconds"
end
else
Result := (a_ns // 1_000).out + " microseconds"
end
else
Result := a_ns.out + " nanoseconds"
end
end
note
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2018, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -13,7 +13,8 @@ CL.exe /c %CL_FLAGS% /TC libfcgi\libfcgi\fcgi_stdio.c libfcgi\libfcgi\fcgiapp
CL.exe /c %CL_FLAGS% /TP libfcgi\libfcgi\fcgio.cpp
link.exe %LINK_FLAGS% /DLL %E_libFCGI_OUTDIR%\fcgi_stdio.obj %E_libFCGI_OUTDIR%\fcgiapp.obj %E_libFCGI_OUTDIR%\os_win32.obj %E_libFCGI_OUTDIR%\fcgio.obj
copy %E_libFCGI_OUTDIR%\libfcgi.* %~dp0..\spec\lib\windows\msc
mkdir /S %~dp0..\spec\lib\win64\%ISE_C_COMPILER%
copy %E_libFCGI_OUTDIR%\libfcgi.* %~dp0..\spec\lib\windows\%ISE_C_COMPILER%
endlocal
exit 0
rem exit 0

View File

@@ -13,7 +13,8 @@ CL.exe /c %CL_FLAGS% /TC libfcgi\libfcgi\fcgi_stdio.c libfcgi\libfcgi\fcgiapp
CL.exe /c %CL_FLAGS% /TP libfcgi\libfcgi\fcgio.cpp
link.exe %LINK_FLAGS% /DLL %E_libFCGI_OUTDIR%\fcgi_stdio.obj %E_libFCGI_OUTDIR%\fcgiapp.obj %E_libFCGI_OUTDIR%\os_win32.obj %E_libFCGI_OUTDIR%\fcgio.obj
copy %E_libFCGI_OUTDIR%\libfcgi.* %~dp0..\spec\lib\win64\msc
mkdir /S %~dp0..\spec\lib\win64\%ISE_C_COMPILER%
copy %E_libFCGI_OUTDIR%\libfcgi.* %~dp0..\spec\lib\win64\%ISE_C_COMPILER%
endlocal
exit 0
rem exit 0

View File

@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<redirection xmlns="http://www.eiffel.com/developers/xml/configuration-1-16-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-16-0 http://www.eiffel.com/developers/xml/configuration-1-16-0.xsd" uuid="5E1C9860-2D9E-4A94-A11D-DA0FD9B31470" message="Obsolete: use libfcgi.ecf !" location="libfcgi.ecf">
</redirection>

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-16-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-16-0 http://www.eiffel.com/developers/xml/configuration-1-16-0.xsd" name="connector_libfcgi_v0" uuid="5E1C9860-2D9E-4A94-A11D-DA0FD9B31470" library_target="connector_libfcgi_v0">
<target name="connector_libfcgi_v0">
<root all_classes="true"/>
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/\.git$</exclude>
<exclude>/\.svn$</exclude>
</file_rule>
<option warning="true">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="ewsgi" location="..\..\ewsgi.ecf"/>
<library name="http" location="..\..\..\..\..\..\network\protocol\http\http.ecf"/>
<library name="libfcgi" location="..\..\..\..\..\libfcgi\libfcgi.ecf"/>
<cluster name="src" location="..\..\..\..\..\ewsgi\connectors\libfcgi\src\" recursive="true">
<file_rule>
<exclude>/wgi_.*_connector.e$</exclude>
</file_rule>
</cluster>
<cluster name="src_v0" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -1,10 +0,0 @@
${NOTE_KEYWORD}
copyright: "2011-${YEAR}, Eiffel Software 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
]"

View File

@@ -1,119 +0,0 @@
note
description: "Summary description for {WGI_LIBFCGI_CONNECTOR}."
legal: "See notice at end of class."
status: "See notice at end of class."
date: "$Date$"
revision: "$Revision$"
class
WGI_LIBFCGI_CONNECTOR
inherit
WGI_CONNECTOR
create
make
feature {NONE} -- Initialization
make (a_service: like service)
do
service := a_service
create fcgi.make
create input.make (fcgi)
create output.make (fcgi)
end
feature -- Access
Name: STRING_8 = "libFCGI"
-- Name of Current connector
Version: STRING_8 = "0.1"
-- Version of Current connector
feature {NONE} -- Access
service: WGI_SERVICE
-- Gateway Service
feature -- Server
launch
local
res: INTEGER
do
from
res := fcgi.fcgi_listen
until
res < 0
loop
process_fcgi_request (fcgi.updated_environ_variables, input, output)
res := fcgi.fcgi_listen
end
end
feature -- Execution
process_fcgi_request (vars: STRING_TABLE [READABLE_STRING_8]; a_input: like input; a_output: like output)
local
req: WGI_REQUEST_FROM_TABLE
res: detachable WGI_RESPONSE_STREAM
rescued: BOOLEAN
utf: UTF_CONVERTER
do
if not rescued then
a_input.reset
create req.make (vars, a_input, Current)
create res.make (a_output, a_output)
service.execute (req, res)
res.push
else
if attached (create {EXCEPTION_MANAGER}).last_exception as e and then attached e.trace as l_trace then
if res /= Void then
if not res.status_is_set then
res.set_status_code ({HTTP_STATUS_CODE}.internal_server_error, Void)
end
if res.message_writable then
res.put_string ("<pre>")
res.put_string (utf.string_32_to_utf_8_string_8 (l_trace))
res.put_string ("</pre>")
end
res.push
end
end
end
rescue
if not rescued then
rescued := True
retry
end
end
feature -- Input/Output
input: WGI_LIBFCGI_INPUT_STREAM
-- Input from client (from httpd server via FCGI)
output: WGI_LIBFCGI_OUTPUT_STREAM
-- Output to client (via httpd server/fcgi)
feature {NONE} -- Implementation
fcgi: FCGI
invariant
fcgi_attached: fcgi /= Void
note
copyright: "2011-2017, Eiffel Software 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

@@ -1,10 +0,0 @@
${NOTE_KEYWORD}
copyright: "2011-${YEAR}, Jocelyn Fiat, Javier Velilla, Eiffel Software 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
]"

View File

@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<redirection xmlns="http://www.eiffel.com/developers/xml/configuration-1-16-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-16-0 http://www.eiffel.com/developers/xml/configuration-1-16-0.xsd" uuid="6E00FB27-C0E2-4859-93A0-BF516C44C95F" message="Obsolete: use nino.ecf !" location="nino.ecf">
</redirection>

View File

@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-16-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-16-0 http://www.eiffel.com/developers/xml/configuration-1-16-0.xsd" name="connector_nino_v0" uuid="6E00FB27-C0E2-4859-93A0-BF516C44C95F" library_target="connector_nino_v0">
<target name="connector_nino_v0">
<root all_classes="true"/>
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/\.git$</exclude>
<exclude>/\.svn$</exclude>
</file_rule>
<option warning="true">
</option>
<capability>
<concurrency support="none"/>
</capability>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="encoder" location="..\..\..\..\..\..\text\encoder\encoder.ecf"/>
<library name="ewsgi" location="..\..\ewsgi.ecf" readonly="false"/>
<library name="http" location="..\..\..\..\..\..\network\protocol\http\http.ecf"/>
<library name="nino" location="..\..\..\..\..\..\..\contrib\library\network\server\nino\nino.ecf" readonly="false">
<renaming old_name="HTTP_CONSTANTS" new_name="NINO_HTTP_CONSTANTS"/>
</library>
<cluster name="src" location="..\..\..\..\..\ewsgi\connectors\nino\src\" recursive="true">
<file_rule>
<exclude>/.*_service.e$</exclude>
<exclude>/wgi_.*_connector.e$</exclude>
<exclude>/wgi_.*_handler.e$</exclude>
</file_rule>
</cluster>
<cluster name="src_v0" location=".\src\" recursive="true"/>
</target>
</system>

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