From 2b29b4138da83bf35e2366ecfa83abbd45884683 Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Thu, 26 May 2016 23:31:42 +0200 Subject: [PATCH] Updated github web page, and replaced wiki by workbook. --- Gemfile | 2 +- _config.yml | 4 +- _layouts/default.html | 7 +- getting-started.html | 2 +- wiki.html | 52 - wiki/Deployment.md | 138 --- wiki/Doc_Getting_Started.mediawiki | 57 - wiki/Doc_Index.mediawiki | 7 - wiki/EWSGI/Open-Questions.md | 30 - wiki/EWSGI/README.md | 77 -- ...fication---difference-in-main-proposals.md | 110 -- wiki/EWSGI/specification.md | 793 ------------- wiki/Libraries.md | 20 - wiki/README.md | 31 - wiki/Source-structure.md | 33 - wiki/Spec-server-architecture.md | 8 - wiki/community.md | 30 - wiki/documentation/Connectors.md | 56 - wiki/documentation/Filter.md | 32 - wiki/documentation/HTTP-client-library.md | 26 - wiki/documentation/Library-conneg.md | 193 ---- wiki/documentation/README.md | 215 ---- wiki/documentation/Request-and-response.md | 14 - wiki/documentation/Request.md | 22 - wiki/documentation/Response.md | 6 - wiki/documentation/Router.md | 11 - wiki/documentation/Service.md | 7 - .../Using-the-policy-driven-framework.md | 31 - .../WSF_OPTIONS_POLICY.md | 8 - .../Writing-the-handlers.md | 228 ---- .../Wsf-caching-policy.md | 57 - .../Wsf-previous-policy.md | 24 - wiki/project/Meetings.md | 9 - wiki/project/Projects-new-suggestions.md | 25 - wiki/project/Projects.md | 244 ---- wiki/project/Task-json.md | 16 - wiki/project/Tasks-Roadmap.md | 41 - wiki/project/Useful-links.md | 19 - .../meetings/Web-meeting-2012-09-18.md | 67 -- wiki/project/roadmap.md | 9 - workbook.html | 46 + workbook.py | 65 ++ workbook/_ | 9 + workbook/basics/APPLICATION_EXECUTION.png | Bin 0 -> 5971 bytes workbook/basics/Launcher Hierarchy.png | Bin 0 -> 16595 bytes workbook/basics/WSF_SERVICE_LAUNCHER_CGI.png | Bin 0 -> 9471 bytes workbook/basics/WSF_SERVICE_LAUNCHER_FCGI.png | Bin 0 -> 9552 bytes workbook/basics/WSF_SERVICE_LAUNCHER_NINO.png | Bin 0 -> 9523 bytes .../WSF_SERVICE_LAUNCHER_STANDALONE.png | Bin 0 -> 11031 bytes workbook/basics/basics.md | 211 ++++ workbook/basics/simple/application.e | 24 + .../basics/simple/application_execution.e | 25 + workbook/basics/simple/simple.ecf | 50 + .../simple_html/apache_config/Readme.md | 57 + .../simple_html/apache_config/config.conf | 12 + workbook/basics/simple_html/application.e | 24 + .../simple_html/application_execution.e | 66 ++ workbook/basics/simple_html/simple_html.ecf | 42 + workbook/deployment/readme.md | 168 +++ .../generating_response/exel/application.e | 24 + .../exel/application_execution.e | 36 + workbook/generating_response/exel/exel.ecf | 42 + .../generating_response.md | 1004 +++++++++++++++++ .../generating_response/headers/application.e | 24 + .../headers/application_execution.e | 57 + .../generating_response/headers/headers.ecf | 42 + .../generating_response/search/application.e | 24 + .../search/application_execution.e | 172 +++ .../generating_response/search/search.ecf | 42 + .../generating_response/status/application.e | 24 + .../status/application_execution.e | 138 +++ .../generating_response/status/status.ecf | 42 + .../handling_cookies/example/application.e | 24 + .../example/application_execution.e | 141 +++ workbook/handling_cookies/example/example.ecf | 42 + workbook/handling_cookies/handling_cookies.md | 293 +++++ workbook/handling_request/form.md | 311 +++++ .../handling_request/form/get/application.e | 24 + .../form/get/application_execution.e | 101 ++ workbook/handling_request/form/get/form.ecf | 42 + workbook/handling_request/form/get/form.html | 49 + .../handling_request/form/post/application.e | 24 + .../form/post/application_execution.e | 83 ++ workbook/handling_request/form/post/form.ecf | 42 + workbook/handling_request/form/post/form.html | 56 + workbook/handling_request/headers.md | 441 ++++++++ .../headers/browser_name/application.e | 24 + .../browser_name/application_execution.e | 97 ++ .../headers/browser_name/browsers.ecf | 42 + .../headers/cgi_variables/application.e | 24 + .../cgi_variables/application_execution.e | 303 +++++ .../headers/cgi_variables/cgi_variables.ecf | 42 + .../headers/header_fields/application.e | 24 + .../header_fields/application_execution.e | 105 ++ .../headers/header_fields/header_fields.ecf | 42 + .../upload_file/application.e | 24 + .../upload_file/application_execution.e | 58 + .../handling_request/upload_file/upload.ecf | 42 + .../handling_request/upload_file/upload.html | 22 + .../handling_request/upload_file/upload.rc | 6 + workbook/readme.md | 9 + workbook/workbook.md | 48 + 102 files changed, 5061 insertions(+), 2755 deletions(-) delete mode 100644 wiki.html delete mode 100644 wiki/Deployment.md delete mode 100644 wiki/Doc_Getting_Started.mediawiki delete mode 100644 wiki/Doc_Index.mediawiki delete mode 100644 wiki/EWSGI/Open-Questions.md delete mode 100644 wiki/EWSGI/README.md delete mode 100644 wiki/EWSGI/specification---difference-in-main-proposals.md delete mode 100644 wiki/EWSGI/specification.md delete mode 100644 wiki/Libraries.md delete mode 100644 wiki/README.md delete mode 100644 wiki/Source-structure.md delete mode 100644 wiki/Spec-server-architecture.md delete mode 100644 wiki/community.md delete mode 100644 wiki/documentation/Connectors.md delete mode 100644 wiki/documentation/Filter.md delete mode 100644 wiki/documentation/HTTP-client-library.md delete mode 100644 wiki/documentation/Library-conneg.md delete mode 100644 wiki/documentation/README.md delete mode 100644 wiki/documentation/Request-and-response.md delete mode 100644 wiki/documentation/Request.md delete mode 100644 wiki/documentation/Response.md delete mode 100644 wiki/documentation/Router.md delete mode 100644 wiki/documentation/Service.md delete mode 100644 wiki/policy-driven-framework/Using-the-policy-driven-framework.md delete mode 100644 wiki/policy-driven-framework/WSF_OPTIONS_POLICY.md delete mode 100644 wiki/policy-driven-framework/Writing-the-handlers.md delete mode 100644 wiki/policy-driven-framework/Wsf-caching-policy.md delete mode 100644 wiki/policy-driven-framework/Wsf-previous-policy.md delete mode 100644 wiki/project/Meetings.md delete mode 100644 wiki/project/Projects-new-suggestions.md delete mode 100644 wiki/project/Projects.md delete mode 100644 wiki/project/Task-json.md delete mode 100644 wiki/project/Tasks-Roadmap.md delete mode 100644 wiki/project/Useful-links.md delete mode 100644 wiki/project/meetings/Web-meeting-2012-09-18.md delete mode 100644 wiki/project/roadmap.md create mode 100644 workbook.html create mode 100755 workbook.py create mode 100644 workbook/_ create mode 100644 workbook/basics/APPLICATION_EXECUTION.png create mode 100644 workbook/basics/Launcher Hierarchy.png create mode 100644 workbook/basics/WSF_SERVICE_LAUNCHER_CGI.png create mode 100644 workbook/basics/WSF_SERVICE_LAUNCHER_FCGI.png create mode 100644 workbook/basics/WSF_SERVICE_LAUNCHER_NINO.png create mode 100644 workbook/basics/WSF_SERVICE_LAUNCHER_STANDALONE.png create mode 100644 workbook/basics/basics.md create mode 100644 workbook/basics/simple/application.e create mode 100644 workbook/basics/simple/application_execution.e create mode 100644 workbook/basics/simple/simple.ecf create mode 100644 workbook/basics/simple_html/apache_config/Readme.md create mode 100644 workbook/basics/simple_html/apache_config/config.conf create mode 100644 workbook/basics/simple_html/application.e create mode 100644 workbook/basics/simple_html/application_execution.e create mode 100644 workbook/basics/simple_html/simple_html.ecf create mode 100644 workbook/deployment/readme.md create mode 100644 workbook/generating_response/exel/application.e create mode 100644 workbook/generating_response/exel/application_execution.e create mode 100644 workbook/generating_response/exel/exel.ecf create mode 100644 workbook/generating_response/generating_response.md create mode 100644 workbook/generating_response/headers/application.e create mode 100644 workbook/generating_response/headers/application_execution.e create mode 100644 workbook/generating_response/headers/headers.ecf create mode 100644 workbook/generating_response/search/application.e create mode 100644 workbook/generating_response/search/application_execution.e create mode 100644 workbook/generating_response/search/search.ecf create mode 100644 workbook/generating_response/status/application.e create mode 100644 workbook/generating_response/status/application_execution.e create mode 100644 workbook/generating_response/status/status.ecf create mode 100644 workbook/handling_cookies/example/application.e create mode 100644 workbook/handling_cookies/example/application_execution.e create mode 100644 workbook/handling_cookies/example/example.ecf create mode 100644 workbook/handling_cookies/handling_cookies.md create mode 100644 workbook/handling_request/form.md create mode 100644 workbook/handling_request/form/get/application.e create mode 100644 workbook/handling_request/form/get/application_execution.e create mode 100644 workbook/handling_request/form/get/form.ecf create mode 100644 workbook/handling_request/form/get/form.html create mode 100644 workbook/handling_request/form/post/application.e create mode 100644 workbook/handling_request/form/post/application_execution.e create mode 100644 workbook/handling_request/form/post/form.ecf create mode 100644 workbook/handling_request/form/post/form.html create mode 100644 workbook/handling_request/headers.md create mode 100644 workbook/handling_request/headers/browser_name/application.e create mode 100644 workbook/handling_request/headers/browser_name/application_execution.e create mode 100644 workbook/handling_request/headers/browser_name/browsers.ecf create mode 100644 workbook/handling_request/headers/cgi_variables/application.e create mode 100644 workbook/handling_request/headers/cgi_variables/application_execution.e create mode 100644 workbook/handling_request/headers/cgi_variables/cgi_variables.ecf create mode 100644 workbook/handling_request/headers/header_fields/application.e create mode 100644 workbook/handling_request/headers/header_fields/application_execution.e create mode 100644 workbook/handling_request/headers/header_fields/header_fields.ecf create mode 100644 workbook/handling_request/upload_file/application.e create mode 100644 workbook/handling_request/upload_file/application_execution.e create mode 100644 workbook/handling_request/upload_file/upload.ecf create mode 100644 workbook/handling_request/upload_file/upload.html create mode 100644 workbook/handling_request/upload_file/upload.rc create mode 100644 workbook/readme.md create mode 100644 workbook/workbook.md diff --git a/Gemfile b/Gemfile index 053c27dc..37f5eaa4 100644 --- a/Gemfile +++ b/Gemfile @@ -1,2 +1,2 @@ source 'https://rubygems.org' -gem 'github-pages' +gem 'github-pages', group: :jekyll_plugins diff --git a/_config.yml b/_config.yml index c435af84..9e81c070 100644 --- a/_config.yml +++ b/_config.yml @@ -7,9 +7,9 @@ permalink: pretty # Server destination: ./_gh_pages -exclude: [".gitignore", "Gruntfile.js", "package.json", "node_modules", "README.md", "server_architecture.png", "wiki.py"] +exclude: [".gitignore", "Gruntfile.js", "package.json", "node_modules", "/README.md", "*.py", "*.png"] port: 9000 # Custom vars repo: https://github.com/EiffelWebFramework/EWF -download: https://github.com/EiffelWebFramework/EWF/archive/v0.4.0.zip +download: https://github.com/EiffelWebFramework/EWF/archive/v1.0.3.zip diff --git a/_layouts/default.html b/_layouts/default.html index 39b5dbf1..80045cbc 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -28,10 +28,7 @@ Community - Overview - - - Wiki + Documentation

Eiffel Web Framework

@@ -40,7 +37,7 @@ {{ content }}
diff --git a/getting-started.html b/getting-started.html index e776a5a6..e62fc351 100644 --- a/getting-started.html +++ b/getting-started.html @@ -39,7 +39,7 @@ base_url: "../"
  • located in $ISE_EIFFEL/contrib/examples/web/ewf
  • available on Github
  • -
  • Read the Overview
  • +
  • Read the Overview
  • diff --git a/wiki.html b/wiki.html deleted file mode 100644 index 5738e21a..00000000 --- a/wiki.html +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: default -title: Wiki -base_url: ../ ---- - - \ No newline at end of file diff --git a/wiki/Deployment.md b/wiki/Deployment.md deleted file mode 100644 index 7d780d29..00000000 --- a/wiki/Deployment.md +++ /dev/null @@ -1,138 +0,0 @@ ---- -layout: default -title: Deployment -base_url: ../../ ---- -EWF Deployment -============== - -##Apache on Windows -###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 - -####CGI overview -> A new process is started for each HTTP request. So if there are N request to the same CGI -> program, the code of the CGI program is loaded into memory N times. -> When a CGI program finished handling a request, the program terminates - -1. Build EWF application. - - ``` - ec -config app.ecf -target app_cgi -finalize -c_compile -project_path . - Note: change app.ecf and target app_cgi based on your own configuration. - ``` -2. Copy the generated exe file and the www content. - - ``` - Copy the app.exe and the folder www into a folder served by apache2, for example under - /htdocs - = path to your apache installation - Edit httpd.conf under c://conf - DocumentRoot "c://htdocs" - /htdocs"> - AllowOverride All -- - Require all granted -- this is required in Apache 2.4.4 - - ``` - -3. Check that you have the following modules enabled. - - ``` - LoadModule cgi_module modules/mod_cgi.so - LoadModule rewrite_module modules/mod_rewrite.so - ``` - -*Tip:* -To check the syntax of your httpd.conf file. From command line run the following. - - ``` - $>httpd - t - ``` -####.htaccess CGI -http://perishablepress.com/stupid-htaccess-trics/ - -``` - Options +ExecCGI +Includes +FollowSymLinks -Indexes - AddHandler cgi-script exe - - RewriteEngine on - RewriteRule ^$ $service [L] - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-d - RewriteCond %{REQUEST_URI} !$service - RewriteRule ^(.*)$ $service/$1 - RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] - 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. - -1. Build EWF application - - ``` - ec -config app.ecf -target app_fcgi -finalize -c_compile -project_path . - Note: change app.ecf and target app_fcgi based on your own configuration. - ``` -2. Copy the generated exe file and the www content - - ``` - Copy the app.exe and the folder "www" into a folder served by apache2, for example under - /htdocs. - = path to your apache installation - Edit httpd.conf under c://conf - - DocumentRoot "c://htdocs" - /htdocs"> - AllowOverride All -- - Require all granted -- this is required in Apache 2.4.4 - - ``` -Check that you have the following modules enabled. - - ``` - LoadModule rewrite_module modules/mod_rewrite.so - LoadModule fcgid_module modules/mod_fcgid.so - ``` - -By default Apache does not comes 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/ - -``` - Options +ExecCGI +Includes +FollowSymLinks -Indexes - - AddHandler fcgid-script .ews - FcgidWrapper $FULL_PATH/$service .ews - - - RewriteEngine on - RewriteBase / - RewriteRule ^$ service.ews [L] - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-d - RewriteCond %{REQUEST_URI} !=/favicon.ico - RewriteCond %{REQUEST_URI} !service.ews - RewriteRule ^(.*)$ service.ews/$1 RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] - -``` -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. diff --git a/wiki/Doc_Getting_Started.mediawiki b/wiki/Doc_Getting_Started.mediawiki deleted file mode 100644 index 865d432a..00000000 --- a/wiki/Doc_Getting_Started.mediawiki +++ /dev/null @@ -1,57 +0,0 @@ -Menu :: [[Doc_Getting_Started.mediawiki|Getting Started]] :: [[Community-collaboration.md|Community]] - -= Getting Started = -This page will help you to get started with EWF. We will first see how to install EWF and then how to compile and run the venerable Hello World example. - -== Installation == -=== EiffelStudio 7.2 === -EWF is already included in EiffelStudio 7.2: you don't have to do anything in this case! This is the recommanded solution if you are a new developer or are new to Eiffel. - -=== Other EiffelStudio versions === -If you have another version of EiffelStudio than 7.2, you have to - -* dowload EWF -* create a directory where you will put your custum Eiffel libraries -* extract EWF in the newly created directory -* define the environment variable EIFFEL_LIBRARY to point to the newly created directory - -=== Source code === -The source code is available on Github. You can get it by running the command: - -git clone git://github.com/EiffelWebFramework/EWF.git - -== Hello World == -The hello world example is located in the directory $ISE_EIFFEL/contrib/examples/web/ewf/simple. Just double click on the simple.ecf file and select the simple target or if you prefer the command line, run the command: -estudio -config simple.ecf -target simple - -Once the project is compiled, we will adapt the root class to point to port number 9090. - -'''Note''': By default, the application listens on port 80, which is often already used by standard webservers (Apache, nginx, ...). Moreover, on Linux, ports below 1024 can only be opened by root. - -To do this, we will redefine the feature initialize as follows: - - - class - APPLICATION - - inherit - WSF_DEFAULT_SERVICE - redefine - initialize - end - - create - make_and_launch - - feature {NONE} -- Initialization - - initialize - -- Initialize current service - do - set_service_option ("port", 9090) - end - end - - -After one more compile, you can now launch the application and point your browser to [http://localhost:9090]. -You should now see a simple page with Hello World. diff --git a/wiki/Doc_Index.mediawiki b/wiki/Doc_Index.mediawiki deleted file mode 100644 index 9f195ef9..00000000 --- a/wiki/Doc_Index.mediawiki +++ /dev/null @@ -1,7 +0,0 @@ -Menu :: [[Doc_Getting_Started.mediawiki|Getting Started]] :: [[community.md|Community]] - -= Eiffel Web Framework = -Framework to build web applications in Eiffel - -[ [http://github.com/EiffelWebFramework/EWF/zipball/ Download Current] ] -[ [http://github.com/EiffelWebFramework/EWF/zipball/release-0.3 Download v0.3] ] diff --git a/wiki/EWSGI/Open-Questions.md b/wiki/EWSGI/Open-Questions.md deleted file mode 100644 index 3a56db0b..00000000 --- a/wiki/EWSGI/Open-Questions.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -layout: default -title: Open Questions -base_url: ../../ ---- -## STRING_32, UTF-8, ... ? ## -Berend raised the point that using STRING_32 is consuming 4 times the space used for STRING_8. -And CPU is cheaper than memory, so we should try to use as less memory as possible. -And then for Berend, STRING_32 is not the solution. - -Most of the data are just STRING_8 in CGI -so let's list the various request data - -- **query_parameter** (from the query string ?foo=bar&extra=blabla ) - in this case, I think the name can be url-encoded, and obviously the value too - I guess it makes sense to url-decode them - but on the other hand, we could just keep them url-encoded (as they are), and it is up to the application to url-decode them if needed. - Of course, we should provide facilities to url-decode those strings. - -- **form_data_parameter** (from the POST method) - quite often, it is same kind of content that `parameters' - but .. here this might depends on the encoding for multi-parts encoding. - -- **meta_variable** (from the request itself ... CGI meta variables..) - I am wondering about unicode domain name ... - -- **input data** ... - I think this is up to the application - -... to be continued ... diff --git a/wiki/EWSGI/README.md b/wiki/EWSGI/README.md deleted file mode 100644 index 20213cd2..00000000 --- a/wiki/EWSGI/README.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -layout: default -title: README -base_url: ../../ ---- -- See proposed specifications: [EWSGI specification](EWSGI-specification.md) -- See [Open questions](EWSGI-Open-Questions.md) -- And below the various proposals and associated decision - ----- -# Waiting for decision - -## Include EWF_HEADER into EWSGI as WGI_HEADERS -- Code: **P-2011-09-26-include-wgi-headers** -- Status: proposed on 2011-09-26 **WAITING FOR APPROVAL** - -> Include WGI_HEADERS to help the user to build HTTP Header. -> So that he doesn't have to know the HTTP specification for usual needs - - ----- -# Adopted entries - -## Rename `parameter` into `item` -- Code: **P-2011-09-07-renaming_REQUEST_item** -- Status: proposed on 2011-09-07 **ADOPTED-by-default** - -> rename `{REQUEST}.parameter (n: READABLE_STRING_GENERAL): detachable WGI_VALUE` -> into `{REQUEST}.item (n: READABLE_STRING_GENERAL): detachable WGI_VALUE` -> and similar for `parameters` -> `items` - -## Return type of `parameter' (and similar query_, form_data_ ...) should be deferred WGI_VALUE -- Code: **P-2011-09-05-WGI_VALUE** -- Status: proposed on 2011-09-05 **ADOPTED-by-default** - -> Instead of returning just `READABLE_STRING_32` , it would be better to use **WGI_VALUE** . -> Mainly to address the multiple value for the same param name, but also for uploaded files. -> This allows to have various types such as WGI_STRING_VALUE, WGI_LIST_VALUE, WGI_TABLE_VALUE, WGI_FILE_VALUE . -> -> Thus we would have: parameter (a_name: READABLE_STRING_GENERAL): detachable WGI_VALUE - -## Include the parameter's name in WGI_VALUE interface -- Code: **P-2011-09-05-WGI_VALUE_interface** -- Dependence: adoption of P-2011-09-05-WGI_VALUE , may impact on P-2011-09-05-parameters_ITERABLE -- Status: proposed on 2011-09-05 **ADOPTED-by-default** - -> include the corresponding parameter's name in WGI_VALUE interface. -> Such as `{WGI_VALUE}.name: READABLE_STRING_GENERAL` (or READABLE_STRING_32). -> -> This would also allow to replace -> signature `parameters: ITERABLE [TUPLE [name: READABLE_STRING_GENERAL; value: WGI__VALUE]]' -> by a nicer signature `parameters: ITERABLE [WGI__VALUE]` - -## Signature of parameters (and similar) using ITERABLE [...] -- Code: **P-2011-09-05-parameters_ITERABLE** -- Status: proposed on 2011-09-05 **ADOPTED-by-default** - -> Description: Instead of forcing the implementation to use HASH_TABLE, DS_HASH_TABLE, DS_HASH_SET, ... or similar -> we should use `ITERABLE` -> -> `parameters: ITERABLE [TUPLE [name: READABLE_STRING_GENERAL; value: WGI_VALUE]]` -> -> Or, if `P-2011-09-05-WGI_VALUE_interface` is adopted (WGI_VALUE.name holds the related parameter's name) -> -> `parameters: ITERABLE [WGI_VALUE]` - -## Change prefix from EWSGI_ to WGI_ -- Code: **P-2011-08-29-WGI_prefix** -- Status: **adopted** -- Decision: **WGI_** - -> shorter and pronouncable prefix for EWSGI class names - ----- -# Rejected entries - -... diff --git a/wiki/EWSGI/specification---difference-in-main-proposals.md b/wiki/EWSGI/specification---difference-in-main-proposals.md deleted file mode 100644 index a633a495..00000000 --- a/wiki/EWSGI/specification---difference-in-main-proposals.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -layout: default -title: specification difference in main proposals -base_url: ../../ ---- -Currently the **design of the EWSGI** is not going very fast, mainly due to conflicts for the core design. - -Let's try to summary today's **points of conflict** between Paul's proposal, and Jocelyn's proposal. -Since Paul put the specification on the wiki from Seibo Solution Studio, -and Jocelyn is implementing the proposal withing the current Eiffel Web Framework. -(If other proposals are provided, we'll compare them to those 2 existing proposals.) - -Let's name the **Seibo-EWSGI** proposal and the **EWF-EWSGI** proposal. - -Of course, **the goal is to have only one specification**, but it is good to have more than one proposal. So that we really try to specify the best EWSGI as possible. - -Let's remind that EWSGI is meant to be a specification, and we are looking at **Specification-Compliance** for the future implementation (so this is mainly about class names, and feature signatures. I.e the Eiffel system interface). - -_Note_: to make the code shorter, we will not always include the prefix EWSGI_ to the class name. -_Note2_: we will use the term of "user" for the developer using the EWSGI specification and/or implementation(s) - -## General goal ## -At first, the main difference between Seibo-EWSGI and EWF-EWSGI is a tiny nuance on the general goal. -_Seibo-EWSGI_: get web application portability across a variety of web servers. -_EWF-EWSGI_: get web application portability across a variety of web servers including connectors. - -Both are following the CGI specification. - -To resume, the goal is to get an Eiffel Web ecosystem based on EWSGI specification which allow to write components, libraries, applications based on EWSGI, and which can be compiled and used without changes on the various EWSGI implementations. -EWF-EWSGI is also targetting to make the connector implementations portable on the various EWSGI implementation. However this is not the most critical point, and could be address in later specification version, or maybe a specific EWSGI/Connector specification ... - -**Conclusion**: the general goal is (merely) the same for both proposals. -That is a good point, otherwise no need to compare the 2 proposals - -## Main entry point for a Web application component ## -This is the first important difference in the class EWSGI_APPLICATION - -Seibo-EWSGI: - - response (request: EWSGI_REQUEST): EWSGI_RESPONSE is - -- The response to the given 'request'. - deferred - ensure - Result.status_is_set - Result.ready_to_transmit - end - -EWF-EWSGI: - - execute (req: EWSGI_REQUEST; res: EWSGI_RESPONSE_BUFFER) - -- Execute the request - -- See `req.input' for input stream - -- `req.environment' for the Gateway environment - -- and `res' for the output buffer - require - res_status_unset: not res.status_is_set - deferred - ensure - res_status_set: res.status_is_set - end - -So the main difference is for the user/developer -Seibo-EWSGI: the user has to create the RESPONSE object. -EWF-EWSGI: the user has the RESPONSE_BUFFER object passed as argument, and it is ready to use. - -The consequences are important because - -* Seibo-EWSGI: - - - to make the creation of RESPONSE simple, the decision was to remove any notion of output stream in EWSGI. - - Then the RESPONSE has to be built before sending it. But the proposal also provides a way to get the response message by block during the transmission. A kind of delay RESPONSE filling which is for now using a read_block pattern. And this mimics the send immediately the message parts to the client. - - This design allows the developer to use its own descendant of RESPONSE and then implement the "read_block" pattern when needed. - - This also allows to control carefully that the status+headers are sent before the body. And if not using the read_block pattern, this allows to set/change the headers even after setting the message body, in fact you really build the Response as an object, and you set the various attributes whenever you want. - - The read_block pattern is meant to address the case where the message would be too big to stay in memory, and/or to send some part immediately to the client, but for that you must stick to the read_block pattern. - - Seibo team find the Response-as-Result design more natural, and adopt the Request/Response model of HTTP protocol - -* EWF-EWSGI: - - - the RESPONSE_BUFFER is passed by argument to the user, then he does not have to worry about how to create it. - - it is a buffer and could be implemented in various way by the implementation(s) to access the output stream if any - - For now, there is no easy way for the user, from the proposed specification, to have a customized RESPONSE_BUFFER - - It is easy to implement the Seibo-EWSGI design on top of EWF-EWSGI (the code is provided in EWF-ewsgi implementation) - - the buffer specification allows the user to send the message parts immediately to the client without complicated pattern. As today implementation, the only restriction imposed by the design is to pass the Status code, and the headers before starting sending the message parts; this is checked by the associated assertions. However this part might be thinked again, to be more flexible, and let this to the responsibility of the user, or of other frameworks built on top of EWSGI spec. - -## Notion of output stream ## - -As you might noticed -In EWF-EWSGI, the output stream is kind of hidden and replaced by the output buffer, which is in fact quite similar. However the goal is to allow the user to send immediately the message parts to the client as simple as writing in the buffer. -The EWSGI implementation will handle this buffer to integrate nicely with the underlying web server techno. This will be done through the implementation of the various connectors. - -In Seibo-EWSGI there is NO notion of output stream, this is to handle the potential case of a HTTP server technologies following CGI specification but without any output stream. -The read_block pattern allows the user to send a big response part by part, and/or also send some message parts immediately to the client during the transmission, this looks like a delayed computation of the messages. - ---- -So for now, those are the main critical (blocking) differences I (Jocelyn) can see. - -## Personal point of views ## - -(Feel free to add your own point of view) - -### Jocelyn ### - -* I prefer the EWF-EWSGI solution (obviously this is my proposal) mainly because with it, you can also implement the Seibo-EWSGI design. And the "more you can do, the less you can also do". -* I find the read_block pattern really not natural to use in non trivial application (even in trivial application, this might not be that easy to handle, and thus a potential source of bugs) -* I admit the need to send immediately to the client might not be the vast majority of cases. But this is useful for big messages, and for long computing message where the client wants to follow the progression (concrete cases: drupal batch, wordpress online update, ...) -* I would prefer to include the connector part into the EWSGI specification, this way any EWSGI_connector implementation could work with any EWSGI implementation. Otherwise we might end up with many different EWSGI implementations, and according to the underlying connector(s) you want to use, you will need to use different EWSGI implementations. - -### Others ... ? ### -Please contribute ... correct also the wiki page if there are mistake or misunderstanding. - diff --git a/wiki/EWSGI/specification.md b/wiki/EWSGI/specification.md deleted file mode 100644 index fc785bd5..00000000 --- a/wiki/EWSGI/specification.md +++ /dev/null @@ -1,793 +0,0 @@ ---- -layout: default -title: specification -base_url: ../../ ---- -**WARNING** **THIS PAGE IS IN PROGRESS, AS IT IS NOW, IT NEEDS UPDATE SINCE IT DOES NOT REFLECT THE FUTURE INTERFACE** - -# The Eiffel Web Server Gateway Interface -## Preface -This specification is a proposition based on recent discussion on the mailing list. -This is work in progress, so far nothing had been decided. -You can find another proposal at [http://eiffel.seibostudios.se/wiki/EWSGI](http://eiffel.seibostudios.se/wiki/EWSGI) , it has common background and goal, however still differ on specific parts. -The main goal for now is to unified those 2 specifications. - ---- -Note the following is work in progress, and reflect a specification proposal, rather than the final specification. -2011-08-01 ---- -For now, the specification from EWF is done in Eiffel interface -please see: [https://github.com/Eiffel-World/Eiffel-Web-Framework/tree/master/library/server/ewsgi/specification](https://github.com/Eiffel-World/Eiffel-Web-Framework/tree/master/library/server/ewsgi/specification) - -WGI_APPLICATION - - deferred class - WGI_APPLICATION - - feature {NONE} -- Execution - - execute (req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) - -- Execute the request - -- See `req.input' for input stream - -- `req.meta_variables' for the CGI meta variable - -- and `res' for the output buffer - require - res_status_unset: not res.status_is_set - deferred - ensure - res_status_set: res.status_is_set - end - - end - -WGI_REQUEST - - deferred class - WGI_REQUEST - - feature -- Access: Input - - input: WGI_INPUT_STREAM - -- Server input channel - deferred - end - - feature -- Access: extra values - - request_time: detachable DATE_TIME - -- Request time (UTC) - deferred - end - - feature -- Access: CGI meta variables - - meta_variable (a_name: READABLE_STRING_GENERAL): detachable WGI_VALUE - -- Environment variable related to `a_name' - require - a_name_valid: a_name /= Void and then not a_name.is_empty - deferred - end - - meta_string_variable (a_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_32 - -- Environment variable related to `a_name' - require - a_name_valid: a_name /= Void and then not a_name.is_empty - do - if attached meta_variable (a_name) as val then - Result := val.as_string - end - end - - meta_variables: ITERABLE [WGI_VALUE] - -- These variables are specific to requests made with HTTP. - -- Interpretation of these variables may depend on the value of - -- SERVER_PROTOCOL. - -- - -- Environment variables with names beginning with "HTTP_" contain - -- header data read from the client, if the protocol used was HTTP. - -- The HTTP header name is converted to upper case, has all - -- occurrences of "-" replaced with "_" and has "HTTP_" prepended to - -- give the environment variable name. The header data may be - -- presented as sent by the client, or may be rewritten in ways which - -- do not change its semantics. If multiple headers with the same - -- field-name are received then they must be rewritten as a single - -- header having the same semantics. Similarly, a header that is - -- received on more than one line must be merged onto a single line. - -- The server must, if necessary, change the representation of the - -- data (for example, the character set) to be appropriate for a CGI - -- environment variable. - -- - -- The server is not required to create environment variables for all - -- the headers that it receives. In particular, it may remove any - -- headers carrying authentication information, such as - -- "Authorization"; it may remove headers whose value is available to - -- the script via other variables, such as "Content-Length" and - -- "Content-Type". - -- - -- For convenience it might also include the following CGI entries - deferred - end - - feature -- Common Gateway Interface - 1.1 8 January 1996 - - auth_type: detachable READABLE_STRING_32 - -- This variable is specific to requests made via the "http" - -- scheme. - -- - -- If the Script-URI required access authentication for external - -- access, then the server MUST set the value of this variable - -- from the 'auth-scheme' token in the request's "Authorization" - -- header field. Otherwise it is set to NULL. - -- - -- AUTH_TYPE = "" | auth-scheme - -- auth-scheme = "Basic" | "Digest" | token - -- - -- HTTP access authentication schemes are described in section 11 - -- of the HTTP/1.1 specification [8]. The auth-scheme is not - -- case-sensitive. - -- - -- Servers MUST provide this metavariable to scripts if the - -- request header included an "Authorization" field that was - -- authenticated. - deferred - end - - content_length: detachable READABLE_STRING_32 - -- This metavariable is set to the size of the message-body - -- entity attached to the request, if any, in decimal number of - -- octets. If no data are attached, then this metavariable is - -- either NULL or not defined. The syntax is the same as for the - -- HTTP "Content-Length" header field (section 14.14, HTTP/1.1 - -- specification [8]). - -- - -- CONTENT_LENGTH = "" | 1*digit - -- - -- Servers MUST provide this metavariable to scripts if the - -- request was accompanied by a message-body entity. - deferred - end - - content_length_value: NATURAL_64 - -- Integer value related to `content_length" - deferred - end - - content_type: detachable READABLE_STRING_32 - -- If the request includes a message-body, CONTENT_TYPE is set to - -- the Internet Media Type [9] of the attached entity if the type - -- was provided via a "Content-type" field in the request header, - -- or if the server can determine it in the absence of a supplied - -- "Content-type" field. The syntax is the same as for the HTTP - -- "Content-Type" header field. - -- - -- CONTENT_TYPE = "" | media-type - -- media-type = type "/" subtype *( ";" parameter) - -- type = token - -- subtype = token - -- parameter = attribute "=" value - -- attribute = token - -- value = token | quoted-string - -- - -- The type, subtype, and parameter attribute names are not - -- case-sensitive. Parameter values MAY be case sensitive. Media - -- types and their use in HTTP are described in section 3.7 of - -- the HTTP/1.1 specification [8]. - -- - -- Example: - -- - -- application/x-www-form-urlencoded - -- - -- There is no default value for this variable. If and only if it - -- is unset, then the script MAY attempt to determine the media - -- type from the data received. If the type remains unknown, then - -- the script MAY choose to either assume a content-type of - -- application/octet-stream or reject the request with a 415 - -- ("Unsupported Media Type") error. See section 7.2.1.3 for more - -- information about returning error status values. - -- - -- Servers MUST provide this metavariable to scripts if a - -- "Content-Type" field was present in the original request - -- header. If the server receives a request with an attached - -- entity but no "Content-Type" header field, it MAY attempt to - -- determine the correct datatype, or it MAY omit this - -- metavariable when communicating the request information to the - -- script. - deferred - end - - gateway_interface: READABLE_STRING_32 - -- This metavariable is set to the dialect of CGI being used by - -- the server to communicate with the script. Syntax: - -- - -- GATEWAY_INTERFACE = "CGI" "/" major "." minor - -- major = 1*digit - -- minor = 1*digit - -- - -- Note that the major and minor numbers are treated as separate - -- integers and hence each may be more than a single digit. Thus - -- CGI/2.4 is a lower version than CGI/2.13 which in turn is - -- lower than CGI/12.3. Leading zeros in either the major or the - -- minor number MUST be ignored by scripts and SHOULD NOT be - -- generated by servers. - -- - -- This document defines the 1.1 version of the CGI interface - -- ("CGI/1.1"). - -- - -- Servers MUST provide this metavariable to scripts. - -- - -- The version of the CGI specification to which this server - -- complies. Syntax: - -- - -- GATEWAY_INTERFACE = "CGI" "/" 1*digit "." 1*digit - -- - -- Note that the major and minor numbers are treated as separate - -- integers and that each may be incremented higher than a single - -- digit. Thus CGI/2.4 is a lower version than CGI/2.13 which in - -- turn is lower than CGI/12.3. Leading zeros must be ignored by - -- scripts and should never be generated by servers. - deferred - end - - path_info: READABLE_STRING_32 - -- The PATH_INFO metavariable specifies a path to be interpreted - -- by the CGI script. It identifies the resource or sub-resource - -- to be returned by the CGI script, and it is derived from the - -- portion of the URI path following the script name but - -- preceding any query data. The syntax and semantics are similar - -- to a decoded HTTP URL 'path' token (defined in RFC 2396 [4]), - -- with the exception that a PATH_INFO of "/" represents a single - -- void path segment. - -- - -- PATH_INFO = "" | ( "/" path ) - -- path = segment *( "/" segment ) - -- segment = *pchar - -- pchar = - -- - -- The PATH_INFO string is the trailing part of the - -- component of the Script-URI (see section 3.2) that follows the - -- SCRIPT_NAME portion of the path. - -- - -- Servers MAY impose their own restrictions and limitations on - -- what values they will accept for PATH_INFO, and MAY reject or - -- edit any values they consider objectionable before passing - -- them to the script. - -- - -- Servers MUST make this URI component available to CGI scripts. - -- The PATH_INFO value is case-sensitive, and the server MUST - -- preserve the case of the PATH_INFO element of the URI when - -- making it available to scripts. - deferred - end - - path_translated: detachable READABLE_STRING_32 - -- PATH_TRANSLATED is derived by taking any path-info component - -- of the request URI (see section 6.1.6), decoding it (see - -- section 3.1), parsing it as a URI in its own right, and - -- performing any virtual-to-physical translation appropriate to - -- map it onto the server's document repository structure. If the - -- request URI includes no path-info component, the - -- PATH_TRANSLATED metavariable SHOULD NOT be defined. - -- - -- - -- PATH_TRANSLATED = *CHAR - -- - -- For a request such as the following: - -- - -- http://somehost.com/cgi-bin/somescript/this%2eis%2epath%2einfo - -- - -- the PATH_INFO component would be decoded, and the result - -- parsed as though it were a request for the following: - -- - -- http://somehost.com/this.is.the.path.info - -- - -- This would then be translated to a location in the server's - -- document repository, perhaps a filesystem path something like - -- this: - -- - -- /usr/local/www/htdocs/this.is.the.path.info - -- - -- The result of the translation is the value of PATH_TRANSLATED. - -- - -- The value of PATH_TRANSLATED may or may not map to a valid - -- repository location. Servers MUST preserve the case of the - -- path-info segment if and only if the underlying repository - -- supports case-sensitive names. If the repository is only - -- case-aware, case-preserving, or case-blind with regard to - -- document names, servers are not required to preserve the case - -- of the original segment through the translation. - -- - -- The translation algorithm the server uses to derive - -- PATH_TRANSLATED is implementation defined; CGI scripts which - -- use this variable may suffer limited portability. - -- - -- Servers SHOULD provide this metavariable to scripts if and - -- only if the request URI includes a path-info component. - deferred - end - - query_string: READABLE_STRING_32 - -- A URL-encoded string; the part of the Script-URI. (See - -- section 3.2.) - -- - -- QUERY_STRING = query-string - -- query-string = *uric - -- The URL syntax for a query string is described in section 3 of - -- RFC 2396 [4]. - -- - -- Servers MUST supply this value to scripts. The QUERY_STRING - -- value is case-sensitive. If the Script-URI does not include a - -- query component, the QUERY_STRING metavariable MUST be defined - -- as an empty string (""). - deferred - end - - remote_addr: READABLE_STRING_32 - -- The IP address of the client sending the request to the - -- server. This is not necessarily that of the user agent (such - -- as if the request came through a proxy). - -- - -- REMOTE_ADDR = hostnumber - -- hostnumber = ipv4-address | ipv6-address - -- The definitions of ipv4-address and ipv6-address are provided - -- in Appendix B of RFC 2373 [13]. - -- - -- Servers MUST supply this value to scripts. - deferred - end - - remote_host: detachable READABLE_STRING_32 - -- The fully qualified domain name of the client sending the - -- request to the server, if available, otherwise NULL. (See - -- section 6.1.9.) Fully qualified domain names take the form as - -- described in section 3.5 of RFC 1034 [10] and section 2.1 of - -- RFC 1123 [5]. Domain names are not case sensitive. - -- - -- Servers SHOULD provide this information to scripts. - deferred - end - - remote_ident: detachable READABLE_STRING_32 - -- The identity information reported about the connection by a - -- RFC 1413 [11] request to the remote agent, if available. - -- Servers MAY choose not to support this feature, or not to - -- request the data for efficiency reasons. - -- - -- REMOTE_IDENT = *CHAR - -- - -- The data returned may be used for authentication purposes, but - -- the level of trust reposed in them should be minimal. - -- - -- Servers MAY supply this information to scripts if the RFC1413 - -- [11] lookup is performed. - deferred - end - - remote_user: detachable READABLE_STRING_32 - -- If the request required authentication using the "Basic" - -- mechanism (i.e., the AUTH_TYPE metavariable is set to - -- "Basic"), then the value of the REMOTE_USER metavariable is - -- set to the user-ID supplied. In all other cases the value of - -- this metavariable is undefined. - -- - -- REMOTE_USER = *OCTET - -- - -- This variable is specific to requests made via the HTTP - -- protocol. - -- - -- Servers SHOULD provide this metavariable to scripts. - deferred - end - - request_method: READABLE_STRING_32 - -- The REQUEST_METHOD metavariable is set to the method with - -- which the request was made, as described in section 5.1.1 of - -- the HTTP/1.0 specification [3] and section 5.1.1 of the - -- HTTP/1.1 specification [8]. - -- - -- REQUEST_METHOD = http-method - -- http-method = "GET" | "HEAD" | "POST" | "PUT" | "DELETE" - -- | "OPTIONS" | "TRACE" | extension-method - -- extension-method = token - -- - -- The method is case sensitive. CGI/1.1 servers MAY choose to - -- process some methods directly rather than passing them to - -- scripts. - -- - -- This variable is specific to requests made with HTTP. - -- - -- Servers MUST provide this metavariable to scripts. - deferred - end - - script_name: READABLE_STRING_32 - -- The SCRIPT_NAME metavariable is set to a URL path that could - -- identify the CGI script (rather than the script's output). The - -- syntax and semantics are identical to a decoded HTTP URL - -- 'path' token (see RFC 2396 [4]). - -- - -- SCRIPT_NAME = "" | ( "/" [ path ] ) - -- - -- The SCRIPT_NAME string is some leading part of the - -- component of the Script-URI derived in some implementation - -- defined manner. No PATH_INFO or QUERY_STRING segments (see - -- sections 6.1.6 and 6.1.8) are included in the SCRIPT_NAME - -- value. - -- - -- Servers MUST provide this metavariable to scripts. - deferred - end - - server_name: READABLE_STRING_32 - -- The SERVER_NAME metavariable is set to the name of the server, - -- as derived from the part of the Script-URI (see section - -- 3.2). - -- - -- SERVER_NAME = hostname | hostnumber - -- - -- Servers MUST provide this metavariable to scripts. - deferred - end - - server_port: INTEGER_32 - -- The SERVER_PORT metavariable is set to the port on which the - -- request was received, as used in the part of the - -- Script-URI. - -- - -- SERVER_PORT = 1*digit - -- - -- If the portion of the script-URI is blank, the actual - -- port number upon which the request was received MUST be - -- supplied. - -- - -- Servers MUST provide this metavariable to scripts. - deferred - end - - server_protocol: READABLE_STRING_32 - -- The SERVER_PROTOCOL metavariable is set to the name and - -- revision of the information protocol with which the request - -- arrived. This is not necessarily the same as the protocol - -- version used by the server in its response to the client. - -- - -- SERVER_PROTOCOL = HTTP-Version | extension-version - -- | extension-token - -- HTTP-Version = "HTTP" "/" 1*digit "." 1*digit - -- extension-version = protocol "/" 1*digit "." 1*digit - -- protocol = 1*( alpha | digit | "+" | "-" | "." ) - -- extension-token = token - -- - -- 'protocol' is a version of the part of the - -- Script-URI, but is not identical to it. For example, the - -- scheme of a request may be "https" while the protocol remains - -- "http". The protocol is not case sensitive, but by convention, - -- 'protocol' is in upper case. - -- - -- A well-known extension token value is "INCLUDED", which - -- signals that the current document is being included as part of - -- a composite document, rather than being the direct target of - -- the client request. - -- - -- Servers MUST provide this metavariable to scripts. - deferred - end - - server_software: READABLE_STRING_32 - -- The SERVER_SOFTWARE metavariable is set to the name and - -- version of the information server software answering the - -- request (and running the gateway). - -- - -- SERVER_SOFTWARE = 1*product - -- product = token [ "/" product-version ] - -- product-version = token - -- Servers MUST provide this metavariable to scripts. - deferred - end - - feature -- HTTP_* - - http_accept: detachable READABLE_STRING_32 - -- Contents of the Accept: header from the current request, if there is one. - -- Example: 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' - deferred - end - - http_accept_charset: detachable READABLE_STRING_32 - -- Contents of the Accept-Charset: header from the current request, if there is one. - -- Example: 'iso-8859-1,*,utf-8'. - deferred - end - - http_accept_encoding: detachable READABLE_STRING_32 - -- Contents of the Accept-Encoding: header from the current request, if there is one. - -- Example: 'gzip'. - deferred - end - - http_accept_language: detachable READABLE_STRING_32 - -- Contents of the Accept-Language: header from the current request, if there is one. - -- Example: 'en'. - deferred - end - - http_connection: detachable READABLE_STRING_32 - -- Contents of the Connection: header from the current request, if there is one. - -- Example: 'Keep-Alive'. - deferred - end - - http_host: detachable READABLE_STRING_32 - -- Contents of the Host: header from the current request, if there is one. - deferred - end - - http_referer: detachable READABLE_STRING_32 - -- The address of the page (if any) which referred the user agent to the current page. - -- This is set by the user agent. - -- Not all user agents will set this, and some provide the ability to modify HTTP_REFERER as a feature. - -- In short, it cannot really be trusted. - deferred - end - - http_user_agent: detachable READABLE_STRING_32 - -- Contents of the User-Agent: header from the current request, if there is one. - -- This is a string denoting the user agent being which is accessing the page. - -- A typical example is: Mozilla/4.5 [en] (X11; U; Linux 2.2.9 i586). - -- Among other things, you can use this value to tailor your page's - -- output to the capabilities of the user agent. - deferred - end - - http_authorization: detachable READABLE_STRING_32 - -- Contents of the Authorization: header from the current request, if there is one. - deferred - end - - feature -- Extra CGI environment variables - - request_uri: READABLE_STRING_32 - -- The URI which was given in order to access this page; for instance, '/index.html'. - deferred - end - - orig_path_info: detachable READABLE_STRING_32 - -- Original version of path_info before processed by Current environment - deferred - end - - feature -- Query string Parameters - - query_parameters: ITERABLE [WGI_VALUE] - -- Variables extracted from QUERY_STRING - deferred - end - - query_parameter (a_name: READABLE_STRING_GENERAL): detachable WGI_VALUE - -- Parameter for name `n'. - require - a_name_valid: a_name /= Void and then not a_name.is_empty - deferred - end - - feature -- Form fields and related - - form_data_parameters: ITERABLE [WGI_VALUE] - -- Variables sent by POST request - deferred - end - - form_data_parameter (a_name: READABLE_STRING_GENERAL): detachable WGI_VALUE - -- Field for name `a_name'. - require - a_name_valid: a_name /= Void and then not a_name.is_empty - deferred - end - - uploaded_files: HASH_TABLE [WGI_UPLOADED_FILE_DATA, READABLE_STRING_GENERAL] - -- Table of uploaded files information - deferred - end - - feature -- Cookies - - cookies: ITERABLE [WGI_VALUE] - -- Expanded cookies variable - deferred - end - - cookie (a_name: READABLE_STRING_GENERAL): detachable WGI_VALUE - -- Field for name `a_name'. - require - a_name_valid: a_name /= Void and then not a_name.is_empty - deferred - end - - feature -- Access: all variables - - items: ITERABLE [WGI_VALUE] - -- Table containing all the various variables - -- Warning: this is computed each time, if you change the content of other containers - -- this won't update this Result's content, unless you query it again - deferred - end - - item (a_name: READABLE_STRING_GENERAL): detachable WGI_VALUE - -- Variable named `a_name' from any of the variables container - -- and following a specific order - -- execution, environment, get, post, cookies - require - a_name_valid: a_name /= Void and then not a_name.is_empty - deferred - end - - feature -- Uploaded File Handling - - is_uploaded_file (a_filename: READABLE_STRING_GENERAL): BOOLEAN - -- Is `a_filename' a file uploaded via HTTP POST - deferred - end - - feature -- URL Utility - - absolute_script_url (a_path: STRING_8): STRING_8 - -- Absolute Url for the script if any, extended by `a_path' - deferred - end - - script_url (a_path: STRING_8): STRING_8 - -- Url relative to script name if any, extended by `a_path' - require - a_path_attached: a_path /= Void - deferred - end - - invariant - server_name_not_empty: not server_name.is_empty - server_port_set: server_port /= 0 - request_method_attached: request_method /= Void - path_info_attached: path_info /= Void - query_string_attached: query_string /= Void - remote_addr_attached: remote_addr /= Void - same_orig_path_info: orig_path_info ~ meta_string_variable ({WGI_META_NAMES}.orig_path_info) - same_path_info: path_info ~ meta_string_variable ({WGI_META_NAMES}.path_info) - path_info_identical: path_info ~ meta_string_variable ({WGI_META_NAMES}.path_info) - end - -WGI_RESPONSE_BUFFER - - deferred class - WGI_RESPONSE_BUFFER - - feature {WGI_APPLICATION} -- Commit - - commit - -- Commit the current response - deferred - ensure - status_is_set: status_is_set - header_committed: header_committed - message_committed: message_committed - end - - feature -- Status report - - header_committed: BOOLEAN - -- Header committed? - deferred - end - - message_committed: BOOLEAN - -- Message committed? - deferred - end - - message_writable: BOOLEAN - -- Can message be written? - deferred - end - - feature {WGI_RESPONSE_BUFFER} -- Core output operation - - write (s: READABLE_STRING_8) - -- Send the string `s' - -- this can be used for header and body - deferred - end - - feature -- Status setting - - status_is_set: BOOLEAN - -- Is status set? - deferred - end - - set_status_code (a_code: INTEGER) - -- Set response status code - -- Should be done before sending any data back to the client - require - status_not_set: not status_is_set - header_not_committed: not header_committed - deferred - ensure - status_code_set: status_code = a_code - status_set: status_is_set - end - - status_code: INTEGER - -- Response status - deferred - end - - feature -- Header output operation - - write_headers_string (a_headers: READABLE_STRING_8) - require - status_set: status_is_set - header_not_committed: not header_committed - deferred - ensure - status_set: status_is_set - header_committed: header_committed - message_writable: message_writable - end - - write_header (a_status_code: INTEGER; a_headers: detachable ARRAY [TUPLE [key: READABLE_STRING_8; value: READABLE_STRING_8]]) - -- Send headers with status `a_status', and headers from `a_headers' - require - status_not_set: not status_is_set - header_not_committed: not header_committed - deferred - ensure - header_committed: header_committed - status_set: status_is_set - message_writable: message_writable - end - - feature -- Output operation - - write_string (s: READABLE_STRING_8) - -- Send the string `s' - require - message_writable: message_writable - deferred - end - - write_substring (s: READABLE_STRING_8; a_begin_index, a_end_index: INTEGER) - -- Send the substring `s[a_begin_index:a_end_index]' - require - message_writable: message_writable - deferred - end - - write_file_content (fn: READABLE_STRING_8) - -- Send the content of file `fn' - require - message_writable: message_writable - deferred - end - - flush - -- Flush if it makes sense - deferred - end - end - - -## Proof-of-concept and reference implementation - -# Specification overview - -## The Server/Gateway Side - -## The Application/Framework Side - -## Specification Details - -## Implementation/Application Notes - -## Questions and Answers - -## Proposed/Under Discussion - -## Acknowledgements - -## References diff --git a/wiki/Libraries.md b/wiki/Libraries.md deleted file mode 100644 index 87f82913..00000000 --- a/wiki/Libraries.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -layout: default -title: Libraries -base_url: ../../ ---- -## libraries currently part of the Eiffel Web Framework ## -* [[Library-EWSGI]]: Eiffel Web Server Gateway Interface (prefix WGI_ for **W**eb**G**ateway**I**nterface) -* [[Library-libFCGI]]: Eiffel wrapper of libfcgi SDK (http://www.fastcgi.com/devkit/libfcgi/) -* [[Library-encoder]]: simple encoder for base64, url-encode, xml entities -* [[Library-error]]: simple framework to handle error in EWSGI and related -* [[Library-http]]: utility classes to handle HTTP protocol, status, ... -* [[Library-uri-template]]: URI Template parser and expander (follow draft spec 0.5) - -## libraries needed but not (yet) in the current framework ## -* [[Library-EiffelWebNino]]: Eiffel Web Server written in Eiffel -* [[Library-JSON]]: JSON format parser and converter - -## libraries that could be included, or or used ## -* eMIME: MIME parser, and encoder ... -* ... diff --git a/wiki/README.md b/wiki/README.md deleted file mode 100644 index 2773c635..00000000 --- a/wiki/README.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -layout: default -title: README -base_url: ../../ ---- -# Eiffel-Web-Framework # - -## Location ## -The official documentation/wiki is located at [https://github.com/EiffelWebFramework/EWF/wiki](https://github.com/EiffelWebFramework/EWF/wiki) , if you are visiting a "clone/fork", please always check the [official wiki](https://github.com/EiffelWebFramework/EWF/wiki). - -## Organization ## -- Mailing list: please visit and subscribe to the mailing list page [http://groups.google.com/group/eiffel-web-framework](http://groups.google.com/group/eiffel-web-framework) ![logo](http://groups.google.com/intl/en/images/logos/groups_logo_sm.gif) -- Most of the topics are discussed on the mailing list (google group). -- For time to time we have [web meetings](project/Meetings.md), and less frequently [physical meetings](project/Meetings.md) that occurs usually during other Eiffel related events. - -## Documentation ## -- [Documentation](documentation/README.md) - -## Contributions ## -- You want to contribute or follow the progress/discussion, see the [collaboration page](community.md) -- Potential tasks/projects on EWF: [Projects page](project/Projects.md) - -## See also ## - - [list of tasks, and a potential roadmap](project/Tasks-Roadmap.md) - - [General source structure of this project](Source-structure.md) - - EWSGI: [Eiffel Web Server Gateway Interface](EWSGI/README.md) - - [Overview of the server side architecture](Spec-Server-Architecture.md) - - This project is also a collection of [Libraries](Libraries.md) related to the Web - -## Note ## - - This wiki needs to be updated, in the meantime, please have a look at the presentation: [https://docs.google.com/presentation/pub?id=1GPFv6aHhTjFSLMnlAt-J4WeIHSGfHdB42dQxmOVOH8s&start=false&loop=false&delayms=3000](https://docs.google.com/presentation/pub?id=1GPFv6aHhTjFSLMnlAt-J4WeIHSGfHdB42dQxmOVOH8s&start=false&loop=false&delayms=3000) diff --git a/wiki/Source-structure.md b/wiki/Source-structure.md deleted file mode 100644 index 983077bd..00000000 --- a/wiki/Source-structure.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -layout: default -title: Source structure -base_url: ../../ ---- -## Currently ## - -- LICENSE : file containing the global license -- README : quick README to point to the github project -- doc/ -- doc/wiki : clone of the associated github wiki repository -- examples/ -- tests/ -- library/ -- library/server/ewsgi/ewsgi.ecf -- library/server/ewsgi/ewsgi.ecf - -Any library/component (in development mode) should follow the following structure (when not needed, there is no need to create the associated folder(s) ): - -- **README** -- **COPYRIGHT** -- **LICENSE** -- **.ecf** and **-safe.ecf** : configuration file -- **library/** : the place to put the source code of the related library/component -- **doc/** : notes, documentations, ... -- **tests/** : standard place to put your tests, if you are using Eiffel Software auto-tests, I would suggest to add a new target into the associated .ecf (instead of building a new .ecf under test/ ) -- **examples/** : standard place to put the example -- **build/** : a convenient place to compile your project, using this convention, you can setup utilities such as backup to ignore this folder. -- **resources/** : contains pixmap files, .... -- **install/** : contains installation scripts for each platform -- **data/** : contains eventual data for the related tools - -See that we use the singular, since it is an Eiffel convention for naming cluster or folder. diff --git a/wiki/Spec-server-architecture.md b/wiki/Spec-server-architecture.md deleted file mode 100644 index 501b0dfc..00000000 --- a/wiki/Spec-server-architecture.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: default -title: Spec server architecture -base_url: ../../ ---- -## Diagram: Overview of the server architecture ## - -![server_architecture.png](server_architecture.png) \ No newline at end of file diff --git a/wiki/community.md b/wiki/community.md deleted file mode 100644 index ef9f5ce4..00000000 --- a/wiki/community.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -layout: default -title: community -base_url: ../../ ---- -This project is a community project - -## Mailing list ## -- Google group: [http://groups.google.com/group/eiffel-web-framework](http://groups.google.com/group/eiffel-web-framework) - -## Materials ## -- wiki: github wiki at [https://github.com/Eiffel-World/Eiffel-Web-Framework/wiki](https://github.com/Eiffel-World/Eiffel-Web-Framework/wiki) -- Shared documents: on google docs at [http://goo.gl/M8WLP](http://goo.gl/M8WLP) -- source code: git repository at [https://github.com/Eiffel-World/Eiffel-Web-Framework](https://github.com/Eiffel-World/Eiffel-Web-Framework) -- Proposal from Paul Cohen for a EWSGI spec at [http://eiffel.seibostudios.se/wiki/EWSGI](http://eiffel.seibostudios.se/wiki/EWSGI) - -## Main contributors ## -- **jfiat**: Jocelyn Fiat (Eiffel Software) -- **jvelilla**: Hector Javier Velilla (Seibo Software Studios) -- **paco**: Paul Cohen (Seibo Software Studios) -- **daro**: Daniel Rodriguez (Seibo Software Studios) -- Olivier Ligot (Groupe-S) -- Paul G.Crismer (Groupe-S) -- Berend de Boer (XplainHosting) -- Colin Adams (AXA R.) -- Alexander Kogtenkov (Eiffel Software) - -## You want to participate ## -- You are welcome to contribute (code, test, doc, code review, feedback, suggestion, spread the words ...) -- Feel free to subscribe to the mailing list \ No newline at end of file diff --git a/wiki/documentation/Connectors.md b/wiki/documentation/Connectors.md deleted file mode 100644 index 407dce6b..00000000 --- a/wiki/documentation/Connectors.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -layout: default -title: Connectors -base_url: ../../ ---- -The main goal of the connectors is to let you choose a target at compile time. -This allows you to concentrate on your business during development time and then decide which target you choose at deployment time. -The current connectors are: -* Nino -* FastCGI -* CGI -* OpenShift - -The most widely used workflow is to use Nino on your development machine and FastCGI on your production server. -Nino being a web server written entirely in Eiffel, you can inspect your HTTP requests and respones in EiffelStudio which is great during development. -On the other hand, FastCGI is great at handling concurrent requests and coupled with Apache (or another web production server), you don't even need to worry about the lifecyle of your application (creation and destruction) as Apache will do it for you! - -Let's now dig into each of the connecters. - -# Nino - -Nino is a web server entirely written in Eiffel. -The goal of Nino is to provide a simple web server for development (like Java, Python and Ruby provide). -Nino is currently maintained by Javier Velilla and the repository can be found here: https://github.com/jvelilla/EiffelWebNino - -# FastCGI - -FastCGI is a protocol for interfacing an application server with a web server. -It is an improvement over CGI as FastCGI supports long running processes, i.e. processes than can handle multipe requests during their lifecyle. CGI, on the other hand, launches a new process for every new request which is quite time consuming. -FastCGI is implemented by every major web servers: Apache, IIS, Nginx, ... -We recommend to use FastCGI instead of CGI as it is way more faster. -You can read more about FastCGI here: http://www.fastcgi.com/ - -# CGI - -CGI predates FastCGI and is also a protocol for interfacing an application server with a web server. -His main drawback (and the reason why FastCGI was created) is that it launches a new process for every new request, which is quite time consuming. -We recommend to use FastCGI instead of CGI as it is way more faster. - -# OpenShift - -OpenShift is a cloud computing platform as a service product from Red Hat. -It basically let's you run your application in the cloud. -More informations are available here: https://www.openshift.com - -# Writing your own - -It's fairly easy to write your own connector. Just inherit from these classes: -* WGI_CONNECTOR -* WGI_ERROR_STREAM -* WGI_INPUT_STREAM -* WGI_OUTPUT_STREAM -* WSF_SERVICE_LAUNCHER - - -See WSF_CONNECTOR diff --git a/wiki/documentation/Filter.md b/wiki/documentation/Filter.md deleted file mode 100644 index e94af2cd..00000000 --- a/wiki/documentation/Filter.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -layout: default -title: Filter -base_url: ../../ ---- -# Introduction - -The basic idea of a filter is to pre-process incoming data and post-process outgoing data. -Filters are part of a filter chain, thus following the [chain of responsability design pattern](http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern). - -Each filter decides to call the next filter or not. - -# Levels - -In EWF, there are two levels of filters. - -## WSF_FILTER - -Typical examples of such filters are: logging, compression, routing (WSF_ROUTING_FILTER), ... - -## WSF_FILTER_HANDLER - -Handler that can also play the role of a filter. - -Typical examples of such filters are: authentication, ... - -# References - -Filters (also called middelwares) in other environments: -* in Python: http://www.wsgi.org/en/latest/libraries.html -* in Node.js: http://expressjs.com/guide.html#middleware -* in Apache: http://httpd.apache.org/docs/2.2/en/filter.html \ No newline at end of file diff --git a/wiki/documentation/HTTP-client-library.md b/wiki/documentation/HTTP-client-library.md deleted file mode 100644 index be2eccf4..00000000 --- a/wiki/documentation/HTTP-client-library.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -layout: default -title: HTTP client library -base_url: ../../ ---- -# HTTP Library Features -The following list of features are taken form the book [RESTful Web Services](http://www.amazon.com/Restful-Web-Services-Leonard-Richardson/dp/0596529260/ref=sr_1_1?ie=UTF8&qid=1322155984&sr=8-1) - -* **HTTPS**: _It must support HTTPS and SSL certificate validation_ - -* **HTTP methods**: _It must support at least the five main HTTP methods: GET, HEAD, POST, PUT, and DELETE. Optional methods - OPTIONS and TRACE, and WebDAV extensions like MOVE, ._ -* **Custom data** : _It must allow the programmer to customize the data sent as the entity-body of a -PUT or POST request._ -* **Custom headers** : _It must allow the programmer to customize a request’s HTTP headers_ -* **Response Codes** : _It must give the programmer access to the response code and headers of an HTTP -response; not just access to the entity-body._ -* **Proxies**: _It must be able to communicate through an HTTP proxy_ - -* **Compression**:_it should automatically request data in compressed form to save -bandwidth, and transparently decompress the data it receives._ -* **Caching**:_It should automatically cache the responses to your requests._ -* **Auth methods** : _It should transparently support the most common forms of HTTP authentication: -Basic, Digest, and WSSE._ -* **Cookies** :_It should be able to parse and create HTTP cookie strings_ -* **Redirects**:_It should be able to transparently follow HTTP redirects_ \ No newline at end of file diff --git a/wiki/documentation/Library-conneg.md b/wiki/documentation/Library-conneg.md deleted file mode 100644 index 1e536f13..00000000 --- a/wiki/documentation/Library-conneg.md +++ /dev/null @@ -1,193 +0,0 @@ ---- -layout: default -title: Library conneg -base_url: ../../ ---- -# Server-driven content negotiation - -EWF supports server-driven content content negotiation, as defined in [HTTP/1.1 Content Negotiation](http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.1) . To enable this facility: - -1. Add ${EWF}/library/network/protocol/conneg/conneg.ecf to your system ECF. -1. In the class where your handlers reside, add an attribute `conneg: CONNEG_SERVER_SIDE`, and ensure it is always attached (create in the creation procedure, or make it a once, or add an attribute body). An example creation call is: `create conneg.make ({HTTP_MIME_TYPES}.application_json, "en", "UTF-8", "")`. - -That call defines our defaults for media-type, language, charset and encoding, respectively. The encoding could also be written as `"identity"`. It means no compression. As an alternative, we might code `"gzip"`. - -The user agent (a web browser, for example. or the curl program), can request different representations by using headers. For example, `Accept: application/json; q=0.2, application/xml` says the client would be very happy to get back an XML representation (if you omit the q for quality parameter, it defaults to 1, which is best), but (s)he will tolerate JSON. Clearly, we are going to be able to satisfy that client, as we serve JSON by default. But what if the client had requested `Accept: application/xml;q=0.8, text/html`? In this example, we are going to serve both JSON and XML representations upon request. A client who requests `Accept: text/html, text/plain` is going to be disappointed. For the other aspects (language, charset and encoding), we are not going to offer any choices. That does not mean we ignore the client's headers for these aspects. We are going to check if our representation is acceptable to the client, and if not, return a 406 Not Acceptable response (an alternative is to send our representation anyway, and let the user decide whether or not to use it). - -Next, we need to declare all the representations we support: - - mime_types_supported: LINKED_LIST [STRING] is - -- Media types `Current' supports - once - create Result.make - Result.put_front ({HTTP_MIME_TYPES}.application_xml) - Result.put_front ({HTTP_MIME_TYPES}.application_json) - ensure - mime_types_supported_not_void: Result /= Void - no_void_entry: not Result.has (Void) - end - - charsets_supported: LINKED_LIST [STRING] is - -- Character sets `Current' supports - once - create Result.make - Result.put_front ("UTF-8") - ensure - charsets_supported_not_void: Result /= Void - no_void_entry: not Result.has (Void) - end - - encodings_supported: LINKED_LIST [STRING] is - -- Encodings `Current' supports - once - create Result.make - Result.put_front ("identity") - Result.put_front ("") -- identity encoding - ensure - encoding_supported_not_void: Result /= Void - no_void_entry: not Result.has (Void) - end - - languages_supported: LINKED_LIST [STRING] is - -- Languages `Current' supports - once - create Result.make - Result.put_front ("en") - ensure - languages_supported_not_void: Result /= Void - no_void_entry: not Result.has (Void) - end - -Now we are in a position to do some negotiating. At the beginning of your handler(s), code: - - - local - l_media_variants: MEDIA_TYPE_VARIANT_RESULTS - l_is_head: BOOLEAN - h: HTTP_HEADER - l_msg: STRING - do - l_is_head := False -- or `True' if this is for a HEAD handler - l_media_variants:= conneg.media_type_preference (mime_types_supported, a_req.http_accept) - if not l_media_variants.is_acceptable then - send_unacceptable_media_type (a_res, l_is_head) - elseif not conneg.charset_preference (charsets_supported, a_req.http_accept_charset).is_acceptable then - send_unacceptable_charset (a_res, l_is_head) - elseif not conneg.encoding_preference (encodings_supported, a_req.http_accept_encoding).is_acceptable then - send_unacceptable_encoding (a_res, l_is_head) - elseif not conneg.language_preference (languages_supported, a_req.http_accept_language).is_acceptable then - send_unacceptable_encoding (a_res) - else - -- We have agreed a representation, let's go and serve it to the client -` - -Now for those `send_unnacceptable_...` routines. They are fairly simple: - - send_unacceptable_media_type (a_res: WSF_RESPONSE; a_is_head: BOOLEAN) is - -- Send error result as text/plain that the media type is unnacceptable. - require - a_res_not_void: a_res /= Void - status_not_set: not a_res.status_is_set - header_not_committed: not a_res.header_committed - local - l_error_text: STRING - do - l_error_text := "The requested media type(s) is/are not supported by this server." - send_unacceptable (a_res, a_is_head, l_error_text) - end - - send_unacceptable_charset (a_res: WSF_RESPONSE; a_is_head: BOOLEAN) is - -- Send error result as text/plain that the character set is unnacceptable. - require - a_res_not_void: a_res /= Void - status_not_set: not a_res.status_is_set - header_not_committed: not a_res.header_committed - local - l_error_text: STRING - do - l_error_text := "The requested character set(s) is/are not supported by this server. Only UTF-8 is supported." - send_unacceptable (a_res, a_is_head, l_error_text) - end - - send_unacceptable_encoding (a_res: WSF_RESPONSE; a_is_head: BOOLEAN) is - -- Send error result as text/plain that the encoding is unnacceptable. - require - a_res_not_void: a_res /= Void - status_not_set: not a_res.status_is_set - header_not_committed: not a_res.header_committed - local - l_error_text: STRING - do - l_error_text := "The requested encoding(s) is/are not supported by this server. Only identity is supported." - send_unacceptable (a_res, a_is_head, l_error_text) - end - - send_unacceptable_language (a_res: WSF_RESPONSE; a_is_head: BOOLEAN) is - -- Send error result as text/plain that the language unnacceptable. - require - a_res_not_void: a_res /= Void - status_not_set: not a_res.status_is_set - header_not_committed: not a_res.header_committed - local - l_error_text: STRING - do - l_error_text := "The requested language(s) is/are not supported by this server. Only en (English) is supported." - send_unacceptable (a_res, a_is_head, l_error_text) - end - - send_unacceptable (a_res: WSF_RESPONSE; a_is_head: BOOLEAN; a_error_text: STRING) is - -- Send a_error_text as text/plain that a header is unnacceptable. - require - a_res_not_void: a_res /= Void - status_not_set: not a_res.status_is_set - header_not_committed: not a_res.header_committed - a_error_text_not_void: a_error_text /= Void - local - h: HTTP_HEADER - do - create h.make - set_content_type (h, Void) - h.put_content_length (a_error_text.count) - h.put_current_date - a_res.set_status_code ({HTTP_STATUS_CODE}.not_acceptable) - a_res.put_header_text (h.string) - if not a_is_head then - a_res.put_string (a_error_text) - end - end - -We'll see that `set_content_type` routine in a bit. But for now, we just have to generate the response. Let's go back to that `else ...` bit: - - else - -- We have agreed a representation, let's go and serve it to the client - create h.make - set_content_type (h, a_media_variants) - if a_media_variants.media_type ~ {HTTP_MIME_TYPES}.application_xml then - l_msg := "etc." - else - l_msg := json.value ().representation - end - h.put_content_length (l_msg.count) - h.put_current_date - a_res.set_status_code ({HTTP_STATUS_CODE}.ok) - a_res.put_header_text (h.string) - if not a_is_head then - a_res.put_string (l_msg) - end - -There's that `set_content_type` again. Finally, we will take a look at it: - - set_content_type (a_h: HTTP_HEADER; a_media_variants: MEDIA_TYPE_VARIANT_RESULTS) is - -- Set the content=type header in `a_h' according to `a_media_variants'. - require - a_h_not_void: a_h /= Void - do - if a_media_variants = Void or else not a_media_variants.is_acceptable then - a_h.put_content_type ({HTTP_MIME_TYPES}.text_plain) - else - a_h.put_content_type (a_media_variants.media_type) - end - a_h.put_header_key_value ({HTTP_HEADER_NAMES}.header_vary, {HTTP_HEADER_NAMES}.header_accept) - end - -Firstly, if we haven't agreed a media-type, then we send our (negative) response as `plain/text`. Otherwise we will send the response in the agreed media-type. But in each case we add a `Vary:Accept` header. This tells proxy caches that they have to check the media-type before returning a cached result, as there may be a different representation available. Since we do not vary our representation by language, charset or encoding, we don't add `Vary:Accept-Language,Accept-Charset,Accept-Encoding`. But if we were to negotiate different representations on those dimensions also, we would need to list the appropriate headers in the `Vary` header. \ No newline at end of file diff --git a/wiki/documentation/README.md b/wiki/documentation/README.md deleted file mode 100644 index 05a6007e..00000000 --- a/wiki/documentation/README.md +++ /dev/null @@ -1,215 +0,0 @@ ---- -layout: default -title: README -base_url: ../../ ---- -# Current Status -* Official repository: -* Official website: - -# What is EWF? - -Eiffel Web Framework, is mainly a collection of Eiffel libraries designed to be integrated with each other. One benefit is that it supports all core HTTP features, so enable you embrace HTTP as an application protocol to develop web applications. So you do not need to adapt your applications to the web, instead you use the web power. It means you can build different kind of web applications, from Web APIs following the Hypermedia API style (REST style), CRUD web services or just conventional web applications building a session on top of an stateless protocol. - -# EWF core/kernel -> The Web Server Foundation (WSF\_) is the core of the framework. It is compliant with the EWSGI interface (WGI\_). - -To build a web [service](#service), the framework provides a set of core components to launch the service, for each [request](#request-and-response), access the data, and send the [response](#request-and-response). -The framework also provides a router component to help dispatching the incoming request. - -A service can be a web api, a web interface, … what ever run on top of HTTP. - - - -# Service -> see interface: **WSF_SERVICE** - -Each incoming http request is processed by the following routine. - -> `{WSF_SERVICE}.execute (req: WSF_REQUEST; res: WSF_RESPONSE)` - -This is the low level of the framework, at this point, `req` provides access to the query and form parameters, input data, headers, ... as specified by the Common Gateway Interface (CGI). -The response `res` is the interface to send data back to the client. -For convenience, the framework provides richer service interface that handles the most common needs (filter, router, ...). - -> [Learn more about service](Service.md) - - - -# Request and Response -> see interface: **WSF_REQUEST** and **WSF_RESPONSE** - -Any incoming http request is represented by an new object of type **WSF_REQUEST**. - -**WSF_REQUEST** provides access to -+ __meta variables__: CGI variables (coming from the request http header) -+ __query parameters__: from the uri ex: `?q=abc&type=pdf` -+ __input data__: the message of the request, if this is a web form, this is parsed to build the form parameters. It can be retrieved once. -+ __form parameters__: standard parameters from the request input data. - - typically available when a web form is sent using POST as content of type `multipart/form-data` or `application/x-www-form-urlencoded` - - (advanced usage: it is possible to write mime handler that can processed other type of content, even custom format.) -+ __uploaded files__: if files are uploaded, their value will be available from the form parameters, and from the uploaded files as well. -+ __cookies variable__: cookies extracted from the http header. -+ __path parameters__: note this is related to the router and carry the semantic of the mapping (see the section on router ) -+ __execution variables__: used by the application to keep value associated with the request. - -The **WSF_RESPONSE** represents the communication toward the client, a service need to provide correct headers, and content. For instance the `Content-Type`, and `Content-Length`. It also allows to send data with chunked encoding. - -> [Learn more about request](Request.md) and [about response](Response.md) - - - -# Connectors: -> see **WGI_CONNECTOR** - -Using EWF, your service is built on top of underlying httpd solution/connectors. -Currently 3 main connectors are available: -* __CGI__: following the CGI interface, this is an easy solution to run the service on any platform. -* __libFCGI__: based on the libfcgi solution, this can be used with Apache, IIS, nginx, ... -* __nino__: a standalone server: Eiffel Web Nino allow you to embed a web server anywhere, on any platform without any dependencies on other httpd server. - -At compilation time, you can use a default connector (by using the associated default lib), but you can also use a mixed of them and choose which one to execute at runtime. -It is fairly easy to add new connector, it just has to follow the EWSGI interface - -> [Learn more about connector](Connector.md) - - - -# Router or Request Dispatcher: -> Routes HTTP requests to the proper execution code - -A web application needs to have a clean and elegant URL scheme, and EWF provides a router component to design URLs. - -The association between a URL pattern and the code handling the URL request is called a Router mapping in EWF. - -EWF provides 3 main kinds of mappings -+ __URI__: any URL with path being the specified uri. - - example: “/users/” redirects any “/users/” and “/users/?query=...” -+ __URI-template__: any URL matching the specified URI-template - - example: “/project/{name}/” redirects any “/project/foo” or “/project/bar” -+ __Starts-with__: any URL starting with the specified path - -Note: in the future, a Regular-Expression based kind will be added in the future, and it is possible to use custom mapping on top of EWF. - -Code: - - router.map ( create {WSF_URI_TEMPLATE_MAPPING}.make ( - “/project/{name}”, project_handler) - ) - -- And precising the request methods - router.map_with_request_methods ( ... , router.methods_GET_POST) - -In the previous code, the `project_handler` is an object conforming to **WSF_HANDLER**, that will process the incoming requests matching URI-template “/project/{name}”. - -Usually, the service will inherit from WSF_ROUTED_SERVICE, which has a `router` attribute. -Configuring the URL scheme is done by implementing `{WSF_ROUTED_SERVICE}.setup_router`. - -To make life easier, by inheriting from WSF_URI_TEMPLATE_HELPER_FOR_ROUTED_SERVICE, a few help methods are available to `map` URI template with agent, and so on. -See -+ `map_uri_template (a_tpl: STRING; h: WSF_URI_TEMPLATE_HANDLER)` -+ `map_uri_template_agent (a_tpl: READABLE_STRING_8; proc: PROCEDURE [ANY, TUPLE [req: WSF_REQUEST; res: WSF_RESPONSE]])` -+ and same with request methods ... - -... - -Check WSF_\*_HELPER_FOR_ROUTED_SERVICE for other available helper classes. - -How we do that in EWF? : Router with (or without context). -Related code: wsf_router, wsf_router_context -Examples - -> [Learn more about router](Router.md) - -# EWF components -## URI Handler: -> Parses the details of the URI (scheme, path, query info, etc.) and exposes them for use. - -How we do that in EWF?: URI Templates, but we could also use regex. -Related code: uri_template -Examples: - - -## Mime Parser/ Content Negotiation: -> Handles the details of determining the media type, language, encoding, compression (conneg). - -How do we do that in EWF? Content_Negotiation library. -Example - - -## Request Handler -> target of request dispatcher + uri handler. - -Here is where we handle GET, POST PUT, etc. - -## Representation Mapping -> Converts stored data into the proper representation for responses and handles incoming representations from requests. - -We don’t have a representation library, the developer need to do that. -If we want to provide different kind of representations: JSON, XML, HTML, the responsibility is let -to the developer to map their domain to the target representation. - -## Http Client: -> A simple library to make requests and handle responses from other http servers. - -How we do that in EWF? http client library -examples: - -## Authentication/Security: -> Handle different auth models. (Basic, Digest?, OAuth, OpenId) - -How we do that in EWF? http_authorization, OpenId, and Cypress -examples. - -## Caching: -> Support for Caching and conditional request - -How we do that in Eiffel? Policy framework on top of EWF. {{{need_review}}} -examples - - -## EWF HTML5 Widgets - -## EWF policy Framework - -## EWF application generators - - - - -# EWSGI Specification - - - -# Libraries - -External libraries are included, such as Cypress OAuth (Security), HTML parsing library, Template Engine Smarty. - -## server -* __ewsgi__: Eiffel Web Server Gateway Interface [read more](../EWSGI/index.md). - * connectors: various web server connectors for EWSGI -* __libfcgi__: Wrapper for libfcgi SDK -* __wsf__: Web Server Framework - * __router__: URL dispatching/routing based on uri, uri_template, or custom [read more](Router.md). - * __filter__: Filter chain [read more](Filter.md). - * __wsf_html__: (html and css) Content generator from the server side. - * CMS example: - -## protocol -* __http__: HTTP related classes, constants for status code, content types, ... -* __uri_template__: URI Template library (parsing and expander) -* __content_negotiation__: [CONNEG](Library-conneg.md) library (Content-type Negociation) - -## Client -* __http_client__: simple [HTTP client](HTTP-client.library.md) based on cURL -* __Firebase API__: - -## Text -* __encoder__: Various simple encoders: base64, url-encoder, xml entities, html entities - -## Utils -* __error__: very simple/basic library to handle error - -## Security -* __http_authentication__ (under EWF/library/server/authentication) -* __open_id__ (under EWF/library/security) -* __OAuth__ see diff --git a/wiki/documentation/Request-and-response.md b/wiki/documentation/Request-and-response.md deleted file mode 100644 index a8cef6e9..00000000 --- a/wiki/documentation/Request-and-response.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: default -title: Request and response -base_url: ../../ ---- -# Request -The class _WSF_REQUEST_ can be used to access data related to the HTTP request. - -**TODO**: describe the request interface - -# Response -The class _WSF_RESPONSE_ is the media to send data back to the client. - -**TODO**: describe the response interface \ No newline at end of file diff --git a/wiki/documentation/Request.md b/wiki/documentation/Request.md deleted file mode 100644 index 47177d5c..00000000 --- a/wiki/documentation/Request.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -layout: default -title: Request -base_url: ../../ ---- -See WSF_REQUEST - -## About parameters -Note that by default there is a smart computation for the query/post/... parameters: -for instance -- `q=a&q=b` : will create a **WSF_MULTIPLE_STRING** parameter with name **q** and value `[a,b]` -- `tab[a]=ewf&tab[b]=demo` : will create a **WSF_TABLE** parameter with name **tab** and value `{ "a": "ewf", "b": "demo"}` -- `tab[]=ewf&tab[]=demo` : will create a **WSF_TABLE** parameter with name **tab** and value `{ "1": "ewf", "2": "demo"}` -- `tab[foo]=foo&tab[foo]=bar` : will create a **WSF_TABLE** parameter with name **tab** and value `{ "foo": "bar"}` **WARNING: only the last `tab[foo]` is kept**. - -Those rules are applied to query, post, path, .... parameters. - -## How to get the input data (i.e entity-body) ? -See `{WSF_REQUEST}.read_input_data_into (buf: STRING)` - -## How to get the raw header data (i.e the http header text) ? -See `{WSF_REQUEST}.raw_header_data: detachable READABLE_STRING_32` diff --git a/wiki/documentation/Response.md b/wiki/documentation/Response.md deleted file mode 100644 index 4a721149..00000000 --- a/wiki/documentation/Response.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: default -title: Response -base_url: ../../ ---- -See WSF_RESPONSE \ No newline at end of file diff --git a/wiki/documentation/Router.md b/wiki/documentation/Router.md deleted file mode 100644 index bba768ac..00000000 --- a/wiki/documentation/Router.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -layout: default -title: Router -base_url: ../../ ---- -The primary goal of the router (class _WSF_ROUTER_) is to dispatch requests according to the request URI. - -See WSF_ROUTER - -**TODO**: describe the router interface - diff --git a/wiki/documentation/Service.md b/wiki/documentation/Service.md deleted file mode 100644 index 3329a94a..00000000 --- a/wiki/documentation/Service.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: default -title: Service -base_url: ../../ ---- -EWF Services -> See WSF\_SERVICE \ No newline at end of file diff --git a/wiki/policy-driven-framework/Using-the-policy-driven-framework.md b/wiki/policy-driven-framework/Using-the-policy-driven-framework.md deleted file mode 100644 index 382083f5..00000000 --- a/wiki/policy-driven-framework/Using-the-policy-driven-framework.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -layout: default -title: Using the policy driven framework -base_url: ../../ ---- -# Using the policy driven framework - -## Introduction - -The aim of the policy-driven framework is to allow authors of web-servers to concentrate on the business logic (e.g., in the case of a GET request, generating the content), without having to worry about the details of the HTTP protocol (such as headers and response codes). However, there are so many possibilities in the HTTP protocol, that it is impossible to correctly guess what to do in all cases. Therefore the author has to supply policy decisions to the framework, in areas such as caching decisions. These are implemented as a set of deferred classes for which the author needs to provide effective implementations. - -We aim to provide unconditional compliance [See HTTP/1.1 specification](http://www.w3.org/Protocols/rfc2616/rfc2616-sec1.html#sec1) for you. Note that byte-ranges are not yet supported. - -## Mapping the URI space - -The authors first task is to decide which URIs the server will respond to (we do this using [URI templates](http://tools.ietf.org/html/rfc6570) ) and which methods are supported for each template.This is done in the class that that defines the service (which is often the root class for the application). This class must be a descendant of WSF_ROUTED_SKELETON_SERVICE. Throughout this tutorial, we will refer to the restbucksCRUD example application, which can be found in the EWF distribution in the examples directory. It's root class, RESTBUCKS_SERVER, inherits from WSF_ROUTED_SKELETON_SERVICE, as well as WSF_DEFAULT_SERVICE. The latter class means that you must specify in the ECF which connector you will use by default.This means you can easily change connectors just by changing the ECF and recompiling. - -### Declaring your URI templates - -In order to map your URI space to handlers (which you will write), you need to implement the routine setup_router. You can see in the example that the ORDER_HANDLER handler is associated with two URI templates. The URI /order is associated with the POST method (only). Any requests to /order with the GET method (or any other method) will result in an automatically generated compliant response being sent on your behalf to the client. The other principle methods (you get compliant responses to the HEAD method for free whenever you allow the GET method) are associated with the URI template /order/{orderid}. Here, orderid is a template variable. It's value for any given request is provided to your application as {WSF_REQUEST}.path_parameter ("orderid"). If the client passes a URI of /order/21, then you will see the value 21. If the client passes /order/fred, you will see the value fred. But if the client passes /order/21/new, he will see a compliant error response generated by the framework. - -## Declaring your policy in responding to OPTIONS - -WSF_ROUTED_SKELETON_SERVICE inherits from WSF_SYSTEM_OPTIONS_ACCESS_POLICY. This policy declares that the framework will provide a compliant default response to OPTIONS * requests. If you prefer to not respond to OPTIONS * requests (and I am doubtful if it is fully compliant to make that choice), then you can redefine -is_system_options_forbidden. - -## Declaring your policy on requiring use of a proxy server - -WSF_ROUTED_SKELETON_SERVICE also inherits from WSF_PROXY_USE_POLICY. This determines if the server will require clients to use a proxy server. By default, it will do so for HTTP/1.0 clients. This is a sensible default, as the framework assumes an HTTP/1.1 client throughout. If you are sure that you will only ever have HTTP/1.1 clients, then you can instead inherit from WSF_NO_PROXY_POLICY, as RESTBUCKS_SERVER does. If not, then you need to implement proxy_server. - -Next you have to [write your handler(s)](Writing-the-handlers.md) diff --git a/wiki/policy-driven-framework/WSF_OPTIONS_POLICY.md b/wiki/policy-driven-framework/WSF_OPTIONS_POLICY.md deleted file mode 100644 index 6082379c..00000000 --- a/wiki/policy-driven-framework/WSF_OPTIONS_POLICY.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: default -title: WSF OPTIONS POLICY -base_url: ../../ ---- -# Implementing routines in WSF_OPTIONS_POLICY - -This class provides a default response to OPTIONS requests other than OPTIONS *. So you don't have to do anything. The default response just includes the mandatory Allow headers for all the methods that are allowed for the request URI. if you want to include a body text, or additional header, then you should redefine this routine. \ No newline at end of file diff --git a/wiki/policy-driven-framework/Writing-the-handlers.md b/wiki/policy-driven-framework/Writing-the-handlers.md deleted file mode 100644 index f39cd528..00000000 --- a/wiki/policy-driven-framework/Writing-the-handlers.md +++ /dev/null @@ -1,228 +0,0 @@ ---- -layout: default -title: Writing the handlers -base_url: ../../ ---- -# Writing the handlers - -Now you have to implement each handler. You need to inherit from WSF_SKELETON_HANDLER (as ORDER_HANDLER does). This involves implementing a lot of deferred routines. There are other routines for which default implementations are provided, which you might want to override. This applies to both routines defined in this class, and those declared in the three policy classes from which it inherits. - -## Communicating between routines - -Depending upon the connector (Nino, CGI, FastCGI etc.) that you are using, your handler may be invoked concurrently for multiple requests. Therefore it is unsafe to save state in normal attributes. WSF_REQUEST has a pair of getter/setter routines, execution_variable/set_execution_variable, which you can use for this purpose. -Internally, the framework uses the following execution variable names, so you must avoid them: - -1. REQUEST_ENTITY -1. NEGOTIATED_LANGUAGE -1. NEGOTIATED_CHARSET -1. NEGOTIATED_MEDIA_TYPE -1. NEGOTIATED_ENCODING -1. NEGOTIATED_HTTP_HEADER -1. CONFLICT_CHECK_CODE -1. CONTENT_CHECK_CODE -1. REQUEST_CHECK_CODE - -The first one makes the request entity from a PULL or POST request available to your routines. - -The next four make the results of content negotiation available to your routines. The sixth one makes an HTTP_HEADER available to your routines. You should use this rather than create your own, as it may contain a **Vary** header as a by-product of content negotiation. -The last three are for reporting the result from check_conflict, check_content and check_request. - -All names are defined as constants in WSF_SKELETON_HANDLER, to make it easier for you to refer to them. - -## Implementing the routines declared directly in WSF_SKELETON_HANDLER - -### check_resource_exists - -Here you check for the existence of the resource named by the request URI. If it does, then you need to call set_resource_exists on the helper argument. -Note that if you support multiple representations through content negotiation, then etags are dependent upon -the selected variant. If you support etags, then you will need to make the response entity available at this point, rather than in ensure_content_available. - -### is_chunking - -HTTP/1.1 supports streaming responses (and providing you have configured your server to use a proxy server in WSF_PROXY_USE_POLICY, this framework guarantees you have an HTTP/1.1 client to deal with). It is up to you whether or not you choose to make use of it. If so, then you have to serve the response one chunk at a time (but you could generate it all at once, and slice it up as you go). In this routine you just say whether or not you will be doing this. So the framework n=knows which other routines to call. -Currently we only support chunking for GET or HEAD routines. This might change in the future, so if you intend to return True, you should call req.is_get_head_request_method. -Note that currently this framework does not support writing a trailer. - -### includes_response_entity - -The response to a DELETE, PUT or POST will include HTTP headers. It may or may not include a body. It is up to you, and this is where you tell the framework. - -### conneg - -[The HTTP/1.1 specification](http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.1) defines server-driven content negotiation. Based on the Accept* headers in the request, we can determine whether we have a format for the response entity that is acceptable to the client. You need to indicate what formats you support. The framework does the rest. Normally you will have the same options for all requests, in which case you can use a once object. - -### mime_types_supported - -Here you need to indicate which media types you support for responses. One of the entries must be passed to the creation routine for conneg. - -### languages_supported - -Here you need to indicate which languages you support for responses. One of the entries must be passed to the creation routine for conneg. - - -### charsets_supported - -Here you need to indicate which character sets you support for responses. One of the entries must be passed to the creation routine for conneg. - - -### encodings_supported - -Here you need to indicate which compression encodings you support for responses. One of the entries must be passed to the creation routine for conneg. - -### additional_variant_headers - -The framework will write a Vary header if conneg indicates that different formats are supported. This warns caches that they may not be able to use a cached response if the Accept* headers in the request differ. If the author knows that the response may be affected by other request headers in addition to these, then they must be indicated here, so they can be included in a Vary header with the response. - -### predictable_response - -If the response may vary in other ways not predictable from the request headers, then redefine this routine to return True. In that case we will generate a Vary: * header to inform the cache that the response is not necessarily repeatable. - -### matching_etag - -An **ETag** header is a kind of message digest. Clients can use etags to avoid re-fetching responses for unchanged resources, or to avoid updating a resource that may have changed since the client last updated it. -You must implement this routine to test for matches **if and only if** you return non-Void responses for the etag routine. -Note that if you support multiple representations through content negotiation, then etags are dependent upon -the selected variant. Therefore you will need to have the response entity available for this routine. This can be done in check_resource_exists. - -### etag - -You are strongly encouraged to return non-Void for this routine. See [Validation Model](http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3) for more details. -Note that if you support multiple representations through content negotiation, then etags are dependent upon -the selected variant. Therefore you will need to have the response entity available for this routine. This can be done in check_resource_exists. - -### modified_since - -You need to implement this. If you do not have information about when a resource was last modified, then return True as a precaution. Of course, you return false for a static resource. - -### treat_as_moved_permanently - -This routine when a PUT request is made to a resource that does not exist. See [PUT](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6) in the HTTP/1.1 specification for why you might want to return zero. - -### allow_post_to_missing_resource - -POST requests are normally made to an existing entity. However it is possible to create new resources using a POST, if the server allows it. This is where you make that decision. - -If you return True, and the resource is created, a 201 Created response will be returned. - -### content_length - -If you are not streaming the result, the the HTTP protocol requires that the length of the entity is known. You need to implement this routine to provide that information. - -### finished - -If you are streaming the response, then you need to tell the framework when the last chunk has been sent. -To implement this routine, you will probably need to call req.set_execution_variable (some-name, True) in ensure_content_avaiable and generate_next_chunk, and call attached {BOOLEAN} req.execution_variable (some-name) in this routine. - -### description - -This is for the automatically generated documentation that the framework will generate in response to a request that you have not mapped into an handler. - -### delete - -This routine is for carrying out a DELETE request to a resource. If it is valid to delete the named resource, then you should either go ahead and do it, or queue a deletion request somewhere (if you do that then you will probably need to call req.set_execution_variable (some-name-or-other, True). Otherwise you should call req.error_handler.add_custom_error to explain why the DELETE could not proceed (you should also do this if the attempt to delete the resource fails). -Of course, if you have not mapped any DELETE requests to the URI space of this handler, then you can just do nothing. - -### delete_queued - -If in the delete routine, you elected to queue the request, then you need to return True here. You will probably need to check the execution variable you set in the delete routine. - -### ensure_content_available - -This routine is called for GET and DELETE (when a entity is provided in the response) processing. It's purpose is to make the text of the entity (body of the response) available for future routines (if is_chunking is true, then only the first chunk needs to be made available, although if you only serve, as opposed to generate, the result in chunks, then you will make the entire entity available here). This is necessary so that we can compute the length before we start to serve the response. You would normally save it in an execution variable on the request object (as ORDER_HANDLER does). Note that this usage of execution variables ensures your routines can successfully cope with simultaneous requests. If you encounter a problem generating the content, then add an error to req.error_handler. - -As well as the request object, we provide the results of content negotiation, so you can generate the entity in the agreed format. If you only support one format (i.e. all of mime_types_supported, charsets_supported, encodings_supported and languages_supported are one-element lists), then you are guaranteed that this is what you are being asked for, and so you can ignore them. - -Note that if you support multiple representations through content negotiation, then etags are dependent upon -the selected variant. Therefore you will need to have the response entity available for this routine. In such cases, this will have to be done in check_resource_exists, rather than here, as this routine is called later on. - -### content - -When not streaming, this routine provides the entity to the framework (for GET or DELETE). Normally you would just access the execution variable that you set in ensure_content_available. Again, the results of content negotiation are made available, but you probably don't need them at this stage. If you only stream responses (for GET), and if you don't support DELETE, then you don't need to do anything here. - -### generate_next_chunk - -When streaming the response, this routine is called to enable you to generate chunks beyond the first, so that you can incrementally generate the response entity. If you generated the entire response entity in -ensure_content_available, then you do nothing here. Otherwise, you will generate the next chunk, and save it in the same execution variable that you use in ensure_content_available (or add an error to req.error_handler). If you don't support streaming, then you don't need to do anything here. - -### next_chunk - -When streaming the response, the framework calls this routine to provides the contents of each generated chunk. If you generated the entire response entity in ensure_content_available, then you need to slice it in this routine (you will have to keep track of where you are with execution variables). If instead you generate the response incrementally, then your task is much easier - you just access the execution variable saved in ensure_content_available/generate_next_chunk. -As in all these content-serving routines, we provide the results of content negotiation. This might be necessary, for instance, if you were compressing an incrementally generated response (it might be more convenient to do the compression here rather than in both ensure_content_available and generate_next_chunk). - -### read_entity - -This is called for PUT and POST processing, to read the entity provided in the request. A default implementation is provided. This assumes that no decoding (e.g. decompression or character set conversion) is necessary. And it saves it in the execution variable REQUEST_ENTITY. - -Currently the framework provides very little support for PUT and POST requests (so you may well need to redefine this routine). There are several reasons for this: - -1. I personally don't have much experience with PUT and POST. -1. It has taken a long time to develop this framework, and to some extent I was working in the dark (I couldn't check what I was doing until the entire framework was written - it wouldn't even compile before then). -1. The idea for the framework came from a code review process on servers I had written for the company that I work for. I had acquired a lot of knowledge of the HTTP protocol in the process, and some of it showed in the code that I had written. It was thought that it would be a good idea if this knowledge were encapsulated in Eiffel, so other developers would be able to write servers without such knowledge. So this framework has been developed in company time. However, at present, we are only using GET requests. - -Experience with converting the restbucksCRUD example to use the framework, shows that it is certainly possible to do POST and PUT processing with it. But enhancements are needed, especially in the area of decoding the request entity. - -### is_entity_too_large - -If your application has limits on the size of entities that it can store, then you implement them here. - -### check_content_headers - -This is called after is_entity_too_large returns False. You are supposed to check the following request headers, and take any appropriate actions (such as setting an error, decompression the entity, or converting it to a different character set): - -* Content-Encoding -* Content-Language -* Content-MD5 -* Content-Range -* Content-Type - -At the moment, your duty is to set the execution variable CONTENT_CHECK_CODE to zero, or an HTTP error status code. A future enhancement of the framework might be to provide more support for this. - -### content_check_code - -This simply accesses the execution variable CONTENT_CHECK_CODE set in check_content_headers. if you want to use some other mechanism, then you can redefine this routine. - -### create_resource - -This routine is called when a PUT request is made with a URI that refers to a resource that does not exist (PUT is normally used for updating an existing resource), and you have already decided to allow this. -In this routine you have the responsibilities of: - -1. Creating the resource using the entity in REQUEST_ENTITY (or some decoded version that you have stored elsewhere). -1. Writing the entire response yourself (as I said before, support for PUT and POST processing is poor at present), including setting the status code of 201 Created or 303 See Other or 500 Internal server error). - -### append_resource - -This routine is called for POST requests on an existing resource (normal usage). - -In this routine you have the responsibilities of: - -1. Storing the entity from REQUEST_ENTITY (or some decoded version that you have stored elsewhere), or whatever other action is appropriate for the semantics of POST requests to this URI. -1. Writing the entire response yourself (as I said before, support for PUT and POST processing is poor at present), including setting the status code of 200 OK, 204 No Content, 303 See Other or 500 Internal server error). - -### check_conflict - -This is called for a normal (updating) PUT request. You have to check to see if the current state of the resource makes updating impossible. If so, then you need to write the entire response with a status code of 409 Conflict, and set the execution variable CONFLICT_CHECK_CODE to 409. -Otherwise you just set the execution variable CONFLICT_CHECK_CODE to 0. - -See [the HTTP/1.1 specification](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.10) for when you are allowed to use the 409 response, and what to write in the response entity. If this is not appropriate then a 500 Internal server error would be more appropriate (and set CONFLICT_CHECK_CODE to 500 - the framework only tests for non-zero). - -### conflict_check_code - -This is implemented to check CONFLICT_CHECK_CODE from the previous routine. If you choose to use a different mechanism, then you need to redefine this. - -### check_request - -This is called for PUT and POST requests. You need to check that the request entity (available in the execution variable REQUEST_ENTITY) is valid for the semantics of the request URI. You should set the execution variable REQUEST_CHECK_CODE to 0 if it is OK. If not, set it to 400 and write the full response, including a status code of 400 Bad Request. - -### request_check_code - -This routine just checks REQUEST_CHECK_CODE. if you choose to use a different mechanism, then redefine it. - -### update_resource - -This routine is called for a normal (updating) PUT request. You have to update the state of the resource using the entity saved in the execution environment variable REQUEST_ENTITY (or more likely elsewhere - see what ORDER_HANDLER does). Then write the entire response including a status code of 204 No Content or 500 Internal server error. - -## Implementing the policies - -* [WSF_OPTIONS_POLICY](WSF_OPTIONS_POLICY.md) -* [WSF_PREVIOUS_POLICY](Wsf-previous-policy.md) -* [WSF_CACHING_POLICY](Wsf-caching-policy.md) diff --git a/wiki/policy-driven-framework/Wsf-caching-policy.md b/wiki/policy-driven-framework/Wsf-caching-policy.md deleted file mode 100644 index 122f7078..00000000 --- a/wiki/policy-driven-framework/Wsf-caching-policy.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -layout: default -title: Wsf caching policy -base_url: ../../ ---- -# Implementing WSF_CACHING_POLICY - -This class contains a large number of routines, some of which have sensible defaults. - -## age - -This is used to generate a **Cache-Control: max-age** header. It says how old the response can before a cache will consider it stale (and therefore will need to revalidate with the server). Common values are zero (always consider it stale) and Never_expires (never always mean up to one year) and 1440 (one day). - -## shared_age - -This defaults to the same as age, so you only have to redefine it if you want a different value. If different from age, then we generate a **Cache-Control: s-max-age** header. This applies to shared caches only. Otherwise it has the same meaning as age. This overrides the value specified in age for shared caches. - -## http_1_0_age - -This generates an **Expires** header, and has the same meaning as age, but is understood by HTTP/1.0 caches. By default it has the same value as age. You only need to redefine this if you want to treat HTTP/1.0 caches differently (you might not trust them so well, so you might want to return 0 here). - -## is_freely_cacheable - -This routine says whether a shared cache can use this response for all client. If True, then it generates a **Cache-Control: public** header. If your data is at all sensitive, then you want to return False here. - -## is_transformable - -Non-transparent proxies are allowed to make some modifications to headers. If your application relies on this _not_ happening, then you want to return False here. This is the default, so you don't have to do anything. This means a **Cache-Control: no-transform** header will be generated. -But most applications can return True. - -## must_revalidate - -Some clients request that their private cache ignores server expiry times (and so freely reuse stale responses). If you want to force revalidation anyway in such circumstances, then redefine to return True. In which case, we generate a **Cache-Control: must-revalidate** header. - -## must_proxy_revalidate - -This is the same as must_revalidate, but only applies to shared caches that are configured to serve stale responses. If you redefine to return True, then we generate a **Cache-Control: proxy-revalidate** header. - -## private_headers - -This is used to indicate that parts (or all) of a response are considered private to a single user, and should not be freely served from a shared cache. You must implement this routine. Your choices are: - -1. Return Void. None of the response is considered private. -1. Return and empty list. All of the response is considered private. -1. Return a list of header names. - -If you don't return Void, then a **Cache-Control: private** header will be generated. - -## non_cacheable_headers - -This is similar to private_headers, and you have the same three choices. the difference is that it is a list of headers (or the whole response) that will not be sent from a cache without revalidation. - -If you don't return Void, then a **Cache-Control: no-cache** header will be generated. - -## is_sensitive - -Is the response to be considered of a sensitive nature? If so, then it will not be archived from a cache. We generate a **Cache-Control: no-store** header. \ No newline at end of file diff --git a/wiki/policy-driven-framework/Wsf-previous-policy.md b/wiki/policy-driven-framework/Wsf-previous-policy.md deleted file mode 100644 index ce63a127..00000000 --- a/wiki/policy-driven-framework/Wsf-previous-policy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -layout: default -title: Wsf previous policy -base_url: ../../ ---- -# WSF_PREVIOUS_POLICY - -This class deals with resources that have moved or gone. The default assumes no such resources. It exists as a separate class, rather than have the routines directly in WSF_SKELETON_HANDLER, as sub-classing it may be convenient for an organisation. - -## resource_previously_existed - -Redefining this routine is always necessary if you want to deal with any previous resources. - -## resource_moved_permanently - -Redefine this routine for any resources that have permanently changed location. The framework will generate a 301 Moved Permanently response, and the user agent will automatically redirect the request to (one of) the new location(s) you provide. The user agent will use the new URI for future requests. - -## resource_moved_temporarily - -This is for resource that have only been moved for a short period. The framework will generate a 302 Found response. The only substantial difference between this and resource_moved_permanently, is that the agent will use the old URI for future requests. - -## previous_location - -When you redefine resource_moved_permanently or resource_moved_temporarily, the framework will generate a Location header for the new URI, and a hypertext document to the new URI(s). You **must** redefine this routine to provide those locations (the first one you provide will be in the location header). \ No newline at end of file diff --git a/wiki/project/Meetings.md b/wiki/project/Meetings.md deleted file mode 100644 index 94ec16aa..00000000 --- a/wiki/project/Meetings.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: default -title: Meetings -base_url: ../../ ---- -# Previous and future meetings - -* [Web-meeting: 2012-09-18](meetings/Web-meeting-2012-09-18.md) -* For previous meetings, check the ["meeting" topics](https://groups.google.com/forum/?fromgroups=#!tags/eiffel-web-framework/meeting) on the [forum](http://groups.google.com/group/eiffel-web-framework) diff --git a/wiki/project/Projects-new-suggestions.md b/wiki/project/Projects-new-suggestions.md deleted file mode 100644 index fab96ece..00000000 --- a/wiki/project/Projects-new-suggestions.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -layout: default -title: Projects new suggestions -base_url: ../../ ---- -Use this to suggest new projects, or request features. -The content of this page will be moved to the main [Projects](Projects.md) page for time to time. -For any entry, please use this template - ----- - -## Short title -* _Suggested by **your name**_": so that we know who suggested a feature. -* **Requirement**: ... if any, otherwise remove this line -* _Description_: ... a few lines to describe the project -* _References_: ... if any, otherwise remove this line - ----- - -## Add support for Swagger -* _Suggested by **Olivier**_ -* _Description_: Build a Swagger Eiffel implementation -* _References_: [http://swagger.wordnik.com/](http://swagger.wordnik.com/) - ----- diff --git a/wiki/project/Projects.md b/wiki/project/Projects.md deleted file mode 100644 index 59035603..00000000 --- a/wiki/project/Projects.md +++ /dev/null @@ -1,244 +0,0 @@ ---- -layout: default -title: Projects -base_url: ../../ ---- -This page lists potential projects on EWF, this is open for contribution. -If you are a student, don't hesitate to pick one, or even suggest a new project, or a project being a merge of several, in any case, you will get close support from EWF's team. - ----- -# Study/Analysis/Documentation - -## Evaluate EWF according to the following constraints ... -* _Suggested by **Javier**_ -* _Description_: According to [http://www.amundsen.com/blog/archives/1130](http://www.amundsen.com/blog/archives/1130) , evaluate the current design of EWF to see if this match the different points. An other option would be to take the following REST implementation toolkit as a guide to evaluate EWF [http://code.google.com/p/implementing-rest/wiki/RESTImplementationToolkit](http://code.google.com/p/implementing-rest/wiki/RESTImplementationToolkit). - -## Road to Hypermedia API -* _Suggested by **Javier**_ -* _Supervisor_: -* _Suitability_: -* _Description_: describe differents types of Web API, and how you can build them using EWF. Describing Pros and Cons. This should be on [http://martinfowler.com/articles/richardsonMaturityModel.html](http://martinfowler.com/articles/richardsonMaturityModel.html) - -## Build a video to demonstrate how an Hypermedia API works, and how to build it using EWF -* _Suggested by **Javier**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: produce a audio+video+slide that demonstrates how to build an hypermedia API using EWF. This could be based on upcoming "graphviz server" example, or an extension of existing RestBucksCRUD example, or any new example. - ----- -# Works related to EWF / framework / tools - -## Improve EWF -* _Suggested by **Jocelyn**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: Improve existing EWF source, this is a permanent task for EWF, and this can be code, documentation, tests, ... Among others , here is a list of needed effort: -** Improve encoding support -*∗ Better MIME handler -** _Support for configuration _ -** Ready to use logging facilities -** Smart handler for HEAD or similar -** Adding component to ease the caching functionalities -** Adding Session support -** URL rewriting ? -** Mass testing -** ... - -## Eiffel Web Nino -* _Suggested by **Javier & Jocelyn**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: Currently Eiffel Web Nino, is a standalone httpd server written in Eiffel. It is great for development, or embedding httpd component in application. However there are room for improvement so that one can also use it as replacement for apache, iis, ... To reach this state, here are a list of task that should be achieved: -** Implement persistent connection -** Complete implementation of Eiffel Web Nino using pool of threads -** Complete migration of Eiffel Web Nino to SCOOP -** Improve Nino to become a real solution to host any web services/sites -** ... - -## New EWF connectors -* _Suggested by **Jocelyn & Javier**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: EWF is relying on the notion of "connector" to achieve portability on various platform and underlying httpd server, currently EWF support any CGI or libFCGI system (i.e apache, IIS, ...), and provide a standalone version thanks to Eiffel Web Nino. The goal now, would be to support specific connector for: -** LightHTTP ([http://www.lighttpd.net/](http://www.lighttpd.net/)) -** nginx ([http://nginx.org/en/](http://nginx.org/en/)) - -## Concurrenty and EWF -* _Suggested by **Jocelyn**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: Check that EWF is compliant with concurrency (Eiffel Thread, and SCOOP), and provide an example using concurrency. - -## Design and build something like Ruby on Rails or Grails -* _Suggested by **Javier**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: Using EWF, design and build the set of tools to provide a conventional MVC to create Web sites. This could be useful even if this is not the taste of everyone. - -## Provide a Websocket implementation -* _Suggested by **Jocelyn**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: Provide an implementation of websocket with EWF and eventually Eiffel Web Nino, then demonstrate it on a simple example. WebSocket is a web technology providing for bi-directional, full-duplex communications channels over a single TCP connection. -* See [http://en.wikipedia.org/wiki/Websocket](http://en.wikipedia.org/wiki/Websocket) - ----- -# Usage of EWF - -## HAL browser -* _Suggested by **Javier**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: Build a HAL browser to discover an API using HAL mediatype. The browser will be able to follow the links, and display the transmitted data. This could be a vision2 application inspired by [http://haltalk.herokuapp.com/explorer/hal_browser.html#/](http://haltalk.herokuapp.com/explorer/hal_browser.html#/). HAL stands for Hypertext Application Language see [http://stateless.co/hal_specification.html](http://stateless.co/hal_specification.html). - -## Collection-JSON browser -* _Suggested by **Javier**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: Build a Collection/JSON browser to discover an API using Collection/JSON mediatype. The browser will be able to follow the links, and display the transmitted data. This could be a vision2 application inspired by [http://haltalk.herokuapp.com/explorer/hal_browser.html#/](http://haltalk.herokuapp.com/explorer/hal_browser.html#/). Collection+JSON is a JSON-based read/write hypermedia-type, see [http://www.amundsen.com/media-types/collection/](http://www.amundsen.com/media-types/collection/) - -## Build a simple CMS with EWF -* _Suggested by **Jocelyn**_ -* _Supervisor_: -* _Suitability_: TODO -* _Status_: started, and open for contribution, collaboration, please contact Jocelyn. -* _Description_: Using EWF, Build a simple CMS (Content Management System) framework and then an example. It should provide common features such as: - - user management (register, login, lost password -> send email) - - page editing - - blog - - template / theme - - persistency / storage / ... - - extension at compilation time -* The result should be usable by any user to build his own CMS website, and extend it easily. - -## Build P2P connector -* _Suggested by **Jocelyn**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: Imagine you want to publish a website (or web service, API) running on your machine (behind firewall). One would need to initiate the connection via a public website, this is common for P2P software such as remote assistance (i.e: join.me, teamviewer, showmypc, ...) - ----- -# Libraries - -## Hypermedia API library to work with XHTML -* _Suggested by **Javier**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: Use XHTML as a media type to for hypermedia API. See [http://codeartisan.blogspot.com.ar/2012/07/using-html-as-media-type-for-your-api.html](http://codeartisan.blogspot.com.ar/2012/07/using-html-as-media-type-for-your-api.html) - -## Add support for Mediatype such as RSS, ATOM, ... -* _Suggested by **Jocelyn**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: In addition to JSON, HAL, Collection+JSON, XHTML, application might want to support (read and write) standard media type such as RSS, ATOM, ... - -## Security: provide popular authentication mechanisms -* _Suggested by **Jocelyn**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: Any web service, web site, API need a reliable authentication mechanism, the could be on the server side or the client side to build mashup service (integrate with other web API such as google, flicker, ...). So far, EWF provides only basic HTTP Authorization, and application would need more solutions such as : - - OAuth: consumer and provider - - OpenID - - Google Connect - - Facebook Connect -* The goal is to provide component to consume other popular API/service, but also component for your own service so that other can consume it. - -## Security: provide popular authentication mechanisms -* _Suggested by **Jocelyn**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: Any web service, web site, API need a reliable authentication mechanism, the could be on the server side or the client side to build mashup service (integrate with other web API such as google, flicker, ...). So far, EWF provides only basic HTTP Authorization, and application would need more solutions such as : - - OAuth: consumer and provider - - OpenID - - Google Connect - - Facebook Connect -* The goal is to provide component to consume other popular API/service, but also component for your own service so that other can consume it. - -## Provide a SSO (Single Sign On) implementation (server, and clients) -* _Suggested by **Jocelyn**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: Design and build a Single Sign On implementation for Eiffel. That should include the authentication server, and at least one Eiffel client component (it would be convenient to also provide php, js, ...). In the same spirit, having Eiffel client for popular SSO server would be appreciated as well. -* _Reference_: - - [http://en.wikipedia.org/wiki/Single_sign-on](http://en.wikipedia.org/wiki/Single_sign-on) - - [http://en.wikipedia.org/wiki/List_of_single_sign-on_implementations](http://en.wikipedia.org/wiki/List_of_single_sign-on_implementations) - -## library: Template engine -* _Suggested by **Jocelyn**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: Get inspired by any existing template engine, and build one for Eiffel, this should be easily usable within a web application. This could be inspired, or implementation of standard template engine, this way people can reuse existing content, or migrate easily their application to EWF. For inspiration, one can look at: - - [http://www.smarty.net/](http://www.smarty.net/) - - [http://mustache.github.com/](http://mustache.github.com/) - - [http://en.wikipedia.org/wiki/Web_template_system](http://en.wikipedia.org/wiki/Web_template_system) ... they are plenty of them, a comparison of the different engine would help. -* This is not specific to EWF, but it will be very useful in website context. - -## library: Wikitext, markdown parser and render engine -* _Suggested by **Jocelyn**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: Build component to support (read and write, and why not convert), lightweight markup language (see [http://en.wikipedia.org/wiki/Lightweight_markup_language](http://en.wikipedia.org/wiki/Lightweight_markup_language)) such as wikitext, markdown, and other. The component should be able to read/scan, but also produce an HTML output. Focus first on wikitext, and markdown since they seems to be the most popular. -* Then , a nice addition would be to render those lightweight markup lang into Vision2 widget (not related to EWF, but could be useful to build (editor) desktop application) - -## library: Web component to build HTML5 widget -* _Suggested by **Jocelyn**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: Build set of Eiffel components to ease development of websites. First this should be based on HTML5. Idea for components: - - table widget (with sorting ...) - - suggestive typing widget - - tab ... - - WYSIWYG textarea widget (could reuse existing Javascript solution TinyMCE, CKEditor, OpenWysiwyg, ...) - - ... - ----- -# Clients - -## Libraries: Reusable Client Design based on J.Moore Presentation -* _Suggested by **Javier**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: TODO -* Generic client that can be customized (see design in slide 12) -* [http://s3.amazonaws.com/cimlabs/Oredev-Hypermedia-APIs.pdf](http://s3.amazonaws.com/cimlabs/Oredev-Hypermedia-APIs.pdf) -* video [http://vimeo.com/20781278](http://vimeo.com/20781278) - -## Create a Client Cache based on Apache commons Client Cache. -* _Suggested by **Javier**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: TODO -* [http://hc.apache.org/httpcomponents-client-ga/httpclient-cache/index.html](http://hc.apache.org/httpcomponents-client-ga/httpclient-cache/index.html) -* [http://labs.xfinity.com/benchmarking-the-httpclient-caching-module](http://labs.xfinity.com/benchmarking-the-httpclient-caching-module) - -## Add SSL support to Eiffel Net -* _Suggested by **Jocelyn**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: Currently Eiffel Net does not provide any support to SSL (thus no HTTPS). For now Eiffel application often use the Eiffel cURL wrapper which provide SSL, but it would be more convenient to use directly Eiffel Net. Then find solution to add SSL support to EiffelNet, or to extend EiffelNet with an EiffelNet+OpenSSL solution, or other. - -## Build clients to consume popular RESTful APIs -* _Suggested by **Jocelyn & Javier**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: Build Eiffel libraries to consume popular web APIs, such as: - - Google Discovery APIs - - Twitter - - Facebook - - Github - - Flickr - - ... etc -* This should reuse and improve the "http_client" provided by EWF. Eventually also write the EiffelNet implementation to be independant from cURL -* **Requirement**: OAuth client eiffel component - -## Build a ESI preprocessor, or proxy -* _Suggested by **Jocelyn**_ -* _Supervisor_: -* _Suitability_: TODO -* _Description_: TODO -* See: [http://en.wikipedia.org/wiki/Edge_Side_Includes](http://en.wikipedia.org/wiki/Edge_Side_Includes) - ----- -# Feel free to add new idea below this line ----- -Use the following page [Projects new suggestions](Projects-new-suggestions.md) to suggest new project, or request a feature. diff --git a/wiki/project/Task-json.md b/wiki/project/Task-json.md deleted file mode 100644 index dc956c20..00000000 --- a/wiki/project/Task-json.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -layout: default -title: Task json -base_url: ../../ ---- -## Goal ## -- Make this JSON library the default one for the community - -## Steps ## -- restructure to follow the (doc,library,test,...) structure -- extract gobo from it, and provide json.ecf, json-safe.ecf, json_with_gobo.ecf (and json_with_gobo-safe.ecf : not possible right now, but we can _cheat_) -- and then let Eiffel Software include it, in the official libraries - -## Roadmap ## -- This task is completed. -- Future task: review the library, and improve it. \ No newline at end of file diff --git a/wiki/project/Tasks-Roadmap.md b/wiki/project/Tasks-Roadmap.md deleted file mode 100644 index 9090b9e4..00000000 --- a/wiki/project/Tasks-Roadmap.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -layout: default -title: Tasks Roadmap -base_url: ../../ ---- -Check new roadmap wiki page: [roadmap](roadmap.md) -## Future -* Focus on REST API - - Hypermedia API - - HAL, Collection/JSON ... - - ATOM, RSS, XHTML, ... ? -* Extend WSF with libraries addressing common needs - - Logging - - Caching - - Security (authentication) + OAuth (consumer+provider) + OpenID? - - Filter chain - -* Start thinking about application friendly libraries - - Template engine - - State machine - - HTML5 (XHTML+JS) generation for widgets (table, suggestive box, ...) - - Google API, Twitter API, ... ? - -* Improve documentation - - WSF documentation + tutorial - - Topic: how to contribute ? - - Example: add a "graphviz server" example, which will demonstrate an REST Hypermedia API, with logging, caching and security - -## Version 0.1 june 2012 ## -* "Eiffel WSGI" spec -* Core of Eiffel Web Framework - - EWSGI connectors: CGI, libfcgi, Nino - - WSF: request, response, router - - And utility lib, error, http, encoders, ...) -* Examples -* Documentation (tutorial inside the examples folder) -* Installation scripts - -## Contributors ## - - See [the collaboration page](../community.md) - diff --git a/wiki/project/Useful-links.md b/wiki/project/Useful-links.md deleted file mode 100644 index 94e8b8c3..00000000 --- a/wiki/project/Useful-links.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -layout: default -title: Useful links -base_url: ../../ ---- -## Eiffel - -* [http://www.scoop.it/t/eiffel-resources](http://www.scoop.it/t/eiffel-resources) -* [http://www.scoop.it/t/eiffel](http://www.scoop.it/t/eiffel) - -## Hypermedia - -* [http://www.scoop.it/t/hyper-media-apis](http://www.scoop.it/t/hyper-media-apis) -* [http://www.scoop.it/t/hypermedia-api](http://www.scoop.it/t/hypermedia-api) - -## ETags - -* [http://www.mnot.net/blog/2007/08/07/etags](http://www.mnot.net/blog/2007/08/07/etags) -* [http://bitworking.org/news/150/REST-Tip-Deep-etags-give-you-more-benefits](http://bitworking.org/news/150/REST-Tip-Deep-etags-give-you-more-benefits) \ No newline at end of file diff --git a/wiki/project/meetings/Web-meeting-2012-09-18.md b/wiki/project/meetings/Web-meeting-2012-09-18.md deleted file mode 100644 index 8b967ee4..00000000 --- a/wiki/project/meetings/Web-meeting-2012-09-18.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -layout: default -title: Web meeting 2012 09 18 -base_url: ../../ ---- -## Participants - -* Jocelyn Fiat -* Berend de Boer -* Olivier Ligot -* Javier Velilla - -## Information - -### When ? -* Tuesday 18th of september, 19:00 - 20:00 UTC/GMT time (see 3rd time in [http://www.doodle.com/8v2sekiyebp4dpyh](http://www.doodle.com/8v2sekiyebp4dpyh)) - -### Where ? -Web meeting using webex - -* Short url: [http://goo.gl/wBz11](http://goo.gl/wBz11) -* Long url: [https://eiffel.webex.com/eiffel/j.php?ED=211265702&UID=0&PW=NZWNiMjBiZWIz&RT=MiMyMA%3D%3D](https://eiffel.webex.com/eiffel/j.php?ED=211265702&UID=0&PW=NZWNiMjBiZWIz&RT=MiMyMA%3D%3D) -* Related Google group topic: [https://groups.google.com/d/topic/eiffel-web-framework/A7ADPAT3nj8/discussion](https://groups.google.com/d/topic/eiffel-web-framework/A7ADPAT3nj8/discussion) - -## Agenda - -* Current status of EWF - * Focus on new design for the router system, and take decision - * decide if this replace the previous system, - * or if this is provided as another solution (we would then have 2 routers system). - * It might be possible to implement the previous uri and uri-template router with the new design, and mark them obsolete, this would avoid breaking existing code, but if no-one ask for it, no need to spend time doing it. - * Current activities - * Technology forecasting about REST, Hypermedia API, Collection/JSON, HAL, ... - * Building a CMS framework inspired by Drupal, and using EWF - * Libraries in-progress or draft: OAuth (consumer), Google API, Github API, Template engine, Wikitext parser, CMS (including sub libraries which will be part of EWF, such as session handling, mailer, ...) - * Documentation - * Remaining issues - * Review design in relation to concurrency, and provide example demonstrating concurrency with EWF - * Review design to allow easier extension/customization of EWF, such as using its own MIME handlers. - * Demo for a CMS built with EWF (inspired by Drupal) -* Future tasks - * [graphviz-server](https://github.com/EiffelWebFramework/graphviz-server) - * Improving Eiffel Web Nino: to support persistent connection, and better concurrency design. - * Provide friendly components to generate HTML (DHTML, HTML5, ...), (coders do not want to learn HTML and -CSS) -* Users feedback, suggestions and requests - * ... -* Next meeting - -## Materials - -## Minutes -* swagger: see if we could generate EWF code from a swagger specification -* Jocelyn will publish its attempt to build a CMS with EWF - * CMS demo: ... as announced ... some parts look very like drupal. -* Jocelyn will publish a few in-progress draft libraries -* Javier will focus on graphviz-server and hypermedia API -* Berend may send a short note on how he uses EWF (and generate code from description) -* Jocelyn will try to find time to complete the thread and SCOOP implementation of Eiffel Web Nino -* Olivier will have a closer look at swagger -* EWF will adopt the new WSF_ROUTER design as no-one expressed opposition. Olivier said converting his code is not a big task. Same for other users. -* The current state of EWF/WSF seems to be ok for users, we can focus on libraries on top of EWF/WSF -* We might need an HTML parser, if we want to support HTML as an hypermedia API (maybe we can require XHTML for now) -* No high priority to improve Eiffel Web Nino , for now it is mainly used during development. - -* It seems RESTful + Hypermedia API is the top priority for EWF. - diff --git a/wiki/project/roadmap.md b/wiki/project/roadmap.md deleted file mode 100644 index 989602d6..00000000 --- a/wiki/project/roadmap.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: default -title: roadmap -base_url: ../../ ---- -# Upcoming versions - -# Current state: oct-2013 -- check previous wiki page: [Tasks roadmap](Tasks-roadmap.md) diff --git a/workbook.html b/workbook.html new file mode 100644 index 00000000..ebbfc3e2 --- /dev/null +++ b/workbook.html @@ -0,0 +1,46 @@ +--- +layout: default +title: Workbook +base_url: ../ +--- + +

    Workbook index

    + diff --git a/workbook.py b/workbook.py new file mode 100755 index 00000000..f6e9b9c4 --- /dev/null +++ b/workbook.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +"Update the workbook pages to work with Jekyll" + +import os + +HEADER = """--- +layout: default +title: %s +base_url: %s +--- +""" + +def process_dir (dn,base,rel): + s = '
      \n' + nodes = os.listdir(dn) + for file in nodes: + path = os.path.join (dn, file) + if not file.startswith('.') and not os.path.isdir(path) and not file.endswith('.mediawiki'): + originalname = os.path.splitext(file)[0] + name = " ".join(originalname.replace('-', ' ').replace('_', ' ').split()) + uri = "%s%s" % (base,file) + if uri.endswith(".md"): + uri = uri[:-3] + if file.endswith('.md'): + print "Processing [%s@%s] %s" % (name, uri, path) + with open(path, 'r') as f: + content = f.read() + if not content.startswith('---'): + content = (HEADER % (name, rel) + content) + with open(path, 'w') as f: + f.write(content.replace('.md)',')') + .replace('(../','(../../') + .replace('(/doc/workbook/', '(../') + .replace('(./', '(../') + ) + s += '
    • %s
    • \n' % (uri, name) + for file in nodes: + path = os.path.join (dn, file) + if not file.startswith('.') and os.path.isdir(path): + originalname = os.path.splitext(file)[0] + name = " ".join(originalname.replace('-', ' ').replace('_', ' ').split()) + path = os.path.join (dn, file) + uri = "%s%s" % (base,file) + if uri.endswith(".md"): + uri = uri[:-3] + print 'Enter', path + sub_s = process_dir (path, uri + '/', rel + '../') + if sub_s != '
        \n
      \n': + if "%s.md" % (file) in nodes: + s += '
    • %s/\n' % (uri, name) + else: + s += '
    • %s/\n' % (name) + s += sub_s + s += '
    • \n' + s += '
    \n' + return s + +txt = HEADER % ('Workbook', '../') + '\n'; +txt += "

    Workbook index

    \n" +txt += process_dir ("workbook",'', '../../'); +with open('workbook.html', 'w') as f: + f.write(txt) +print 'Done' diff --git a/workbook/_ b/workbook/_ new file mode 100644 index 00000000..98f4400d --- /dev/null +++ b/workbook/_ @@ -0,0 +1,9 @@ +readme.md +handling_request/headers.md +handling_request/form.md +deployment.md +generating_response/generating_response.md +basics/simple_html/apache_config/Readme.md +basics/basics.md +workbook.md +handling_cookies/handling_cookies.md diff --git a/workbook/basics/APPLICATION_EXECUTION.png b/workbook/basics/APPLICATION_EXECUTION.png new file mode 100644 index 0000000000000000000000000000000000000000..270c78aa571825451c5620c5d424292dd348f7ac GIT binary patch literal 5971 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 literal 0 HcmV?d00001 diff --git a/workbook/basics/Launcher Hierarchy.png b/workbook/basics/Launcher Hierarchy.png new file mode 100644 index 0000000000000000000000000000000000000000..d7c047237a1a71b690c29fd31dea6d7f93f77991 GIT binary patch literal 16595 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 ONa2;LY=!ijkpBh`q`oQu literal 0 HcmV?d00001 diff --git a/workbook/basics/WSF_SERVICE_LAUNCHER_CGI.png b/workbook/basics/WSF_SERVICE_LAUNCHER_CGI.png new file mode 100644 index 0000000000000000000000000000000000000000..ba484064766681fff3b3cfb3fda0a16a9d58fa76 GIT binary patch literal 9471 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 AG5`Po literal 0 HcmV?d00001 diff --git a/workbook/basics/WSF_SERVICE_LAUNCHER_FCGI.png b/workbook/basics/WSF_SERVICE_LAUNCHER_FCGI.png new file mode 100644 index 0000000000000000000000000000000000000000..b42d4b96eab5a19e8793c14ae5f032072c8bcc57 GIT binary patch literal 9552 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 E9||hX#sB~S literal 0 HcmV?d00001 diff --git a/workbook/basics/WSF_SERVICE_LAUNCHER_NINO.png b/workbook/basics/WSF_SERVICE_LAUNCHER_NINO.png new file mode 100644 index 0000000000000000000000000000000000000000..ecca100efcd47b3cf630e6f526aa225dfce1052e GIT binary patch literal 9523 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 z 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 +## 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 + APPLICATION + +inherit + WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION] + +create + make_and_launch + +end +``` + +The class ```APPLICATION``` inherit from +```WSF_DEFAULT_SERVICE [G ->WSF_EXECUTION create make end]``` it will be responsible to launch the service and set optional options. + +The class ```APPLICATION_EXECUTION``` is an implementation of ```WSF_EXECUTION``` interface, which is instantiated for each incoming request. + +```eiffel +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + do + -- To read incoming HTTP request, we need to use `req' + + -- May require talking to databases or other services. + + -- To send a response we need to setup, the status code and + -- the response headers and the content we want to send out our client + end +end +``` + +When using the "nino" connector or the new "standalone" connector, by default the service listens on port 80, but often this port is already used by other applications, so it is recommended to use another port. +To define another port, redefine the feature `initialize' and set up a new port number using the service options (see below). + + +```eiffel +class + APPLICATION + +inherit + WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION] + redefine + initialize + end + +create + make_and_launch + +feature {NONE} -- Initialization + + initialize + -- Initialize current service. + do + set_service_option ("port", 9090) + end +end +``` + +The **WSF_REQUEST** gives access to the incoming data; the class provides features to get information such as request method, form data, query parameters, uploaded files, HTTP request headers, and hostname of the client among others. + +The **WSF_RESPONSE** provides features to define the response with information such as HTTP status codes (10x,20x, 30x, 40x, and 50x), response headers (Content-Type, Content-Length, etc.) and obviously the body of the message itself. + +**APPLICATION** is the root class of our example, it launches the application, using the corresponding connector, Which connector? this depends how you want to run it cgi, fcgi,nino or standalone. For development is recommended to use a standalone web server written in Eiffel, and run the execution within the EiffelStudio debugger. For production fcgi (or cgi) using Apache or another popular web server. + +![Launcher Hierarchy](Launcher Hierarchy.png "Launcher Hierarchy") + +**WS_LAUNCHABLE_SERVICE** inherit from **WS_SERVICE** class, which is a marker interface in EWF. And also provides a way to launch our application using different kind of connectors. The class **WSF_DEFAULT_SERVICE_I**, inherit from **WS_LAUNCHABLE_SERVICE** and has a formal generic that should conform to **WSF_SERVICE_LAUNCHER [WSF_EXECUTION]**. Below a [BON diagram](http://www.bon-method.com/index_normal.htm) showing one of the possible options. + +![Standalone Launcher](WSF_SERVICE_LAUNCHER_STANDALONE.png "Standalone Hierarchy") +Other connectors: + +**WSF_STANDALONE_SERVICE_LAUNCHER** +**WSF_CGI_SERVICE_LAUNCHER** +**WSF_NINO_SERVICE_LAUNCHER** +**WSF_LIBFCGI_SERVICE_LAUNCHER** + +A basic EWF service inherits from **WSF_DEFAULT_SERVICE**, which has a formal generic that should conform to **WSF_EXECUTION** class with a `make' creation procedure, in our case the class **APPLICATION_EXECUTION**. + +The **APPLICATION_EXECUTION** class inherits from **WSF_EXECUTION** interface, which is instantiated for each incoming request. **WSF_EXECUTION** inherit from **WGI_EXECUTION** which is the low level entry point in EWF, handling each incoming request with a single procedure ```execute (req: WSF_REQUEST; res: WSF_RESPONSE) ...```. + +In the **APPLICATION_EXECUTION** class class you will need to implement implement the **execute** feature, get data from the request *req* and write the response in *res*. + +![Execution Hierarchy](APPLICATION_EXECUTION.png "Application Execution ") + +The WSF_EXECUTION instance, in this case ```APPLICATION_EXECUTION``` is created per request, with two main attributes request: ```WSF_REQUEST``` and response: ```WSF_RESPONSE```. + + +## A simple Service to Generate Plain Text. + +Before to continue, it is recommended to review the getting started guided. In the example we will only shows the implementation of the WSF_EXECUTION interface. + +```eiffel +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute + -- Execute the incomming request + do + -- To send a response we need to setup, the status code and + -- the response headers. + response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/plain"], ["Content-Length", "11"]>>) + response.put_string ("Hello World") + end +end + +``` + +##### 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/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 Service to Generate HTML. +To generate HTML, it's needed + +1. Change the Content-Type : "text/html" +2. Build an HTML page + +```eiffel +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute + -- Execute the incomming request + do + -- To send a response we need to setup, the status code and + -- the response headers. + response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", web_page.count.out]>>) + response.put_string (web_page) + end + + web_page: STRING = "[ + + + + Resume + + + Hello World + + +]" + +end +``` +##### Source code +The source code is available on Github. You can get it by running the command: + +```git clone https://github.com/EiffelWebFramework/ewf.git``` + +The example of the service that generates HTML is located in the directory $PATH/ewf/doc/workbook/basics/simple_html, where $PATH is where you run ```git clone``` . Just double click on the simple_html.ecf file and select the simple_html_nino target or if you prefer the command line, run the command: + +```estudio -config simple_html.ecf -target simple_html_nino``` + +Nav: [Workbook](../../workbook) | [Handling Requests: Form/Query Parameter](../../handling_request/form) + diff --git a/workbook/basics/simple/application.e b/workbook/basics/simple/application.e new file mode 100644 index 00000000..cf939f60 --- /dev/null +++ b/workbook/basics/simple/application.e @@ -0,0 +1,24 @@ +note + description: "Basic Service launcher" + +class + APPLICATION + +inherit + WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION] + redefine + initialize + end + +create + make_and_launch + +feature {NONE} -- Initialization + + initialize + -- Initialize current service. + do + set_service_option ("port", 9090) + end + +end diff --git a/workbook/basics/simple/application_execution.e b/workbook/basics/simple/application_execution.e new file mode 100644 index 00000000..58917ef8 --- /dev/null +++ b/workbook/basics/simple/application_execution.e @@ -0,0 +1,25 @@ +note + description : "Basic Service that Generates Plain Text" + date : "$Date$" + revision : "$Revision$" + +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute + -- Execute the incomming request + do + -- To send a response we need to setup, the status code and + -- the response headers. + response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/plain"], ["Content-Length", "11"]>>) + response.put_string ("Hello World") + end +end diff --git a/workbook/basics/simple/simple.ecf b/workbook/basics/simple/simple.ecf new file mode 100644 index 00000000..3d9149d0 --- /dev/null +++ b/workbook/basics/simple/simple.ecf @@ -0,0 +1,50 @@ + + + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workbook/basics/simple_html/apache_config/Readme.md b/workbook/basics/simple_html/apache_config/Readme.md new file mode 100644 index 00000000..0449cdc2 --- /dev/null +++ b/workbook/basics/simple_html/apache_config/Readme.md @@ -0,0 +1,57 @@ +--- +layout: default +title: Readme +base_url: ../../../../../ +--- +##Run simple_html example on Apache with FCGI on Windows. + + + +####Prerequisites + +* This tutorial was written for people working under Windows environment, and using Apache Server with FCGI connector +* Compile the ewf application from command line. +* Assuming you have installed Apache Server under C:/home/server/Apache24. +* Assuming you have placed your current project under C:/home/server/Apache24/fcgi-bin. +* Assuming you have setted the Listen to 8888, the defautl value is 80 . + + + +####FCGI module +If you don't have the FCGI module installed, you can get it from https://www.apachelounge.com/download/, download the module based on your platform [modules-2.4-win64-VC11.zip](https://www.apachelounge.com/download/VC11/modules/modules-2.4-win64-VC11.zip) or [modules-2.4-win32-VC11.zip](https://www.apachelounge.com/download/VC11/modules/modules-2.4-win32-VC11.zip), uncompress it +and copy the _mod_fcgid.so_ to C:/home/server/Apache24/modules + +####Compile the project simple_html using the fcgi connector. + + ec -config simple_html.ecf -target simple_html_fcgi -finalize -c_compile -project_path . + +Copy the genereted exe to C:/home/server/Apache24/fcgi-bin folder. + +Check if you have _libfcgi.dll_ in your PATH. + + +####Apache configuration +Add to httpd.conf the content, you can get the configuration file [here](config.conf) + +``` +LoadModule fcgid_module modules/mod_fcgid.so + + + + SetHandler fcgid-script + Options +ExecCGI +Includes +FollowSymLinks -Indexes + AllowOverride All + Require all granted + + ScriptAlias /simple "C:/home/server/Apache24/fcgi-bin/simple_html.exe" + +``` + +Test if your httpd.conf is ok +>httpd -t + +Luanch the server +>httpd + +Check the application +>http://localhost:8888/simple diff --git a/workbook/basics/simple_html/apache_config/config.conf b/workbook/basics/simple_html/apache_config/config.conf new file mode 100644 index 00000000..f41212d2 --- /dev/null +++ b/workbook/basics/simple_html/apache_config/config.conf @@ -0,0 +1,12 @@ +LoadModule fcgid_module modules/mod_fcgid.so + + + + SetHandler fcgid-script + Options +ExecCGI +Includes +FollowSymLinks -Indexes + AllowOverride All + Require all granted + + ScriptAlias /simple "C:/home/server/Apache24/fcgi-bin/simple_html.exe" + + diff --git a/workbook/basics/simple_html/application.e b/workbook/basics/simple_html/application.e new file mode 100644 index 00000000..cf939f60 --- /dev/null +++ b/workbook/basics/simple_html/application.e @@ -0,0 +1,24 @@ +note + description: "Basic Service launcher" + +class + APPLICATION + +inherit + WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION] + redefine + initialize + end + +create + make_and_launch + +feature {NONE} -- Initialization + + initialize + -- Initialize current service. + do + set_service_option ("port", 9090) + end + +end diff --git a/workbook/basics/simple_html/application_execution.e b/workbook/basics/simple_html/application_execution.e new file mode 100644 index 00000000..63204435 --- /dev/null +++ b/workbook/basics/simple_html/application_execution.e @@ -0,0 +1,66 @@ +note + description : "Basic Service that Generate HTML" + date : "$Date$" + revision : "$Revision$" + +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute + -- Execute the incomming request + do + -- To send a response we need to setup, the status code and + -- the response headers. + response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", web_page.count.out]>>) + response.put_string (web_page) + end + + + web_page: STRING = "[ + + + + Resume + + + +
    +
    +

    Objective

    +

    To take a position as a software engineer.

    +

    Experience

    +

    Junior Developer, Software Company (2010 - Present)

    +
      +
    • Designed and implemented end-user features for Flagship Product
    • +
    • Wrote third-party JavaScript and Eiffel libraries
    • +
    +

    Skills

    +

    Languages: C#, JavaScript, Python, Ruby, Eiffel

    +

    Frameworks: .NET, Node.js, Django, Ruby on Rails, EWF

    +

    Education

    +

    BS, Economics, My University

    +
      +
    • Award for best senior thesis
    • +
    • GPA: 3.8
    • +
    +
    + + + +]" + + +end diff --git a/workbook/basics/simple_html/simple_html.ecf b/workbook/basics/simple_html/simple_html.ecf new file mode 100644 index 00000000..7a93e788 --- /dev/null +++ b/workbook/basics/simple_html/simple_html.ecf @@ -0,0 +1,42 @@ + + + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workbook/deployment/readme.md b/workbook/deployment/readme.md new file mode 100644 index 00000000..9902eb18 --- /dev/null +++ b/workbook/deployment/readme.md @@ -0,0 +1,168 @@ +--- +layout: default +title: readme +base_url: ../../../ +--- +Nav: [Workbook](../../workbook) + +EWF Deployment +============== + +#Apache on Windows# + +1. Apache Install +2. Deploying EWF CGI +3. CGI overview + 1. Build EWF application + 2. Copy the generated exe file and the www content .htaccess CGI +4. Deploying EWF FCGI +5. FCGI overview + 1. Build EWF application + 2. Copy the generated exe file and the www content.htaccess CGI + + + +##Apache on Windows + +###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 + +####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. + +* Build EWF application + + ec -config [app.ecf] -target [app_cgi] -finalize -c_compile -project_path + + +>Note: change app.ecf and target app_cgi based on your own configuration. + +* Copy the generated exe file and the www content + +Copy the app.exe and the folder _www_ into a folder served by apache2, for example under. + + + /htdocs. + + = path to your apache installation + + Edit httpd.conf under c://conf + + DocumentRoot "c://htdocs" + + /htdocs"> + AllowOverride All -- + Require all granted -- this is required in Apache 2.4.4 + + +Check that you have the following modules enabled + + LoadModule cgi_module modules/mod_cgi.so + LoadModule rewrite_module modules/mod_rewrite.so + +####Tip: +>To check the syntax of your httpd.conf file. From command line run the following + + $>httpd - t + + +>.htaccess CGI + http://perishablepress.com/stupid-htaccess-tricks/ + +####.htaccess + + Options +ExecCGI +Includes +FollowSymLinks -Indexes + AddHandler cgi-script exe + + + RewriteEngine on + + RewriteRule ^$ $service [L] + + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} !$service + RewriteRule ^(.*)$ $service/$1 + + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] + Replace $service with the name of your executable service, for example app_service.exe + + +####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 +>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 + + ec -config [app.ecf] -target [app_fcgi] -finalize -c_compile -project_path . + +>Note: change app.ecf and target app_fcgi based on your own configuration. + +* Copy the generated exe file and the www content + +Copy the app.exe and the folder "www" into a folder served by apache2, for example under + + /htdocs. + + = path to your apache installation + + Edit httpd.conf under c://conf + + DocumentRoot "c://htdocs" + + /htdocs"> + AllowOverride All -- + Require all granted -- this is required in Apache 2.4.4 + + +>Check that you have the following modules enabled + + LoadModule rewrite_module modules/mod_rewrite.so + LoadModule fcgid_module modules/mod_fcgid.so + +>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 + + Options +ExecCGI +Includes +FollowSymLinks -Indexes + + + AddHandler fcgid-script .ews + FcgidWrapper $FULL_PATH/$service .ews + + + + + RewriteEngine on + + RewriteBase / + RewriteRule ^$ service.ews [L] + + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} !=/favicon.ico + RewriteCond %{REQUEST_URI} !service.ews + RewriteRule ^(.*)$ service.ews/$1 + + + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] + + +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) diff --git a/workbook/generating_response/exel/application.e b/workbook/generating_response/exel/application.e new file mode 100644 index 00000000..cf939f60 --- /dev/null +++ b/workbook/generating_response/exel/application.e @@ -0,0 +1,24 @@ +note + description: "Basic Service launcher" + +class + APPLICATION + +inherit + WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION] + redefine + initialize + end + +create + make_and_launch + +feature {NONE} -- Initialization + + initialize + -- Initialize current service. + do + set_service_option ("port", 9090) + end + +end diff --git a/workbook/generating_response/exel/application_execution.e b/workbook/generating_response/exel/application_execution.e new file mode 100644 index 00000000..3c93fb5a --- /dev/null +++ b/workbook/generating_response/exel/application_execution.e @@ -0,0 +1,36 @@ +note + description : "Basic Service that show how to use common Status Code" + date : "$Date$" + revision : "$Revision$" + +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute + -- Execute the incomming request + do + -- To send a response we need to setup, the status code and + -- the response headers. + response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/csv"],["Content-Disposition","attachment;filename=Report.xls"],["Content-Length", sheet.count.out]>>) + response.put_string (sheet) + end + + -- ,["Content-Disposition","attachment;filename=Report.xls"] + + + sheet: STRING ="[ + Q1 Q2 Q3 Q4 Total +Cherries 78 87 92 29 =SUM(B2:E2) +Grapes 77 86 93 30 =SUM(B3:E3) + ]" + + +end diff --git a/workbook/generating_response/exel/exel.ecf b/workbook/generating_response/exel/exel.ecf new file mode 100644 index 00000000..7408b1fb --- /dev/null +++ b/workbook/generating_response/exel/exel.ecf @@ -0,0 +1,42 @@ + +l + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workbook/generating_response/generating_response.md b/workbook/generating_response/generating_response.md new file mode 100644 index 00000000..51a1cebe --- /dev/null +++ b/workbook/generating_response/generating_response.md @@ -0,0 +1,1004 @@ +--- +layout: default +title: generating response +base_url: ../../../ +--- + +Nav: [Workbook](../../workbook) | [Handling Requests: Header Fields](../../handling_request/headers) | [Handling Cookies](../../handling_cookies/handling_cookies) + + +## EWF Generating Response + +##### Table of Contents +- [Format of the HTTP response](#format) +- [How to set status code](#status_set) +- [How to redirect to a particular location.](#redirect) +- [HTTP Status codes](#status) +- [Example Staus Codes](#example_1) +- [Generic Search Engine](#example_2) +- [Response Header Fields](#header_fields) + + + +## Format of the HTTP response + +As we saw in the previous documents, a request from a user-agent (browser or other client) consists of an HTTP command (usually GET or POST), zero or more request headers (one or more in HTTP 1.1, since Host is required), a blank line, and only in the case of POST/PUT requests, payload data. A typical request looks like the following. + +``` + GET /url[query_string] HTTP/1.1 + Host: ... + Header2: ... + ... + HeaderN: + (Blank Line) +``` + +When a Web server responds to a request, the response typically consists of a status line, some response headers, a blank line, and the document. A typical response +looks like this: + +``` + HTTP/1.1 200 OK + Content-Type: text/html + Header2: ... + ... + HeaderN: ... + (Blank Line) + + + ... + + ... + + +``` + +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). + + +## How to set the status code + +If you need to set an arbitrary status code, you can use the ```WSF_RESPONSE.put_header``` feature or the ```WSF_RESPONSE.set_status_code``` feature. An status code of 200 is a default value. See below examples using the mentioned features. + +### Using the WSF_RESPONSE.put_header feature. +In this case you provide the status code with a collection of headers. + +```eiffel + put_header (a_status_code: INTEGER_32; a_headers: detachable ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]) + -- Put headers with status `a_status', and headers from `a_headers' + require + a_status_code_valid: a_status_code > 0 + status_not_committed: not status_committed + header_not_committed: not header_committed + ensure + status_code_set: status_code = a_status_code + status_set: status_is_set + message_writable: message_writable + +Example + res.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", output_size]>>) + res.put_string (web_page) +``` + +### Using the WSF_RESPONSE.set_status code + +```eiffel + custom_response (req: WSF_REQUEST; res: WSF_RESPONSE; output: STRING) + local + h: HTTP_HEADER + l_msg: STRING + do + create h.make + create l_msg.make_from_string (output) + h.put_content_type_text_html + h.put_content_length (l_msg.count) + h.put_current_date + res.set_status_code ({HTTP_STATUS_CODE}.ok) + res.put_header_text (h.string) + res.put_string (l_msg) + end +``` +Both features takes an INTEGER (the status code) as an formal argument, you can use 200, 300, 500 etc directly, but instead of using explicit numbers, it's recommended to use the constants defined in the class [HTTP_STATUS_CODE](). The name of each constant is based from the standard [HTTP 1.1](https://httpwg.github.io/). + + +## How to redirect to a particular location. +To redirect the response to a new location, we need to send a 302 status code, to do that we use ```{HTTP_STATUS_CODE}.found``` + +> The 302 (Found) status code indicates that the target resource resides temporarily under a different URI. Since the redirection might be altered on occasion, the client ought to continue to use the effective request URI for future requests. + +Another way to do redirection is with 303 status code + +> The 303 (See Other) status code indicates that the server is redirecting the user agent to a different resource, as indicated by a URI in the Location header field, which is intended to provide an indirect response to the original request. + +The next code show a custom feature to write a redirection, you can use found or see_other based on your particular requirements. + +```eiffel + send_redirect (req: WSF_REQUEST; res: WSF_RESPONSE; a_location: READABLE_STRING_32) + -- Redirect to `a_location' + local + h: HTTP_HEADER + do + create h.make + h.put_content_type_text_html + h.put_current_date + h.put_location (a_location) + res.set_status_code ({HTTP_STATUS_CODE}.found) + res.put_header_text (h.string) + end +``` + +The class [WSF_RESPONSE]() provide features to work with redirection + +```eiffel + redirect_now (a_url: READABLE_STRING_8) + -- Redirect to the given url `a_url' + require + header_not_committed: not header_committed + + redirect_now_custom (a_url: READABLE_STRING_8; a_status_code: INTEGER_32; a_header: detachable HTTP_HEADER; a_content: detachable TUPLE [body: READABLE_STRING_8; type: READABLE_STRING_8]) + -- Redirect to the given url `a_url' and precise custom `a_status_code', custom header and content + -- Please see http://www.faqs.org/rfcs/rfc2616 to use proper status code. + -- if `a_status_code' is 0, use the default {HTTP_STATUS_CODE}.temp_redirect + require + header_not_committed: not header_committed + + redirect_now_with_content (a_url: READABLE_STRING_8; a_content: READABLE_STRING_8; a_content_type: READABLE_STRING_8) + -- Redirect to the given url `a_url' +``` + +The ```WSF_RESPONSE.redirect_now``` feature use the status code ```{HTTP_STATUS_CODE}.found```,the other redirect features enable customize the status code and content based on your requirements. + + +Using a similar approach we can build features to answer a bad request (400), internal server error (500), etc. We will build a simple example showing the most common HTTP status codes. + + +## [HTTP 1.1 Status Codes](https://httpwg.github.io/specs/rfc7231.html#status.codes) +The status-code element is a three-digit integer code giving the result of the attempt to understand and satisfy the request. The first digit of the status-code defines the class of response. + +General categories: +* [1xx](https://httpwg.github.io/specs/rfc7231.html#status.1xx) Informational: The 1xx series of response codes are used only in negotiations with the HTTP server. +* [2xx](https://httpwg.github.io/specs/rfc7231.html#status.2xx) Sucessful: The 2xx error codes indicate that an operation was successful. +* [3xx](https://httpwg.github.io/specs/rfc7231.html#status.3xx) Redirection: The 3xx status codes indicate that the client needs to do some extra work to get what it wants. +* [4xx](https://httpwg.github.io/specs/rfc7231.html#status.4xx) Client Error: These status codes indicate that something is wrong on the client side. +* [5xx](https://httpwg.github.io/specs/rfc7231.html#status.5xx) Server Error: These status codes indicate that something is wrong on the server side. + +Note: use ```res.set_status_code({HTTP_STATUS_CODE}.bad_request)``` rather than ```res.set_status_code(400)```. + + + +### Example Staus Codes +Basic Service that builds a simple web page to show the most common status codes +```eiffel +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the incomming request + local + l_message: STRING + do + -- To send a response we need to setup, the status code and + -- the response headers. + if req.is_get_request_method then + if req.path_info.same_string ("/") then + res.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", web_page.count.out]>>) + res.put_string (web_page) + elseif req.path_info.same_string ("/redirect") then + send_redirect (req, res, "https://httpwg.github.io/") + -- res.redirect_now (l_engine_url) + elseif req.path_info.same_string ("/bad_request") then + -- Here you can do some logic for example log, send emails to register the error, before to send the response. + create l_message.make_from_string (message_template) + l_message.replace_substring_all ("$title", "Bad Request") + l_message.replace_substring_all ("$status", "Bad Request 400") + res.put_header ({HTTP_STATUS_CODE}.bad_request, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>) + res.put_string (l_message) + elseif req.path_info.same_string ("/internal_error") then + -- Here you can do some logic for example log, send emails to register the error, before to send the response. + create l_message.make_from_string (message_template) + l_message.replace_substring_all ("$title", "Internal Server Error") + l_message.replace_substring_all ("$status", "Internal Server Error 500") + res.put_header ({HTTP_STATUS_CODE}.internal_server_error, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>) + res.put_string (l_message) + else + create l_message.make_from_string (message_template) + l_message.replace_substring_all ("$title", "Resource not found") + l_message.replace_substring_all ("$status", "Resource not found 400") + res.put_header ({HTTP_STATUS_CODE}.not_found, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>) + res.put_string (l_message) + end + else + create l_message.make_from_string (message_template) + l_message.replace_substring_all ("$title", "Method Not Allowed") + l_message.replace_substring_all ("$status", "Method Not Allowed 405") + -- Method not allowed + res.put_header ({HTTP_STATUS_CODE}.method_not_allowed, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>) + res.put_string (l_message) + end + end + + +feature -- Home Page + + send_redirect (req: WSF_REQUEST; res: WSF_RESPONSE; a_location: READABLE_STRING_32) + -- Redirect to `a_location' + local + h: HTTP_HEADER + do + create h.make + h.put_content_type_text_html + h.put_current_date + h.put_location (a_location) + res.set_status_code ({HTTP_STATUS_CODE}.see_other) + res.put_header_text (h.string) + end + + web_page: STRING = "[ + + + + Example showing common status codes + + + +
    +
    +

    This page is an example of Status Code 200

    + +

    Redirect Example

    +

    Click on the following link will redirect you to the HTTP Specifcation, we can do the redirect from the HTML directly but + here we want to show you an exmaple, where you can do something before to send a redirect Redirect

    + +

    Bad Request

    +

    Click on the following link, the server will answer with a 400 error, check the status code Bad Request

    + +

    Internal Server Error

    +

    Click on the following link, the server will answer with a 500 error, check the status code Internal Error

    + +

    Resource not found

    +

    Click on the following link or add to the end of the url something like /1030303 the server will answer with a 404 error, check the status code Not found

    + +
    + + + +]" + +feature -- Generic Message + + message_template: STRING="[ + + + + $title + + + +
    +
    +

    This page is an example of $status

    + + + + +]" +end + +``` + + + + +### Example Generic Search Engine +The following example shows a basic EWF service that builds a generic front end for the most used search engines. This example shows how +redirection works, and we will use a tools to play with the API to show differents responses. + +```eiffel +note + description : "Basic Service that build a generic front end for the most used search engines." + date : "$Date$" + revision : "$Revision$" + +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the incomming request + local + l_message: STRING + do + -- To send a response we need to setup, the status code and + -- the response headers. + if req.is_get_request_method then + if req.path_info.same_string ("/") then + res.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", web_page.count.out]>>) + res.put_string (web_page) + else + send_resouce_not_found (req, res) + end + elseif req.is_post_request_method then + if req.path_info.same_string ("/search") then + if attached {WSF_STRING} req.form_parameter ("query") as l_query then + if attached {WSF_STRING} req.form_parameter ("engine") as l_engine then + if attached {STRING} map.at (l_engine.value) as l_engine_url then + l_engine_url.append (l_query.value) + send_redirect (req, res, l_engine_url) + else + send_bad_request (req, res, " search engine: " + l_engine.value + " not supported,
    try with Google or Bing") + end + else + send_bad_request (req, res, " search engine not selected") + end + else + send_bad_request (req, res, " form_parameter query is not present") + end + else + send_resouce_not_found (req, res) + end + else + create l_message.make_from_string (message_template) + l_message.replace_substring_all ("$title", "Method Not Allowed") + l_message.replace_substring_all ("$status", "Method Not Allowed 405") + -- Method not allowed + res.put_header ({HTTP_STATUS_CODE}.method_not_allowed, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>) + res.put_string (l_message) + end + end + + +feature -- Engine Map + + map : STRING_TABLE[STRING] + do + create Result.make (2) + Result.put ("http://www.google.com/search?q=", "Google") + Result.put ("http://www.bing.com/search?q=", "Bing") + end + +feature -- Redirect + + send_redirect (req: WSF_REQUEST; res: WSF_RESPONSE; a_location: READABLE_STRING_32) + -- Redirect to `a_location' + local + h: HTTP_HEADER + do + create h.make + h.put_content_type_text_html + h.put_current_date + h.put_location (a_location) + res.set_status_code ({HTTP_STATUS_CODE}.see_other) + res.put_header_text (h.string) + end + +feature -- Bad Request + + send_bad_request (req: WSF_REQUEST; res: WSF_RESPONSE; description: STRING) + local + l_message: STRING + do + create l_message.make_from_string (message_template) + l_message.replace_substring_all ("$title", "Bad Request") + l_message.replace_substring_all ("$status", "Bad Request" + description) + res.put_header ({HTTP_STATUS_CODE}.bad_request, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>) + res.put_string (l_message) + end + +feature -- Resource not found + + send_resouce_not_found (req: WSF_REQUEST; res: WSF_RESPONSE) + local + l_message: STRING + do + create l_message.make_from_string (message_template) + l_message.replace_substring_all ("$title", "Resource not found") + l_message.replace_substring_all ("$status", "Resource" + req.request_uri + "not found 404") + res.put_header ({HTTP_STATUS_CODE}.not_found, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>) + res.put_string (l_message) + end + +feature -- Home Page + + web_page: STRING = "[ + + + + Generic Search Engine + + +
    +

    Generic Search Engine

    +
    +
    + Search:
    +
    + +
    + +
    + +

    +
    + +
    + + +
    + + + +]" + +feature -- Generic Message + + message_template: STRING="[ + + + + $title + + + +
    +
    +

    This page is an example of $status

    + + + + +]" + +end + +``` + +Using cURL to test the application + +In the first call we use the ```res.redirect_now (l_engine_url)``` feature +``` +#>curl -i -H -v -X POST -d "query=Eiffel&engine=Google" http://localhost:9090/search +HTTP/1.1 302 Found +Location: http://www.google.com/search?q=Eiffel +Content-Length: 0 +Connection: close +``` + +Here we use our custom send_redirect feature call. + +``` +#>curl -i -H -v -X POST -d "query=Eiffel&engine=Google" http://localhost:9090/search +HTTP/1.1 303 See Other +Content-Type: text/html +Date: Fri, 06 Mar 2015 14:37:33 GMT +Location: http://www.google.com/search?q=Eiffel +Connection: close +``` + +#### Engine Ask Not supported + +``` +#>curl -i -H -v -X POST -d "query=Eiffel&engine=Ask" http://localhost:9090/search +HTTP/1.1 400 Bad Request +Content-Type: text/html +Content-Length: 503 +Connection: close + + + + + Bad Request + + + +
    +
    +

    This page is an example of Bad Request search engine: Ask not supported,
    try with Google or Bing

    + + + + +``` + + +#### Missing query form parameter + +``` +#>curl -i -H -v -X POST -d "engine=Google" http://localhost:9090/search +HTTP/1.1 400 Bad Request +Content-Type: text/html +Content-Length: 477 +Connection: close + + + + + Bad Request + + + +
    +
    +

    This page is an example of Bad Request form_parameter query is not present

    + + + + +``` + +#### Resource searchs not found + +``` +#>curl -i -H -v -X POST -d "query=Eiffel&engine=Google" http://localhost:9090/searchs +HTTP/1.1 404 Not Found +Content-Type: text/html +Content-Length: 449 +Connection: close + + + + + Resource not found + + + +
    +
    +

    This page is an example of Resource /searchs not found 404

    + + + + +``` + + +## [Response Header Fields](https://httpwg.github.io/specs/rfc7231.html#response.header.fields) + +The response header fields allow the server to pass additional information about the response beyond what is placed in the status-line. These header fields give information about the server, about further access to the target resource, or about related resources. We can specify cookies, page modification date (for caching), reload a page after a designated period of time, size of the document. + + + +### How to set response headers. + +HTTP allows multiple occurrences of the same header name, the features ```put_XYZ``` replace existing headers with the same name and +features ```add_XYZ``` add headers that can lead to duplicated entries. + + +```eiffel + add_header_line (h: READABLE_STRING_8) + -- Add header `h' + -- This can lead to duplicated header entries + require + header_not_committed: not header_committed + + add_header_text (a_text: READABLE_STRING_8) + -- Add the multiline header `a_text' + -- Does not replace existing header with same name + -- This could leads to multiple header with the same name + require + header_not_committed: not header_committed + a_text_ends_with_single_crlf: a_text.count > 2 implies not a_text.substring (a_text.count - 2, a_text.count).same_string ("%R%N") + a_text_does_not_end_with_double_crlf: a_text.count > 4 implies not a_text.substring (a_text.count - 4, a_text.count).same_string ("%R%N%R%N") + ensure + status_set: status_is_set + message_writable: message_writable + + put_header_line (h: READABLE_STRING_8) + -- Put header `h' + -- Replace any existing value + require + header_not_committed: not header_committed + + put_header_text (a_text: READABLE_STRING_8) + -- Put the multiline header `a_text' + -- Overwite potential existing header + require + header_not_committed: not header_committed + a_text_ends_with_single_crlf: a_text.count > 2 implies not a_text.substring (a_text.count - 2, a_text.count).same_string ("%R%N") + a_text_does_not_end_with_double_crlf: a_text.count > 4 implies not a_text.substring (a_text.count - 4, a_text.count).same_string ("%R%N%R%N") + ensure + message_writable: message_writable + +helpers + + add_header (a_status_code: INTEGER_32; a_headers: detachable ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]) + -- Put headers with status `a_status', and headers from `a_headers' + require + a_status_code_valid: a_status_code > 0 + status_not_committed: not status_committed + header_not_committed: not header_committed + ensure + status_code_set: status_code = a_status_code + status_set: status_is_set + message_writable: message_writable + + add_header_lines (a_lines: ITERABLE [READABLE_STRING_8]) + -- Add headers from `a_lines' + require + header_not_committed: not header_committed + + put_header (a_status_code: INTEGER_32; a_headers: detachable ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]) + -- Put headers with status `a_status', and headers from `a_headers' + require + a_status_code_valid: a_status_code > 0 + status_not_committed: not status_committed + header_not_committed: not header_committed + ensure + status_code_set: status_code = a_status_code + status_set: status_is_set + message_writable: message_writable + + put_header_lines (a_lines: ITERABLE [READABLE_STRING_8]) + -- Put headers from `a_lines' + require + header_not_committed: not header_committed + +``` + +The other way to build headers is using the class [HTTP_HEADER](), that provide routines to build a header. It's recomended to +take a look at constants classes such as [HTTP_MIME_TYPES](),[HTTP_HEADER_NAMES](),[HTTP_STATUS_CODE](),[HTTP_REQUEST_METHODS](), or +[HTTP_CONSTANTS]() which groups them for convenience. + + +```eiffel + custom_answer (req: WSF_REQUEST; res: WSF_RESPONSE; output: STRING) + local + h: HTTP_HEADER + l_msg: STRING + do + create h.make + create l_msg.make_from_string (output) + h.put_content_type_text_html + h.put_content_length (l_msg.count) + h.put_current_date + res.set_status_code ({HTTP_STATUS_CODE}.bad_gateway) + res.put_header_text (h.string) + res.put_string (l_msg) + end +``` +The class [HTTP_HEADER]() also supplies a number of convenience routines for specifying common headers, in fact the features are inherited from the class [HTTP_HEADER_MODIFIER]. + + +```eiffel +deferred class interface + HTTP_HEADER_MODIFIER + +feature -- Access + + date_to_rfc1123_http_date_format (dt: DATE_TIME): STRING_8 + -- String representation of `dt' using the RFC 1123 + + item alias "[]" (a_header_name: READABLE_STRING_8): detachable READABLE_STRING_8 assign force + -- First header item found for `a_name' if any + +feature -- Status report + + has (a_name: READABLE_STRING_8): BOOLEAN + -- Has header item for `n'? + -- Was declared in HTTP_HEADER_MODIFIER as synonym of has_header_named. + + has_content_length: BOOLEAN + -- Has header "Content-Length" + + has_content_type: BOOLEAN + -- Has header "Content-Type" + + has_header_named (a_name: READABLE_STRING_8): BOOLEAN + -- Has header item for `n'? + -- Was declared in HTTP_HEADER_MODIFIER as synonym of has. + + has_transfer_encoding_chunked: BOOLEAN + -- Has "Transfer-Encoding: chunked" header + +feature -- Access: deferred + + new_cursor: INDEXABLE_ITERATION_CURSOR [READABLE_STRING_8] + -- Fresh cursor associated with current structure. + +feature -- Authorization + + put_authorization (a_authorization: READABLE_STRING_8) + -- Put `a_authorization' with "Authorization" header + -- The Authorization header is constructed as follows: + -- 1. Username and password are combined into a string "username:password". + -- 2. The resulting string literal is then encoded using Base64. + -- 3. The authorization method and a space, i.e. "Basic " is then put before the encoded string. + -- ex: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + +feature -- Content related header + + add_content_type (a_content_type: READABLE_STRING_8) + -- same as put_content_type, but allow multiple definition of "Content-Type" + + add_content_type_with_charset (a_content_type: READABLE_STRING_8; a_charset: READABLE_STRING_8) + -- Same as put_content_type_with_charset, but allow multiple definition of "Content-Type". + + add_content_type_with_name (a_content_type: READABLE_STRING_8; a_name: READABLE_STRING_8) + -- same as put_content_type_with_name, but allow multiple definition of "Content-Type" + + add_content_type_with_parameters (a_content_type: READABLE_STRING_8; a_params: detachable ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]) + -- Add header line "Content-Type:" + type `a_content_type' and extra paramaters `a_params'. + + put_content_disposition (a_type: READABLE_STRING_8; a_params: detachable READABLE_STRING_8) + -- Put "Content-Disposition" header + + put_content_encoding (a_encoding: READABLE_STRING_8) + -- Put "Content-Encoding" header of value `a_encoding'. + + put_content_language (a_lang: READABLE_STRING_8) + -- Put "Content-Language" header of value `a_lang'. + + put_content_length (a_length: INTEGER_32) + -- Put "Content-Length:" + length `a_length'. + + put_content_transfer_encoding (a_mechanism: READABLE_STRING_8) + -- Put "Content-Transfer-Encoding" header with `a_mechanism' + + put_content_type (a_content_type: READABLE_STRING_8) + -- Put header line "Content-Type:" + type `a_content_type' + + put_content_type_with_charset (a_content_type: READABLE_STRING_8; a_charset: READABLE_STRING_8) + -- Put content type `a_content_type' with `a_charset' as "charset" parameter. + + put_content_type_with_name (a_content_type: READABLE_STRING_8; a_name: READABLE_STRING_8) + -- Put content type `a_content_type' with `a_name' as "name" parameter. + + put_content_type_with_parameters (a_content_type: READABLE_STRING_8; a_params: detachable ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]) + -- Put header line "Content-Type:" + type `a_content_type' and extra paramaters `a_params' + + put_transfer_encoding (a_encoding: READABLE_STRING_8) + -- Put "Transfer-Encoding" header with `a_encoding' value. + + put_transfer_encoding_binary + -- Put "Transfer-Encoding: binary" header + + put_transfer_encoding_chunked + -- Put "Transfer-Encoding: chunked" header + +feature -- Content-type helpers + + put_content_type_application_javascript + + put_content_type_application_json + + put_content_type_application_pdf + + put_content_type_application_x_www_form_encoded + + put_content_type_application_zip + + put_content_type_image_gif + + put_content_type_image_jpg + + put_content_type_image_png + + put_content_type_image_svg_xml + + put_content_type_message_http + + put_content_type_multipart_alternative + + put_content_type_multipart_encrypted + + put_content_type_multipart_form_data + + put_content_type_multipart_mixed + + put_content_type_multipart_related + + put_content_type_multipart_signed + + put_content_type_text_css + + put_content_type_text_csv + + put_content_type_text_html + + put_content_type_text_javascript + + put_content_type_text_json + + put_content_type_text_plain + + put_content_type_text_xml + + put_content_type_utf_8_text_plain + +feature -- Cookie + + put_cookie (key, value: READABLE_STRING_8; expiration, path, domain: detachable READABLE_STRING_8; secure, http_only: BOOLEAN) + -- Set a cookie on the client's machine + -- with key 'key' and value 'value'. + -- Note: you should avoid using "localhost" as `domain' for local cookies + -- since they are not always handled by browser (for instance Chrome) + require + make_sense: (key /= Void and value /= Void) and then (not key.is_empty and not value.is_empty) + domain_without_port_info: domain /= Void implies domain.index_of (':', 1) = 0 + + put_cookie_with_expiration_date (key, value: READABLE_STRING_8; expiration: DATE_TIME; path, domain: detachable READABLE_STRING_8; secure, http_only: BOOLEAN) + -- Set a cookie on the client's machine + -- with key 'key' and value 'value'. + require + make_sense: (key /= Void and value /= Void) and then (not key.is_empty and not value.is_empty) + +feature -- Cross-Origin Resource Sharing + + put_access_control_allow_all_origin + -- Put "Access-Control-Allow-Origin: *" header. + + put_access_control_allow_credentials (b: BOOLEAN) + -- Indicates whether or not the response to the request can be exposed when the credentials flag is true. + -- When used as part of a response to a preflight request, this indicates whether or not the actual request can be made using credentials. + -- Note that simple GET requests are not preflighted, and so if a request is made for a resource with credentials, + -- if this header is not returned with the resource, the response is ignored by the browser and not returned to web content. + -- ex: Access-Control-Allow-Credentials: true | false + + put_access_control_allow_headers (a_headers: READABLE_STRING_8) + -- Put "Access-Control-Allow-Headers" header. with value `a_headers' + -- Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request. + -- ex: Access-Control-Allow-Headers: [, ]* + + put_access_control_allow_iterable_headers (a_fields: ITERABLE [READABLE_STRING_8]) + -- Put "Access-Control-Allow-Headers" header. with value `a_headers' + -- Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request. + -- ex: Access-Control-Allow-Headers: [, ]* + + put_access_control_allow_methods (a_methods: ITERABLE [READABLE_STRING_8]) + -- If `a_methods' is not empty, put `Access-Control-Allow-Methods' header with list `a_methods' of methods + -- `a_methods' specifies the method or methods allowed when accessing the resource. + -- This is used in response to a preflight request. + -- ex: Access-Control-Allow-Methods: [, ]* + + put_access_control_allow_origin (a_origin: READABLE_STRING_8) + -- Put "Access-Control-Allow-Origin: " + `a_origin' header. + -- `a_origin' specifies a URI that may access the resource + +feature -- Date + + put_current_date + -- Put current date time with "Date" header + + put_date (a_date: READABLE_STRING_8) + -- Put "Date: " header + + put_last_modified (a_utc_date: DATE_TIME) + -- Put UTC date time `dt' with "Last-Modified" header + + put_utc_date (a_utc_date: DATE_TIME) + -- Put UTC date time `a_utc_date' with "Date" header + -- using RFC1123 date formating. + +feature -- Header change: deferred + + add_header (h: READABLE_STRING_8) + -- Add header `h' + -- if it already exists, there will be multiple header with same name + -- which can also be valid + require + h_not_empty: h /= Void and then not h.is_empty + + put_header (h: READABLE_STRING_8) + -- Add header `h' or replace existing header of same header name + require + h_not_empty: h /= Void and then not h.is_empty + +feature -- Header change: general + + add_header_key_value (a_header_name, a_value: READABLE_STRING_8) + -- Add header `a_header_name:a_value'. + -- If it already exists, there will be multiple header with same name + -- which can also be valid + ensure + added: has_header_named (a_header_name) + + force (a_value: detachable READABLE_STRING_8; a_header_name: READABLE_STRING_8) + -- Put header `a_header_name:a_value' or replace existing header of name `a_header_name'. + + put_header_key_value (a_header_name, a_value: READABLE_STRING_8) + -- Add header `a_header_name:a_value', or replace existing header of same header name/key + ensure + added: has_header_named (a_header_name) + + put_header_key_values (a_header_name: READABLE_STRING_8; a_values: ITERABLE [READABLE_STRING_8]; a_separator: detachable READABLE_STRING_8) + -- Add header `a_header_name: a_values', or replace existing header of same header values/key. + -- Use Comma_space as default separator if `a_separator' is Void or empty. + ensure + added: has_header_named (a_header_name) + +feature -- Method related + + put_allow (a_methods: ITERABLE [READABLE_STRING_8]) + -- If `a_methods' is not empty, put `Allow' header with list `a_methods' of methods + +feature -- Others + + put_cache_control (a_cache_control: READABLE_STRING_8) + -- Put "Cache-Control" header with value `a_cache_control' + + put_expires (a_seconds: INTEGER_32) + -- Put "Expires" header to `a_seconds' seconds + + put_expires_date (a_utc_date: DATE_TIME) + -- Put "Expires" header with UTC date time value + -- formatted following RFC1123 specification. + + put_expires_string (a_expires: STRING_8) + -- Put "Expires" header with `a_expires' string value + + put_pragma (a_pragma: READABLE_STRING_8) + -- Put "Pragma" header with value `a_pragma' + + put_pragma_no_cache + -- Put "Pragma" header with "no-cache" a_pragma + +feature -- Redirection + + put_location (a_uri: READABLE_STRING_8) + -- Tell the client the new location `a_uri' + -- using "Location" header. + require + a_uri_valid: not a_uri.is_empty + + put_refresh (a_uri: READABLE_STRING_8; a_timeout_in_seconds: INTEGER_32) + -- Tell the client to refresh page with `a_uri' after `a_timeout_in_seconds' in seconds + -- using "Refresh" header. + require + a_uri_valid: not a_uri.is_empty + +end -- class HTTP_HEADER_MODIFIER +``` + + + +## HTTP 1.1 Response Headers + +There are four categories for response header fields: + - [Control Data](https://httpwg.github.io/specs/rfc7231.html#response.control.data) : Supply control data that supplements the status code, directs caching, or instructs the client where to go next. + - Age,Cache-Control,Expires,Date,Location,Retry-After,Vary,Warning. + - [Validator](https://httpwg.github.io/specs/rfc7231.html#response.validator): Validator header fields convey metadata about the selected representation. In responses to safe requests, validator fields describe the selected representation chosen by the origin server while handling the response. + - [Authentication Challenges](https://httpwg.github.io/specs/rfc7231.html#response.auth): Indicate what mechanisms are available for the client to provide authentication credentials in future requests. + - [Response Context](https://httpwg.github.io/specs/rfc7231.html#response.context): Provide more information about the target resource for potential use in later requests. + + + +Nav: [Workbook](../../workbook) | [Handling Requests: Header Fields](../../handling_request/headers) | [Handling Cookies](../../handling_cookies/handling_cookies) diff --git a/workbook/generating_response/headers/application.e b/workbook/generating_response/headers/application.e new file mode 100644 index 00000000..cf939f60 --- /dev/null +++ b/workbook/generating_response/headers/application.e @@ -0,0 +1,24 @@ +note + description: "Basic Service launcher" + +class + APPLICATION + +inherit + WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION] + redefine + initialize + end + +create + make_and_launch + +feature {NONE} -- Initialization + + initialize + -- Initialize current service. + do + set_service_option ("port", 9090) + end + +end diff --git a/workbook/generating_response/headers/application_execution.e b/workbook/generating_response/headers/application_execution.e new file mode 100644 index 00000000..5b042d53 --- /dev/null +++ b/workbook/generating_response/headers/application_execution.e @@ -0,0 +1,57 @@ +note + description : "Basic Service that build a generic front end for the most used search engines." + date : "$Date$" + revision : "$Revision$" + +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute + -- Execute the incomming request + local + l_message: STRING + do + -- (1) To send a response we need to setup, the status code and the response headers. +-- response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", web_page.count.out]>>) +-- response.put_string (web_page) + + -- (2) Using put_header_line +-- response.set_status_code ({HTTP_STATUS_CODE}.ok) +-- response.put_header_line ("Content-Type:text/html") + response.put_header_line ("Content-Length:"+ web_page.count.out) + response.put_header_line ("Content-Type:text/plain") + + response.put_string (web_page) + end + + + +feature -- Home Page + + web_page: STRING = "[ + + + + EWF Headers Responses + + +
    +

    Example Header Response

    +

    Response headers

    + +
    + + + +]" +end diff --git a/workbook/generating_response/headers/headers.ecf b/workbook/generating_response/headers/headers.ecf new file mode 100644 index 00000000..5f337681 --- /dev/null +++ b/workbook/generating_response/headers/headers.ecf @@ -0,0 +1,42 @@ + + + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workbook/generating_response/search/application.e b/workbook/generating_response/search/application.e new file mode 100644 index 00000000..cf939f60 --- /dev/null +++ b/workbook/generating_response/search/application.e @@ -0,0 +1,24 @@ +note + description: "Basic Service launcher" + +class + APPLICATION + +inherit + WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION] + redefine + initialize + end + +create + make_and_launch + +feature {NONE} -- Initialization + + initialize + -- Initialize current service. + do + set_service_option ("port", 9090) + end + +end diff --git a/workbook/generating_response/search/application_execution.e b/workbook/generating_response/search/application_execution.e new file mode 100644 index 00000000..1e3ebe08 --- /dev/null +++ b/workbook/generating_response/search/application_execution.e @@ -0,0 +1,172 @@ +note + description : "Basic Service that build a generic front end for the most used search engines." + date : "$Date$" + revision : "$Revision$" + +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute + -- Execute the incomming request + local + l_message: STRING + do + -- To send a response we need to setup, the status code and + -- the response headers. + if request.is_get_request_method then + if request.path_info.same_string ("/") then + response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", web_page.count.out]>>) + response.put_string (web_page) + else + send_resouce_not_found (request, response) + end + elseif request.is_post_request_method then + if request.path_info.same_string ("/search") then + if attached {WSF_STRING} request.form_parameter ("query") as l_query then + if attached {WSF_STRING} request.form_parameter ("engine") as l_engine then + if attached {STRING} map.at (l_engine.value) as l_engine_url then + l_engine_url.append (l_query.value) + send_redirect (request, response, l_engine_url) + -- response.redirect_now (l_engine_url) + else + send_bad_request (request, response, " search engine: " + l_engine.value + " not supported,
    try with Google or Bing") + end + else + send_bad_request (request, response, " search engine not selected") + end + else + send_bad_request (request, response, " form_parameter query is not present") + end + else + send_resouce_not_found (request, response) + end + else + create l_message.make_from_string (message_template) + l_message.replace_substring_all ("$title", "Method Not Allowed") + l_message.replace_substring_all ("$status", "Method Not Allowed 405") + -- Method not allowed + response.put_header ({HTTP_STATUS_CODE}.method_not_allowed, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>) + response.put_string (l_message) + end + end + + +feature -- Engine Map + + map : STRING_TABLE[STRING] + do + create Result.make (2) + Result.put ("http://www.google.com/search?q=", "Google") + Result.put ("http://www.bing.com/search?q=", "Bing") + end + +feature -- Redirect + + send_redirect (req: WSF_REQUEST; res: WSF_RESPONSE; a_location: READABLE_STRING_32) + -- Redirect to `a_location' + local + h: HTTP_HEADER + do + create h.make + h.put_content_type_text_html + h.put_current_date + h.put_location (a_location) + res.set_status_code ({HTTP_STATUS_CODE}.see_other) + res.put_header_text (h.string) + end + +feature -- Bad Request + + send_bad_request (req: WSF_REQUEST; res: WSF_RESPONSE; description: STRING) + local + l_message: STRING + do + create l_message.make_from_string (message_template) + l_message.replace_substring_all ("$title", "Bad Request") + l_message.replace_substring_all ("$status", "Bad Request" + description) + res.put_header ({HTTP_STATUS_CODE}.bad_request, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>) + res.put_string (l_message) + end + +feature -- Resource not found + + send_resouce_not_found (req: WSF_REQUEST; res: WSF_RESPONSE) + local + l_message: STRING + do + create l_message.make_from_string (message_template) + l_message.replace_substring_all ("$title", "Resource not found") + l_message.replace_substring_all ("$status", "Resource " + req.request_uri + " not found 404") + res.put_header ({HTTP_STATUS_CODE}.not_found, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>) + res.put_string (l_message) + end + +feature -- Home Page + + web_page: STRING = "[ + + + + Generic Search Engine + + +
    +

    Generic Search Engine

    +
    +
    + Search:
    +
    + +
    + +
    + +

    +
    + +
    + + +
    + + + +]" + +feature -- Generic Message + + message_template: STRING="[ + + + + $title + + + +
    +
    +

    This page is an example of $status

    + + + + +]" + + + + +end diff --git a/workbook/generating_response/search/search.ecf b/workbook/generating_response/search/search.ecf new file mode 100644 index 00000000..1e58d207 --- /dev/null +++ b/workbook/generating_response/search/search.ecf @@ -0,0 +1,42 @@ + + + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workbook/generating_response/status/application.e b/workbook/generating_response/status/application.e new file mode 100644 index 00000000..cf939f60 --- /dev/null +++ b/workbook/generating_response/status/application.e @@ -0,0 +1,24 @@ +note + description: "Basic Service launcher" + +class + APPLICATION + +inherit + WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION] + redefine + initialize + end + +create + make_and_launch + +feature {NONE} -- Initialization + + initialize + -- Initialize current service. + do + set_service_option ("port", 9090) + end + +end diff --git a/workbook/generating_response/status/application_execution.e b/workbook/generating_response/status/application_execution.e new file mode 100644 index 00000000..691513b6 --- /dev/null +++ b/workbook/generating_response/status/application_execution.e @@ -0,0 +1,138 @@ +note + description : "Basic Service that a simple web page to show the most common status codes" + date : "$Date$" + revision : "$Revision$" + +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute + -- Execute the incomming request + local + l_message: STRING + do + -- To send a response we need to setup, the status code and + -- the response headers. + if request.is_get_request_method then + if request.path_info.same_string ("/") then + response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", web_page.count.out]>>) + response.put_string (web_page) + elseif request.path_info.same_string ("/redirect") then + send_redirect (request, response, "https://httpwg.github.io/") + elseif request.path_info.same_string ("/bad_request") then + -- Here you can do some logic for example log, send emails to register the error, before to send the response. + create l_message.make_from_string (message_template) + l_message.replace_substring_all ("$title", "Bad Request") + l_message.replace_substring_all ("$status", "Bad Request 400") + response.put_header ({HTTP_STATUS_CODE}.bad_request, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>) + response.put_string (l_message) + elseif request.path_info.same_string ("/internal_error") then + -- Here you can do some logic for example log, send emails to register the error, before to send the response. + create l_message.make_from_string (message_template) + l_message.replace_substring_all ("$title", "Internal Server Error") + l_message.replace_substring_all ("$status", "Internal Server Error 500") + response.put_header ({HTTP_STATUS_CODE}.internal_server_error, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>) + response.put_string (l_message) + else + create l_message.make_from_string (message_template) + l_message.replace_substring_all ("$title", "Resource not found") + l_message.replace_substring_all ("$status", "Resource not found 400") + response.put_header ({HTTP_STATUS_CODE}.not_found, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>) + response.put_string (l_message) + end + else + create l_message.make_from_string (message_template) + l_message.replace_substring_all ("$title", "Method Not Allowed") + l_message.replace_substring_all ("$status", "Method Not Allowed 405") + -- Method not allowed + response.put_header ({HTTP_STATUS_CODE}.method_not_allowed, <<["Content-Type", "text/html"], ["Content-Length", l_message.count.out]>>) + response.put_string (l_message) + end + end + + +feature -- Home Page + + send_redirect (req: WSF_REQUEST; res: WSF_RESPONSE; a_location: READABLE_STRING_32) + -- Redirect to `a_location' + local + h: HTTP_HEADER + do + create h.make + h.put_content_type_text_html + h.put_current_date + h.put_location (a_location) + res.set_status_code ({HTTP_STATUS_CODE}.see_other) + res.put_header_text (h.string) + end + + web_page: STRING = "[ + + + + Example showing common status codes + + + +
    +
    +

    This page is an example of Status Code 200

    + +

    Redirect Example

    +

    Click on the following link will redirect you to the HTTP Specifcation, we can do the redirect from the HTML directly but + here we want to show you an exmaple, where you can do something before to send a redirect Redirect

    + +

    Bad Request

    +

    Click on the following link, the server will answer with a 400 error, check the status code Bad Request

    + +

    Internal Server Error

    +

    Click on the following link, the server will answer with a 500 error, check the status code Internal Error

    + +

    Resource not found

    +

    Click on the following link or add to the end of the url something like /1030303 the server will answer with a 404 error, check the status code Not found

    + +
    + + + +]" + +feature -- Generic Message + + message_template: STRING="[ + + + + $title + + + +
    +
    +

    This page is an example of $status

    + + + + +]" + + + + +end diff --git a/workbook/generating_response/status/status.ecf b/workbook/generating_response/status/status.ecf new file mode 100644 index 00000000..ddcf39a0 --- /dev/null +++ b/workbook/generating_response/status/status.ecf @@ -0,0 +1,42 @@ + +l + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workbook/handling_cookies/example/application.e b/workbook/handling_cookies/example/application.e new file mode 100644 index 00000000..cf939f60 --- /dev/null +++ b/workbook/handling_cookies/example/application.e @@ -0,0 +1,24 @@ +note + description: "Basic Service launcher" + +class + APPLICATION + +inherit + WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION] + redefine + initialize + end + +create + make_and_launch + +feature {NONE} -- Initialization + + initialize + -- Initialize current service. + do + set_service_option ("port", 9090) + end + +end diff --git a/workbook/handling_cookies/example/application_execution.e b/workbook/handling_cookies/example/application_execution.e new file mode 100644 index 00000000..56562904 --- /dev/null +++ b/workbook/handling_cookies/example/application_execution.e @@ -0,0 +1,141 @@ +note + description : "Basic Service that build a generic front to demonstrate the use of Cookies" + date : "$Date$" + revision : "$Revision$" + +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute + -- Execute the incomming request + local + l_message: STRING + l_header: HTTP_HEADER + l_time: HTTP_DATE + l_cookies: STRING + l_answer: STRING + do + -- all the cookies + create l_cookies.make_empty + across request.cookies as ic loop + l_cookies.append (ic.item.name) + l_cookies.append("
    ") + end + + if request.path_info.same_string ("/") then + create l_header.make + create l_answer.make_from_string (web_page) + if request.cookie ("_EWF_Cookie") = Void then + -- First access the the home page, find a cookie with specific name `_EWF_Cookie' + l_answer.replace_substring_all ("$header_title", "Hey, thanks for access our cool site, this is your first acess") + l_answer.replace_substring_all ("$cookies", l_cookies) + create l_time.make_now_utc + l_time.date_time.day_add (40) + l_header.put_cookie_with_expiration_date ("_EWF_Cookie", "EXAMPLE",l_time.date_time, "", Void, False, True) + else + -- No a new access + l_answer.replace_substring_all ("$header_title", "Welcome back, please check all the new things we have!!!") + l_answer.replace_substring_all ("$cookies", l_cookies) + end + l_header.put_content_type_text_html + l_header.put_content_length (l_answer.count) + response.put_header_text (l_header.string) + response.put_string (l_answer) + + elseif request.path_info.same_string ("/visitors") then + create l_header.make + create l_answer.make_from_string (visit_page) + if request.cookie ("_visits") = Void then + -- First access the the visit page, find a cookie with specific name `_visits' + l_answer.replace_substring_all ("$visit", "1") + l_answer.replace_substring_all ("$cookies", l_cookies) + create l_time.make_now_utc + l_time.date_time.day_add (40) + l_header.put_cookie_with_expiration_date ("_visits", "1",l_time.date_time, "/visitors", Void, False, True) + + else + if attached {WSF_STRING} request.cookie ("_visits") as l_visit then + create l_time.make_now_utc + l_time.date_time.day_add (40) + l_answer.replace_substring_all ("$visit", (l_visit.value.to_integer + 1).out ) + l_answer.replace_substring_all ("$cookies", l_cookies) + l_header.put_cookie_with_expiration_date ("_visits", (l_visit.value.to_integer + 1).out,l_time.date_time, "/visitors", Void, False, True) + end + end + create l_time.make_now_utc + l_time.date_time.second_add (120) + l_header.put_content_type_text_html + -- This cookie expires in 120 seconds, its valid for 120 seconds + l_header.put_cookie_with_expiration_date ("_Framework", "EWF",l_time.date_time, "/", Void, False, True) + -- This is a session cookie, valid only to the current browsing session. + l_header.put_cookie ("Session", "Cookie",Void, "/", Void, False, True) + l_header.put_content_length (l_answer.count) + response.add_header_text (l_header.string) + response.put_string (l_answer) + end + + end + +feature -- Home Page + + web_page: STRING = "[ + + + + EWF Handling Cookies + + +
    +

    $header_title

    +
    + +
    + Visitors +
    + +
    +

    Cookies for the home page

    + $cookies +
    + + +]" + + +visit_page: STRING = "[ + + + + EWF Handling Visit Page + + +
    +

    The number of visits is $visit

    +
    + +
    +

    Cookies for the Visit page

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

    $header_title

    +
    + +
    + Visitors +
    + +
    +

    Cookies for the home page

    + $cookies +
    + + +]" + + +visit_page: STRING = "[ + + + + EWF Handling Visit Page + + +
    +

    The number of visits is $visit

    +
    + +
    +

    Cookies for the Visit page

    + $cookies +
    +
    + +
    + Back to Home +
    + + + + +]" + +end + +``` + + +Nav: [Workbook](../../workbook) | [Generating Responses](../../generating_response/generating_response) diff --git a/workbook/handling_request/form.md b/workbook/handling_request/form.md new file mode 100644 index 00000000..bc07fdcd --- /dev/null +++ b/workbook/handling_request/form.md @@ -0,0 +1,311 @@ +--- +layout: default +title: form +base_url: ../../../ +--- +Nav: [Workbook](../../workbook) | [Basic Concepts](../../basics/basics) | [Handling Requests: Header Fields](../headers) + + +#Handling Requests: Form/Query Data + + +##### Table of Contents +- [Reading Form Data](#read) + - [Query Parameters](#query) + - [Form Parameters](#form_parameters) + - [Uniform Read](#uniform) +- [Reading Parameters and Values](#reading_pv) + - [How to read all parameters names](#all_names) + - [How to read single values](#single_values) + - [How to read multiple values](#multiple_values) + - [How to read table values](#table_values) +- [Reading raw data](#raw_data) +- [Upload Files](#upload) +- [Examples](#examples) + + +An HTML Form can handle GET and POST requests. +When we use a form with method GET, the data is attached at the end of the url for example: + +>http://wwww.example.com?key1=value1&...keyn=valuen + +If we use the method POST, the data is sent to the server in a different line. + +Extracting form data from the server side is one of the most tedious parts. If you do it by hand, you will need +to parse the input, you'll have to URL-decode the value. + +Here we will show you how to read input submitted by a user using a Form (GET and POST). + * How to handle missing values: + * client side validattion, server side validations, set default if it's a valid option. + * How to populate Eiffel objects from the request data. + + +## Reading Form Data +EWF [WSF_REQUEST]() class, provides features to handling this form parsing automatically. + + +### Query Parameters + + WSF_REQUEST.query_parameters: ITERABLE [WSF_VALUE] + -- All query parameters + + WSF_REQUEST.query_parameter (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE + -- Query parameter for name `a_name'. + +### Form Parameters + + WSF_REQUEST.form_parameters: ITERABLE [WSF_VALUE] + -- All form parameters sent by a POST + + WSF_REQUEST.form_parameter (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE + -- Field for name `a_name'. + +The values supplied to form_parameter and query_parameter are case sensitive. + + +### Read Data +The previous features, let you read the data one way for GET request and a different way for POST request. WSF_REQUEST provide a feature to read all the data in a uniform way. + + WSF_REQUEST.item (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE + -- Variable named `a_name' from any of the variables container + -- and following a specific order: form_, query_ and path_ parameters + +So, you use **WSF_REQUEST.item** feature exactly the same way for GET and POST request. + +>Note: if a query parameter has the same name as a form paramenter req.item will retrieve the form paramenter. Remember the precedence: form > query > path + + + +## Reading Parameters and Values + +Suppose we have the following HTML5 form using Method POST. This HTML5 form has client side form validation using the new HTML5 attribute, you can do the same using Javascript. So in this case if the user does not fill the fields as expected the form will not be submitted to the server. + +>Note: You want to validate on the server side because you can protect against the malicious user, who can easily bypass your JavaScript and submit dangerous input to the server. + +``` +

    EWF Handling Client Request: Form example

    +
    +
    + Personal details +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +``` + +### How to read all parameter names +To read all the parameters names we simple call WSF_REQUEST.form_parameters. + +``` + req: WSF_REQUEST + across req.form_parameters as ic loop show_parameter_name (ic.item.key) end +``` + +### How to read single values +To read a particular parameter, a single value, for example `given-name', we simple call WSF_REQUEST.form_parameter (a_name) and we check if it's attached to WSF_STRING (represents a String parameter) +``` + req: WSF_REQUEST + if attached {WSF_STRING} req.form_paramenter ('given-name') as l_given_name then + -- Work with the given parameter, for example populate an USER object + -- the argument is case sensitive + else + -- Value missing, check the name against the HTML form + end +``` + +### How to read multiple values + +To read multiple values, for example in the case of `languages', we simple call WSF_REQUEST.form_parameter (a_name) and we check if it's attached to WSF_MULTIPLE_STRING (represents a String parameter) + +``` + req: WSF_REQUEST + idioms: LIST[STRING] + -- the argument is case sensitive + if attached {WSF_MULTIPLE_STRING} req.form_paramenter ('languages') as l_languages then + -- Work with the given parameter, for example populate an USER object + -- Get all the associated values + create {ARRAYED_LIST[STRING]} idioms.make (2) + across l_languages as ic loop idioms.force (ic.item.value) end + elseif attached {WSF_STRING} req.form_paramenter ('languages') as l_language then + -- Value missing, check the name against the HTML form + create {ARRAYED_LIST[STRING]} idioms.make (1) + idioms.force (l_language.value) + else + -- Value missing + end +``` +In this case we are handling strings values, but in some cases you will need to do a conversion, betweend the strings that came from the request to map them to your domain model. + + +### How to read table values +This is particularly useful when you have a request with the following format + +``` ``` + +To read table values, for example in the case of `tab', we simple call WSF_REQUEST.form_parameter (a_name) and we check if it's attached to WSF_TABLE. + +``` +if attached {WSF_TABLE} req.query_parameter ("tab") as l_tab then + l_parameter_names.append ("
    ") + l_parameter_names.append (l_tab.name) + from + l_tab.values.start + until + l_tab.values.after + loop + l_parameter_names.append ("
    ") + l_parameter_names.append (l_tab.values.key_for_iteration) + if attached {WSF_STRING} l_tab.value (l_tab.values.key_for_iteration) as l_value then + l_parameter_names.append ("=") + l_parameter_names.append (l_value.value) + end + l_tab.values.forth + end +end +``` + +
    +## Reading Raw Data +You can also access the data in raw format, it means you will need to parse and url-decode it, and also you will not be able to use the previous features, by default, to enable that you need to call `req.set_raw_input_data_recorded (True)'. This feature (reading raw data) is useful if you are reading POST data with JSON or XML formats, but it's not convinient for HTML forms. + +To read raw data you need to do this + +``` + l_raw_data:STRING + + req.set_raw_input_data_recorded (True) -- + create l_raw_data.make_empty + req.read_input_data_into (l_raw_data) +``` + +> given-name=testr&family-name=test&dob=1976-08-26&email=test%40gmail.com&url=http%3A%2F%2Fwww.eiffelroom.com&phone=455555555555&languages=Spanish&languages=English + + +## Upload Files +How can we read data when the date come from an uploaded file/s?. +HTML supports a form element ``` ``` to upload a single file and ``` ``` to upload multiple files. + +So supose we have the following form + +``` + + + + EWF Handling Client Request: File Upload Example + + +

    EWF Handling Client Request: File Upload Example

    +
    +
    + Upload file/s +
    + +
    +
    + +
    +
    + + + +``` + +The class WSF_REQUEST has defines mechanism to work with uploaded files. We can call the query + +``` +WSF_REQUEST.has_uploaded_file: BOOLEAN + -- Has any uploaded file? +``` + +to check if the request form parameters has any uploaded file, and we can call the feature + +``` +WSF_REQUEST.uploaded_files: ITERABLE [WSF_UPLOADED_FILE] + -- uploaded files values + --| filename: original path from the user + --| type: content type + --| tmp_name: path to temp file that resides on server + --| tmp_base_name: basename of `tmp_name' + --| error: if /= 0 , there was an error : TODO ... + --| size: size of the file given by the http request +``` +to iterate over the uploaded files if any, and the details in the class [WSF_UPLOADED_FILE]. + +The following snipet code show how to work with Uploaded files using EWF [WSF_REQUEST] class, in the example +we build a simple html answer with basic information, if there is not uploaded files, we send a 400 status code +and a simple message. + +```eiffel + + if req.path_info.same_string ("/upload") then + -- Check if we have an uploaded file + if req.has_uploaded_file then + -- iterate over all the uploaded files + create l_answer.make_from_string ("

    Uploaded File/s


    ") + across req.uploaded_files as ic loop + l_answer.append ("FileName:") + l_answer.append (ic.item.filename) + l_answer.append ("
    Size:") + l_answer.append (ic.item.size.out) + l_answer.append ("
    ") + end + res.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-type","text/html"],["Content-lenght", l_answer.count.out]>>) + res.put_string (l_answer) + else + -- Here we should handle unexpected errors. + create l_answer.make_from_string ("No uploaded files
    ") + create l_answer.append ("Back to Home") + res.put_header ({HTTP_STATUS_CODE}.bad_request, <<["Content-type","text/html"],["Content-lenght", l_answer.count.out]>>) + res.put_string (l_answer) + end + else + -- Handle error + end +``` +The source code is available on Github. You can get it by running the command: + +```git clone https://github.com/EiffelWebFramework/ewf.git``` + +The example is located in the directory $PATH/ewf/doc/workbook/upload_file where $PATH is where you run git clone. + + + +## Examples +The source code is available on Github. You can get it by running the command: + +```git clone https://github.com/EiffelWebFramework/ewf.git``` + +The GET example is located in the directory $PATH/ewf/doc/workbook/form/get, and the post example is located in the directory $PATH/ewf_examples/workbook/form/post where $PATH is where you run git clone . To run open it using Eiffel Studio or just run theg following command + +```estudio -config .ecf -target ``` + +>Note: replace and with the corresponding values. + + +Nav: [Workbook](../../workbook) | [Basic Concepts](../../basics/basics) | [Handling Requests: Header Fields](../headers) diff --git a/workbook/handling_request/form/get/application.e b/workbook/handling_request/form/get/application.e new file mode 100644 index 00000000..cf939f60 --- /dev/null +++ b/workbook/handling_request/form/get/application.e @@ -0,0 +1,24 @@ +note + description: "Basic Service launcher" + +class + APPLICATION + +inherit + WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION] + redefine + initialize + end + +create + make_and_launch + +feature {NONE} -- Initialization + + initialize + -- Initialize current service. + do + set_service_option ("port", 9090) + end + +end diff --git a/workbook/handling_request/form/get/application_execution.e b/workbook/handling_request/form/get/application_execution.e new file mode 100644 index 00000000..1fb05acd --- /dev/null +++ b/workbook/handling_request/form/get/application_execution.e @@ -0,0 +1,101 @@ +note + description : "Basic Service that show how to handle a GET request" + date : "$Date$" + revision : "$Revision$" + +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute + -- Execute the incomming request + local + file: WSF_FILE_RESPONSE + l_parameter_names: STRING + l_answer: STRING + idioms: LIST[STRING] + l_raw_data: STRING + do + if request.is_get_request_method then + if request.path_info.same_string ("/") then + create file.make_html ("form.html") + response.send (file) + elseif request.path_info.same_string ("/search") then + + -- (1) the parameter is case sensitive + if not (attached request.query_parameter ("GIVEN-NAME")) then + -- Wrong `GIVEN-NAME' need to be in lower case. + end + + -- (2) Multiple values + if attached {WSF_MULTIPLE_STRING} request.query_parameter ("languages") as l_languages then + -- Get all the associated values + create {ARRAYED_LIST[STRING]} idioms.make (2) + across l_languages as ic loop idioms.force (ic.item.value) end + elseif attached {WSF_STRING} request.query_parameter ("languages") as l_language then + -- Single value + print (l_language.value) + else + -- Value Missing + end + + -- Read the all parameters names and his values. + create l_parameter_names.make_from_string ("

    Parameters Names

    ") + l_parameter_names.append ("
    ") + create l_answer.make_from_string ("

    Parameter Names and Values

    ") + l_answer.append ("
    ") + across request.query_parameters as ic loop + l_parameter_names.append (ic.item.key) + l_parameter_names.append ("
    ") + + l_answer.append (ic.item.key) + l_answer.append_character ('=') + if attached {WSF_STRING} request.query_parameter (ic.item.key) as l_value then + l_answer.append_string (l_value.value) + end + l_answer.append ("
    ") + end + + l_parameter_names.append ("
    ") + l_parameter_names.append_string (l_answer) + response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_parameter_names.count.out]>>) + response.put_string (l_parameter_names) + elseif request.path_info.same_string ("/link") then + -- WSF_TABLE example + create l_parameter_names.make_from_string ("

    Parameters Name

    ") + if attached {WSF_TABLE} request.query_parameter ("tab") as l_tab then + l_parameter_names.append ("
    ") + l_parameter_names.append (l_tab.name) + + from + l_tab.values.start + until + l_tab.values.after + loop + l_parameter_names.append ("
    ") + l_parameter_names.append (l_tab.values.key_for_iteration) + if attached {WSF_STRING} l_tab.value (l_tab.values.key_for_iteration) as l_value then + l_parameter_names.append ("=") + l_parameter_names.append (l_value.value) + end + l_tab.values.forth + end + l_parameter_names.append ("
    ") + response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_parameter_names.count.out]>>) + response.put_string (l_parameter_names) + + end + else + -- Here we should handle unexpected errors. + end + end + end + +end diff --git a/workbook/handling_request/form/get/form.ecf b/workbook/handling_request/form/get/form.ecf new file mode 100644 index 00000000..5841d747 --- /dev/null +++ b/workbook/handling_request/form/get/form.ecf @@ -0,0 +1,42 @@ + + + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workbook/handling_request/form/get/form.html b/workbook/handling_request/form/get/form.html new file mode 100644 index 00000000..36673ba1 --- /dev/null +++ b/workbook/handling_request/form/get/form.html @@ -0,0 +1,49 @@ + + + + EWF Handling Client Request: Search Form example + + +

    EWF Handling Search Request: Form example

    +
    +
    + Search by +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +

    Example link

    +
    + Link +
    + + \ No newline at end of file diff --git a/workbook/handling_request/form/post/application.e b/workbook/handling_request/form/post/application.e new file mode 100644 index 00000000..cf939f60 --- /dev/null +++ b/workbook/handling_request/form/post/application.e @@ -0,0 +1,24 @@ +note + description: "Basic Service launcher" + +class + APPLICATION + +inherit + WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION] + redefine + initialize + end + +create + make_and_launch + +feature {NONE} -- Initialization + + initialize + -- Initialize current service. + do + set_service_option ("port", 9090) + end + +end diff --git a/workbook/handling_request/form/post/application_execution.e b/workbook/handling_request/form/post/application_execution.e new file mode 100644 index 00000000..d893804e --- /dev/null +++ b/workbook/handling_request/form/post/application_execution.e @@ -0,0 +1,83 @@ +note + description : "Reading Parameters from a HTML FORM (method POST) " + date : "$Date$" + revision : "$Revision$" + +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute + -- Execute the incomming request + local + file: WSF_FILE_RESPONSE + l_parameter_names: STRING + l_answer: STRING + idioms: LIST[STRING] + l_raw_data: STRING + do + if request.is_get_request_method then + create file.make_html ("form.html") + response.send (file) + elseif request.is_post_request_method then + request.set_raw_input_data_recorded (True) + + -- (3) Read Raw Data + create l_raw_data.make_empty + request.read_input_data_into (l_raw_data) + + -- (1) the parameter is case sensitive + if not (attached request.form_parameter ("GIVEN-NAME")) then + -- Wrong `GIVEN-NAME' need to be in lower case. + end + + -- (2) Multiple values + if attached {WSF_MULTIPLE_STRING} request.form_parameter ("languages") as l_languages then + -- Get all the associated values + create {ARRAYED_LIST[STRING]} idioms.make (2) + across l_languages as ic loop idioms.force (ic.item.value) end + elseif attached {WSF_STRING} request.form_parameter ("langauges") as l_language then + -- Single value + print (l_language.value) + else + -- Value Missing + end + + -- Read the all parameters names and his values. + create l_parameter_names.make_from_string ("

    Parameters Names

    ") + l_parameter_names.append ("
    ") + create l_answer.make_from_string ("

    Parameter Names and Values

    ") + l_answer.append ("
    ") + + across request.form_parameters as ic loop + l_parameter_names.append (ic.item.key) + l_parameter_names.append ("
    ") + + l_answer.append (ic.item.key) + l_answer.append_character ('=') + if attached {WSF_STRING} request.form_parameter (ic.item.key) as l_value then + l_answer.append_string (l_value.value) + end + l_answer.append ("
    ") + end + + l_parameter_names.append ("
    ") + l_parameter_names.append_string (l_answer) + l_parameter_names.append ("
    ") + l_parameter_names.append ("

    Raw content

    ") + l_parameter_names.append (l_raw_data) + response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_parameter_names.count.out]>>) + response.put_string (l_parameter_names) + else + -- Here we should handle unexpected errors. + end + end + +end diff --git a/workbook/handling_request/form/post/form.ecf b/workbook/handling_request/form/post/form.ecf new file mode 100644 index 00000000..547713d6 --- /dev/null +++ b/workbook/handling_request/form/post/form.ecf @@ -0,0 +1,42 @@ + + + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workbook/handling_request/form/post/form.html b/workbook/handling_request/form/post/form.html new file mode 100644 index 00000000..1e1b28c1 --- /dev/null +++ b/workbook/handling_request/form/post/form.html @@ -0,0 +1,56 @@ + + + + EWF Handling Client Request: Form example + + +

    EWF Handling Client Request: Form example

    +
    +
    + Personal details +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    + + \ No newline at end of file diff --git a/workbook/handling_request/headers.md b/workbook/handling_request/headers.md new file mode 100644 index 00000000..27486b29 --- /dev/null +++ b/workbook/handling_request/headers.md @@ -0,0 +1,441 @@ +--- +layout: default +title: headers +base_url: ../../../ +--- +Nav: [Workbook](../../workbook) | [Handling Requests: Form/Query parameters](../form) | [Generating Responses](../../generating_response/generating_response) + + +#Handling Requests: Headers + +##### Introduction +- The [HTTP request header fields (also known as "headers")](https://httpwg.github.io/specs/rfc7231.html#request.header.fields) are set by the client (usually web browser) and sent in the header of the http request text (see http protocol), as opposed to form or query parameters [Form Data](). +- Query parameters are encoded in the URL [GET requests](https://httpwg.github.io/specs/rfc7230.html#http.message). +- Form parameters are encoded in the request message for [POST/PUT requests.](https://httpwg.github.io/specs/rfc7230.html#http.message). + +A request usually includes the header fields [Accept, Accept-Encoding, Connection, Cookie, Host, Referer, and User-Agent](https://httpwg.github.io/specs/rfc7231.html#request.header), defining important information about how the server should process the request. And then, the server needs to read the request header fields to use those informations. + +##### Table of Contents +- [Reading HTTP Header fields](#read_header) +- [Reading HTTP Request line](#read_line) +- [Understanding HTTP header fields](#understand) + - [Accept](#accept) + - [Accept-Charset](#accept_charset) + - [Accept-Encoding](#accept_encoding) + - [Accept-Language](#accept_language) + - [Connection](#connection) + - [Authorization](#authorization) + - [Content-length](#content-length) + - [Cookie](#cookie) + - [Host](#host) + - [If-Modified-Since](#if-modified-since) + - [If-Unmodified-Since](#if-unmodified-since) + - [Referer](#referer) + - [User-Agent](#user-agent) +- [Example: Request Headers](#example) +- [Example: How to compress pages](#compress) +- [Example: Detecting Browser Types](#browser-types) +- [Example: CGI Variables](#cgi-variables) + + +That section explains how to read HTTP information sent by the browser via the request header fields. Mostly by defining the most important HTTP request header fields, for more information, read [HTTP 1.1 specification](https://httpwg.github.io/specs/). + +## Prerequisites +The Eiffel Web Framework is using the traditional Common Gateway Interface (CGI) programming interface to access the header fields, query and form parameters. +Among other, this means the header fields are exposed with associated CGI field names: +- the header field name are uppercased, and any dash "-" replaced by underscore "_". +- and also prefixed by "HTTP_" except for CONTENT_TYPE and CONTENT_LENGTH. +- For instance `X-Server` will be known as `HTTP_X_SERVER`. + + +## Reading HTTP Header fields +EWF [WSF_REQUEST]() class provides features to access HTTP headers. + +Reading most headers is straightforward by calling: +- the corresponding `http_*` functions such as `http_accept` for header "Accept". +- or indirectly using the `meta_string_variable (a_name)` function by passing the associated CGI field name. +In both cases, if the related header field is supplied by the request, the result is a string value, otherwise it is Void. + +Note: always check if the result of those functions is non-void before using it. + +* Cookies: + - To iterate on all cookies valued, use `cookies: ITERABLE [WSF_VALUE]` + - To retrieve a specific cookie value, use `cookie (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE` + +* Authorization + - To read the Authorization header, first check its type with: `auth_type: detachable READABLE_STRING_8` + - And its value via `http_authorization: detachable READABLE_STRING_8 --Contents of the Authorization: header from the current wgi_request, if there is one.` + +* Content_length + - If supplied, get the content length as an string value: `content_length: detachable READABLE_STRING_8` + - or directly as a natural value with `content_length_value: NATURAL_64` + +* Content_type + - If supplied, get the content type as an string value with `content_type: detachable HTTP_CONTENT_TYPE` + +Due to CGI compliance, the original header names are not available, however the function `raw_header_data` may return the http header data as a string value (warning: this may not be available, depending on the underlying connector). Apart from very specific cases (proxy, debugging, ...), it should not be useful. +Note: CGI variables are information about the current request (and also about the server). Some are based on the HTTP request line and headers (e.g., form parameters, query parameters), others are derived from the socket itself (e.g., the name and IP address of the requesting host), and still others are taken from server installation parameters (e.g., the mapping of URLs to actual paths). + + +####Retrieve information from the Request Line + +For convenience, the following sections refer to a request starting with line: +``` +GET http://eiffel.org/search?q=EiffelBase HTTP/1.1 +``` + +Overview of the features + +* HTTP method + - The function `request_method: READABLE_STRING_8` gives access to the HTTP request method, (usually GET or POST in conventional Web Applications), but with the raise of REST APIs other methods are also frequently used such as HEAD, PUT, DELETE, OPTIONS, or TRACE. + A few functions helps determining quickly the nature of the request method: + - `is_get_request_method: BOOLEAN -- Is Current a GET request method?` + - `is_put_request_method: BOOLEAN -- Is Current a PUT request method?` + - `is_post_request_method: BOOLEAN -- Is Current a POST request method?` + - `is_delete_request_method: BOOLEAN -- Is Current a DELETE request method?` + + In our example the request method is `GET` + + * Query String + - The query string for the example is `q=EiffelBase` + - `query_string: READABLE_STRING_8` + + * Protocol + - The feature return the third part of the request line, which is generally HTTP/1.0 or HTTP/1.1. + - `server_protocol: READABLE_STRING_8` + In the example the request method is `HTTP/1.1` + + + +#### 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). + + + * [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]() + + + * [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). + + + * [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. + + + * [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). + + + * [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. + + + * [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. + + + * [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. + + + * [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). + + + * [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. + + + * [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. + + + * [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”). + + + * [Referer](https://httpwg.github.io/specs/rfc7231.html#header.referer) + - The "Referer" header field allows the user agent to specify a URI reference for the resource from which the target URI was obtained (i.e., the "referrer", though the field name is misspelled). A user agent MUST NOT include the fragment and userinfo components of the URI reference [RFC3986], if any, when generating the Referer field value. This header indicates the URL of the referring Web page. + +For example, if you are at Web page A and click on a link to Web page B, the URL of Web page A is +included in the Referer header when the browser requests Web page B. + + + * [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. + + +#### Building a Table of All Request Headers + +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). + +The service also writes three components of the main request line (method, URI, and protocol), and also the raw header. + +```eiffel +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the incomming request + local + l_raw_data: STRING + l_page_response: STRING + l_rows: STRING + do + create l_page_response.make_from_string (html_template) + if req.path_info.same_string ("/") then + + -- HTTP method + l_page_response.replace_substring_all ("$http_method", req.request_method) + -- URI + l_page_response.replace_substring_all ("$uri", req.path_info) + -- Protocol + l_page_response.replace_substring_all ("$protocol", req.server_protocol) + + -- Fill the table rows with HTTP Headers + create l_rows.make_empty + across req.meta_variables as ic loop + if ic.item.name.starts_with ("HTTP_") then + l_rows.append ("") + l_rows.append ("") + l_rows.append (ic.item.name) + l_rows.append ("") + l_rows.append ("") + l_rows.append (ic.item.value) + l_rows.append ("") + l_rows.append ("") + end + end + + l_page_response.replace_substring_all ("$rows", l_rows) + + -- Reading the raw header + if attached req.raw_header_data as l_raw_header then + l_page_response.replace_substring_all ("$raw_header", l_raw_header) + end + res.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_page_response.count.out]>>) + res.put_string (l_page_response) + end + end + + html_template: STRING = "[ + + + + + + + +

    EWF service example: Showing Request Headers

    + + HTTP METHOD:$http_method
    + URI:$uri
    + PROTOCOL:$protocol
    + REQUEST TIME:$time
    + +
    + + + + + + + + + $rows + +
    Header NameHeader Value
    + + +

    Raw header

    + + $raw_header + + + ]" +end +``` + + +#### How to compress pages +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](../headers/browser_name/application.e) that sends browser-specific responses. + +The examples uses the ideas based on the [Browser detection using the user agent](https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent) article. +Basically the code check if the header user_agent exist and then call the ```browser_name (a_user_agent: READABLE_STRING_8): READABLE_STRING_32``` +feature to retrieve the current browser name or Unknown in other case. + +```eiffel +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the incomming request + local + l_raw_data: STRING + l_page_response: STRING + l_rows: STRING + do + create l_page_response.make_from_string (html_template) + if req.path_info.same_string ("/") then + + -- retrieve the user-agent + if attached req.http_user_agent as l_user_agent then + l_page_response.replace_substring_all ("$user_agent", l_user_agent) + l_page_response.replace_substring_all ("$browser", browser_name (l_user_agent)) + else + l_page_response.replace_substring_all ("$user_agent", "[]") + l_page_response.replace_substring_all ("$browser", "Unknown, the user-agent was not present.") + end + res.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_page_response.count.out]>>) + res.put_string (l_page_response) + end + end + + +feature -- Browser utility + + browser_name (a_user_agent: READABLE_STRING_8): READABLE_STRING_32 + -- Browser name. + -- Must contain Must not contain + -- Firefox Firefox/xyz Seamonkey/xyz + -- Seamonkey Seamonkey/xyz + -- Chrome Chrome/xyz Chromium/xyz + -- Chromium Chromium/xyz + -- Safari Safari/xyz Chrome/xyz + -- Chromium/xyz + -- Opera OPR/xyz [1] + -- Opera/xyz [2] + -- Internet Explorer ;MSIE xyz; Internet Explorer doesn't put its name in the BrowserName/VersionNumber format + + do + if + a_user_agent.has_substring ("Firefox") and then + not a_user_agent.has_substring ("Seamonkey") + then + Result := "Firefox" + elseif a_user_agent.has_substring ("Seamonkey") then + Result := "Seamonkey" + elseif a_user_agent.has_substring ("Chrome") and then not a_user_agent.has_substring ("Chromium")then + Result := "Chrome" + elseif a_user_agent.has_substring ("Chromium") then + Result := "Chromiun" + elseif a_user_agent.has_substring ("Safari") and then not (a_user_agent.has_substring ("Chrome") or else a_user_agent.has_substring ("Chromium")) then + Result := "Safari" + elseif a_user_agent.has_substring ("OPR") or else a_user_agent.has_substring ("Opera") then + Result := "Opera" + elseif a_user_agent.has_substring ("MSIE") or else a_user_agent.has_substring ("Trident")then + Result := "Internet Explorer" + else + Result := "Unknown" + end + end + + + html_template: STRING = "[ + + + + + + +

    EWF service example: Showing Browser Dectection Using User-Agent


    + + User Agent: $user_agent
    + +

    Enjoy using $browser

    + + + ]" +end +``` +Let see some results, we will show the html returned + +**Internet Explorer** +--- +``` +

    EWF service example: Showing Browser Dectection Using User-Agent


    + +User Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; MDDCJS; rv:11.0) like Gecko
    + +

    Enjoy using Internet Explorer

    +``` + +**Chrome** +--- +``` +

    EWF service example: Showing Browser Dectection Using User-Agent


    + +User Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.91 Safari/537.36
    + +

    Enjoy using Chrome

    +``` + +As an exercise, try to write a similar service to retrieve the OS family using the User-Agent information. + + + +[Meta-variables](https://tools.ietf.org/html/rfc3875#section-4.1) contains data about the request, they are identified by case-insensitive names. In this section, the purpose is to show a similar example to HEADERS FIELDS, but in this case building a table showing the standard CGI variables. + + + * [AUTH_TYPE](https://tools.ietf.org/html/rfc3875#section-4.1.1). + * [CONTENT_LENGTH](https://tools.ietf.org/html/rfc3875#section-4.1.2) + * [CONTENT_TYPE](https://tools.ietf.org/html/rfc3875#section-4.1.3) + * [GATEWAY_INTERFACE](https://tools.ietf.org/html/rfc3875#section-4.1.4) + * [PATH_INFO](https://tools.ietf.org/html/rfc3875#section-4.1.5) + * [PATH_TRANSLATED](https://tools.ietf.org/html/rfc3875#section-4.1.6) + * [QUERY_STRING](https://tools.ietf.org/html/rfc3875#section-4.1.7) + * [REMOTE_ADDR](https://tools.ietf.org/html/rfc3875#section-4.1.8) + * [REMOTE_HOST](https://tools.ietf.org/html/rfc3875#section-4.1.9) + * [REMOTE_IDENT](https://tools.ietf.org/html/rfc3875#section-4.1.10) + * [REMOTE_USER](https://tools.ietf.org/html/rfc3875#section-4.1.11) + * [REQUEST_METHOD](https://tools.ietf.org/html/rfc3875#section-4.1.12) + * [SCRIPT_NAME](https://tools.ietf.org/html/rfc3875#section-4.1.13) + * [SERVER_NAME](https://tools.ietf.org/html/rfc3875#section-4.1.14) + * [SERVER_PROTOCOL](https://tools.ietf.org/html/rfc3875#section-4.1.15) + * [SERVER_SOFTWARE](https://tools.ietf.org/html/rfc3875#section-4.1.16) + +**Example** +An [EWF service](../cgi_variables/application.e) that shows the CGI variables, creates a table showing the values of all the CGI variables. + + +Nav: [Workbook](../../workbook) | [Handling Requests: Form/Query parameters](../form) | [Generating Responses](../../generating_response/generating_response) + diff --git a/workbook/handling_request/headers/browser_name/application.e b/workbook/handling_request/headers/browser_name/application.e new file mode 100644 index 00000000..cf939f60 --- /dev/null +++ b/workbook/handling_request/headers/browser_name/application.e @@ -0,0 +1,24 @@ +note + description: "Basic Service launcher" + +class + APPLICATION + +inherit + WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION] + redefine + initialize + end + +create + make_and_launch + +feature {NONE} -- Initialization + + initialize + -- Initialize current service. + do + set_service_option ("port", 9090) + end + +end diff --git a/workbook/handling_request/headers/browser_name/application_execution.e b/workbook/handling_request/headers/browser_name/application_execution.e new file mode 100644 index 00000000..8ef85fd0 --- /dev/null +++ b/workbook/handling_request/headers/browser_name/application_execution.e @@ -0,0 +1,97 @@ +note + description : "Basic Service that Read a Request, a " + date : "$Date$" + revision : "$Revision$" + EIS: "name=Browser detection using user agent","src=https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent", "protocol=url" + +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute + -- Execute the incomming request + local + l_raw_data: STRING + l_page_response: STRING + l_rows: STRING + do + create l_page_response.make_from_string (html_template) + if request.path_info.same_string ("/") then + + -- retrieve the user-agent + if attached request.http_user_agent as l_user_agent then + l_page_response.replace_substring_all ("$user_agent", l_user_agent) + l_page_response.replace_substring_all ("$browser", browser_name (l_user_agent)) + else + l_page_response.replace_substring_all ("$user_agent", "[]") + l_page_response.replace_substring_all ("$browser", "Unknown, the user-agent was not present.") + end + response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_page_response.count.out]>>) + response.put_string (l_page_response) + end + end + + +feature -- Browser utility + + browser_name (a_user_agent: READABLE_STRING_8): STRING_8 + -- Must contain Must not contain + -- Firefox Firefox/xyz Seamonkey/xyz + -- Seamonkey Seamonkey/xyz + -- Chrome Chrome/xyz Chromium/xyz + -- Chromium Chromium/xyz + -- Safari Safari/xyz Chrome/xyz + -- Chromium/xyz + -- Opera OPR/xyz [1] + -- Opera/xyz [2] + -- Internet Explorer ;MSIE xyz; Internet Explorer doesn't put its name in the BrowserName/VersionNumber format + + do + if + a_user_agent.has_substring ("Firefox") and then + not a_user_agent.has_substring ("Seamonkey") + then + Result := "Firefox" + elseif a_user_agent.has_substring ("Seamonkey") then + Result := "Seamonkey" + elseif a_user_agent.has_substring ("Chrome") and then not a_user_agent.has_substring ("Chromium")then + Result := "Chrome" + elseif a_user_agent.has_substring ("Chromium") then + Result := "Chromiun" + elseif a_user_agent.has_substring ("Safari") and then not (a_user_agent.has_substring ("Chrome") or else a_user_agent.has_substring ("Chromium")) then + Result := "Safari" + elseif a_user_agent.has_substring ("OPR") or else a_user_agent.has_substring ("Opera") then + Result := "Opera" + elseif a_user_agent.has_substring ("MSIE") or else a_user_agent.has_substring ("Trident")then + Result := "Internet Explorer" + else + Result := "Unknown" + end + end + + + html_template: STRING = "[ + + + + + + +

    EWF service example: Showing Browser Dectection Using User-Agent


    + + User Agent: $user_agent
    + +

    Enjoy using $browser

    + + + ]" + + +end diff --git a/workbook/handling_request/headers/browser_name/browsers.ecf b/workbook/handling_request/headers/browser_name/browsers.ecf new file mode 100644 index 00000000..7a798643 --- /dev/null +++ b/workbook/handling_request/headers/browser_name/browsers.ecf @@ -0,0 +1,42 @@ + + + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workbook/handling_request/headers/cgi_variables/application.e b/workbook/handling_request/headers/cgi_variables/application.e new file mode 100644 index 00000000..cf939f60 --- /dev/null +++ b/workbook/handling_request/headers/cgi_variables/application.e @@ -0,0 +1,24 @@ +note + description: "Basic Service launcher" + +class + APPLICATION + +inherit + WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION] + redefine + initialize + end + +create + make_and_launch + +feature {NONE} -- Initialization + + initialize + -- Initialize current service. + do + set_service_option ("port", 9090) + end + +end diff --git a/workbook/handling_request/headers/cgi_variables/application_execution.e b/workbook/handling_request/headers/cgi_variables/application_execution.e new file mode 100644 index 00000000..10063855 --- /dev/null +++ b/workbook/handling_request/headers/cgi_variables/application_execution.e @@ -0,0 +1,303 @@ +note + description : "Basic Service that shows the standard CGI variables" + date : "$Date$" + revision : "$Revision$" + EIS: "name=CGI specification","src=(https://tools.ietf.org/html/rfc3875", "protocol=url" + +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute + -- Execute the incomming request + local + l_raw_data: STRING + l_page_response: STRING + l_rows: STRING + do + create l_page_response.make_from_string (html_template) + if request.path_info.same_string ("/") then + + -- HTTP method + l_page_response.replace_substring_all ("$http_method", request.request_method) + -- URI + l_page_response.replace_substring_all ("$uri", request.path_info) + -- Protocol + l_page_response.replace_substring_all ("$protocol", request.server_protocol) + + -- Fill the table rows with CGI standard variables + create l_rows.make_empty + + -- Auth_type + l_rows.append ("") + l_rows.append ("") + l_rows.append ("AUTH_TYPE") + l_rows.append ("") + l_rows.append ("") + if attached request.auth_type as l_type then + l_rows.append (l_type) + else + l_rows.append ("Not present") + end + l_rows.append ("") + l_rows.append ("") + + + -- Content length + l_rows.append ("") + l_rows.append ("") + l_rows.append ("CONTENT_LENGTH") + l_rows.append ("") + l_rows.append ("") + if attached request.content_length as l_content_length then + l_rows.append (l_content_length) + else + l_rows.append ("Not present") + end + l_rows.append ("") + l_rows.append ("") + + -- Content length + l_rows.append ("") + l_rows.append ("") + l_rows.append ("CONTENT_TYPE") + l_rows.append ("") + l_rows.append ("") + if attached request.content_type as l_content_type then + l_rows.append (l_content_type.string) + else + l_rows.append ("Not present") + end + l_rows.append ("") + l_rows.append ("") + + + -- Gateway interface + l_rows.append ("") + l_rows.append ("") + l_rows.append ("GATEWAY_INTERFACE") + l_rows.append ("") + l_rows.append ("") + if attached request.gateway_interface as l_gateway_interface then + l_rows.append (l_gateway_interface) + else + l_rows.append ("Not present") + end + l_rows.append ("") + l_rows.append ("") + + + -- Path info + l_rows.append ("") + l_rows.append ("") + l_rows.append ("PATH_INFO") + l_rows.append ("") + l_rows.append ("") + if attached request.path_info as l_path_info then + l_rows.append (l_path_info) + else + l_rows.append ("Not present") + end + l_rows.append ("") + l_rows.append ("") + + -- Path translated + l_rows.append ("") + l_rows.append ("") + l_rows.append ("PATH_TRANSLATED") + l_rows.append ("") + l_rows.append ("") + if attached request.path_translated as l_path_translated then + l_rows.append (l_path_translated) + else + l_rows.append ("Not present") + end + l_rows.append ("") + l_rows.append ("") + + -- Query string + l_rows.append ("") + l_rows.append ("") + l_rows.append ("QUERY_STRING") + l_rows.append ("") + l_rows.append ("") + if attached request.query_string as l_query_string then + l_rows.append (l_query_string) + else + l_rows.append ("Not present") + end + l_rows.append ("") + l_rows.append ("") + + -- Remote addr + l_rows.append ("") + l_rows.append ("") + l_rows.append ("REMOTE_ADDR") + l_rows.append ("") + l_rows.append ("") + l_rows.append (request.remote_addr) + l_rows.append ("") + l_rows.append ("") + + + -- Remote host + l_rows.append ("") + l_rows.append ("") + l_rows.append ("REMOTE_HOST") + l_rows.append ("") + l_rows.append ("") + if attached request.remote_host as l_remote_host then + l_rows.append (l_remote_host) + else + l_rows.append ("Not present") + end + l_rows.append ("") + l_rows.append ("") + + + + -- Remote ident + l_rows.append ("") + l_rows.append ("") + l_rows.append ("REMOTE_IDENT") + l_rows.append ("") + l_rows.append ("") + if attached request.remote_ident as l_remote_ident then + l_rows.append (l_remote_ident) + else + l_rows.append ("Not present") + end + l_rows.append ("") + l_rows.append ("") + + + -- Remote user + l_rows.append ("") + l_rows.append ("") + l_rows.append ("REMOTE_USER") + l_rows.append ("") + l_rows.append ("") + if attached request.remote_user as l_remote_user then + l_rows.append (l_remote_user) + else + l_rows.append ("Not present") + end + l_rows.append ("") + l_rows.append ("") + + + -- Request method + l_rows.append ("") + l_rows.append ("") + l_rows.append ("REQUEST_METHOD") + l_rows.append ("") + l_rows.append ("") + l_rows.append (request.request_method) + l_rows.append ("") + l_rows.append ("") + + + -- Script name + l_rows.append ("") + l_rows.append ("") + l_rows.append ("SCRIPT_NAME") + l_rows.append ("") + l_rows.append ("") + l_rows.append (request.script_name) + l_rows.append ("") + l_rows.append ("") + + -- Server name + l_rows.append ("") + l_rows.append ("") + l_rows.append ("SERVER_NAME") + l_rows.append ("") + l_rows.append ("") + l_rows.append (request.server_name) + l_rows.append ("") + l_rows.append ("") + + -- Server protocol + l_rows.append ("") + l_rows.append ("") + l_rows.append ("SERVER_PROTOCOL") + l_rows.append ("") + l_rows.append ("") + l_rows.append (request.server_protocol) + l_rows.append ("") + l_rows.append ("") + + -- Server software + l_rows.append ("") + l_rows.append ("") + l_rows.append ("SERVER_SOFTWARE") + l_rows.append ("") + l_rows.append ("") + l_rows.append (request.server_software) + l_rows.append ("") + l_rows.append ("") + + + l_page_response.replace_substring_all ("$rows", l_rows) + + -- Reading the raw header + if attached request.raw_header_data as l_raw_header then + l_page_response.replace_substring_all ("$raw_header", l_raw_header) + end + response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_page_response.count.out]>>) + response.put_string (l_page_response) + end + end + + + + html_template: STRING = "[ + + + + + + + +

    EWF service example: Showing Standard CGI Variables

    + + HTTP METHOD:$http_method
    + URI:$uri
    + PROTOCOL:$protocol
    + +
    + + + + + + + + + $rows + +
    CGI NameValue
    + + +

    Raw header

    + + $raw_header + + + ]" + + +end diff --git a/workbook/handling_request/headers/cgi_variables/cgi_variables.ecf b/workbook/handling_request/headers/cgi_variables/cgi_variables.ecf new file mode 100644 index 00000000..57e837d6 --- /dev/null +++ b/workbook/handling_request/headers/cgi_variables/cgi_variables.ecf @@ -0,0 +1,42 @@ + + + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workbook/handling_request/headers/header_fields/application.e b/workbook/handling_request/headers/header_fields/application.e new file mode 100644 index 00000000..cf939f60 --- /dev/null +++ b/workbook/handling_request/headers/header_fields/application.e @@ -0,0 +1,24 @@ +note + description: "Basic Service launcher" + +class + APPLICATION + +inherit + WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION] + redefine + initialize + end + +create + make_and_launch + +feature {NONE} -- Initialization + + initialize + -- Initialize current service. + do + set_service_option ("port", 9090) + end + +end diff --git a/workbook/handling_request/headers/header_fields/application_execution.e b/workbook/handling_request/headers/header_fields/application_execution.e new file mode 100644 index 00000000..9f3242fc --- /dev/null +++ b/workbook/handling_request/headers/header_fields/application_execution.e @@ -0,0 +1,105 @@ +note + description : "Basic Service that Read Request Headers" + date : "$Date$" + revision : "$Revision$" + +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute + -- Execute the incomming request + local + l_raw_data: STRING + l_page_response: STRING + l_rows: STRING + do + create l_page_response.make_from_string (html_template) + if request.path_info.same_string ("/") then + + -- HTTP method + l_page_response.replace_substring_all ("$http_method", request.request_method) + -- URI + l_page_response.replace_substring_all ("$uri", request.path_info) + -- Protocol + l_page_response.replace_substring_all ("$protocol", request.server_protocol) + + -- Fill the table rows with HTTP Headers + create l_rows.make_empty + across request.meta_variables as ic loop + if ic.item.name.starts_with ("HTTP_") then + l_rows.append ("") + l_rows.append ("") + l_rows.append (ic.item.name) + l_rows.append ("") + l_rows.append ("") + l_rows.append (ic.item.value) + l_rows.append ("") + l_rows.append ("") + end + end + + l_page_response.replace_substring_all ("$rows", l_rows) + + -- Reading the raw header + if attached request.raw_header_data as l_raw_header then + l_page_response.replace_substring_all ("$raw_header", l_raw_header) + end + response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", l_page_response.count.out]>>) + response.put_string (l_page_response) + end + end + + + + html_template: STRING = "[ + + + + + + + +

    EWF service example: Showing Request Headers

    + + HTTP METHOD:$http_method
    + URI:$uri
    + PROTOCOL:$protocol
    + REQUEST TIME:$time
    + +
    + + + + + + + + + $rows + +
    Header NameHeader Value
    + + +

    Raw header

    + + $raw_header + + + ]" + + +end diff --git a/workbook/handling_request/headers/header_fields/header_fields.ecf b/workbook/handling_request/headers/header_fields/header_fields.ecf new file mode 100644 index 00000000..cec11da3 --- /dev/null +++ b/workbook/handling_request/headers/header_fields/header_fields.ecf @@ -0,0 +1,42 @@ + + + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workbook/handling_request/upload_file/application.e b/workbook/handling_request/upload_file/application.e new file mode 100644 index 00000000..cf939f60 --- /dev/null +++ b/workbook/handling_request/upload_file/application.e @@ -0,0 +1,24 @@ +note + description: "Basic Service launcher" + +class + APPLICATION + +inherit + WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION] + redefine + initialize + end + +create + make_and_launch + +feature {NONE} -- Initialization + + initialize + -- Initialize current service. + do + set_service_option ("port", 9090) + end + +end diff --git a/workbook/handling_request/upload_file/application_execution.e b/workbook/handling_request/upload_file/application_execution.e new file mode 100644 index 00000000..8186494e --- /dev/null +++ b/workbook/handling_request/upload_file/application_execution.e @@ -0,0 +1,58 @@ +note + description : "Basic Service that show how to Upload a file" + date : "$Date$" + revision : "$Revision$" + +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + +create + make + +feature -- Basic operations + + execute + -- Execute the incomming request + local + file: WSF_FILE_RESPONSE + l_answer: STRING + do + if request.is_get_request_method then + if request.path_info.same_string ("/") then + create file.make_html ("upload.html") + response.send (file) + else + -- Here we should handle unexpected errors. + end + elseif request.is_post_request_method then + if request.path_info.same_string ("/upload") then + -- Check if we have an uploaded file + if request.has_uploaded_file then + -- iterate over all the uploaded files + create l_answer.make_from_string ("

    Uploaded File/s


    ") + across request.uploaded_files as ic loop + l_answer.append ("FileName:") + l_answer.append (ic.item.filename) + l_answer.append ("
    Size:") + l_answer.append (ic.item.size.out) + l_answer.append ("
    ") + end + response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-type","text/html"],["Content-lenght", l_answer.count.out]>>) + response.put_string (l_answer) + else + -- Here we should handle unexpected errors. + create l_answer.make_from_string ("No uploaded files
    ") + l_answer.append ("Back to Home") + response.put_header ({HTTP_STATUS_CODE}.bad_request, <<["Content-type","text/html"],["Content-lenght", l_answer.count.out]>>) + response.put_string (l_answer) + end + else + -- Handle error + end + end + end + +end diff --git a/workbook/handling_request/upload_file/upload.ecf b/workbook/handling_request/upload_file/upload.ecf new file mode 100644 index 00000000..dc9ce0dc --- /dev/null +++ b/workbook/handling_request/upload_file/upload.ecf @@ -0,0 +1,42 @@ + + + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/workbook/handling_request/upload_file/upload.html b/workbook/handling_request/upload_file/upload.html new file mode 100644 index 00000000..9fde7944 --- /dev/null +++ b/workbook/handling_request/upload_file/upload.html @@ -0,0 +1,22 @@ + + + + EWF Handling Client Request: File Upload Example + + +

    EWF Handling Client Request: File Upload Example

    +
    +
    + Upload file/s +
    + +
    +
    + +
    +
    + + + \ No newline at end of file diff --git a/workbook/handling_request/upload_file/upload.rc b/workbook/handling_request/upload_file/upload.rc new file mode 100644 index 00000000..b0ec159c --- /dev/null +++ b/workbook/handling_request/upload_file/upload.rc @@ -0,0 +1,6 @@ +#include + +STRINGTABLE +BEGIN + 1 "This Program was made using EiffelStudio using Visual Studio C++" +END diff --git a/workbook/readme.md b/workbook/readme.md new file mode 100644 index 00000000..5212b41e --- /dev/null +++ b/workbook/readme.md @@ -0,0 +1,9 @@ +--- +layout: default +title: readme +base_url: ../../ +--- +The [Workbook](../workbook) lets you discover the EiffelWeb framework. + +[Enter the documentation](../workbook) + diff --git a/workbook/workbook.md b/workbook/workbook.md new file mode 100644 index 00000000..bd3f366c --- /dev/null +++ b/workbook/workbook.md @@ -0,0 +1,48 @@ +--- +layout: default +title: workbook +base_url: ../../ +--- +# EWF Workbook + +##### Table of Contents +* [EWF Core](#core) +* [EWF Introduction](#introduction) +* [Handling Requests: Form/Query Parameter](#form_query_parameters) +* [Handling Requests: Header Fields](#header_fields) +* [Generating Responses](#generating_responses) +* [Handling Cookies](#handling_cookies) +* [EWF Deployment](#deployment) + + +# EWF Core +Before reading (or walking throught) the workbook, to get a quick overview of EWF, it is recommended to read the following articles: + +* [Getting Started with EWF](http://eiffelwebframework.github.io/EWF/getting-started/) +* [EWF Documentation](http://eiffelwebframework.github.io/EWF/wiki/Documentation/) +* [EWF Application Lifecyle](https://github.com/EiffelWebFramework/ewf_examples/wiki/Application-Lifecycle) + + + +## Introduction +[Basic Concepts](../basics/basics). + + +## Handling Requests: Form/Query Parameter +[Handling Requests: Form/Query Parameter](../handling_request/form). + + +## Handling Requests: Header Fields +[Handling Requests: Header Fields](../handling_request/headers). + + +## Generating Response +[Generating Responses](../generating_response/generating_response) + + +## Handling Cookies +[Handling Cookies](../handling_cookies/handling_cookies) + + +## EWF Deployment +[EWF Deployment](../deployment/readme)