Compare commits
52 Commits
v1.0.6
...
es_rev1032
| Author | SHA1 | Date | |
|---|---|---|---|
| 256f7581f1 | |||
| 5df220beef | |||
| 5ff361af54 | |||
| 65525aa112 | |||
| d4bbdea5e4 | |||
| 8260336d6c | |||
| 19c14d28c7 | |||
| f1e8dfa40b | |||
| e73302639d | |||
| 1c58674523 | |||
| 7cfe0cc5ed | |||
| 4f8341e04e | |||
| 7f36e539f1 | |||
| 8241c0209a | |||
| 45179b58a3 | |||
| 31fa31bd53 | |||
| a8a3ca5b97 | |||
| 7c6fe5a04a | |||
| c8e2009638 | |||
| 627ec7aefc | |||
| e7087bcbc1 | |||
| 8d881bcd7d | |||
| ed3ad962d1 | |||
| d3e865cf6c | |||
| 9fcd30b4e1 | |||
| 0baa05cf63 | |||
| f97f59b703 | |||
| dc377b84d3 | |||
| f14431fc05 | |||
| 9577d7d82a | |||
| 99f8377721 | |||
| 73d5555532 | |||
| ce3c7ac57a | |||
|
|
8754c2d67d | ||
| 5e928b9a47 | |||
|
|
9cdfbd2538 | ||
| e4fcc863ca | |||
|
|
7c8d6b9eef | ||
| a97eb4b062 | |||
| bd5aba3db6 | |||
| d43c4edb7d | |||
| 9cdd676417 | |||
| cb273c3176 | |||
| ec7d504502 | |||
| 7ed1e815b0 | |||
| da2e26f697 | |||
| bc169d6b26 | |||
| cf2f0f09fa | |||
| 207a109e44 | |||
| 2f2e2067ba | |||
| 7aa7bf1ab2 | |||
| 8e8c3602c6 |
@@ -1,8 +1,8 @@
|
|||||||
language: eiffel
|
language: eiffel
|
||||||
before_script:
|
before_script:
|
||||||
- export current_dir=$PWD ; echo current_dir=$current_dir ; cd ..
|
- export current_dir=$PWD ; echo current_dir=$current_dir ; cd ..
|
||||||
- curl -sSL https://www.eiffel.org/setup/install.sh | bash
|
- curl -sSL https://www.eiffel.org/setup/install.sh | bash > eiffel.rc
|
||||||
- source eiffel_latest.rc
|
- source ./eiffel.rc
|
||||||
- echo `ec -version`
|
- echo `ec -version`
|
||||||
- cd $current_dir
|
- cd $current_dir
|
||||||
- echo Check projects compilation status...
|
- echo Check projects compilation status...
|
||||||
|
|||||||
62
CHANGELOG.md
@@ -7,15 +7,68 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
|
### Changed
|
||||||
|
### Deprecated
|
||||||
|
### Removed
|
||||||
|
### Fixed
|
||||||
|
### Security
|
||||||
|
|
||||||
|
## [v1.1.1] - 2019-05-30
|
||||||
|
### Added
|
||||||
|
- `wsf_proxy`: 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.
|
||||||
|
### 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.
|
||||||
|
- Removed VDRS(4) errors.
|
||||||
|
### 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).
|
- `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 support for ciphers setting in the libcurl implementation only.
|
||||||
- `http_client`: added convenient `get` and `custom` functions on HTTP_CLIENT directly.
|
- `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.
|
|
||||||
- `wsf`: added `WSF_EXECUTE_HANDLER`, and `WSF_CGI_HANDLER`. Demonstration of `WSF_CGI_HANDLER` in the new `tools/httpd` project.
|
- `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_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
|
### Changed
|
||||||
- adopted ecf version 1-16-0 and use a single .ecf file (the -safe.ecf are now redirection to normal .ecf)
|
- 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
|
### Deprecated
|
||||||
- removed support for Eiffel version before 17.05 .
|
- removed support for Eiffel version before 17.05 .
|
||||||
- SSL 2 or 3 is obsolete and will raise an exception if used.
|
- SSL 2 or 3 is obsolete and will raise an exception if used.
|
||||||
@@ -23,9 +76,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|||||||
### Fixed
|
### Fixed
|
||||||
- Removed a few obsolete calls.
|
- 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`.
|
- `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`.
|
- `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.
|
- `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
|
### Security
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,3 +14,9 @@ port: 9000
|
|||||||
repo: https://github.com/EiffelWebFramework/EWF
|
repo: https://github.com/EiffelWebFramework/EWF
|
||||||
version: v1
|
version: v1
|
||||||
download: https://github.com/EiffelWebFramework/EWF/archive/v1.zip
|
download: https://github.com/EiffelWebFramework/EWF/archive/v1.zip
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- jekyll-relative-links
|
||||||
|
relative_links:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 9.3 KiB |
@@ -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.
|
**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.
|
||||||
|
|
||||||

|

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

|

|
||||||
|
|
||||||
Other connectors:
|
Other connectors:
|
||||||
|
|
||||||
**WSF_STANDALONE_SERVICE_LAUNCHER**
|
**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*.
|
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*.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
The WSF_EXECUTION instance, in this case ```APPLICATION_EXECUTION``` is created per request, with two main attributes request: ```WSF_REQUEST``` and response: ```WSF_RESPONSE```.
|
The WSF_EXECUTION instance, in this case ```APPLICATION_EXECUTION``` is created per request, with two main attributes request: ```WSF_REQUEST``` and response: ```WSF_RESPONSE```.
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
BIN
docs/workbook/basics/images/APPLICATION_EXECUTION.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
docs/workbook/basics/images/Launcher_Hierarchy.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/workbook/basics/images/WSF_SERVICE_LAUNCHER_CGI.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
docs/workbook/basics/images/WSF_SERVICE_LAUNCHER_FCGI.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
docs/workbook/basics/images/WSF_SERVICE_LAUNCHER_STANDALONE.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
50
examples/docker/Dockerfile
Normal 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
@@ -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.
|
||||||
|
|
||||||
3
examples/docker/files/apache.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
exec /usr/sbin/apache2ctl -D FOREGROUND
|
||||||
25
examples/docker/files/build_service_fcgi
Normal 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 $?
|
||||||
20
examples/docker/files/html/.htaccess
Normal 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>
|
||||||
19
examples/docker/files/html/index.html
Normal 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>
|
||||||
53
examples/docker/files/httpd.conf
Normal 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>
|
||||||
@@ -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.
|
|
||||||
|
|
||||||
@@ -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>
|
|
||||||
@@ -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)"
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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.
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||
@@ -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>
|
|
||||||
@@ -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
|
|
||||||
@@ -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)"
|
|
||||||
|
|
||||||
@@ -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)
|
|
||||||
@@ -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>
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -2,5 +2,17 @@ Proxy example
|
|||||||
=============
|
=============
|
||||||
|
|
||||||
Via the `wsf_proxy` library, it is possible to implement a simple reverse proxy service.
|
Via the `wsf_proxy` library, it is possible to implement a simple reverse proxy service.
|
||||||
Note: you need to edit the `application_execution.e` file to use proper remote service.
|
|
||||||
(You can use for instance any of the EWF examples as remote server, or also existing public server).
|
This example forwards request based on server name, or location as described below:
|
||||||
|
- if server name is `foo`, forwards to `http://localhost:8080/foo`
|
||||||
|
- if server name is `bar`, forwards to `http://localhost:8080/bar`
|
||||||
|
- if path starts with "/search/", forwards to `http://www.google.com/search?q=`
|
||||||
|
|
||||||
|
For instance:
|
||||||
|
- http://foo/a/test -> http://localhost:8080/foo/a/test
|
||||||
|
- http://localhost:9090/search/eiffel -> http://www.google.com/search?q=eiffel
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- look at `{APPLICATION_EXECUTION}.execute` for the implementation.
|
||||||
|
- the `send_proxy_response` implements a general forwarding, but for more advanced need, this could be customized per forwarding. For instance, a specific forwarding, may need the X-Forwarded headers, or to keep the `Host` from the client, ...
|
||||||
|
|
||||||
|
|||||||
@@ -9,41 +9,66 @@ class
|
|||||||
inherit
|
inherit
|
||||||
WSF_EXECUTION
|
WSF_EXECUTION
|
||||||
|
|
||||||
WSF_URI_REWRITER
|
|
||||||
rename
|
|
||||||
uri as proxy_uri
|
|
||||||
end
|
|
||||||
|
|
||||||
create
|
create
|
||||||
make
|
make
|
||||||
|
|
||||||
feature -- Basic operations
|
feature -- Basic operations
|
||||||
|
|
||||||
execute
|
execute
|
||||||
|
local
|
||||||
|
l_forwarded: BOOLEAN
|
||||||
do
|
do
|
||||||
-- NOTE: please enter the target server uri here
|
-- Hardocoded for the example
|
||||||
-- replace "http://localhost:8080/foobar"
|
if request.server_name.same_string ("foo") then
|
||||||
send_proxy_response ("http://localhost:8080/foobar", Current)
|
send_proxy_response ("http://localhost:8080/foo", Void)
|
||||||
|
l_forwarded := True
|
||||||
|
elseif request.server_name.same_string ("bar") then
|
||||||
|
send_proxy_response ("http://localhost:8080/bar", Void)
|
||||||
|
l_forwarded := True
|
||||||
|
elseif request.path_info.starts_with_general ("/search/") then
|
||||||
|
send_proxy_response ("http://www.google.com/search?q=", agent uri_for_location_based_proxy ("/search/", ?))
|
||||||
|
else
|
||||||
|
response.send (create {WSF_PAGE_RESPONSE}.make_with_body ("EiffelWeb proxy: not forwarded!"))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
send_proxy_response (a_remote: READABLE_STRING_8; a_rewriter: detachable WSF_URI_REWRITER)
|
send_proxy_response (a_remote: READABLE_STRING_8; a_rewriter: detachable FUNCTION [WSF_REQUEST, STRING])
|
||||||
local
|
local
|
||||||
h: WSF_SIMPLE_REVERSE_PROXY_HANDLER
|
h: WSF_SIMPLE_REVERSE_PROXY_HANDLER
|
||||||
do
|
do
|
||||||
create h.make (a_remote)
|
create h.make (a_remote)
|
||||||
h.set_uri_rewriter (a_rewriter)
|
if a_rewriter /= Void then
|
||||||
h.set_uri_rewriter (create {WSF_AGENT_URI_REWRITER}.make (agent proxy_uri))
|
h.set_uri_rewriter (create {WSF_AGENT_URI_REWRITER}.make (a_rewriter))
|
||||||
h.set_timeout (30) -- 30 seconds
|
end
|
||||||
|
h.set_timeout_ns (10_000_000_000) -- 10 seconds
|
||||||
h.set_connect_timeout (5_000) -- milliseconds = 5 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)
|
||||||
|
|
||||||
|
-- For debug information, uncomment next line
|
||||||
|
-- response.put_error ("Forwarding to " + h.proxy_url (request))
|
||||||
|
|
||||||
h.execute (request, response)
|
h.execute (request, response)
|
||||||
end
|
end
|
||||||
|
|
||||||
feature -- Helpers
|
feature -- Helpers
|
||||||
|
|
||||||
proxy_uri (a_request: WSF_REQUEST): STRING
|
uri_for_location_based_proxy (a_location: READABLE_STRING_8; a_request: WSF_REQUEST): STRING
|
||||||
-- Request uri rewriten as url.
|
-- Request uri rewritten as url.
|
||||||
do
|
do
|
||||||
Result := a_request.request_uri
|
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
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ port=9090
|
|||||||
max_concurrent_connections=100
|
max_concurrent_connections=100
|
||||||
max_tcp_clients=100
|
max_tcp_clients=100
|
||||||
socket_timeout=30
|
socket_timeout=30
|
||||||
socket_recv_timeout=5
|
socket_recv_timeout=100 ms
|
||||||
|
|
||||||
#Persistent connections
|
#Persistent connections
|
||||||
keep_alive_timeout=2
|
keep_alive_timeout=50 ms
|
||||||
max_keep_alive_requests=-1
|
max_keep_alive_requests=-1
|
||||||
|
|
||||||
#SSL
|
#SSL
|
||||||
|
|||||||
@@ -151,14 +151,14 @@ feature -- Settings
|
|||||||
|
|
||||||
timeout: INTEGER
|
timeout: INTEGER
|
||||||
-- HTTP transaction timeout in seconds.
|
-- HTTP transaction timeout in seconds.
|
||||||
--| 0 means it nevers timeout
|
--| 0 means it never timeouts
|
||||||
do
|
do
|
||||||
Result := session.timeout
|
Result := session.timeout
|
||||||
end
|
end
|
||||||
|
|
||||||
connect_timeout: INTEGER
|
connect_timeout: INTEGER
|
||||||
-- HTTP connection timeout in seconds.
|
-- HTTP connection timeout in milliseconds.
|
||||||
--| 0 means it nevers timeout
|
--| 0 means it never timeouts
|
||||||
do
|
do
|
||||||
Result := session.connect_timeout
|
Result := session.connect_timeout
|
||||||
end
|
end
|
||||||
@@ -208,7 +208,7 @@ feature -- Settings
|
|||||||
end
|
end
|
||||||
|
|
||||||
note
|
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)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ feature {NONE} -- Internal
|
|||||||
create Result.make_client_by_port (a_port, a_host)
|
create Result.make_client_by_port (a_port, a_host)
|
||||||
end
|
end
|
||||||
Result.set_connect_timeout (connect_timeout)
|
Result.set_connect_timeout (connect_timeout)
|
||||||
Result.set_timeout (timeout)
|
Result.set_timeout_ns (Result.seconds_to_nanoseconds (timeout))
|
||||||
Result.connect
|
Result.connect
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -887,7 +887,7 @@ feature {NONE} -- Helpers
|
|||||||
|
|
||||||
invariant
|
invariant
|
||||||
note
|
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)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -27,10 +27,22 @@
|
|||||||
<custom name="ssl_enabled" excluded_value="true"/>
|
<custom name="ssl_enabled" excluded_value="true"/>
|
||||||
</condition>
|
</condition>
|
||||||
</cluster>
|
</cluster>
|
||||||
<cluster name="ssl_network" location="$|ssl\" recursive="true">
|
<cluster name="ssl_network" location="$|ssl\" recursive="false">
|
||||||
<condition>
|
<condition>
|
||||||
<custom name="ssl_enabled" value="true"/>
|
<custom name="ssl_enabled" value="true"/>
|
||||||
</condition>
|
</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>
|
||||||
</cluster>
|
</cluster>
|
||||||
</target>
|
</target>
|
||||||
|
|||||||
@@ -2,22 +2,9 @@ note
|
|||||||
description: "[
|
description: "[
|
||||||
Extension to HTTP_STREAM_SOCKET to support backward compatibility.
|
Extension to HTTP_STREAM_SOCKET to support backward compatibility.
|
||||||
|
|
||||||
TO BE REMOVED IN THE FUTURE, WHEN 16.05 IS OLD.
|
|
||||||
]"
|
]"
|
||||||
|
|
||||||
deferred class
|
deferred class
|
||||||
HTTP_STREAM_SECURE_SOCKET_EXT
|
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
|
end
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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))
|
expiration_set: attached expiration as l_expiration and then l_expiration.same_string (date_to_rfc1123_http_date_format (a_date))
|
||||||
end
|
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 (a_path: READABLE_STRING_8)
|
||||||
-- Set `path' to `a_path'.
|
-- Set `path' to `a_path'.
|
||||||
do
|
do
|
||||||
|
|||||||
@@ -228,7 +228,9 @@ feature -- Header: merging
|
|||||||
if line [line.count] = '%R' then
|
if line [line.count] = '%R' then
|
||||||
line.remove_tail (1)
|
line.remove_tail (1)
|
||||||
end
|
end
|
||||||
put_header (line)
|
if not line.is_empty then
|
||||||
|
put_header (line)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ feature -- Content related header
|
|||||||
--| note: see `put_content_type_with_charset' for examples.
|
--| note: see `put_content_type_with_charset' for examples.
|
||||||
local
|
local
|
||||||
s: STRING_8
|
s: STRING_8
|
||||||
|
v: READABLE_STRING_8
|
||||||
do
|
do
|
||||||
if a_params /= Void and then not a_params.is_empty then
|
if a_params /= Void and then not a_params.is_empty then
|
||||||
create s.make_from_string (a_content_type)
|
create s.make_from_string (a_content_type)
|
||||||
@@ -224,9 +225,14 @@ feature -- Content related header
|
|||||||
s.append_character (' ')
|
s.append_character (' ')
|
||||||
s.append (nv.name)
|
s.append (nv.name)
|
||||||
s.append_character ('=')
|
s.append_character ('=')
|
||||||
s.append_character ('%"')
|
v := nv.value
|
||||||
s.append (nv.value)
|
if v.has(' ') or v.has ('%T') or v.has ('=') then
|
||||||
s.append_character ('%"')
|
s.append_character ('%"')
|
||||||
|
s.append (v)
|
||||||
|
s.append_character ('%"')
|
||||||
|
else
|
||||||
|
s.append (v)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
put_header_key_value ({HTTP_HEADER_NAMES}.header_content_type, s)
|
put_header_key_value ({HTTP_HEADER_NAMES}.header_content_type, s)
|
||||||
|
|||||||
@@ -2,25 +2,34 @@ JSON Web Token (JWT)
|
|||||||
|
|
||||||
http://jwt.io/
|
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
|
# How to use
|
||||||
```eiffel
|
```eiffel
|
||||||
local
|
|
||||||
jwt: JWT
|
example
|
||||||
do
|
local
|
||||||
create jwt
|
jwt: JWS
|
||||||
tok := jwt.encoded_string ("[
|
tok: STRING
|
||||||
{"iss":"joe", "exp":1200819380,"http://example.com/is_root":true}
|
l_loader: JWT_LOADER
|
||||||
]", "secret", "HS256")
|
do
|
||||||
if
|
create jwt.make_with_json_payload ("[
|
||||||
attached jwt.decoded_string (tok, "secret", Void) as l_tok_payload and
|
{"iss":"joe", "exp":1200819380,"http://example.com/is_root":true}
|
||||||
not jwt.has_error
|
]")
|
||||||
then
|
jwt.set_algorithm_to_hs256
|
||||||
check verified: not jwt.has_unverified_token_error end
|
tok := jwt.encoded_string ("my-secret")
|
||||||
check no_error: not jwt.has_error end
|
|
||||||
print (l_tok_payload)
|
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
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
note
|
note
|
||||||
description: "Summary description for {JWS}."
|
description: "JSON Web Signature (JWS)"
|
||||||
date: "$Date$"
|
date: "$Date$"
|
||||||
revision: "$Revision$"
|
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
|
class
|
||||||
JWS
|
JWS
|
||||||
@@ -64,14 +66,16 @@ feature -- Conversion
|
|||||||
|
|
||||||
encoded_string (a_secret: READABLE_STRING_8): STRING
|
encoded_string (a_secret: READABLE_STRING_8): STRING
|
||||||
local
|
local
|
||||||
alg, sign: READABLE_STRING_8
|
sign, alg_name: READABLE_STRING_8
|
||||||
|
alg: JWT_ALG
|
||||||
l_enc_payload, l_enc_header: READABLE_STRING_8
|
l_enc_payload, l_enc_header: READABLE_STRING_8
|
||||||
do
|
do
|
||||||
reset_error
|
reset_error
|
||||||
alg := header.algorithm
|
alg_name := header.algorithm
|
||||||
if not is_supporting_signature_algorithm (alg) then
|
alg := algorithms [alg_name]
|
||||||
report_unsupported_alg_error (alg)
|
if alg = Void then
|
||||||
alg := alg_hs256 -- Default ...
|
report_unsupported_alg_error (alg_name)
|
||||||
|
alg := algorithms.hs256 -- Default ...
|
||||||
end
|
end
|
||||||
l_enc_header := base64url_encode (header.string)
|
l_enc_header := base64url_encode (header.string)
|
||||||
l_enc_payload := base64url_encode (claimset.string)
|
l_enc_payload := base64url_encode (claimset.string)
|
||||||
@@ -94,12 +98,12 @@ feature -- Element change
|
|||||||
|
|
||||||
set_algorithm_to_hs256
|
set_algorithm_to_hs256
|
||||||
do
|
do
|
||||||
set_algorithm (alg_hs256)
|
set_algorithm (algorithms.hs256.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
set_algorithm_to_none
|
set_algorithm_to_none
|
||||||
do
|
do
|
||||||
set_algorithm (alg_none)
|
set_algorithm (algorithms.none.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ note
|
|||||||
description: "JSON Web Token"
|
description: "JSON Web Token"
|
||||||
date: "$Date$"
|
date: "$Date$"
|
||||||
revision: "$Revision$"
|
revision: "$Revision$"
|
||||||
|
EIS: "name=JSON Web Token (JWT)", "src=https://tools.ietf.org/html/rfc7519", "protocol=uri"
|
||||||
|
|
||||||
deferred class
|
deferred class
|
||||||
JWT
|
JWT
|
||||||
@@ -16,12 +17,15 @@ feature {NONE} -- Initialization
|
|||||||
|
|
||||||
default_create
|
default_create
|
||||||
do
|
do
|
||||||
|
create algorithms
|
||||||
create header
|
create header
|
||||||
create claimset
|
create claimset
|
||||||
end
|
end
|
||||||
|
|
||||||
feature -- Access
|
feature -- Access
|
||||||
|
|
||||||
|
algorithms: JWT_ALGORITHMS
|
||||||
|
|
||||||
header: JWT_HEADER
|
header: JWT_HEADER
|
||||||
|
|
||||||
claimset: JWT_CLAIMSET
|
claimset: JWT_CLAIMSET
|
||||||
@@ -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
|
Result := attached errors as errs and then across errs as ic some attached {JWT_UNSUPPORTED_ALG_ERROR} ic.item end
|
||||||
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
|
has_unverified_token_error: BOOLEAN
|
||||||
do
|
do
|
||||||
Result := attached errors as errs and then across errs as ic some attached {JWT_UNVERIFIED_TOKEN_ERROR} ic.item end
|
Result := attached errors as errs and then across errs as ic some attached {JWT_UNVERIFIED_TOKEN_ERROR} ic.item end
|
||||||
|
|||||||
26
library/security/jwt/src/jwt_alg.e
Normal 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
|
||||||
56
library/security/jwt/src/jwt_alg_hs256.e
Normal 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
|
||||||
29
library/security/jwt/src/jwt_alg_none.e
Normal 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
|
||||||
99
library/security/jwt/src/jwt_algorithms.e
Normal 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
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
note
|
note
|
||||||
description: "Summary description for {JWT_CLAIMSET}."
|
description: "Object representing a JWT claim set"
|
||||||
author: ""
|
|
||||||
date: "$Date$"
|
date: "$Date$"
|
||||||
revision: "$Revision$"
|
revision: "$Revision$"
|
||||||
|
EIS: "name=JWT claims", "src=https://tools.ietf.org/html/rfc7519#section-4", "protocol=uri"
|
||||||
class
|
class
|
||||||
JWT_CLAIMSET
|
JWT_CLAIMSET
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -6,6 +6,7 @@ note
|
|||||||
]"
|
]"
|
||||||
date: "$Date$"
|
date: "$Date$"
|
||||||
revision: "$Revision$"
|
revision: "$Revision$"
|
||||||
|
EIS: "name=JOSE Header", "src=https://tools.ietf.org/html/rfc7519#section-5", "protocol=uri"
|
||||||
|
|
||||||
class
|
class
|
||||||
JWT_HEADER
|
JWT_HEADER
|
||||||
@@ -52,6 +53,10 @@ feature -- Access
|
|||||||
-- The issuer can freely set an algorithm to verify the signature on the token.
|
-- The issuer can freely set an algorithm to verify the signature on the token.
|
||||||
-- However, some supported algorithms are insecure.
|
-- 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
|
feature -- Conversion
|
||||||
|
|
||||||
string: STRING
|
string: STRING
|
||||||
@@ -67,7 +72,13 @@ feature -- Conversion
|
|||||||
end
|
end
|
||||||
Result.append (",%"alg%":%"")
|
Result.append (",%"alg%":%"")
|
||||||
Result.append (algorithm)
|
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
|
end
|
||||||
|
|
||||||
feature -- Element change
|
feature -- Element change
|
||||||
@@ -84,13 +95,21 @@ feature -- Element change
|
|||||||
|
|
||||||
set_algorithm (alg: detachable READABLE_STRING_8)
|
set_algorithm (alg: detachable READABLE_STRING_8)
|
||||||
do
|
do
|
||||||
if alg = Void then
|
if
|
||||||
|
alg = Void or else
|
||||||
|
alg.is_case_insensitive_equal ("none")
|
||||||
|
then
|
||||||
algorithm := "none"
|
algorithm := "none"
|
||||||
else
|
else
|
||||||
algorithm := alg
|
algorithm := alg.as_upper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
set_private_key_id (a_id: detachable READABLE_STRING_8)
|
||||||
|
do
|
||||||
|
private_key_id := a_id
|
||||||
|
end
|
||||||
|
|
||||||
feature -- Element change
|
feature -- Element change
|
||||||
|
|
||||||
import_json (a_json: READABLE_STRING_8)
|
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
|
if attached {JSON_STRING} jo.item ("alg") as j_alg then
|
||||||
set_algorithm (j_alg.unescaped_string_8)
|
set_algorithm (j_alg.unescaped_string_8)
|
||||||
end
|
end
|
||||||
|
if attached {JSON_STRING} jo.item ("kid") as j_kid then
|
||||||
|
set_private_key_id (j_kid.unescaped_string_8)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,20 @@ class
|
|||||||
|
|
||||||
inherit
|
inherit
|
||||||
JWT_UTILITIES
|
JWT_UTILITIES
|
||||||
|
redefine
|
||||||
|
default_create
|
||||||
|
end
|
||||||
|
|
||||||
|
feature {NONE} -- Initialization
|
||||||
|
|
||||||
|
default_create
|
||||||
|
do
|
||||||
|
create algorithms
|
||||||
|
end
|
||||||
|
|
||||||
|
feature -- Settings
|
||||||
|
|
||||||
|
algorithms: JWT_ALGORITHMS
|
||||||
|
|
||||||
feature -- Access
|
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,
|
-- 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!
|
-- leaving the possibility to use the header alg is dangerous as client may use "none" and then bypass verification!
|
||||||
require
|
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
|
local
|
||||||
jws: JWS
|
jws: JWS
|
||||||
i,j,n: INTEGER
|
i,j,n: INTEGER
|
||||||
alg, l_enc_payload, l_enc_header, l_signature: READABLE_STRING_8
|
alg, l_enc_payload, l_enc_header, l_signature: READABLE_STRING_8
|
||||||
|
alg_encoder: JWT_ALG
|
||||||
do
|
do
|
||||||
n := a_token_input.count
|
n := a_token_input.count
|
||||||
i := a_token_input.index_of ('.', 1)
|
i := a_token_input.index_of ('.', 1)
|
||||||
@@ -43,17 +58,18 @@ feature -- Access
|
|||||||
else
|
else
|
||||||
if alg = Void then
|
if alg = Void then
|
||||||
-- Use default
|
-- Use default
|
||||||
alg := alg_hs256
|
alg := algorithms.default_algorithm.name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
jws.set_algorithm (alg)
|
jws.set_algorithm (alg)
|
||||||
check alg_set: alg /= Void end
|
check alg_set: alg /= Void end
|
||||||
if ctx = Void or else not ctx.validation_ignored then
|
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)
|
jws.report_unsupported_alg_error (alg)
|
||||||
alg := alg_hs256
|
alg_encoder := algorithms.default_algorithm
|
||||||
end
|
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
|
jws.report_unverified_token_error
|
||||||
end
|
end
|
||||||
if
|
if
|
||||||
|
|||||||
@@ -1,20 +1,11 @@
|
|||||||
note
|
note
|
||||||
description: "Summary description for {JWT_UTILITIES}."
|
description: "Summary description for {JWT_UTILITIES}."
|
||||||
author: ""
|
|
||||||
date: "$Date$"
|
date: "$Date$"
|
||||||
revision: "$Revision$"
|
revision: "$Revision$"
|
||||||
|
|
||||||
class
|
class
|
||||||
JWT_UTILITIES
|
JWT_UTILITIES
|
||||||
|
|
||||||
feature -- Constants
|
|
||||||
|
|
||||||
alg_hs256: STRING = "HS256"
|
|
||||||
-- HMAC SHA256.
|
|
||||||
|
|
||||||
alg_none: STRING = "none"
|
|
||||||
-- for unsecured token.
|
|
||||||
|
|
||||||
feature -- Encoding
|
feature -- Encoding
|
||||||
|
|
||||||
base64url_encode (s: READABLE_STRING_8): STRING_8
|
base64url_encode (s: READABLE_STRING_8): STRING_8
|
||||||
@@ -35,61 +26,21 @@ feature -- Encoding
|
|||||||
Result.replace_substring_all ("/", "_")
|
Result.replace_substring_all ("/", "_")
|
||||||
end
|
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
|
local
|
||||||
s: STRING
|
s: STRING
|
||||||
do
|
do
|
||||||
if alg.is_case_insensitive_equal (alg_none) then
|
if alg.is_none then
|
||||||
create Result.make_empty
|
create Result.make_empty
|
||||||
else
|
else
|
||||||
create s.make (a_enc_header.count + 1 + a_enc_payload.count)
|
create s.make (a_enc_header.count + 1 + a_enc_payload.count)
|
||||||
s.append (a_enc_header)
|
s.append (a_enc_header)
|
||||||
s.append_character ('.')
|
s.append_character ('.')
|
||||||
s.append (a_enc_payload)
|
s.append (a_enc_payload)
|
||||||
if alg.is_case_insensitive_equal (alg_hs256) then
|
Result := urlsafe_encode (alg.encoded_string (s, a_secret))
|
||||||
Result := base64_hmacsha256 (s, a_secret)
|
|
||||||
else
|
|
||||||
Result := base64_hmacsha256 (s, a_secret)
|
|
||||||
end
|
|
||||||
Result := urlsafe_encode (Result)
|
|
||||||
end
|
end
|
||||||
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
|
feature -- Decoding
|
||||||
|
|
||||||
base64url_decode (s: READABLE_STRING_8): STRING_8
|
base64url_decode (s: READABLE_STRING_8): STRING_8
|
||||||
@@ -119,20 +70,4 @@ feature -- Decoding
|
|||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|||||||
22
library/security/jwt/testing/jwt_alg_test.e
Normal 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
|
||||||
@@ -16,6 +16,30 @@ inherit
|
|||||||
|
|
||||||
feature -- Test
|
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
|
test_jwt_io
|
||||||
local
|
local
|
||||||
jwt: JWS
|
jwt: JWS
|
||||||
@@ -33,6 +57,22 @@ feature -- Test
|
|||||||
assert ("signature", jwt.encoded_string ("secret").same_string ("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.pcHcZspUvuiqIPVB_i_qmcvCJv63KLUgIAKIlXI1gY8"))
|
assert ("signature", jwt.encoded_string ("secret").same_string ("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.pcHcZspUvuiqIPVB_i_qmcvCJv63KLUgIAKIlXI1gY8"))
|
||||||
end
|
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
|
test_jwt
|
||||||
local
|
local
|
||||||
jwt: JWS
|
jwt: JWS
|
||||||
@@ -185,7 +225,8 @@ feature -- Test
|
|||||||
tok := jwt.encoded_string ("secret")
|
tok := jwt.encoded_string ("secret")
|
||||||
|
|
||||||
if attached (create {JWT_LOADER}).token (tok, "HS256", "secret", Void) as l_tok then
|
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))
|
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -205,15 +246,50 @@ feature -- Test
|
|||||||
tok := jwt.encoded_string ("secret")
|
tok := jwt.encoded_string ("secret")
|
||||||
|
|
||||||
if attached (create {JWT_LOADER}).token (tok, "none", "secret", Void) as l_tok then
|
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))
|
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
||||||
end
|
end
|
||||||
if attached (create {JWT_LOADER}).token (tok, Void, "secret", Void) as l_tok then
|
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))
|
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
||||||
end
|
end
|
||||||
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
|
feature -- Implementation
|
||||||
|
|
||||||
duplicated_time (dt: DATE_TIME): DATE_TIME
|
duplicated_time (dt: DATE_TIME): DATE_TIME
|
||||||
|
|||||||
@@ -38,11 +38,11 @@ feature -- Execution
|
|||||||
exec.execute
|
exec.execute
|
||||||
res.push
|
res.push
|
||||||
exec.clean
|
exec.clean
|
||||||
|
elseif exec /= Void then
|
||||||
|
exec.execute_rescue ((create {EXCEPTION_MANAGER}).last_exception)
|
||||||
|
exec.clean
|
||||||
else
|
else
|
||||||
process_rescue (res)
|
(create {WGI_RESCUE_EXECUTION}).execute (req, res, (create {EXCEPTION_MANAGER}).last_exception)
|
||||||
if exec /= Void then
|
|
||||||
exec.clean
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
rescue
|
rescue
|
||||||
if not rescued then
|
if not rescued then
|
||||||
@@ -51,24 +51,6 @@ feature -- Execution
|
|||||||
end
|
end
|
||||||
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
|
note
|
||||||
copyright: "2011-2015, Eiffel Software and others"
|
copyright: "2011-2015, Eiffel Software and others"
|
||||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
|
|||||||
@@ -73,11 +73,11 @@ feature -- Execution
|
|||||||
exec.execute
|
exec.execute
|
||||||
res.push
|
res.push
|
||||||
exec.clean
|
exec.clean
|
||||||
|
elseif exec /= Void then
|
||||||
|
exec.execute_rescue ((create {EXCEPTION_MANAGER}).last_exception)
|
||||||
|
exec.clean
|
||||||
else
|
else
|
||||||
process_rescue (res)
|
(create {WGI_RESCUE_EXECUTION}).execute (req, res, (create {EXCEPTION_MANAGER}).last_exception)
|
||||||
if exec /= Void then
|
|
||||||
exec.clean
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
rescue
|
rescue
|
||||||
if not rescued then
|
if not rescued then
|
||||||
@@ -86,24 +86,6 @@ feature -- Execution
|
|||||||
end
|
end
|
||||||
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
|
feature -- Input/Output
|
||||||
|
|
||||||
input: WGI_LIBFCGI_INPUT_STREAM
|
input: WGI_LIBFCGI_INPUT_STREAM
|
||||||
|
|||||||
@@ -88,13 +88,15 @@ feature -- Request processing
|
|||||||
exec.execute
|
exec.execute
|
||||||
res.push
|
res.push
|
||||||
exec.clean
|
exec.clean
|
||||||
else
|
elseif exec /= Void then
|
||||||
if not has_error 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
|
end
|
||||||
rescue
|
rescue
|
||||||
if l_output = Void or else not l_output.is_available then
|
if l_output = Void or else not l_output.is_available then
|
||||||
@@ -106,31 +108,6 @@ feature -- Request processing
|
|||||||
end
|
end
|
||||||
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]
|
httpd_environment (a_socket: HTTPD_STREAM_SOCKET): STRING_TABLE [READABLE_STRING_8]
|
||||||
local
|
local
|
||||||
p: INTEGER
|
p: INTEGER
|
||||||
|
|||||||
@@ -110,7 +110,9 @@ feature -- Header output operation
|
|||||||
if
|
if
|
||||||
not l_connection.is_case_insensitive_equal_general ("close")
|
not l_connection.is_case_insensitive_equal_general ("close")
|
||||||
then
|
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
|
end
|
||||||
elseif not is_http_version_1_0 then
|
elseif not is_http_version_1_0 then
|
||||||
-- HTTP/1.1: always return "close" since persistent connection is not supported.
|
-- HTTP/1.1: always return "close" since persistent connection is not supported.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
<?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">
|
<target name="ewsgi_spec">
|
||||||
<root all_classes="true"/>
|
<root all_classes="true"/>
|
||||||
<file_rule>
|
<file_rule>
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
<option warning="true">
|
<option warning="true">
|
||||||
</option>
|
</option>
|
||||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
<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"/>
|
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
|
||||||
<cluster name="specification" location="specification\" recursive="true"/>
|
<cluster name="specification" location="specification\" recursive="true"/>
|
||||||
</target>
|
</target>
|
||||||
|
|||||||
@@ -42,6 +42,14 @@ feature -- Execution
|
|||||||
is_valid_end_of_execution: is_valid_end_of_execution
|
is_valid_end_of_execution: is_valid_end_of_execution
|
||||||
end
|
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
|
feature -- Status report
|
||||||
|
|
||||||
is_valid_end_of_execution: BOOLEAN
|
is_valid_end_of_execution: BOOLEAN
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -10,9 +10,6 @@ class
|
|||||||
|
|
||||||
inherit
|
inherit
|
||||||
HTTPD_CONNECTION_HANDLER_I
|
HTTPD_CONNECTION_HANDLER_I
|
||||||
redefine
|
|
||||||
initialize
|
|
||||||
end
|
|
||||||
|
|
||||||
create
|
create
|
||||||
make
|
make
|
||||||
|
|||||||
@@ -10,9 +10,6 @@ class
|
|||||||
|
|
||||||
inherit
|
inherit
|
||||||
HTTPD_CONNECTION_HANDLER_I
|
HTTPD_CONNECTION_HANDLER_I
|
||||||
redefine
|
|
||||||
initialize
|
|
||||||
end
|
|
||||||
|
|
||||||
create
|
create
|
||||||
make
|
make
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ inherit
|
|||||||
|
|
||||||
HTTPD_CONSTANTS
|
HTTPD_CONSTANTS
|
||||||
|
|
||||||
|
SOCKET_TIMEOUT_UTILITIES
|
||||||
|
|
||||||
feature {NONE} -- Initialization
|
feature {NONE} -- Initialization
|
||||||
|
|
||||||
make
|
make
|
||||||
@@ -18,9 +20,9 @@ feature {NONE} -- Initialization
|
|||||||
http_server_port := default_http_server_port
|
http_server_port := default_http_server_port
|
||||||
max_concurrent_connections := default_max_concurrent_connections
|
max_concurrent_connections := default_max_concurrent_connections
|
||||||
max_tcp_clients := default_max_tcp_clients
|
max_tcp_clients := default_max_tcp_clients
|
||||||
socket_timeout := default_socket_timeout
|
socket_timeout_ns := seconds_to_nanoseconds (default_socket_timeout)
|
||||||
socket_recv_timeout := default_socket_recv_timeout
|
socket_recv_timeout_ns := seconds_to_nanoseconds (default_socket_recv_timeout)
|
||||||
keep_alive_timeout := default_keep_alive_timeout
|
keep_alive_timeout_ns := seconds_to_nanoseconds (default_keep_alive_timeout)
|
||||||
max_keep_alive_requests := default_max_keep_alive_requests
|
max_keep_alive_requests := default_max_keep_alive_requests
|
||||||
is_secure := False
|
is_secure := False
|
||||||
create secure_certificate.make_empty
|
create secure_certificate.make_empty
|
||||||
@@ -39,12 +41,12 @@ feature -- Access
|
|||||||
max_tcp_clients: INTEGER assign set_max_tcp_clients
|
max_tcp_clients: INTEGER assign set_max_tcp_clients
|
||||||
-- Listen on socket for at most `queue' connections.
|
-- 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.
|
-- Amount of seconds that the server waits for receipts and transmissions during communications.
|
||||||
-- note: with timeout of 0, socket can wait for ever.
|
-- note: with timeout of 0, socket can wait for ever.
|
||||||
-- By default: 60 seconds, which is appropriate for most situations.
|
-- 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.
|
-- Amount of seconds that the server waits for receiving data during communications.
|
||||||
-- note: with timeout of 0, socket can wait for ever.
|
-- note: with timeout of 0, socket can wait for ever.
|
||||||
-- By default: 5 seconds.
|
-- By default: 5 seconds.
|
||||||
@@ -65,7 +67,7 @@ feature -- Access
|
|||||||
verbose_level: INTEGER assign set_verbose_level
|
verbose_level: INTEGER assign set_verbose_level
|
||||||
-- Verbosity of output.
|
-- 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.
|
-- Persistent connection timeout.
|
||||||
-- Number of seconds the server waits after a request has been served before it closes the connection.
|
-- Number of seconds the server waits after a request has been served before it closes the connection.
|
||||||
-- Timeout unit in Seconds.
|
-- Timeout unit in Seconds.
|
||||||
@@ -86,9 +88,9 @@ feature -- Access
|
|||||||
do
|
do
|
||||||
Result.is_verbose := is_verbose
|
Result.is_verbose := is_verbose
|
||||||
Result.verbose_level := verbose_level
|
Result.verbose_level := verbose_level
|
||||||
Result.timeout := socket_timeout
|
Result.timeout_ns := socket_timeout_ns
|
||||||
Result.socket_recv_timeout := socket_recv_timeout
|
Result.socket_recv_timeout_ns := socket_recv_timeout_ns
|
||||||
Result.keep_alive_timeout := keep_alive_timeout
|
Result.keep_alive_timeout_ns := keep_alive_timeout_ns
|
||||||
Result.max_keep_alive_requests := max_keep_alive_requests
|
Result.max_keep_alive_requests := max_keep_alive_requests
|
||||||
Result.is_secure := is_secure
|
Result.is_secure := is_secure
|
||||||
end
|
end
|
||||||
@@ -166,28 +168,52 @@ feature -- Element change
|
|||||||
max_concurrent_connections_set : max_concurrent_connections = v
|
max_concurrent_connections_set : max_concurrent_connections = v
|
||||||
end
|
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'.
|
-- Set `socket_timeout' with `a_nb_seconds'.
|
||||||
do
|
do
|
||||||
socket_timeout := a_nb_seconds
|
set_socket_timeout_ns (seconds_to_nanoseconds (a_nb_seconds))
|
||||||
ensure
|
|
||||||
socket_timeout_set: socket_timeout = a_nb_seconds
|
|
||||||
end
|
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'.
|
-- Set `socket_recv_timeout' with `a_nb_seconds'.
|
||||||
do
|
do
|
||||||
socket_recv_timeout := a_nb_seconds
|
set_socket_recv_timeout_ns (seconds_to_nanoseconds (a_nb_seconds))
|
||||||
ensure
|
|
||||||
socket_recv_timeout_set: socket_recv_timeout = a_nb_seconds
|
|
||||||
end
|
end
|
||||||
|
|
||||||
set_keep_alive_timeout (a_seconds: like keep_alive_timeout)
|
set_keep_alive_timeout (a_nb_seconds: INTEGER)
|
||||||
-- Set `keep_alive_timeout' with `a_seconds'.
|
-- Set `keep_alive_timeout' with `a_nb_seconds'.
|
||||||
do
|
do
|
||||||
keep_alive_timeout := a_seconds
|
set_keep_alive_timeout_ns (seconds_to_nanoseconds (a_nb_seconds))
|
||||||
ensure
|
|
||||||
keep_alive_timeout_set: keep_alive_timeout = a_seconds
|
|
||||||
end
|
end
|
||||||
|
|
||||||
set_max_keep_alive_requests (nb: like max_keep_alive_requests)
|
set_max_keep_alive_requests (nb: like max_keep_alive_requests)
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ note
|
|||||||
expanded class
|
expanded class
|
||||||
HTTPD_REQUEST_SETTINGS
|
HTTPD_REQUEST_SETTINGS
|
||||||
|
|
||||||
|
inherit
|
||||||
|
SOCKET_TIMEOUT_UTILITIES
|
||||||
|
|
||||||
feature -- Access
|
feature -- Access
|
||||||
|
|
||||||
is_verbose: BOOLEAN assign set_is_verbose
|
is_verbose: BOOLEAN assign set_is_verbose
|
||||||
@@ -20,13 +23,13 @@ feature -- Access
|
|||||||
is_secure: BOOLEAN assign set_is_secure
|
is_secure: BOOLEAN assign set_is_secure
|
||||||
-- Is using secure connection? i.e SSL?
|
-- 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.
|
-- 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.
|
-- 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.
|
-- 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.
|
-- Number of seconds the server waits after a request has been served before it closes the connection.
|
||||||
-- Unit in Seconds.
|
-- Unit in Seconds.
|
||||||
@@ -34,6 +37,29 @@ feature -- Access
|
|||||||
max_keep_alive_requests: INTEGER assign set_max_keep_alive_requests
|
max_keep_alive_requests: INTEGER assign set_max_keep_alive_requests
|
||||||
-- Maximum number of requests allowed per persistent connection.
|
-- 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
|
feature -- Change
|
||||||
|
|
||||||
set_is_verbose (b: BOOLEAN)
|
set_is_verbose (b: BOOLEAN)
|
||||||
@@ -54,24 +80,48 @@ feature -- Change
|
|||||||
is_secure := b
|
is_secure := b
|
||||||
end
|
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 (a_timeout_in_seconds: INTEGER)
|
||||||
-- Set `timeout' to `a_timeout_in_seconds'.
|
-- Set `timeout' to `a_timeout_in_seconds'.
|
||||||
do
|
do
|
||||||
timeout := a_timeout_in_seconds
|
set_timeout_ns (seconds_to_nanoseconds (a_timeout_in_seconds))
|
||||||
end
|
end
|
||||||
|
|
||||||
set_socket_recv_timeout (a_timeout_in_seconds: INTEGER)
|
set_socket_recv_timeout (a_timeout_in_seconds: INTEGER)
|
||||||
-- Set `socket_recv_timeout' to `a_timeout_in_seconds'.
|
-- Set `socket_recv_timeout' to `a_timeout_in_seconds'.
|
||||||
do
|
do
|
||||||
socket_recv_timeout := a_timeout_in_seconds
|
set_socket_recv_timeout_ns (seconds_to_nanoseconds (a_timeout_in_seconds))
|
||||||
end
|
end
|
||||||
|
|
||||||
set_keep_alive_timeout (a_timeout_in_seconds: INTEGER)
|
set_keep_alive_timeout (a_timeout_in_seconds: INTEGER)
|
||||||
-- Set `keep_alive_timeout' to `a_timeout_in_seconds'.
|
-- Set `keep_alive_timeout' to `a_timeout_in_seconds'.
|
||||||
do
|
do
|
||||||
keep_alive_timeout := a_timeout_in_seconds
|
set_keep_alive_timeout_ns (seconds_to_nanoseconds (a_timeout_in_seconds))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
feature -- Change
|
||||||
|
|
||||||
set_max_keep_alive_requests (nb: like max_keep_alive_requests)
|
set_max_keep_alive_requests (nb: like max_keep_alive_requests)
|
||||||
-- Set `max_keep_alive_requests' with `nb'
|
-- Set `max_keep_alive_requests' with `nb'
|
||||||
do
|
do
|
||||||
@@ -79,7 +129,7 @@ feature -- Change
|
|||||||
end
|
end
|
||||||
|
|
||||||
note
|
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)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ feature {NONE} -- Initialization
|
|||||||
do
|
do
|
||||||
reset
|
reset
|
||||||
-- Import global request settings.
|
-- Import global request settings.
|
||||||
timeout := a_request_settings.timeout -- seconds
|
timeout_ns := a_request_settings.timeout_ns -- nanoseconds
|
||||||
socket_recv_timeout := a_request_settings.socket_recv_timeout -- seconds
|
socket_recv_timeout_ns := a_request_settings.socket_recv_timeout_ns -- nanoseconds
|
||||||
keep_alive_timeout := a_request_settings.keep_alive_timeout -- seconds
|
keep_alive_timeout_ns := a_request_settings.keep_alive_timeout_ns -- nanoseconds
|
||||||
max_keep_alive_requests := a_request_settings.max_keep_alive_requests
|
max_keep_alive_requests := a_request_settings.max_keep_alive_requests
|
||||||
|
|
||||||
is_verbose := a_request_settings.is_verbose
|
is_verbose := a_request_settings.is_verbose
|
||||||
@@ -148,17 +148,17 @@ feature -- Settings
|
|||||||
-- Is next persistent connection supported?
|
-- Is next persistent connection supported?
|
||||||
-- note: it is relevant only if `is_persistent_connection_supported' is True.
|
-- note: it is relevant only if `is_persistent_connection_supported' is True.
|
||||||
|
|
||||||
timeout: INTEGER -- seconds
|
timeout_ns: NATURAL_64 -- nanoseconds
|
||||||
-- Amount of seconds that the server waits for receipts and transmissions during communications.
|
-- Amount of nanoseconds that the server waits for receipts and transmissions during communications.
|
||||||
|
|
||||||
socket_recv_timeout: INTEGER -- seconds
|
socket_recv_timeout_ns: NATURAL_64 -- nanoseconds
|
||||||
-- Amount of seconds that the server waits for receiving data on socket during communications.
|
-- Amount of nanoseconds that the server waits for receiving data on socket during communications.
|
||||||
|
|
||||||
max_keep_alive_requests: INTEGER
|
max_keep_alive_requests: INTEGER
|
||||||
-- Maximum number of requests allowed per persistent connection.
|
-- Maximum number of requests allowed per persistent connection.
|
||||||
|
|
||||||
keep_alive_timeout: INTEGER -- seconds
|
keep_alive_timeout_ns: NATURAL_64 -- nanoseconds
|
||||||
-- Number of seconds for persistent connection timeout.
|
-- Number of nanoseconds for persistent connection timeout.
|
||||||
|
|
||||||
feature -- Status report
|
feature -- Status report
|
||||||
|
|
||||||
@@ -226,6 +226,10 @@ feature -- Execution
|
|||||||
do
|
do
|
||||||
l_socket := client_socket
|
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.
|
-- Compute remote info once for the persistent connection.
|
||||||
create l_remote_info
|
create l_remote_info
|
||||||
if attached l_socket.peer_address as l_addr then
|
if attached l_socket.peer_address as l_addr then
|
||||||
@@ -299,13 +303,13 @@ feature -- Execution
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Try to get request header.
|
-- Try to get request header.
|
||||||
-- If the request is reusing persistent connection, use `keep_alive_timeout',
|
-- If the request is reusing persistent connection, use `keep_alive_timeout_ns',
|
||||||
-- otherwise `socket_recv_timeout'.
|
-- otherwise `socket_recv_timeout_ns'.
|
||||||
get_request_header (l_socket, a_is_reusing_connection)
|
get_request_header (l_socket, a_is_reusing_connection)
|
||||||
|
|
||||||
if has_error then
|
if has_error then
|
||||||
if a_is_reusing_connection and then request_header.is_empty 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")
|
debug ("dbglog")
|
||||||
dbglog ("execute_request socket=" + l_socket.descriptor.out + "} close persistent connection.")
|
dbglog ("execute_request socket=" + l_socket.descriptor.out + "} close persistent connection.")
|
||||||
end
|
end
|
||||||
@@ -407,9 +411,9 @@ feature -- Parsing
|
|||||||
a_socket.readable
|
a_socket.readable
|
||||||
then
|
then
|
||||||
if a_is_reusing_connection 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
|
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
|
end
|
||||||
|
|
||||||
if
|
if
|
||||||
@@ -424,7 +428,7 @@ feature -- Parsing
|
|||||||
if not has_error then
|
if not has_error then
|
||||||
if a_is_reusing_connection then
|
if a_is_reusing_connection then
|
||||||
-- Restore normal recv timeout!
|
-- 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
|
end
|
||||||
from
|
from
|
||||||
line := next_line (a_socket)
|
line := next_line (a_socket)
|
||||||
@@ -646,7 +650,7 @@ invariant
|
|||||||
request_header_attached: request_header /= Void
|
request_header_attached: request_header /= Void
|
||||||
|
|
||||||
note
|
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)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -109,9 +109,9 @@ feature -- Execution
|
|||||||
log (" - port = " + configuration.http_server_port.out)
|
log (" - port = " + configuration.http_server_port.out)
|
||||||
log (" - max_tcp_clients = " + configuration.max_tcp_clients.out)
|
log (" - max_tcp_clients = " + configuration.max_tcp_clients.out)
|
||||||
log (" - max_concurrent_connections = " + configuration.max_concurrent_connections.out)
|
log (" - max_concurrent_connections = " + configuration.max_concurrent_connections.out)
|
||||||
log (" - socket_timeout = " + configuration.socket_timeout.out + " seconds")
|
log (" - socket_timeout = " + timeout_representation (configuration.socket_timeout_ns))
|
||||||
log (" - socket_recv_timeout = " + configuration.socket_recv_timeout.out + " seconds")
|
log (" - socket_recv_timeout = " + timeout_representation (configuration.socket_recv_timeout_ns))
|
||||||
log (" - keep_alive_timeout = " + configuration.keep_alive_timeout.out + " seconds")
|
log (" - keep_alive_timeout = " + timeout_representation (configuration.keep_alive_timeout_ns))
|
||||||
log (" - max_keep_alive_requests = " + configuration.max_keep_alive_requests.out)
|
log (" - max_keep_alive_requests = " + configuration.max_keep_alive_requests.out)
|
||||||
if configuration.has_secure_support then
|
if configuration.has_secure_support then
|
||||||
if configuration.is_secure then
|
if configuration.is_secure then
|
||||||
@@ -366,8 +366,25 @@ feature -- Output
|
|||||||
end
|
end
|
||||||
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
|
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)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -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
|
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
|
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
|
endlocal
|
||||||
exit 0
|
rem exit 0
|
||||||
|
|||||||
@@ -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
|
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
|
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
|
endlocal
|
||||||
exit 0
|
rem exit 0
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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
|
|
||||||
]"
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
]"
|
|
||||||
@@ -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>
|
|
||||||