Compare commits
29 Commits
es_rev9865
...
reverse_pr
| Author | SHA1 | Date | |
|---|---|---|---|
| 5029049ef0 | |||
| 210fae5000 | |||
| 9cc9b95190 | |||
| 8b172b5d33 | |||
| cc2d7dbb1c | |||
| c88394b9fd | |||
| 4283662f43 | |||
| 1b951376f9 | |||
| 193cc3cbde | |||
| b49e841ac7 | |||
| 8ba74e1c90 | |||
| 0cecb9594c | |||
| e384a6d6ed | |||
| 71a5c086a5 | |||
| dfa60bf8f5 | |||
| 113aa69efc | |||
| af5fc75743 | |||
|
|
e53c960a89 | ||
|
|
63be2c278c | ||
|
|
f8ba741aa2 | ||
|
|
fe07af587d | ||
|
|
a3a9dd1393 | ||
|
|
fbb860024d | ||
|
|
a14488346f | ||
|
|
f74d1b3069 | ||
|
|
1ba3528974 | ||
| 5890ca6f73 | |||
| 7f4bf09d84 | |||
|
|
ad90e7c135 |
10
doc/readme.md
Normal file
10
doc/readme.md
Normal file
@@ -0,0 +1,10 @@
|
||||
Welcome to the EiffelWeb framework documentation.
|
||||
|
||||
The EiffelWeb framework is also known as *EWF*.
|
||||
|
||||
The [Workbook](/doc/workbook/workbook.md) lets you discover the EiffelWeb framework.
|
||||
|
||||
And you may found in the *old* [Wiki](/doc/wiki/readme.md) notes that were not migrated to the [Workbook](/doc/workbook/workbook.md), but consider them as obsolete.
|
||||
|
||||
[Enter the documentation](/doc/workbook/workbook.md)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
## Diagram: Overview of the server architecture ##
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Nav: [Workbook](../workbook.md) | [Handling Requests: Form/Query Parameter](/doc/workbook/handling_request/form.md)
|
||||
Nav: [Workbook](../workbook.md) :: [Handling Requests: Form/Query Parameter](../handling_request/form.md)
|
||||
|
||||
|
||||
## EWF basic service
|
||||
@@ -11,14 +11,15 @@ Nav: [Workbook](../workbook.md) | [Handling Requests: Form/Query Parameter](/doc
|
||||
- [Source code](#source_2)
|
||||
|
||||
|
||||
<a name="structure"/>
|
||||
<a name="structure"></a>
|
||||
|
||||
## EWF service structure
|
||||
|
||||
The following code describes the basic structure of an EWF basic service that handles HTTP requests. We will need to define a Service Launcher and a Request Execution implementation.
|
||||
|
||||
```eiffel
|
||||
class
|
||||
APPLICACTION
|
||||
APPLICATION
|
||||
|
||||
inherit
|
||||
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
|
||||
@@ -91,11 +92,11 @@ 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 you want to run it cgi, fcgi,nino or standalone. For development is recommended to use a standalone web server written in Eiffel, and run the execution within the EiffelStudio debugger. For production fcgi (or cgi) using Apache or another popular web server.
|
||||
|
||||

|
||||

|
||||
|
||||
**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:
|
||||
|
||||
**WSF_STANDALONE_SERVICE_LAUNCHER**
|
||||
@@ -109,11 +110,12 @@ The **APPLICATION_EXECUTION** class inherits from **WSF_EXECUTION** interface,
|
||||
|
||||
In the **APPLICATION_EXECUTION** class class you will need to implement implement the **execute** feature, get data from the request *req* and write the response in *res*.
|
||||
|
||||

|
||||

|
||||
|
||||
The WSF_EXECUTION instance, in this case ```APPLICATION_EXECUTION``` is created per request, with two main attributes request: ```WSF_REQUEST``` and response: ```WSF_RESPONSE```.
|
||||
|
||||
<a name="text"/>
|
||||
<a name="text"></a>
|
||||
|
||||
## A simple Service to Generate Plain Text.
|
||||
|
||||
Before to continue, it is recommended to review the getting started guided. In the example we will only shows the implementation of the WSF_EXECUTION interface.
|
||||
@@ -142,16 +144,18 @@ end
|
||||
|
||||
```
|
||||
<a name="source_1"></a>
|
||||
##### Source code
|
||||
|
||||
### Source code
|
||||
The source code is available on Github. You can get it by running the command:
|
||||
|
||||
```git clone https://github.com/EiffelWebFramework/ewf.git```
|
||||
|
||||
The example of simple service that generate plain text response is located in the directory $PATH/ewd/doc/workbook/basics/simple, where $PATH is where you run ```git clone``` . Just double click on the simple.ecf file and select the simple_nino target or if you prefer the command line, run the command:
|
||||
The example of simple service that generate plain text response is located in the directory $PATH/ewf/doc/workbook/basics/simple, where $PATH is where you run ```git clone``` . Just double click on the simple.ecf file and select the simple_nino target or if you prefer the command line, run the command:
|
||||
|
||||
```estudio -config simple.ecf -target simple_nino```
|
||||
|
||||
<a name="html"></a>
|
||||
|
||||
## A Service to Generate HTML.
|
||||
To generate HTML, it's needed
|
||||
|
||||
@@ -193,7 +197,10 @@ feature -- Basic operations
|
||||
|
||||
end
|
||||
```
|
||||
##### Source code
|
||||
|
||||
<a name="source_2"></a>
|
||||
|
||||
### Source code
|
||||
The source code is available on Github. You can get it by running the command:
|
||||
|
||||
```git clone https://github.com/EiffelWebFramework/ewf.git```
|
||||
@@ -202,5 +209,5 @@ The example of the service that generates HTML is located in the directory $PATH
|
||||
|
||||
```estudio -config simple_html.ecf -target simple_html_nino```
|
||||
|
||||
Nav: [Workbook](../workbook.md) | [Handling Requests: Form/Query Parameter](/doc/workbook/handling_request/form.md)
|
||||
Nav: [Workbook](../workbook.md) :: [Handling Requests: Form/Query Parameter](../handling_request/form.md)
|
||||
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
##Run simple_html example on Apache with FCGI on Windows.
|
||||
Nav: [Workbook](../../../workbook.md) :: [Basic concepts](../../../basics/basics.md)
|
||||
|
||||
## Run simple_html example on Apache with FCGI on Windows.
|
||||
|
||||
|
||||
|
||||
####Prerequisites
|
||||
#### Prerequisites
|
||||
|
||||
* This tutorial was written for people working under Windows environment, and using Apache Server with FCGI connector
|
||||
* Compile the ewf application from command line.
|
||||
* Assuming you have installed Apache Server under C:/home/server/Apache24.
|
||||
* Assuming you have placed your current project under C:/home/server/Apache24/fcgi-bin.
|
||||
* Assuming you have setted the Listen to 8888, the defautl value is 80 .
|
||||
* Assuming you have installed Apache Server under `C:/home/server/Apache24`.
|
||||
* Assuming you have placed your current project under `C:/home/server/Apache24/fcgi-bin`.
|
||||
* Assuming you have setted the Listen to `8888`, the defautl value is `80` .
|
||||
|
||||
|
||||
|
||||
####FCGI module
|
||||
#### FCGI module
|
||||
If you don't have the FCGI module installed, you can get it from https://www.apachelounge.com/download/, download the module based on your platform [modules-2.4-win64-VC11.zip](https://www.apachelounge.com/download/VC11/modules/modules-2.4-win64-VC11.zip) or [modules-2.4-win32-VC11.zip](https://www.apachelounge.com/download/VC11/modules/modules-2.4-win32-VC11.zip), uncompress it
|
||||
and copy the _mod_fcgid.so_ to C:/home/server/Apache24/modules
|
||||
and copy the _mod_fcgid.so_ to `C:/home/server/Apache24/modules`
|
||||
|
||||
####Compile the project simple_html using the fcgi connector.
|
||||
#### Compile the project simple_html using the fcgi connector.
|
||||
|
||||
ec -config simple_html.ecf -target simple_html_fcgi -finalize -c_compile -project_path .
|
||||
|
||||
Copy the genereted exe to C:/home/server/Apache24/fcgi-bin folder.
|
||||
Copy the genereted exe to `C:/home/server/Apache24/fcgi-bin` folder.
|
||||
|
||||
Check if you have _libfcgi.dll_ in your PATH.
|
||||
|
||||
|
||||
####Apache configuration
|
||||
#### Apache configuration
|
||||
Add to httpd.conf the content, you can get the configuration file [here](config.conf)
|
||||
|
||||
```
|
||||
@@ -43,10 +44,18 @@ LoadModule fcgid_module modules/mod_fcgid.so
|
||||
```
|
||||
|
||||
Test if your httpd.conf is ok
|
||||
>httpd -t
|
||||
```
|
||||
> httpd -t
|
||||
```
|
||||
|
||||
Luanch the server
|
||||
>httpd
|
||||
Launch the server
|
||||
```
|
||||
> httpd
|
||||
```
|
||||
|
||||
Check the application
|
||||
>http://localhost:8888/simple
|
||||
```
|
||||
> http://localhost:8888/simple
|
||||
```
|
||||
|
||||
Nav: [Workbook](../../../workbook.md) :: [Basic concepts](../../../basics/basics.md)
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
#include <windows.h>
|
||||
|
||||
STRINGTABLE
|
||||
BEGIN
|
||||
1 "This Program was made using EiffelStudio using Visual Studio C++"
|
||||
END
|
||||
@@ -1,7 +1,9 @@
|
||||
Nav: [Workbook](../workbook.md)
|
||||
|
||||
EWF Deployment
|
||||
==============
|
||||
|
||||
#Apache on Windows#
|
||||
# Apache on Windows#
|
||||
|
||||
1. Apache Install
|
||||
2. Deploying EWF CGI
|
||||
@@ -15,17 +17,17 @@ EWF Deployment
|
||||
|
||||
|
||||
|
||||
##Apache on Windows
|
||||
## Apache on Windows
|
||||
|
||||
###Apache Install
|
||||
### Apache Install
|
||||
|
||||
>Check the correct version (Win 32 or Win64)
|
||||
>Apache Version: Apache 2.4.4
|
||||
>Windows: http://www.apachelounge.com/download/
|
||||
|
||||
####Deploying EWF CGI
|
||||
#### Deploying EWF CGI
|
||||
|
||||
####CGI overview
|
||||
#### CGI overview
|
||||
>A new process is started for each HTTP request. So if there are N requests to the same >CGI program, the code of the CGI program is loaded into memory N times.
|
||||
>When a CGI program finishes handling a request, the program terminates.
|
||||
|
||||
@@ -59,7 +61,7 @@ Check that you have the following modules enabled
|
||||
LoadModule cgi_module modules/mod_cgi.so
|
||||
LoadModule rewrite_module modules/mod_rewrite.so
|
||||
|
||||
####Tip:
|
||||
#### Tip:
|
||||
>To check the syntax of your httpd.conf file. From command line run the following
|
||||
|
||||
$>httpd - t
|
||||
@@ -68,7 +70,7 @@ Check that you have the following modules enabled
|
||||
>.htaccess CGI
|
||||
http://perishablepress.com/stupid-htaccess-tricks/
|
||||
|
||||
####.htaccess
|
||||
#### .htaccess
|
||||
|
||||
Options +ExecCGI +Includes +FollowSymLinks -Indexes
|
||||
AddHandler cgi-script exe
|
||||
@@ -89,11 +91,11 @@ Check that you have the following modules enabled
|
||||
>Replace $service with the name of your executable service, for example app_service.exe
|
||||
|
||||
|
||||
####Deploying EWF FCGI
|
||||
#### Deploying EWF FCGI
|
||||
>To deploy FCGI you will need to download the mod_fcgi module.
|
||||
>You can get it from here http://www.apachelounge.com/download/
|
||||
|
||||
####FCGI overview
|
||||
#### FCGI overview
|
||||
>FastCGI allows a single, long-running process to handle more than one user request while keeping close to the CGI programming model, retaining the simplicity while eliminating the overhead of creating a new process for each request. Unlike converting an application to a web server plug-in, FastCGI applications remain independent of the web server.
|
||||
|
||||
* Build EWF application
|
||||
@@ -126,10 +128,13 @@ Copy the app.exe and the folder "www" into a folder served by apache2, for exam
|
||||
|
||||
>NOTE: By default Apache does not come with fcgid module, so you will need to download it, and put the module under Apache2/modules
|
||||
|
||||
#.htaccess FCGI
|
||||
>http://perishablepress.com/stupid-htaccess-tricks/
|
||||
# .htaccess FCGI
|
||||
|
||||
####.htaccess
|
||||
```
|
||||
http://perishablepress.com/stupid-htaccess-tricks/
|
||||
```
|
||||
|
||||
#### .htaccess
|
||||
|
||||
Options +ExecCGI +Includes +FollowSymLinks -Indexes
|
||||
|
||||
@@ -158,6 +163,4 @@ Copy the app.exe and the folder "www" into a folder served by apache2, for exam
|
||||
Replace $service with the name of your executable $service, for example app_service.exe
|
||||
You will need to create an service.ews file, this file will be located at the same place where you copy your app service executable.
|
||||
|
||||
|
||||
|
||||
|
||||
Nav: [Workbook](../workbook.md)
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
Nav: [Workbook](../workbook.md) | [Handling Requests: Header Fields](/doc/workbook/handling_request/headers.md) | [Handling Cookies](/doc/workbook/handling_cookies/handling_cookies.md)
|
||||
Nav: [Workbook](../workbook.md) :: [Handling Requests: Header Fields](../handling_request/headers.md) :: [Handling Cookies](../handling_cookies/handling_cookies.md)
|
||||
|
||||
|
||||
## EWF Generating Response
|
||||
@@ -14,7 +14,8 @@ Nav: [Workbook](../workbook.md) | [Handling Requests: Header Fields](/doc/workbo
|
||||
- [Response Header Fields](#header_fields)
|
||||
|
||||
|
||||
<a name="format"/>
|
||||
<a name="format"></a>
|
||||
|
||||
## Format of the HTTP response
|
||||
|
||||
As we saw in the previous documents, a request from a user-agent (browser or other client) consists of an HTTP command (usually GET or POST), zero or more request headers (one or more in HTTP 1.1, since Host is required), a blank line, and only in the case of POST/PUT requests, payload data. A typical request looks like the following.
|
||||
@@ -49,7 +50,8 @@ looks like this:
|
||||
|
||||
The status line consists of the HTTP version (HTTP/1.1 in the preceding example), a status code (an integer 200 in the example), and a very short message corresponding to the status code (OK in the example). In most cases, the headers are optional except for Content-Type, which specifies the MIME type of the document that follows. Although most responses contain a document, some don’t. For example, responses to HEAD requests should never include a document, and various status codes essentially indicate failure or redirection (and thus either don’t include a document or include only a short error-message document).
|
||||
|
||||
<a name="status_set"/>
|
||||
<a name="status_set"></a>
|
||||
|
||||
## How to set the status code
|
||||
|
||||
If you need to set an arbitrary status code, you can use the ```WSF_RESPONSE.put_header``` feature or the ```WSF_RESPONSE.set_status_code``` feature. An status code of 200 is a default value. See below examples using the mentioned features.
|
||||
@@ -94,7 +96,8 @@ Example
|
||||
```
|
||||
Both features takes an INTEGER (the status code) as an formal argument, you can use 200, 300, 500 etc directly, but instead of using explicit numbers, it's recommended to use the constants defined in the class [HTTP_STATUS_CODE](). The name of each constant is based from the standard [HTTP 1.1](https://httpwg.github.io/).
|
||||
|
||||
<a name="redirect"/>
|
||||
<a name="redirect"></a>
|
||||
|
||||
## How to redirect to a particular location.
|
||||
To redirect the response to a new location, we need to send a 302 status code, to do that we use ```{HTTP_STATUS_CODE}.found```
|
||||
|
||||
@@ -145,7 +148,8 @@ The ```WSF_RESPONSE.redirect_now``` feature use the status code ```{HTTP_STATUS_
|
||||
|
||||
Using a similar approach we can build features to answer a bad request (400), internal server error (500), etc. We will build a simple example showing the most common HTTP status codes.
|
||||
|
||||
<a name="status"/>
|
||||
<a name="status"></a>
|
||||
|
||||
## [HTTP 1.1 Status Codes](https://httpwg.github.io/specs/rfc7231.html#status.codes)
|
||||
The status-code element is a three-digit integer code giving the result of the attempt to understand and satisfy the request. The first digit of the status-code defines the class of response.
|
||||
|
||||
@@ -159,7 +163,8 @@ General categories:
|
||||
Note: use ```res.set_status_code({HTTP_STATUS_CODE}.bad_request)``` rather than ```res.set_status_code(400)```.
|
||||
|
||||
|
||||
<a name="example_1"/>
|
||||
<a name="example_1"></a>
|
||||
|
||||
### Example Staus Codes
|
||||
Basic Service that builds a simple web page to show the most common status codes
|
||||
```eiffel
|
||||
@@ -298,7 +303,8 @@ end
|
||||
|
||||
|
||||
|
||||
<a name="example_2"/>
|
||||
<a name="example_2"></a>
|
||||
|
||||
### Example Generic Search Engine
|
||||
The following example shows a basic EWF service that builds a generic front end for the most used search engines. This example shows how
|
||||
redirection works, and we will use a tools to play with the API to show differents responses.
|
||||
@@ -585,7 +591,8 @@ Connection: close
|
||||
</html>
|
||||
```
|
||||
|
||||
<a name="header_fields"/>
|
||||
<a name="header_fields"></a>
|
||||
|
||||
## [Response Header Fields](https://httpwg.github.io/specs/rfc7231.html#response.header.fields)
|
||||
|
||||
The response header fields allow the server to pass additional information about the response beyond what is placed in the status-line. These header fields give information about the server, about further access to the target resource, or about related resources. We can specify cookies, page modification date (for caching), reload a page after a designated period of time, size of the document.
|
||||
@@ -996,4 +1003,4 @@ There are four categories for response header fields:
|
||||
|
||||
|
||||
|
||||
Nav: [Workbook](../workbook.md) | [Handling Requests: Header Fields](/doc/workbook/handling_request/headers.md) | [Handling Cookies](/doc/workbook/handling_cookies/handling_cookies.md)
|
||||
Nav: [Workbook](../workbook.md) :: [Handling Requests: Header Fields](../handling_request/headers.md) :: [Handling Cookies](../handling_cookies/handling_cookies.md)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Nav: [Workbook](../workbook.md) | [Generating Responses](/doc/workbook/generating_response/generating_response.md)
|
||||
Nav: [Workbook](../workbook.md) :: [Generating Responses](../generating_response/generating_response.md)
|
||||
|
||||
# Handling Cookies
|
||||
|
||||
@@ -9,9 +9,10 @@ Nav: [Workbook](../workbook.md) | [Generating Responses](/doc/workbook/generatin
|
||||
- [How to read a cookie](#read_cookie)
|
||||
- [Examples](#examples)
|
||||
|
||||
<a name="cookie"/>
|
||||
## [Cookie](http://httpwg.github.io/specs/rfc6265.html)
|
||||
A cookie is a piece of data that can be stored in a browser's cache. If you visit a web site and then revisit it, the cookie data can be used to identify you as a return visitor. Cookies enable state information, such as an online shopping cart, to be remembered. A cookie can be short term, holding data for a single web session, that is, until you close the browser, or a cookie can be longer term, holding data for a week or a year.
|
||||
<a name="cookie"></a>
|
||||
|
||||
## Cookie
|
||||
A [cookie](http://httpwg.github.io/specs/rfc6265.html) is a piece of data that can be stored in a browser's cache. If you visit a web site and then revisit it, the cookie data can be used to identify you as a return visitor. Cookies enable state information, such as an online shopping cart, to be remembered. A cookie can be short term, holding data for a single web session, that is, until you close the browser, or a cookie can be longer term, holding data for a week or a year.
|
||||
|
||||
Cookies are used a lot in web client-server communication.
|
||||
|
||||
@@ -30,7 +31,8 @@ Client send cookies to server
|
||||
|
||||
|
||||
|
||||
<a name="properties"/>
|
||||
<a name="properties"></a>
|
||||
|
||||
### Cookie properties
|
||||
|
||||
- Comment: describe the purpose of the cookie. Note that server doesn’t receive this information when client sends cookie in request header.
|
||||
@@ -43,7 +45,8 @@ Client send cookies to server
|
||||
- HttpOnly: Checks whether this Cookie has been marked as HttpOnly.
|
||||
- Version:
|
||||
|
||||
<a name="set_get"/>
|
||||
<a name="set_get"></a>
|
||||
|
||||
## Write and Read Cookies.
|
||||
|
||||
To send a cookie to the client we should use the [HTTP_HEADER] class, and call ```h.put_cookie``` feature or
|
||||
@@ -67,7 +70,8 @@ WSF_REQUEST.cookie (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE
|
||||
feature.
|
||||
|
||||
|
||||
<a name="set_cookie"/>
|
||||
<a name="set_cookie"></a>
|
||||
|
||||
### How to set Cookies
|
||||
Here we have the feature definitions to set cookies
|
||||
|
||||
@@ -111,7 +115,8 @@ Example of use:
|
||||
res.put_string (web_page)
|
||||
end
|
||||
```
|
||||
<a name="read_cookie"/>
|
||||
<a name="read_cookie"></a>
|
||||
|
||||
### How to read Cookies
|
||||
|
||||
Reading a particular cookie
|
||||
@@ -130,7 +135,8 @@ Reading all the cookies
|
||||
```
|
||||
|
||||
|
||||
<a name="examples"/>
|
||||
<a name="examples"></a>
|
||||
|
||||
### Example
|
||||
The following EWF service shows a basic use of cookies.
|
||||
1. It display a message to first-time visitors.
|
||||
@@ -285,4 +291,4 @@ end
|
||||
```
|
||||
|
||||
|
||||
Nav: [Workbook](../workbook.md) | [Generating Responses](/doc/workbook/generating_response/generating_response.md)
|
||||
Nav: [Workbook](../workbook.md) :: [Generating Responses](../generating_response/generating_response.md)
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
Nav: [Workbook](../workbook.md) | [Basic Concepts] (/doc/workbook/basics/basics.md) | [Handling Requests: Header Fields](/doc/workbook/handling_request/headers.md)
|
||||
|
||||
|
||||
#Handling Requests: Form/Query Data
|
||||
Nav: [Workbook](../workbook.md) :: [Basic Concepts](../basics/basics.md) :: [Handling Requests: Header Fields](./headers.md)
|
||||
|
||||
# Handling Requests: Form/Query Data
|
||||
|
||||
##### Table of Contents
|
||||
- [Reading Form Data](#read)
|
||||
- [Query Parameters](#query)
|
||||
- [Form Parameters](#form)
|
||||
- [Form Parameters](#form_parameters)
|
||||
- [Uniform Read](#uniform)
|
||||
- [Reading Parameters and Values](#reading_pv)
|
||||
- [How to read all parameters names](#all_names)
|
||||
@@ -34,11 +32,13 @@ Here we will show you how to read input submitted by a user using a Form (GET an
|
||||
* client side validattion, server side validations, set default if it's a valid option.
|
||||
* How to populate Eiffel objects from the request data.
|
||||
|
||||
<a name="read"/>
|
||||
<a name="read"></a>
|
||||
|
||||
## Reading Form Data
|
||||
EWF [WSF_REQUEST]() class, provides features to handling this form parsing automatically.
|
||||
|
||||
<a name="query"/>
|
||||
<a name="query"></a>
|
||||
|
||||
### Query Parameters
|
||||
|
||||
WSF_REQUEST.query_parameters: ITERABLE [WSF_VALUE]
|
||||
@@ -46,7 +46,8 @@ EWF [WSF_REQUEST]() class, provides features to handling this form parsing autom
|
||||
|
||||
WSF_REQUEST.query_parameter (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE
|
||||
-- Query parameter for name `a_name'.
|
||||
<a name="form"/>
|
||||
<a name="form_parameters"></a>
|
||||
|
||||
### Form Parameters
|
||||
|
||||
WSF_REQUEST.form_parameters: ITERABLE [WSF_VALUE]
|
||||
@@ -57,7 +58,8 @@ EWF [WSF_REQUEST]() class, provides features to handling this form parsing autom
|
||||
|
||||
The values supplied to form_parameter and query_parameter are case sensitive.
|
||||
|
||||
<a name="uniform"/>
|
||||
<a name="uniform"></a>
|
||||
|
||||
### Read Data
|
||||
The previous features, let you read the data one way for GET request and a different way for POST request. WSF_REQUEST provide a feature to read all the data in a uniform way.
|
||||
|
||||
@@ -70,7 +72,8 @@ So, you use **WSF_REQUEST.item** feature exactly the same way for GET and POST r
|
||||
>Note: if a query parameter has the same name as a form paramenter req.item will retrieve the form paramenter. Remember the precedence: form > query > path
|
||||
|
||||
|
||||
<a name="reading_pv">
|
||||
<a name="reading_pv"></a>
|
||||
|
||||
## Reading Parameters and Values
|
||||
|
||||
Suppose we have the following HTML5 form using Method POST. This HTML5 form has client side form validation using the new HTML5 attribute, you can do the same using Javascript. So in this case if the user does not fill the fields as expected the form will not be submitted to the server.
|
||||
@@ -111,7 +114,8 @@ Suppose we have the following HTML5 form using Method POST. This HTML5 form has
|
||||
</fieldset>
|
||||
</form>
|
||||
```
|
||||
<a name="all_names">
|
||||
<a name="all_names"></a>
|
||||
|
||||
### How to read all parameter names
|
||||
To read all the parameters names we simple call WSF_REQUEST.form_parameters.
|
||||
|
||||
@@ -119,7 +123,8 @@ To read all the parameters names we simple call WSF_REQUEST.form_parameters.
|
||||
req: WSF_REQUEST
|
||||
across req.form_parameters as ic loop show_parameter_name (ic.item.key) end
|
||||
```
|
||||
<a name="single_values">
|
||||
<a name="single_values"></a>
|
||||
|
||||
### How to read single values
|
||||
To read a particular parameter, a single value, for example `given-name', we simple call WSF_REQUEST.form_parameter (a_name) and we check if it's attached to WSF_STRING (represents a String parameter)
|
||||
```
|
||||
@@ -131,7 +136,8 @@ To read a particular parameter, a single value, for example `given-name', we sim
|
||||
-- Value missing, check the name against the HTML form
|
||||
end
|
||||
```
|
||||
<a name="multiple_values">
|
||||
<a name="multiple_values"></a>
|
||||
|
||||
### How to read multiple values
|
||||
|
||||
To read multiple values, for example in the case of `languages', we simple call WSF_REQUEST.form_parameter (a_name) and we check if it's attached to WSF_MULTIPLE_STRING (represents a String parameter)
|
||||
@@ -155,7 +161,8 @@ To read multiple values, for example in the case of `languages', we simple call
|
||||
```
|
||||
In this case we are handling strings values, but in some cases you will need to do a conversion, betweend the strings that came from the request to map them to your domain model.
|
||||
|
||||
<a name="table_values">
|
||||
<a name="table_values"></a>
|
||||
|
||||
### How to read table values
|
||||
This is particularly useful when you have a request with the following format
|
||||
|
||||
@@ -183,7 +190,8 @@ if attached {WSF_TABLE} req.query_parameter ("tab") as l_tab then
|
||||
end
|
||||
```
|
||||
|
||||
<a name="raw_data">
|
||||
<a name="raw_data"></a>
|
||||
|
||||
## Reading Raw Data
|
||||
You can also access the data in raw format, it means you will need to parse and url-decode it, and also you will not be able to use the previous features, by default, to enable that you need to call `req.set_raw_input_data_recorded (True)'. This feature (reading raw data) is useful if you are reading POST data with JSON or XML formats, but it's not convinient for HTML forms.
|
||||
|
||||
@@ -199,7 +207,8 @@ To read raw data you need to do this
|
||||
|
||||
> given-name=testr&family-name=test&dob=1976-08-26&email=test%40gmail.com&url=http%3A%2F%2Fwww.eiffelroom.com&phone=455555555555&languages=Spanish&languages=English
|
||||
|
||||
<a name=upload></a>
|
||||
<a name="upload"></a>
|
||||
|
||||
## Upload Files
|
||||
How can we read data when the date come from an uploaded file/s?.
|
||||
HTML supports a form element ```<input type="File" ... > ``` to upload a single file and ```<input type="File" ... multiplr> ``` to upload multiple files.
|
||||
@@ -290,7 +299,8 @@ The source code is available on Github. You can get it by running the command:
|
||||
The example is located in the directory $PATH/ewf/doc/workbook/upload_file where $PATH is where you run git clone.
|
||||
|
||||
|
||||
<a name=examples>
|
||||
<a name="examples"></a>
|
||||
|
||||
## Examples
|
||||
The source code is available on Github. You can get it by running the command:
|
||||
|
||||
@@ -303,5 +313,4 @@ The GET example is located in the directory $PATH/ewf/doc/workbook/form/get, and
|
||||
>Note: replace <ecf_name> and<target_name> with the corresponding values.
|
||||
|
||||
|
||||
Nav: [Workbook](../workbook.md) | [Basic Concepts] (/doc/workbook/basics/basics.md) | [Handling Requests: Header Fields](/doc/workbook/handling_request/headers.md)
|
||||
|
||||
Nav: [Workbook](../workbook.md) :: [Basic Concepts](../basics/basics.md) :: [Handling Requests: Header Fields](./headers.md)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Nav: [Workbook](../workbook.md) | [Handling Requests: Form/Query parameters] (/doc/workbook/handling_request/form.md) | [Generating Responses](/doc/workbook/generating_response/generating_response.md)
|
||||
Nav: [Workbook](../workbook.md) :: [Handling Requests: Form/Query parameters](./form.md) :: [Generating Responses](../generating_response/generating_response.md)
|
||||
|
||||
|
||||
#Handling Requests: Headers
|
||||
# Handling Requests: Headers
|
||||
|
||||
##### Introduction
|
||||
- The [HTTP request header fields (also known as "headers")](https://httpwg.github.io/specs/rfc7231.html#request.header.fields) are set by the client (usually web browser) and sent in the header of the http request text (see http protocol), as opposed to form or query parameters [Form Data]().
|
||||
@@ -43,6 +43,7 @@ Among other, this means the header fields are exposed with associated CGI field
|
||||
- For instance `X-Server` will be known as `HTTP_X_SERVER`.
|
||||
|
||||
<a name="read_header"></a>
|
||||
|
||||
## Reading HTTP Header fields
|
||||
EWF [WSF_REQUEST]() class provides features to access HTTP headers.
|
||||
|
||||
@@ -72,7 +73,8 @@ Due to CGI compliance, the original header names are not available, however the
|
||||
Note: CGI variables are information about the current request (and also about the server). Some are based on the HTTP request line and headers (e.g., form parameters, query parameters), others are derived from the socket itself (e.g., the name and IP address of the requesting host), and still others are taken from server installation parameters (e.g., the mapping of URLs to actual paths).
|
||||
|
||||
<a name="read_line"></a>
|
||||
####Retrieve information from the Request Line
|
||||
|
||||
#### Retrieve information from the Request Line
|
||||
|
||||
For convenience, the following sections refer to a request starting with line:
|
||||
```
|
||||
@@ -102,59 +104,72 @@ Overview of the features
|
||||
|
||||
|
||||
<a name="understand"></a>
|
||||
|
||||
#### Understanding HTTP 1.1 Request Headers
|
||||
Access to the request headers permits the web server applications or APIs to perform optimizations and provide behavior that would not be possible without them for instance such as adapting the response according to the browser preferences.
|
||||
This section summarizes the headers most often used; for more information, see the [HTTP 1.1 specification](https://httpwg.github.io/specs/), note that [RFC 2616 is dead](https://www.mnot.net/blog/2014/06/07/rfc2616_is_dead).
|
||||
|
||||
<a name="accept"></a>
|
||||
|
||||
* [Accept](https://httpwg.github.io/specs/rfc7231.html#header.accept)
|
||||
- The "Accept" header field can be used by user agents (browser or other clients) to define response media types that are acceptable. Accept header fields can be used to indicate that the request is limited to a small set of desired types, as in the case of a request for an inline image.
|
||||
For example, assume an APIs Learn4Kids can respond with XML or JSON data (JSON format have some advantages over XML, readability, parsing etc...), a client can define its preference using "Accept: application/json" to request data in JSON format, or "Accept: application/xml" to get XML format. In other case the server sends a not acceptable response. Note that the client can define an ordered list of accepted content types, including "*", the client will get the response and know the content type via the response header field "Content-Type". Related [Content-Negotiation]()
|
||||
|
||||
<a name="accept_charset"></a>
|
||||
|
||||
* [Accept-Charset](https://httpwg.github.io/specs/rfc7231.html#header.accept-charset)
|
||||
- The "Accept-Charset" header field can be sent by a user agent (browser or other clients) to indicate which charsets are acceptable in textual response content (e.g., ISO-8859-1).
|
||||
|
||||
<a name="accept_encoding"></a>
|
||||
|
||||
* [Accept-Encoding](https://httpwg.github.io/specs/rfc7231.html#header.accept-encoding)
|
||||
- The "Accept-Encoding" header field can be used by user agents (browser or other clients) to indicate which response content-codings (`gzip`, `compress`) are acceptable in the response. An "identity" token is used as a synonym for "no encoding" in order to communicate when no encoding is preferred. If the server receives this header, it is free to encode the page by using one of the content-encodings specified (usually to reduce transmission time), sending the `Content-Encoding` response header to indicate that it has done so.
|
||||
|
||||
<a name="accept_language"></a>
|
||||
|
||||
* [Accept-Language](https://httpwg.github.io/specs/rfc7231.html#header.accept-language)
|
||||
- The "Accept-Language" header field can be used by user agents (browser or other client) to indicate the set of natural languages that are preferred in the response in case the server can produce representation in more than one language. The value of the header should be one of the standard language codes such as en, en-us, da, etc. See RFC 1766 for details (start at http://www.rfc-editor.org/ to get a current list of the RFC archive sites).
|
||||
|
||||
<a name="connection"></a>
|
||||
|
||||
* [Connection](https://httpwg.github.io/specs/rfc7230.html#header.connection)
|
||||
- The "Connection" header field allows the sender to indicate desired control options for the current connection, for example if it can hanlde persistent HTTP connections.
|
||||
By default HTTP/1.1 uses "persistent connections", allowing multiple requests and responses to be carried over a single connection. The "close" connection option is used to signal that a connection will not persist after the current request/response.
|
||||
|
||||
<a name="authorization"></a>
|
||||
|
||||
* [Authorization](https://httpwg.github.io/specs/rfc7235.html#header.authorization)
|
||||
- The header is used by user agents to authenticate themselves when accessing password protected resources.
|
||||
|
||||
<a name="content-length"></a>
|
||||
|
||||
* [Content-Length](https://httpwg.github.io/specs/rfc7230.html#header.content-length)
|
||||
- For messages that includes a payload body, the Content-Length field-value provides the framing information necessary to determine where the body (and message) ends.
|
||||
|
||||
<a name="cookie"></a>
|
||||
|
||||
* [Cookie](https://httpwg.github.io/specs/rfc6265.html)
|
||||
- The Cookie header contains cookies received by the user agent in previous Set-Cookie headers. The origin server is free to ignore the Cookie header or use its contents for an application-specific purpose. (Related State Management).
|
||||
|
||||
<a name="host"></a>
|
||||
|
||||
* [Host](https://httpwg.github.io/specs/rfc7230.html#header.host)
|
||||
- The "Host" header field provides the host and port information from the target URI, enabling the origin server to distinguish among resources while serving requests for multiple host names on a single IP address. In HTTP 1.1, browsers and other clients are required to specify this header, which indicates the host and port as given in the original URL.
|
||||
|
||||
<a name="if-modified-since"></a>
|
||||
|
||||
* [If-Modified-Since](https://httpwg.github.io/specs/rfc7232.html#header.if-modified-since)
|
||||
- The "If-Modified-Since" header field makes a GET or HEAD request method conditional on the selected representation's modification date being more recent than the date provided in the field-value. Transfering of the selected representation's data is avoided if that data has not changed. So, indicates that the user agents wants the page only if it has been changes after the specified date. The server sends a 304 resource not modified if not has a newer result representation available.
|
||||
|
||||
<a name="if-unmodified-since"></a>
|
||||
|
||||
* [If-Unmodified-Since](https://httpwg.github.io/specs/rfc7232.html#header.if-unmodified-since)
|
||||
- The "If-Unmodified-Since" header field makes the request method conditional on the selected representation's last modification date being earlier than or equal to the date provided in the field-value. The operation should succeed only if the document is older than the specified date.
|
||||
|
||||
Generally, If-Modified-Since is used for GET requests (“give me the document only if it is newer than my cached version”), whereas If-Unmodified-Since is used for PUT requests (“update this document only if nobody else has changed it since I generated it”).
|
||||
|
||||
<a name="referer"></a>
|
||||
|
||||
* [Referer](https://httpwg.github.io/specs/rfc7231.html#header.referer)
|
||||
- The "Referer" header field allows the user agent to specify a URI reference for the resource from which the target URI was obtained (i.e., the "referrer", though the field name is misspelled). A user agent MUST NOT include the fragment and userinfo components of the URI reference [RFC3986], if any, when generating the Referer field value. This header indicates the URL of the referring Web page.
|
||||
|
||||
@@ -162,15 +177,17 @@ For example, if you are at Web page A and click on a link to Web page B, the URL
|
||||
included in the Referer header when the browser requests Web page B.
|
||||
|
||||
<a name="user-agent"></a>
|
||||
|
||||
* [User-Agent](https://httpwg.github.io/specs/rfc7231.html#header.user-agent)
|
||||
- The "User-Agent" header field contains information about the user agent of the request, which is often used by servers to help identify the scope of reported interoperability problems, to work around or tailor responses to avoid particular user agent limitations, and for analytics regarding browser or operating system use or device.
|
||||
|
||||
**Note**: the example shows the **WSF_EXECUTION** implementation, that will be used by the service launcher.
|
||||
|
||||
<a name="example"></a>
|
||||
|
||||
#### Building a Table of All Request Headers
|
||||
|
||||
The following [EWF service](/doc/workbook/handling_request/headers/header_fields/application.e) code simply uses an ```html_template``` to fill a table (names and values) with all the headers fields it receives.
|
||||
The following [EWF service](./headers/header_fields/application.e) code simply uses an ```html_template``` to fill a table (names and values) with all the headers fields it receives.
|
||||
|
||||
The service accomplishes this task by calling ```req.meta_variables``` feature to get an ```ITERABLE [WSF_STRING]```, an structure that can be iterated over using ```across...loop...end```, then it checks if the name has the prefix ```HTTP_``` and if it is true, put the header name and value in a row. (the name in the left cell, the value in the right cell).
|
||||
|
||||
@@ -276,6 +293,7 @@ end
|
||||
```
|
||||
|
||||
<a name="compress"></a>
|
||||
|
||||
#### How to compress pages
|
||||
To be completed.
|
||||
|
||||
@@ -284,7 +302,7 @@ To be completed.
|
||||
|
||||
#### Detecting Browser Types
|
||||
|
||||
The User-Agent header identifies the specific browser/client that is sending the request. The following code shows a [EWF service](/doc/workbook/handling_request/headers/browser_name/application.e) that sends browser-specific responses.
|
||||
The User-Agent header identifies the specific browser/client that is sending the request. The following code shows a [EWF service](./headers/browser_name/application.e) that sends browser-specific responses.
|
||||
|
||||
The examples uses the ideas based on the [Browser detection using the user agent](https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent) article.
|
||||
Basically the code check if the header user_agent exist and then call the ```browser_name (a_user_agent: READABLE_STRING_8): READABLE_STRING_32```
|
||||
@@ -429,10 +447,8 @@ As an exercise, try to write a similar service to retrieve the OS family using t
|
||||
* [SERVER_SOFTWARE](https://tools.ietf.org/html/rfc3875#section-4.1.16)
|
||||
|
||||
**Example**
|
||||
An [EWF service](/doc/workbook/handling_request/headers/cgi_variables/application.e) that shows the CGI variables, creates a table showing the values of all the CGI variables.
|
||||
|
||||
|
||||
Nav: [Workbook](../workbook.md) | [Handling Requests: Form/Query parameters] (/doc/workbook/handling_request/form.md) | [Generating Responses](/doc/workbook/generating_response/generating_response.md)
|
||||
An [EWF service](./headers/cgi_variables/application.e) that shows the CGI variables, creates a table showing the values of all the CGI variables.
|
||||
|
||||
|
||||
Nav: [Workbook](../workbook.md) :: [Handling Requests: Form/Query parameters](./form.md) :: [Generating Responses](../generating_response/generating_response.md)
|
||||
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
Introduction
|
||||
|
||||
Basic Concepts
|
||||
Generating Plain Text
|
||||
Generation HTML
|
||||
The [Workbook](./workbook.md) lets you discover the EiffelWeb framework.
|
||||
|
||||
Handling Client Request:
|
||||
Form Data
|
||||
Request Heders
|
||||
Query Parameters.
|
||||
|
||||
|
||||
|
||||
[Enter the documentation](./workbook.md)
|
||||
|
||||
|
||||
@@ -5,38 +5,46 @@
|
||||
* [EWF Introduction](#introduction)
|
||||
* [Handling Requests: Form/Query Parameter](#form_query_parameters)
|
||||
* [Handling Requests: Header Fields](#header_fields)
|
||||
* [Generating Responses](#generating responses)
|
||||
* [Generating Responses](#generating_responses)
|
||||
* [Handling Cookies](#handling_cookies)
|
||||
* [EWF Deployment](#deployment)
|
||||
|
||||
<a name="core"></a>
|
||||
|
||||
# EWF Core
|
||||
Before reading (or walking throught) the workbook, to get a quick overview of EWF, it is recommended to read the following articles:
|
||||
|
||||
* [Getting Started with EWF](http://eiffelwebframework.github.io/EWF/getting-started/)
|
||||
* [EWF Documentation](http://eiffelwebframework.github.io/EWF/wiki/Documentation/)
|
||||
* [EWF Documentation](http://eiffelwebframework.github.io/EWF/workbook/workbook)
|
||||
* [EWF Application Lifecyle](https://github.com/EiffelWebFramework/ewf_examples/wiki/Application-Lifecycle)
|
||||
|
||||
|
||||
<a name="introduction"></a>
|
||||
|
||||
## Introduction
|
||||
[Basic Concepts] (/doc/workbook/basics/basics.md).
|
||||
[Basic Concepts](./basics/basics.md).
|
||||
|
||||
<a name="form_query_parameters"></a>
|
||||
|
||||
## Handling Requests: Form/Query Parameter
|
||||
[Handling Requests: Form/Query Parameter] (/doc/workbook/handling_request/form.md).
|
||||
[Handling Requests: Form/Query Parameter](./handling_request/form.md).
|
||||
|
||||
<a name="header_fields"></a>
|
||||
|
||||
## Handling Requests: Header Fields
|
||||
[Handling Requests: Header Fields](/doc/workbook/handling_request/headers.md).
|
||||
[Handling Requests: Header Fields](./handling_request/headers.md).
|
||||
|
||||
<a name="generating_responses"></a>
|
||||
|
||||
## Generating Response
|
||||
[Generating Responses](/doc/workbook/generating_response/generating_response.md)
|
||||
[Generating Responses](./generating_response/generating_response.md)
|
||||
|
||||
<a name="handling_cookies"></a>
|
||||
## Handling Cookies
|
||||
[Handling Cookies](/doc/workbook/handling_cookies/handling_cookies.md)
|
||||
|
||||
<a name="deployment"/>
|
||||
## Handling Cookies
|
||||
[Handling Cookies](./handling_cookies/handling_cookies.md)
|
||||
|
||||
<a name="deployment"></a>
|
||||
|
||||
## EWF Deployment
|
||||
[EWF Deployment](/doc/workbook/deployment.md)
|
||||
[EWF Deployment](./deployment/readme.md)
|
||||
|
||||
29
examples/proxy/application.e
Normal file
29
examples/proxy/application.e
Normal file
@@ -0,0 +1,29 @@
|
||||
note
|
||||
description: "Launcher for reverse proxy web application."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
APPLICATION
|
||||
|
||||
inherit
|
||||
WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION]
|
||||
redefine
|
||||
initialize
|
||||
end
|
||||
|
||||
create
|
||||
make_and_launch
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
initialize
|
||||
-- Initialize current service.
|
||||
do
|
||||
-- Specific to `standalone' connector (the EiffelWeb server).
|
||||
-- See `{WSF_STANDALONE_SERVICE_LAUNCHER}.initialize'
|
||||
set_service_option ("port", 9090)
|
||||
import_service_options (create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI}.make_from_file ("server.ini"))
|
||||
end
|
||||
|
||||
end
|
||||
49
examples/proxy/application_execution.e
Normal file
49
examples/proxy/application_execution.e
Normal file
@@ -0,0 +1,49 @@
|
||||
note
|
||||
description: "Reverse proxy example."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
APPLICATION_EXECUTION
|
||||
|
||||
inherit
|
||||
WSF_EXECUTION
|
||||
|
||||
WSF_URI_REWRITER
|
||||
rename
|
||||
uri as proxy_uri
|
||||
end
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Basic operations
|
||||
|
||||
execute
|
||||
do
|
||||
-- NOTE: please enter the target server uri here
|
||||
-- replace "http://localhost:8080/foobar"
|
||||
send_proxy_response ("http://localhost:8080/foobar", Current)
|
||||
end
|
||||
|
||||
send_proxy_response (a_remote: READABLE_STRING_8; a_rewriter: detachable WSF_URI_REWRITER)
|
||||
local
|
||||
h: WSF_SIMPLE_REVERSE_PROXY_HANDLER
|
||||
do
|
||||
create h.make (a_remote)
|
||||
h.set_uri_rewriter (a_rewriter)
|
||||
h.set_uri_rewriter (create {WSF_AGENT_URI_REWRITER}.make (agent proxy_uri))
|
||||
h.set_timeout (30) -- 30 seconds
|
||||
h.set_connect_timeout (5_000) -- milliseconds = 5 seconds
|
||||
h.execute (request, response)
|
||||
end
|
||||
|
||||
feature -- Helpers
|
||||
|
||||
proxy_uri (a_request: WSF_REQUEST): STRING
|
||||
-- Request uri rewriten as url.
|
||||
do
|
||||
Result := a_request.request_uri
|
||||
end
|
||||
|
||||
end
|
||||
28
examples/proxy/proxy.ecf
Normal file
28
examples/proxy/proxy.ecf
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="proxy" uuid="B55F0D95-3793-4C90-BBAC-BF5F2DECD5E6" library_target="proxy">
|
||||
<target name="common" abstract="true">
|
||||
<file_rule>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="false" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="console_application" value="true"/>
|
||||
<variable name="ssl_supported" value="false"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="http" location="..\..\library\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf"/>
|
||||
<library name="wsf_proxy" location="..\..\library\server\wsf_proxy\wsf_proxy-safe.ecf" readonly="false"/>
|
||||
</target>
|
||||
<target name="proxy" extends="common">
|
||||
<root class="APPLICATION" feature="make_and_launch"/>
|
||||
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone-safe.ecf"/>
|
||||
<cluster name="proxy" location=".\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
8
examples/proxy/server.ini
Normal file
8
examples/proxy/server.ini
Normal file
@@ -0,0 +1,8 @@
|
||||
verbose=true
|
||||
verbose_level=ALERT
|
||||
port=9090
|
||||
#max_concurrent_connections=100
|
||||
#keep_alive_timeout=15
|
||||
#max_tcp_clients=100
|
||||
#socket_timeout=300
|
||||
#max_keep_alive_requests=300
|
||||
@@ -20,9 +20,10 @@ feature {NONE} -- Initialization
|
||||
initialize
|
||||
-- Initialize current service.
|
||||
do
|
||||
set_service_option ("port", 9090)
|
||||
-- Specific to `standalone' connector (the EiffelWeb server).
|
||||
-- See `{WSF_STANDALONE_SERVICE_LAUNCHER}.initialize'
|
||||
set_service_option ("port", 9090)
|
||||
import_service_options (create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI}.make_from_file ("simple.ini"))
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
@@ -17,10 +17,13 @@ feature -- Basic operations
|
||||
execute
|
||||
local
|
||||
s: STRING
|
||||
do
|
||||
-- To send a response we need to setup, the status code and
|
||||
-- the response headers.
|
||||
s := "Hello World!"
|
||||
dt: HTTP_DATE
|
||||
do
|
||||
-- To send a response we need to setup, the status code and
|
||||
-- the response headers.
|
||||
s := "Hello World!"
|
||||
create dt.make_now_utc
|
||||
s.append (" (UTC time is " + dt.rfc850_string + ").")
|
||||
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", s.count.out]>>)
|
||||
response.set_status_code ({HTTP_STATUS_CODE}.ok)
|
||||
response.header.put_content_type_text_html
|
||||
|
||||
@@ -1,35 +1,28 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-13-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-13-0 http://www.eiffel.com/developers/xml/configuration-1-13-0.xsd" name="simple" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486" library_target="simple">
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="simple" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486" library_target="simple">
|
||||
<target name="common" abstract="true">
|
||||
<file_rule>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="false" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
|
||||
<option warning="true" full_class_checking="false" is_attached_by_default="true" is_obsolete_routine_type="true" void_safety="transitional" syntax="transitional">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="console_application" value="true"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="http" location="..\..\library\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf"/>
|
||||
</target>
|
||||
<target name="simple_standalone" extends="common">
|
||||
<root class="APPLICATION" feature="make_and_launch"/>
|
||||
<option warning="true" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
|
||||
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="concurrency" value="thread"/>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone-safe.ecf"/>
|
||||
<cluster name="simple" location=".\" recursive="true"/>
|
||||
</target>
|
||||
<target name="simple_nino" extends="common">
|
||||
<root class="APPLICATION" feature="make_and_launch"/>
|
||||
<option warning="true" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<library name="default_nino" location="..\..\library\server\wsf\default\nino-safe.ecf"/>
|
||||
<cluster name="simple" location=".\" recursive="true"/>
|
||||
</target>
|
||||
<target name="simple_cgi" extends="common">
|
||||
<root class="APPLICATION" feature="make_and_launch"/>
|
||||
<option warning="true" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
|
||||
|
||||
8
examples/simple/simple.ini
Normal file
8
examples/simple/simple.ini
Normal file
@@ -0,0 +1,8 @@
|
||||
verbose=true
|
||||
verbose_level=ALERT
|
||||
port=9090
|
||||
#max_concurrent_connections=100
|
||||
#keep_alive_timeout=15
|
||||
#max_tcp_clients=100
|
||||
#socket_timeout=300
|
||||
#max_keep_alive_requests=300
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="upload_image" uuid="F2400BE8-D8EB-48EB-B4E4-5D4377062A7F" library_target="upload_image">
|
||||
<target name="upload_image">
|
||||
<root class="IMAGE_UPLOADER" feature="make"/>
|
||||
<target name="upload_image_common">
|
||||
<file_rule>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/\.git$</exclude>
|
||||
@@ -11,14 +10,26 @@
|
||||
<debug name="standalone" enabled="true"/>
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="concurrency" value="thread"/>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone-safe.ecf" readonly="false" use_application_options="true"/>
|
||||
<library name="encoder" location="..\..\library\text\encoder\encoder-safe.ecf" readonly="false"/>
|
||||
<library name="http" location="..\..\library\network\protocol\http\http-safe.ecf" readonly="false"/>
|
||||
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
|
||||
<library name="uri_template" location="..\..\library\text\parser\uri_template\uri_template-safe.ecf" readonly="false"/>
|
||||
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf" readonly="false" use_application_options="true"/>
|
||||
</target>
|
||||
<target name="upload_image_standalone" extends="upload_image_common">
|
||||
<root class="IMAGE_UPLOADER" feature="make"/>
|
||||
<setting name="concurrency" value="thread"/>
|
||||
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone-safe.ecf" readonly="false" use_application_options="true"/>
|
||||
<cluster name="src" location="src\" recursive="true"/>
|
||||
</target>
|
||||
<target name="upload_image_libfcgi" extends="upload_image_common">
|
||||
<root class="IMAGE_UPLOADER" feature="make"/>
|
||||
<setting name="concurrency" value="none"/>
|
||||
<library name="default_libfcgi" location="..\..\library\server\wsf\default\libfcgi-safe.ecf" readonly="false" use_application_options="true"/>
|
||||
<cluster name="src" location="src\" recursive="true"/>
|
||||
</target>
|
||||
<target name="upload_image" extends="upload_image_standalone">
|
||||
</target>
|
||||
</system>
|
||||
|
||||
29
examples/websocket/application.e
Normal file
29
examples/websocket/application.e
Normal file
@@ -0,0 +1,29 @@
|
||||
note
|
||||
description : "simple application root class"
|
||||
date : "$Date$"
|
||||
revision : "$Revision$"
|
||||
|
||||
class
|
||||
APPLICATION
|
||||
|
||||
create
|
||||
make_and_launch
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make_and_launch
|
||||
local
|
||||
l_launcher: WSF_STANDALONE_WEBSOCKET_SERVICE_LAUNCHER [APPLICATION_EXECUTION]
|
||||
opts: WSF_SERVICE_LAUNCHER_OPTIONS
|
||||
do
|
||||
create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI} opts.make_from_file ("ws.ini")
|
||||
create l_launcher.make_and_launch (options)
|
||||
end
|
||||
|
||||
options: WSF_SERVICE_LAUNCHER_OPTIONS
|
||||
-- Initialize current service.
|
||||
do
|
||||
create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI} Result.make_from_file ("ws.ini")
|
||||
end
|
||||
|
||||
end
|
||||
184
examples/websocket/application_execution.e
Normal file
184
examples/websocket/application_execution.e
Normal file
@@ -0,0 +1,184 @@
|
||||
note
|
||||
description : "simple application execution"
|
||||
date : "$Date$"
|
||||
revision : "$Revision$"
|
||||
|
||||
class
|
||||
APPLICATION_EXECUTION
|
||||
|
||||
inherit
|
||||
WSF_WEBSOCKET_EXECUTION
|
||||
|
||||
WEB_SOCKET_EVENT_I
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Basic operations
|
||||
|
||||
execute
|
||||
local
|
||||
s: STRING
|
||||
dt: HTTP_DATE
|
||||
do
|
||||
-- To send a response we need to setup, the status code and
|
||||
-- the response headers.
|
||||
if request.path_info.same_string_general ("/app") then
|
||||
s := websocket_app_html (9090)
|
||||
else
|
||||
s := "Hello World!"
|
||||
create dt.make_now_utc
|
||||
s.append (" (UTC time is " + dt.rfc850_string + ").")
|
||||
s.append ("<p><a href=%"/app%">Websocket demo</a></p>")
|
||||
end
|
||||
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", s.count.out]>>)
|
||||
response.set_status_code ({HTTP_STATUS_CODE}.ok)
|
||||
response.header.put_content_type_text_html
|
||||
response.header.put_content_length (s.count)
|
||||
if attached request.http_connection as l_connection and then l_connection.is_case_insensitive_equal_general ("keep-alive") then
|
||||
response.header.put_header_key_value ("Connection", "keep-alive")
|
||||
end
|
||||
response.put_string (s)
|
||||
end
|
||||
|
||||
feature -- Websocket execution
|
||||
|
||||
new_websocket_handler (ws: WEB_SOCKET): WEB_SOCKET_HANDLER
|
||||
do
|
||||
create Result.make (ws, Current)
|
||||
end
|
||||
|
||||
feature -- Websocket execution
|
||||
|
||||
on_open (ws: WEB_SOCKET)
|
||||
do
|
||||
ws.put_error ("Connecting")
|
||||
ws.send (Text_frame, "Hello, this is a simple demo with Websocket using Eiffel. (/help for more information).%N")
|
||||
end
|
||||
|
||||
on_binary (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||
do
|
||||
ws.send (Binary_frame, a_message)
|
||||
end
|
||||
|
||||
on_text (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||
do
|
||||
if a_message.same_string_general ("/help") then
|
||||
-- Echo the message for testing.
|
||||
ws.send (Text_frame, "Help: available commands%N - /time : return the server UTC time.%N")
|
||||
elseif a_message.starts_with_general ("/time") then
|
||||
ws.send (Text_frame, "Server time is " + (create {HTTP_DATE}.make_now_utc).string)
|
||||
else
|
||||
-- Echo the message for testing.
|
||||
ws.send (Text_frame, a_message)
|
||||
end
|
||||
end
|
||||
|
||||
on_close (ws: WEB_SOCKET)
|
||||
-- Called after the WebSocket connection is closed.
|
||||
do
|
||||
ws.put_error ("Connection closed")
|
||||
end
|
||||
|
||||
feature -- HTML Resource
|
||||
|
||||
websocket_app_html (a_port: INTEGER): STRING
|
||||
do
|
||||
Result := "[
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
|
||||
var socket;
|
||||
|
||||
function connect(){
|
||||
|
||||
var host = "ws://127.0.0.1:##PORTNUMBER##";
|
||||
|
||||
try{
|
||||
socket = new WebSocket(host);
|
||||
message('<p class="event">Socket Status: '+socket.readyState);
|
||||
socket.onopen = function(){
|
||||
message('<p class="event">Socket Status: '+socket.readyState+' (open)');
|
||||
}
|
||||
socket.onmessage = function(msg){
|
||||
message('<p class="message">Received: '+msg.data);
|
||||
}
|
||||
socket.onclose = function(){
|
||||
message('<p class="event">Socket Status: '+socket.readyState+' (Closed)');
|
||||
}
|
||||
} catch(exception){
|
||||
message('<p>Error'+exception);
|
||||
}
|
||||
}
|
||||
|
||||
function send(){
|
||||
var text = $('#text').val();
|
||||
if(text==""){
|
||||
message('<p class="warning">Please enter a message');
|
||||
return ;
|
||||
}
|
||||
try{
|
||||
socket.send(text);
|
||||
message('<p class="event">Sent: '+text)
|
||||
} catch(exception){
|
||||
message('<p class="warning">');
|
||||
}
|
||||
$('#text').val("");
|
||||
}
|
||||
|
||||
function message(msg){
|
||||
$('#chatLog').append(msg+'</p>');
|
||||
}//End message()
|
||||
|
||||
$('#text').keypress(function(event) {
|
||||
if (event.keyCode == '13') {
|
||||
send();
|
||||
}
|
||||
});
|
||||
|
||||
$('#disconnect').click(function(){
|
||||
socket.close();
|
||||
});
|
||||
|
||||
if (!("WebSocket" in window)){
|
||||
$('#chatLog, input, button, #examples').fadeOut("fast");
|
||||
$('<p>Oh no, you need a browser that supports WebSockets. How about <a href="http://www.google.com/chrome">Google Chrome</a>?</p>').appendTo('#container');
|
||||
}else{
|
||||
//The user has WebSockets
|
||||
connect();
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
<meta charset="utf-8" />
|
||||
<style type="text/css">
|
||||
body {font-family:Arial, Helvetica, sans-serif;}
|
||||
#container { border:5px solid grey; width:800px; margin:0 auto; padding:10px; }
|
||||
#chatLog { padding:5px; border:1px solid black; }
|
||||
#chatLog p {margin:0;}
|
||||
.event {color:#999;}
|
||||
.warning { font-weight:bold; color:#CCC; }
|
||||
</style>
|
||||
<title>WebSockets Client</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrapper">
|
||||
<div id="container">
|
||||
<h1>WebSockets Client</h1>
|
||||
<div id="chatLog"></div>
|
||||
<input id="text" type="text" />
|
||||
<button id="disconnect">Disconnect</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
]"
|
||||
Result.replace_substring_all ("##PORTNUMBER##", a_port.out)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
21
examples/websocket/websocket_app.ecf
Normal file
21
examples/websocket/websocket_app.ecf
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="websocket_app" uuid="75D17C20-10A8-4E4C-A059-33D72A2B6AEF">
|
||||
<target name="websocket_app">
|
||||
<root class="APPLICATION" feature="make_and_launch"/>
|
||||
<file_rule>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="console_application" value="true"/>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="http" location="..\..\library\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="standalone_websocket_connector" location="..\..\library\server\wsf\connector\standalone_websocket-safe.ecf" readonly="false"/>
|
||||
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf"/>
|
||||
<cluster name="app" location=".\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
8
examples/websocket/ws.ini
Normal file
8
examples/websocket/ws.ini
Normal file
@@ -0,0 +1,8 @@
|
||||
verbose=true
|
||||
verbose_level=INFORMATION
|
||||
port=9090
|
||||
max_concurrent_connections=100
|
||||
keep_alive_timeout=35
|
||||
max_tcp_clients=100
|
||||
socket_timeout=30000
|
||||
max_keep_alive_requests=3000
|
||||
@@ -180,7 +180,9 @@ feature -- Header: adding
|
||||
if line [line.count] = '%R' then
|
||||
line.remove_tail (1)
|
||||
end
|
||||
add_header (line)
|
||||
if not line.is_empty then
|
||||
add_header (line)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,8 +23,8 @@ feature {NONE} -- Initialization
|
||||
local
|
||||
n: INTEGER
|
||||
p: like pool
|
||||
do
|
||||
n := max_concurrent_connections (server)
|
||||
do
|
||||
n := max_concurrent_connections (server).max (1) -- At least one processor!
|
||||
create p.make (n)
|
||||
initialize_pool (p, n)
|
||||
pool := p
|
||||
@@ -23,7 +23,7 @@ feature {NONE} -- Initialization
|
||||
local
|
||||
n: INTEGER
|
||||
do
|
||||
n := max_concurrent_connections (server)
|
||||
n := max_concurrent_connections (server).max (1) -- At least one thread!
|
||||
create pool.make (n.to_natural_32)
|
||||
end
|
||||
|
||||
@@ -10,17 +10,26 @@ feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
do
|
||||
http_server_port := 80
|
||||
max_concurrent_connections := 100
|
||||
max_tcp_clients := 100
|
||||
socket_accept_timeout := 1_000
|
||||
socket_connect_timeout := 5_000
|
||||
keep_alive_timeout := 5
|
||||
http_server_port := default_http_server_port
|
||||
max_concurrent_connections := default_max_concurrent_connections
|
||||
max_tcp_clients := default_max_tcp_clients
|
||||
socket_timeout := default_socket_timeout
|
||||
keep_alive_timeout := default_keep_alive_timeout
|
||||
max_keep_alive_requests := default_max_keep_alive_requests
|
||||
is_secure := False
|
||||
create ca_crt.make_empty
|
||||
create ca_key.make_empty
|
||||
end
|
||||
|
||||
feature -- Defaults
|
||||
|
||||
default_http_server_port: INTEGER = 80
|
||||
default_max_concurrent_connections: INTEGER = 100
|
||||
default_max_tcp_clients: INTEGER = 100
|
||||
default_socket_timeout: INTEGER = 300 -- seconds
|
||||
default_keep_alive_timeout: INTEGER = 15 -- seconds
|
||||
default_max_keep_alive_requests: INTEGER = 100
|
||||
|
||||
feature -- Access
|
||||
|
||||
Server_details: STRING_8
|
||||
@@ -31,35 +40,62 @@ feature -- Access
|
||||
http_server_name: detachable READABLE_STRING_8 assign set_http_server_name
|
||||
http_server_port: INTEGER assign set_http_server_port
|
||||
max_tcp_clients: INTEGER assign set_max_tcp_clients
|
||||
-- Listen on socket for at most `queue' connections.
|
||||
|
||||
socket_timeout: INTEGER assign set_socket_timeout
|
||||
-- Amount of seconds that the server waits for receipts and transmissions during communications.
|
||||
-- note: with timeout of 0, socket can wait for ever.
|
||||
-- By default: 300 seconds, which is appropriate for most situations.
|
||||
|
||||
max_concurrent_connections: INTEGER assign set_max_concurrent_connections
|
||||
socket_accept_timeout: INTEGER assign set_socket_accept_timeout
|
||||
socket_connect_timeout: INTEGER assign set_socket_connect_timeout
|
||||
-- Max number of concurrent connections.
|
||||
|
||||
force_single_threaded: BOOLEAN assign set_force_single_threaded
|
||||
do
|
||||
Result := (max_concurrent_connections = 0)
|
||||
Result := max_concurrent_connections = 0
|
||||
end
|
||||
|
||||
is_verbose: BOOLEAN assign set_is_verbose
|
||||
-- Display verbose message to the output?
|
||||
|
||||
verbose_level: INTEGER assign set_verbose_level
|
||||
-- Verbosity of output.
|
||||
|
||||
keep_alive_timeout: INTEGER assign set_keep_alive_timeout
|
||||
-- Persistent connection timeout
|
||||
-- Persistent connection timeout.
|
||||
-- Number of seconds the server waits after a request has been served before it closes the connection.
|
||||
-- Timeout unit in Seconds.
|
||||
-- By default: 5 seconds.
|
||||
|
||||
max_keep_alive_requests: INTEGER assign set_max_keep_alive_requests
|
||||
-- Maximum number of requests allowed per persistent connection.
|
||||
-- Recommended a high setting.
|
||||
-- To disable KeepAlive, set `max_keep_alive_requests' to 0.
|
||||
-- By default: 100 .
|
||||
|
||||
has_ssl_support: BOOLEAN
|
||||
-- Has SSL support?
|
||||
deferred
|
||||
end
|
||||
|
||||
request_settings: HTTPD_REQUEST_SETTINGS
|
||||
do
|
||||
Result.is_verbose := is_verbose
|
||||
Result.verbose_level := verbose_level
|
||||
Result.timeout := socket_timeout
|
||||
Result.keep_alive_timeout := keep_alive_timeout
|
||||
Result.max_keep_alive_requests := max_keep_alive_requests
|
||||
end
|
||||
|
||||
feature -- Access: SSL
|
||||
|
||||
is_secure: BOOLEAN
|
||||
-- Is SSL/TLS session?.
|
||||
|
||||
ca_crt: STRING
|
||||
ca_crt: IMMUTABLE_STRING_8
|
||||
-- the signed certificate.
|
||||
|
||||
ca_key: STRING
|
||||
ca_key: IMMUTABLE_STRING_8
|
||||
-- private key to the certificate.
|
||||
|
||||
ssl_protocol: NATURAL
|
||||
@@ -74,7 +110,6 @@ feature -- Element change
|
||||
else
|
||||
create {IMMUTABLE_STRING_8} http_server_name.make_from_separate (v)
|
||||
end
|
||||
--| Missing postcondition.
|
||||
end
|
||||
|
||||
unset_http_server_name
|
||||
@@ -109,23 +144,35 @@ feature -- Element change
|
||||
max_concurrent_connections_set : max_concurrent_connections = v
|
||||
end
|
||||
|
||||
set_socket_accept_timeout (v: like socket_accept_timeout)
|
||||
-- Set `socket_accept_timeout' with `v'
|
||||
set_socket_timeout (a_nb_seconds: like socket_timeout)
|
||||
-- Set `socket_timeout' with `a_nb_seconds'
|
||||
do
|
||||
socket_accept_timeout := v
|
||||
socket_timeout := a_nb_seconds
|
||||
ensure
|
||||
socket_accept_timeout_set: socket_accept_timeout = v
|
||||
socket_timeout_set: socket_timeout = a_nb_seconds
|
||||
end
|
||||
|
||||
set_socket_connect_timeout (v: like socket_connect_timeout)
|
||||
-- Set `socket_connect_timeout' with `v'
|
||||
set_keep_alive_timeout (a_seconds: like keep_alive_timeout)
|
||||
-- Set `keep_alive_timeout' with `a_seconds'
|
||||
do
|
||||
socket_connect_timeout := v
|
||||
keep_alive_timeout := a_seconds
|
||||
ensure
|
||||
socket_connect_timeout_set: socket_connect_timeout = v
|
||||
keep_alive_timeout_set: keep_alive_timeout = a_seconds
|
||||
end
|
||||
|
||||
set_max_keep_alive_requests (nb: like max_keep_alive_requests)
|
||||
-- Set `max_keep_alive_requests' with `nb'
|
||||
do
|
||||
max_keep_alive_requests := nb
|
||||
ensure
|
||||
max_keep_alive_requests_set: max_keep_alive_requests = nb
|
||||
end
|
||||
|
||||
set_force_single_threaded (v: like force_single_threaded)
|
||||
-- Force server to handle incoming request in a single thread.
|
||||
-- i.e set max_concurrent_connections to 0!
|
||||
obsolete
|
||||
"Use set_max_concurrent_connections (0) [June/2016]"
|
||||
do
|
||||
if v then
|
||||
set_max_concurrent_connections (0)
|
||||
@@ -143,12 +190,12 @@ feature -- Element change
|
||||
is_verbose_set: is_verbose = b
|
||||
end
|
||||
|
||||
set_keep_alive_timeout (a_seconds: like keep_alive_timeout)
|
||||
-- Set `keep_alive_timeout' with `a_seconds'
|
||||
set_verbose_level (lev: INTEGER)
|
||||
-- Set `verbose_level' to `lev'.
|
||||
do
|
||||
keep_alive_timeout := a_seconds
|
||||
verbose_level := lev
|
||||
ensure
|
||||
keep_alive_timeout_set: keep_alive_timeout = a_seconds
|
||||
verbose_level_set: verbose_level = lev
|
||||
end
|
||||
|
||||
mark_secure
|
||||
@@ -171,20 +218,16 @@ feature -- Element change
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_ca_crt (a_value: STRING)
|
||||
-- Set `ca_crt' with `a_value'
|
||||
set_ca_crt (a_value: separate READABLE_STRING_8)
|
||||
-- Set `ca_crt' from `a_value'.
|
||||
do
|
||||
ca_crt := a_value
|
||||
ensure
|
||||
ca_crt_set: ca_crt = a_value
|
||||
create ca_crt.make_from_separate (a_value)
|
||||
end
|
||||
|
||||
set_ca_key (a_value: STRING)
|
||||
-- Set `ca_key' with `a_value'
|
||||
set_ca_key (a_value: separate READABLE_STRING_8)
|
||||
-- Set `ca_key' with `a_value'.
|
||||
do
|
||||
ca_key := a_value
|
||||
ensure
|
||||
ca_key_set: ca_key = a_value
|
||||
create ca_key.make_from_separate (a_value)
|
||||
end
|
||||
|
||||
set_ssl_protocol (a_version: NATURAL)
|
||||
@@ -0,0 +1,64 @@
|
||||
note
|
||||
description: "[
|
||||
Request settings for the standalone HTTPd server.
|
||||
]"
|
||||
author: "$Author$"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
expanded class
|
||||
HTTPD_REQUEST_SETTINGS
|
||||
|
||||
feature -- Access
|
||||
|
||||
is_verbose: BOOLEAN assign set_is_verbose
|
||||
-- Is verbose?
|
||||
|
||||
verbose_level: INTEGER assign set_verbose_level
|
||||
-- Verbosity of output.
|
||||
|
||||
timeout: INTEGER assign set_timeout
|
||||
-- Amount of seconds that the server waits for receipts and transmissions during communications.
|
||||
|
||||
keep_alive_timeout: INTEGER assign set_keep_alive_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.
|
||||
-- Unit in Seconds.
|
||||
|
||||
max_keep_alive_requests: INTEGER assign set_max_keep_alive_requests
|
||||
-- Maximum number of requests allowed per persistent connection.
|
||||
|
||||
feature -- Change
|
||||
|
||||
set_is_verbose (b: BOOLEAN)
|
||||
-- Set `is_verbose' to `b'.
|
||||
do
|
||||
is_verbose := b
|
||||
end
|
||||
|
||||
set_verbose_level (lev: INTEGER)
|
||||
-- Set `verbose_level' to `lev'.
|
||||
do
|
||||
verbose_level := lev
|
||||
end
|
||||
|
||||
set_timeout (a_timeout_in_seconds: INTEGER)
|
||||
-- Set `timeout' to `a_timeout_in_seconds'.
|
||||
do
|
||||
timeout := a_timeout_in_seconds
|
||||
end
|
||||
|
||||
set_keep_alive_timeout (a_timeout_in_seconds: INTEGER)
|
||||
-- Set `keep_alive_timeout' to `a_timeout_in_seconds'.
|
||||
do
|
||||
keep_alive_timeout := a_timeout_in_seconds
|
||||
end
|
||||
|
||||
set_max_keep_alive_requests (nb: like max_keep_alive_requests)
|
||||
-- Set `max_keep_alive_requests' with `nb'
|
||||
do
|
||||
max_keep_alive_requests := nb
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="http_network" uuid="56DAA1CE-0A2E-451A-BFC9-7821578E79F0" library_target="http_network">
|
||||
<target name="http_network">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="false" is_attached_by_default="true" void_safety="all" syntax="standard">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
|
||||
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl-safe.ecf">
|
||||
<condition>
|
||||
<custom name="net_ssl_enabled" value="true"/>
|
||||
</condition>
|
||||
</library>
|
||||
<cluster name="network" location=".\network" recursive="false">
|
||||
<cluster name="ssl_network" location="$|ssl" recursive="true">
|
||||
<condition>
|
||||
<custom name="net_ssl_enabled" value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="http_network" uuid="56DAA1CE-0A2E-451A-BFC9-7821578E79F0" library_target="http_network">
|
||||
<target name="http_network">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="false" void_safety="none" syntax="standard">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
|
||||
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl.ecf">
|
||||
<condition>
|
||||
<custom name="net_ssl_enabled" value="true"/>
|
||||
</condition>
|
||||
</library>
|
||||
<cluster name="network" location=".\network\">
|
||||
<cluster name="ssl_network" location="$|ssl\" recursive="true">
|
||||
<condition>
|
||||
<custom name="net_ssl_enabled" value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
@@ -24,11 +24,19 @@
|
||||
</condition>
|
||||
</library>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
||||
<cluster name="network" location=".\network" recursive="false">
|
||||
<cluster name="ssl_network" location="$|ssl" recursive="true">
|
||||
<condition>
|
||||
<custom name="httpd_ssl_enabled" value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
</cluster>
|
||||
<cluster name="httpd_server" location=".\" recursive="true">
|
||||
<file_rule>
|
||||
<exclude>/concurrency$</exclude>
|
||||
<exclude>/no_ssl$</exclude>
|
||||
<exclude>/ssl$</exclude>
|
||||
<exclude>/network$</exclude>
|
||||
</file_rule>
|
||||
<cluster name="no_ssl" location="$|no_ssl\" recursive="true">
|
||||
<condition>
|
||||
@@ -24,12 +24,19 @@
|
||||
</condition>
|
||||
</library>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
|
||||
<cluster name="network" location=".\network" recursive="false">
|
||||
<cluster name="ssl_network" location="$|ssl" recursive="true">
|
||||
<condition>
|
||||
<custom name="httpd_ssl_enabled" value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
</cluster>
|
||||
<cluster name="httpd_server" location=".\" recursive="true">
|
||||
<file_rule>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/concurrency$</exclude>
|
||||
<exclude>/no_ssl$</exclude>
|
||||
<exclude>/ssl$</exclude>
|
||||
<exclude>/network$</exclude>
|
||||
</file_rule>
|
||||
<cluster name="no_ssl" location="$|no_ssl\" recursive="true">
|
||||
<condition>
|
||||
@@ -0,0 +1,22 @@
|
||||
note
|
||||
description: "[
|
||||
Constant value to define the logging level.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
HTTPD_LOGGER_CONSTANTS
|
||||
|
||||
feature -- Access
|
||||
|
||||
alert_level: INTEGER = 1 -- 0000 0001
|
||||
critical_level: INTEGER = 2 -- 0000 0010
|
||||
error_level: INTEGER = 4 -- 0000 0100
|
||||
warning_level: INTEGER = 8 -- 0000 1000
|
||||
|
||||
notice_level: INTEGER = 16 -- 0001 0000
|
||||
information_level: INTEGER = 32 -- 0010 0000
|
||||
debug_level: INTEGER = 64 -- 0100 0000
|
||||
|
||||
end
|
||||
@@ -9,11 +9,20 @@ deferred class
|
||||
inherit
|
||||
HTTPD_DEBUG_FACILITIES
|
||||
|
||||
HTTPD_LOGGER_CONSTANTS
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
make (a_request_settings: HTTPD_REQUEST_SETTINGS)
|
||||
do
|
||||
reset
|
||||
-- Import global request settings.
|
||||
timeout := a_request_settings.timeout -- seconds
|
||||
keep_alive_timeout := a_request_settings.keep_alive_timeout -- seconds
|
||||
max_keep_alive_requests := a_request_settings.max_keep_alive_requests
|
||||
|
||||
is_verbose := a_request_settings.is_verbose
|
||||
verbose_level := a_request_settings.verbose_level
|
||||
end
|
||||
|
||||
reset
|
||||
@@ -107,16 +116,29 @@ feature -- Access
|
||||
feature -- Settings
|
||||
|
||||
is_verbose: BOOLEAN
|
||||
-- Output messages?
|
||||
|
||||
verbose_level: INTEGER
|
||||
-- Output verbosity.
|
||||
|
||||
is_persistent_connection_supported: BOOLEAN
|
||||
-- Is persistent connection supported?
|
||||
do
|
||||
Result := {HTTPD_SERVER}.is_persistent_connection_supported
|
||||
end
|
||||
Result := {HTTPD_SERVER}.is_persistent_connection_supported and then max_keep_alive_requests > 0
|
||||
end
|
||||
|
||||
persistent_connection_timeout: INTEGER = 5 -- seconds
|
||||
is_next_persistent_connection_supported: BOOLEAN
|
||||
-- Is next persistent connection supported?
|
||||
-- note: it is relevant only if `is_persistent_connection_supported' is True.
|
||||
|
||||
timeout: INTEGER -- seconds
|
||||
-- Amount of seconds that the server waits for receipts and transmissions during communications.
|
||||
|
||||
max_keep_alive_requests: INTEGER
|
||||
-- Maximum number of requests allowed per persistent connection.
|
||||
|
||||
keep_alive_timeout: INTEGER -- seconds
|
||||
-- Number of seconds for persistent connection timeout.
|
||||
-- Default: 5 sec.
|
||||
|
||||
feature -- Status report
|
||||
|
||||
@@ -162,7 +184,7 @@ feature -- Execution
|
||||
local
|
||||
l_socket: like client_socket
|
||||
l_exit: BOOLEAN
|
||||
n: INTEGER
|
||||
n,m: INTEGER
|
||||
do
|
||||
l_socket := client_socket
|
||||
check
|
||||
@@ -172,15 +194,23 @@ feature -- Execution
|
||||
from
|
||||
-- Process persistent connection as long the socket is not closed.
|
||||
n := 0
|
||||
m := max_keep_alive_requests
|
||||
is_next_persistent_connection_supported := True
|
||||
until
|
||||
l_exit
|
||||
loop
|
||||
n := n + 1
|
||||
if n >= m then
|
||||
is_next_persistent_connection_supported := False
|
||||
elseif n > 1 and is_verbose then
|
||||
log ("Reuse connection (" + n.out + ")", information_level)
|
||||
end
|
||||
-- FIXME: it seems to be called one more time, mostly to see this is done.
|
||||
execute_request
|
||||
l_exit := not is_persistent_connection_supported
|
||||
or has_error or l_socket.is_closed or not l_socket.is_open_read
|
||||
or not is_next_persistent_connection_supported -- related to `max_keep_alive_requests'
|
||||
or not is_persistent_connection_requested
|
||||
or has_error or l_socket.is_closed or not l_socket.is_open_read
|
||||
reset_request
|
||||
end
|
||||
end
|
||||
@@ -192,7 +222,6 @@ feature -- Execution
|
||||
l_remote_info: detachable like remote_info
|
||||
l_socket: like client_socket
|
||||
l_is_ready: BOOLEAN
|
||||
i: INTEGER
|
||||
do
|
||||
l_socket := client_socket
|
||||
check
|
||||
@@ -210,19 +239,11 @@ feature -- Execution
|
||||
|
||||
--| TODO: add configuration options for socket timeout.
|
||||
--| set by default 5 seconds.
|
||||
-- l_socket.set_timeout (persistent_connection_timeout) -- 5 seconds!
|
||||
l_socket.set_timeout (1) -- 1 second!
|
||||
from
|
||||
i := persistent_connection_timeout -- * 1 sec
|
||||
until
|
||||
l_is_ready or i <= 0 or has_error
|
||||
loop
|
||||
l_is_ready := l_socket.ready_for_reading
|
||||
check not l_socket.is_closed end
|
||||
i := i - 1
|
||||
end
|
||||
l_socket.set_timeout (keep_alive_timeout) -- 5 seconds!
|
||||
l_is_ready := l_socket.ready_for_reading
|
||||
|
||||
if l_is_ready then
|
||||
l_socket.set_timeout (timeout) -- FIXME: return a 408 Request Timeout response ..
|
||||
create l_remote_info
|
||||
if attached l_socket.peer_address as l_addr then
|
||||
l_remote_info.addr := l_addr.host_address.host_address
|
||||
@@ -242,10 +263,13 @@ feature -- Execution
|
||||
if l_is_ready then
|
||||
-- check catch_bad_incoming_connection: False end
|
||||
if is_verbose then
|
||||
log ("ERROR: invalid HTTP incoming request")
|
||||
log (request_header + "%NWARNING: invalid HTTP incoming request", warning_level)
|
||||
end
|
||||
end
|
||||
else
|
||||
if is_verbose then
|
||||
log (request_header, information_level)
|
||||
end
|
||||
process_request (l_socket)
|
||||
end
|
||||
debug ("dbglog")
|
||||
@@ -289,7 +313,12 @@ feature -- Parsing
|
||||
do
|
||||
create txt.make (64)
|
||||
request_header := txt
|
||||
if a_socket.is_readable and then attached next_line (a_socket) as l_request_line and then not l_request_line.is_empty then
|
||||
if
|
||||
not has_error and then
|
||||
a_socket.is_readable and then
|
||||
attached next_line (a_socket) as l_request_line and then
|
||||
not l_request_line.is_empty
|
||||
then
|
||||
txt.append (l_request_line)
|
||||
txt.append_character ('%N')
|
||||
analyze_request_line (l_request_line)
|
||||
@@ -302,11 +331,13 @@ feature -- Parsing
|
||||
from
|
||||
line := next_line (a_socket)
|
||||
until
|
||||
line = Void or end_of_stream
|
||||
line = Void or end_of_stream or has_error
|
||||
loop
|
||||
n := line.count
|
||||
if l_is_verbose then
|
||||
log (line)
|
||||
debug ("ew_standalone")
|
||||
if l_is_verbose then
|
||||
log (line, debug_level)
|
||||
end
|
||||
end
|
||||
pos := line.index_of (':', 1)
|
||||
if pos > 0 then
|
||||
@@ -353,9 +384,11 @@ feature -- Parsing
|
||||
local
|
||||
n, pos, next_pos: INTEGER
|
||||
do
|
||||
if is_verbose then
|
||||
log ("%N## Parse HTTP request line ##")
|
||||
log (line)
|
||||
debug ("ew_standalone")
|
||||
if is_verbose then
|
||||
log ("%N## Parse HTTP request line ##", debug_level)
|
||||
log (line, debug_level)
|
||||
end
|
||||
end
|
||||
pos := line.index_of (' ', 1)
|
||||
method := line.substring (1, pos - 1)
|
||||
@@ -372,15 +405,31 @@ feature -- Parsing
|
||||
next_line (a_socket: HTTPD_STREAM_SOCKET): detachable STRING
|
||||
-- Next line fetched from `a_socket' is available.
|
||||
require
|
||||
not_has_error: not has_error or is_verbose
|
||||
is_readable: a_socket.is_open_read
|
||||
local
|
||||
retried: BOOLEAN
|
||||
do
|
||||
if retried then
|
||||
has_error := True
|
||||
Result := Void
|
||||
elseif a_socket.socket_ok then
|
||||
elseif a_socket.readable then
|
||||
a_socket.read_line_thread_aware
|
||||
Result := a_socket.last_string
|
||||
-- Do no check `socket_ok' before socket operation,
|
||||
-- otherwise it may be False, due to error during other socket operation in same thread.
|
||||
if not a_socket.socket_ok then
|
||||
has_error := True
|
||||
if is_verbose then
|
||||
log (request_header +"%N" + Result + "%N## socket_ok=False! ##", debug_level)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Error with socket...
|
||||
has_error := True
|
||||
if is_verbose then
|
||||
log (request_header + "%N## Socket is not readable! ##", debug_level)
|
||||
end
|
||||
end
|
||||
rescue
|
||||
retried := True
|
||||
@@ -399,13 +448,17 @@ feature -- Output
|
||||
logger_set: logger = a_logger
|
||||
end
|
||||
|
||||
log (m: STRING)
|
||||
log (m: STRING; a_level: INTEGER)
|
||||
-- Log message `m'.
|
||||
require
|
||||
is_verbose: is_verbose
|
||||
do
|
||||
if attached logger as l_logger then
|
||||
l_logger.log (m)
|
||||
else
|
||||
io.put_string (m + "%N")
|
||||
if is_verbose and (verbose_level & a_level) = a_level then
|
||||
if attached logger as l_logger then
|
||||
l_logger.log (m)
|
||||
else
|
||||
io.put_string (m + "%N")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -37,7 +37,7 @@ feature {NONE} -- Initialization
|
||||
build_controller
|
||||
-- Build `controller'.
|
||||
do
|
||||
create controller
|
||||
create <NONE> controller
|
||||
end
|
||||
|
||||
initialize
|
||||
@@ -51,6 +51,9 @@ feature -- Access
|
||||
is_verbose: BOOLEAN
|
||||
-- Is verbose for output messages.
|
||||
|
||||
verbose_level: INTEGER
|
||||
-- Verbosity of output.
|
||||
|
||||
configuration: HTTPD_CONFIGURATION
|
||||
-- Associated server configuration.
|
||||
|
||||
@@ -100,7 +103,16 @@ feature -- Execution
|
||||
apply_configuration
|
||||
is_terminated := False
|
||||
if is_verbose then
|
||||
log ("%N%NStarting Web Application Server (port=" + configuration.http_server_port.out + "):%N")
|
||||
log ("%N%NStarting Web Application Server ...")
|
||||
log (" - port = " + configuration.http_server_port.out)
|
||||
log (" - max_tcp_clients = " + configuration.max_tcp_clients.out)
|
||||
log (" - max_concurrent_connections = " + configuration.max_concurrent_connections.out)
|
||||
log (" - socket_timeout = " + configuration.socket_timeout.out + " seconds")
|
||||
log (" - keep_alive_timeout = " + configuration.keep_alive_timeout.out + " seconds")
|
||||
log (" - max_keep_alive_requests = " + configuration.max_keep_alive_requests.out)
|
||||
if configuration.verbose_level > 0 then
|
||||
log (" - verbose_level = " + configuration.verbose_level.out)
|
||||
end
|
||||
end
|
||||
is_shutdown_requested := False
|
||||
listen
|
||||
@@ -150,7 +162,7 @@ feature -- Listening
|
||||
|
||||
if not l_listening_socket.is_bound then
|
||||
if is_verbose then
|
||||
log ("Socket could not be bound on port " + l_http_port.out)
|
||||
log ("Socket could not be bound on port " + l_http_port.out + " !")
|
||||
end
|
||||
else
|
||||
l_http_port := l_listening_socket.port
|
||||
@@ -159,9 +171,9 @@ feature -- Listening
|
||||
l_listening_socket.listen (configuration.max_tcp_clients)
|
||||
if is_verbose then
|
||||
if configuration.is_secure then
|
||||
log ("%NHTTP Connection Server ready on port " + l_http_port.out +" : https://localhost:" + l_http_port.out + "/")
|
||||
log ("%NListening on port " + l_http_port.out +" : https://localhost:" + l_http_port.out + "/")
|
||||
else
|
||||
log ("%NHTTP Connection Server ready on port " + l_http_port.out +" : http://localhost:" + l_http_port.out + "/")
|
||||
log ("%NListening on port " + l_http_port.out +" : http://localhost:" + l_http_port.out + "/")
|
||||
end
|
||||
end
|
||||
on_launched (l_http_port)
|
||||
@@ -312,6 +324,7 @@ feature -- Configuration change
|
||||
is_not_launched: not is_launched
|
||||
do
|
||||
is_verbose := configuration.is_verbose
|
||||
verbose_level := configuration.verbose_level
|
||||
end
|
||||
|
||||
feature -- Output
|
||||
@@ -12,6 +12,8 @@ class
|
||||
create
|
||||
make_server_by_address_and_port,
|
||||
make_server_by_port,
|
||||
make_client_by_address_and_port,
|
||||
make_client_by_port,
|
||||
make_from_separate,
|
||||
make_empty
|
||||
|
||||
@@ -30,6 +32,16 @@ feature {NONE} -- Initialization
|
||||
create {TCP_STREAM_SOCKET} socket.make_server_by_port (a_port)
|
||||
end
|
||||
|
||||
make_client_by_address_and_port (an_address: INET_ADDRESS; a_port: INTEGER)
|
||||
do
|
||||
create {TCP_STREAM_SOCKET} socket.make_client_by_address_and_port (an_address, a_port)
|
||||
end
|
||||
|
||||
make_client_by_port (a_peer_port: INTEGER; a_peer_host: STRING)
|
||||
do
|
||||
create {TCP_STREAM_SOCKET} socket.make_client_by_port (a_peer_port, a_peer_host)
|
||||
end
|
||||
|
||||
make_from_separate (s: separate HTTPD_STREAM_SOCKET)
|
||||
require
|
||||
descriptor_available: s.descriptor_available
|
||||
@@ -164,6 +176,11 @@ feature -- Status Report
|
||||
end
|
||||
end
|
||||
|
||||
exists: BOOLEAN
|
||||
do
|
||||
Result := socket.exists
|
||||
end
|
||||
|
||||
is_blocking: BOOLEAN
|
||||
do
|
||||
Result := socket.is_blocking
|
||||
@@ -176,6 +193,13 @@ feature -- Status Report
|
||||
end
|
||||
end
|
||||
|
||||
is_connected: BOOLEAN
|
||||
do
|
||||
if attached {TCP_STREAM_SOCKET} socket as l_socket then
|
||||
Result := l_socket.is_connected
|
||||
end
|
||||
end
|
||||
|
||||
is_created: BOOLEAN
|
||||
do
|
||||
if attached {NETWORK_SOCKET} socket as l_socket then
|
||||
@@ -220,6 +244,16 @@ feature -- Status Report
|
||||
end
|
||||
end
|
||||
|
||||
connect
|
||||
do
|
||||
socket.connect
|
||||
end
|
||||
|
||||
close
|
||||
do
|
||||
socket.close
|
||||
end
|
||||
|
||||
listen (a_queue: INTEGER)
|
||||
do
|
||||
socket.listen (a_queue)
|
||||
@@ -22,7 +22,9 @@ inherit
|
||||
|
||||
create
|
||||
make_ssl_server_by_address_and_port, make_ssl_server_by_port,
|
||||
make_server_by_address_and_port, make_server_by_port
|
||||
make_server_by_address_and_port, make_server_by_port,
|
||||
make_ssl_client_by_address_and_port, make_ssl_client_by_port,
|
||||
make_client_by_address_and_port, make_client_by_port
|
||||
|
||||
create {HTTPD_STREAM_SOCKET}
|
||||
make
|
||||
@@ -49,6 +51,26 @@ feature {NONE} -- Initialization
|
||||
set_certificates (a_crt, a_key)
|
||||
end
|
||||
|
||||
make_ssl_client_by_address_and_port (an_address: INET_ADDRESS; a_port: INTEGER; a_ssl_protocol: NATURAL; a_crt: STRING; a_key: STRING)
|
||||
local
|
||||
l_socket: SSL_TCP_STREAM_SOCKET
|
||||
do
|
||||
create l_socket.make_client_by_address_and_port (an_address, a_port)
|
||||
l_socket.set_tls_protocol (a_ssl_protocol)
|
||||
socket := l_socket
|
||||
set_certificates (a_crt, a_key)
|
||||
end
|
||||
|
||||
make_ssl_client_by_port (a_peer_port: INTEGER; a_peer_host: STRING; a_ssl_protocol: NATURAL; a_crt: STRING; a_key: STRING)
|
||||
local
|
||||
l_socket: SSL_TCP_STREAM_SOCKET
|
||||
do
|
||||
create l_socket.make_client_by_port (a_peer_port, a_peer_host)
|
||||
l_socket.set_tls_protocol (a_ssl_protocol)
|
||||
socket := l_socket
|
||||
set_certificates (a_crt, a_key)
|
||||
end
|
||||
|
||||
feature -- Output
|
||||
|
||||
put_readable_string_8 (s: READABLE_STRING_8)
|
||||
@@ -12,6 +12,7 @@ inherit
|
||||
|
||||
create
|
||||
make_server_by_address_and_port, make_server_by_port,
|
||||
make_client_by_address_and_port, make_client_by_port,
|
||||
make_empty
|
||||
|
||||
create {SSL_NETWORK_STREAM_SOCKET}
|
||||
@@ -15,6 +15,8 @@ inherit
|
||||
create
|
||||
make_server_by_address_and_port,
|
||||
make_server_by_port,
|
||||
make_client_by_address_and_port,
|
||||
make_client_by_port,
|
||||
make_from_separate,
|
||||
make_empty
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package httpd
|
||||
|
||||
project
|
||||
httpd = "httpd-safe.ecf"
|
||||
httpd = "httpd.ecf"
|
||||
|
||||
note
|
||||
title: HTTP server
|
||||
description: "[
|
||||
Simple HTTP listener and handler, that can be extended easily.
|
||||
]"
|
||||
tags: http,httpd,server,web
|
||||
collection: EWF
|
||||
copyright: 2011-2016, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others
|
||||
license: Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)
|
||||
link[license]: http://www.eiffel.com/licensing/forum.txt
|
||||
link[source]: "Github" https://github.com/EiffelWebFramework/EWF/library/server/httpd
|
||||
link[doc]: "Documentation" http://eiffelwebframework.github.io/EWF/
|
||||
|
||||
end
|
||||
@@ -24,13 +24,10 @@ create
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make_with_connector (conn: like connector)
|
||||
make_with_connector (a_request_settings: HTTPD_REQUEST_SETTINGS; conn: like connector)
|
||||
do
|
||||
make
|
||||
make (a_request_settings)
|
||||
connector := conn
|
||||
-- if conn /= Void then
|
||||
-- set_is_verbose (is_connector_verbose (conn))
|
||||
-- end
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
@@ -56,11 +53,6 @@ feature -- SCOOP helpers
|
||||
Result := conn.base
|
||||
end
|
||||
|
||||
is_connector_verbose (conn: separate WGI_STANDALONE_CONNECTOR [G]): BOOLEAN
|
||||
do
|
||||
Result := conn.is_verbose
|
||||
end
|
||||
|
||||
feature -- Request processing
|
||||
|
||||
process_request (a_socket: HTTPD_STREAM_SOCKET)
|
||||
@@ -87,7 +79,7 @@ feature -- Request processing
|
||||
else
|
||||
l_output.set_http_version (version)
|
||||
end
|
||||
res.set_is_persistent_connection_supported ({HTTPD_SERVER}.is_persistent_connection_supported)
|
||||
res.set_is_persistent_connection_supported (is_persistent_connection_supported and is_next_persistent_connection_supported)
|
||||
res.set_is_persistent_connection_requested (is_persistent_connection_requested)
|
||||
|
||||
req.set_meta_string_variable ("RAW_HEADER_DATA", request_header)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
note
|
||||
description: "Implementation of WGI request handler factory for WGI_STANDALOE_CONNECTOR."
|
||||
description: "Implementation of WGI request handler factory for WGI_STANDALONE_CONNECTOR."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
@@ -14,19 +14,23 @@ feature -- Access
|
||||
connector: detachable separate WGI_STANDALONE_CONNECTOR [G]
|
||||
-- httpd solution.
|
||||
|
||||
request_settings: HTTPD_REQUEST_SETTINGS
|
||||
-- Settings specific to request handling.
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_connector (conn: like connector)
|
||||
update_with (conn: like connector; a_conf: separate HTTPD_CONFIGURATION)
|
||||
-- Set `connector' with `conn'.
|
||||
do
|
||||
connector := conn
|
||||
request_settings := a_conf.request_settings
|
||||
end
|
||||
|
||||
feature -- Factory
|
||||
|
||||
new_handler: separate WGI_HTTPD_REQUEST_HANDLER [G]
|
||||
do
|
||||
create Result.make_with_connector (connector)
|
||||
create Result.make_with_connector (request_settings, connector)
|
||||
end
|
||||
|
||||
note
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
note
|
||||
description: "[
|
||||
Standalone Web Server connector
|
||||
Standalone Web Server connector.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
@@ -20,18 +20,18 @@ feature {NONE} -- Initialization
|
||||
make
|
||||
-- Create current standalone connector.
|
||||
local
|
||||
fac: separate WGI_HTTPD_REQUEST_HANDLER_FACTORY [G]
|
||||
fac: like request_handler_factory
|
||||
do
|
||||
-- Callbacks
|
||||
create on_launched_actions
|
||||
|
||||
-- Server
|
||||
create fac
|
||||
create <NONE> fac
|
||||
request_handler_factory := fac
|
||||
create server.make (fac)
|
||||
create observer
|
||||
create <NONE> observer
|
||||
configuration := server_configuration (server)
|
||||
controller := server_controller (server)
|
||||
set_factory_connector (Current, fac)
|
||||
initialize_server (server)
|
||||
end
|
||||
|
||||
@@ -51,9 +51,9 @@ feature {NONE} -- Separate helper
|
||||
a_server.set_observer (observer)
|
||||
end
|
||||
|
||||
set_factory_connector (conn: detachable separate WGI_STANDALONE_CONNECTOR [G]; fac: separate WGI_HTTPD_REQUEST_HANDLER_FACTORY [G])
|
||||
update_factory (conn: detachable separate WGI_STANDALONE_CONNECTOR [G]; fac: separate WGI_HTTPD_REQUEST_HANDLER_FACTORY [G]; a_conf: like configuration)
|
||||
do
|
||||
fac.set_connector (conn)
|
||||
fac.update_with (conn, a_conf)
|
||||
end
|
||||
|
||||
server_configuration (a_server: like server): like configuration
|
||||
@@ -63,17 +63,26 @@ feature {NONE} -- Separate helper
|
||||
|
||||
feature -- Access
|
||||
|
||||
name: STRING_8 = "httpd"
|
||||
name: STRING_8
|
||||
-- Name of Current connector
|
||||
once
|
||||
Result := "httpd"
|
||||
end
|
||||
|
||||
version: STRING_8 = "0.1"
|
||||
version: STRING_8
|
||||
-- Version of Current connector
|
||||
once
|
||||
Result := "1.0"
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
server: separate HTTPD_SERVER
|
||||
-- HTTPd server object.
|
||||
|
||||
request_handler_factory: separate WGI_HTTPD_REQUEST_HANDLER_FACTORY [G]
|
||||
-- Factory for request handlers.
|
||||
|
||||
controller: separate HTTPD_CONTROLLER
|
||||
-- Controller used to shutdown server.
|
||||
|
||||
@@ -97,9 +106,6 @@ feature -- Status report
|
||||
-- Listening port.
|
||||
--| 0: not launched
|
||||
|
||||
is_verbose: BOOLEAN
|
||||
-- Is verbose?
|
||||
|
||||
feature -- Callbacks
|
||||
|
||||
on_launched_actions: ACTION_SEQUENCE [TUPLE [WGI_STANDALONE_CONNECTOR [WGI_EXECUTION]]]
|
||||
@@ -190,18 +196,22 @@ feature {NONE} -- Implementation
|
||||
Result := a_server.controller
|
||||
end
|
||||
|
||||
configure_server (a_configuration: like configuration)
|
||||
apply_configuration (a_configuration: like configuration)
|
||||
local
|
||||
v: BOOLEAN
|
||||
do
|
||||
if a_configuration.is_verbose then
|
||||
if attached base as l_base then
|
||||
v := a_configuration.is_verbose
|
||||
if v then
|
||||
if attached base as l_base and then not l_base.is_whitespace then
|
||||
io.error.put_string ("Base=" + l_base + "%N")
|
||||
end
|
||||
end
|
||||
update_factory (Current, request_handler_factory, a_configuration)
|
||||
end
|
||||
|
||||
launch_server (a_server: like server)
|
||||
do
|
||||
configure_server (a_server.configuration)
|
||||
apply_configuration (a_server.configuration)
|
||||
a_server.launch
|
||||
end
|
||||
|
||||
@@ -229,13 +239,12 @@ feature {NONE} -- Implementation: element change
|
||||
|
||||
set_is_verbose_on_configuration (b: BOOLEAN; cfg: like configuration)
|
||||
do
|
||||
is_verbose := b
|
||||
cfg.set_is_verbose (b)
|
||||
end
|
||||
|
||||
|
||||
note
|
||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
note
|
||||
description: "[
|
||||
Interface to access protected feature from {WGI_STANDALONE_CONNECTOR}.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
WGI_STANDALONE_CONNECTOR_EXPORTER
|
||||
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
note
|
||||
description: "Export HTTPD_LOGGER_CONSTANTS to Standlone connector interfaces."
|
||||
|
||||
class
|
||||
WGI_STANDALONE_HTTPD_LOGGER_CONSTANTS
|
||||
|
||||
inherit
|
||||
HTTPD_LOGGER_CONSTANTS
|
||||
|
||||
end
|
||||
@@ -24,7 +24,7 @@ feature {NONE} -- Initialization
|
||||
set_source (a_source)
|
||||
end
|
||||
|
||||
feature {WGI_STANDALONE_CONNECTOR, WGI_SERVICE} -- Nino
|
||||
feature {WGI_STANDALONE_CONNECTOR, WGI_SERVICE, WGI_STANDALONE_CONNECTOR_EXPORTER} -- Standalone
|
||||
|
||||
set_source (i: like source)
|
||||
do
|
||||
|
||||
@@ -108,7 +108,6 @@ feature -- Header output operation
|
||||
l_connection := s.substring (i + 12, j - 1)
|
||||
l_connection.adjust
|
||||
if
|
||||
not is_http_version_1_0 and
|
||||
not l_connection.is_case_insensitive_equal_general ("close")
|
||||
then
|
||||
s.replace_substring ("Connection: close", i + 1, j - 1)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<library name="encoder" location="..\..\..\..\text\encoder\encoder-safe.ecf"/>
|
||||
<library name="ewsgi" location="..\..\ewsgi-safe.ecf" readonly="false"/>
|
||||
<library name="http" location="..\..\..\..\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="httpd" location="src\httpd\httpd-safe.ecf" readonly="false"/>
|
||||
<library name="httpd" location="lib\httpd\httpd-safe.ecf" readonly="false"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="implementation" location="$|implementation\" hidden="true"/>
|
||||
</cluster>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<library name="encoder" location="..\..\..\..\text\encoder\encoder.ecf"/>
|
||||
<library name="ewsgi" location="..\..\ewsgi.ecf" readonly="false"/>
|
||||
<library name="http" location="..\..\..\..\network\protocol\http\http.ecf"/>
|
||||
<library name="httpd" location="src\httpd\httpd.ecf" readonly="false"/>
|
||||
<library name="httpd" location="lib\httpd\httpd.ecf" readonly="false"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="implementation" location="$|implementation\" hidden="true"/>
|
||||
</cluster>
|
||||
|
||||
@@ -10,12 +10,11 @@
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="connector_standalone" location="standalone-safe.ecf" readonly="false"/>
|
||||
<library name="ewsgi" location="..\..\ewsgi-safe.ecf" readonly="false"/>
|
||||
<library name="httpd_edit" location="src\httpd\httpd-safe.ecf" readonly="false">
|
||||
<library name="httpd_edit" location="lib\httpd\httpd-safe.ecf" readonly="false">
|
||||
<option debug="true">
|
||||
<debug name="dbglog" enabled="true"/>
|
||||
</option>
|
||||
</library>
|
||||
<library name="net_ssl_edit" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl-safe.ecf" readonly="false"/>
|
||||
<library name="wsf" location="..\..\..\wsf\wsf-safe.ecf" readonly="false"/>
|
||||
<cluster name="tests" location="tests\" recursive="true"/>
|
||||
</target>
|
||||
|
||||
@@ -24,11 +24,11 @@
|
||||
<platform value="windows"/>
|
||||
</condition>
|
||||
</external_library>
|
||||
<external_library location="/usr/lib/libfcgi.so">
|
||||
<external_linker_flag value="-lfcgi">
|
||||
<condition>
|
||||
<platform value="unix"/>
|
||||
</condition>
|
||||
</external_library>
|
||||
</external_linker_flag>
|
||||
<external_library location="/opt/local/lib/libfcgi.dylib">
|
||||
<condition>
|
||||
<platform value="macintosh"/>
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
<platform value="windows"/>
|
||||
</condition>
|
||||
</external_library>
|
||||
<external_library location="/usr/lib/libfcgi.so">
|
||||
<external_linker_flag value="-lfcgi">
|
||||
<condition>
|
||||
<platform value="unix"/>
|
||||
</condition>
|
||||
</external_library>
|
||||
</external_linker_flag>
|
||||
<external_library location="/opt/local/lib/libfcgi.dylib">
|
||||
<condition>
|
||||
<platform value="macintosh"/>
|
||||
|
||||
@@ -25,6 +25,8 @@ inherit
|
||||
launchable
|
||||
end
|
||||
|
||||
WGI_STANDALONE_HTTPD_LOGGER_CONSTANTS
|
||||
|
||||
create
|
||||
make,
|
||||
make_and_launch
|
||||
@@ -34,38 +36,68 @@ feature {NONE} -- Initialization
|
||||
initialize
|
||||
local
|
||||
conn: like connector
|
||||
s: READABLE_STRING_GENERAL
|
||||
do
|
||||
create on_launched_actions
|
||||
create on_stopped_actions
|
||||
|
||||
port_number := 80 --| Default, but quite often, this port is already used ...
|
||||
max_concurrent_connections := 100
|
||||
max_tcp_clients := 100
|
||||
socket_timeout := 300 -- 300 seconds
|
||||
keep_alive_timeout := 15 -- 15 seconds.
|
||||
max_keep_alive_requests := 100
|
||||
verbose := False
|
||||
verbose_level := notice_level
|
||||
|
||||
base_url := ""
|
||||
|
||||
if attached options as opts then
|
||||
if attached {READABLE_STRING_GENERAL} opts.option ("server_name") as l_server_name then
|
||||
server_name := l_server_name.to_string_8
|
||||
end
|
||||
if attached {INTEGER} opts.option ("port") as l_port then
|
||||
port_number := l_port
|
||||
elseif
|
||||
attached {READABLE_STRING_GENERAL} opts.option ("port") as l_port_str and then
|
||||
l_port_str.is_integer
|
||||
then
|
||||
port_number := l_port_str.as_string_8.to_integer
|
||||
end
|
||||
if attached {READABLE_STRING_GENERAL} opts.option ("base") as l_base_str then
|
||||
base_url := l_base_str.as_string_8
|
||||
end
|
||||
if attached {BOOLEAN} opts.option ("force_single_threaded") as l_single_threaded then
|
||||
single_threaded := l_single_threaded
|
||||
elseif attached {READABLE_STRING_GENERAL} opts.option ("force_single_threaded") as l_single_threaded_str then
|
||||
single_threaded := l_single_threaded_str.as_lower.same_string ("true")
|
||||
verbose := opts.option_boolean_value ("verbose", verbose)
|
||||
-- See `{HTTPD_REQUEST_HANDLER_I}.*_verbose_level`
|
||||
|
||||
if opts.has_integer_option ("verbose_level") then
|
||||
verbose_level := opts.option_integer_value ("verbose_level", verbose_level)
|
||||
elseif attached {READABLE_STRING_GENERAL} opts.option ("verbose_level") as s_verbose_level then
|
||||
verbose_level := 0 -- Reset
|
||||
across
|
||||
s_verbose_level.split ('+') as ic
|
||||
loop
|
||||
s := ic.item
|
||||
if s.is_case_insensitive_equal ("alert") then
|
||||
verbose_level := verbose_level | alert_level
|
||||
elseif s.is_case_insensitive_equal ("critical") then
|
||||
verbose_level := verbose_level | critical_level
|
||||
elseif s.is_case_insensitive_equal ("error") then
|
||||
verbose_level := verbose_level | error_level
|
||||
elseif s.is_case_insensitive_equal ("warning") then
|
||||
verbose_level := verbose_level | warning_level
|
||||
elseif s.is_case_insensitive_equal ("notice") then
|
||||
verbose_level := verbose_level | notice_level
|
||||
elseif s.is_case_insensitive_equal ("information") then
|
||||
verbose_level := verbose_level | information_level
|
||||
elseif s.is_case_insensitive_equal ("debug") then
|
||||
verbose_level := verbose_level | debug_level
|
||||
else
|
||||
end
|
||||
end
|
||||
end
|
||||
if attached {BOOLEAN} opts.option ("verbose") as l_verbose then
|
||||
verbose := l_verbose
|
||||
elseif attached {READABLE_STRING_GENERAL} opts.option ("verbose") as l_verbose_str then
|
||||
verbose := l_verbose_str.as_lower.same_string ("true")
|
||||
port_number := opts.option_integer_value ("port", port_number)
|
||||
|
||||
if opts.option_boolean_value ("force_single_threaded", False) then
|
||||
force_single_threaded
|
||||
end
|
||||
max_concurrent_connections := opts.option_integer_value ("max_concurrent_connections", max_concurrent_connections)
|
||||
max_tcp_clients := opts.option_integer_value ("max_tcp_clients", max_tcp_clients)
|
||||
socket_timeout := opts.option_integer_value ("socket_timeout", socket_timeout)
|
||||
keep_alive_timeout := opts.option_integer_value ("keep_alive_timeout", keep_alive_timeout)
|
||||
max_keep_alive_requests := opts.option_integer_value ("max_keep_alive_requests", max_keep_alive_requests)
|
||||
end
|
||||
|
||||
create conn.make
|
||||
@@ -76,18 +108,25 @@ feature {NONE} -- Initialization
|
||||
update_configuration (conn.configuration)
|
||||
end
|
||||
|
||||
force_single_threaded
|
||||
-- Set `single_threaded' to True.
|
||||
do
|
||||
max_concurrent_connections := 1
|
||||
end
|
||||
|
||||
feature -- Execution
|
||||
|
||||
update_configuration (cfg: like connector.configuration)
|
||||
do
|
||||
if single_threaded then
|
||||
cfg.set_force_single_threaded (True)
|
||||
end
|
||||
cfg.set_is_verbose (verbose)
|
||||
if attached server_name as l_server_name then
|
||||
cfg.set_http_server_name (l_server_name)
|
||||
end
|
||||
cfg.set_verbose_level (verbose_level)
|
||||
cfg.set_http_server_name (server_name)
|
||||
cfg.http_server_port := port_number
|
||||
cfg.set_max_concurrent_connections (max_concurrent_connections)
|
||||
cfg.set_max_tcp_clients (max_tcp_clients)
|
||||
cfg.set_socket_timeout (socket_timeout)
|
||||
cfg.set_keep_alive_timeout (keep_alive_timeout)
|
||||
cfg.set_max_keep_alive_requests (max_keep_alive_requests)
|
||||
end
|
||||
|
||||
launch
|
||||
@@ -98,7 +137,7 @@ feature -- Execution
|
||||
do
|
||||
conn := connector
|
||||
conn.set_base (base_url)
|
||||
debug ("nino")
|
||||
debug ("ew_standalone")
|
||||
if verbose then
|
||||
io.error.put_string ("Launching standalone web server on port " + port_number.out)
|
||||
if attached server_name as l_name then
|
||||
@@ -114,15 +153,15 @@ feature -- Execution
|
||||
|
||||
feature -- Callback
|
||||
|
||||
on_launched_actions: ACTION_SEQUENCE [TUPLE [WGI_STANDALONE_CONNECTOR [G]]]
|
||||
on_launched_actions: ACTION_SEQUENCE [TUPLE [like connector]]
|
||||
-- Actions triggered when launched
|
||||
|
||||
on_stopped_actions: ACTION_SEQUENCE [TUPLE [WGI_STANDALONE_CONNECTOR [G]]]
|
||||
on_stopped_actions: ACTION_SEQUENCE [TUPLE [like connector]]
|
||||
-- Actions triggered when stopped
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
on_launched (conn: WGI_STANDALONE_CONNECTOR [G])
|
||||
on_launched (conn: like connector)
|
||||
do
|
||||
on_launched_actions.call ([conn])
|
||||
end
|
||||
@@ -134,8 +173,20 @@ feature {NONE} -- Implementation
|
||||
base_url: READABLE_STRING_8
|
||||
|
||||
verbose: BOOLEAN
|
||||
verbose_level: INTEGER
|
||||
-- Help defining the verbosity.
|
||||
-- The higher, the more output.
|
||||
|
||||
max_concurrent_connections: INTEGER
|
||||
max_tcp_clients: INTEGER
|
||||
socket_timeout: INTEGER
|
||||
keep_alive_timeout: INTEGER
|
||||
max_keep_alive_requests: INTEGER
|
||||
|
||||
single_threaded: BOOLEAN
|
||||
do
|
||||
Result := max_concurrent_connections = 0
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
|
||||
|
||||
25
library/server/wsf/connector/standalone_websocket-safe.ecf
Normal file
25
library/server/wsf/connector/standalone_websocket-safe.ecf
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="wsf_standalone_websocket" uuid="7C83D4B4-39C9-4D27-941B-0F0AAD45122E" library_target="wsf_standalone_websocket">
|
||||
<target name="wsf_standalone_websocket">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/\.git$</exclude>
|
||||
<exclude>/\.svn$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
|
||||
</option>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="connector_standalone" location="..\..\ewsgi\connectors\standalone\standalone-safe.ecf"/>
|
||||
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto-safe.ecf"/>
|
||||
<library name="encoder" location="..\..\..\text\encoder\encoder-safe.ecf" readonly="false"/>
|
||||
<library name="error" location="..\..\..\utility\general\error\error-safe.ecf"/>
|
||||
<library name="ewsgi" location="..\..\ewsgi\ewsgi-safe.ecf"/>
|
||||
<library name="http" location="..\..\..\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="httpd" location="..\..\ewsgi\connectors\standalone\lib\httpd\httpd-safe.ecf"/>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
||||
<library name="wsf" location="..\wsf-safe.ecf"/>
|
||||
<library name="wsf_standalone" location="standalone-safe.ecf"/>
|
||||
<cluster name="wsf_standalone_websocket" location=".\standalone_websocket\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
25
library/server/wsf/connector/standalone_websocket.ecf
Normal file
25
library/server/wsf/connector/standalone_websocket.ecf
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="wsf_standalone_websocket" uuid="7C83D4B4-39C9-4D27-941B-0F0AAD45122E" library_target="wsf_standalone_websocket">
|
||||
<target name="wsf_standalone_websocket">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/\.git$</exclude>
|
||||
<exclude>/\.svn$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="true" is_obsolete_routine_type="true" void_safety="none" syntax="provisional">
|
||||
</option>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="connector_standalone" location="..\..\ewsgi\connectors\standalone\standalone.ecf"/>
|
||||
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto.ecf"/>
|
||||
<library name="encoder" location="..\..\..\text\encoder\encoder.ecf" readonly="false"/>
|
||||
<library name="error" location="..\..\..\utility\general\error\error.ecf"/>
|
||||
<library name="ewsgi" location="..\..\ewsgi\ewsgi.ecf"/>
|
||||
<library name="http" location="..\..\..\network\protocol\http\http.ecf"/>
|
||||
<library name="httpd" location="..\..\ewsgi\connectors\standalone\lib\httpd\httpd.ecf"/>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
|
||||
<library name="wsf" location="..\wsf.ecf"/>
|
||||
<library name="wsf_standalone" location="standalone.ecf"/>
|
||||
<cluster name="wsf_standalone_websocket" location=".\standalone_websocket\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
@@ -0,0 +1,137 @@
|
||||
note
|
||||
description: "[
|
||||
Websocket callback events for actions like opening and closing the connection,
|
||||
sending and receiving messages, and listening.
|
||||
|
||||
Define the websocket events:
|
||||
- on_open
|
||||
- on_binary
|
||||
- on_text
|
||||
- on_close
|
||||
|
||||
note: the following features could also be redefined:
|
||||
- on_pong
|
||||
- on_ping
|
||||
- on_unsupported
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
WEB_SOCKET_EVENT_I
|
||||
|
||||
inherit
|
||||
WEB_SOCKET_CONSTANTS
|
||||
|
||||
REFACTORING_HELPER
|
||||
|
||||
feature -- Web Socket Interface
|
||||
|
||||
on_event (ws: WEB_SOCKET; a_message: detachable READABLE_STRING_8; a_opcode: INTEGER)
|
||||
-- Called when a frame from the client has been receive
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
local
|
||||
l_message: READABLE_STRING_8
|
||||
do
|
||||
debug ("ws")
|
||||
ws.log ("%Non_event (ws, a_message, " + opcode_name (a_opcode) + ")%N", {HTTPD_LOGGER_CONSTANTS}.debug_level)
|
||||
end
|
||||
if a_message = Void then
|
||||
create {STRING} l_message.make_empty
|
||||
else
|
||||
l_message := a_message
|
||||
end
|
||||
|
||||
if a_opcode = Binary_frame then
|
||||
on_binary (ws, l_message)
|
||||
elseif a_opcode = Text_frame then
|
||||
on_text (ws, l_message)
|
||||
elseif a_opcode = Pong_frame then
|
||||
on_pong (ws, l_message)
|
||||
elseif a_opcode = Ping_frame then
|
||||
on_ping (ws, l_message)
|
||||
elseif a_opcode = Connection_close_frame then
|
||||
on_connection_close (ws, "")
|
||||
else
|
||||
on_unsupported (ws, l_message, a_opcode)
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Websocket events
|
||||
|
||||
on_open (ws: WEB_SOCKET)
|
||||
-- Called after handshake, indicates that a complete WebSocket connection has been established.
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
deferred
|
||||
end
|
||||
|
||||
on_binary (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
deferred
|
||||
end
|
||||
|
||||
on_text (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
deferred
|
||||
end
|
||||
|
||||
on_close (ws: detachable WEB_SOCKET)
|
||||
-- Called after the WebSocket connection is closed.
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Websocket events: implemented
|
||||
|
||||
on_pong (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
do
|
||||
-- log ("Its a pong frame")
|
||||
-- at first we ignore pong
|
||||
-- FIXME: provide better explanation
|
||||
end
|
||||
|
||||
on_ping (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
do
|
||||
ws.send (Pong_frame, a_message)
|
||||
end
|
||||
|
||||
on_unsupported (ws: WEB_SOCKET; a_message: READABLE_STRING_8; a_opcode: INTEGER)
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
do
|
||||
-- do nothing
|
||||
end
|
||||
|
||||
on_connection_close (ws: WEB_SOCKET; a_message: detachable READABLE_STRING_8)
|
||||
require
|
||||
ws_attached: ws /= Void
|
||||
ws_valid: ws.is_open_read and then ws.is_open_write
|
||||
do
|
||||
ws.send (Connection_close_frame, "")
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2016, 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
|
||||
@@ -0,0 +1,39 @@
|
||||
note
|
||||
description: "[
|
||||
A web socket message has an opcode specifying the type of the message payload. The
|
||||
opcode consists of the last four bits in the first byte of the frame header.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
EIS: "name=Data Frame", "src=http://tools.ietf.org/html/rfc6455#section-5.6", "protocol=uri"
|
||||
EIS: "name=Control Frame", "src=http://tools.ietf.org/html/rfc6455#section-5.5", "protocol=uri"
|
||||
|
||||
class
|
||||
WEB_SOCKET_MESSAGE_TYPE
|
||||
|
||||
feature -- Data Frames
|
||||
|
||||
Text: INTEGER = 0x1
|
||||
-- The data type of the message is text.
|
||||
|
||||
Binary: INTEGER = 0x2
|
||||
-- The data type of the message is binary.
|
||||
|
||||
feature -- Control Frames
|
||||
|
||||
Close: INTEGER = 0x8
|
||||
-- The client or server is sending a closing
|
||||
-- handshake to the server or client.
|
||||
|
||||
Ping: INTEGER = 0x9
|
||||
-- The client or server sends a ping to the server or client.
|
||||
|
||||
Pong: INTEGER = 0xA
|
||||
-- The client or server sends a pong to the server or client.
|
||||
|
||||
feature -- Reserverd
|
||||
|
||||
-- Opcodes 0x3-0x7 are reserved for further non-control frames yet to be
|
||||
-- defined.
|
||||
|
||||
end
|
||||
@@ -0,0 +1,203 @@
|
||||
note
|
||||
description: "Constants for WebSockets"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET_CONSTANTS
|
||||
|
||||
feature -- Constants
|
||||
|
||||
|
||||
HTTP_1_1: STRING = "HTTP/1.1 101 WebSocket Protocol Handshake"
|
||||
|
||||
Upgrade_ws: STRING = "Upgrade: websocket"
|
||||
|
||||
Connection_ws: STRING = "Connection: Upgrade"
|
||||
|
||||
Sec_WebSocket_Origin: STRING = "Sec-WebSocket-Origin: "
|
||||
|
||||
Sec_WebSocket_Protocol: STRING = "Sec-WebSocket-Protocol: "
|
||||
|
||||
Sec_WebSocket_Location: STRING = "Sec-WebSocket-Location: "
|
||||
|
||||
Sec_WebSocket_Version: STRING = "Sec-WebSocket-Version: "
|
||||
|
||||
Sec_WebSocket_Extensions: STRING = "Sec-WebSocket-Extensions: "
|
||||
|
||||
WebSocket_Origin: STRING = "WebSocket-Origin: "
|
||||
|
||||
WebSocket_Protocol: STRING = "WebSocket-Protocol: "
|
||||
|
||||
WebSocket_Location: STRING = "WebSocket-Location: "
|
||||
|
||||
Origin: STRING = "Origin"
|
||||
|
||||
Server: STRING = "EWSS"
|
||||
|
||||
Sec_WebSocket_Key: STRING = "Sec-WebSocket-Key"
|
||||
|
||||
Ws_scheme: STRING = "ws://"
|
||||
|
||||
Wss_scheme: STRING = "wss://"
|
||||
|
||||
Magic_guid: STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
-- The handshake from the client looks as follows:
|
||||
|
||||
-- GET /chat HTTP/1.1
|
||||
-- Host: server.example.com
|
||||
-- Upgrade: websocket
|
||||
-- Connection: Upgrade
|
||||
-- Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
|
||||
-- Origin: http://example.com
|
||||
-- Sec-WebSocket-Protocol: chat, superchat
|
||||
-- Sec-WebSocket-Version: 13
|
||||
|
||||
-- The handshake from the server looks as follows:
|
||||
|
||||
-- HTTP/1.1 101 Switching Protocols
|
||||
-- Upgrade: websocket
|
||||
-- Connection: Upgrade
|
||||
-- Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
||||
-- Sec-WebSocket-Protocol: chat
|
||||
|
||||
feature -- Opcodes Standard actions
|
||||
|
||||
--| Maybe we need an enum STANDARD_ACTIONS_OPCODES?
|
||||
-- |Opcode | Meaning | Reference |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 0 | Continuation Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 1 | Text Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 2 | Binary Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 8 | Connection Close Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 9 | Ping Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
-- | 10 | Pong Frame | RFC 6455 |
|
||||
-- -+--------+-------------------------------------+-----------|
|
||||
|
||||
Continuation_frame: INTEGER = 0
|
||||
|
||||
Text_frame: INTEGER = 1
|
||||
|
||||
Binary_frame: INTEGER = 2
|
||||
|
||||
Connection_close_frame: INTEGER = 8
|
||||
|
||||
Ping_frame: INTEGER = 9
|
||||
|
||||
Pong_frame: INTEGER = 10
|
||||
|
||||
is_control_frame (a_opcode: INTEGER): BOOLEAN
|
||||
-- Is `a_opcode' a control frame?
|
||||
do
|
||||
inspect a_opcode
|
||||
when Connection_close_frame, Ping_frame, Pong_frame then
|
||||
Result := True
|
||||
else
|
||||
end
|
||||
end
|
||||
|
||||
opcode_name (a_opcode: INTEGER): STRING
|
||||
do
|
||||
inspect a_opcode
|
||||
when Continuation_frame then Result := "Continuation"
|
||||
when Text_frame then Result := "Text"
|
||||
when Binary_frame then Result := "Binary"
|
||||
when Connection_close_frame then Result := "Connection Close"
|
||||
when Ping_frame then Result := "Ping"
|
||||
when Pong_frame then Result := "Pong"
|
||||
else
|
||||
Result := "Unknown-Opcode"
|
||||
end
|
||||
Result := "0x" + a_opcode.to_hex_string + " " + Result
|
||||
end
|
||||
|
||||
feature -- Close code numbers
|
||||
|
||||
-- Maybe an ENUM CLOSE_CODES
|
||||
|
||||
-- |Status Code | Meaning | Contact | Reference |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1000 | Normal Closure | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1001 | Going Away | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1002 | Protocol error | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1003 | Unsupported Data| hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1004 | ---Reserved---- | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1005 | No Status Rcvd | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1006 | Abnormal Closure| hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1007 | Invalid frame | hybi@ietf.org | RFC 6455 |
|
||||
-- | | payload data | | |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1008 | Policy Violation| hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1009 | Message Too Big | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1010 | Mandatory Ext. | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1011 | Internal Server | hybi@ietf.org | RFC 6455 |
|
||||
-- | | Error | | |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
-- | 1015 | TLS handshake | hybi@ietf.org | RFC 6455 |
|
||||
-- -+------------+-----------------+---------------+-----------|
|
||||
|
||||
Normal_closure: INTEGER = 1000
|
||||
-- Indicates a normal closure, meaning that the purpose for
|
||||
-- which the connection was established has been fulfilled.
|
||||
|
||||
Going_away: INTEGER = 1001
|
||||
-- Indicates that an endpoint is "going away", such as a server
|
||||
-- going down or a browser having navigated away from a page.
|
||||
|
||||
Protocol_error: INTEGER = 1002
|
||||
-- Indicates that an endpoint is terminating the connection due
|
||||
-- to a protocol error.
|
||||
|
||||
Unsupported_data: INTEGER = 1003
|
||||
-- Indicates that an endpoint is terminating the connection
|
||||
-- because it has received a type of data it cannot accept (e.g., an
|
||||
-- endpoint that understands only text data MAY send this if it
|
||||
-- receives a binary message).
|
||||
|
||||
Invalid_data: INTEGER = 1007
|
||||
-- Indicates that an endpoint is terminating the connection
|
||||
-- because it has received data within a message that was not
|
||||
-- consistent with the type of the message (e.g., non-UTF-8 [RFC3629]
|
||||
-- data within a text message).
|
||||
|
||||
Policy_violation: INTEGER = 1008
|
||||
-- Indicates that an endpoint is terminating the connection
|
||||
-- because it has received a message that violates its policy. This
|
||||
-- is a generic status code that can be returned when there is no
|
||||
-- other more suitable status code (e.g., 1003 or 1009) or if there
|
||||
-- is a need to hide specific details about the policy.
|
||||
|
||||
Message_too_large: INTEGER = 1009
|
||||
-- Indicates that an endpoint is terminating the connection
|
||||
-- because it has received a message that is too big for it to
|
||||
-- process.
|
||||
|
||||
Extension_required: INTEGER = 1010
|
||||
-- Indicates that an endpoint (client) is terminating the
|
||||
-- connection because it has expected the server to negotiate one or
|
||||
-- more extension, but the server didn't return them in the response
|
||||
-- message of the WebSocket handshake.
|
||||
|
||||
Internal_error: INTEGER = 1011
|
||||
-- Indicates that a server is terminating the connection because
|
||||
-- it encountered an unexpected condition that prevented it from
|
||||
-- fulfilling the request.
|
||||
|
||||
|
||||
end
|
||||
@@ -0,0 +1,35 @@
|
||||
note
|
||||
description: "Summary description for {WEB_SOCKET_ERROR_FRAME}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET_ERROR_FRAME
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_code: INTEGER; a_desc: like description)
|
||||
do
|
||||
code := a_code
|
||||
description := a_desc
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
code: INTEGER
|
||||
|
||||
description: READABLE_STRING_8
|
||||
|
||||
feature -- Conversion
|
||||
|
||||
string: STRING
|
||||
do
|
||||
create Result.make_from_string ("Error(" + code.out + "): " + description)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
@@ -0,0 +1,437 @@
|
||||
note
|
||||
description: "[
|
||||
Summary description for {WEB_SOCKET_FRAME}.
|
||||
See Base Framing Protocol: http://tools.ietf.org/html/rfc6455#section-5.2
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-------+-+-------------+-------------------------------+
|
||||
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|
||||
|I|S|S|S| (4) |A| (7) | (16/64) |
|
||||
|N|V|V|V| |S| | (if payload len==126/127) |
|
||||
| |1|2|3| |K| | |
|
||||
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
||||
| Extended payload length continued, if payload len == 127 |
|
||||
+ - - - - - - - - - - - - - - - +-------------------------------+
|
||||
| |Masking-key, if MASK set to 1 |
|
||||
+-------------------------------+-------------------------------+
|
||||
| Masking-key (continued) | Payload Data |
|
||||
+-------------------------------- - - - - - - - - - - - - - - - +
|
||||
: Payload Data continued ... :
|
||||
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
||||
| Payload Data continued ... |
|
||||
+---------------------------------------------------------------+
|
||||
|
||||
Check the `check_utf_8_validity_on_chop' if there is performance issue
|
||||
with bigger data.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
EIS: "name=Websocket RFC6455 section-5.2", "protocol=URI", "src=http://tools.ietf.org/html/rfc6455#section-5.2", "tag=rfc"
|
||||
|
||||
class
|
||||
WEB_SOCKET_FRAME
|
||||
|
||||
inherit
|
||||
ANY
|
||||
|
||||
WEB_SOCKET_CONSTANTS
|
||||
|
||||
create
|
||||
make,
|
||||
make_as_injected_control
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_opcode: INTEGER; flag_is_fin: BOOLEAN)
|
||||
-- Create current frame with opcode `a_opcode'
|
||||
-- and `a_fin' to indicate if this is the final fragment.
|
||||
do
|
||||
is_incomplete := False
|
||||
opcode := a_opcode
|
||||
is_fin := flag_is_fin
|
||||
|
||||
inspect opcode
|
||||
when
|
||||
Continuation_frame, -- 0
|
||||
Text_frame, -- 1
|
||||
Binary_frame -- 2
|
||||
then
|
||||
--| Supported opcode
|
||||
when
|
||||
Connection_close_frame, -- 8
|
||||
Ping_frame, -- 9
|
||||
Pong_frame -- 10
|
||||
then
|
||||
--| Supported control opcode
|
||||
-- All control frames MUST have a payload length of 125 bytes or less
|
||||
-- and MUST NOT be fragmented.
|
||||
if flag_is_fin then
|
||||
-- So far it is valid.
|
||||
else
|
||||
report_error (Protocol_error, "Control frames MUST NOT be fragmented.")
|
||||
end
|
||||
else
|
||||
report_error (Protocol_error, "Unknown opcode")
|
||||
end
|
||||
end
|
||||
|
||||
make_as_injected_control (a_opcode: INTEGER; a_parent: WEB_SOCKET_FRAME)
|
||||
require
|
||||
parent_is_not_control_frame: not a_parent.is_control
|
||||
a_opcode_is_control_frame: is_control_frame (a_opcode)
|
||||
do
|
||||
make (a_opcode, True)
|
||||
parent := a_parent
|
||||
a_parent.add_injected_control_frame (Current)
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
opcode: INTEGER
|
||||
-- CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING
|
||||
|
||||
is_fin: BOOLEAN
|
||||
-- is the final fragment in a message?
|
||||
|
||||
fragment_count: INTEGER
|
||||
|
||||
payload_length: NATURAL_64
|
||||
payload_data: detachable STRING_8
|
||||
-- Maybe we need a buffer here.
|
||||
|
||||
uncoded_payload_data: detachable STRING_32
|
||||
local
|
||||
utf: UTF_CONVERTER
|
||||
do
|
||||
if attached payload_data as d then
|
||||
Result := utf.utf_8_string_8_to_string_32 (d)
|
||||
end
|
||||
end
|
||||
|
||||
error: detachable WEB_SOCKET_ERROR_FRAME
|
||||
-- Describe the type of error
|
||||
|
||||
feature -- Access: injected control frames
|
||||
|
||||
injected_control_frames: detachable LIST [WEB_SOCKET_FRAME]
|
||||
|
||||
parent: detachable WEB_SOCKET_FRAME
|
||||
-- If Current is injected, `parent' is the related fragmented frame
|
||||
|
||||
is_injected_control: BOOLEAN
|
||||
do
|
||||
Result := parent /= Void
|
||||
ensure
|
||||
Result implies (is_control_frame (opcode))
|
||||
end
|
||||
|
||||
feature -- Operation
|
||||
|
||||
update_fin (a_flag_is_fin: BOOLEAN)
|
||||
do
|
||||
is_fin := a_flag_is_fin
|
||||
end
|
||||
|
||||
feature {WEB_SOCKET_FRAME} -- Change: injected control frames
|
||||
|
||||
add_injected_control_frame (f: WEB_SOCKET_FRAME)
|
||||
require
|
||||
Current_is_not_control: not is_control
|
||||
f_is_control_frame: f.is_control
|
||||
parented_to_current: f.parent = Current
|
||||
local
|
||||
lst: like injected_control_frames
|
||||
do
|
||||
lst := injected_control_frames
|
||||
if lst = Void then
|
||||
create {ARRAYED_LIST [WEB_SOCKET_FRAME]} lst.make (1)
|
||||
injected_control_frames := lst
|
||||
end
|
||||
lst.force (f)
|
||||
ensure
|
||||
parented_to_current: f.parent = Current
|
||||
end
|
||||
|
||||
remove_injected_control_frame (f: WEB_SOCKET_FRAME)
|
||||
require
|
||||
Current_is_not_control: not is_control
|
||||
f_is_control_frame: f.is_control
|
||||
parented_to_current: f.parent = Current
|
||||
local
|
||||
lst: like injected_control_frames
|
||||
do
|
||||
lst := injected_control_frames
|
||||
if lst /= Void then
|
||||
lst.prune (f)
|
||||
if lst.is_empty then
|
||||
injected_control_frames := Void
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Query
|
||||
|
||||
is_binary: BOOLEAN
|
||||
do
|
||||
Result := opcode = binary_frame
|
||||
end
|
||||
|
||||
is_text: BOOLEAN
|
||||
do
|
||||
Result := opcode = text_frame
|
||||
end
|
||||
|
||||
is_continuation: BOOLEAN
|
||||
do
|
||||
Result := opcode = continuation_frame
|
||||
end
|
||||
|
||||
is_connection_close: BOOLEAN
|
||||
do
|
||||
Result := opcode = connection_close_frame
|
||||
end
|
||||
|
||||
is_control: BOOLEAN
|
||||
do
|
||||
inspect opcode
|
||||
when connection_close_frame, Ping_frame, Pong_frame then
|
||||
Result := True
|
||||
else
|
||||
end
|
||||
end
|
||||
|
||||
is_ping: BOOLEAN
|
||||
do
|
||||
Result := opcode = ping_frame
|
||||
end
|
||||
|
||||
is_pong: BOOLEAN
|
||||
do
|
||||
Result := opcode = pong_frame
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
|
||||
is_valid: BOOLEAN
|
||||
do
|
||||
Result := not has_error
|
||||
end
|
||||
|
||||
is_incomplete: BOOLEAN
|
||||
|
||||
has_error: BOOLEAN
|
||||
do
|
||||
Result := error /= Void
|
||||
end
|
||||
|
||||
feature -- Change
|
||||
|
||||
increment_fragment_count
|
||||
do
|
||||
fragment_count := fragment_count + 1
|
||||
end
|
||||
|
||||
check_utf_8_validity_on_chop: BOOLEAN = False
|
||||
-- True: check for each chop
|
||||
-- False: check only for each fragment
|
||||
--| see autobahntestsuite #6.4.3 and #6.4.4
|
||||
|
||||
append_payload_data_chop (a_data: STRING_8; a_len: INTEGER; a_flag_chop_complete: BOOLEAN)
|
||||
do
|
||||
if a_flag_chop_complete then
|
||||
increment_fragment_count
|
||||
end
|
||||
if attached payload_data as l_payload_data then
|
||||
l_payload_data.append (a_data)
|
||||
else
|
||||
payload_data := a_data
|
||||
end
|
||||
payload_length := payload_length + a_len.to_natural_64
|
||||
|
||||
if is_text then
|
||||
if is_fin and a_flag_chop_complete then
|
||||
-- Check the whole message is a valid UTF-8 string
|
||||
if attached payload_data as d then
|
||||
if not is_valid_utf_8_string (d) then
|
||||
report_error (invalid_data, "The text message is not a valid UTF-8 text!")
|
||||
end
|
||||
else
|
||||
-- empty payload??
|
||||
end
|
||||
elseif check_utf_8_validity_on_chop or else a_flag_chop_complete then
|
||||
-- Check the payload data as utf-8 stream (may be incomplete at this point)
|
||||
if not is_valid_text_payload_stream then
|
||||
report_error (invalid_data, "This is not a valid UTF-8 stream!")
|
||||
-- is_valid implies the connection will be closed!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
report_error (a_code: INTEGER; a_description: READABLE_STRING_8)
|
||||
require
|
||||
not has_error
|
||||
do
|
||||
create error.make (a_code, a_description)
|
||||
ensure
|
||||
has_error: has_error
|
||||
is_not_valid: not is_valid
|
||||
end
|
||||
|
||||
feature {NONE} -- Helper
|
||||
|
||||
last_utf_8_stream_validation_position: INTEGER
|
||||
-- In relation with `is_valid_utf_8 (.., a_is_stream=True)'
|
||||
|
||||
is_valid_text_payload_stream: BOOLEAN
|
||||
require
|
||||
is_text_frame: is_text
|
||||
do
|
||||
if attached payload_data as s then
|
||||
Result := is_valid_utf_8 (s, not is_fin)
|
||||
end
|
||||
end
|
||||
|
||||
is_valid_utf_8_string (s: READABLE_STRING_8): BOOLEAN
|
||||
do
|
||||
Result := is_valid_utf_8 (s, False)
|
||||
-- and (create {UTF_CONVERTER}).is_valid_utf_8_string_8 (s)
|
||||
end
|
||||
|
||||
is_valid_utf_8 (s: READABLE_STRING_8; a_is_stream: BOOLEAN): BOOLEAN
|
||||
-- UTF-8 validity checker.
|
||||
note
|
||||
EIS: "name=UTF-8 RFC3629", "protocol=URI", "src=https://tools.ietf.org/html/rfc3629", "tag=rfc"
|
||||
require
|
||||
is_text_frame: is_text
|
||||
local
|
||||
i: like {STRING_8}.count
|
||||
n: like {STRING_8}.count
|
||||
c,c2,c3,c4,w: NATURAL_32
|
||||
l_is_incomplete_stream: BOOLEAN
|
||||
do
|
||||
Result := True
|
||||
-- Following code also check that codepoint is between 0 and 0x10FFFF
|
||||
-- (as expected by spec, and tested by autobahn ws testsuite)
|
||||
from
|
||||
if a_is_stream then
|
||||
i := last_utf_8_stream_validation_position -- to avoid recomputing from the beginning each time.
|
||||
else
|
||||
i := 0
|
||||
end
|
||||
n := s.count
|
||||
until
|
||||
i >= n or not Result
|
||||
loop
|
||||
i := i + 1
|
||||
c := s.code (i)
|
||||
if c <= 0x7F then
|
||||
-- 0xxxxxxx
|
||||
w := c
|
||||
elseif c <= 0xC1 then
|
||||
-- The octet values C0, C1, F5 to FF never appear.
|
||||
--| case 0xC0 and 0xC1
|
||||
Result := False
|
||||
elseif (c & 0xE0) = 0xC0 then
|
||||
-- 110xxxxx 10xxxxxx
|
||||
i := i + 1
|
||||
if i <= n then
|
||||
c2 := s.code (i)
|
||||
if
|
||||
(c2 & 0xC0) = 0x80
|
||||
then
|
||||
w := ((c & 0x1F) |<< 6)
|
||||
| (c2 & 0x3F)
|
||||
Result := 0x80 <= w and w <= 0x7FF
|
||||
else
|
||||
Result := False
|
||||
end
|
||||
else
|
||||
l_is_incomplete_stream := True
|
||||
end
|
||||
elseif (c & 0xF0) = 0xE0 then
|
||||
-- 1110xxxx 10xxxxxx 10xxxxxx
|
||||
i := i + 2
|
||||
if i <= n then
|
||||
c2 := s.code (i - 1)
|
||||
c3 := s.code (i)
|
||||
if
|
||||
(c2 & 0xC0) = 0x80 and
|
||||
(c3 & 0xC0) = 0x80
|
||||
then
|
||||
w := ((c & 0xF) |<< 12)
|
||||
| ((c2 & 0x3F) |<< 6)
|
||||
| (c3 & 0x3F)
|
||||
if 0x800 <= w and w <= 0xFFFF then
|
||||
if 0xD800 <= w and w <= 0xDFFF then
|
||||
-- The definition of UTF-8 prohibits encoding character numbers between U+D800 and U+DFFF
|
||||
Result := False
|
||||
end
|
||||
else
|
||||
Result := False
|
||||
end
|
||||
else
|
||||
Result := False
|
||||
end
|
||||
else
|
||||
if i - 1 <= n then
|
||||
Result := (s.code (i - 1) & 0xC0) = 0x80
|
||||
end
|
||||
l_is_incomplete_stream := True
|
||||
end
|
||||
elseif (c & 0xF8) = 0xF0 then -- 0001 0000-0010 FFFF
|
||||
-- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
if 0xF5 <= c and c <= 0xFF then
|
||||
-- The octet values C0, C1, F5 to FF never appear.
|
||||
Result := False
|
||||
else
|
||||
i := i + 3
|
||||
if i <= n then
|
||||
c2 := s.code (i - 2)
|
||||
c3 := s.code (i - 1)
|
||||
c4 := s.code (i)
|
||||
if
|
||||
(c2 & 0xC0) = 0x80 and
|
||||
(c3 & 0xC0) = 0x80 and
|
||||
(c4 & 0xC0) = 0x80
|
||||
then
|
||||
w := ((c & 0x7) |<< 18) |
|
||||
((c2 & 0x3F) |<< 12) |
|
||||
((c3 & 0x3F) |<< 6) |
|
||||
(c4 & 0x3F)
|
||||
Result := 0x1_0000 <= w and w <= 0x10_FFFF
|
||||
else
|
||||
Result := False
|
||||
end
|
||||
else
|
||||
if i - 2 <= n then
|
||||
c2 := s.code (i - 2)
|
||||
Result := (c2 & 0xC0) = 0x80
|
||||
if Result then
|
||||
if c = 0xF4 and c2 >= 0x90 then
|
||||
--| any byte 10xxxxxx (i.e >= 0x80) that would come after,
|
||||
-- will result in out of range code point
|
||||
-- indeed 0xF4 0x90 0x80 0x80 = 0x1100 0000 > 0x10_FFFF
|
||||
Result := False
|
||||
elseif i - 1 <= n then
|
||||
Result := (s.code (i - 1) & 0xC0) = 0x80
|
||||
end
|
||||
end
|
||||
end
|
||||
l_is_incomplete_stream := True
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Invalid byte in UTF-8
|
||||
Result := False
|
||||
end
|
||||
if Result then
|
||||
if l_is_incomplete_stream then
|
||||
Result := a_is_stream
|
||||
elseif a_is_stream then
|
||||
last_utf_8_stream_validation_position := i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,805 @@
|
||||
note
|
||||
description: "[
|
||||
Object representing the websocket connection.
|
||||
It contains the `request` and `response`, and more important the `socket` itself.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET
|
||||
|
||||
inherit
|
||||
WGI_STANDALONE_CONNECTOR_EXPORTER
|
||||
|
||||
WSF_RESPONSE_EXPORTER
|
||||
|
||||
WGI_EXPORTER
|
||||
|
||||
HTTPD_LOGGER_CONSTANTS
|
||||
|
||||
WEB_SOCKET_CONSTANTS
|
||||
|
||||
SHARED_BASE64
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
do
|
||||
request := req
|
||||
response := res
|
||||
is_verbose := False
|
||||
verbose_level := notice_level
|
||||
|
||||
if
|
||||
attached {WGI_STANDALONE_INPUT_STREAM} req.input as r_input
|
||||
then
|
||||
socket := r_input.source
|
||||
else
|
||||
create socket.make_empty
|
||||
check has_socket: False end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
socket: HTTPD_STREAM_SOCKET
|
||||
-- Underlying connected socket.
|
||||
|
||||
feature {NONE} -- Access
|
||||
|
||||
request: WSF_REQUEST
|
||||
-- Associated request.
|
||||
|
||||
response: WSF_RESPONSE
|
||||
-- Associated response stream.
|
||||
|
||||
feature -- Access
|
||||
|
||||
is_websocket: BOOLEAN
|
||||
-- Does `open_ws_handshake' detect valid websocket upgrade handshake?
|
||||
|
||||
feature -- Settings
|
||||
|
||||
is_verbose: BOOLEAN
|
||||
-- Output verbose log messages?
|
||||
|
||||
verbose_level: INTEGER
|
||||
-- Level of verbosity.
|
||||
|
||||
feature -- Status
|
||||
|
||||
has_error: BOOLEAN
|
||||
-- Error occured during processing?
|
||||
|
||||
feature -- Socket status
|
||||
|
||||
is_ready_for_reading: BOOLEAN
|
||||
-- Is `socket' ready for reading?
|
||||
--| at this point, socket should be set to blocking.
|
||||
do
|
||||
Result := socket.ready_for_reading
|
||||
end
|
||||
|
||||
is_open_read: BOOLEAN
|
||||
-- Is `socket' open for reading?
|
||||
do
|
||||
Result := socket.is_open_read
|
||||
end
|
||||
|
||||
is_open_write: BOOLEAN
|
||||
-- Is `socket' open for writing?
|
||||
do
|
||||
Result := socket.is_open_write
|
||||
end
|
||||
|
||||
socket_descriptor: INTEGER
|
||||
-- Descriptor for current `socket'.
|
||||
do
|
||||
Result := socket.descriptor
|
||||
end
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_is_verbose (b: BOOLEAN)
|
||||
do
|
||||
is_verbose := b
|
||||
end
|
||||
|
||||
set_verbose_level (lev: INTEGER)
|
||||
do
|
||||
verbose_level := lev
|
||||
end
|
||||
|
||||
feature -- Basic operation
|
||||
|
||||
put_error (a_message: READABLE_STRING_8)
|
||||
do
|
||||
response.put_error (a_message)
|
||||
end
|
||||
|
||||
log (m: READABLE_STRING_8; lev: INTEGER)
|
||||
-- Log `m' in the error channel, i.e stderr for standalone.
|
||||
do
|
||||
if is_verbose then
|
||||
put_error (m)
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Basic Operation
|
||||
|
||||
open_ws_handshake
|
||||
-- The opening handshake is intended to be compatible with HTTP-based
|
||||
-- server-side software and intermediaries, so that a single port can be
|
||||
-- used by both HTTP clients alking to that server and WebSocket
|
||||
-- clients talking to that server. To this end, the WebSocket client's
|
||||
-- handshake is an HTTP Upgrade request:
|
||||
|
||||
-- GET /chat HTTP/1.1
|
||||
-- Host: server.example.com
|
||||
-- Upgrade: websocket
|
||||
-- Connection: Upgrade
|
||||
-- Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
|
||||
-- Origin: http://example.com
|
||||
-- Sec-WebSocket-Protocol: chat, superchat
|
||||
-- Sec-WebSocket-Version: 13
|
||||
local
|
||||
l_sha1: SHA1
|
||||
l_key : STRING
|
||||
req: like request
|
||||
res: like response
|
||||
do
|
||||
-- Reset values.
|
||||
is_websocket := False
|
||||
has_error := False
|
||||
|
||||
-- Local cache.
|
||||
req := request
|
||||
res := response
|
||||
|
||||
-- Reading client's opening GT
|
||||
|
||||
-- TODO extract to a validator handshake or something like that.
|
||||
if is_verbose then
|
||||
log ("%NReceive <====================", debug_level)
|
||||
if attached req.raw_header_data as rhd then
|
||||
log (rhd, debug_level)
|
||||
end
|
||||
end
|
||||
if
|
||||
req.is_get_request_method and then -- MUST be GET request!
|
||||
attached req.meta_string_variable ("HTTP_UPGRADE") as l_upgrade_key and then
|
||||
l_upgrade_key.is_case_insensitive_equal_general ("websocket") -- Upgrade header must be present with value websocket
|
||||
then
|
||||
is_websocket := True
|
||||
socket.set_blocking
|
||||
if
|
||||
attached req.meta_string_variable ("HTTP_SEC_WEBSOCKET_KEY") as l_ws_key and then -- Sec-websocket-key must be present
|
||||
attached req.meta_string_variable ("HTTP_CONNECTION") as l_connection_key and then -- Connection header must be present with value Upgrade
|
||||
l_connection_key.has_substring ("Upgrade") and then
|
||||
attached req.meta_string_variable ("HTTP_SEC_WEBSOCKET_VERSION") as l_version_key and then -- Version header must be present with value 13
|
||||
l_version_key.is_case_insensitive_equal ("13") and then
|
||||
attached req.http_host -- Host header must be present
|
||||
then
|
||||
if is_verbose then
|
||||
log ("key " + l_ws_key, debug_level)
|
||||
end
|
||||
-- Sending the server's opening handshake
|
||||
create l_sha1.make
|
||||
l_sha1.update_from_string (l_ws_key + magic_guid)
|
||||
l_key := Base64_encoder.encoded_string (digest (l_sha1))
|
||||
res.header.add_header_key_value ("Upgrade", "websocket")
|
||||
res.header.add_header_key_value ("Connection", "Upgrade")
|
||||
res.header.add_header_key_value ("Sec-WebSocket-Accept", l_key)
|
||||
|
||||
if is_verbose then
|
||||
log ("%N================> Send Handshake", debug_level)
|
||||
if attached {HTTP_HEADER} res.header as h then
|
||||
log (h.string, debug_level)
|
||||
end
|
||||
end
|
||||
res.set_status_code_with_reason_phrase (101, "Switching Protocols")
|
||||
res.wgi_response.push
|
||||
else
|
||||
has_error := True
|
||||
if is_verbose then
|
||||
log ("Error (opening_handshake)!!!", debug_level)
|
||||
end
|
||||
-- If we cannot complete the handshake, then the server MUST stop processing the client's handshake and return an HTTP response with an
|
||||
-- appropriate error code (such as 400 Bad Request).
|
||||
res.set_status_code_with_reason_phrase (400, "Bad Request")
|
||||
end
|
||||
else
|
||||
is_websocket := False
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Response!
|
||||
|
||||
send (a_opcode:INTEGER; a_message: READABLE_STRING_8)
|
||||
local
|
||||
i: INTEGER
|
||||
l_chunk_size: INTEGER
|
||||
l_chunk: READABLE_STRING_8
|
||||
l_header_message: STRING
|
||||
l_message_count: INTEGER
|
||||
n: NATURAL_64
|
||||
retried: BOOLEAN
|
||||
do
|
||||
debug ("ws")
|
||||
print (">>do_send (..., "+ opcode_name (a_opcode) +", ..)%N")
|
||||
end
|
||||
if not retried then
|
||||
create l_header_message.make_empty
|
||||
l_header_message.append_code ((0x80 | a_opcode).to_natural_32)
|
||||
l_message_count := a_message.count
|
||||
n := l_message_count.to_natural_64
|
||||
if l_message_count > 0xffff then
|
||||
--! Improve. this code needs to be checked.
|
||||
l_header_message.append_code ((0 | 127).to_natural_32)
|
||||
l_header_message.append_character ((n |>> 56).to_character_8)
|
||||
l_header_message.append_character ((n |>> 48).to_character_8)
|
||||
l_header_message.append_character ((n |>> 40).to_character_8)
|
||||
l_header_message.append_character ((n |>> 32).to_character_8)
|
||||
l_header_message.append_character ((n |>> 24).to_character_8)
|
||||
l_header_message.append_character ((n |>> 16).to_character_8)
|
||||
l_header_message.append_character ((n |>> 8).to_character_8)
|
||||
l_header_message.append_character ( n.to_character_8)
|
||||
elseif l_message_count > 125 then
|
||||
l_header_message.append_code ((0 | 126).to_natural_32)
|
||||
l_header_message.append_code ((n |>> 8).as_natural_32)
|
||||
l_header_message.append_character (n.to_character_8)
|
||||
else
|
||||
l_header_message.append_code (n.as_natural_32)
|
||||
end
|
||||
socket.put_string (l_header_message)
|
||||
|
||||
l_chunk_size := 16_384 -- 16K TODO: see if we should make it customizable.
|
||||
if l_message_count < l_chunk_size then
|
||||
socket.put_string (a_message)
|
||||
else
|
||||
from
|
||||
i := 0
|
||||
until
|
||||
l_chunk_size = 0
|
||||
loop
|
||||
debug ("ws")
|
||||
print ("Sending chunk " + (i + 1).out + " -> " + (i + l_chunk_size).out +" / " + l_message_count.out + "%N")
|
||||
end
|
||||
l_chunk := a_message.substring (i + 1, l_message_count.min (i + l_chunk_size))
|
||||
socket.put_string (l_chunk)
|
||||
if l_chunk.count < l_chunk_size then
|
||||
l_chunk_size := 0
|
||||
end
|
||||
i := i + l_chunk_size
|
||||
end
|
||||
debug ("ws")
|
||||
print ("Sending chunk done%N")
|
||||
end
|
||||
end
|
||||
else
|
||||
-- FIXME: what should be done on rescue?
|
||||
end
|
||||
rescue
|
||||
retried := True
|
||||
io.put_string ("Internal error in " + generator + ".do_send (conn, a_opcode=" + a_opcode.out + ", a_message) !%N")
|
||||
retry
|
||||
end
|
||||
|
||||
next_frame: detachable WEB_SOCKET_FRAME
|
||||
-- TODO Binary messages
|
||||
-- Handle error responses in a better way.
|
||||
-- IDEA:
|
||||
-- class FRAME
|
||||
-- is_fin: BOOLEAN
|
||||
-- opcode: WEB_SOCKET_STATUS_CODE (TEXT, BINARY, CLOSE, CONTINUE,PING, PONG)
|
||||
-- data/payload
|
||||
-- status_code: #see Status Codes http://tools.ietf.org/html/rfc6455#section-7.3
|
||||
-- has_error
|
||||
--
|
||||
-- See Base Framing Protocol: http://tools.ietf.org/html/rfc6455#section-5.2
|
||||
-- 0 1 2 3
|
||||
-- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
-- +-+-+-+-+-------+-+-------------+-------------------------------+
|
||||
-- |F|R|R|R| opcode|M| Payload len | Extended payload length |
|
||||
-- |I|S|S|S| (4) |A| (7) | (16/64) |
|
||||
-- |N|V|V|V| |S| | (if payload len==126/127) |
|
||||
-- | |1|2|3| |K| | |
|
||||
-- +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
||||
-- | Extended payload length continued, if payload len == 127 |
|
||||
-- + - - - - - - - - - - - - - - - +-------------------------------+
|
||||
-- | |Masking-key, if MASK set to 1 |
|
||||
-- +-------------------------------+-------------------------------+
|
||||
-- | Masking-key (continued) | Payload Data |
|
||||
-- +-------------------------------- - - - - - - - - - - - - - - - +
|
||||
-- : Payload Data continued ... :
|
||||
-- + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
||||
-- | Payload Data continued ... |
|
||||
-- +---------------------------------------------------------------+
|
||||
note
|
||||
EIS: "name=WebSocket RFC", "protocol=URI", "src=http://tools.ietf.org/html/rfc6455#section-5.2"
|
||||
require
|
||||
socket_in_blocking_mode: socket.is_blocking
|
||||
local
|
||||
l_socket: like socket
|
||||
l_opcode: INTEGER
|
||||
l_len: INTEGER
|
||||
l_remaining_len: INTEGER
|
||||
l_payload_len: NATURAL_64
|
||||
l_masking_key: detachable READABLE_STRING_8
|
||||
l_chunk: STRING
|
||||
l_rsv: BOOLEAN
|
||||
l_fin: BOOLEAN
|
||||
l_has_mask: BOOLEAN
|
||||
l_chunk_size: INTEGER
|
||||
l_byte: INTEGER
|
||||
l_fetch_count: INTEGER
|
||||
l_bytes_read: INTEGER
|
||||
s: STRING
|
||||
is_data_frame_ok: BOOLEAN -- Is the last process data framing ok?
|
||||
retried: BOOLEAN
|
||||
do
|
||||
if not retried then
|
||||
l_socket := socket
|
||||
debug ("ws")
|
||||
print ("next_frame:%N")
|
||||
end
|
||||
from
|
||||
is_data_frame_ok := True
|
||||
until
|
||||
l_fin or not is_data_frame_ok
|
||||
loop
|
||||
-- multi-frames or continue is only valid for Binary or Text
|
||||
s := next_bytes (l_socket, 1)
|
||||
if s.is_empty then
|
||||
is_data_frame_ok := False
|
||||
debug ("ws")
|
||||
print ("[ERROR] incomplete_data!%N")
|
||||
end
|
||||
else
|
||||
l_byte := s [1].code
|
||||
debug ("ws")
|
||||
print (" fin,rsv(3),opcode(4)=")
|
||||
print (to_byte_representation (l_byte))
|
||||
print ("%N")
|
||||
end
|
||||
l_fin := l_byte & (0b10000000) /= 0
|
||||
l_rsv := l_byte & (0b01110000) = 0
|
||||
l_opcode := l_byte & 0b00001111
|
||||
if Result /= Void then
|
||||
if l_opcode = Result.opcode then
|
||||
-- should not occur in multi-fragment frame!
|
||||
create Result.make (l_opcode, l_fin)
|
||||
Result.report_error (protocol_error, "Unexpected injected frame")
|
||||
elseif l_opcode = continuation_frame then
|
||||
-- Expected
|
||||
Result.update_fin (l_fin)
|
||||
elseif is_control_frame (l_opcode) then
|
||||
-- Control frames (see Section 5.5) MAY be injected in the middle of
|
||||
-- a fragmented message. Control frames themselves MUST NOT be fragmented.
|
||||
-- if the l_opcode is a control frame then there is an error!!!
|
||||
-- CLOSE, PING, PONG
|
||||
create Result.make_as_injected_control (l_opcode, Result)
|
||||
else
|
||||
-- should not occur in multi-fragment frame!
|
||||
create Result.make (l_opcode, l_fin)
|
||||
Result.report_error (protocol_error, "Unexpected frame")
|
||||
end
|
||||
else
|
||||
create Result.make (l_opcode, l_fin)
|
||||
if Result.is_continuation then
|
||||
-- Continuation frame is not expected without parent frame!
|
||||
Result.report_error (protocol_error, "There is no message to continue!")
|
||||
end
|
||||
end
|
||||
if Result.is_valid then
|
||||
--| valid frame/fragment
|
||||
if is_verbose then
|
||||
log ("+ frame " + opcode_name (l_opcode) + " (fin=" + l_fin.out + ")", debug_level)
|
||||
end
|
||||
|
||||
-- rsv validation
|
||||
if not l_rsv then
|
||||
-- RSV1, RSV2, RSV3: 1 bit each
|
||||
|
||||
-- MUST be 0 unless an extension is negotiated that defines meanings
|
||||
-- for non-zero values. If a nonzero value is received and none of
|
||||
-- the negotiated extensions defines the meaning of such a nonzero
|
||||
-- value, the receiving endpoint MUST _Fail the WebSocket
|
||||
-- Connection_
|
||||
|
||||
-- FIXME: add support for extension ?
|
||||
Result.report_error (protocol_error, "RSV values MUST be 0 unless an extension is negotiated that defines meanings for non-zero values")
|
||||
end
|
||||
else
|
||||
if is_verbose then
|
||||
log ("+ INVALID frame " + opcode_name (l_opcode) + " (fin=" + l_fin.out + ")", debug_level)
|
||||
end
|
||||
end
|
||||
|
||||
-- At the moment only TEXT, (pending Binary)
|
||||
if Result.is_valid then
|
||||
if Result.is_text or Result.is_binary or Result.is_control then
|
||||
-- Reading next byte (mask+payload_len)
|
||||
s := next_bytes (l_socket, 1)
|
||||
if s.is_empty then
|
||||
Result.report_error (invalid_data, "Incomplete data for mask and payload len")
|
||||
else
|
||||
l_byte := s [1].code
|
||||
debug ("ws")
|
||||
print (" mask,payload_len(7)=")
|
||||
print (to_byte_representation (l_byte))
|
||||
io.put_new_line
|
||||
end
|
||||
l_has_mask := l_byte & (0b10000000) /= 0 -- MASK
|
||||
l_len := l_byte & 0b01111111 -- 7bits
|
||||
|
||||
debug ("ws")
|
||||
print (" payload_len=" + l_len.out)
|
||||
io.put_new_line
|
||||
end
|
||||
if Result.is_control and then l_len > 125 then
|
||||
-- All control frames MUST have a payload length of 125 bytes or less
|
||||
-- and MUST NOT be fragmented.
|
||||
Result.report_error (protocol_error, "Control frame MUST have a payload length of 125 bytes or less")
|
||||
elseif l_len = 127 then -- TODO proof of concept read 8 bytes.
|
||||
-- the following 8 bytes interpreted as a 64-bit unsigned integer
|
||||
-- (the most significant bit MUST be 0) are the payload length.
|
||||
-- Multibyte length quantities are expressed in network byte order.
|
||||
s := next_bytes (l_socket, 8) -- 64 bits
|
||||
debug ("ws")
|
||||
print (" extended payload length=" + string_to_byte_representation (s))
|
||||
io.put_new_line
|
||||
end
|
||||
if s.count < 8 then
|
||||
Result.report_error (Invalid_data, "Incomplete data for 64 bit Extended payload length")
|
||||
else
|
||||
l_payload_len := s [8].natural_32_code.to_natural_64
|
||||
l_payload_len := l_payload_len | (s [7].natural_32_code.to_natural_64 |<< 8)
|
||||
l_payload_len := l_payload_len | (s [6].natural_32_code.to_natural_64 |<< 16)
|
||||
l_payload_len := l_payload_len | (s [5].natural_32_code.to_natural_64 |<< 24)
|
||||
l_payload_len := l_payload_len | (s [4].natural_32_code.to_natural_64 |<< 32)
|
||||
l_payload_len := l_payload_len | (s [3].natural_32_code.to_natural_64 |<< 40)
|
||||
l_payload_len := l_payload_len | (s [2].natural_32_code.to_natural_64 |<< 48)
|
||||
l_payload_len := l_payload_len | (s [1].natural_32_code.to_natural_64 |<< 56)
|
||||
end
|
||||
elseif l_len = 126 then
|
||||
s := next_bytes (l_socket, 2) -- 16 bits
|
||||
debug ("ws")
|
||||
print (" extended payload length bits=" + string_to_byte_representation (s))
|
||||
io.put_new_line
|
||||
end
|
||||
if s.count < 2 then
|
||||
Result.report_error (Invalid_data, "Incomplete data for 16 bit Extended payload length")
|
||||
else
|
||||
l_payload_len := s [2].natural_32_code.to_natural_64
|
||||
l_payload_len := l_payload_len | (s [1].natural_32_code.to_natural_64 |<< 8)
|
||||
end
|
||||
else
|
||||
l_payload_len := l_len.to_natural_64
|
||||
end
|
||||
debug ("ws")
|
||||
print (" Full payload length=" + l_payload_len.out)
|
||||
io.put_new_line
|
||||
end
|
||||
if Result.is_valid then
|
||||
if l_has_mask then
|
||||
l_masking_key := next_bytes (l_socket, 4) -- 32 bits
|
||||
debug ("ws")
|
||||
print (" Masking key bits=" + string_to_byte_representation (l_masking_key))
|
||||
io.put_new_line
|
||||
end
|
||||
if l_masking_key.count < 4 then
|
||||
debug ("ws")
|
||||
print ("masking-key read stream -> " + l_socket.bytes_read.out + " bits%N")
|
||||
end
|
||||
Result.report_error (Invalid_data, "Incomplete data for Masking-key")
|
||||
l_masking_key := Void
|
||||
end
|
||||
else
|
||||
Result.report_error (protocol_error, "All frames sent from client to server are masked!")
|
||||
end
|
||||
if Result.is_valid then
|
||||
l_chunk_size := 0x4000 -- 16 K
|
||||
if l_payload_len > {INTEGER_32}.max_value.to_natural_64 then
|
||||
-- Issue .. to big to store in STRING
|
||||
-- FIXME !!!
|
||||
Result.report_error (Message_too_large, "Can not handle payload data (len=" + l_payload_len.out + ")")
|
||||
else
|
||||
l_len := l_payload_len.to_integer_32
|
||||
end
|
||||
from
|
||||
l_fetch_count := 0
|
||||
l_remaining_len := l_len
|
||||
until
|
||||
l_fetch_count >= l_len or l_len = 0 or not Result.is_valid
|
||||
loop
|
||||
if l_remaining_len < l_chunk_size then
|
||||
l_chunk_size := l_remaining_len
|
||||
end
|
||||
l_socket.read_stream (l_chunk_size)
|
||||
l_bytes_read := l_socket.bytes_read
|
||||
debug ("ws")
|
||||
print ("read chunk size=" + l_chunk_size.out + " fetch_count=" + l_fetch_count.out + " l_len=" + l_len.out + " -> " + l_bytes_read.out + "bytes%N")
|
||||
end
|
||||
if l_bytes_read > 0 then
|
||||
l_remaining_len := l_remaining_len - l_bytes_read
|
||||
l_chunk := l_socket.last_string
|
||||
if l_masking_key /= Void then
|
||||
-- Masking
|
||||
-- http://tools.ietf.org/html/rfc6455#section-5.3
|
||||
unmask (l_chunk, l_fetch_count + 1, l_masking_key)
|
||||
else
|
||||
check
|
||||
client_frame_should_always_be_encoded: False
|
||||
end
|
||||
end
|
||||
l_fetch_count := l_fetch_count + l_bytes_read
|
||||
Result.append_payload_data_chop (l_chunk, l_bytes_read, l_remaining_len = 0)
|
||||
else
|
||||
Result.report_error (internal_error, "Issue reading payload data...")
|
||||
end
|
||||
end
|
||||
if is_verbose then
|
||||
log (" Received " + l_fetch_count.out + " out of " + l_len.out + " bytes <===============", debug_level)
|
||||
end
|
||||
debug ("ws")
|
||||
print (" -> ")
|
||||
if attached Result.payload_data as l_payload_data then
|
||||
s := l_payload_data.tail (l_fetch_count)
|
||||
if s.count > 50 then
|
||||
print (string_to_byte_hexa_representation (s.head (50) + ".."))
|
||||
else
|
||||
print (string_to_byte_hexa_representation (s))
|
||||
end
|
||||
print ("%N")
|
||||
if Result.is_text and Result.is_fin and Result.fragment_count = 0 then
|
||||
print (" -> ")
|
||||
if s.count > 50 then
|
||||
print (s.head (50) + "..")
|
||||
else
|
||||
print (s)
|
||||
end
|
||||
print ("%N")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if Result /= Void then
|
||||
if attached Result.error as err then
|
||||
if is_verbose then
|
||||
log (" !Invalid frame: " + err.string, debug_level)
|
||||
end
|
||||
end
|
||||
if Result.is_injected_control then
|
||||
if attached Result.parent as l_parent then
|
||||
if not Result.is_valid then
|
||||
l_parent.report_error (protocol_error, "Invalid injected frame")
|
||||
end
|
||||
if Result.is_connection_close then
|
||||
-- Return this and process the connection close right away!
|
||||
l_parent.update_fin (True)
|
||||
l_fin := Result.is_fin
|
||||
else
|
||||
Result := l_parent
|
||||
l_fin := l_parent.is_fin
|
||||
check
|
||||
-- This is a control frame but occurs in fragmented frame.
|
||||
inside_fragmented_frame: not l_fin
|
||||
end
|
||||
end
|
||||
else
|
||||
check
|
||||
has_parent: False
|
||||
end
|
||||
l_fin := False -- This is a control frame but occurs in fragmented frame.
|
||||
end
|
||||
end
|
||||
if not Result.is_valid then
|
||||
is_data_frame_ok := False
|
||||
end
|
||||
else
|
||||
is_data_frame_ok := False
|
||||
end
|
||||
end
|
||||
end
|
||||
has_error := Result = Void or else Result.has_error
|
||||
rescue
|
||||
retried := True
|
||||
if Result /= Void then
|
||||
Result.report_error (internal_error, "Internal error")
|
||||
end
|
||||
retry
|
||||
end
|
||||
|
||||
|
||||
feature -- Encoding
|
||||
|
||||
digest (a_sha1: SHA1): STRING
|
||||
-- Digest of `a_sha1'.
|
||||
-- Should by in SHA1 class
|
||||
local
|
||||
l_digest: SPECIAL [NATURAL_8]
|
||||
index, l_upper: INTEGER
|
||||
do
|
||||
l_digest := a_sha1.digest
|
||||
create Result.make (l_digest.count // 2)
|
||||
from
|
||||
index := l_digest.Lower
|
||||
l_upper := l_digest.upper
|
||||
until
|
||||
index > l_upper
|
||||
loop
|
||||
Result.append_character (l_digest [index].to_character_8)
|
||||
index := index + 1
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Socket helpers
|
||||
|
||||
next_bytes (a_socket: HTTPD_STREAM_SOCKET; nb: INTEGER): STRING
|
||||
require
|
||||
nb > 0
|
||||
local
|
||||
n, l_bytes_read: INTEGER
|
||||
do
|
||||
create Result.make (nb)
|
||||
from
|
||||
n := nb
|
||||
until
|
||||
n = 0
|
||||
loop
|
||||
a_socket.read_stream (nb)
|
||||
l_bytes_read := a_socket.bytes_read
|
||||
if l_bytes_read > 0 then
|
||||
Result.append (a_socket.last_string)
|
||||
n := n - l_bytes_read
|
||||
else
|
||||
n := 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Masking Data Client - Server
|
||||
|
||||
unmask (a_chunk: STRING_8; a_pos: INTEGER; a_key: READABLE_STRING_8)
|
||||
local
|
||||
i, n: INTEGER
|
||||
do
|
||||
from
|
||||
i := 1
|
||||
n := a_chunk.count
|
||||
until
|
||||
i > n
|
||||
loop
|
||||
a_chunk.put_code (a_chunk.code (i).bit_xor (a_key [((i + (a_pos - 1) - 1) \\ 4) + 1].natural_32_code), i)
|
||||
i := i + 1
|
||||
end
|
||||
end
|
||||
|
||||
append_chunk_unmasked (a_chunk: READABLE_STRING_8; a_pos: INTEGER; a_key: READABLE_STRING_8; a_target: STRING)
|
||||
-- To convert masked data into unmasked data, or vice versa, the following
|
||||
-- algorithm is applied. The same algorithm applies regardless of the
|
||||
-- direction of the translation, e.g., the same steps are applied to
|
||||
-- mask the data as to unmask the data.
|
||||
|
||||
-- Octet i of the transformed data ("transformed-octet-i") is the XOR of
|
||||
-- octet i of the original data ("original-octet-i") with octet at index
|
||||
-- i modulo 4 of the masking key ("masking-key-octet-j"):
|
||||
|
||||
-- j = i MOD 4
|
||||
-- transformed-octet-i = original-octet-i XOR masking-key-octet-j
|
||||
|
||||
-- The payload length, indicated in the framing as frame-payload-length,
|
||||
-- does NOT include the length of the masking key. It is the length of
|
||||
-- the "Payload data", e.g., the number of bytes following the masking
|
||||
-- key.
|
||||
note
|
||||
EIS: "name=Masking", "src=http://tools.ietf.org/html/rfc6455#section-5.3", "protocol=uri"
|
||||
local
|
||||
i, n: INTEGER
|
||||
do
|
||||
-- debug ("ws")
|
||||
-- print ("append_chunk_unmasked (%"" + string_to_byte_representation (a_chunk) + "%",%N%Ta_pos=" + a_pos.out+ ", a_key, a_target #.count=" + a_target.count.out + ")%N")
|
||||
-- end
|
||||
from
|
||||
i := 1
|
||||
n := a_chunk.count
|
||||
until
|
||||
i > n
|
||||
loop
|
||||
a_target.append_code (a_chunk.code (i).bit_xor (a_key [((i + (a_pos - 1) - 1) \\ 4) + 1].natural_32_code))
|
||||
i := i + 1
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Debug
|
||||
|
||||
to_byte_representation (a_integer: INTEGER): STRING
|
||||
require
|
||||
valid: a_integer >= 0 and then a_integer <= 255
|
||||
local
|
||||
l_val: INTEGER
|
||||
do
|
||||
create Result.make (8)
|
||||
from
|
||||
l_val := a_integer
|
||||
until
|
||||
l_val < 2
|
||||
loop
|
||||
Result.prepend_integer (l_val \\ 2)
|
||||
l_val := l_val // 2
|
||||
end
|
||||
Result.prepend_integer (l_val)
|
||||
end
|
||||
|
||||
string_to_byte_representation (s: STRING): STRING
|
||||
require
|
||||
valid: s.count > 0
|
||||
local
|
||||
i, n: INTEGER
|
||||
do
|
||||
n := s.count
|
||||
create Result.make (8 * n)
|
||||
if n > 0 then
|
||||
from
|
||||
i := 1
|
||||
until
|
||||
i > n
|
||||
loop
|
||||
if not Result.is_empty then
|
||||
Result.append_character (':')
|
||||
end
|
||||
Result.append (to_byte_representation (s [i].code))
|
||||
i := i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
string_to_byte_hexa_representation (s: STRING): STRING
|
||||
local
|
||||
i, n: INTEGER
|
||||
c: INTEGER
|
||||
do
|
||||
n := s.count
|
||||
create Result.make (8 * n)
|
||||
if n > 0 then
|
||||
from
|
||||
i := 1
|
||||
until
|
||||
i > n
|
||||
loop
|
||||
if not Result.is_empty then
|
||||
Result.append_character (':')
|
||||
end
|
||||
c := s [i].code
|
||||
check
|
||||
c <= 0xFF
|
||||
end
|
||||
Result.append_character (((c |>> 4) & 0xF).to_hex_character)
|
||||
Result.append_character (((c) & 0xF).to_hex_character)
|
||||
i := i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
note
|
||||
copyright: "2011-2016, 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
|
||||
@@ -0,0 +1,152 @@
|
||||
note
|
||||
description: "[
|
||||
To implement websocket handling, provide a `callbacks` object implementing the {WEB_SOCKET_EVENT_I} interface.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET_HANDLER
|
||||
|
||||
inherit
|
||||
WEB_SOCKET_CONSTANTS
|
||||
|
||||
REFACTORING_HELPER
|
||||
|
||||
HTTPD_LOGGER_CONSTANTS
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (ws: WEB_SOCKET; a_callbacks: WEB_SOCKET_EVENT_I)
|
||||
do
|
||||
web_socket := ws
|
||||
callbacks := a_callbacks
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
web_socket: WEB_SOCKET
|
||||
-- Associated websocket.
|
||||
|
||||
callbacks: WEB_SOCKET_EVENT_I
|
||||
|
||||
feature -- Execution
|
||||
|
||||
frozen execute
|
||||
do
|
||||
callbacks.on_open (web_socket)
|
||||
execute_websocket
|
||||
end
|
||||
|
||||
execute_websocket
|
||||
local
|
||||
exit: BOOLEAN
|
||||
l_frame: detachable WEB_SOCKET_FRAME
|
||||
l_client_message: detachable READABLE_STRING_8
|
||||
l_utf: UTF_CONVERTER
|
||||
ws: like web_socket
|
||||
s: STRING
|
||||
do
|
||||
from
|
||||
-- loop until ws is closed or has error.
|
||||
ws := web_socket
|
||||
until
|
||||
exit
|
||||
loop
|
||||
debug ("dbglog")
|
||||
dbglog (generator + ".execute_websocket (loop) WS_REQUEST_HANDLER.process_request {" + ws.socket_descriptor.out + "}")
|
||||
end
|
||||
if ws.is_ready_for_reading then
|
||||
l_frame := ws.next_frame
|
||||
if l_frame /= Void and then l_frame.is_valid then
|
||||
if attached l_frame.injected_control_frames as l_injections then
|
||||
-- Process injected control frames now.
|
||||
-- FIXME
|
||||
across
|
||||
l_injections as ic
|
||||
loop
|
||||
if ic.item.is_connection_close then
|
||||
-- FIXME: we should probably send this event .. after the `l_frame.parent' frame event.
|
||||
callbacks.on_event (ws, ic.item.payload_data, ic.item.opcode)
|
||||
exit := True
|
||||
elseif ic.item.is_ping then
|
||||
-- FIXME reply only to the most recent ping ...
|
||||
callbacks.on_event (ws, ic.item.payload_data, ic.item.opcode)
|
||||
else
|
||||
callbacks.on_event (ws, ic.item.payload_data, ic.item.opcode)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
l_client_message := l_frame.payload_data
|
||||
if l_client_message = Void then
|
||||
l_client_message := ""
|
||||
end
|
||||
|
||||
debug ("ws")
|
||||
create s.make_from_string ("%NExecute: %N")
|
||||
s.append (" [opcode: "+ opcode_name (l_frame.opcode) +"]%N")
|
||||
if l_frame.is_text then
|
||||
s.append (" [client message: %""+ l_client_message +"%"]%N")
|
||||
elseif l_frame.is_binary then
|
||||
s.append (" [client binary message length: %""+ l_client_message.count.out +"%"]%N")
|
||||
end
|
||||
s.append (" [is_control: " + l_frame.is_control.out + "]%N")
|
||||
s.append (" [is_binary: " + l_frame.is_binary.out + "]%N")
|
||||
s.append (" [is_text: " + l_frame.is_text.out + "]%N")
|
||||
dbglog (s)
|
||||
end
|
||||
|
||||
if l_frame.is_connection_close then
|
||||
callbacks.on_event (ws, l_client_message, l_frame.opcode)
|
||||
exit := True
|
||||
elseif l_frame.is_binary then
|
||||
callbacks.on_event (ws, l_client_message, l_frame.opcode)
|
||||
elseif l_frame.is_text then
|
||||
check is_valid_utf_8: l_utf.is_valid_utf_8_string_8 (l_client_message) end
|
||||
callbacks.on_event (ws, l_client_message, l_frame.opcode)
|
||||
else
|
||||
callbacks.on_event (ws, l_client_message, l_frame.opcode)
|
||||
end
|
||||
else
|
||||
debug ("ws")
|
||||
create s.make_from_string ("%NExecute: %N")
|
||||
s.append (" [ERROR: invalid frame]%N")
|
||||
if l_frame /= Void and then attached l_frame.error as err then
|
||||
s.append (" [Code: "+ err.code.out +"]%N")
|
||||
s.append (" [Description: "+ err.description +"]%N")
|
||||
end
|
||||
dbglog (s)
|
||||
end
|
||||
callbacks.on_event (ws, "", connection_close_frame)
|
||||
exit := True -- FIXME: check proper close protocol
|
||||
end
|
||||
else
|
||||
debug ("ws")
|
||||
dbglog (generator + ".WAITING WS_REQUEST_HANDLER.process_request {" + ws.socket_descriptor.out + "}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Logging
|
||||
|
||||
dbglog (m: READABLE_STRING_8)
|
||||
do
|
||||
web_socket.log (m, debug_level)
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2016, 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
|
||||
@@ -0,0 +1,44 @@
|
||||
note
|
||||
description: "Summary description for {WGI_STANDALONE_WEBSOCKET_CONNECTOR}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WGI_STANDALONE_WEBSOCKET_CONNECTOR [G -> WGI_EXECUTION create make end]
|
||||
|
||||
inherit
|
||||
WGI_STANDALONE_CONNECTOR [G]
|
||||
redefine
|
||||
name, version
|
||||
end
|
||||
|
||||
create
|
||||
make,
|
||||
make_with_base
|
||||
|
||||
feature -- Access
|
||||
|
||||
name: STRING_8
|
||||
-- Name of Current connector
|
||||
once
|
||||
Result := "ws_httpd"
|
||||
end
|
||||
|
||||
version: STRING_8
|
||||
-- Version of Current connector
|
||||
once
|
||||
Result := "1.0"
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2016, 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
|
||||
@@ -0,0 +1,49 @@
|
||||
note
|
||||
description: "[
|
||||
Component to launch the service using the default connector
|
||||
|
||||
Eiffel Web httpd for this class
|
||||
|
||||
|
||||
The httpd default connector support options:
|
||||
port: numeric such as 8099 (or equivalent string as "8099")
|
||||
base: base_url (very specific to standalone server)
|
||||
verbose: to display verbose output, useful for standalone connector
|
||||
force_single_threaded: use only one thread, useful for standalone connector
|
||||
|
||||
check WSF_SERVICE_LAUNCHER for more documentation
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WSF_STANDALONE_WEBSOCKET_SERVICE_LAUNCHER [G -> WSF_WEBSOCKET_EXECUTION create make end]
|
||||
|
||||
inherit
|
||||
WSF_STANDALONE_SERVICE_LAUNCHER [G]
|
||||
redefine
|
||||
connector
|
||||
end
|
||||
|
||||
create
|
||||
make,
|
||||
make_and_launch
|
||||
|
||||
feature -- Status report
|
||||
|
||||
connector: WGI_STANDALONE_WEBSOCKET_CONNECTOR [G]
|
||||
-- Default connector
|
||||
|
||||
;note
|
||||
copyright: "2011-2016, 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
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
note
|
||||
description: "[
|
||||
Request execution based on attributes `request' and `response'.
|
||||
Also support Upgrade to Websocket protocol.
|
||||
|
||||
|
||||
]"
|
||||
author: "$Author$"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
WSF_WEBSOCKET_EXECUTION
|
||||
|
||||
inherit
|
||||
WSF_EXECUTION
|
||||
rename
|
||||
execute as http_execute
|
||||
end
|
||||
|
||||
--create
|
||||
-- make
|
||||
|
||||
feature -- Execution
|
||||
|
||||
frozen http_execute
|
||||
local
|
||||
ws: WEB_SOCKET
|
||||
ws_h: like new_websocket_handler
|
||||
do
|
||||
create ws.make (request, response)
|
||||
ws.open_ws_handshake
|
||||
if ws.is_websocket then
|
||||
if ws.has_error then
|
||||
-- Upgrade to websocket raised an error
|
||||
-- stay on standard HTTP/1.1 protocol
|
||||
execute
|
||||
else
|
||||
ws_h := new_websocket_handler (ws)
|
||||
ws_h.execute
|
||||
end
|
||||
else
|
||||
execute
|
||||
end
|
||||
end
|
||||
|
||||
execute
|
||||
-- Execute Current request,
|
||||
-- getting data from `request'
|
||||
-- and response to client via `response'.
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Factory
|
||||
|
||||
new_websocket_handler (ws: WEB_SOCKET): WEB_SOCKET_HANDLER
|
||||
-- Websocket request specific handler on socket `ws'.
|
||||
--| For the creation, it requires an instance of `{WEB_SOCKET_EVENT_I}'
|
||||
--| to receive the websocket events.
|
||||
--| One can inherit from {WEB_SOCKET_EVENT_I} and implement the related
|
||||
--| deferred features.
|
||||
--| Or even provide a new class implementing {WEB_SOCKET_EVENT_I}.
|
||||
require
|
||||
is_websocket: ws.is_websocket
|
||||
no_error: not ws.has_error
|
||||
deferred
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2016, 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
|
||||
@@ -1,13 +1,3 @@
|
||||
note
|
||||
title: Web Server Foundation
|
||||
description: Core of the Eiffel Web Framework, used to build web server application.
|
||||
tags: ewf,server,httpd,request,connector
|
||||
license: Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)
|
||||
link[license]: https://github.com/EiffelWebFramework/EWF/blob/master/LICENSE
|
||||
link[source]: "Github" https://github.com/EiffelWebFramework/EWF
|
||||
link[doc]: "Documentation" http://eiffelwebframework.github.io/EWF/
|
||||
|
||||
end
|
||||
|
||||
package wsf
|
||||
|
||||
@@ -37,6 +27,7 @@ project
|
||||
default_standalone = "default/standalone-safe.ecf"
|
||||
default_standalone = "default/standalone.ecf"
|
||||
|
||||
|
||||
note
|
||||
title: Web Server Foundation
|
||||
description: "[
|
||||
|
||||
@@ -62,6 +62,9 @@ feature {NONE} -- Implementation
|
||||
a_handler.append_resource (req, res)
|
||||
-- 200 or 204 or 303 or 500 (add support for this?)
|
||||
-- FIXME: more support, such as includes_response_entity
|
||||
if not a_handler.response_ok (req) then
|
||||
write_error_response (req, res)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -62,6 +62,9 @@ feature {NONE} -- Implementation
|
||||
a_handler.update_resource (req, res)
|
||||
-- 204 or 500 (add support for this?)
|
||||
-- FIXME: more support, such as includes_response_entity
|
||||
if not a_handler.response_ok (req) then
|
||||
write_error_response (req, res)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -83,6 +83,57 @@ feature -- Access
|
||||
Result := options.item (a_name)
|
||||
end
|
||||
|
||||
feature -- Helpers
|
||||
|
||||
has_integer_option (a_opt_name: READABLE_STRING_GENERAL): BOOLEAN
|
||||
-- Is there any INTEGER value associated to option name `a_opt_name'?
|
||||
local
|
||||
s: READABLE_STRING_GENERAL
|
||||
do
|
||||
if attached option (a_opt_name) as opt then
|
||||
if attached {INTEGER} opt as i then
|
||||
Result := True
|
||||
else
|
||||
s := opt.out
|
||||
Result := s.is_integer
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
option_integer_value (a_opt_name: READABLE_STRING_GENERAL; a_default: INTEGER): INTEGER
|
||||
-- INTEGER value associated to option name `a_opt_name', other return `a_default'.
|
||||
local
|
||||
s: READABLE_STRING_GENERAL
|
||||
do
|
||||
Result := a_default
|
||||
if attached option (a_opt_name) as opt then
|
||||
if attached {INTEGER} opt as i then
|
||||
Result := i
|
||||
else
|
||||
s := opt.out
|
||||
if s.is_integer then
|
||||
Result := s.to_integer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
option_boolean_value (a_opt_name: READABLE_STRING_GENERAL; a_default: BOOLEAN): BOOLEAN
|
||||
-- BOOLEAN value associated to option name `a_opt_name', other return `a_default'.
|
||||
local
|
||||
s: READABLE_STRING_GENERAL
|
||||
do
|
||||
Result := a_default
|
||||
if attached option (a_opt_name) as opt then
|
||||
if attached {BOOLEAN} opt as b then
|
||||
Result := b
|
||||
else
|
||||
s := opt.out
|
||||
Result := s.is_case_insensitive_equal ("true")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
new_cursor: TABLE_ITERATION_CURSOR [detachable ANY, READABLE_STRING_GENERAL]
|
||||
|
||||
@@ -14,7 +14,6 @@ inherit
|
||||
make_from_execution as make_from_wgi_execution
|
||||
redefine
|
||||
make,
|
||||
execute,
|
||||
clean,
|
||||
is_valid_end_of_execution
|
||||
end
|
||||
|
||||
@@ -261,10 +261,12 @@ feature -- Access: Input
|
||||
local
|
||||
l_input: WGI_INPUT_STREAM
|
||||
n: INTEGER
|
||||
buf_initial_size: INTEGER
|
||||
do
|
||||
if raw_input_data_recorded and then attached raw_input_data as d then
|
||||
buf.append (d)
|
||||
else
|
||||
buf_initial_size := buf.count
|
||||
l_input := input
|
||||
if is_chunked_input then
|
||||
from
|
||||
@@ -286,73 +288,53 @@ feature -- Access: Input
|
||||
end
|
||||
end
|
||||
if raw_input_data_recorded then
|
||||
set_raw_input_data (buf)
|
||||
set_raw_input_data (buf.substring (buf_initial_size + 1, buf.count))
|
||||
-- Only the input data! And differente reference.
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
read_input_data_into_file (a_file: FILE)
|
||||
read_input_data_into_file (a_medium: IO_MEDIUM)
|
||||
-- retrieve the content from the `input' stream into `s'
|
||||
-- warning: if the input data has already been retrieved
|
||||
-- you might not get anything
|
||||
require
|
||||
a_file_is_open_write: a_file.is_open_write
|
||||
a_medium_is_open_write: a_medium.is_open_write
|
||||
local
|
||||
s: STRING
|
||||
l_input: WGI_INPUT_STREAM
|
||||
l_raw_data: detachable STRING_8
|
||||
len: NATURAL_64
|
||||
nb, l_step: INTEGER
|
||||
l_size: NATURAL_64
|
||||
do
|
||||
if raw_input_data_recorded and then attached raw_input_data as d then
|
||||
a_file.put_string (d)
|
||||
a_medium.put_string (d)
|
||||
else
|
||||
if raw_input_data_recorded then
|
||||
create l_raw_data.make_empty
|
||||
end
|
||||
l_input := input
|
||||
len := content_length_value
|
||||
|
||||
debug ("wsf")
|
||||
io.error.put_string (generator + ".read_input_data_into_file (a_file) content_length=" + len.out + "%N")
|
||||
end
|
||||
|
||||
from
|
||||
l_size := 0
|
||||
l_step := 8_192
|
||||
create s.make (l_step)
|
||||
until
|
||||
l_step = 0 or l_input.end_of_input
|
||||
loop
|
||||
if len < l_step.to_natural_64 then
|
||||
l_step := len.to_integer_32
|
||||
l_input.append_to_string (s, l_step)
|
||||
nb := l_input.last_appended_count
|
||||
|
||||
a_medium.put_string (s)
|
||||
if l_raw_data /= Void then
|
||||
l_raw_data.append (s)
|
||||
end
|
||||
if l_step > 0 then
|
||||
l_input.append_to_string (s, l_step)
|
||||
nb := l_input.last_appended_count
|
||||
l_size := l_size + nb.to_natural_64
|
||||
len := len - nb.to_natural_64
|
||||
|
||||
debug ("wsf")
|
||||
io.error.put_string (" append (s, " + l_step.out + ") -> " + nb.out + " (" + l_size.out + " / "+ content_length_value.out + ")%N")
|
||||
end
|
||||
|
||||
a_file.put_string (s)
|
||||
if l_raw_data /= Void then
|
||||
l_raw_data.append (s)
|
||||
end
|
||||
s.wipe_out
|
||||
if nb < l_step then
|
||||
l_step := 0
|
||||
end
|
||||
s.wipe_out
|
||||
if nb < l_step then
|
||||
l_step := 0
|
||||
end
|
||||
end
|
||||
a_file.flush
|
||||
debug ("wsf")
|
||||
io.error.put_string ("offset =" + len.out + "%N")
|
||||
if attached {FILE} a_medium as f then
|
||||
f.flush
|
||||
end
|
||||
check got_all_data: len = 0 end
|
||||
if l_raw_data /= Void then
|
||||
set_raw_input_data (l_raw_data)
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@ class
|
||||
inherit
|
||||
WSF_REQUEST
|
||||
|
||||
create {WSF_TO_WGI_SERVICE, WSF_SERVICE}
|
||||
create {WSF_SERVICE}
|
||||
make_from_wgi
|
||||
|
||||
end
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
note
|
||||
description: "Summary description for {WSF_PROXY_SOCKET_FACTORY}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WSF_PROXY_SOCKET_FACTORY
|
||||
|
||||
inherit
|
||||
WSF_PROXY_SOCKET_FACTORY_I
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
ssl_socket (a_host: READABLE_STRING_8; a_port: INTEGER): detachable NETWORK_STREAM_SOCKET
|
||||
do
|
||||
check supported: False end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
note
|
||||
description: "Summary description for {WSF_PROXY_SOCKET_FACTORY}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WSF_PROXY_SOCKET_FACTORY
|
||||
|
||||
inherit
|
||||
WSF_PROXY_SOCKET_FACTORY_I
|
||||
redefine
|
||||
is_ssl_supported
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
ssl_socket (a_host: READABLE_STRING_8; a_port: INTEGER): detachable SSL_NETWORK_STREAM_SOCKET
|
||||
do
|
||||
if attached create_from_name (a_host) as l_peer_address then
|
||||
create Result.make_client_by_address_and_port (l_peer_address, a_port)
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Status
|
||||
|
||||
is_ssl_supported: BOOLEAN = True
|
||||
-- Is https:// supported?
|
||||
|
||||
end
|
||||
@@ -0,0 +1,67 @@
|
||||
note
|
||||
description: "Summary description for {WSF_PROXY_SOCKET_FACTORY_I}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
WSF_PROXY_SOCKET_FACTORY_I
|
||||
|
||||
inherit
|
||||
INET_ADDRESS_FACTORY
|
||||
|
||||
feature -- Access
|
||||
|
||||
socket_from_uri (a_uri: URI): like socket
|
||||
local
|
||||
l_port: INTEGER
|
||||
do
|
||||
if a_uri.is_valid and then attached a_uri.host as l_host then
|
||||
l_port := a_uri.port
|
||||
if a_uri.scheme.is_case_insensitive_equal_general ("https") then
|
||||
if is_ssl_supported then
|
||||
if l_port <= 0 then
|
||||
l_port := 443
|
||||
end
|
||||
Result := ssl_socket (l_host, l_port)
|
||||
end
|
||||
elseif a_uri.scheme.is_case_insensitive_equal_general ("http") then
|
||||
if l_port <= 0 then
|
||||
l_port := 80
|
||||
end
|
||||
Result := socket (l_host, l_port)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Status
|
||||
|
||||
is_uri_supported (a_uri: URI): BOOLEAN
|
||||
do
|
||||
Result := a_uri.scheme.is_case_insensitive_equal_general ("http")
|
||||
or else (
|
||||
a_uri.scheme.is_case_insensitive_equal_general ("https")
|
||||
and is_ssl_supported
|
||||
)
|
||||
end
|
||||
|
||||
is_ssl_supported: BOOLEAN
|
||||
-- Is https:// supported?
|
||||
do
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
socket (a_host: READABLE_STRING_8; a_port: INTEGER): detachable NETWORK_STREAM_SOCKET
|
||||
do
|
||||
if attached create_from_name (a_host) as l_peer_address then
|
||||
create Result.make_client_by_address_and_port (l_peer_address, a_port)
|
||||
end
|
||||
end
|
||||
|
||||
ssl_socket (a_host: READABLE_STRING_8; a_port: INTEGER): detachable NETWORK_STREAM_SOCKET
|
||||
require
|
||||
is_ssl_supported: is_ssl_supported
|
||||
deferred
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,303 @@
|
||||
note
|
||||
description: "Summary description for {WSF_SIMPLE_REVERSE_PROXY_HANDLER}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WSF_SIMPLE_REVERSE_PROXY_HANDLER
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_remote_uri: READABLE_STRING_8)
|
||||
do
|
||||
create remote_uri.make_from_string (a_remote_uri)
|
||||
timeout := 30 -- seconds. See {NETWORK_SOCKET}.default_timeout
|
||||
connect_timeout := 5_000 -- 5 seconds.
|
||||
is_via_header_supported := True
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
remote_uri: URI
|
||||
-- Url for the targetted service.
|
||||
|
||||
uri_rewriter: detachable WSF_URI_REWRITER assign set_uri_rewriter
|
||||
-- URI rewriter component, to compute the URI on targetted service
|
||||
-- based on current request.
|
||||
|
||||
feature -- Settings
|
||||
|
||||
connect_timeout: INTEGER assign set_connect_timeout
|
||||
-- In milliseconds.
|
||||
|
||||
timeout: INTEGER assign set_timeout
|
||||
-- In seconds.
|
||||
|
||||
is_via_header_supported: BOOLEAN
|
||||
-- Via: header supported.
|
||||
-- Default: True.
|
||||
|
||||
feature -- Change
|
||||
|
||||
set_uri_rewriter (a_rewriter: like uri_rewriter)
|
||||
do
|
||||
uri_rewriter := a_rewriter
|
||||
end
|
||||
|
||||
set_timeout (a_timeout_in_seconds: INTEGER)
|
||||
-- in seconds.
|
||||
do
|
||||
timeout := a_timeout_in_seconds
|
||||
end
|
||||
|
||||
set_connect_timeout (a_timeout_in_milliseconds: INTEGER)
|
||||
-- in milliseconds.
|
||||
do
|
||||
connect_timeout := a_timeout_in_milliseconds
|
||||
end
|
||||
|
||||
set_is_via_header_supported (b: BOOLEAN)
|
||||
-- Set `is_via_header_supported' to `b'.
|
||||
do
|
||||
is_via_header_supported := b
|
||||
end
|
||||
|
||||
feature -- Execution
|
||||
|
||||
proxy_uri (request: WSF_REQUEST): STRING
|
||||
-- URI to query on proxyfied host.
|
||||
do
|
||||
if attached uri_rewriter as r then
|
||||
Result := r.uri (request)
|
||||
else
|
||||
Result := request.request_uri
|
||||
end
|
||||
end
|
||||
|
||||
execute (request: WSF_REQUEST; response: WSF_RESPONSE)
|
||||
-- Execute reverse proxy request.
|
||||
local
|
||||
h: HTTP_HEADER
|
||||
l_http_query: STRING
|
||||
l_status_line: STRING
|
||||
l_max_forward: INTEGER
|
||||
l_via: detachable STRING
|
||||
l_protocol: STRING
|
||||
i: INTEGER
|
||||
l_completed: BOOLEAN
|
||||
l_remote_uri: like remote_uri
|
||||
l_socket_factory: WSF_PROXY_SOCKET_FACTORY
|
||||
do
|
||||
l_remote_uri := remote_uri
|
||||
create l_socket_factory
|
||||
if not l_socket_factory.is_uri_supported (l_remote_uri) then
|
||||
send_error (request, response, {HTTP_STATUS_CODE}.bad_gateway, l_remote_uri.scheme + " is not supported! [for remote " + l_remote_uri.string + "]")
|
||||
elseif attached l_socket_factory.socket_from_uri (l_remote_uri) as l_socket then
|
||||
l_socket.set_connect_timeout (connect_timeout) -- milliseconds
|
||||
l_socket.set_timeout (timeout) -- seconds
|
||||
|
||||
l_socket.connect
|
||||
if l_socket.is_connected then
|
||||
create l_http_query.make_from_string (request.request_method)
|
||||
l_http_query.append_character (' ')
|
||||
l_http_query.append (l_remote_uri.path)
|
||||
l_http_query.append (proxy_uri (request))
|
||||
l_http_query.append_character (' ')
|
||||
l_http_query.append (request.server_protocol)
|
||||
if attached request.raw_header_data as l_raw_header then
|
||||
i := l_raw_header.substring_index ("%R%N", 1)
|
||||
if i > 0 then
|
||||
-- Skip the first status line.
|
||||
create h.make_from_raw_header_data (l_raw_header.substring (i + 2, l_raw_header.count))
|
||||
else
|
||||
create h.make_from_raw_header_data (l_raw_header)
|
||||
end
|
||||
if attached l_remote_uri.host as l_remote_host then
|
||||
if l_remote_uri.port > 0 then
|
||||
h.put_header_key_value ("Host", l_remote_host + ":" + l_remote_uri.port.out)
|
||||
else
|
||||
h.put_header_key_value ("Host", l_remote_host)
|
||||
end
|
||||
end
|
||||
|
||||
-- Via header
|
||||
if is_via_header_supported then
|
||||
if attached h.item ("Via") as v then
|
||||
l_via := v
|
||||
l_via.append (", ")
|
||||
else
|
||||
create l_via.make_empty
|
||||
end
|
||||
l_via.append (request.server_protocol + " " + request.server_name + " (PROXY-" + request.server_software + ")")
|
||||
h.put_header_key_value ("Via", l_via)
|
||||
end
|
||||
|
||||
-- Max-Forwards header handling
|
||||
if attached h.item ("Max-Forwards") as h_max_forward then
|
||||
-- Max-Forwards: 0 stop, otherwise decrement by one.
|
||||
-- see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.31
|
||||
if h_max_forward.is_integer then
|
||||
l_max_forward := h_max_forward.to_integer - 1
|
||||
if l_max_forward >= 0 then
|
||||
h.put_header_key_value ("Max-Forwards", l_max_forward.out)
|
||||
end
|
||||
end
|
||||
end
|
||||
if l_max_forward < 0 then
|
||||
-- i.e previous Max-Forwards was '0'
|
||||
send_error (request, response, {HTTP_STATUS_CODE}.bad_gateway, "Reached maximum number of Forwards, not forwarded to " + l_remote_uri.string)
|
||||
else
|
||||
l_socket.put_string (l_http_query)
|
||||
l_socket.put_string ("%R%N")
|
||||
l_socket.put_string (h.string)
|
||||
l_socket.put_string ("%R%N")
|
||||
if request.content_length_value > 0 then
|
||||
request.read_input_data_into_file (l_socket)
|
||||
end
|
||||
|
||||
-- Get HTTP status
|
||||
l_socket.read_line_thread_aware
|
||||
create l_status_line.make_from_string (l_socket.last_string)
|
||||
-- Get HTTP header block
|
||||
if attached next_http_header_block (l_socket) as l_resp_header then
|
||||
create h.make_from_raw_header_data (l_resp_header)
|
||||
if attached status_line_info (l_status_line) as l_status_info then
|
||||
l_protocol := l_status_info.protocol
|
||||
if attached l_status_info.reason_phrase as l_phrase then
|
||||
response.set_status_code_with_reason_phrase (l_status_info.status_code, l_phrase)
|
||||
else
|
||||
response.set_status_code (l_status_info.status_code)
|
||||
end
|
||||
else
|
||||
check has_status_line: False end
|
||||
l_protocol := "1.0" -- Default?
|
||||
response.set_status_code (80)
|
||||
end
|
||||
|
||||
if is_via_header_supported then
|
||||
if attached h.item ("Via") as v then
|
||||
l_via := v
|
||||
l_via.append (", ")
|
||||
else
|
||||
create l_via.make_empty
|
||||
end
|
||||
l_via.append (l_protocol + " " + request.server_name + " (PROXY-" + request.server_software + ")")
|
||||
h.put_header_key_value ("Via", l_via)
|
||||
end
|
||||
|
||||
response.add_header_lines (h)
|
||||
from
|
||||
l_socket.read_stream (2_048)
|
||||
until
|
||||
l_socket.was_error
|
||||
or not l_socket.is_connected
|
||||
or l_socket.bytes_read <= 0
|
||||
or l_completed
|
||||
loop
|
||||
response.put_string (l_socket.last_string)
|
||||
if l_socket.bytes_read = 2_048 then
|
||||
l_socket.read_stream (2_048)
|
||||
else
|
||||
l_completed := True
|
||||
end
|
||||
end
|
||||
else
|
||||
send_error (request, response, {HTTP_STATUS_CODE}.internal_server_error, "Invalid response header!")
|
||||
end
|
||||
end
|
||||
else
|
||||
send_error (request, response, {HTTP_STATUS_CODE}.internal_server_error, "Can not access request header!")
|
||||
end
|
||||
else
|
||||
send_error (request, response, {HTTP_STATUS_CODE}.gateway_timeout, "Unable to connect " + l_remote_uri.string)
|
||||
end
|
||||
else
|
||||
send_error (request, response, {HTTP_STATUS_CODE}.bad_gateway, "Unable to connect " + l_remote_uri.string)
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
status_line_info (a_line: READABLE_STRING_8): detachable TUPLE [protocol: READABLE_STRING_8; status_code: INTEGER; reason_phrase: detachable READABLE_STRING_8]
|
||||
-- Info from status line
|
||||
--| Such as "HTTP/1.1 200 OK" -> ["1.1", 200, "OK"]
|
||||
local
|
||||
i,j: INTEGER
|
||||
p,s: detachable READABLE_STRING_8
|
||||
c: INTEGER
|
||||
do
|
||||
i := a_line.index_of (' ', 1)
|
||||
if i > 0 then
|
||||
p := a_line.substring (1, i - 1)
|
||||
if p.starts_with_general ("HTTP/") then
|
||||
p := p.substring (6, p.count) -- We could also keep HTTP/
|
||||
end
|
||||
j := i + 1
|
||||
i := a_line.index_of (' ', j)
|
||||
if i > 0 then
|
||||
s := a_line.substring (j, i - 1)
|
||||
if s.is_integer then
|
||||
c := s.to_integer
|
||||
s := a_line.substring (i + 1, a_line.count)
|
||||
if s.is_whitespace then
|
||||
s := Void
|
||||
elseif s[s.count].is_space then
|
||||
s := s.substring (1, s.count - 1)
|
||||
end
|
||||
Result := [p, c, s]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
next_http_header_block (a_socket: NETWORK_STREAM_SOCKET): detachable STRING
|
||||
local
|
||||
h: STRING
|
||||
do
|
||||
create h.make_empty
|
||||
from
|
||||
a_socket.read_line_thread_aware
|
||||
until
|
||||
Result /= Void
|
||||
or a_socket.was_error
|
||||
or (a_socket.bytes_read = 0 or a_socket.bytes_read = -1)
|
||||
or not a_socket.is_connected
|
||||
loop
|
||||
if a_socket.last_string.same_string ("%R") then
|
||||
-- End of header
|
||||
Result := h
|
||||
else
|
||||
h.append (a_socket.last_string)
|
||||
h.append ("%N")
|
||||
a_socket.read_line_thread_aware
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
send_error (request: WSF_REQUEST; response: WSF_RESPONSE; a_status_code: INTEGER; a_message: READABLE_STRING_8)
|
||||
local
|
||||
s: STRING
|
||||
do
|
||||
-- To send a response we need to setup, the status code and
|
||||
-- the response headers.
|
||||
create s.make_from_string (a_message)
|
||||
debug
|
||||
s.append ("%N(UTC time is " + (create {HTTP_DATE}.make_now_utc).rfc850_string + ").%N")
|
||||
end
|
||||
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "plain/text"], ["Content-Length", s.count.out]>>)
|
||||
response.set_status_code (a_status_code)
|
||||
response.header.put_content_type_text_html
|
||||
response.header.put_content_length (s.count)
|
||||
if
|
||||
attached request.http_connection as l_connection and then
|
||||
l_connection.is_case_insensitive_equal_general ("keep-alive")
|
||||
then
|
||||
response.header.put_header_key_value ("Connection", "keep-alive")
|
||||
end
|
||||
response.put_string (s)
|
||||
end
|
||||
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user