Compare commits

...

46 Commits

Author SHA1 Message Date
b5d6a75155 Fixed table item output by appending html attribute for WSF widget table item. 2015-12-28 17:40:59 +01:00
4fc4b02449 Made WSF_TABLE a TABLE_ITERABLE. 2015-12-28 17:40:20 +01:00
5276bd1479 Fixed URI mapping with regard to trailing slash handling. 2015-12-28 17:39:54 +01:00
Javier Velilla
81ab31b19a Updated workbook
Added EWF Deployment title
2015-12-22 11:44:21 -03:00
Javier Velilla
e21e30ff74 Updated workbook
Added deployment document
2015-12-22 11:43:17 -03:00
Javier Velilla
3a9ba75717 Initial commit Deployment file 2015-12-22 11:37:25 -03:00
7d94413297 removed non void-safe tests.ecf for feeds library 2015-11-05 21:48:17 +01:00
35855941e6 Comment and code cleaning. 2015-11-05 21:37:44 +01:00
50ba8ca703 Fixed various unicode issue related to query and form parameters.
(Especially for the multipart/form-data encoding.)
Factorized code related to smart parameters computing (handling list , table, ...) in WSF_VALUE_UTILITIES.
Fixed an issue with percent_encoded_path_info computation from request_uri.
Fixed issue with cookie addition having same cookie name.
Fixed unicode support for uploaded file.
WSF_STRING is reusing WSF_PERCENT_ENCODER.
Use unicode output for WSF_DEBUG_HANDLER.
Code cleaning
2015-11-05 21:32:24 +01:00
dde6a0b7de Added specific configuration file, so that it is easier to use either libcurl or net implementation. 2015-10-19 08:46:31 +02:00
b64a281d75 Fixed timeout issue due to too many "ready_for_reading".
Fixed Connection behavior.
Fixed Content-Type settings.
Removed condition on POST or PUT, since code also applied to any request methods.
Added verbose output implementation.
2015-10-19 08:46:30 +02:00
b69b8aaaf9 Added first support for persistent connection in NET http client implementation.
Various improvement related to eventual errors.
2015-10-19 08:46:29 +02:00
65b28ed877 Updated README.md with configuration topics related to libcurl or net disabling.
Fixed ssl test by precising insecure ssl.
2015-10-19 08:46:27 +02:00
6c7637716b Updated a few comments
Removed useless NULL_HTTP_CLIENT.
Extracted mime code from NET_HTTP_CLIENT_REQUEST.response into specific routine.
2015-10-19 08:46:26 +02:00
ff9a238f5c Added https support with Net implementation.
Added notion of default HTTP_CLIENT, to be able to build portable code among http client implementation.
2015-10-19 08:46:25 +02:00
eec3cbdba1 Added null http client for upcoming changes.
Refactored NET request implementation.
  - fixed potential issue with header conflict.
  - simplified, and extract parts of the code into routine.
  - Implemented read of chunked Transfer-Encoding
  - Fixed potential issue with socket handling.
First steps to be able to exclude net or libcurl implementation when using http_client lib.
Removed from NET implementation the hack related to PUT and upload data (it was used to workaround an issue with libcurl).
2015-10-19 08:46:23 +02:00
29c4931dc0 Added support for chunked transfer-encoding response.
Implemented correctly the redirection support for NET_HTTP_CLIENT...
Added the possibility to use HTTP/1.0 .
Splitted the manual tests that were using during development.
First step to redesign and clean the new code.
2015-10-19 08:46:22 +02:00
Florian Jacky
9cd0f0b117 Fixed configuration files 2015-10-19 08:46:21 +02:00
Florian Jacky
aa0eb4fc43 Fixed configuration files 2015-10-19 08:46:20 +02:00
Florian Jacky
dbdc594b59 config files 2015-10-19 08:46:18 +02:00
Florian Jacky
4176a8c68b correct password for authentication test 2015-10-19 08:46:17 +02:00
Florian Jacky
0557d1ee2d added remaining features 2015-10-19 08:46:16 +02:00
Florian Jacky
eed8af9a0a now supports sending requests, receiving headers, receiving message text, redirection, agent header, cookies, basic http authorization, sending data using post using url-encoding, sending file as post as data, sending put data 2015-10-19 08:46:15 +02:00
Florian Jacky
1b881c4f60 implemented http authorization, support for redirection and user-agent 2015-10-19 08:46:14 +02:00
Florian Jacky
770488dbd3 implemented http authorization, support for redirection and user-agent 2015-10-19 08:46:12 +02:00
3f69081d32 Added postcondition to ensure the result of {HTTP_CLIENT_REQUEST}.response is attached.
(useless with void-safety compilation, but keep it for non void-safe execution).
2015-10-19 08:46:11 +02:00
7033db7dc4 Removed useless redefination of is_equal. 2015-10-19 08:46:10 +02:00
a1a16b4a22 Fixing http_client.ecf file with correct locations. 2015-10-19 08:46:09 +02:00
98e92ee0fe Basic initial Eiffel NET implementation. 2015-10-19 08:46:07 +02:00
29b55f36cf Added skeleton for Eiffel Net implementation of HTTP_CLIENT solution.
This is work in progress.
2015-10-19 08:46:06 +02:00
061e88c9fe Added FEED.prune (a_item: FEED_ITEM). 2015-10-14 17:40:38 +02:00
66f204b1f2 Make custom error interface more flexible with READABLE_STRING_... instead of STRING_... 2015-10-10 00:58:07 +02:00
c92b1b8c3b Added feed to xhtml visitor.
Updated interfaces, mainly related to date attributes.
2015-10-09 19:08:53 +02:00
98c12b8fb9 Made HTTP_DATE more flexible and support UTC+0000, GMT+0000 and now also +0000.
Added comments.
2015-10-08 11:00:01 +02:00
5fee483fd9 Added FEED + FEED operator to merge two feeds.
Added FEED sorting routine.
Added FEED_ITEM.link: detachable FEED_LINK that represents the main feed link.
Comments.
2015-10-08 10:10:08 +02:00
f7a7afccd6 Fixed compilation of non void-safe feed.ecf 2015-10-05 22:58:58 +02:00
e2c70e6d70 Updated a few comments.
Renamed generator to follow *_FEED_GENERATOR naming.
Renamed feed entry as feed item.
Made FEED conforms to ITERABLE [FEED_ITEM] for convenience.
2015-09-16 10:02:09 +02:00
a5e150d1c0 Improved feed library with comments, bug fixes and code factorization. 2015-09-08 21:45:27 +02:00
39887c8bdb Added initial ATOM and RSS feed parser and generator.
(work in progress)
2015-09-07 19:22:50 +02:00
jvelilla
1f1e2abbda Removed support for SSLv3 2015-08-26 11:56:24 -03:00
1796d9631f Added target "all_stable_with_ssl" to check compilation with ssl enabled. 2015-08-26 13:38:50 +02:00
389975e409 Merge branch 'v1' 2015-08-24 16:13:01 +02:00
jvelilla
cc65bae644 Fixed typo: Aug instead of Aou. 2015-08-06 10:45:47 +02:00
f0cba1d536 Fixing script_url' that wrongly used path_info' instead of `percent_encoded_path_info'.
(issue on script_url when path info contains unicode character).
2015-08-04 13:21:36 +02:00
ed891546bc Updated set_value for WSF_FORM_SELECTABLE_INPUT (for example a checkbox).
Call the feature set_checked_by_value iff the the current value exist in the
list of values, in other case set checked in Flase.
If we call set_checked_by_value without filter, previous checked values will be
set in False.
2015-08-04 13:21:07 +02:00
d0836d49a4 Merge branch 'v1' 2015-06-10 09:49:28 +02:00
87 changed files with 5337 additions and 527 deletions

163
doc/workbook/deployment.md Normal file
View File

@@ -0,0 +1,163 @@
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.
<APACHE_PATH>/htdocs.
<APACHE_PATH> = path to your apache installation
Edit httpd.conf under c:/<APACHE_PATH>/conf
DocumentRoot "c:/<APACHE_PATH>/htdocs"
<Directory "c:/<APACHE_PATH>/htdocs">
AllowOverride All --
Require all granted -- this is required in Apache 2.4.4
</Directory>
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
<IfModule mod_rewrite.c>
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]
</IfModule
>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
<APACHE_PATH>/htdocs.
<APACHE_PATH> = path to your apache installation
Edit httpd.conf under c:/<APACHE_PATH>/conf
DocumentRoot "c:/<APACHE_PATH>/htdocs"
<Directory "c:/<APACHE_PATH>/htdocs">
AllowOverride All --
Require all granted -- this is required in Apache 2.4.4
</Directory>
>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
<IfModule mod_fcgid.c>
AddHandler fcgid-script .ews
FcgidWrapper $FULL_PATH/$service .ews
</IfModule>
<IfModule mod_rewrite.c>
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]
</IfModule>
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.

View File

@@ -5,9 +5,9 @@
* [EWF Introduction](#introduction)
* [Handling Requests: Form/Query Parameter](#form_query_parameters)
* [Handling Requests: Header Fields](#header_fields)
* [Generating Responses](/workbook/generating_response/generating_response.md)
* [Handling Cookies](/workbook/handling_cookies/handling_cookies.md)
*
* [Generating Responses](#generating responses)
* [Handling Cookies](#handling_cookies)
* [EWF Deployment](#deployment)
<a name="core"></a>
# EWF Core
@@ -29,10 +29,14 @@ Before reading (or walking throught) the workbook, to get a quick overview of EW
## Handling Requests: Header Fields
[Handling Requests: Header Fields](/doc/workbook/handling_request/headers.md).
<a name="header_fields"></a>
<a name="generating_responses"></a>
## Generating Response
[Generating Responses](/doc/workbook/generating_response/generating_response.md)
<a name="handling_cookies"></a>
## Handling Cookies
[Handling Cookies](/doc/workbook/handling_cookies/handling_cookies.md)
<a name="deployment"/>
## EWF Deployment
[EWF Deployment](/doc/workbook/deployment.md)

View File

@@ -1,13 +1,56 @@
# simple HTTP client
## Overview
It provides simple routine to perform http requests, and get response.
## Requirements
* Eiffel cURL library
* cURL dynamic libraries in the PATH or the current directory (.dll or .so)
* One of the following
- Eiffel cURL library
- cURL dynamic libraries in the PATH or the current directory (.dll or .so)
- Eiffel Net library
- and optionally Eiffel NetSSL library to support https://
This means on Windows, do not forget to copy the libcurl.dll (and related) either in the same directory of the executable, or ensure the .dll are in the PATH environment.
It is possible to exclude the libcurl implementation xor the Eiffel Net implementation:
In the .ecf configuration file of your project, you can use the following custom variables:
* Disable the libcurl implementation
```
<variable name="libcurl_http_client_disabled" value="True"/>
```
* Disable the net implementation
```
<variable name="net_http_client_disabled" value="True"/>
```
* If you disabled both, the http client will not work as expected.
For the net implementation (using EiffelNet), if you need https:// support, you need to enabled the ssl support with the custom variables :
```
<variable name="netssl_http_client_enabled" value="True"/>
```
* By default, SSL is not included (mostly because it is sometime a pain to get the needed dynamic libraries .dll or .so)
## Usage
* To build code that is portable across the libcurl or net implementation of http_client library, use the DEFAULT_HTTP_CLIENT
```
cl: DEFAULT_HTTP_CLIENT
sess: HTTP_CLIENT_SESSION
create cl
sess := cl.new_session ("http://example.com")
if attached sess.get ("/path-to-test") as l_response then
if not l_response.error_occurred then
if attached l_response.body as l_body then
print (l_body)
end
end
end
```
## Examples
* See the tests/test-safe.ecf project to see how to use.
* Examples will come in the future.

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="http_client" uuid="628F5A96-021B-4191-926B-B3BF49272866" library_target="http_client">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="http_client" uuid="628F5A96-021B-4191-926B-B3BF49272866" library_target="http_client">
<target name="http_client">
<root all_classes="true"/>
<file_rule>
@@ -7,12 +7,78 @@
<exclude>/EIFGENs$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
<option debug="false" warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL-safe.ecf"/>
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL-safe.ecf">
<condition>
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
</condition>
</library>
<library name="encoder" location="..\..\text\encoder\encoder-safe.ecf"/>
<library name="http" location="..\protocol\http\http-safe.ecf"/>
<library name="http_auth" location="..\..\server\authentication\http_authorization\http_authorization-safe.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<cluster name="src" location=".\src\" recursive="true"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl-safe.ecf">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="netssl_http_client_enabled" value="true"/>
</condition>
</library>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
<cluster name="src" location=".\src\">
<cluster name="spec_null" location="$|spec\null\" recursive="true"/>
<cluster name="spec_net" location="$|spec\net\">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
</condition>
<cluster name="net_implementation" location="$|implementation\" hidden="true">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="net_ssl_disabled" location="$|no_ssl\">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="netssl_http_client_enabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="net_ssl_enabled" location="$|ssl\">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="netssl_http_client_enabled" value="true"/>
</condition>
</cluster>
</cluster>
<cluster name="spec_libcurl" location="$|spec\libcurl\" recursive="true">
<condition>
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="default_null" location="$|default\null\">
<condition>
<custom name="net_http_client_disabled" value="true"/>
<custom name="libcurl_http_client_disabled" value="true"/>
</condition>
</cluster>
<cluster name="default_net" location="$|default\net\">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="libcurl_http_client_disabled" value="true"/>
</condition>
</cluster>
<cluster name="default_libcurl" location="$|default\libcurl\">
<condition>
<custom name="net_http_client_disabled" value="true"/>
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="default_libcurl_or_net" location="$|default\libcurl_or_net\">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
</condition>
</cluster>
</cluster>
</target>
</system>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-8-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-8-0 http://www.eiffel.com/developers/xml/configuration-1-8-0.xsd" name="http_client" uuid="628F5A96-021B-4191-926B-B3BF49272866" library_target="http_client">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="http_client" uuid="628F5A96-021B-4191-926B-B3BF49272866" library_target="http_client">
<target name="http_client">
<root all_classes="true"/>
<file_rule>
@@ -9,10 +9,76 @@
</file_rule>
<option warning="true" full_class_checking="true" void_safety="none" syntax="provisional">
</option>
<library name="base" location="$ISE_LIBRARY/library/base/base.ecf"/>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL.ecf">
<condition>
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
</condition>
</library>
<library name="encoder" location="..\..\text\encoder\encoder.ecf"/>
<library name="http" location="..\protocol\http\http.ecf"/>
<library name="http_auth" location="..\..\server\authentication\http_authorization\http_authorization.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL.ecf"/>
<library name="encoder" location="../../text/encoder/encoder.ecf"/>
<cluster name="src" location=".\src\" recursive="true"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl.ecf">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="netssl_http_client_enabled" value="true"/>
</condition>
</library>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
<cluster name="src" location=".\src\">
<cluster name="spec_null" location="$|spec\null\" recursive="true"/>
<cluster name="spec_net" location="$|spec\net\">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
</condition>
<cluster name="net_implementation" location="$|implementation\" hidden="true">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="net_ssl_disabled" location="$|no_ssl\">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="netssl_http_client_enabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="net_ssl_enabled" location="$|ssl\">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="netssl_http_client_enabled" value="true"/>
</condition>
</cluster>
</cluster>
<cluster name="spec_libcurl" location="$|spec\libcurl\" recursive="true">
<condition>
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="default_null" location="$|default\null\">
<condition>
<custom name="net_http_client_disabled" value="true"/>
<custom name="libcurl_http_client_disabled" value="true"/>
</condition>
</cluster>
<cluster name="default_net" location="$|default\net\">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="libcurl_http_client_disabled" value="true"/>
</condition>
</cluster>
<cluster name="default_libcurl" location="$|default\libcurl\">
<condition>
<custom name="net_http_client_disabled" value="true"/>
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="default_libcurl_or_net" location="$|default\libcurl_or_net\">
<condition>
<custom name="net_http_client_disabled" excluded_value="true"/>
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
</condition>
</cluster>
</cluster>
</target>
</system>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="libcurl_http_client" uuid="FA686EE7-D01D-43A3-8E95-A00120658040" library_target="libcurl_http_client">
<target name="libcurl_http_client">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option debug="false" warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL-safe.ecf"/>
<library name="encoder" location="..\..\text\encoder\encoder-safe.ecf"/>
<library name="http" location="..\protocol\http\http-safe.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
<cluster name="src" location=".\src\">
<cluster name="spec_libcurl" location="$|spec\libcurl\" recursive="true"/>
<cluster name="default_libcurl" location="$|default\libcurl\"/>
</cluster>
</target>
</system>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="libcurl_http_client" uuid="FA686EE7-D01D-43A3-8E95-A00120658040" library_target="libcurl_http_client">
<target name="libcurl_http_client">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option debug="false" warning="true" full_class_checking="true" void_safety="none" syntax="provisional">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL.ecf"/>
<library name="encoder" location="..\..\text\encoder\encoder.ecf"/>
<library name="http" location="..\protocol\http\http.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
<cluster name="src" location=".\src\">
<cluster name="spec_libcurl" location="$|spec\libcurl\" recursive="true"/>
<cluster name="default_libcurl" location="$|default\libcurl\"/>
</cluster>
</target>
</system>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="http_client" uuid="7897B317-7AD3-44E4-A933-0544A169AB1B" library_target="net_http_client">
<target name="net_http_client">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option debug="false" warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="encoder" location="..\..\text\encoder\encoder-safe.ecf"/>
<library name="http" location="..\protocol\http\http-safe.ecf"/>
<library name="http_auth" location="..\..\server\authentication\http_authorization\http_authorization-safe.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl-safe.ecf">
<condition>
<custom name="netssl_http_client_enabled" value="true"/>
</condition>
</library>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
<cluster name="src" location=".\src\">
<cluster name="spec_net" location="$|spec\net\">
<cluster name="net_implementation" location="$|implementation\" hidden="true"/>
<cluster name="net_ssl_disabled" location="$|no_ssl\">
<condition>
<custom name="netssl_http_client_enabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="net_ssl_enabled" location="$|ssl\">
<condition>
<custom name="netssl_http_client_enabled" value="true"/>
</condition>
</cluster>
</cluster>
<cluster name="default_net" location="$|default\net\"/>
</cluster>
</target>
</system>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="http_client" uuid="7897B317-7AD3-44E4-A933-0544A169AB1B" library_target="net_http_client">
<target name="net_http_client">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option debug="false" warning="true" full_class_checking="true" void_safety="none" syntax="provisional">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="encoder" location="..\..\text\encoder\encoder.ecf"/>
<library name="http" location="..\protocol\http\http.ecf"/>
<library name="http_auth" location="..\..\server\authentication\http_authorization\http_authorization.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl.ecf">
<condition>
<custom name="netssl_http_client_enabled" value="true"/>
</condition>
</library>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
<cluster name="src" location=".\src\">
<cluster name="spec_net" location="$|spec\net\">
<cluster name="net_implementation" location="$|implementation\" hidden="true"/>
<cluster name="net_ssl_disabled" location="$|no_ssl\">
<condition>
<custom name="netssl_http_client_enabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="net_ssl_enabled" location="$|ssl\">
<condition>
<custom name="netssl_http_client_enabled" value="true"/>
</condition>
</cluster>
</cluster>
<cluster name="default_net" location="$|default\net\"/>
</cluster>
</target>
</system>

View File

@@ -0,0 +1,15 @@
note
description: "[
Default HTTP_CLIENT based on LIBCURL_HTTP_CLIENT.
]"
author: "$Author$"
date: "$Date$"
revision: "$Revision$"
class
DEFAULT_HTTP_CLIENT
inherit
LIBCURL_HTTP_CLIENT
end

View File

@@ -0,0 +1,46 @@
note
description: "[
Default HTTP_CLIENT based on LIBCURL_HTTP_CLIENT or NET_HTTP_CLIENT.
The preference goes to libcurl implementation for now,
since the net implementation has currently less functionalities.
]"
author: "$Author$"
date: "$Date$"
revision: "$Revision$"
class
DEFAULT_HTTP_CLIENT
inherit
HTTP_CLIENT
feature -- Access
new_session (a_base_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
-- Create a new session using `a_base_url'.
local
libcurl: LIBCURL_HTTP_CLIENT
net: NET_HTTP_CLIENT
do
--| For now, try libcurl first, and then net
--| the reason is the net implementation is still in progress.
create libcurl
Result := libcurl.new_session (a_base_url)
if not Result.is_available then
create net
Result := net.new_session (a_base_url)
end
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,15 @@
note
description: "[
Default HTTP_CLIENT based on NET_HTTP_CLIENT.
]"
author: "$Author$"
date: "$Date$"
revision: "$Revision$"
class
DEFAULT_HTTP_CLIENT
inherit
NET_HTTP_CLIENT
end

View File

@@ -0,0 +1,33 @@
note
description: "[
Default HTTP_CLIENT based on NULL_HTTP_CLIENT.
]"
author: "$Author$"
date: "$Date$"
revision: "$Revision$"
class
DEFAULT_HTTP_CLIENT
inherit
HTTP_CLIENT
feature -- Access
new_session (a_base_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
-- Create a new session using `a_base_url'.
do
create {NULL_HTTP_CLIENT_SESSION} Result.make (a_base_url)
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -9,7 +9,7 @@ note
deferred class
HTTP_CLIENT
feature -- Status
feature -- Access
new_session (a_base_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
-- Create a new session using `a_base_url'.
@@ -17,7 +17,7 @@ feature -- Status
end
note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -14,24 +14,41 @@ inherit
feature {NONE} -- Initialization
make (a_url: READABLE_STRING_8; a_session: like session; ctx: like context)
-- Initialize `Current'.
make (a_url: READABLE_STRING_8; a_request_method: like request_method; a_session: like session; ctx: like context)
-- Initialize `Current' with request url `a_url', method `a_request_method' within the session `a_session'
-- and optional context `ctx' which can be used to pass additional parameters.
do
request_method := a_request_method
session := a_session
url := a_url
headers := session.headers.twin
if ctx /= Void then
context := ctx
import (ctx)
end
initialize (a_url, ctx)
ensure
context_set: context = ctx
ctx_header_set: ctx /= Void implies across ctx.headers as ctx_h all attached headers.item (ctx_h.key) as v and then v.same_string (ctx_h.item) end
end
initialize (a_url: READABLE_STRING_8; ctx: like context)
-- Initialize Current with `a_url' and `ctx'.
-- This can be used to reset/reinitialize Current with new url
-- in the case of redirection.
do
url := a_url
headers := session.headers.twin
if ctx /= Void then
context := ctx
import (ctx)
else
context := Void
end
end
feature {NONE} -- Internal
session: HTTP_CLIENT_SESSION
-- Session related to Current request.
-- It provides a few parameters related to session.
context: detachable HTTP_CLIENT_REQUEST_CONTEXT
-- Potential additional parameters for this specific request.
feature -- Status report
@@ -44,16 +61,27 @@ feature -- Status report
feature -- Access
request_method: READABLE_STRING_8
deferred
end
-- Request method associated with Current request.
url: READABLE_STRING_8
-- URL associated with current request.
headers: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]
-- Specific headers to be used for current request.
response: HTTP_CLIENT_RESPONSE
-- Response received from request execution.
-- Check `error_occurred' for eventual error.
-- note: two consecutive calls will trigger two executions!
deferred
ensure
Result_set: Result /= Void
end
feature {HTTP_CLIENT_SESSION} -- Execution
import (ctx: HTTP_CLIENT_REQUEST_CONTEXT)
-- Import `ctx' parameters.
local
l_headers: like headers
do
@@ -67,10 +95,6 @@ feature {HTTP_CLIENT_SESSION} -- Execution
end
end
execute: HTTP_CLIENT_RESPONSE
deferred
end
feature -- Authentication
auth_type: STRING
@@ -88,21 +112,26 @@ feature -- Authentication
end
username: detachable READABLE_STRING_32
-- Username specified for the `session'.
do
Result := session.username
end
password: detachable READABLE_STRING_32
-- Password specified for the `session'.
do
Result := session.password
end
credentials: detachable READABLE_STRING_32
-- Credentials specified for the `session'.
--| Usually `username':`password'
do
Result := session.credentials
end
proxy: detachable TUPLE [host: READABLE_STRING_8; port: INTEGER]
-- Optional proxy settings.
do
Result := session.proxy
end
@@ -220,7 +249,7 @@ feature {NONE} -- Utilities: encoding
end
note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -83,6 +83,9 @@ feature -- Access
output_content_file: detachable FILE
-- Optional output file to get downloaded content
http_version: detachable IMMUTABLE_STRING_8
-- Overwrite default http version if set.
feature -- Status report
has_form_data: BOOLEAN
@@ -209,6 +212,17 @@ feature -- Element change
output_content_file := f
end
set_http_version (v: detachable READABLE_STRING_8)
require
valid_version: v = Void or else v.starts_with_general ("HTTP/")
do
if v = Void then
http_version := Void
else
create http_version.make_from_string (v)
end
end
feature -- Status setting
set_proxy (a_host: detachable READABLE_STRING_8; a_port: INTEGER)
@@ -264,7 +278,7 @@ feature {NONE} -- Implementation
end
note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -13,7 +13,7 @@ create
feature {NONE} -- Initialization
make (a_url: like url)
make (a_url: READABLE_STRING_8)
-- Initialize `Current'.
do
--| Default values
@@ -54,12 +54,23 @@ feature -- Access
status_line: detachable READABLE_STRING_8
http_version: detachable READABLE_STRING_8
-- http version associated with `status_line'.
raw_header: READABLE_STRING_8
-- Raw http header of the response.
redirections: detachable ARRAYED_LIST [TUPLE [status_line: detachable READABLE_STRING_8; raw_header: READABLE_STRING_8; body: detachable READABLE_STRING_8]]
-- Header of previous redirection if any.
redirections_count: INTEGER
-- Number of redirections.
do
if attached redirections as lst then
Result := lst.count
end
end
header (a_name: READABLE_STRING_8): detachable READABLE_STRING_8
-- Header entry value related to `a_name'
-- if multiple entries, just concatenate them using comma character
@@ -92,6 +103,24 @@ feature -- Access
Result := s
end
multiple_header (a_name: READABLE_STRING_8): detachable LIST [READABLE_STRING_8]
-- Header multiple entries related to `a_name'
local
k: READABLE_STRING_8
do
across
headers as hds
loop
k := hds.item.name
if k.same_string (a_name) then
if Result = Void then
create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (1)
end
Result.force (hds.item.value)
end
end
end
headers: LIST [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]
-- Computed table of http headers of the response.
--| We use a LIST since one might have multiple message-header fields with the same field-name
@@ -115,7 +144,7 @@ feature -- Access
loop
l_start := pos
--| Left justify
from until not h[l_start].is_space loop
from until not h [l_start].is_space loop
l_start := l_start + 1
end
pos := h.index_of ('%N', l_start)
@@ -129,7 +158,7 @@ feature -- Access
end
if l_end > 0 then
--| Right justify
from until not h[l_end].is_space loop
from until not h [l_end].is_space loop
l_end := l_end - 1
end
c := h.index_of (':', l_start)
@@ -188,17 +217,72 @@ feature -- Access
end
end
feature -- Status report
is_http_1_0: BOOLEAN
-- Is response using HTTP/1.0 protocole?
--| Note: it is relevant once the raw header are set.
do
Result := attached http_version as v and then v.same_string ("1.0")
end
is_http_1_1: BOOLEAN
-- Is response using HTTP/1.1 protocole?
--| Note: it is relevant once the raw header are set.
do
Result := attached http_version as v and then v.same_string ("1.1")
end
feature -- Change
set_http_version (v: like http_version)
-- Set `http_version' to `v'.
do
http_version := v
end
set_status (s: INTEGER)
-- Set response `status' code to `s'
do
status := s
end
set_status_line (a_line: detachable READABLE_STRING_8)
-- Set status line to `a_line',
-- and also `status' extracted from `a_line' if possible.
local
i,j: INTEGER
s: READABLE_STRING_8
do
status_line := a_line
http_version := Void
if a_line /= Void then
if a_line.starts_with ("HTTP/") then
i := a_line.index_of (' ', 1)
if i > 0 then
http_version := a_line.substring (1 + 5, i - 1) -- ("HTTP/").count = 5
i := i + 1
end
else
i := 1
end
-- Get status code token.
if i > 0 then
j := a_line.index_of (' ', i)
if j > i then
s := a_line.substring (i, j - 1)
if s.is_integer then
set_status (s.to_integer)
end
end
end
end
end
set_response_message (a_source: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT)
-- Parse `a_source' response message
-- and set `header' and `body'.
-- and set `status_line', `status', `header' and `body'.
--| ctx is the context associated with the request
--| it might be useful to deal with redirection customization...
local
@@ -234,7 +318,7 @@ feature -- Change
j := i + 2
pos := j
status_line := l_status_line
set_status_line (l_status_line)
set_raw_header (h)
-- libcURL does not cache redirection content.
@@ -277,10 +361,23 @@ feature -- Change
set_raw_header (h: READABLE_STRING_8)
-- Set http header `raw_header' to `h'
local
i: INTEGER
s: STRING_8
do
raw_header := h
--| Reset internal headers
internal_headers := Void
--| Set status line, right away.
i := h.index_of ('%N', 1)
if i > 0 then
s := h.substring (1, i - 1)
if s.starts_with ("HTTP/") then
s.right_adjust
set_status_line (s)
end
end
end
add_redirection (s: detachable READABLE_STRING_8; h: READABLE_STRING_8; a_body: detachable READABLE_STRING_8)
@@ -308,7 +405,7 @@ feature {NONE} -- Implementation
-- Internal cached value for the headers
;note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -50,6 +50,14 @@ feature {NONE} -- Initialization
feature -- Basic operation
close
-- Close session.
--| useful to disconnect persistent connection.
do
end
feature -- Access
url (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): STRING_8
-- Url computed from Current and `ctx' data.
local
@@ -77,6 +85,61 @@ feature -- Basic operation
end
end
feature {NONE} -- Access: verbose
verbose_mode: INTEGER
-- Internal verbose mode.
verbose_header_sent_mode: INTEGER = 1 --| 0001
verbose_header_received_mode: INTEGER = 2 --| 0010
verbose_debug_mode: INTEGER = 4 --| 0100
feature -- Access: verbose
is_header_sent_verbose: BOOLEAN
do
Result := verbose_mode & verbose_header_sent_mode = verbose_header_sent_mode
end
is_header_received_verbose: BOOLEAN
do
Result := verbose_mode & verbose_header_received_mode = verbose_header_received_mode
end
is_debug_verbose: BOOLEAN
do
Result := verbose_mode & verbose_debug_mode = verbose_debug_mode
end
feature -- Element change: verbose
set_header_sent_verbose (b: BOOLEAN)
do
if b then
verbose_mode := verbose_mode | verbose_header_sent_mode
else
verbose_mode := verbose_mode & verbose_header_sent_mode.bit_not
end
end
set_header_received_verbose (b: BOOLEAN)
do
if b then
verbose_mode := verbose_mode | verbose_header_received_mode
else
verbose_mode := verbose_mode & verbose_header_received_mode.bit_not
end
end
set_debug_verbose (b: BOOLEAN)
do
if b then
verbose_mode := verbose_mode | verbose_debug_mode
else
verbose_mode := verbose_mode & verbose_debug_mode.bit_not
end
end
feature -- Custom
custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
@@ -88,17 +151,23 @@ feature -- Helper
get (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
-- Response for GET request based on Current, `a_path' and `ctx'.
require
is_available: is_available
deferred
end
head (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
-- Response for HEAD request based on Current, `a_path' and `ctx'.
require
is_available: is_available
deferred
end
post (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- Response for POST request based on Current, `a_path' and `ctx'
-- with input `data'
require
is_available: is_available
deferred
end
@@ -111,29 +180,39 @@ feature -- Helper
patch (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- Response for PATCH request based on Current, `a_path' and `ctx'
-- with input `data'
require
is_available: is_available
deferred
end
patch_file (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- Response for PATCH request based on Current, `a_path' and `ctx'
-- with uploaded data file `fn'
require
is_available: is_available
deferred
end
put (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- Response for PUT request based on Current, `a_path' and `ctx'
-- with input `data'
require
is_available: is_available
deferred
end
put_file (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- Response for PUT request based on Current, `a_path' and `ctx'
-- with uploaded file `fn'
require
is_available: is_available
deferred
end
delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
-- Response for DELETE request based on Current, `a_path' and `ctx'
require
is_available: is_available
deferred
end
@@ -185,6 +264,9 @@ feature -- Access
headers: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]
-- Headers common to any request created by Current session.
cookie: detachable READABLE_STRING_8
-- Cookie for the current base_url
feature -- Authentication
auth_type: STRING
@@ -194,11 +276,15 @@ feature -- Authentication
auth_type_id: INTEGER
-- See {HTTP_CLIENT_CONSTANTS}.Auth_type_*
username,
username: detachable READABLE_STRING_32
-- Associated optional username value.
password: detachable READABLE_STRING_32
-- Associated optional password value.
credentials: detachable READABLE_STRING_32
-- Associated optional credentials value.
-- Computed as `username':`password'.
feature -- Status setting
@@ -212,6 +298,12 @@ feature -- Element change
set_base_url (u: like base_url)
do
base_url := u
cookie := Void
end
set_cookie (a_cookie: detachable READABLE_STRING_8)
do
cookie := a_cookie
end
set_timeout (n_seconds: like timeout)
@@ -244,12 +336,26 @@ feature -- Element change
headers.prune (k)
end
set_credentials (u: like username; p: like password)
set_credentials (u,p: detachable READABLE_STRING_GENERAL)
local
s: STRING_32
do
username := u
password := p
if u = Void then
username := Void
else
create {STRING_32} username.make_from_string_general (u)
end
if p = Void then
password := Void
else
create {STRING_32} password.make_from_string_general (p)
end
if u /= Void and p /= Void then
credentials := u + ":" + p
create s.make (u.count + 1 + p.count)
s.append_string_general (u)
s.append_character (':')
s.append_string_general (p)
credentials := s
else
credentials := Void
end
@@ -300,7 +406,7 @@ feature -- Element change
end
note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -1,6 +1,9 @@
note
description : "[
Specific implementation of HTTP_CLIENT based on Eiffel cURL library
WARNING: Do not forget to have the dynamic libraries libcurl (.dll or .so)
and related accessible to the executable (i.e in same directory, or in the PATH)
]"
author : "$Author$"
date : "$Date$"
@@ -13,6 +16,7 @@ inherit
HTTP_CLIENT
create
default_create,
make
feature {NONE} -- Initialization
@@ -20,6 +24,7 @@ feature {NONE} -- Initialization
make
-- Initialize `Current'.
do
default_create
end
feature -- Status
@@ -30,7 +35,7 @@ feature -- Status
end
note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -1,6 +1,9 @@
note
description: "[
Specific implementation of HTTP_CLIENT_REQUEST based on Eiffel cURL library
WARNING: Do not forget to have the dynamic libraries libcurl (.dll or .so)
and related accessible to the executable (i.e in same directory, or in the PATH)
]"
date: "$Date$"
revision: "$Revision$"
@@ -10,9 +13,8 @@ class
inherit
HTTP_CLIENT_REQUEST
rename
make as make_request
redefine
make,
session
end
@@ -23,8 +25,7 @@ feature {NONE} -- Initialization
make (a_url: READABLE_STRING_8; a_request_method: like request_method; a_session: like session; ctx: like context)
do
make_request (a_url, a_session, ctx)
request_method := a_request_method
Precursor (a_url, a_request_method, a_session, ctx)
apply_workaround
end
@@ -38,13 +39,10 @@ feature {NONE} -- Initialization
session: LIBCURL_HTTP_CLIENT_SESSION
feature -- Access
request_method: READABLE_STRING_8
feature -- Execution
execute: HTTP_CLIENT_RESPONSE
response: HTTP_CLIENT_RESPONSE
-- <Precursor>
local
l_result: INTEGER
l_curl_string: detachable CURL_STRING
@@ -63,6 +61,8 @@ feature -- Execution
l_upload_data: detachable READABLE_STRING_8
l_upload_filename: detachable READABLE_STRING_GENERAL
l_headers: like headers
l_is_http_1_0: BOOLEAN
l_uri: URI
do
if not retried then
curl := session.curl
@@ -72,6 +72,10 @@ feature -- Execution
ctx := context
if ctx /= Void then
l_is_http_1_0 := attached ctx.http_version as l_http_version and then l_http_version.same_string ("HTTP/1.0")
end
--| Configure cURL session
initialize_curl_session (ctx, curl, curl_easy, curl_handle)
@@ -81,12 +85,48 @@ feature -- Execution
append_parameters_to_url (ctx.query_parameters, l_url)
end
if session.is_header_sent_verbose then
io.error.put_string ("> Sending:%N")
create l_uri.make_from_string (l_url)
io.error.put_string ("> ")
io.error.put_string (request_method + " " + l_uri.path)
if attached l_uri.query as q then
io.error.put_string (q)
end
if l_is_http_1_0 then
io.error.put_string (" HTTP/1.0")
else
io.error.put_string (" HTTP/1.1")
end
io.error.put_new_line
if attached l_uri.host as l_host then
io.error.put_string ("> ")
io.error.put_string ("Host: " + l_host)
io.error.put_new_line
end
across
headers as ic
loop
io.error.put_string ("> ")
io.error.put_string (ic.key)
io.error.put_string (": ")
io.error.put_string (ic.item)
io.error.put_new_line
end
io.error.put_string ("> ... ")
io.error.put_new_line
end
debug ("service")
io.put_string ("SERVICE: " + l_url)
io.put_new_line
end
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_url, l_url)
if l_is_http_1_0 then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_http_version, {CURL_OPT_CONSTANTS}.curl_http_version_1_0)
else
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_http_version, {CURL_OPT_CONSTANTS}.curl_http_version_none)
end
l_headers := headers
-- Context
@@ -241,6 +281,10 @@ feature -- Execution
Result.status := response_status_code (curl_easy, curl_handle)
if l_curl_string /= Void then
Result.set_response_message (l_curl_string.string, ctx)
if session.is_header_received_verbose then
io.error.put_string ("< Receiving:%N")
io.error.put_string (Result.raw_header)
end
end
else
Result.set_error_message ("Error: cURL Error[" + l_result.out + "]")
@@ -390,7 +434,7 @@ feature {NONE} -- Implementation
end
note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -1,6 +1,9 @@
note
description: "[
Specific implementation of HTTP_CLIENT_SESSION based on Eiffel cURL library
WARNING: Do not forget to have the dynamic libraries libcurl (.dll or .so)
and related accessible to the executable (i.e in same directory, or in the PATH)
]"
date: "$Date$"
revision: "$Revision$"
@@ -34,19 +37,22 @@ feature -- Status report
feature -- Custom
custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
-- <Precursor>
local
req: HTTP_CLIENT_REQUEST
do
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, a_method, Current, ctx)
Result := req.execute
Result := req.response
end
custom_with_upload_data (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- Same as `custom' but including upload data `a_data'.
do
Result := impl_custom (a_method, a_path, a_ctx, data, Void)
end
custom_with_upload_file (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- Same as `custom' but including upload file `fn'.
do
Result := impl_custom (a_method, a_path, a_ctx, Void, fn)
end
@@ -54,46 +60,55 @@ feature -- Custom
feature -- Helper
get (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := custom ("GET", a_path, ctx)
end
head (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := custom ("HEAD", a_path, ctx)
end
post (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := impl_custom ("POST", a_path, a_ctx, data, Void)
end
post_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := impl_custom ("POST", a_path, a_ctx, Void, fn)
end
patch (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := impl_custom ("PATCH", a_path, a_ctx, data, Void)
end
patch_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := impl_custom ("PATCH", a_path, a_ctx, Void, fn)
end
put (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := impl_custom ("PUT", a_path, a_ctx, data, Void)
end
put_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := impl_custom ("PUT", a_path, a_ctx, Void, fn)
end
delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := custom ("DELETE", a_path, ctx)
end
@@ -140,7 +155,7 @@ feature {NONE} -- Implementation
end
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, a_method, Current, ctx)
Result := req.execute
Result := req.response
if f /= Void then
f.delete
@@ -161,7 +176,7 @@ feature {LIBCURL_HTTP_CLIENT_REQUEST} -- Curl implementation
;note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -0,0 +1,52 @@
note
description: "Socket connection information, used for peristent connection implementation."
date: "$Date$"
revision: "$Revision$"
class
NET_HTTP_CLIENT_CONNECTION
create
make
feature {NONE} -- Initialization
make (a_socket: NETWORK_STREAM_SOCKET; a_host: READABLE_STRING_GENERAL; a_port: INTEGER)
do
socket := a_socket
host := a_host
port := a_port
end
feature -- Access
socket: NETWORK_STREAM_SOCKET
-- Persistent connection socket.
host: READABLE_STRING_GENERAL
-- Host used for this connection.
port: INTEGER
-- Port used for this connection.
feature -- Status report
is_reusable (a_host: READABLE_STRING_GENERAL; a_port: INTEGER): BOOLEAN
-- Is Current connection reusable for new connection `a_host:a_port'?
do
if a_host.same_string (host) and port = a_port then
Result := socket.is_connected
end
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,34 @@
note
description : "[
Specific implementation of HTTP_CLIENT based on Eiffel NET library
WARNING: this is work in progress
]"
author : "$Author$"
date : "$Date$"
revision : "$Revision$"
class
NET_HTTP_CLIENT
inherit
HTTP_CLIENT
feature -- Status
new_session (a_base_url: READABLE_STRING_8): NET_HTTP_CLIENT_SESSION
do
create Result.make (a_base_url)
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,739 @@
note
description: "[
Specific implementation of HTTP_CLIENT_REQUEST based on Eiffel NET library
]"
date: "$Date$"
revision: "$Revision$"
class
NET_HTTP_CLIENT_REQUEST
inherit
HTTP_CLIENT_REQUEST
redefine
session
end
TRANSFER_COMMAND_CONSTANTS
REFACTORING_HELPER
SHARED_EXECUTION_ENVIRONMENT
create
make
feature {NONE} -- Internal
session: NET_HTTP_CLIENT_SESSION
net_http_client_version: STRING = "0.1"
session_socket (a_host: READABLE_STRING_8; a_port: INTEGER; a_is_https: BOOLEAN; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): NETWORK_STREAM_SOCKET
-- Session socket to use for connection.
-- Eventually reuse the persistent connection if any.
local
l_socket: detachable NETWORK_STREAM_SOCKET
do
if
attached session.persistent_connection as l_persistent_connection and then
l_persistent_connection.is_reusable (a_host, a_port)
then
l_socket := l_persistent_connection.socket
if a_is_https then
if attached {SSL_NETWORK_STREAM_SOCKET} l_socket as l_ssl_socket then
Result := l_ssl_socket
else
l_socket := Void
end
elseif attached {SSL_NETWORK_STREAM_SOCKET} l_socket as l_ssl_socket then
l_socket := Void
end
if l_socket /= Void and then not l_socket.is_connected then
-- Reset persistent connection
l_socket := Void
end
end
if l_socket /= Void then
-- Reuse persistent connection.
Result := l_socket
else
session.set_persistent_connection (Void)
if a_is_https then
create {SSL_NETWORK_STREAM_SOCKET} Result.make_client_by_port (a_port, a_host)
else
create Result.make_client_by_port (a_port, a_host)
end
Result.set_connect_timeout (connect_timeout)
Result.set_timeout (timeout)
Result.connect
end
end
feature -- Access
response: HTTP_CLIENT_RESPONSE
-- <Precursor>
local
redirection_response: detachable like response
l_uri: URI
l_header_key: READABLE_STRING_8
l_host: READABLE_STRING_8
l_cookie: detachable READABLE_STRING_8
l_request_uri: STRING
l_url: HTTP_URL
l_socket: NETWORK_STREAM_SOCKET
s: STRING
l_message: STRING
l_content_length: INTEGER
l_location: detachable READABLE_STRING_8
l_port: INTEGER
l_is_https: BOOLEAN
l_authorization: HTTP_AUTHORIZATION
l_platform: STRING
l_upload_data: detachable READABLE_STRING_8
l_form_data: detachable HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
ctx: like context
l_upload_file: detachable RAW_FILE
l_upload_filename: detachable READABLE_STRING_GENERAL
l_form_string: STRING
l_prev_header: READABLE_STRING_8
l_boundary: READABLE_STRING_8
l_is_http_1_0_request: BOOLEAN
l_is_keep_alive: BOOLEAN
retried: BOOLEAN
do
if not retried then
ctx := context
if ctx /= Void then
l_is_http_1_0_request := attached ctx.http_version as l_http_version and then l_http_version.same_string ("HTTP/1.0")
end
create Result.make (url)
-- Get URL data
l_is_https := url.starts_with_general ("https://")
create l_uri.make_from_string (url)
l_port := l_uri.port
if l_port = 0 then
if l_is_https then
l_port := 443
else
l_port := 80
end
end
if attached l_uri.host as h then
l_host := h
else
create l_url.make (url)
l_host := l_url.host
end
if attached session.proxy as l_proxy_settings then
-- For now, so proxy support.
check
not_supported: False
end
end
-- Connect
l_socket := session_socket (l_host, l_port, l_is_https, ctx)
if l_socket.is_connected then
create l_form_string.make_empty
-- add headers for authorization
if not headers.has ("Authorization") then
if
attached username as u_name and
attached password as u_pass
then
create l_authorization.make_basic_auth (u_name, u_pass)
if attached l_authorization.http_authorization as auth then
headers.extend (auth, "Authorization")
end
check headers.has_key ("Authorization") end
end
end
create l_request_uri.make_from_string (l_uri.path)
if attached l_uri.query as l_query then
l_request_uri.append_character ('?')
l_request_uri.append (l_query)
end
-- add computed header User-Agent if not yet set.
if not headers.has ("User-Agent") then
if {PLATFORM}.is_unix then
l_platform := "Unix"
elseif {PLATFORM}.is_windows then
l_platform := "Windows"
elseif {PLATFORM}.is_mac then
l_platform := "Mac"
elseif {PLATFORM}.is_vms then
l_platform := "VMS"
elseif {PLATFORM}.is_vxworks then
l_platform := "VxWorks"
else
l_platform := "Unknown"
end
headers.extend ("eiffelhttpclient/" + net_http_client_version + " (" + l_platform + ")", "User-Agent")
end
-- handle sending data
if ctx /= Void then
if ctx.has_upload_filename then
l_upload_filename := ctx.upload_filename
end
if ctx.has_upload_data then
l_upload_data := ctx.upload_data
end
if ctx.has_form_data then
l_form_data := ctx.form_parameters
if l_upload_data = Void and l_upload_filename = Void then
-- Send as form-urlencoded
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
l_upload_data := ctx.form_parameters_to_url_encoded_string
headers.force (l_upload_data.count.out, "Content-Length")
else
-- create form using multipart/form-data encoding
l_boundary := new_mime_boundary
headers.extend ("multipart/form-data; boundary=" + l_boundary, "Content-Type")
if l_form_data /= Void then
l_upload_data := form_date_and_uploaded_files_to_mime_string (l_form_data, l_upload_filename, l_boundary)
headers.extend (l_upload_data.count.out, "Content-Length")
end
end
elseif l_upload_data /= Void then
check ctx.has_upload_data end
if not headers.has ("Content-Type") then
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
end
headers.extend (l_upload_data.count.out, "Content-Length")
elseif l_upload_filename /= Void then
check ctx.has_upload_filename end
create l_upload_file.make_with_name (l_upload_filename)
if l_upload_file.exists and then l_upload_file.readable then
headers.extend (l_upload_file.count.out, "Content-Length")
end
check l_upload_file /= Void end
end
end
-- FIXME: check usage of headers and specific header variable.
--| only one Cookie: is allowed, so merge multiple into one;
--| if Host is in header, use that one.
-- Compute Request line.
create s.make_from_string (request_method.as_upper)
s.append_character (' ')
s.append (l_request_uri)
s.append_character (' ')
if l_is_http_1_0_request then
s.append ("HTTP/1.0")
else
s.append ("HTTP/1.1")
end
s.append (Http_end_of_header_line)
-- Compute Header Host:
s.append (Http_host_header)
s.append (": ")
if attached headers [Http_host_header] as h_host then
s.append (h_host)
else
s.append (l_host)
end
s.append (http_end_of_header_line)
if not headers.has ("Connection") then
if l_is_http_1_0_request then
s.append ("Connection: keep-alive")
s.append (http_end_of_header_line)
end
end
-- Append the given request headers
l_cookie := Void
if not headers.is_empty then
across
headers as ic
loop
l_header_key := ic.key
if l_header_key.same_string_general ("Host") then
-- FIXME: already handled elsewhere!
elseif l_header_key.same_string_general ("Cookie") then
-- FIXME: need cookie merging.
l_cookie := ic.item
else
s.append (ic.key)
s.append (": ")
s.append (ic.item)
s.append (Http_end_of_header_line)
end
end
end
-- Compute Header Cookie: if needed
-- Use session cookie
if l_cookie = Void then
l_cookie := session.cookie
else
-- Overwrite potential session cookie, if specified by the user.
end
if l_cookie /= Void then
s.append ("Cookie: ")
s.append (l_cookie)
s.append (http_end_of_header_line)
end
--| End of client header.
s.append (Http_end_of_header_line)
if l_upload_data /= Void then
s.append (l_upload_data)
s.append (http_end_of_header_line)
end
--| Note that any remaining file to upload will be done directly via the socket
--| to optimize memory usage
--|-----------------------------|--
--| Request preparation is done |--
--|-----------------------------|--
if l_socket.ready_for_writing then
--| Socket is ready for writing, so let's send the request.
--|-------------------------|--
--| Send request |--
--|-------------------------|--
if session.is_header_sent_verbose then
log ("> Sending:%N")
log (s)
end
l_socket.put_string (s)
--| Send remaining payload data, if needed.
if l_upload_file /= Void then
-- i.e: not yet processed
append_file_content_to_socket (l_upload_file, l_upload_file.count, l_socket)
end
--|-------------------------|--
--| Get response. |--
--| Get header message |--
--|-------------------------|--
if is_ready_for_reading (l_socket) then
create l_message.make_empty
append_socket_header_content_to (Result, l_socket, l_message)
if session.is_header_received_verbose then
log ("< Receiving:%N")
log (l_message)
end
l_prev_header := Result.raw_header
Result.set_raw_header (l_message.string)
l_message.append (http_end_of_header_line)
if not Result.error_occurred then
-- Get information from header
l_content_length := -1
if attached Result.header ("Content-Length") as s_len and then s_len.is_integer then
l_content_length := s_len.to_integer
end
l_location := Result.header ("Location")
if attached Result.header ("Set-Cookie") as s_cookies then
session.set_cookie (s_cookies)
end
-- Keep-alive connection?
-- with HTTP/1.1, this is the default, and could be changed by Connection: close
-- with HTTP/1.0, it requires "Connection: keep-alive" header line.
if attached Result.header ("Connection") as s_connection then
l_is_keep_alive := s_connection.same_string ("keep-alive")
else
l_is_keep_alive := not Result.is_http_1_0
end
-- Get content if any.
append_socket_content_to (Result, l_socket, l_content_length, l_message)
-- Restore previous header
Result.set_raw_header (l_prev_header)
-- Set message
Result.set_response_message (l_message, ctx)
-- Check status code.
check status_coherent: attached Result.status_line as l_status_line implies l_status_line.has_substring (Result.status.out) end
if l_is_keep_alive then
session.set_persistent_connection (create {NET_HTTP_CLIENT_CONNECTION}.make (l_socket, l_host, l_port))
else
session.set_persistent_connection (Void)
end
-- follow redirect
if l_location /= Void then
if Result.redirections_count < max_redirects then
initialize (l_location, ctx)
redirection_response := response
redirection_response.add_redirection (Result.status_line, Result.raw_header, Result.body)
Result := redirection_response
end
end
if not l_is_keep_alive then
l_socket.cleanup
end
end
else
if session.is_debug_verbose then
log ("Debug: Read Timeout!%N")
end
Result.set_error_message ("Read Timeout")
end
else
if session.is_debug_verbose then
log ("Debug: Write Timeout!%N")
end
Result.set_error_message ("Write Timeout")
end
else
if session.is_debug_verbose then
log ("Debug: Could not connect!%N")
end
Result.set_error_message ("Could not connect")
end
else
create Result.make (url)
Result.set_error_message ("Error: internal error")
end
rescue
retried := True
retry
end
feature {NONE} -- Helpers
log (m: READABLE_STRING_8)
-- Output log messages.
do
io.error.put_string (m)
end
is_ready_for_reading (a_socket: NETWORK_STREAM_SOCKET): BOOLEAN
-- Is `a_socket' ready for reading?
do
Result := a_socket.ready_for_reading
end
form_date_and_uploaded_files_to_mime_string (a_form_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]; a_upload_filename: detachable READABLE_STRING_GENERAL; a_mime_boundary: READABLE_STRING_8): STRING
-- Form data and uploaded files converted to mime string.
-- TODO: design a proper MIME... component.
local
l_path: PATH
l_mime_type: READABLE_STRING_8
l_upload_file: detachable RAW_FILE
l_mime_type_mapping: HTTP_FILE_EXTENSION_MIME_MAPPING
do
create Result.make (100)
across
a_form_parameters as ic
loop
Result.append (a_mime_boundary)
Result.append (http_end_of_header_line)
Result.append ("Content-Disposition: form-data; name=")
Result.append_character ('%"')
Result.append (string_to_mime_encoded_string (ic.key))
Result.append_character ('%"')
Result.append (http_end_of_header_line)
Result.append (http_end_of_header_line)
Result.append (string_to_mime_encoded_string (ic.item))
Result.append (http_end_of_header_line)
end
if a_upload_filename /= Void then
-- get file extension, otherwise set default
create l_mime_type_mapping.make_default
create l_path.make_from_string (a_upload_filename)
if
attached l_path.extension as ext and then
attached l_mime_type_mapping.mime_type (ext) as l_mt
then
l_mime_type := l_mt
else
l_mime_type := "application/octet-stream"
end
Result.append (a_mime_boundary)
Result.append (http_end_of_header_line)
Result.append ("Content-Disposition: form-data; name=%"")
Result.append (string_to_mime_encoded_string (a_upload_filename))
Result.append_character ('%"')
Result.append ("; filename=%"")
Result.append (string_to_mime_encoded_string (a_upload_filename))
Result.append_character ('%"')
Result.append (http_end_of_header_line)
Result.append ("Content-Type: ")
Result.append (l_mime_type)
Result.append (http_end_of_header_line)
Result.append (http_end_of_header_line)
create l_upload_file.make_with_path (l_path)
if l_upload_file.exists and then l_upload_file.is_access_readable then
append_file_content_to (l_upload_file, l_upload_file.count, Result)
-- Reset l_upload_file to Void, since the related content is already processed.
l_upload_file := Void
end
Result.append (http_end_of_header_line)
end
Result.append (a_mime_boundary)
Result.append ("--") --| end
end
string_to_mime_encoded_string (s: READABLE_STRING_GENERAL): STRING
-- Encoded unicode string for mime value.
-- For instance uploaded filename, or form data key or values.
local
utf: UTF_CONVERTER
do
-- FIXME: find the proper encoding!
Result := utf.utf_32_string_to_utf_8_string_8 (s)
end
append_file_content_to_socket (a_file: FILE; a_len: INTEGER; a_output: NETWORK_STREAM_SOCKET)
-- Append `a_file' content to `a_output'.
-- If `a_len' >= 0 then read only `a_len' characters.
require
a_file_readable: a_file.exists and then a_file.is_access_readable
local
l_was_open: BOOLEAN
l_count: INTEGER
do
if a_len >= 0 then
l_count := a_len
else
l_count := a_file.count
end
if l_count > 0 then
l_was_open := a_file.is_open_read
if a_file.is_open_read then
l_was_open := True
else
a_file.open_read
end
from
until
l_count = 0 or a_file.exhausted
loop
a_file.read_stream_thread_aware (l_count.min (2_048))
a_output.put_string (a_file.last_string)
l_count := l_count - a_file.bytes_read
end
if not l_was_open then
a_file.close
end
end
end
append_file_content_to (a_file: FILE; a_len: INTEGER; a_output: STRING)
-- Append `a_file' content to `a_output'.
-- If `a_len' >= 0 then read only `a_len' characters.
require
a_file_readable: a_file.exists and then a_file.is_access_readable
local
l_was_open: BOOLEAN
l_count: INTEGER
do
if a_len >= 0 then
l_count := a_len
else
l_count := a_file.count
end
if l_count > 0 then
l_was_open := a_file.is_open_read
if a_file.is_open_read then
l_was_open := True
else
a_file.open_read
end
from
until
l_count = 0 or a_file.exhausted
loop
a_file.read_stream_thread_aware (l_count.min (2_048))
a_output.append (a_file.last_string)
l_count := l_count - a_file.bytes_read
end
if not l_was_open then
a_file.close
end
end
end
append_socket_header_content_to (a_response: HTTP_CLIENT_RESPONSE; a_socket: NETWORK_STREAM_SOCKET; a_output: STRING)
-- Get header from `a_socket' into `a_output'.
local
s: READABLE_STRING_8
do
from
s := ""
until
s.same_string ("%R") or not a_socket.readable or a_response.error_occurred
loop
a_socket.read_line_thread_aware
s := a_socket.last_string
if s.is_empty then
if session.is_debug_verbose then
log ("Debug: ERROR: zero byte read when receiving header.%N")
end
a_response.set_error_message ("Read zero byte, expecting header line")
elseif s.same_string ("%R") then
-- Reach end of header
else
a_output.append (s)
a_output.append_character ('%N')
end
end
end
append_socket_content_to (a_response: HTTP_CLIENT_RESPONSE; a_socket: NETWORK_STREAM_SOCKET; a_len: INTEGER; a_output: STRING)
-- Get content from `a_socket' and append it to `a_output'.
-- If `a_len' is negative, try to get as much as possible,
-- this is probably HTTP/1.0 without any Content-Length.
local
s: STRING_8
r: INTEGER -- remaining count
n,l_chunk_size, l_count: INTEGER
do
if a_socket.readable then
if a_len >= 0 then
if session.is_debug_verbose then
log ("Debug: Content-Length="+ a_len.out +"%N")
end
from
r := a_len
until
r = 0 or else not a_socket.readable or else a_response.error_occurred
loop
a_socket.read_stream_thread_aware (r)
l_count := l_count + a_socket.bytes_read
if session.is_debug_verbose then
log ("Debug: - byte read=" + a_socket.bytes_read.out + "%N")
log ("Debug: - current count=" + l_count.out + "%N")
end
r := r - a_socket.bytes_read
a_output.append (a_socket.last_string)
end
check full_content_read: not a_response.error_occurred implies l_count = a_len end
elseif attached a_response.header ("Transfer-Encoding") as l_enc and then l_enc.is_case_insensitive_equal ("chunked") then
append_socket_chunked_content_to (a_response, a_socket, a_output)
else
-- No Content-Length and no chunked transfer encoding!
-- maybe HTTP/1.0 ?
-- FIXME: check solution!
from
l_count := 0
l_chunk_size := 1_024
n := l_chunk_size --| value to satisfy until condition on first loop.
until
n < l_chunk_size or not a_socket.readable
loop
a_socket.read_stream_thread_aware (l_chunk_size)
s := a_socket.last_string
n := a_socket.bytes_read
l_count := l_count + n
a_output.append (s)
end
end
end
end
append_socket_chunked_content_to (a_response: HTTP_CLIENT_RESPONSE; a_socket: NETWORK_STREAM_SOCKET; a_output: STRING)
-- Get chunked content from `a_socket' and append it to `a_output'.
require
socket_readable: a_socket.readable
has_chunked_transfer_encoding: attached a_response.header ("Transfer-Encoding") as l_enc and then
l_enc.is_case_insensitive_equal ("chunked")
local
s: STRING_8
r: INTEGER -- remaining count
n,pos, l_count: INTEGER
hexa2int: HEXADECIMAL_STRING_TO_INTEGER_CONVERTER
do
if session.is_debug_verbose then
log ("Debug: Chunked encoding%N")
end
from
create hexa2int.make
n := 1
until
n = 0 or not a_socket.readable
loop
a_socket.read_line_thread_aware -- Read chunk info
s := a_socket.last_string
s.right_adjust
if session.is_debug_verbose then
log ("Debug: - chunk info='" + s + "'%N")
end
pos := s.index_of (';', 1)
if pos > 0 then
s.keep_head (pos - 1)
end
if s.is_empty then
n := 0
else
hexa2int.parse_string_with_type (s, hexa2int.type_integer)
if hexa2int.parse_successful then
n := hexa2int.parsed_integer
else
n := 0
end
end
if session.is_debug_verbose then
log ("Debug: - chunk size=" + n.out + "%N")
end
if n > 0 then
from
r := n
until
r = 0 or else not a_socket.readable or else a_response.error_occurred
loop
a_socket.read_stream_thread_aware (r)
l_count := l_count + a_socket.bytes_read
if session.is_debug_verbose then
log ("Debug: - byte read=" + a_socket.bytes_read.out + "%N")
log ("Debug: - current count=" + l_count.out + "%N")
end
r := r - a_socket.bytes_read
a_output.append (a_socket.last_string)
end
a_socket.read_character
check a_socket.last_character = '%R' end
a_socket.read_character
check a_socket.last_character = '%N' end
if session.is_debug_verbose then
log ("Debug: - Found CRNL %N")
end
end
end
end
new_mime_boundary: STRING
-- New MIME boundary.
do
-- FIXME: better boundary creation
Result := "----------------------------5eadfcf3bb3e"
end
invariant
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,184 @@
note
description: "[
Specific implementation of HTTP_CLIENT_SESSION based on Eiffel NET library
]"
date: "$Date$"
revision: "$Revision$"
class
NET_HTTP_CLIENT_SESSION
inherit
HTTP_CLIENT_SESSION
redefine
close
end
NET_HTTP_CLIENT_INFO
create
make
feature {NONE} -- Initialization
initialize
do
end
feature -- Status report
is_available: BOOLEAN
-- Is interface usable?
do
Result := True
if base_url.starts_with_general ("https://") then
Result := has_https_support
end
end
feature -- Access: persistent connection
persistent_connection: detachable NET_HTTP_CLIENT_CONNECTION
-- Socket used for persistent connection purpose.
feature -- Element change: persistent connection
set_persistent_connection (a_connection: like persistent_connection)
-- Set `persistent_connection' to `a_connection'.
do
persistent_connection := a_connection
end
feature -- Basic operation
close
-- <Precursor>
do
if attached persistent_connection as l_connection then
persistent_connection := Void
l_connection.socket.cleanup
end
end
feature -- Custom
custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
-- <Precursor>
local
req: HTTP_CLIENT_REQUEST
do
create {NET_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, a_method, Current, ctx)
Result := req.response
end
custom_with_upload_data (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- Same as `custom' but including upload data `a_data'.
do
Result := impl_custom (a_method, a_path, a_ctx, data, Void)
end
custom_with_upload_file (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- Same as `custom' but including upload file `fn'.
do
Result := impl_custom (a_method, a_path, a_ctx, Void, fn)
end
feature -- Helper
get (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := custom ("GET", a_path, ctx)
end
head (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := custom ("HEAD", a_path, ctx)
end
post (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := impl_custom ("POST", a_path, a_ctx, data, Void)
end
post_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := impl_custom ("POST", a_path, a_ctx, Void, fn)
end
patch (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := impl_custom ("PATCH", a_path, a_ctx, data, Void)
end
patch_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := impl_custom ("PATCH", a_path, a_ctx, Void, fn)
end
put (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := impl_custom ("PUT", a_path, a_ctx, data, Void)
end
put_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := impl_custom ("PUT", a_path, a_ctx, Void, fn)
end
delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
-- <Precursor>
do
Result := custom ("DELETE", a_path, ctx)
end
feature {NONE} -- Implementation
impl_custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
local
req: HTTP_CLIENT_REQUEST
ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT
l_data: detachable READABLE_STRING_8
do
ctx := a_ctx
if data /= Void then
if ctx = Void then
create ctx.make
end
ctx.set_upload_data (data)
end
if fn /= Void then
if ctx = Void then
create ctx.make
end
ctx.set_upload_filename (fn)
end
if ctx /= Void then
l_data := ctx.upload_data
if l_data /= Void and a_method.is_case_insensitive_equal_general ("PUT") then
check put_conflict_file_and_data: not ctx.has_upload_filename end
end
end
create {NET_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, a_method, Current, ctx)
Result := req.response
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,24 @@
note
description: "Additional information related to NET HTTP Client.."
date: "$Date$"
revision: "$Revision$"
class
NET_HTTP_CLIENT_INFO
feature -- Access
has_https_support: BOOLEAN = False
-- Is HTTPS supported?
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,22 @@
note
description: "[
A fake SSL network stream socket... when SSL is disabled at compilation time.
Its behavior is similar to NETWORK_STREAM_SOCKET.
]"
date: "$Date$"
revision: "$Revision$"
class
SSL_NETWORK_STREAM_SOCKET
inherit
NETWORK_STREAM_SOCKET
create
make, make_empty, make_client_by_port, make_client_by_address_and_port, make_server_by_port, make_loopback_server_by_port
create {SSL_NETWORK_STREAM_SOCKET}
make_from_descriptor_and_address, create_from_descriptor
end

View File

@@ -0,0 +1,24 @@
note
description: "Additional information related to NET HTTP Client.."
date: "$Date$"
revision: "$Revision$"
class
NET_HTTP_CLIENT_INFO
feature -- Access
has_https_support: BOOLEAN = True
-- Is HTTPS supported?
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,110 @@
note
description : "[
NULL version of HTTP_CLIENT_SESSION.
It is used if no implementation is available (libcurl or net)
]"
date: "$Date$"
revision: "$Revision$"
class
NULL_HTTP_CLIENT_SESSION
inherit
HTTP_CLIENT_SESSION
create
make
feature {NONE} -- Initialization
initialize
do
end
feature -- Custom
custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
do
create Result.make (base_url + a_path)
end
custom_with_upload_data (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
do
Result := impl_custom (a_method, a_path, a_ctx, data, Void)
end
custom_with_upload_file (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
do
Result := impl_custom (a_method, a_path, a_ctx, Void, fn)
end
feature -- Helper
get (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
do
Result := custom ("GET", a_path, ctx)
end
head (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
do
Result := custom ("HEAD", a_path, ctx)
end
post (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
do
Result := impl_custom ("POST", a_path, a_ctx, data, Void)
end
post_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
do
Result := impl_custom ("POST", a_path, a_ctx, Void, fn)
end
patch (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
do
Result := impl_custom ("PATCH", a_path, a_ctx, data, Void)
end
patch_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
do
Result := impl_custom ("PATCH", a_path, a_ctx, Void, fn)
end
put (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
do
Result := impl_custom ("PUT", a_path, a_ctx, data, Void)
end
put_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
do
Result := impl_custom ("PUT", a_path, a_ctx, Void, fn)
end
delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
do
Result := custom ("DELETE", a_path, ctx)
end
feature {NONE} -- Implementation
impl_custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
do
Result := custom (a_method, a_path, a_ctx)
end
feature -- Status report
is_available: BOOLEAN = False
-- Is interface usable?
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="test_http_client" uuid="920E5C50-41E1-4DAC-8D48-D9C860E49228">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="test_http_client" uuid="920E5C50-41E1-4DAC-8D48-D9C860E49228">
<target name="test_http_client">
<root class="TEST" feature="make"/>
<file_rule>
@@ -10,6 +10,9 @@
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<variable name="netssl_http_client_enabled" value="false"/>
<variable name="net_http_client_disabled" value="false"/>
<variable name="libcurl_http_client_disabled" value="false"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http_client" location="..\http_client-safe.ecf" readonly="false" use_application_options="true">
<option>
@@ -17,6 +20,19 @@
</option>
</library>
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
<tests name="tests" location=".\"/>
<tests name="tests" location=".\">
<file_rule>
<exclude>.*libcurl_.*.e$</exclude>
<condition>
<custom name="libcurl_http_client_disabled" value="true"/>
</condition>
</file_rule>
<file_rule>
<exclude>.*net_.*.e$</exclude>
<condition>
<custom name="net_http_client_disabled" value="true"/>
</condition>
</file_rule>
</tests>
</target>
</system>

View File

@@ -6,17 +6,46 @@ create
feature -- Init
make
local
null: NULL_HTTP_CLIENT_SESSION
do
create null.make ("http://example.com/")
check not null.is_available end
test_get_with_authentication
test_http_client
end
test_get_with_authentication
local
cl: DEFAULT_HTTP_CLIENT
sess: HTTP_CLIENT_SESSION
ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
-- GET REQUEST WITH AUTHENTICATION, see http://browserspy.dk/password.php
-- check header WWW-Authenticate is received (authentication successful)
create cl
sess := cl.new_session ("http://browserspy.dk")
sess.set_credentials ("test", "test")
create ctx.make_with_credentials_required
if attached sess.get ("/password-ok.php", ctx) as res then
if attached {READABLE_STRING_8} res.body as l_body then
assert ("Fetch all body, including closing html tag", l_body.has_substring ("</html>"))
else
assert ("has body", False)
end
end
end
test_http_client
-- New test routine
local
sess: LIBCURL_HTTP_CLIENT_SESSION
cl: DEFAULT_HTTP_CLIENT
sess: HTTP_CLIENT_SESSION
h: STRING_8
do
create sess.make ("http://www.google.com")
create cl
sess := cl.new_session ("http://www.google.com")
if attached sess.get ("/search?q=eiffel", Void) as res then
assert ("Get returned without error", not res.error_occurred)
create h.make_empty
@@ -42,7 +71,7 @@ feature -- Init
do
if not b then
create e
e.set_message (m)
e.set_description (m)
e.raise
end
end

View File

@@ -0,0 +1 @@
This is a text sample for testing HTTP Client library.

View File

@@ -7,21 +7,27 @@ note
revision: "$Revision$"
testing: "type/manual"
class
TEST_HTTP_CLIENT
deferred class
TEST_HTTP_CLIENT_I
inherit
EQA_TEST_SET
feature -- Factory
new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
deferred
end
feature -- Test routines
test_http_client
-- New test routine
local
sess: LIBCURL_HTTP_CLIENT_SESSION
sess: like new_session
h: STRING_8
do
create sess.make ("http://www.google.com")
sess := new_session ("http://www.google.com")
if attached sess.get ("/search?q=eiffel", Void) as res then
assert ("Get returned without error", not res.error_occurred)
create h.make_empty
@@ -38,8 +44,33 @@ feature -- Test routines
assert ("missing body", False)
end
assert ("same headers", h.same_string (res.raw_header))
end
end
test_http_client_ssl
-- New test routine
local
sess: like new_session
h: STRING_8
do
sess := new_session ("https://www.eiffel.org")
sess.set_is_insecure (True)
if attached sess.get ("/welcome", Void) as res then
assert ("Get returned without error", not res.error_occurred)
create h.make_empty
if attached res.headers as hds then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
if attached res.body as l_body then
assert ("body not empty", not l_body.is_empty)
else
assert ("Not found", False)
assert ("missing body", False)
end
assert ("same headers", h.same_string (res.raw_header))
end
end

View File

@@ -0,0 +1,43 @@
note
description: "[
Eiffel tests that can be executed by testing tool.
]"
author: "EiffelStudio test wizard"
date: "$Date$"
revision: "$Revision$"
testing: "type/manual"
class
TEST_LIBCURL_HTTP_CLIENT
inherit
TEST_HTTP_CLIENT_I
feature -- Factory
new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
do
create {LIBCURL_HTTP_CLIENT_SESSION} Result.make (a_url)
end
feature -- Tests
test_libcurl_http_client
do
test_http_client
end
test_libcurl_http_client_ssl
do
test_http_client_ssl
end
test_libcurl_headers
do
test_headers
end
end

View File

@@ -0,0 +1,67 @@
note
description: "[
Objects that ...
]"
author: "$Author$"
date: "$Date$"
revision: "$Revision$"
class
TEST_LIBCURL_WITH_WEB
inherit
TEST_WITH_WEB_I
feature -- Factory
new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
local
cl: LIBCURL_HTTP_CLIENT
do
create cl
Result := cl.new_session (a_url)
end
feature -- Tests
libcurl_test_post_url_encoded
do
test_post_url_encoded
end
libcurl_test_post_with_form_data
do
test_post_with_form_data
end
libcurl_test_post_with_file
do
test_post_with_file
end
libcurl_test_put_with_file
do
test_put_with_file
end
libcurl_test_put_with_data
do
test_put_with_data
end
libcurl_test_post_with_file_and_form_data
do
test_post_with_file_and_form_data
end
libcurl_test_get_with_redirection
do
test_get_with_redirection
end
libcurl_test_get_with_authentication
do
test_get_with_authentication
end
end

View File

@@ -0,0 +1,86 @@
note
description: "[
Eiffel tests that can be executed by testing tool.
]"
author: "EiffelStudio test wizard"
date: "$Date$"
revision: "$Revision$"
testing: "type/manual"
class
TEST_NET_HTTP_CLIENT
inherit
TEST_HTTP_CLIENT_I
feature -- Factory
new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
do
create {NET_HTTP_CLIENT_SESSION} Result.make (a_url)
end
feature -- Tests
test_net_http_client
do
test_http_client
end
test_net_http_client_ssl
do
test_http_client_ssl
end
test_net_headers
do
test_headers
end
test_persistent_connection
local
sess: like new_session
h: STRING_8
do
sess := new_session ("http://www.google.fr")
if attached sess.get ("/", Void) as res then
assert ("Get returned without error", not res.error_occurred)
create h.make_empty
if attached res.headers as hds then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
if attached res.body as l_body then
assert ("body not empty", not l_body.is_empty)
else
assert ("missing body", False)
end
assert ("same headers", h.same_string (res.raw_header))
end
if attached sess.get ("/", Void) as res then
assert ("Get returned without error", not res.error_occurred)
create h.make_empty
if attached res.headers as hds then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
if attached res.body as l_body then
assert ("body not empty", not l_body.is_empty)
else
assert ("missing body", False)
end
assert ("same headers", h.same_string (res.raw_header))
end
sess.close
end
end

View File

@@ -0,0 +1,67 @@
note
description: "[
Objects that ...
]"
author: "$Author$"
date: "$Date$"
revision: "$Revision$"
class
TEST_NET_WITH_WEB
inherit
TEST_WITH_WEB_I
feature -- Factory
new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
local
cl: NET_HTTP_CLIENT
do
create cl
Result := cl.new_session (a_url)
end
feature -- Tests
net_test_post_url_encoded
do
test_post_url_encoded
end
net_test_post_with_form_data
do
test_post_with_form_data
end
net_test_post_with_file
do
test_post_with_file
end
net_test_put_with_file
do
test_put_with_file
end
net_test_put_with_data
do
test_put_with_data
end
net_test_post_with_file_and_form_data
do
test_post_with_file_and_form_data
end
net_test_get_with_redirection
do
test_get_with_redirection
end
net_test_get_with_authentication
do
test_get_with_authentication
end
end

View File

@@ -0,0 +1,302 @@
note
description: "[
Eiffel tests that can be executed by testing tool.
]"
author: "EiffelStudio test wizard"
date: "$Date$"
revision: "$Revision$"
testing: "type/manual"
deferred class
TEST_WITH_WEB_I
inherit
EQA_TEST_SET
redefine
on_prepare
end
feature -- Initialization
on_prepare
do
Precursor
global_requestbin_path := new_requestbin_path
end
feature -- Factory
new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
deferred
end
feature -- Requestbin
global_requestbin_path: detachable READABLE_STRING_8
new_requestbin_path: detachable STRING
local
i,j: INTEGER
do
if
attached new_session ("http://requestb.in") as sess and then
attached sess.post ("/api/v1/bins", Void, Void) as resp
then
if resp.error_occurred then
print ("Error occurred!%N")
elseif attached resp.body as l_content then
i := l_content.substring_index ("%"name%":", 1)
if i > 0 then
j := l_content.index_of (',', i + 1)
if j > 0 then
Result := l_content.substring (i + 7, j - 1)
Result.adjust
if Result.starts_with ("%"") then
Result.remove_head (1)
end
if Result.ends_with ("%"") then
Result.remove_tail (1)
end
if not Result.starts_with ("/") then
Result.prepend_character ('/')
end
end
end
end
end
end
feature -- Factory
test_post_url_encoded
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
do
if attached global_requestbin_path as requestbin_path then
-- URL ENCODED POST REQUEST
-- check requestbin to ensure the "Hello World" has been received in the raw body
-- also check that User-Agent was sent
create h.make_empty
sess := new_session ("http://requestb.in")
if
attached sess.post (requestbin_path, Void, "Hello World") as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
end
end
test_post_with_form_data
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- POST REQUEST WITH FORM DATA
-- check requestbin to ensure the form parameters are correctly received
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.form_parameters.extend ("First Value", "First Key")
l_ctx.form_parameters.extend ("Second Value", "Second Key")
create h.make_empty
if
attached sess.post (requestbin_path, l_ctx, "") as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
end
end
test_post_with_file
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- POST REQUEST WITH A FILE
-- check requestbin to ensure the form parameters are correctly received
-- set filename to a local file
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.set_upload_filename ("test.txt")
create h.make_empty
if
attached sess.post (requestbin_path, l_ctx, "") as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
end
end
test_put_with_file
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- PUT REQUEST WITH A FILE
-- check requestbin to ensure the file is correctly received
-- set filename to a local file
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.set_upload_filename ("test.txt")
create h.make_empty
if
attached sess.put (requestbin_path, l_ctx, Void) as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
end
end
test_put_with_data
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- PUT REQUEST WITH A FILE
-- check requestbin to ensure the file is correctly received
-- set filename to a local file
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.set_upload_data ("This is a test for http client.%N")
create h.make_empty
if
attached sess.put (requestbin_path, l_ctx, Void) as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
end
end
test_post_with_file_and_form_data
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- POST REQUEST WITH A FILE AND FORM DATA
-- check requestbin to ensure the file and form parameters are correctly received
-- set filename to a local file
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.set_upload_filename ("logo.jpg")
l_ctx.form_parameters.extend ("First Value", "First Key")
l_ctx.form_parameters.extend ("Second Value", "Second Key")
create h.make_empty
if
attached sess.post (requestbin_path, l_ctx, Void) as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
end
end
test_get_with_redirection
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
do
if attached global_requestbin_path as requestbin_path then
-- GET REQUEST, Forwarding (google's first answer is a forward)
-- check headers received (printed in console)
sess := new_session ("http://google.com")
create h.make_empty
if attached sess.get ("/", Void) as res and then attached res.headers as hds then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
end
end
test_get_with_authentication
local
sess: HTTP_CLIENT_SESSION
ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
-- GET REQUEST WITH AUTHENTICATION, see http://browserspy.dk/password.php
-- check header WWW-Authenticate is received (authentication successful)
sess := new_session ("http://browserspy.dk")
sess.set_credentials ("test", "test")
create ctx.make_with_credentials_required
if attached sess.get ("/password-ok.php", ctx) as res then
if attached {READABLE_STRING_8} res.body as l_body then
assert ("Fetch all body, including closing html tag", l_body.has_substring ("</html>"))
else
assert ("has body", False)
end
end
end
end

View File

@@ -122,8 +122,8 @@ feature {NONE} -- Internal
feature -- Conversion to string
yyyy_mmm_dd_string: STRING
-- String representation YYYY mmm dd
-- 2012 Dec 25
-- String representation [YYYY mmm dd]
-- ex: 2012 Dec 25
do
create Result.make (11)
append_date_time_to_yyyy_mmm_dd_string (date_time, Result)
@@ -131,7 +131,8 @@ feature -- Conversion to string
rfc1123_string: STRING
-- String representation following RFC 1123.
--| Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
-- format: [ddd, dd mmm yyyy hh:mi:ss GMT]
-- ex: Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
local
s: like internal_rfc1123_string
do
@@ -145,7 +146,8 @@ feature -- Conversion to string
end
rfc850_string: STRING
-- String representation following RFC 850
-- String representation following RFC 850.
-- format: [mmm, dd-mmm-yy hh:mi:ss GMT]
do
create Result.make (32)
append_date_time_to_rfc850_string (date_time, Result)
@@ -153,6 +155,7 @@ feature -- Conversion to string
ansi_c_string: STRING
-- ANSI C's asctime() format
--| Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
do
create Result.make (32)
append_date_time_to_ansi_c_string (date_time, Result)
@@ -161,74 +164,34 @@ feature -- Conversion to string
feature -- Conversion into string
append_to_yyyy_mmm_dd_string (s: STRING_GENERAL)
local
dt: DATE_TIME
-- Append `datetime' as [yyyy mmm dd] format to `s'.
do
dt := date_time
append_integer_to (dt.year, s) -- yyyy
s.append_code (32) -- 32 ' ' -- SPace
append_month_mmm_to (dt.month, s) -- mmm
s.append_code (32) -- 32 ' ' -- SPace
append_2_digits_integer_to (dt.day, s) -- dd
append_date_time_to_yyyy_mmm_dd_string (date_time, s)
end
append_to_rfc1123_string (s: STRING_GENERAL)
local
dt: DATE_TIME
-- Append `date_time' as [ddd, dd mmm yyyy hh:mi:ss GMT] format to `s'.
do
dt := date_time
append_day_ddd_to (dt.date.day_of_the_week, s) -- ddd
s.append_code (44) -- 44 ',' -- ','
s.append_code (32) -- 32 ' ' -- SPace
append_2_digits_integer_to (dt.day, s) -- dd
s.append_code (32) -- 32 ' ' -- SPace
append_month_mmm_to (dt.month, s) -- mmm
s.append_code (32) -- 32 ' ' -- SPace
append_integer_to (dt.year, s) -- YYYY
s.append_code (32) -- 32 ' ' -- SPace
append_2_digits_time_to (dt.time, s) -- hh:mi:ss
s.append (" GMT") -- SPace + GMT
append_date_time_to_rfc1123_string (date_time, s)
end
append_rfc850_string (s: STRING_GENERAL)
local
dt: DATE_TIME
-- Append `date_time' as [mmm, dd-mmm-yy hh:mi:ss GMT] format to `s'.
do
dt := date_time
append_day_name_to (dt.date.day_of_the_week, s) -- mmm
s.append_code (44) -- 44 ',' -- ','
s.append_code (32) -- 32 ' ' -- SPace
append_2_digits_integer_to (dt.day, s) -- dd
s.append_code (45) -- 45 '-' -- '-'
append_month_mmm_to (dt.month, s) -- mmm
s.append_code (45) -- 45 '-' -- '-'
append_integer_to (dt.year \\ 100, s) -- yy
s.append_code (32) -- 32 ' ' -- SPace
append_2_digits_time_to (dt.time, s) -- hh:mi:ss
s.append (" GMT") -- SPace + GMT
append_date_time_to_rfc850_string (date_time, s)
end
append_to_ansi_c_string (s: STRING_GENERAL)
-- Append `date_time' as ANSI C's asctime format to `s'.
--| Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
local
dt: DATE_TIME
do
dt := date_time
append_day_ddd_to (dt.date.day_of_the_week, s) -- ddd
s.append_code (32) -- 32 ' ' -- SPace
append_month_mmm_to (dt.month, s) -- mmm
s.append_code (32) -- 32 ' ' -- SPace
s.append_code (32) -- 32 ' ' -- SPace
append_integer_to (dt.day, s) -- d
s.append_code (32) -- 32 ' ' -- SPace
append_2_digits_time_to (dt.time, s) -- hh:mi:ss
s.append_code (32) -- 32 ' ' -- SPace
append_integer_to (dt.year, s) -- yyyy
append_date_time_to_ansi_c_string (date_time, s)
end
feature -- Conversion into string
append_date_time_to_yyyy_mmm_dd_string (dt: DATE_TIME; s: STRING_GENERAL)
-- Append `dt' as [yyyy mmm dd] format to `s'.
do
append_integer_to (dt.year, s) -- yyyy
s.append_code (32) -- 32 ' ' -- SPace
@@ -238,6 +201,7 @@ feature -- Conversion into string
end
append_date_time_to_rfc1123_string (dt: DATE_TIME; s: STRING_GENERAL)
-- Append `dt' as [ddd, dd mmm yyyy hh:mi:ss GMT] format to `s'.
do
append_day_ddd_to (dt.date.day_of_the_week, s) -- ddd
s.append_code (44) -- 44 ',' -- ','
@@ -253,6 +217,7 @@ feature -- Conversion into string
end
append_date_time_to_rfc850_string (dt: DATE_TIME; s: STRING_GENERAL)
-- Append `dt' as [mmm, dd-mmm-yy hh:mi:ss GMT] format to `s'.
do
append_day_name_to (dt.date.day_of_the_week, s) -- mmm
s.append_code (44) -- 44 ',' -- ','
@@ -268,7 +233,8 @@ feature -- Conversion into string
end
append_date_time_to_ansi_c_string (dt: DATE_TIME; s: STRING_GENERAL)
--| Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
-- Append `dt' as ANSI C's asctime format to `s'.
-- Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
do
append_day_ddd_to (dt.date.day_of_the_week, s) -- ddd
s.append_code (32) -- 32 ' ' -- SPace
@@ -294,7 +260,7 @@ feature -- Status report
end
end
feature {NONE} -- Implementation
feature -- Helper routines.
append_2_digits_integer_to (i: INTEGER; s: STRING_GENERAL)
require
@@ -599,7 +565,11 @@ feature {NONE} -- Implementation
t.extend (s[i].as_upper)
i := i + 1
end
if t.same_string ("GMT") or t.same_string ("UTC") then
if
t.same_string ("GMT") -- for instance: GMT+0002
or t.same_string ("UTC") -- for instance: UTC+0002
or t.is_empty -- for instance: +0002
then
from until i > n or else not s[i].is_space loop i := i + 1 end
if i <= n then
t.wipe_out

View File

@@ -202,11 +202,6 @@ feature -- SSL Helpers
deferred
end
set_ssl_protocol_to_ssl_3
-- Set `ssl_protocol' with `Ssl_3'.
deferred
end
set_ssl_protocol_to_tls_1_0
-- Set `ssl_protocol' with `Tls_1_0'.
deferred

View File

@@ -27,11 +27,6 @@ feature -- SSL Helpers
-- Ignored
end
set_ssl_protocol_to_ssl_3
-- Set `ssl_protocol' with `Ssl_3'.
do
-- Ignored
end
set_ssl_protocol_to_tls_1_0
-- Set `ssl_protocol' with `Tls_1_0'.

View File

@@ -41,12 +41,6 @@ feature -- SSL Helpers
set_ssl_protocol ({SSL_PROTOCOL}.Ssl_23)
end
set_ssl_protocol_to_ssl_3
-- Set `ssl_protocol' with `Ssl_3'.
do
set_ssl_protocol ({SSL_PROTOCOL}.Ssl_3)
end
set_ssl_protocol_to_tls_1_0
-- Set `ssl_protocol' with `Tls_1_0'.
do

View File

@@ -59,6 +59,7 @@ feature -- Access
create dbg.make
dbg.set_is_verbose (True)
dbg.set_unicode_output_enabled (True)
dbg.append_cgi_variables_to (req, res, s)
dbg.append_information_to (req, res, s)

View File

@@ -42,6 +42,9 @@ feature -- Settings
is_verbose: BOOLEAN
-- Has verbose output (default: True)?
unicode_output_enabled: BOOLEAN
-- Display names and values as unicode.
feature -- Settings change
set_is_verbose (b: BOOLEAN)
@@ -50,6 +53,12 @@ feature -- Settings change
is_verbose := b
end
set_unicode_output_enabled (b: BOOLEAN)
-- if `b' then enable unicode output, otherwise disable.
do
unicode_output_enabled := b
end
feature -- Execution
append_connector_informations_to (req: WSF_REQUEST; res: WSF_RESPONSE; a_output: STRING)
@@ -232,7 +241,7 @@ feature {NONE} -- Implementation
it as c
loop
s.append (" - ")
s.append (utf.escaped_utf_32_string_to_utf_8_string_8 (c.key))
append_unicode (c.key, s)
s.append_character (' ')
if attached c.item as l_item then
s.append_character ('{')
@@ -302,7 +311,11 @@ feature {NONE} -- Implementation
it as c
loop
a_output.append (" - ")
if unicode_output_enabled then
append_unicode (c.item.name, a_output)
else
a_output.append (c.item.url_encoded_name)
end
t := c.item.generating_type
if t.same_string ("WSF_STRING") then
else
@@ -313,15 +326,15 @@ feature {NONE} -- Implementation
end
a_output.append_character ('=')
if attached {WSF_STRING} c.item as l_str then
if unicode_output_enabled then
s := l_str.value
else
s := l_str.url_encoded_value
end
else
s := c.item.string_representation
end
if s.is_valid_as_string_8 then
v := s.as_string_8
else
v := utf.escaped_utf_32_string_to_utf_8_string_8 (s)
end
v := utf.utf_32_string_to_utf_8_string_8 (s)
if v.has ('%N') then
a_output.append (eol)
across
@@ -349,7 +362,6 @@ feature {NONE} -- Implementation
local
n: INTEGER
v: READABLE_STRING_8
utf: UTF_CONVERTER
do
if is_verbose then
a_output.append (a_title)
@@ -368,11 +380,7 @@ feature {NONE} -- Implementation
it as c
loop
a_output.append (" - ")
if c.key.is_valid_as_string_8 then
a_output.append (c.key.as_string_8)
else
a_output.append (utf.utf_32_string_to_utf_8_string_8 (c.key))
end
append_unicode (c.key, a_output)
a_output.append_character ('=')
v := c.item
if v.has ('%N') then
@@ -398,6 +406,13 @@ feature {NONE} -- Implementation
end
end
append_unicode (s: READABLE_STRING_GENERAL; a_output: STRING)
local
utf: UTF_CONVERTER
do
a_output.append (utf.utf_32_string_to_utf_8_string_8 (s))
end
feature -- Constants
eol: STRING = "%N"

View File

@@ -58,6 +58,7 @@ feature -- Status
do
p := a_path
l_uri := based_uri (uri, a_router)
if trailing_slash_ignored then
if l_uri.ends_with ("/") then
if not p.ends_with ("/") then
p := p + "/"
@@ -67,6 +68,7 @@ feature -- Status
p := p.substring (1, p.count - 1)
end
end
end
if p.same_string (l_uri) then
Result := True
end

View File

@@ -1,6 +1,5 @@
note
description: "Summary description for {WSF_APPLICATION_X_WWW_FORM_URLENCODED_HANDLER}."
author: ""
date: "$Date$"
revision: "$Revision$"
@@ -12,6 +11,11 @@ inherit
WSF_MIME_HANDLER_HELPER
WSF_VALUE_UTILITIES
export
{NONE} all
end
feature -- Status report
valid_content_type (a_content_type: HTTP_CONTENT_TYPE): BOOLEAN
@@ -54,7 +58,7 @@ feature -- Execution
if j > 0 then
l_name := s.substring (1, j - 1)
l_value := s.substring (j + 1, s.count)
add_string_value_to_table (l_name, l_value, a_vars)
add_percent_encoded_string_value_to_table (l_name, l_value, a_vars)
end
end
end
@@ -62,7 +66,7 @@ feature -- Execution
end
note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -1,6 +1,5 @@
note
description: "Summary description for {WSF_MULTIPART_FORM_DATA_HANDLER}."
author: ""
date: "$Date$"
revision: "$Revision$"
@@ -139,10 +138,12 @@ feature {NONE} -- Implementation: Form analyzer
local
n, i,p, b,e: INTEGER
l_name, l_filename, l_content_type: detachable STRING_8
l_unicode_name: READABLE_STRING_32
l_header: detachable STRING_8
l_content: detachable STRING_8
l_line: detachable STRING_8
l_up_file: WSF_UPLOADED_FILE
utf: UTF_CONVERTER
do
from
p := 1
@@ -235,12 +236,13 @@ feature {NONE} -- Implementation: Form analyzer
if l_content_type = Void then
l_content_type := default_content_type
end
create l_up_file.make (l_name, l_filename, l_content_type, l_content.count)
add_value_to_table (l_name, l_up_file, vars)
l_unicode_name := utf.utf_8_string_8_to_string_32 (l_name)
create l_up_file.make (l_unicode_name, utf.utf_8_string_8_to_escaped_string_32 (l_filename), l_content_type, l_content.count)
add_value_to_table (l_unicode_name, l_up_file, vars)
--| `l_up_file' might have a new name
req.save_uploaded_file (l_up_file, l_content)
else
add_string_value_to_table (l_name, l_content, vars)
add_utf_8_string_value_to_table (l_name, l_content, vars)
end
else
req.error_handler.add_custom_error (0, "unamed multipart entry", Void)
@@ -254,7 +256,7 @@ feature {NONE} -- Implementation: Form analyzer
-- Default content type
note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -34,10 +34,10 @@ feature -- Access
feature -- Element change
change_name (a_name: like name)
-- <Precursor>
do
name := url_decoded_string (a_name)
ensure then
a_name.same_string (url_encoded_name)
name := a_name
url_encoded_name := url_encoded_string (a_name)
end
feature -- Status report
@@ -71,7 +71,7 @@ feature -- Visitor
end
note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -89,10 +89,9 @@ feature -- Access
feature -- Element change
change_name (a_name: like name)
-- <Precursor>
do
name := a_name
ensure then
a_name.same_string (name)
end
feature -- Status report
@@ -180,7 +179,7 @@ invariant
string_values_not_empty: values.count >= 1
note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -16,17 +16,24 @@ inherit
end
create
make
make,
make_with_percent_encoded_values
convert
string_representation: {READABLE_STRING_32, STRING_32}
feature {NONE} -- Initialization
make (a_name: READABLE_STRING_8; a_string: READABLE_STRING_8)
make (a_name: READABLE_STRING_GENERAL; a_string: READABLE_STRING_GENERAL)
do
url_encoded_name := utf_8_percent_encoded_string (a_name)
url_encoded_value := utf_8_percent_encoded_string (a_string)
url_encoded_name := url_encoded_string (a_name)
url_encoded_value := url_encoded_string (a_string)
end
make_with_percent_encoded_values (a_encoded_name: READABLE_STRING_8; a_encoded_value: READABLE_STRING_8)
do
url_encoded_name := a_encoded_name
url_encoded_value := a_encoded_value
end
feature -- Access
@@ -105,11 +112,10 @@ feature -- Status report
feature -- Element change
change_name (a_name: like name)
-- <Precursor>
do
internal_name := Void
url_encoded_name := a_name
ensure then
a_name.same_string (url_encoded_name)
internal_name := a_name
url_encoded_name := url_encoded_string (a_name)
end
feature -- Helper
@@ -147,89 +153,8 @@ feature -- Visitor
vis.process_string (Current)
end
feature {NONE} -- Implementation
utf_8_percent_encoded_string (s: READABLE_STRING_8): READABLE_STRING_8
-- Percent-encode the UTF-8 sequence characters from UTF-8 encoded `s' and
-- return the Result.
local
s8: STRING_8
i, n, nb: INTEGER
do
-- First check if there are such UTF-8 character
-- If it has, convert them and return a new object as Result
-- otherwise return `s' directly to avoid creating a new object
from
i := 1
n := s.count
nb := 0
until
i > n
loop
if s.code (i) > 0x7F then -- >= 128
nb := nb + 1
end
i := i + 1
end
if nb > 0 then
create s8.make (s.count + nb * 3)
utf_8_string_8_into_percent_encoded_string_8 (s, s8)
Result := s8
else
Result := s
end
end
utf_8_string_8_into_percent_encoded_string_8 (s: READABLE_STRING_8; a_result: STRING_8)
-- Copy STRING_32 corresponding to UTF-8 sequence `s' appended into `a_result'.
local
i: INTEGER
n: INTEGER
c: NATURAL_32
do
from
n := s.count
a_result.grow (a_result.count + n)
until
i >= n
loop
i := i + 1
c := s.code (i)
if c <= 0x7F then
-- 0xxxxxxx
a_result.append_code (c)
elseif c <= 0xDF then
-- 110xxxxx 10xxxxxx
url_encoder.append_percent_encoded_character_code_to (c, a_result)
i := i + 1
if i <= n then
url_encoder.append_percent_encoded_character_code_to (s.code (i), a_result)
end
elseif c <= 0xEF then
-- 1110xxxx 10xxxxxx 10xxxxxx
url_encoder.append_percent_encoded_character_code_to (s.code (i), a_result)
i := i + 2
if i <= n then
url_encoder.append_percent_encoded_character_code_to (s.code (i - 1), a_result)
url_encoder.append_percent_encoded_character_code_to (s.code (i), a_result)
end
elseif c <= 0xF7 then
-- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
url_encoder.append_percent_encoded_character_code_to (s.code (i), a_result)
i := i + 3
if i <= n then
url_encoder.append_percent_encoded_character_code_to (s.code (i - 2), a_result)
url_encoder.append_percent_encoded_character_code_to (s.code (i - 1), a_result)
url_encoder.append_percent_encoded_character_code_to (s.code (i), a_result)
end
else
a_result.append_code (c)
end
end
end
note
copyright: "2011-2014, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -2,8 +2,8 @@ note
description: "[
Table which can contain value indexed by a key
]"
date: "$Date$"
revision: "$Revision$"
date: "$Date: 2015-11-05 21:52:56 +0100 (jeu., 05 nov. 2015) $"
revision: "$Revision: 98081 $"
class
WSF_TABLE
@@ -14,26 +14,37 @@ inherit
as_string
end
ITERABLE [WSF_VALUE]
TABLE_ITERABLE [WSF_VALUE, READABLE_STRING_32]
create
make
make,
make_with_percent_encoded_name
feature {NONE} -- Initialization
make (a_name: READABLE_STRING_8)
make (a_name: READABLE_STRING_GENERAL)
do
name := url_decoded_string (a_name)
url_encoded_name := a_name
create values.make (5)
name := a_name.as_string_32
url_encoded_name := url_encoded_string (a_name)
create values.make (3)
end
make_with_percent_encoded_name (a_encoded_name: READABLE_STRING_8)
do
url_encoded_name := a_encoded_name
name := url_decoded_string (a_encoded_name)
create values.make (3)
end
feature -- Access
name: READABLE_STRING_32
-- Parameter name
-- <Precursor>
--| Note that the value might be html encoded as well
--| this is the application responsibility to html decode it
url_encoded_name: READABLE_STRING_8
-- URL encoded string of `name'.
first_value: detachable WSF_VALUE
-- First value if any.
@@ -59,13 +70,16 @@ feature -- Access
end
values: HASH_TABLE [WSF_VALUE, STRING_32]
-- Associated items values, indexed by unicode name.
value (k: READABLE_STRING_GENERAL): detachable WSF_VALUE
-- Value associated with name `k'.
do
Result := values.item (k.to_string_32)
end
count: INTEGER
-- Number of values.
do
Result := values.count
end
@@ -77,14 +91,15 @@ feature -- Access
Result := first_name
end
feature -- Element change
feature -- Element change
change_name (a_name: like name)
-- <Precursor>
do
name := url_decoded_string (a_name)
url_encoded_name := a_name
ensure then
a_name.same_string (url_encoded_name)
name := a_name
url_encoded_name := url_encoded_string (a_name)
end
feature -- Status report
@@ -178,7 +193,7 @@ feature -- Element change
feature -- Traversing
new_cursor: ITERATION_CURSOR [WSF_VALUE]
new_cursor: TABLE_ITERATION_CURSOR [WSF_VALUE, READABLE_STRING_32]
do
Result := values.new_cursor
end
@@ -217,7 +232,7 @@ feature -- Visitor
end
note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -15,17 +15,27 @@ inherit
end
create
make
make,
make_with_percent_encoded_values
feature {NONE} -- Initialization
make (a_name: READABLE_STRING_8; n: like filename; t: like content_type; s: like size)
make (a_name: READABLE_STRING_GENERAL; a_filename: READABLE_STRING_GENERAL; a_content_type: like content_type; a_size: like size)
do
name := url_decoded_string (a_name)
url_encoded_name := a_name
filename := n
content_type := t
size := s
name := a_name.as_string_32
url_encoded_name := url_encoded_string (a_name)
filename := a_filename.as_string_32
content_type := a_content_type
size := a_size
end
make_with_percent_encoded_values (a_encoded_name: READABLE_STRING_8; a_filename: READABLE_STRING_GENERAL; a_content_type: like content_type; a_size: like size)
do
name := url_decoded_string (a_encoded_name)
url_encoded_name := a_encoded_name
filename := a_filename.as_string_32
content_type := a_content_type
size := a_size
end
feature -- Access
@@ -65,11 +75,10 @@ feature -- Status report
feature -- Element change
change_name (a_name: like name)
-- <Precursor>
do
name := url_decoded_string (a_name)
url_encoded_name := a_name
ensure then
a_name.same_string (url_encoded_name)
name := a_name
url_encoded_name := url_encoded_string (a_name)
end
feature -- Status report
@@ -99,7 +108,7 @@ feature -- Visitor
feature -- Access: Uploaded File
filename: STRING
filename: STRING_32
-- original filename
safe_filename: STRING
@@ -241,7 +250,7 @@ feature -- Element change
end
note
copyright: "2011-2014, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -36,8 +36,10 @@ feature -- Access
feature -- Element change
change_name (a_name: like name)
-- Change parameter name
-- Change parameter `name'.
deferred
ensure
name_changed: a_name.same_string (name)
end
feature -- Status report
@@ -128,7 +130,7 @@ feature -- Visitor
end
note
copyright: "2011-2014, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -7,122 +7,25 @@ note
deferred class
WSF_MIME_HANDLER_HELPER
inherit
ANY
WSF_VALUE_UTILITIES
export
{NONE} all
end
feature {NONE} -- Implementation
full_input_data (req: WSF_REQUEST): STRING_8
-- Read or reused full input data from `req'.
do
create Result.make (0)
req.read_input_data_into (Result)
end
add_string_value_to_table (a_name: READABLE_STRING_8; a_value: READABLE_STRING_8; a_table: HASH_TABLE [WSF_VALUE, READABLE_STRING_GENERAL])
do
add_value_to_table (a_name, new_string_value (a_name, a_value), a_table)
end
add_value_to_table (a_name: READABLE_STRING_8; a_value: WSF_VALUE; a_table: HASH_TABLE [WSF_VALUE, READABLE_STRING_GENERAL])
local
l_decoded_name: STRING_32
v: detachable WSF_VALUE
n,k,r: STRING_32
-- k32: STRING_32
p,q: INTEGER
tb,ptb: detachable WSF_TABLE
do
--| Check if this is a list format such as choice[] or choice[a] or even choice[a][] or choice[a][b][c]...
l_decoded_name := url_encoder.decoded_string (a_name)
p := l_decoded_name.index_of ({CHARACTER_32}'[', 1)
if p > 0 then
q := l_decoded_name.index_of ({CHARACTER_32}']', p + 1)
if q > p then
n := l_decoded_name.substring (1, p - 1)
r := l_decoded_name.substring (q + 1, l_decoded_name.count)
r.left_adjust; r.right_adjust
create tb.make (n)
if a_table.has_key (tb.name) and then attached {WSF_TABLE} a_table.found_item as l_existing_table then
tb := l_existing_table
end
k := l_decoded_name.substring (p + 1, q - 1)
k.left_adjust; k.right_adjust
if k.is_empty then
k.append_integer (tb.count + 1)
end
v := tb
n.append_character ({CHARACTER_32}'[')
n.append (k)
n.append_character ({CHARACTER_32}']')
from
until
r.is_empty
loop
ptb := tb
p := r.index_of ({CHARACTER_32} '[', 1)
if p > 0 then
q := r.index_of ({CHARACTER_32} ']', p + 1)
if q > p then
if attached {WSF_TABLE} ptb.value (k) as l_tb_value then
tb := l_tb_value
else
create tb.make (n)
ptb.add_value (tb, k)
end
k := r.substring (p + 1, q - 1)
r := r.substring (q + 1, r.count)
r.left_adjust; r.right_adjust
if k.is_empty then
k.append_integer (tb.count + 1)
end
n.append_character ('[')
n.append (k)
n.append_character (']')
end
else
r.wipe_out
--| Ignore bad value
end
end
a_value.change_name (n)
tb.add_value (a_value, k)
else
--| Missing end bracket
end
end
if v = Void then
a_value.change_name (a_name)
v := a_value
end
if a_table.has_key (v.name) and then attached a_table.found_item as l_existing_value then
if tb /= Void then
--| Already done in previous part
elseif attached {WSF_MULTIPLE_STRING} l_existing_value as l_multi then
l_multi.add_value (v)
else
a_table.force (create {WSF_MULTIPLE_STRING}.make_with_array (<<l_existing_value, v>>), v.name)
check replaced: a_table.found and then a_table.found_item ~ l_existing_value end
end
else
a_table.force (v, v.name)
end
end
new_string_value (a_name: READABLE_STRING_8; a_value: READABLE_STRING_8): WSF_STRING
do
create Result.make (a_name, a_value)
end
feature {NONE} -- Implementation
url_encoder: URL_ENCODER
once
create {UTF8_URL_ENCODER} Result
end
note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -0,0 +1,163 @@
note
description: "[
Utility routines to handle parameters and convert them as string, list or table values.
]"
date: "$Date$"
revision: "$Revision$"
class
WSF_VALUE_UTILITIES
inherit
ANY
SHARED_WSF_PERCENT_ENCODER
rename
percent_encoder as url_encoder
export
{NONE} all
end
feature -- Smart parameter identification
add_utf_8_string_value_to_table (a_utf_8_name: READABLE_STRING_8; a_utf_8_value: READABLE_STRING_8; a_table: HASH_TABLE [WSF_VALUE, READABLE_STRING_GENERAL])
-- Add a utf-8 string value `a_utf_8_value' associated with name `a_utf_8_name' to `a_table'.
local
utf: UTF_CONVERTER
n,v: READABLE_STRING_32
do
n := utf.utf_8_string_8_to_string_32 (a_utf_8_name)
v := utf.utf_8_string_8_to_string_32 (a_utf_8_value)
add_value_to_table (n, new_string_value (n, v), a_table)
end
add_percent_encoded_string_value_to_table (a_encoded_name: READABLE_STRING_8; a_encoded_value: READABLE_STRING_8; a_table: HASH_TABLE [WSF_VALUE, READABLE_STRING_GENERAL])
-- Add a percent-encoded string value `a_encoded_value' associated with name `a_encoded_name' to `a_table'.
local
v: WSF_STRING
do
v := new_string_value_with_percent_encoded_values (a_encoded_name, a_encoded_value)
add_value_to_table (v.name, v, a_table)
end
add_value_to_table (a_name: READABLE_STRING_GENERAL; a_value: WSF_VALUE; a_table: HASH_TABLE [WSF_VALUE, READABLE_STRING_GENERAL])
-- Add value `a_value' associated with unicode name `a_name' to `a_table'.
local
l_decoded_name: STRING_32
l_encoded_name: READABLE_STRING_8
v: detachable WSF_VALUE
n,k,r,vn: STRING_32
p,q: INTEGER
tb,ptb: detachable WSF_TABLE
do
--| Check if this is a list format such as choice[] or choice[a] or even choice[a][] or choice[a][b][c]...
l_decoded_name := a_name.as_string_32
l_encoded_name := a_value.url_encoded_name
p := l_decoded_name.index_of ({CHARACTER_32}'[', 1)
n := l_decoded_name
if p > 0 then
q := l_decoded_name.index_of ({CHARACTER_32}']', p + 1)
if q > p then
n := l_decoded_name.substring (1, p - 1)
r := l_decoded_name.substring (q + 1, l_decoded_name.count)
r.left_adjust; r.right_adjust
create tb.make (n)
if attached {WSF_TABLE} a_table.item (tb.name) as l_existing_table then
tb := l_existing_table
end
k := l_decoded_name.substring (p + 1, q - 1)
k.left_adjust; k.right_adjust
if k.is_empty then
k.append_integer (tb.count + 1)
end
v := tb
create vn.make_from_string (n)
vn.append_character ({CHARACTER_32}'[')
vn.append (k)
vn.append_character ({CHARACTER_32}']')
from
until
r.is_empty
loop
ptb := tb
p := r.index_of ({CHARACTER_32} '[', 1)
if p > 0 then
q := r.index_of ({CHARACTER_32} ']', p + 1)
if q > p then
if attached {WSF_TABLE} ptb.value (k) as l_tb_value then
tb := l_tb_value
else
create tb.make (n)
ptb.add_value (tb, k)
end
k := r.substring (p + 1, q - 1)
r := r.substring (q + 1, r.count)
r.left_adjust; r.right_adjust
if k.is_empty then
k.append_integer (tb.count + 1)
end
vn.append_character ('[')
vn.append (k)
vn.append_character (']')
end
else
r.wipe_out
--| Ignore bad value
end
end
a_value.change_name (vn)
tb.add_value (a_value, k)
else
--| Missing end bracket
end
end
if v = Void then
a_value.change_name (l_decoded_name)
v := a_value
end
if attached a_table.item (n) as l_existing_value then
if tb /= Void then
--| Already done in previous part
elseif attached {WSF_MULTIPLE_STRING} l_existing_value as l_multi then
l_multi.add_value (v)
elseif attached {WSF_TABLE} l_existing_value as l_table then
-- Keep previous values (most likely we have table[1]=foo, table[2]=bar ..and table=/foo/bar
-- Anyway for this case, we keep the previous version, and ignore this "conflict"
else
a_table.force (create {WSF_MULTIPLE_STRING}.make_with_array (<<l_existing_value, v>>), v.name)
check replaced: a_table.found and then a_table.found_item ~ l_existing_value end
end
else
a_table.force (v, n)
end
end
feature -- Factory
new_string_value (a_name: READABLE_STRING_GENERAL; a_value: READABLE_STRING_GENERAL): WSF_STRING
-- New WSF_STRING value built from unicode `a_name' and `a_value'.
do
create Result.make (a_name, a_value)
end
new_string_value_with_percent_encoded_values (a_encoded_name: READABLE_STRING_8; a_encoded_value: READABLE_STRING_8): WSF_STRING
-- New WSF_STRING value built from utf8+percent encoded `a_encoded_name' and `a_encoded_value'.
do
create Result.make_with_percent_encoded_values (a_encoded_name, a_encoded_value)
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -20,8 +20,8 @@ note
About https support: `is_https' indicates if the request is made through an https connection or not.
]"
date: "$Date: 2014-05-14 16:52:42 +0200 (mer., 14 mai 2014) $"
revision: "$Revision: 95057 $"
date: "$Date$"
revision: "$Revision$"
class
WSF_REQUEST
@@ -41,6 +41,11 @@ inherit
{NONE} all
end
WSF_VALUE_UTILITIES
export
{NONE} all
end
create {WSF_EXECUTION, WGI_EXPORTER}
make_from_wgi
@@ -846,7 +851,7 @@ feature -- Access: CGI meta parameters - 1.1
local
l_result: like internal_percent_encoded_path_info
r: READABLE_STRING_8
i: INTEGER
i,m,n,spos: INTEGER
do
l_result := internal_percent_encoded_path_info
if l_result = Void then
@@ -860,6 +865,28 @@ feature -- Access: CGI meta parameters - 1.1
if attached script_name as s then
if l_result.starts_with (s) then
l_result := l_result.substring (s.count + 1, l_result.count)
else
--| Handle Rewrite url engine, to have clean path
from
i := 1
m := l_result.count
n := s.count
until
i > m or i > n or l_result[i] /= s[i]
loop
if l_result[i] = '/' then
spos := i
end
i := i + 1
end
if i > 1 then
if l_result[i-1] = '/' then
i := i -1
elseif spos > 0 then
i := spos
end
l_result := l_result.substring (i, m)
end
end
end
internal_percent_encoded_path_info := l_result
@@ -1399,7 +1426,7 @@ feature {NONE} -- Cookies
k.left_adjust
k.right_adjust
if not k.is_empty then
add_value_to_table (k, v, l_cookies)
add_percent_encoded_string_value_to_table (k, v, l_cookies)
end
end
end
@@ -1463,7 +1490,7 @@ feature {WSF_REQUEST_PATH_PARAMETERS_SOURCE} -- Path parameters: Element change
across
lst as c
loop
add_value_to_table (c.key, c.item, l_table)
add_percent_encoded_string_value_to_table (c.key, c.item, l_table)
end
src.update_path_parameters (l_table)
end
@@ -1494,7 +1521,7 @@ feature {NONE} -- Query parameters: implementation
vars: like internal_query_parameters_table
p,e: INTEGER
rq_uri: like request_uri
s: detachable STRING
s: detachable READABLE_STRING_8
do
vars := internal_query_parameters_table
if vars = Void then
@@ -1556,7 +1583,7 @@ feature {NONE} -- Query parameters: implementation
l_name := s
l_value := empty_string_8
end
add_value_to_table (l_name, l_value, Result)
add_percent_encoded_string_value_to_table (l_name, l_value, Result)
end
end
end
@@ -1715,100 +1742,6 @@ feature {NONE} -- Form fields and related
Result := vars
end
feature {NONE} -- Implementation: smart parameter identification
add_value_to_table (a_name: READABLE_STRING_8; a_value: READABLE_STRING_8; a_table: STRING_TABLE [WSF_VALUE])
-- Add urlencoded parameter `a_name'=`a_value' to `a_table'
-- following smart computation such as handling the "..[..]" as table
local
v: detachable WSF_VALUE
n, k, r: STRING_8
k32: STRING_32
p, q: INTEGER
tb, ptb: detachable WSF_TABLE
do
--| Check if this is a list format such as choice[] or choice[a] or even choice[a][] or choice[a][b][c]...
p := a_name.index_of ('[', 1)
if p > 0 then
q := a_name.index_of (']', p + 1)
if q > p then
n := a_name.substring (1, p - 1)
r := a_name.substring (q + 1, a_name.count)
r.left_adjust; r.right_adjust
create tb.make (n)
if a_table.has_key (tb.name) and then attached {WSF_TABLE} a_table.found_item as l_existing_table then
tb := l_existing_table
end
k := a_name.substring (p + 1, q - 1)
k.left_adjust; k.right_adjust
if k.is_empty then
k.append_integer (tb.count + 1)
end
v := tb
create n.make_from_string (n)
n.append_character ('[')
n.append (k)
n.append_character (']')
from
until
r.is_empty
loop
ptb := tb
p := r.index_of ({CHARACTER_8} '[', 1)
if p > 0 then
q := r.index_of ({CHARACTER_8} ']', p + 1)
if q > p then
k32 := url_decoded_string (k)
if attached {WSF_TABLE} ptb.value (k32) as l_tb_value then
tb := l_tb_value
else
create tb.make (n)
ptb.add_value (tb, k32)
end
k := r.substring (p + 1, q - 1)
r := r.substring (q + 1, r.count)
r.left_adjust; r.right_adjust
if k.is_empty then
k.append_integer (tb.count + 1)
end
n.append_character ('[')
n.append (k)
n.append_character (']')
end
else
r.wipe_out
--| Ignore bad value
end
end
tb.add_value (new_string_value (n, a_value), k)
else
--| Missing end bracket
end
end
if v = Void then
v := new_string_value (a_name, a_value)
end
if a_table.has_key (v.name) and then attached a_table.found_item as l_existing_value then
if tb /= Void then
--| Already done in previous part
elseif attached {WSF_MULTIPLE_STRING} l_existing_value as l_multi then
l_multi.add_value (v)
elseif attached {WSF_TABLE} l_existing_value as l_table then
-- Keep previous values (most likely we have table[1]=foo, table[2]=bar ..and table=/foo/bar
-- Anyway for this case, we keep the previous version, and ignore this "conflict"
else
a_table.force (create {WSF_MULTIPLE_STRING}.make_with_array (<<l_existing_value, v>>), v.name)
check replaced: a_table.found and then a_table.found_item ~ l_existing_value end
end
else
a_table.force (v, v.name)
end
end
feature -- Uploaded File Handling
is_uploaded_file (a_filename: READABLE_STRING_GENERAL): BOOLEAN
@@ -2133,11 +2066,6 @@ feature {NONE} -- Implementation: utilities
one_starting_slash: Result[1] = '/' and (Result.count = 1 or else Result[2] /= '/')
end
new_string_value (a_name: READABLE_STRING_8; a_value: READABLE_STRING_8): WSF_STRING
do
create Result.make (a_name, a_value)
end
empty_string_32: IMMUTABLE_STRING_32
-- Reusable empty string
once

View File

@@ -564,7 +564,7 @@ feature {NONE} -- Implemenation
loop
j := j + 1
end
if j > n then
if j <= n then
Result := a_cookie_name.same_characters (a_cookie_line, j, i, 1)
end
end

View File

@@ -70,6 +70,7 @@ feature -- Conversion
a_html.append ("<td")
append_css_class_to (a_html, Void)
append_css_style_to (a_html)
append_html_attributes_to (a_html)
a_html.append_character ('>')
content.append_to_html (a_theme, a_html)
a_html.append ("</td>")

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="feed" uuid="71364A69-1549-472E-AF78-FDA4FDA016EB" library_target="feed">
<target name="feed">
<root all_classes="true"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="base_extension" location="$ISE_LIBRARY\library\base_extension\base_extension-safe.ecf"/>
<library name="encoders" location="..\..\encoder\encoder-safe.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
<library name="uuid" location="$ISE_LIBRARY\library\uuid\uuid-safe.ecf"/>
<library name="xml_parser" location="$ISE_LIBRARY\library\text\parser\xml\parser\xml_parser-safe.ecf"/>
<library name="xml_tree" location="$ISE_LIBRARY\library\text\parser\xml\tree\xml_tree-safe.ecf"/>
<cluster name="src" location="src\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="feed" uuid="71364A69-1549-472E-AF78-FDA4FDA016EB" library_target="feed">
<target name="feed">
<root all_classes="true"/>
<option void_safety="none">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="base_extension" location="$ISE_LIBRARY\library\base_extension\base_extension.ecf"/>
<library name="encoders" location="..\..\encoder\encoder.ecf"/>
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http.ecf"/>
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
<library name="uuid" location="$ISE_LIBRARY\library\uuid\uuid.ecf"/>
<library name="xml_parser" location="$ISE_LIBRARY\library\text\parser\xml\parser\xml_parser.ecf"/>
<library name="xml_tree" location="$ISE_LIBRARY\library\text\parser\xml\tree\xml_tree.ecf"/>
<cluster name="src" location="src\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,186 @@
note
description: "Convert a FEED into an ATOM content."
date: "$Date$"
revision: "$Revision$"
class
ATOM_FEED_GENERATOR
inherit
FEED_VISITOR
FEED_GENERATOR
rename
process_feed as visit_feed
end
create
make
feature -- Visitor
visit_feed (a_feed: FEED)
do
buffer.append ("[
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
]")
buffer.append_character ('%N')
indent
append_content_tag_to ("title", Void, a_feed.title, buffer)
append_content_tag_to ("subtitle", Void, a_feed.description, buffer)
if attached a_feed.id as l_id then
append_content_tag_to ("id", Void, l_id, buffer)
else
append_content_tag_to ("id", Void, "urn:uuid:" + new_uuid, buffer)
end
across
a_feed.links as tb
loop
tb.item.accept (Current)
end
if attached a_feed.date as dt then
append_content_tag_to ("updated", Void, date_to_string (dt), buffer)
end
across
a_feed.items as ic
loop
ic.item.accept (Current)
end
exdent
buffer.append ("</feed>")
end
visit_item (a_entry: FEED_ITEM)
do
buffer.append (indentation)
buffer.append ("<entry>%N")
indent
append_content_tag_to ("title", Void, a_entry.title, buffer)
across
a_entry.links as tb
loop
tb.item.accept (Current)
end
if attached a_entry.id as l_id then
append_content_tag_to ("id", Void, l_id, buffer)
else
append_content_tag_to ("id", Void, "urn:uuid:" + new_uuid, buffer)
end
if attached a_entry.date as dt then
append_content_tag_to ("updated", Void, date_to_string (dt), buffer)
end
append_content_tag_to ("summary", Void, a_entry.description, buffer)
if attached a_entry.content as l_content then
if attached a_entry.content_type_or_default ("xhtml").is_case_insensitive_equal_general ("xhtml") then
-- if l_content.has_substring ("<div xmlns=%"http://www.w3.org/1999/xhtml%">") then
append_content_tag_to ("content", <<["type", "xhtml"]>>, l_content, buffer)
-- else
-- append_content_tag_to ("content", <<["type", "xhtml"]>>, {STRING_32} "<div xmlns=%"http://www.w3.org/1999/xhtml%">" + l_content + {STRING_32} "</div>", buffer)
-- end
else
append_content_tag_to ("content", <<["type", a_entry.content_type]>>, a_entry.content, buffer)
end
end
if attached a_entry.author as u then
u.accept (Current)
end
exdent
buffer.append (indentation)
buffer.append ("</entry>%N")
end
visit_link (a_link: FEED_LINK)
local
attr: detachable ARRAYED_LIST [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_32]]
tu: TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_32]
do
create attr.make (2)
if attached a_link.relation as rel and then not rel.is_whitespace then
tu := ["rel", rel]
attr.force (tu)
end
if attached a_link.type as t and then not t.is_whitespace then
tu := ["type", t.as_string_32]
attr.force (tu)
end
tu := ["href", a_link.href.as_string_32]
attr.force (tu)
if attr.is_empty then
attr := Void
end
append_content_tag_to ("link", attr, Void, buffer)
end
visit_author (a_author: FEED_AUTHOR)
do
buffer.append (indentation)
buffer.append ("<author>%N")
indent
append_content_tag_to ("name", Void, a_author.name, buffer)
append_content_tag_to ("email", Void, a_author.email, buffer)
exdent
buffer.append (indentation)
buffer.append ("</author>%N")
end
feature {NONE} -- Helpers
new_uuid: STRING
local
gen: UUID_GENERATOR
do
create gen
Result := gen.generate_uuid.out.as_lower
end
date_to_string (dt: DATE_TIME): STRING
do
Result := date_to_rfc3339_string (dt)
end
date_to_rfc3339_string (d: DATE_TIME): STRING
-- 2003-12-13T18:30:02Z
local
i: INTEGER
do
create Result.make_empty
Result.append_integer (d.year)
Result.append_character ('-')
i := d.month
if i < 10 then
Result.append_integer (0)
end
Result.append_integer (i)
Result.append_character ('-')
i := d.day
if i < 10 then
Result.append_integer (0)
end
Result.append_integer (i)
Result.append_character ('T')
i := d.hour
if i < 10 then
Result.append_integer (0)
end
Result.append_integer (i)
Result.append_character (':')
i := d.minute
if i < 10 then
Result.append_integer (0)
end
Result.append_integer (i)
Result.append_character (':')
i := d.second
if i < 10 then
Result.append_integer (0)
end
Result.append_integer (i)
Result.append_character ('Z')
end
end

View File

@@ -0,0 +1,104 @@
note
description: "[
ATOM Parser.
Warning: the implementation may not support the full ATOM specification.
]"
date: "$Date$"
revision: "$Revision$"
EIS: "name=ATOM at wikipedia", "protocol=URI", "src=https://en.wikipedia.org/wiki/Atom_(standard)"
EIS: "name=RSS at wikipedia", "protocol=URI", "src=https://en.wikipedia.org/wiki/RSS"
EIS: "name=ATOM 1.0 RFC4287", "protocol=URI", "src=https://tools.ietf.org/html/rfc4287"
class
ATOM_FEED_PARSER
inherit
FEED_PARSER
feature -- Access
name: STRING = "atom1"
-- Associated name.
is_detected (xdoc: XML_DOCUMENT): BOOLEAN
-- Is `xdoc' an ATOM feed representation?
do
Result := attached {XML_ELEMENT} xdoc.element_by_name ("feed") as x_feed and then
(
not attached xml_attribute_text (x_feed, "xmlns") as l_xmlns
or else l_xmlns.same_string ("http://www.w3.org/2005/Atom")
)
end
feed (xdoc: XML_DOCUMENT): detachable FEED
-- Feed from `xdoc' XML document.
local
l_title: READABLE_STRING_32
x_entry, x_link: detachable XML_ELEMENT
e: FEED_ITEM
l_author: FEED_AUTHOR
lnk: FEED_LINK
s: STRING_32
do
if
attached xdoc.element_by_name ("feed") as x_feed and then
-- (not attached xml_attribute_text (x_feed, "xmlns") as l_xmlns or else l_xmlns.same_string ("http://www.w3.org/2005/Atom"))
attached xml_element_text (x_feed, "title") as t
then
l_title := t
create Result.make (l_title)
Result.set_description (xml_element_text (x_feed, "subtitle"), "plain")
Result.set_id (xml_element_text (x_feed, "id"))
Result.set_updated_date_with_text (xml_element_text (x_feed, "updated"))
if attached links_from_xml (x_feed, "link") as l_links then
across
l_links as link_ic
loop
lnk := link_ic.item
Result.links.force (lnk, lnk.relation)
end
end
if attached x_feed.elements_by_name ("entry") as x_entries then
across
x_entries as ic
loop
x_entry := ic.item
if attached xml_element_text (x_entry, "title") as e_title then
create e.make (e_title)
e.set_description (xml_element_text (x_entry, "summary"))
e.set_id (xml_element_text (x_entry, "id"))
e.set_updated_date_with_text (xml_element_text (x_entry, "updated"))
if attached links_from_xml (x_entry, "link") as l_links then
across
l_links as link_ic
loop
lnk := link_ic.item
e.links.force (lnk, lnk.relation)
end
end
if attached x_entry.element_by_name ("content") as x_content then
e.set_content (xml_element_code (x_content), xml_attribute_text (x_content, "type"))
end
if attached x_entry.element_by_name ("author") as x_author then
if attached x_author.element_by_name ("name") as x_name and then
attached x_name.text as l_author_name
then
create l_author.make (l_author_name)
if attached x_author.element_by_name ("email") as x_email then
l_author.set_email (x_email.text)
end
e.set_author (l_author)
end
end
Result.extend (e)
end
end
end
end
end
end

View File

@@ -0,0 +1,69 @@
note
description: "[
Collection of default feed parsers provided by the current library.
A new parser can be easily added via `parsers.extend (...)'.
]"
date: "$Date$"
revision: "$Revision$"
class
FEED_DEFAULT_PARSERS
inherit
ANY
redefine
default_create
end
create
default_create
feature {NONE} -- Initialization
default_create
do
Precursor
create {ARRAYED_LIST [FEED_PARSER]} parsers.make (2)
parsers.force (create {RSS_2_FEED_PARSER})
parsers.force (create {ATOM_FEED_PARSER})
end
feature -- Access
parsers: LIST [FEED_PARSER]
-- Available Feed parsers.
feature -- Access
feed_from_string (a_content: READABLE_STRING_8): detachable FEED
-- Feed object from `a_content' string, if a parser is able to parse.it.
local
p: XML_STANDARD_PARSER
cb_tree: XML_CALLBACKS_FILTER_DOCUMENT
xdoc: XML_DOCUMENT
do
create p.make
create cb_tree.make_null
p.set_callbacks (cb_tree)
p.parse_from_string_8 (a_content)
if p.is_correct then
xdoc := cb_tree.document
Result := feed (xdoc)
end
end
feed (xdoc: XML_DOCUMENT): like feed_from_string
-- Feed from `xdoc' XML document.
do
across
parsers as ic
until
Result /= Void
loop
if ic.item.is_detected (xdoc) then
Result := ic.item.feed (xdoc)
end
end
end
end

View File

@@ -0,0 +1,62 @@
note
description: "[
Interface common to any FEED parser.
Usage:
create parser
if attached parser.feed_from_string (l_feed_content) as l_feed then
...
]"
date: "$Date$"
revision: "$Revision$"
deferred class
FEED_PARSER
inherit
FEED_PARSER_UTILITIES
feature -- Access
name: STRING
-- Associated name.
deferred
ensure
not_blanc: not Result.is_whitespace
end
is_detected (xdoc: XML_DOCUMENT): BOOLEAN
-- Is `xdoc' an feed representation or Current supported format?
deferred
end
feed (xdoc: XML_DOCUMENT): detachable FEED
-- Feed from `xdoc' XML document.
require
is_detected: is_detected (xdoc)
deferred
end
feature -- Basic operations
feed_from_string (a_content: READABLE_STRING_8): like feed
-- Feed from `a_content' document.
local
p: XML_STANDARD_PARSER
cb_tree: XML_CALLBACKS_FILTER_DOCUMENT
xdoc: XML_DOCUMENT
do
create p.make
create cb_tree.make_null
p.set_callbacks (cb_tree)
p.parse_from_string_8 (a_content)
if p.is_correct then
xdoc := cb_tree.document
if is_detected (xdoc) then
Result := feed (xdoc)
end
end
end
end

View File

@@ -0,0 +1,159 @@
note
description: "FEED interface, could be RSS, ATOM, ..."
date: "$Date$"
revision: "$Revision$"
class
FEED
inherit
FEED_HELPERS
ITERABLE [FEED_ITEM]
create
make
feature {NONE} -- Initialization
make (a_title: READABLE_STRING_GENERAL)
do
create title.make_from_string_general (a_title)
create items.make (1)
create links.make (1)
end
feature -- Access
title: IMMUTABLE_STRING_32
-- Title of the feed/channel.
description: detachable IMMUTABLE_STRING_32
-- Associated description/subtitle.
description_content_type: detachable READABLE_STRING_8
-- Optional content type for `description'.
-- By default, this should be text/plain.
id: detachable IMMUTABLE_STRING_32
-- Id associated with Current feed if any.
date: detachable DATE_TIME
-- Build date.
links: STRING_TABLE [FEED_LINK]
-- Url indexed by relation
items: ARRAYED_LIST [FEED_ITEM]
-- List of feed items.
feature -- Access
new_cursor: ITERATION_CURSOR [FEED_ITEM]
-- <Precursor>
do
Result := items.new_cursor
end
feature -- Element change
set_description (a_description: detachable READABLE_STRING_GENERAL; a_description_content_type: like description_content_type)
-- Set `description' with `a_description' and optional content type `text:$a_description_content_type'.
do
if a_description = Void then
description := Void
description_content_type := Void
else
create description.make_from_string_general (a_description)
description_content_type := a_description_content_type
end
end
set_id (a_id: detachable READABLE_STRING_GENERAL)
do
if a_id = Void then
id := Void
else
create id.make_from_string_general (a_id)
end
end
set_updated_date_with_text (a_date_text: detachable READABLE_STRING_32)
-- Set `date' from date string representation `a_date_text'.
obsolete
"Use set_date_with_text [oct/2015]"
do
set_date_with_text (a_date_text)
end
set_date_with_text (a_date_text: detachable READABLE_STRING_32)
-- Set `date' from date string representation `a_date_text'.
do
if a_date_text = Void then
set_date (Void)
else
set_date (date_time (a_date_text))
end
end
set_date (a_date: detachable DATE_TIME)
-- Set `date' from `a_date'.
do
date := a_date
end
extend (a_item: FEED_ITEM)
-- Add item `a_item' to feed `items'.
do
items.force (a_item)
end
prune (a_item: FEED_ITEM)
-- Remove feed item `a_item' from Current list of feed items.
do
items.prune (a_item)
end
extended alias "+" (a_feed: FEED): FEED
-- New feed object made from Current merged with a_feed.
local
l_title: STRING_32
do
create l_title.make (title.count + a_feed.title.count)
l_title.append_character ('(')
l_title.append (title)
l_title.append_character (')')
l_title.append_character ('+')
l_title.append_character ('(')
l_title.append (a_feed.title)
l_title.append_character (')')
create Result.make (l_title)
Result.items.append (items)
across
a_feed.items as ic
loop
-- FIXME jfiat [2015/10/07] : check there is no duplication! (same id, or link, ...)
Result.extend (ic.item)
end
Result.sort
end
sort
-- Sort `items', (recent first).
local
s: QUICK_SORTER [FEED_ITEM]
comp: COMPARABLE_COMPARATOR [FEED_ITEM]
do
create comp
create s.make (comp)
s.reverse_sort (items)
end
feature -- Visitor
accept (vis: FEED_VISITOR)
do
vis.visit_feed (Current)
end
end

View File

@@ -0,0 +1,48 @@
note
description: "[
Author of feed or feed entry.
- name and email information.
]"
date: "$Date$"
revision: "$Revision$"
class
FEED_AUTHOR
create
make
feature {NONE} -- Initialization
make (a_name: READABLE_STRING_GENERAL)
do
create name.make_from_string_general (a_name)
end
feature -- Access
name: IMMUTABLE_STRING_32
email: detachable READABLE_STRING_8
feature -- Element change
set_email (a_email: detachable READABLE_STRING_GENERAL)
do
if a_email = Void then
email := Void
elseif a_email.is_valid_as_string_8 then
email := a_email.as_string_8
else
email := Void
end
end
feature -- Visitor
accept (vis: FEED_VISITOR)
do
vis.visit_author (Current)
end
end

View File

@@ -0,0 +1,208 @@
note
description: "[
A feed contains a list of items.
This FEED_ITEM interface provides
- title, description, content, id, date, ...
- could be compared with other item to sort by date+title.
]"
date: "$Date$"
revision: "$Revision$"
class
FEED_ITEM
inherit
FEED_HELPERS
undefine
is_equal
end
COMPARABLE
create
make
feature {NONE} -- Initialization
make (a_title: READABLE_STRING_GENERAL)
do
create title.make_from_string_general (a_title)
create links.make (1)
end
feature -- Access
title: IMMUTABLE_STRING_32
-- Title of associated feed item.
description: detachable IMMUTABLE_STRING_32
-- Optional description (or summary).
content: detachable IMMUTABLE_STRING_32
-- Content of Current feed item.
content_type: detachable READABLE_STRING_8
-- Optional content type for `content'.
-- By default, this should be text/html.
content_type_or_default (dft: READABLE_STRING_8): READABLE_STRING_8
-- Associated content type, and if none, return given value `dft'.
do
if attached content_type as l_type then
Result := l_type
else
Result := dft
end
end
id: detachable IMMUTABLE_STRING_32
-- Identifier of current feed item, if any/
date: detachable DATE_TIME
-- Publishing date.
link: detachable FEED_LINK
-- Main link for the entry, if any.
do
if attached links as l_links then
Result := l_links.item ("")
across
l_links as ic
until
Result /= Void
loop
Result := ic.item
end
end
end
links: STRING_TABLE [FEED_LINK]
-- Url indexed by relation
categories: detachable LIST [READABLE_STRING_32]
-- Categories
author: detachable FEED_AUTHOR
-- Author information.
feature -- Status report
has_category (cat: READABLE_STRING_GENERAL): BOOLEAN
-- Has category `cat'?
--| note: case insensitive.
do
if attached categories as cats then
Result := across cats as ic some cat.is_case_insensitive_equal (ic.item) end
end
end
feature -- Comparison
is_less alias "<" (other: like Current): BOOLEAN
-- Is current object less than `other'?
local
d1,d2: like date
do
d1 := date
d2 := other.date
if d1 = Void and d2 = Void then
Result := title < other.title
elseif d1 = Void then
Result := True
elseif d2 = Void then
Result := False
else
if d1 ~ d2 then
Result := title < other.title
else
Result := d1 < d2
end
end
end
feature -- Element change
set_id (a_id: detachable READABLE_STRING_GENERAL)
do
if a_id = Void then
id := Void
else
create id.make_from_string_general (a_id)
end
end
set_description (a_description: detachable READABLE_STRING_GENERAL)
do
if a_description = Void then
description := Void
else
create description.make_from_string_general (a_description)
end
end
set_content (a_content: detachable READABLE_STRING_GENERAL; a_type: detachable READABLE_STRING_GENERAL)
do
if a_content = Void then
content := Void
content_type := Void
else
create content.make_from_string_general (a_content)
if a_type = Void then
content_type := Void
else
content_type := a_type.as_string_8
end
end
end
set_updated_date_with_text (a_date_text: detachable READABLE_STRING_32)
-- Set `date' from date string representation `a_date_text'.
obsolete
"Use set_date_with_text [oct/2015]"
do
set_date_with_text (a_date_text)
end
set_date_with_text (a_date_text: detachable READABLE_STRING_32)
-- Set `date' from date string representation `a_date_text'.
do
if a_date_text = Void then
set_date (Void)
else
set_date (date_time (a_date_text))
end
end
set_date (a_date: detachable DATE_TIME)
-- Set `date' from `a_date'.
do
date := a_date
end
set_author (a_author: detachable FEED_AUTHOR)
do
author := a_author
end
set_category (cat: READABLE_STRING_GENERAL)
local
cats: like categories
do
cats := categories
if cats = Void then
create {ARRAYED_LIST [READABLE_STRING_32]} cats.make (1)
categories := cats
end
cats.force (cat.as_string_32)
ensure
cat_set: has_category (cat)
end
feature -- Visitor
accept (vis: FEED_VISITOR)
do
vis.visit_item (Current)
end
end

View File

@@ -0,0 +1,58 @@
note
description: "Link mentioned in feed and feed entry."
date: "$Date$"
revision: "$Revision$"
class
FEED_LINK
create
make
feature {NONE} -- Initialization
make (a_href: READABLE_STRING_8)
do
href := a_href
set_relation (Void)
end
feature -- Access
href: READABLE_STRING_8
-- Location of Current link.
relation: READABLE_STRING_32
-- Relation associated with Current link.
type: detachable READABLE_STRING_8
-- Optional type of link.
feature -- Element change
set_relation (rel: detachable READABLE_STRING_GENERAL)
do
if rel = Void then
relation := ""
else
relation := rel.as_string_8
end
end
set_type (a_type: detachable READABLE_STRING_GENERAL)
do
if a_type = Void then
type := Void
else
type := a_type.as_string_8
end
end
feature -- Visitor
accept (vis: FEED_VISITOR)
do
vis.visit_link (Current)
end
end

View File

@@ -0,0 +1,126 @@
note
description: "Convert a FEED into an RSS 2.0 content."
date: "$Date$"
revision: "$Revision$"
class
RSS_2_FEED_GENERATOR
inherit
FEED_VISITOR
FEED_GENERATOR
rename
process_feed as visit_feed
end
create
make
feature -- Visitor
visit_feed (a_feed: FEED)
do
buffer.append ("[
<?xml version="1.0" encoding="UTF-8"?>
<rss
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
version="2.0">
<channel>
]")
buffer.append_character ('%N')
indent
indent
append_content_tag_to ("title", Void, a_feed.title, buffer)
append_content_tag_to ("description", Void, a_feed.description, buffer)
across
a_feed.links as tb
loop
tb.item.accept (Current)
end
if attached a_feed.date as dt then
append_content_tag_to ("lastBuildDate", Void, date_to_string (dt), buffer)
end
across
a_feed.items as ic
loop
ic.item.accept (Current)
end
exdent
exdent
buffer.append ("[
</channel>
</rss>
]")
end
visit_item (a_item: FEED_ITEM)
do
buffer.append (indentation)
buffer.append ("<item>%N")
indent
append_content_tag_to ("title", Void, a_item.title, buffer)
if attached a_item.date as dt then
append_content_tag_to ("pubDate", Void, date_to_string (dt), buffer)
end
across
a_item.links as tb
loop
tb.item.accept (Current)
end
if attached a_item.author as u then
u.accept (Current)
end
if attached a_item.categories as cats then
across
cats as ic
loop
append_content_tag_to ("category", Void, ic.item, buffer)
end
end
append_content_tag_to ("guid", Void, a_item.id, buffer)
append_content_tag_to ("description", Void, a_item.description, buffer)
append_cdata_content_tag_to ("content:encoded", Void, a_item.content, buffer)
exdent
buffer.append (indentation)
buffer.append ("</item>%N")
end
visit_link (a_link: FEED_LINK)
local
attr: detachable ARRAYED_LIST [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_32]]
tu: TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_32]
do
create attr.make (2)
if attached a_link.relation as rel and then not rel.is_whitespace then
tu := ["rel", rel]
attr.force (tu)
end
if attached a_link.type as t and then not t.is_whitespace then
tu := ["type", t.as_string_32]
attr.force (tu)
end
if attr.is_empty then
attr := Void
end
append_content_tag_to ("link", attr, a_link.href, buffer)
end
visit_author (a_author: FEED_AUTHOR)
do
append_content_tag_to ("dc:creator", Void, a_author.name, buffer)
end
feature {NONE} -- Helpers
date_to_string (dt: DATE_TIME): STRING
local
htdate: HTTP_DATE
do
create htdate.make_from_date_time (dt)
Result := htdate.rfc850_string
end
end

View File

@@ -0,0 +1,125 @@
note
description: "[
RSS 2.0 Parser.
Warning: the implementation may not support the full RSS 2.0 specification.
]"
date: "$Date$"
revision: "$Revision$"
EIS: "name=RSS at wikipedia", "protocol=URI", "src=https://en.wikipedia.org/wiki/RSS"
EIS: "name=RDF Site Summary (RSS) 1.0", "protocol=URI", "src=http://purl.org/rss/1.0/spec"
class
RSS_2_FEED_PARSER
inherit
FEED_PARSER
feature -- Access
name: STRING = "rss2"
-- Associated name.
is_detected (xdoc: XML_DOCUMENT): BOOLEAN
-- Is `xdoc' an ATOM feed representation?
do
if attached {XML_ELEMENT} xdoc.element_by_name ("rss") as x_rss then
if attached xml_attribute_text (x_rss, "version") as l_version and then
l_version.starts_with ("2.")
then
Result := True
else
-- Let's default to RSS 2.0 for now.
Result := True
end
end
end
feed (xdoc: XML_DOCUMENT): detachable FEED
-- Feed from `xdoc' XML RSS 2.0 document.
local
lnk: FEED_LINK
x_item, x_content, x_author: detachable XML_ELEMENT
e: FEED_ITEM
l_author: FEED_AUTHOR
do
if attached xdoc.element_by_name ("rss") as x_rss then
if
attached xml_attribute_text (x_rss, "version") as l_version and then
l_version.starts_with ("2.")
then
if attached x_rss.element_by_name ("channel") as x_channel then
if attached xml_element_text (x_channel, "title") as x_title then
create Result.make (x_title)
Result.set_description (xml_element_text (x_channel, "description"), "xhtml")
Result.set_updated_date_with_text (xml_element_text (x_channel, "lastBuildDate"))
if attached links_from_xml (x_channel, "link") as l_links then
across
l_links as link_ic
loop
lnk := link_ic.item
Result.links.force (lnk, lnk.relation)
end
end
if attached x_channel.elements_by_name ("item") as x_items then
across
x_items as ic
loop
x_item := ic.item
if attached xml_element_text (x_item, "title") as e_title then
create e.make (e_title)
e.set_description (xml_element_text (x_item, "description"))
e.set_updated_date_with_text (xml_element_text (x_item, "pubDate"))
e.set_id (xml_element_text (x_item, "guid"))
x_author := x_item.element_by_name ("creator")
if x_author = Void then
x_author := element_by_prefixed_name (x_item, "dc" , "creator")
end
if
x_author /= Void and then
attached x_author.text as l_author_name
then
create l_author.make (l_author_name)
e.set_author (l_author)
end
if attached links_from_xml (x_item, "link") as l_links then
across
l_links as link_ic
loop
lnk := link_ic.item
e.links.force (lnk, lnk.relation)
end
end
if attached x_item.elements_by_name ("category") as x_categories then
across
x_categories as cats
loop
if attached cats.item.text as cat then
e.set_category (cat)
end
end
end
x_content := x_item.element_by_name ("content")
if x_content = Void then
x_content := element_by_prefixed_name (x_item, "content" , "encoded")
if x_content /= Void then
e.set_content (x_content.text, Void)
end
else
e.set_content (xml_element_code (x_content), Void)
end
Result.extend (e)
end
end
end
end
end
end
end
end
end

View File

@@ -0,0 +1,119 @@
note
description: "Common ancestor for feed generator."
date: "$Date$"
revision: "$Revision$"
deferred class
FEED_GENERATOR
inherit
XML_UTILITIES
feature {NONE} -- Initialization
make (a_buffer: STRING_8)
do
buffer := a_buffer
create indentation.make_empty
end
feature -- Access
buffer: STRING_8
-- Output of feed conversion.
feature -- Conversion
process_feed (a_feed: FEED)
-- Convert `a_feed' into string representation in `buffer'.
deferred
end
feature {NONE} -- Helpers
indent
do
indentation.append ("%T")
end
exdent
require
has_indentation: indentation.count > 0
do
indentation.remove_tail (1)
end
indentation: STRING
append_content_tag_to (a_tagname: READABLE_STRING_8; a_attr: detachable ITERABLE [TUPLE [name: READABLE_STRING_8; value: detachable READABLE_STRING_GENERAL]]; a_content: detachable READABLE_STRING_GENERAL; a_output: STRING)
do
if a_content /= Void or a_attr /= Void then
a_output.append (indentation)
a_output.append ("<")
a_output.append (a_tagname)
if a_attr /= Void then
across
a_attr as ic
loop
if attached ic.item.value as l_att_value then
a_output.append_character (' ')
a_output.append (ic.item.name)
a_output.append_character ('=')
a_output.append_character ('%"')
a_output.append (escaped_unicode_xml (l_att_value.as_string_32))
a_output.append_character ('%"')
end
end
end
if a_content = Void then
a_output.append ("/>")
else
a_output.append (">")
a_output.append (escaped_unicode_xml (a_content.as_string_32))
a_output.append ("</" + a_tagname + ">%N")
end
end
end
append_cdata_content_tag_to (a_tagname: READABLE_STRING_8; a_attr: detachable ITERABLE [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_32]]; a_content: detachable READABLE_STRING_32; a_output: STRING)
do
if a_content /= Void then
a_output.append (indentation)
a_output.append ("<")
a_output.append (a_tagname)
if a_attr /= Void then
across
a_attr as ic
loop
a_output.append_character (' ')
a_output.append (ic.item.name)
a_output.append_character ('=')
a_output.append_character ('%"')
a_output.append (escaped_unicode_xml (ic.item.value))
a_output.append_character ('%"')
end
end
a_output.append (">")
a_output.append (to_cdata_element (a_content))
a_output.append ("</" + a_tagname + ">%N")
end
end
to_cdata_element (a_value: READABLE_STRING_GENERAL): STRING
local
cdata: XML_CHARACTER_DATA
xdoc: XML_DOCUMENT
pprinter: XML_NODE_PRINTER
l_output: XML_STRING_8_OUTPUT_STREAM
do
create xdoc.make
create cdata.make (xdoc.root_element, a_value.as_string_32)
create pprinter.make
create Result.make (cdata.content_count)
create l_output.make (Result)
pprinter.set_output (l_output)
pprinter.process_character_data (cdata)
end
end

View File

@@ -0,0 +1,86 @@
note
description: "Helpers routine for feed library."
date: "$Date$"
revision: "$Revision$"
class
FEED_HELPERS
feature -- Helpers
date_time (a_date_string: READABLE_STRING_32): DATE_TIME
-- "2015-08-14T10:34:13.493740Z"
-- "Sat, 07 Sep 2002 00:00:01 GMT"
local
i,j: INTEGER
s: READABLE_STRING_GENERAL
y,m,d,h,min: INTEGER
sec: REAL_64
htdate: HTTP_DATE
str: STRING_32
do
if a_date_string.count > 0 and then a_date_string.item (1).is_digit then
i := a_date_string.index_of ('-', 1)
if i > 0 then
s := a_date_string.substring (1, i - 1)
y := s.to_integer_32 -- Year
j := i + 1
i := a_date_string.index_of ('-', j)
if i > 0 then
s := a_date_string.substring (j, i - 1)
m := s.to_integer_32 -- Month
j := i + 1
i := a_date_string.index_of ('T', j)
if i = 0 then
i := a_date_string.index_of (' ', j)
end
if i = 0 then
i := a_date_string.count + 1
end
if i > 0 then
s := a_date_string.substring (j, i - 1)
if s.is_integer then
d := s.to_integer_32 -- Day
j := i + 1
i := a_date_string.index_of (':', j)
if i > 0 then
s := a_date_string.substring (j, i - 1)
h := s.to_integer
j := i + 1
i := a_date_string.index_of (':', j)
if i > 0 then
s := a_date_string.substring (j, i - 1)
min := s.to_integer
j := i + 1
i := a_date_string.index_of ('Z', j)
if i = 0 then
i := a_date_string.count + 1
end
s := a_date_string.substring (j, i - 1)
sec := s.to_double
end
end
end
end
end
end
create Result.make (y,m,d,h,m,0)
Result.fine_second_add (sec)
else
i := a_date_string.index_of ('+', 1)
if i > 0 then
str := a_date_string.substring (1, i - 1)
str.append (" GMT")
create htdate.make_from_string (str)
Result := htdate.date_time
if a_date_string.substring (i + 1, a_date_string.count).is_case_insensitive_equal ("0000") then
end
else
create htdate.make_from_string (a_date_string)
Result := htdate.date_time
end
end
end
end

View File

@@ -0,0 +1,83 @@
note
description: "Helpers routine for feed xml parsers."
date: "$Date$"
revision: "$Revision$"
deferred class
FEED_PARSER_UTILITIES
feature -- Access
xml_element_text (a_parent: XML_ELEMENT; a_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
do
if attached a_parent.element_by_name (a_name) as elt then
if attached elt.text as t then
t.left_adjust
t.right_adjust
Result := t
end
end
end
xml_attribute_text (a_elt: XML_ELEMENT; a_att_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
do
if attached a_elt.attribute_by_name (a_att_name) as att then
Result := att.value
end
end
xml_element_code (elt: XML_ELEMENT): STRING_32
local
xprinter: XML_NODE_PRINTER
do
create xprinter.make
create Result.make_empty
xprinter.set_output (create {XML_STRING_32_OUTPUT_STREAM}.make (Result))
xprinter.process_element (elt)
end
links_from_xml (elt: XML_ELEMENT; a_link_elt_name: READABLE_STRING_GENERAL): detachable ARRAYED_LIST [FEED_LINK]
local
x_link: XML_ELEMENT
lnk: FEED_LINK
do
if attached elt.elements_by_name (a_link_elt_name) as x_links then
create Result.make (0)
across
x_links as ic
loop
x_link := ic.item
if attached xml_attribute_text (x_link, "href") as l_href and then
l_href.is_valid_as_string_8
then
create lnk.make (l_href.as_string_8)
lnk.set_relation (xml_attribute_text (x_link, "rel"))
lnk.set_type (xml_attribute_text (x_link, "type"))
Result.force (lnk)
elseif attached x_link.text as l_url and then not l_url.is_whitespace then
create lnk.make (l_url)
Result.force (lnk)
end
end
end
end
element_by_prefixed_name (elt: XML_ELEMENT; a_ns_prefix: READABLE_STRING_GENERAL; a_name: READABLE_STRING_GENERAL): detachable XML_ELEMENT
do
across
elt as ic
until
Result /= Void
loop
if attached {XML_ELEMENT} ic.item as x_item then
if
attached x_item.ns_prefix as l_ns_prefix and then a_ns_prefix.same_string (l_ns_prefix) and then
a_name.same_string (x_item.name)
then
Result := x_item
end
end
end
end
end

View File

@@ -0,0 +1,200 @@
note
description: "[
Convert a FEED to STRING_32 representation.
Mostly for debug output!
]"
date: "$Date$"
revision: "$Revision$"
class
FEED_TO_STRING_32_DEBUG_VISITOR
inherit
FEED_VISITOR
create
make
feature {NONE} -- Initialization
make (a_buffer: STRING_32)
do
buffer := a_buffer
create indentation.make_empty
end
buffer: STRING_32
feature -- Visitor
visit_feed (a_feed: FEED)
do
if attached a_feed.id as l_id then
append_text ("#")
append (l_id)
append_new_line
end
if attached a_feed.date as dt then
append_text ("date:")
append (dt.out)
append_new_line
end
append_text (a_feed.title)
append_new_line
indent
if attached a_feed.description as l_desc then
append_text (l_desc)
append_new_line
end
across
a_feed.links as ic
loop
ic.item.accept (Current)
append_new_line
end
append_new_line
across
a_feed.items as ic
loop
exdent
append_text (create {STRING_32}.make_filled ('-', 40))
append_new_line
indent
ic.item.accept (Current)
append_new_line
end
end
visit_item (a_entry: FEED_ITEM)
do
if attached a_entry.id as l_id then
append_text ("#")
append (l_id)
append_new_line
end
if attached a_entry.date as dt then
append_text ("date:")
append (dt.out)
append_new_line
end
append_text (a_entry.title)
append_new_line
indent
if attached a_entry.author as l_author then
l_author.accept (Current)
append_new_line
end
if attached a_entry.categories as cats then
append_text ("Categories: ")
from
cats.start
until
cats.after
loop
if not cats.isfirst then
append (", ")
end
append (cats.item)
cats.forth
end
append_new_line
end
if attached a_entry.description as l_summary then
append_text (l_summary)
append_new_line
end
across
a_entry.links as ic
loop
ic.item.accept (Current)
append_new_line
end
if attached a_entry.content as l_content then
append_text (l_content)
append_new_line
end
exdent
end
visit_link (a_link: FEED_LINK)
local
s: STRING_32
do
create s.make_empty
s.append_string_general ("@")
s.append_string (a_link.relation)
s.append_string (" -> ")
s.append_string (a_link.href)
append_text (s)
end
visit_author (a_author: FEED_AUTHOR)
local
s: STRING_32
do
create s.make_empty
s.append_string_general ("by ")
s.append_string (a_author.name)
if attached a_author.email as l_email then
s.append_character (' ')
s.append_character ('(')
s.append_string_general (l_email)
s.append_character (')')
end
append_text (s)
end
feature -- Helper
indentation: STRING_32
indent
do
indentation.append (" ")
end
exdent
do
indentation.remove_tail (2)
end
append_new_line
do
append ("%N")
end
append_text (s: READABLE_STRING_GENERAL)
local
lst: LIST [READABLE_STRING_GENERAL]
do
if indentation.is_empty then
append (s)
else
lst := s.split ('%N')
from
lst.start
until
lst.after
loop
append (indentation)
append (lst.item)
if not lst.islast then
append ("%N")
end
lst.forth
end
end
end
append (s: READABLE_STRING_GENERAL)
do
buffer.append_string_general (s)
end
end

View File

@@ -0,0 +1,229 @@
note
description: "[
Convert a FEED to XHTML representation.
]"
date: "$Date: 2015-10-08 10:45:13 +0200 (jeu., 08 oct. 2015) $"
revision: "$Revision: 97964 $"
class
FEED_TO_XHTML_VISITOR
inherit
FEED_VISITOR
SHARED_HTML_ENCODER
create
make
feature {NONE} -- Initialization
make (a_buffer: STRING_8)
do
buffer := a_buffer
create today.make_now_utc
description_enabled := False
limit := -1
end
buffer: STRING_8
-- Output buffer.
today: DATE_TIME
-- Current date.
feature -- Access
header: detachable READABLE_STRING_8
-- Optional header.
footer: detachable READABLE_STRING_8
-- Optional footer.
feature -- Settings
limit: INTEGER
-- Number of item to include in XHTML generation.
-- Default: -1 => No limit
description_enabled: BOOLEAN
-- Generate description?
-- Default: False
feature -- Element change
set_limit (nb: INTEGER)
-- Set `limit' to `nb'.
do
limit := nb
end
set_description_enabled (b: BOOLEAN)
-- Set `description_enabled' to `b'.
do
description_enabled := b
end
set_header (h: like header)
do
header := h
end
set_footer (f: like footer)
do
footer := f
end
feature -- Visitor
visit_feed (a_feed: FEED)
local
nb: INTEGER
do
append ("<div class=%"feed%">")
if attached header as h then
append (h)
append ("%N")
end
if attached a_feed.date as dt then
append ("<!-- date:")
append (dt.out)
append (" -->%N")
end
append ("<ul>%N")
if
description_enabled and then
attached a_feed.description as l_desc and then
l_desc.is_valid_as_string_8
then
append ("<div class=%"description%">")
append (l_desc.to_string_8)
append ("</div>")
end
nb := limit
across
a_feed as ic
until
nb = 0
loop
ic.item.accept (Current)
nb := nb - 1
end
if attached footer as f then
append (f)
append ("%N")
end
append ("</ul>%N")
end
visit_item (a_entry: FEED_ITEM)
local
lnk: detachable FEED_LINK
do
append ("<li>%N")
lnk := a_entry.link
if attached a_entry.date as dt then
append ("<div class=%"date%">")
append_date_time_to (dt, today.date, buffer)
append ("</div>%N")
end
if lnk /= Void then
append ("<a href=%"" + lnk.href + "%">")
else
check has_link: False end
append ("<a href=%"#%">")
end
append_as_html_encoded (a_entry.title)
append ("</a>%N")
debug
if attached a_entry.categories as l_categories and then not l_categories.is_empty then
append ("<div class=%"category%">")
across
l_categories as cats_ic
loop
append_as_html_encoded (cats_ic.item)
append (" ")
end
append ("</div>%N")
end
end
if
description_enabled and then
attached a_entry.description as l_entry_desc
then
if l_entry_desc.is_valid_as_string_8 then
append ("<div class=%"description%">")
append (l_entry_desc.as_string_8)
append ("</div>%N")
else
check is_html: False end
end
end
append ("</li>%N")
end
visit_link (a_link: FEED_LINK)
do
append ("<a href=%"" + a_link.href + "%">")
append_as_html_encoded (a_link.relation)
append ("</a>%N")
end
visit_author (a_author: FEED_AUTHOR)
do
end
feature -- Helper
append_as_html_encoded (s: READABLE_STRING_GENERAL)
do
buffer.append (html_encoder.general_encoded_string (s))
end
append (s: READABLE_STRING_8)
do
buffer.append (s)
end
append_date_time_to (dt: DATE_TIME; a_today: DATE; a_output: STRING_GENERAL)
do
if dt.year /= a_today.year then
a_output.append (dt.year.out)
a_output.append (",")
end
a_output.append (" ")
append_month_mmm_to (dt.month, a_output)
a_output.append (" ")
if dt.day < 10 then
a_output.append ("0")
end
a_output.append (dt.day.out)
end
append_month_mmm_to (m: INTEGER; s: STRING_GENERAL)
require
1 <= m and m <= 12
do
inspect m
when 1 then s.append ("Jan")
when 2 then s.append ("Feb")
when 3 then s.append ("Mar")
when 4 then s.append ("Apr")
when 5 then s.append ("May")
when 6 then s.append ("Jun")
when 7 then s.append ("Jul")
when 8 then s.append ("Aug")
when 9 then s.append ("Sep")
when 10 then s.append ("Oct")
when 11 then s.append ("Nov")
when 12 then s.append ("Dec")
else
-- Error
end
end
end

View File

@@ -0,0 +1,27 @@
note
description: "Interface to visit Feed objects."
date: "$Date$"
revision: "$Revision$"
deferred class
FEED_VISITOR
feature -- Visit
visit_feed (a_feed: FEED)
deferred
end
visit_link (a_link: FEED_LINK)
deferred
end
visit_item (a_item: FEED_ITEM)
deferred
end
visit_author (a_author: FEED_AUTHOR)
deferred
end
end

View File

@@ -0,0 +1,91 @@
note
description: "Summary description for {APPLICATION}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
APPLICATION
create
make
feature -- Initialization
make
-- New test routine
do
test_file ("data_rss_1_0.rss")
test_web ("https://bertrandmeyer.com/feed/")
end
test_file (fn: READABLE_STRING_GENERAL)
local
t: STRING
f: PLAIN_TEXT_FILE
do
create f.make_with_name (fn)
f.open_read
create t.make_empty
from
f.read_stream_thread_aware (1_024)
until
f.last_string.count < 1024
loop
t.append (f.last_string)
f.read_stream_thread_aware (1_024)
end
t.append (f.last_string)
f.close
test_feed (t)
end
test_feed (t: READABLE_STRING_8)
local
feed_parser: FEED_DEFAULT_PARSERS
vis: FEED_TO_STRING_32_DEBUG_VISITOR
gen: RSS_2_FEED_GENERATOR
atom_gen: ATOM_FEED_GENERATOR
s: STRING_32
s8: STRING_8
pp: XML_PRETTY_PRINT_FILTER
do
create feed_parser
if attached feed_parser.feed_from_string (t) as l_feed then
create s.make_empty
create vis.make (s)
l_feed.accept (vis)
print (s)
create s8.make_empty
create gen.make (s8)
l_feed.accept (gen)
print (s8)
create s8.make_empty
create atom_gen.make (s8)
l_feed.accept (atom_gen)
print (s8)
end
end
test_web (a_url: READABLE_STRING_8)
local
cl: LIBCURL_HTTP_CLIENT
sess: HTTP_CLIENT_SESSION
do
create cl.make
sess := cl.new_session (a_url)
sess.set_is_insecure (True)
if attached sess.get ("", Void) as resp then
if
not resp.error_occurred and then
attached resp.body as l_feed
then
test_feed (l_feed)
end
end
end
end

View File

@@ -0,0 +1,74 @@
note
description: "[
Eiffel tests that can be executed by testing tool.
]"
author: "EiffelStudio test wizard"
date: "$Date$"
revision: "$Revision$"
testing: "type/manual"
class
ATOM_TEST_SET
inherit
EQA_TEST_SET
feature -- Test routines
test_atom
-- New test routine
local
feed_parser: FEED_DEFAULT_PARSERS
vis: FEED_TO_STRING_32_DEBUG_VISITOR
s: STRING_32
do
create feed_parser
if attached feed_parser.feed_from_string (atom_string_1) as l_feed then
create s.make_empty
create vis.make (s)
l_feed.accept (vis)
print (s)
assert ("not_implemented", False)
end
assert ("not_implemented", False)
end
feature {NONE} -- Data
atom_string_1: STRING = "[
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Example Feed</title>
<subtitle>A subtitle.</subtitle>
<link href="http://example.org/feed/" rel="self" />
<link href="http://example.org/" />
<id>urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6</id>
<updated>2003-12-13T18:30:02Z</updated>
<entry>
<title>Atom-Powered Robots Run Amok</title>
<link href="http://example.org/2003/12/13/atom03" />
<link rel="alternate" type="text/html" href="http://example.org/2003/12/13/atom03.html"/>
<link rel="edit" href="http://example.org/2003/12/13/atom03/edit"/>
<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
<updated>2003-12-13T18:30:02Z</updated>
<summary>Some text.</summary>
<content type="xhtml">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This is the entry content.</p>
</div>
</content>
<author>
<name>John Doe</name>
<email>johndoe@example.com</email>
</author>
</entry>
</feed>
]"
end

View File

@@ -0,0 +1,60 @@
note
description: "Summary description for {RSS_TEST_SET}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
RSS_TEST_SET
inherit
EQA_TEST_SET
feature -- Test routines
test_rss_2
-- New test routine
local
feed_parser: FEED_DEFAULT_PARSERS
vis: FEED_TO_STRING_32_DEBUG_VISITOR
s: STRING_32
do
create feed_parser
if attached feed_parser.feed_from_string (rss_2_string_1) as l_feed then
create s.make_empty
create vis.make (s)
l_feed.accept (vis)
print (s)
assert ("not_implemented", False)
end
assert ("not_implemented", False)
end
feature {NONE} -- Data
rss_2_string_1: STRING = "[
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>Mon site</title>
<description>Ceci est un exemple de flux RSS 2.0</description>
<lastBuildDate>Sat, 07 Sep 2002 00:00:01 GMT</lastBuildDate>
<link>http://www.example.org</link>
<item>
<title>Post N1</title>
<description>This is my first post</description>
<pubDate>Sat, 07 Sep 2002 00:00:01 GMT</pubDate>
<link>http://www.example.org/actu1</link>
</item>
<item>
<title>Post N2</title>
<description>This is my second post</description>
<pubDate>Sat, 07 Sep 2002 00:00:01 GMT</pubDate>
<link>http://www.example.org/actu2</link>
</item>
</channel>
</rss>
]"
end

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="tests" uuid="C68BD5DC-F756-484E-A9FE-F2D1FD432B2A">
<target name="tests">
<root class="APPLICATION" feature="make"/>
<setting name="console_application" value="false"/>
<setting name="concurrency" value="none"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="feed" location="..\feed-safe.ecf" readonly="false"/>
<library name="http_client" location="$ISE_LIBRARY\contrib\library\network\http_client\http_client-safe.ecf"/>
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
<library name="xml_parser" location="$ISE_LIBRARY\library\text\parser\xml\parser\xml_parser-safe.ecf"/>
<tests name="src" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -2,8 +2,8 @@ note
description : "Objects that represent a custom error"
legal: "See notice at end of class."
status: "See notice at end of class."
date: "$Date$"
revision: "$Revision$"
date: "$Date: 2015-10-10 00:55:41 +0200 (sam., 10 oct. 2015) $"
revision: "$Revision: 97980 $"
class
ERROR_CUSTOM
@@ -24,7 +24,7 @@ feature {NONE} -- Initialization
if a_message /= Void then
message := a_message
else
message := "Error: " + a_name + " (code=" + a_code.out + ")"
message := {STRING_32} "Error: " + a_name + " (code=" + a_code.out + ")"
end
end

View File

@@ -2,8 +2,8 @@ note
description : "Objects that handle error..."
legal: "See notice at end of class."
status: "See notice at end of class."
date: "$Date$"
revision: "$Revision$"
date: "$Date: 2015-10-10 00:55:41 +0200 (sam., 10 oct. 2015) $"
revision: "$Revision: 97980 $"
class
ERROR_HANDLER
@@ -216,7 +216,7 @@ feature -- Basic operation
on_error_added (a_error)
end
add_error_details, add_custom_error (a_code: INTEGER; a_name: STRING; a_message: detachable STRING_32)
add_error_details, add_custom_error (a_code: INTEGER; a_name: STRING; a_message: detachable READABLE_STRING_32)
-- Add custom error to the stack of error
local
e: ERROR_CUSTOM

View File

@@ -64,4 +64,9 @@
<root all_classes="true"/>
<setting name="platform" value="unix"/>
</target>
<target name="all_stable_with_ssl" extends="all_stable">
<description>Compiling with ssl enabled</description>
<root all_classes="true"/>
<variable name="httpd_ssl_enabled" value="true"/>
</target>
</system>

View File

@@ -98,6 +98,8 @@ echo Install library: openid
echo Install library: uri_template
%COPYCMD% %TMP_DIR%\library\text\parser\uri_template %TMP_CONTRIB_DIR%\library\text\parser\uri_template
echo Install library: feed
%COPYCMD% %TMP_DIR%\library\text\parser\feed %TMP_CONTRIB_DIR%\library\text\parser\feed
echo Install library: notification_email
%SAFE_MD% %TMP_CONTRIB_DIR%\library\runtime

View File

@@ -65,6 +65,8 @@ echo Uninstall library: security\openid
%RDCMD% %TMP_CONTRIB_DIR%\library\web\authentication\openid
echo Uninstall library: uri_template
%RDCMD% %TMP_CONTRIB_DIR%\library\text\parser\uri_template
echo Uninstall library: feed
%RDCMD% %TMP_CONTRIB_DIR%\library\text\parser\feed
echo Uninstall library: runtime\process\notification_email
%RDCMD% %TMP_CONTRIB_DIR%\library\runtime\process\notification_email