Compare commits

...

155 Commits

Author SHA1 Message Date
d4bbdea5e4 Removed unneeded redefine. 2019-01-23 23:50:26 +01:00
8260336d6c Added support for X-Forwarded-For .., and Forwarded header, for the simple proxy implementation.
Also added the possibility to "keep" the original host name.
Updated related example.
2019-01-23 23:46:35 +01:00
19c14d28c7 Updated EiffelWeb wizard. 2018-11-19 19:10:26 +01:00
f1e8dfa40b Updated to use ARGUMENTS_32 as client. 2018-11-19 18:04:35 +01:00
e73302639d updated changelog. 2018-11-16 19:39:13 +01:00
1c58674523 Updated notes for JWS. 2018-11-16 19:35:34 +01:00
7cfe0cc5ed Removed obsolete v0 code. 2018-11-16 19:31:41 +01:00
4f8341e04e Updated JWT library with class descriptions and better features names.
JWT library fixed to use agorithms names in upper case.
Updated README.
2018-11-16 19:28:46 +01:00
7f36e539f1 Accepts string general in html_encoded_string. 2018-11-16 19:28:05 +01:00
8241c0209a Updated JWT README content. 2018-10-29 15:17:31 +01:00
45179b58a3 Fixed custom error creation, do not create default message, otherwise the info will be duplicated in error output. 2018-10-29 15:14:28 +01:00
31fa31bd53 Updated changelog 2018-10-29 13:22:01 +01:00
a8a3ca5b97 typo. 2018-10-29 13:15:32 +01:00
7c6fe5a04a Added HTTP_COOKIE.set_expiration_from_max_age, to add the "Expiration:" based on the max-age value. 2018-10-29 13:15:14 +01:00
c8e2009638 Use double quotes only when needed for put_content_type_with_parameters. 2018-10-29 13:11:19 +01:00
627ec7aefc removed unneeded inheritance. 2018-10-29 13:04:46 +01:00
e7087bcbc1 Added missing WSF_TIMEOUT_UTILITIES file. 2018-10-29 12:19:36 +01:00
8d881bcd7d Updated changelog.md 2018-10-29 12:16:01 +01:00
ed3ad962d1 Updated a few classes from http_client to use nanoseconds as timeout precision.
Fixed typo in comments.
2018-10-29 12:15:20 +01:00
d3e865cf6c Fixed setting of socket.timeout in httpd (was not currently set before).
Adopted the nanoseconds timeout precision
 - in config file added support for ns, us, ms, s timeout precision (without indication, it uses `seconds` precision).
2018-10-29 11:27:26 +01:00
9fcd30b4e1 removed useless JWT_ENCODER 2018-10-17 14:25:56 +02:00
0baa05cf63 JWT: updated to make JWT algorithm support more flexible, and simple to extend with specific algorithm. 2018-10-17 11:00:20 +02:00
f97f59b703 renamed a png file to avoid blank character. 2018-09-27 22:01:34 +02:00
dc377b84d3 Duplicated images to see expected images embedded in github markdown pages, and also in github web pages (jekyll on eiffelweb.org). 2018-09-27 21:57:16 +02:00
f14431fc05 Update basics.md 2018-09-27 21:50:02 +02:00
9577d7d82a Update basics.md 2018-09-27 21:48:45 +02:00
99f8377721 Include images twices to see them under github pages, and also in markdown pages 2018-09-27 21:47:48 +02:00
73d5555532 test (ignore) 2018-09-27 21:39:58 +02:00
ce3c7ac57a try change for related links. 2018-09-27 21:34:18 +02:00
Javier Velilla
8754c2d67d Update basics.md 2018-09-27 16:22:20 -03:00
5e928b9a47 use image location that works for md files, and also generated web files (jekyll) 2018-09-27 21:10:04 +02:00
Javier Velilla
9cdfbd2538 Update basics.md 2018-09-27 16:08:39 -03:00
e4fcc863ca Updated image locations 2018-09-27 21:05:54 +02:00
Javier Velilla
7c8d6b9eef Updated path to Application execution png file 2018-09-27 15:58:09 -03:00
a97eb4b062 Added missing dependencies. 2018-05-30 19:27:04 +02:00
bd5aba3db6 Updated Windows DOS script to build the libfcgi binary lib files. 2018-05-30 17:30:05 +02:00
d43c4edb7d Updated the default rescue response (i.e when exception or bad internal error occurs).
Factorized the implementation in WGI_RESCUE_EXECUTION, and now by redefining the `WGI_EXECUTION.execute_rescue (...)` procedure, it is possible to have a custom response on such rescued execution.
2018-05-30 17:28:24 +02:00
9cdd676417 Fixed HTTP_HEADER.put_raw_header (..) by ignoring any empty line of the argument value.
(note: "%R" is considered as empty line here.)
2018-05-30 17:25:04 +02:00
cb273c3176 Updated to compile with upcoming EiffelStudio 18.05 (with and without ssl). 2018-05-28 17:21:11 +02:00
ec7d504502 Fixed EOL. 2018-05-28 16:18:59 +02:00
7ed1e815b0 Updated to compile with upcoming EiffelStudio 18.05 . 2018-05-28 16:11:24 +02:00
da2e26f697 Renamed the fcgi executable. 2018-04-26 11:58:00 +02:00
bc169d6b26 Fixed remaining issues with docker setup. 2018-04-26 10:58:01 +02:00
cf2f0f09fa Updated container files. 2018-04-24 15:13:47 +02:00
207a109e44 Updated to match docker expectation. 2018-04-24 10:36:20 +02:00
2f2e2067ba Added an example to run the debug app with apache2+libfcgi inside a docker container. 2018-04-23 22:13:07 +02:00
7aa7bf1ab2 Updated travis CI config.
Updated install dos script to include wsf_security.
2018-02-13 18:49:14 +01:00
8e8c3602c6 Allow (websocket) upgrade even without persistent connection for normal http request.
(note: this allows to use websocket in single-threaded mode, and avoid the keep-alive-timeout delay before websocket begins its execution)
2018-02-13 18:39:47 +01:00
Jocelyn Fiat
ffd7dd8540 Improved WSF download response implementation.
- Do not set Transfer-Encoding to binary for download response.
- Use WSF_RESPONSE.put_file_content (...)
2018-02-05 21:02:23 +01:00
Jocelyn Fiat
947c94644e Apply win32 workaround only on Windows 32bits. 2018-02-02 22:43:50 +01:00
Jocelyn Fiat
6a779797a5 Removed a few obsolete calls.
Updated tests/all.ecf to include a few missing libraries.
2018-02-02 22:40:18 +01:00
Jocelyn Fiat
7b0ccc0aec Merge branch 'develop' 2018-01-29 22:16:57 +01:00
Jocelyn Fiat
74001fe674 Properly JSON encode null character as \u0000 . 2018-01-29 22:13:03 +01:00
Jocelyn Fiat
c7eb12ad8e Return NotFound response for "/favicon.ico" request instead of returning wrong plain text response. 2017-12-18 19:15:19 +01:00
Jocelyn Fiat
36eeff9285 Fixed the websocket example to also work for remote client.
(i.e not from the same machine).
2017-12-18 14:52:09 +01:00
Jocelyn Fiat
e9292b3eac Reverted last change as error_message on curl is not available for 17.05. 2017-12-01 12:05:01 +01:00
Jocelyn Fiat
30625d460f Added curl error message to the response error message (in addition to just the curl error code). 2017-11-30 20:08:58 +01:00
Jocelyn Fiat
24eb0a4002 Updated changelog 2017-11-30 15:05:54 +01:00
Jocelyn Fiat
7d738a164d Added missing comments. 2017-11-30 14:54:46 +01:00
Jocelyn Fiat
1037256ea6 Refactored using an abstraction WSF_PROTECTION.
This protection could be implemented with a regular expression,
    or using another solution (as manual parsing).
  Also, when a protection detects an issue, instead of returning empty string,
    it returns Void. If the value is a multiple string value, if an item is detected for an issue,
    the returned multiple string value is now Void.
  This abstraction will allow to return either Void, or a "corrected" value,
    for instance the string value, without the detected "<script..>..</script>" text.
  TODO: improve the WSF_PROTECTION_REGEXP to allow replacement strategy.
2017-11-27 15:44:19 +01:00
Jocelyn Fiat
4d79bba04b Merge branch 'ewf_xss' 2017-11-23 11:50:47 +01:00
jvelilla
5de024923e Updated xss support.
Added a new library wsf_security.
Updated test cases to cover protections policy.
Added a simple filter using an XSS implementation with WSF_XSS_REQUEST, but
it's possible to build custom filters and request using different protection patterns.
2017-11-22 17:22:02 -03:00
jvelilla
8b90241986 Moved XSS protection to WSF_EXTENSION.
Updated code to protect meta_variables.
Fixed typos.
Updated ecf's to use 1-16-0.
2017-11-13 15:06:02 -03:00
Jocelyn Fiat
da1c0b8545 Fixed typo in script. 2017-11-13 19:01:16 +01:00
Jocelyn Fiat
603bedf71d Reverted change that made WSF_URI_FILTER_HANDLER and WSF_URI_TEMPLATE_FILTER_HANDLER inheriting from WSF_EXECUTE_FILTER_HANDLER as it breaks existing projects using EiffelWeb. 2017-11-13 18:58:29 +01:00
Jocelyn Fiat
5fedad7f2e Updated Changelog. 2017-11-13 12:31:19 +01:00
Jocelyn Fiat
e83f5654d8 Updated NOTIFICATION_SMTP_MAILER to follow the EiffelNet EMAIL design. 2017-11-13 12:29:16 +01:00
jvelilla
25446cac12 Initial import WSF XSS protection.
Added an utility class to get safe query and form parameters.
Added a new WSF_XSS_REQUEST to use safe parameters.
Added a filter WSF_XSS_FILTER using WSF_XSS_REQUEST.
Added test cases

Signed-off-by: jvelilla <javier.hector@gmail.com>
2017-11-10 10:37:32 -03:00
Jocelyn Fiat
ccff084642 Updated travis CI config to use install script. 2017-11-08 10:20:50 +01:00
Jocelyn Fiat
830adbe10c Fixed response handlers compilation. 2017-11-07 23:52:42 +01:00
Jocelyn Fiat
e6d998953e Updated changelog. 2017-11-07 23:17:39 +01:00
Jocelyn Fiat
6ca3cca88b Reverted change that made WSF_URI_HANDLER and WSF_URI_TEMPLATE_HANDLER inheriting from WSF_EXECUTE_HANDLER, as it breaks existing project using EiffelWeb. 2017-11-07 23:05:22 +01:00
Jocelyn Fiat
f91a676f41 fixed obsolete v0 wsf_extension ecf file. 2017-11-04 22:44:27 +01:00
Jocelyn Fiat
1c75e11e34 removed unused local variable. 2017-11-03 18:54:46 +01:00
Jocelyn Fiat
b5b4fa6b2f added develop branch for CI 2017-11-03 18:06:47 +01:00
Jocelyn Fiat
211fc425a3 Added handler to add support for CGI scripts.
Added a new tool `httpd` which is a basic httpd server product (with file server and CGI handler).
2017-11-03 18:00:39 +01:00
Jocelyn Fiat
95cebe26bb Added routing condition mapping.
Added WSF_EXECUTE_HANDLER as common ancestor for handler with `execute (WSF_REQUEST, WSF_RESPONSE) ..` routine.
Made more flexible a few routine by accepting ITERABLE instead of ARRAY, and READABLE_STRING_GENERAL when possible.
2017-11-03 17:59:10 +01:00
Jocelyn Fiat
f770c236d5 Improved support for absolute url passed tp HTTP_REQUEST_SESSION . 2017-10-27 19:24:52 +02:00
Jocelyn Fiat
503e5f7915 Merge branch 'master' into v1 2017-10-26 10:23:33 +02:00
Jocelyn Fiat
39f01e95fd Use single ecf file. 2017-10-26 10:19:56 +02:00
Jocelyn Fiat
c725159d7e Merge branch 'master' into with_compression 2017-10-26 10:13:27 +02:00
Jocelyn Fiat
e66f1cf7be updated changelog 2017-10-24 17:51:55 +02:00
Jocelyn Fiat
c03d28cabc Use new on_timer solution, to check every 1 second, the presence of ".stop" file.
If this file exists, close all active websockets.

Redesigned the commands implementation for this example.
2017-10-24 17:45:08 +02:00
Jocelyn Fiat
e834b2b360 Added on_timer callback event so that server can check regularly external state.
This is a basic solution to implement a way to check for time to time for events to notify websocket clients.
2017-10-24 17:43:06 +02:00
Jocelyn Fiat
d089fd3a03 Merge branch 'master' into v1 2017-10-19 11:20:55 +02:00
Jocelyn Fiat
a0c1ab5232 updated simple.ini settings. 2017-10-19 11:20:14 +02:00
Jocelyn Fiat
a8ddd10b46 Merge branch 'master' into v1 2017-10-19 10:57:45 +02:00
Jocelyn Fiat
db39068ceb Updated documentation for standalone connector.
Changed `default_max_keep_alive_requests` from 100 to 300.
2017-10-19 00:14:23 +02:00
Jocelyn Fiat
a1b4337438 Set keep_alive_timeout to 2, this way for single threaded case, browser does not wait too much to start the websocket connection.
Set max_keep_alive_requests to -1, to allow unlimited number of requests within a same websocket connection.
2017-10-18 23:41:03 +02:00
Jocelyn Fiat
74121be470 Support persistent connection, even in single thread mode (i.e concurrency=none).
Warning: as there is no concurrent request handling in single threaded mode,
            it is recommended to either set the keep_alive_timeout to a small value,
            or disable persistent connection by setting max_keep_alive_requests to 0.
Change the default keep_alive_timeout from 15 to 5 seconds.
Accept -1 as value of max_keep_alive_requests to have unlimited number of request in the same persistent connection.
2017-10-18 23:29:16 +02:00
Jocelyn Fiat
1c9f5ac0e7 Merge branch 'master' into v1 2017-10-17 14:52:17 +02:00
Jocelyn Fiat
edec837c4e Made interface of wsf forms and widgets a bit more flexible by accepting READABLE_STRING_GENERAL. 2017-10-17 14:34:50 +02:00
Jocelyn Fiat
f1642a444a Improved support of absolute/relative https:// and http:// in http_client. 2017-10-17 14:30:44 +02:00
Jocelyn Fiat
48af63af83 Fixed typo to process relative or absolute url. 2017-10-09 14:23:45 +02:00
Jocelyn Fiat
2f98d7031f Updated a few package.iron files. 2017-10-06 14:06:46 +02:00
Jocelyn Fiat
70f00651c7 update test case. 2017-10-06 13:58:54 +02:00
Jocelyn Fiat
199f84c7ef Updated Readme file with Build Status on Master Branch. 2017-10-06 10:37:11 +02:00
Jocelyn Fiat
9b97627c76 Update package.iron files. 2017-10-06 10:27:32 +02:00
Jocelyn Fiat
72c87cd74d Fixed curl implementation by setting Content-Type to x-www-form-urlencoded (if not set) when POST send data as x-www-form-urlencoded. 2017-10-06 10:24:48 +02:00
Jocelyn Fiat
2ed4d03168 Renamed ciphers_settings as ciphers_setting . 2017-10-06 09:34:08 +02:00
Jocelyn Fiat
18ed92a61d Moved to unique .ecf from ecf version 1-16-0 .
Requires 17.05 or newer.
2017-10-06 09:02:10 +02:00
Jocelyn Fiat
0a6a4281e7 Merge branch 'master' into es17.05 2017-10-06 08:18:50 +02:00
Jocelyn Fiat
38cf5d7a6f Updated requirements, now EiffelWeb requires 17.05 or newer.
Improved support for future 17.11 version with new openssl library.
2017-10-05 22:07:05 +02:00
jvelilla
96648a16dc Updated Readme file with Build Status on Master Branch.
Update code style: refactor rename ciphers_settings to ciphers_setting.
2017-09-25 14:21:35 -03:00
Javier Velilla
6f35ad7b16 Merge pull request #181 from jvelilla/ewf_http_client
Update HTTP Client cURL implementation:
2017-09-25 11:05:45 -03:00
Jocelyn Fiat
8ff20d34a7 Merge branch 'master' into v1 2017-09-21 21:22:38 +02:00
jvelilla
85c8a46c89 Update Readme.md with a note about ciphers implementation. 2017-09-21 08:07:11 -03:00
Jocelyn Fiat
498e4a6ec2 Fixed validation of iss and aud when issuer and audience are not set. 2017-09-21 10:46:08 +02:00
Jocelyn Fiat
ab507d543a Now HTTP_AUTHORIZATION acceps READABLE_STRING_GENERAL for username and password argument. 2017-09-21 10:45:40 +02:00
Jocelyn Fiat
20a90db2e3 If url is relative, use session.url (...) to get valid url.
It could happen with relative url in `Location: ...` header (for redirection).
2017-09-21 10:45:08 +02:00
Jocelyn Fiat
97fe16b4c2 Code cleaning. 2017-09-21 10:26:29 +02:00
jvelilla
6ed91699b8 Renamed feature 'set_ciphers' to 'set_ciphers_settings' and added description. 2017-09-19 10:32:17 -03:00
jvelilla
bb334aef80 Updated HTTP client cURL implementation.
Refactor rename cipher_list by ciphers_settings and description.
Updated ciphers_settings representation to STIRNG_8
Refactor rename set_cipher_list by set_ciphers.
2017-09-14 11:58:43 -03:00
jvelilla
c2764e25ff Update HTTP Client cURL implementation:
Added the option to set cipher list used to negotiate security settings
(SSL handshake)
2017-09-14 10:21:32 -03:00
Jocelyn Fiat
cdada71f7e Corrected wsf_compression.ecf which was missing a few libraries.
The -safe.ecf was correct.
2017-09-02 21:49:41 +02:00
Jocelyn Fiat
a7d0398ec6 Introduce WSF_COMPRESSION and applied to WSF_*_WITH_COMPRESSION classes.
Modified the example to send the file with or without compression.
2017-09-01 18:59:18 +02:00
jvelilla
267655d7bc Update code, comment style and removed hardcoded value. 2017-08-18 13:55:54 -03:00
jvelilla
e735da1bcb Merge branch 'ewf_compression' of https://github.com/jocelyn/EWF into ewf_compression_2017 2017-08-18 09:52:03 -03:00
Jocelyn Fiat
6425482070 Fixed ecf by removing the override declaration. 2017-08-08 15:55:52 +02:00
Jocelyn Fiat
818c3fb460 Made compilable with EiffelStudio 17.05 and probably before as well. 2017-08-08 15:54:21 +02:00
Jocelyn Fiat
dac50b490d Added output for the travis CI job. 2017-08-08 14:10:12 +02:00
Jocelyn Fiat
16d5076fe5 Added Travis CI support with 17.05. 2017-08-08 14:03:39 +02:00
Jocelyn Fiat
2748e1d9ee Now JWT_LOADER takes the alg as argument, to avoid security issue where the lib is taking alg from the header (which may be a bad security weakness). 2017-07-11 23:32:11 +02:00
Jocelyn Fiat
27ee20f99b Added convenient get and custom functions on HTTP_CLIENT directly. 2017-07-11 23:29:42 +02:00
Javier Velilla
9a3164df70 Merge pull request #178 from jvelilla/ewf_ssl
Updated EWF  http_network, websocket, httpd to use the latest EiffelN…
2017-06-23 09:53:26 -03:00
jvelilla
02383810b4 Fixed bad identation
Updated date to current date in obsolte message.
2017-06-23 09:51:59 -03:00
jvelilla
dbf5e76047 Updated EWF network and httpd libraries.
Updated features using ssl_2 and ssl_3 as obsolete and raise a
developer exception.
2017-06-22 10:23:56 -03:00
jvelilla
5c31905427 Updated EWF http_network, websocket, httpd to use the latest EiffelNet SSL
version.
2017-06-21 18:34:07 -03:00
Jocelyn Fiat
21adef6998 Merge branch 'master' into es17.05
# Conflicts:
#	library/network/http_client/http_client-safe.ecf
#	library/network/http_client/libcurl_http_client-safe.ecf
#	library/network/http_client/net_http_client-safe.ecf
2017-06-21 09:01:27 +02:00
Jocelyn Fiat
c7ef652322 Made the parameters not hidden implementation classes. 2017-06-21 08:59:07 +02:00
Jocelyn Fiat
67bdcfb6ef Made the parameters not hidden implementation classes. 2017-06-21 08:58:15 +02:00
Jocelyn Fiat
ca4043b102 Merge branch 'master' into v1 2017-06-20 18:17:30 +02:00
Jocelyn Fiat
310e96e185 Updated EOL for 2 ecf files. 2017-06-20 18:16:43 +02:00
Jocelyn Fiat
46a48625ac Merge branch 'master' into es17.05 2017-06-20 18:15:03 +02:00
Jocelyn Fiat
7feb45b549 Updated error library (cosmetic, and loop iteration). 2017-06-20 18:08:50 +02:00
Jocelyn Fiat
aeb3392ad8 Merge branch 'master' into es17.05
# Conflicts:
#	contrib/library/network/server/nino/example/SimpleWebServer/web_server-safe.ecf
#	contrib/library/network/server/nino/example/SimpleWebServer/web_server.ecf
#	library/network/http_client/libcurl_http_client-safe.ecf
#	library/network/http_client/net_http_client-safe.ecf
2017-06-20 17:52:40 +02:00
Jocelyn Fiat
5bbd031275 Update EOL on ecf files. 2017-06-20 17:49:28 +02:00
Jocelyn Fiat
90e60fad26 Updated changelog. 2017-06-20 17:47:17 +02:00
Jocelyn Fiat
98c20ee7c1 Fixed specific ecf files for http_client library. 2017-06-20 17:37:24 +02:00
Jocelyn Fiat
9fc5a7c364 Merge branch 'v1' into es17.05 2017-06-20 09:54:07 +02:00
Jocelyn Fiat
64027f56bd Merge branch 'master' into v1 2017-06-20 09:53:29 +02:00
Jocelyn Fiat
0b99e84728 Added installation of JWT into Eiffel installation. 2017-06-20 09:53:13 +02:00
Jocelyn Fiat
c51a0a4788 Merge branch 'v1' into es17.05
# Conflicts:
#	library/network/http_client/tests/test.ecf
2017-06-20 09:49:51 +02:00
Jocelyn Fiat
a6806c676a Updated ecf files to version 1-16-0 .
Minor cosmetic changes.
2017-06-20 09:48:10 +02:00
Jocelyn Fiat
9e5e8bb1bf Added simple way to set the issued_at claim value to current UTC date time. 2017-06-14 16:27:24 +02:00
Jocelyn Fiat
10a83c6ad8 Added possibility to create JWS object with specific algo hs256 or none easily. 2017-06-14 16:26:31 +02:00
Jocelyn Fiat
1ec3b8e7a4 Added support for multiple file in form data.
Made clear what is the meaning of upload_filename, upload_data and form_data.
2017-06-14 16:19:43 +02:00
Jocelyn Fiat
0783049fb4 updated package.iron 2017-06-08 11:25:26 +02:00
Jocelyn Fiat
7e54825b84 Updated JWT library, add supports for claim exp, iat, nbf, iss, aud . 2017-06-07 23:24:46 +02:00
Jocelyn Fiat
40cbe7dfc9 Merge branch 'master' into dev_jwt 2017-06-06 10:58:38 +02:00
Jocelyn Fiat
d4b9301a57 Reverted to previous TYPE.name to be compilable with version prior to 17.05.
(TYPE.name_32 exists since 17.05).
2017-06-02 17:40:53 +02:00
Jocelyn Fiat
06cda97535 New JSON Web Token (JWT) library. 2017-05-24 11:56:15 +02:00
e14bb568d2 Extracted compression code from wsf, and provided new wsf_compression library.
Renamed features.
2016-12-06 14:18:51 +01:00
05d37439bc Merge branch 'ewf_compression' of https://github.com/jvelilla/EWF into ewf_compression 2016-12-06 12:53:41 +01:00
jvelilla
99bf552b89 Added compression support to WSF_FILE_SYSTEM_HANDLER.
Added a simple example using eiffel web compression.
2016-11-29 18:17:44 -03:00
317 changed files with 174067 additions and 6803 deletions

View File

@@ -0,0 +1,18 @@
language: eiffel
before_script:
- export current_dir=$PWD ; echo current_dir=$current_dir ; cd ..
- curl -sSL https://www.eiffel.org/setup/install.sh | bash > eiffel.rc
- source ./eiffel.rc
- echo `ec -version`
- cd $current_dir
- echo Check projects compilation status...
branches:
only:
- master
- v1
- develop
script: compile_all -ecb -melt -list_failures -log_verbose -clean -options dotnet=false
group: stable
os: linux

View File

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

View File

@@ -1,5 +1,7 @@
# Eiffel Web Framework
[![Build Status](https://api.travis-ci.org/EiffelWebFramework/EWF.svg?branch=master)](https://travis-ci.org/EiffelWebFramework/EWF/)
## Overview
@@ -11,14 +13,14 @@ Currently EWF offers a collection of Eiffel libraries designed to be integrated
There is a growing ecosystem around EWF, that provides useful components:
* OpenID and OAuth consumer library
* Various hypermedia format such as HAL, Collection+json,
* Various hypermedia format such as HAL, Collection+json, ...
* Websocket server and client
* Template engine
* API Auto-documentation with swagger
* A simple experimental CMS.
* ...
So if you want to build a website, a web api, RESTful service, or even if you want to consume other web api, EWF is a solution.
So if you want to build a website, a web api, RESTful service, ... or even if you want to consume other web api, EWF is a solution.
EWF brings with it all the advantages of the Eiffel technology and tools with its powerful features such as Design by Contract, debugging, testing tools which enable to build efficient systems expected to be repeatedly refined, extended, and improved in a predictable and controllable way so as to become with time bugfree systems. Enjoy the full power of debugging your web server application from the IDE.
@@ -49,11 +51,11 @@ Tasks and issues are managed with github issue system
## How to get the source code?
Using git
* git clone https://github.com/EiffelWebFramework/EWF.git
* `git clone https://github.com/EiffelWebFramework/EWF.git`
* And to build the required and related Clibs
* cd contrib/ise_library/cURL
* geant compile
* `cd contrib/ise_library/cURL`
* `geant compile`
## Libraries under 'library'
@@ -62,7 +64,7 @@ Using git
* connectors: various web server connectors for EWSGI
* libfcgi: Wrapper for libfcgi SDK
* __wsf__: Web Server Framework [read more](library/server/wsf)
* __router__: URL dispatching/routing based on uri, uri_template, or custom [read more](library/server/wsf/router)
* __router__: URL dispatching/routing based on `uri`, `uri_template`, or custom [read more](library/server/wsf/router)
### protocol
* __http__: HTTP related classes, constants for status code, content types, ... [read more](library/network/protocol/http)

View File

@@ -1,3 +1,3 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<redirection xmlns="http://www.eiffel.com/developers/xml/configuration-1-16-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-16-0 http://www.eiffel.com/developers/xml/configuration-1-16-0.xsd" uuid="B1D3254D-A58E-4259-9796-8A2843A511A9" message="Obsolete: use web_server.ecf !" location="web_server.ecf">
</redirection>
<?xml version="1.0" encoding="ISO-8859-1"?>
<redirection xmlns="http://www.eiffel.com/developers/xml/configuration-1-16-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-16-0 http://www.eiffel.com/developers/xml/configuration-1-16-0.xsd" uuid="B1D3254D-A58E-4259-9796-8A2843A511A9" message="Obsolete: use web_server.ecf !" location="web_server.ecf">
</redirection>

View File

@@ -1,7 +1,6 @@
package nino
project
nino = "nino-safe.ecf"
nino = "nino.ecf"
note

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

View File

@@ -59,7 +59,7 @@ feature -- Basic operations
end
```
When using the "standalone" connector (or the deprecated "nino" connector), by default the service listens on port 80, but often this port is already used by other applications, so it is recommended to use another port.
When using the [standalone](../connectors/standalone.md) connector (or the deprecated "nino" connector), by default the service listens on port 80, but often this port is already used by other applications, so it is recommended to use another port.
To define another port, redefine the feature `initialize` and set up a new port number using the service options (see below).
@@ -86,17 +86,20 @@ feature {NONE} -- Initialization
end
```
Learn more about the [Standalone](../connectors/standalone.md) connector.
The **WSF_REQUEST** gives access to the incoming data; the class provides features to get information such as request method, form data, query parameters, uploaded files, HTTP request headers, and hostname of the client among others.
The **WSF_RESPONSE** provides features to define the response with information such as HTTP status codes (10x,20x, 30x, 40x, and 50x), response headers (Content-Type, Content-Length, etc.) and obviously the body of the message itself.
**APPLICATION** is the root class of our example, it launches the application, using the corresponding connector, Which connector? this depends how do you want to run it `cgi`, `fcgi`, `standalone`. For development is recommended to use a standalone web server written in Eiffel, and run the execution within the EiffelStudio debugger. For production fcgi (or cgi) using Apache or another popular web server.
![Launcher Hierarchy](./Launcher Hierarchy.png "Launcher Hierarchy")
![Launcher Hierarchy](images/Launcher_Hierarchy.png "Launcher Hierarchy")
**WS_LAUNCHABLE_SERVICE** inherit from **WS_SERVICE** class, which is a marker interface in EWF. And also provides a way to launch our application using different kind of connectors. The class **WSF_DEFAULT_SERVICE_I**, inherit from **WS_LAUNCHABLE_SERVICE** and has a formal generic that should conform to **WSF_SERVICE_LAUNCHER [WSF_EXECUTION]**. Below a [BON diagram](http://www.bon-method.com/index_normal.htm) showing one of the possible options.
![Standalone Launcher](./WSF_SERVICE_LAUNCHER_STANDALONE.png "Standalone Hierarchy")
![Standalone Launcher](images/WSF_SERVICE_LAUNCHER_STANDALONE.png "Standalone Hierarchy")
Other connectors:
**WSF_STANDALONE_SERVICE_LAUNCHER**
@@ -109,7 +112,7 @@ The **APPLICATION_EXECUTION** class inherits from **WSF_EXECUTION** interface,
In the **APPLICATION_EXECUTION** class class you will need to implement the **execute** feature, get data from the request *req* and write the response in *res*.
![Execution Hierarchy](./APPLICATION_EXECUTION.png "Application Execution ")
![Execution Hierarchy](images/APPLICATION_EXECUTION.png "Application Execution ")
The WSF_EXECUTION instance, in this case ```APPLICATION_EXECUTION``` is created per request, with two main attributes request: ```WSF_REQUEST``` and response: ```WSF_RESPONSE```.

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,27 @@
Nav: [Workbook](../workbook.md)
## The EiffelWeb standalone connector
It provides a standalone httpd server for the EiffelWeb framework.
It implements HTTP/1.1 with persistent connection, concurrent connection, ...
To easily set the standalone connector, see class `WSF_STANDALONE_SERVICE_OPTIONS`.
### Main settings:
* `port`: Listening port number (defaut: 80).
* `max_concurrent_connections`: maximum of concurrent connections (default: 100)
* `max_tcp_clients`: Listen on socket for at most `max_tcp_clients` connections (default: 100)
* `socket_timeout`: Amount of seconds the server waits for receipts and transmissions during communications. With timeout of 0, socket can wait for ever. (default: 60)
* `socket_recv_timeout`: Amount of seconds the server waits for receiving data during communications. With timeout of 0, socket can waits for ever. (default: 5)
* `keep_alive_timeout`: Persistent connection timeout. Number of seconds the server waits after a request has been served before it closes the connection (default: 5)
* `max_keep_alive_requests`: Maximum number of requests allowed per persistent connection. To disable KeepAlive, set `max_keep_alive_requests` to `0`. To have no limit, set `max_keep_alive_requests` to `-1` (default: 300).
* `is_secure`: check SSL certificate?
* `secure_certificate`: path to SSL certificate.
* `secure_certificate_key`: certificate key
* `verbose`: display verbose output (Default: false)
See also `WGI_STANDALONE_CONSTANTS` for default values.

View File

@@ -113,14 +113,13 @@ feature -- Implementation
across
l_values as c
loop
s.replace_substring_all ({STRING_32} "${" + c.key.as_string_32 + "}", c.item)
s.replace_substring_all ({STRING_32} "${" + c.key.to_string_32 + "}", c.item.to_string_32)
end
end
end
note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Olivier Ligot, 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,50 @@
FROM debian
#ubuntu:xenial
MAINTAINER Jocelyn Fiat <jfiat@eiffel.com>
LABEL description="EiffelWeb debug example hosted using apache2+libfcgi"
RUN apt-get update \
&& apt-get -y install \
curl bzip2 make gcc git-core \
apache2 libapache2-mod-fcgid libfcgi-dev \
&& rm -rf /var/lib/apt/lists/*
#RUN apt-get update && apt-get -y install tmux git-all vim && rm -rf /var/lib/apt/lists/*
EXPOSE 80
RUN a2enmod rewrite suexec include fcgid
RUN export uid=1000 gid=1000 && \
mkdir -p /home/eifweb && \
echo "eifweb:x:${uid}:${gid}:eifweb,,,:/home/eifweb:/bin/bash" >> /etc/passwd && \
echo "eifweb:x:${uid}:" >> /etc/group && \
chown ${uid}:${gid} -R /home/eifweb
USER eifweb
ENV HOME /home/eifweb
ENV WEBDIR /home/eifweb/www
WORKDIR $HOME
# Create expected folders
RUN mkdir $WEBDIR
#Build the debug EiffelWeb example and copy the executable to $HOME/
COPY files/build_service_fcgi $HOME/build_service_fcgi
USER root
RUN chown eifweb:eifweb $HOME/build_service_fcgi && chmod 700 $HOME/build_service_fcgi
USER eifweb
RUN $HOME/build_service_fcgi $HOME/www
USER root
COPY ./files/httpd.conf /etc/apache2/sites-enabled/000-default.conf
COPY ./files/html/.htaccess $WEBDIR/html/.htaccess
COPY ./files/html/index.html $WEBDIR/html/index.html
RUN echo > $WEBDIR/html/service.ews
RUN chown www-data:www-data -R $WEBDIR
#Setup apache as foreground (for docker purpose)
RUN mkdir -p /etc/service/apache
ADD ./files/apache.sh /etc/service/apache/run
RUN chmod +x /etc/service/apache/run
ENTRYPOINT ["/etc/service/apache/run"]

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +0,0 @@
Filter example
To test the example, you can just run in a terminal:
> curl -u foo:bar http://localhost:9090/user/1 -v

View File

@@ -1,57 +0,0 @@
note
description: "Summary description for {DATABASE_API}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
DATABASE_API
create
make
feature -- Initialization
make
local
l_user: USER
do
create users.make (10)
create l_user.make (1, "foo", "bar")
users.put (l_user, l_user.id)
create l_user.make (2, "demo", "demo")
users.put (l_user, l_user.id)
end
feature -- Access
user (a_id: INTEGER; a_name: detachable READABLE_STRING_GENERAL): detachable USER
-- User with id `a_id' or name `a_name'.
require
a_id > 0 xor a_name /= Void
local
n: like {USER}.name
do
if a_id > 0 then
Result := users.item (a_id)
elseif a_name /= Void then
n := a_name.as_string_8
across
users as c
until
Result /= Void
loop
if attached c.item as u and then u.name.same_string (n) then
Result := u
end
end
end
ensure
Result /= Void implies ((a_id > 0 and then Result.id = a_id) xor (a_name /= Void and then Result.name.same_string_general (a_name)))
end
users: HASH_TABLE [USER, INTEGER]
;note
copyright: "2011-2012, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,20 +0,0 @@
note
description: "Summary description for {SHARED_DATABASE_API}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
SHARED_DATABASE_API
feature -- Access
db_access: DATABASE_API
once
create Result.make
end
note
copyright: "2011-2012, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,52 +0,0 @@
note
description: "JSON user converter."
author: "Olivier Ligot"
date: "$Date$"
revision: "$Revision$"
class
JSON_USER_CONVERTER
inherit
JSON_CONVERTER
create
make
feature {NONE} -- Initialization
make
do
create object.make (0, "", "")
end
feature -- Access
object: USER
value: detachable JSON_OBJECT
feature -- Conversion
from_json (j: attached like value): detachable like object
-- Convert from JSON value.
do
end
to_json (o: like object): like value
-- Convert to JSON value.
do
create Result.make
Result.put (json.value (o.id), id_key)
Result.put (json.value (o.name), name_key)
end
feature {NONE} -- Implementation
id_key: STRING = "id"
name_key: STRING = "name"
note
copyright: "2011-2012, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,59 +0,0 @@
note
description: "User."
author: ""
date: "$Date$"
revision: "$Revision$"
class
USER
inherit
ANY
redefine
is_equal
end
create
make
feature {NONE} -- Initialization
make (an_id: INTEGER; a_name, a_password: STRING)
do
id := an_id
name := a_name
password := a_password
ensure
id_set: id = an_id
name_set: name = a_name
password_set: password = a_password
end
feature -- Access
id: INTEGER
-- Identifier
name: STRING
-- Name
password: STRING
-- Password
feature -- Comparison
is_equal (other: like Current): BOOLEAN
-- Is `other' attached to an object considered
-- equal to current object?
do
if Current = other then
Result := True
else
Result := (id = other.id) and (name = other.name) and (password = other.password)
end
end
;note
copyright: "2011-2012, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,65 +0,0 @@
note
description: "Authentication filter."
author: "Olivier Ligot"
date: "$Date$"
revision: "$Revision$"
class
AUTHENTICATION_FILTER
inherit
WSF_FILTER
WSF_URI_TEMPLATE_HANDLER
SHARED_DATABASE_API
SHARED_EJSON
feature -- Basic operations
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute the filter
local
l_auth: detachable HTTP_AUTHORIZATION
do
if attached req.http_authorization as l_http_authorization then
create l_auth.make (l_http_authorization)
end
if
l_auth /= Void and then
l_auth.is_basic and then
attached l_auth.login as l_auth_login and then
attached Db_access.user (0, l_auth_login) as l_user and then
l_auth_login.same_string (l_user.name) and then
attached l_auth.password as l_auth_password and then
l_auth_password.same_string (l_user.password)
then
req.set_execution_variable ("user", l_user)
execute_next (req, res)
else
handle_unauthorized ("Unauthorized", req, res)
end
end
feature {NONE} -- Implementation
handle_unauthorized (a_description: STRING; req: WSF_REQUEST; res: WSF_RESPONSE)
-- Handle forbidden.
local
h: HTTP_HEADER
do
create h.make
h.put_content_type_text_plain
h.put_content_length (a_description.count)
h.put_current_date
h.put_header_key_value ({HTTP_HEADER_NAMES}.header_www_authenticate, "Basic realm=%"User%"")
res.set_status_code ({HTTP_STATUS_CODE}.unauthorized)
res.put_header_text (h.string)
res.put_string (a_description)
end
note
copyright: "2011-2014, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,34 +0,0 @@
note
description: "Summary description for {FILTER_HANDLER_CONTEXT}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
FILTER_HANDLER_CONTEXT
inherit
WSF_HANDLER_CONTEXT
create
make
feature -- Access
user: detachable USER
-- Authenticated user
feature -- Element change
set_user (a_user: USER)
-- Set `user' to `a_user'
do
user := a_user
ensure
user_set: user = a_user
end
note
copyright: "2011-2012, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,111 +0,0 @@
note
description : "Filter example."
author : "Olivier Ligot"
date : "$Date$"
revision : "$Revision$"
class
FILTER_SERVER
inherit
ANY
WSF_DEFAULT_SERVICE
WSF_ROUTED_SERVICE
undefine
execute
end
WSF_FILTERED_SERVICE
SHARED_EJSON
create
make
feature {NONE} -- Initialization
make
local
l_message: STRING
l_factory: INET_ADDRESS_FACTORY
do
initialize_router
initialize_filter
initialize_json
set_service_option ("port", port)
create l_message.make_empty
l_message.append_string ("Launching filter server at ")
create l_factory
l_message.append_string (l_factory.create_localhost.host_name)
l_message.append_string (" port ")
l_message.append_integer (port)
io.put_string (l_message)
io.put_new_line
make_and_launch
end
create_filter
-- Create `filter'
do
create {WSF_CORS_FILTER} filter
end
setup_filter
-- Setup `filter'
local
l_routing_filter: WSF_ROUTING_FILTER
l_logging_filter: WSF_LOGGING_FILTER
do
create l_routing_filter.make (router)
l_routing_filter.set_execute_default_action (agent execute_default)
filter.set_next (l_routing_filter)
create l_logging_filter
l_routing_filter.set_next (l_logging_filter)
end
setup_router
-- Setup `router'
local
l_options_filter: WSF_CORS_OPTIONS_FILTER
l_authentication_filter: AUTHENTICATION_FILTER
l_user_filter: USER_HANDLER
l_methods: WSF_REQUEST_METHODS
do
create l_options_filter.make (router)
create l_authentication_filter
create l_user_filter
l_options_filter.set_next (l_authentication_filter)
l_authentication_filter.set_next (l_user_filter)
create l_methods
l_methods.enable_options
l_methods.enable_get
router.handle ("/user/{userid}", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent l_options_filter.execute), l_methods)
end
initialize_json
-- Initialize `json'.
do
json.add_converter (create {JSON_USER_CONVERTER}.make)
end
feature {NONE} -- Implementation
port: INTEGER = 9090
-- Port number
note
copyright: "2011-2017, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -1,97 +0,0 @@
note
description: "User handler."
author: "Olivier Ligot"
date: "$Date$"
revision: "$Revision$"
class
USER_HANDLER
inherit
WSF_FILTER
WSF_URI_TEMPLATE_HANDLER
WSF_RESOURCE_HANDLER_HELPER
redefine
do_get
end
SHARED_DATABASE_API
SHARED_EJSON
feature -- Basic operations
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute request handler
do
execute_methods (req, res)
execute_next (req, res)
end
do_get (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Using GET to retrieve resource information.
-- If the GET request is SUCCESS, we response with
-- 200 OK, and a representation of the user
-- If the GET request is not SUCCESS, we response with
-- 404 Resource not found
require else
authenticated_user_attached: attached {USER} req.execution_variable ("user")
local
id : STRING
do
if attached req.orig_path_info as orig_path then
id := get_user_id_from_path (orig_path.as_string_32)
if attached retrieve_user (id) as l_user then
if l_user ~ req.execution_variable ("user") then
compute_response_get (req, res, l_user)
elseif attached {USER} req.execution_variable ("user") as l_auth_user then
-- Trying to access another user that the authenticated one,
-- which is forbidden in this example...
handle_forbidden ("You try to access the user " + id.out + " while authenticating with the user " + l_auth_user.id.out, req, res)
end
else
handle_resource_not_found_response ("The following resource " + orig_path + " is not found ", req, res)
end
end
end
feature {NONE} -- Implementation
compute_response_get (req: WSF_REQUEST; res: WSF_RESPONSE; l_user : USER)
local
h: HTTP_HEADER
l_msg : STRING
do
create h.make
h.put_content_type_application_json
if attached {JSON_VALUE} json.value (l_user) as jv then
l_msg := jv.representation
h.put_content_length (l_msg.count)
if attached req.request_time as time then
h.put_utc_date (time)
end
res.set_status_code ({HTTP_STATUS_CODE}.ok)
res.put_header_text (h.string)
res.put_string (l_msg)
end
end
get_user_id_from_path (a_path: READABLE_STRING_32): STRING
do
Result := a_path.split ('/').at (3)
end
retrieve_user (id: STRING) : detachable USER
-- Retrieve the user by id if it exist, in other case, Void
do
if id.is_integer and then Db_access.users.has (id.to_integer) then
Result := db_access.users.item (id.to_integer)
end
end
note
copyright: "2011-2017, Olivier Ligot, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,3 +0,0 @@
The current example has a main target for the server: "restbucks"
But we also provide "policy_driven_restbucks" target which is using the
policy-driven framework than help coder fulfill HTTP expectations.

View File

@@ -1,11 +0,0 @@
Make sure to have the Clib generated in the related cURL library
- if you use EiffelStudio >= 7.0
check %ISE_LIBRARY%\library\cURL\spec\%ISE_C_COMPILER%\$ISE_PLATFORM
or $ISE_LIBRARY/library/cURL/spec/$ISE_PLATFORM
- otherwise if you use earlier version
check under ext/ise_library/curl/spec/...
And on Windows, be sure to get the libcurl.dll from %ISE_LIBRARY%\studio\spec\%ISE_PLATFORM%\bin\libcurl.dll

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-16-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-16-0 http://www.eiffel.com/developers/xml/configuration-1-16-0.xsd" name="client" uuid="D0059CEB-5F5C-4D21-8C71-842BD0F88468">
<target name="client">
<root class="RESTBUCK_CLIENT" feature="make"/>
<file_rule>
<exclude>/\.git$</exclude>
<exclude>/\.svn$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true">
</option>
<capability>
<concurrency support="thread" use="thread"/>
<void_safety support="all"/>
</capability>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="http_client" location="..\..\..\..\..\library\network\http_client\http_client.ecf" readonly="false"/>
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json.ecf"/>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread.ecf"/>
<cluster name="src" location=".\src\" recursive="true"/>
</target>
</system>

View File

@@ -1,154 +0,0 @@
note
description : "Objects that ..."
author : "$Author$"
date : "$Date$"
revision : "$Revision$"
class
RESTBUCK_CLIENT
create
make
feature {NONE} -- Initialization
make
-- Initialize `Current'.
local
h: LIBCURL_HTTP_CLIENT
sess: HTTP_CLIENT_SESSION
resp : detachable HTTP_CLIENT_RESPONSE
l_location : detachable READABLE_STRING_8
body : STRING
do
create h.make
sess := h.new_session ("http://127.0.0.1:9090")
-- Uncomment the following 2 lines, if you use fiddler2 web debugging tool
-- sess.set_is_debug (True)
-- sess.set_proxy ("127.0.0.1", 8888)
-- Create Order
print ("%N Create Order %N")
resp := create_order (sess)
-- Read the Order
print ("%N Read Order %N")
l_location := resp.header ("Location")
resp := read_order (sess, l_location)
-- Update the Order
if resp /= Void and then attached resp.body as l_body then
body := l_body.as_string_8
body.replace_substring_all ("takeAway", "in Shop")
print ("%N Update Order %N")
resp := update_order (sess, l_location, body)
end
end
update_order ( sess: HTTP_CLIENT_SESSION; uri : detachable READABLE_STRING_8; a_body : STRING): detachable HTTP_CLIENT_RESPONSE
local
context : HTTP_CLIENT_REQUEST_CONTEXT
do
if attached uri as l_uri then
sess.set_base_url (l_uri)
create context.make
context.headers.put ("application/json", "Content-Type")
Result := sess.put ("", context, a_body )
-- Show headers
across
Result.headers as l_headers
loop
print (l_headers.item.name)
print (":")
print (l_headers.item.value)
io.put_new_line
end
-- Show body
print (Result.body)
io.put_new_line
end
end
read_order ( sess: HTTP_CLIENT_SESSION; uri : detachable READABLE_STRING_8): detachable HTTP_CLIENT_RESPONSE
do
if attached uri as l_uri then
sess.set_base_url (l_uri)
Result := sess.get ("", Void)
-- Show headers
across
Result.headers as l_headers
loop
print (l_headers.item.name)
print (":")
print (l_headers.item.value)
io.put_new_line
end
-- Show body
print (Result.body)
io.put_new_line
end
end
create_order (sess: HTTP_CLIENT_SESSION) : HTTP_CLIENT_RESPONSE
local
s: READABLE_STRING_8
j: JSON_PARSER
id: detachable STRING
context : HTTP_CLIENT_REQUEST_CONTEXT
do
s := "[
{
"location":"takeAway",
"items":[
{
"name":"Late",
"option":"skim",
"size":"Small",
"quantity":1
}
]
}
]"
create context.make
context.headers.put ("application/json", "Content-Type")
Result := sess.post ("/order", context, s)
-- Show the Headers
across
Result.headers as l_headers
loop
print (l_headers.item.name)
print (":")
print (l_headers.item.value)
io.put_new_line
end
-- Show the Response body
if attached Result.body as m then
create j.make_with_string (m)
j.parse_content
if j.is_valid and then attached j.parsed_json_object as j_o then
if attached {JSON_STRING} j_o.item ("id") as l_id then
id := l_id.item
end
print (m)
io.put_new_line
end
end
end
feature {NONE} -- Implementation
invariant
-- invariant_clause: True
end

View File

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

View File

@@ -1,297 +0,0 @@
Restbuck Eiffel Implementation based on the book of REST in Practice
====================================================================
This is an implementation of CRUD pattern for manipulate resources, this is the first step to use
the HTTP protocol as an application protocol instead of a transport protocol.
Restbuck Protocol
-----------------
<table>
<TR><TH>Verb</TH> <TH>URI or template</TH> <TH>Use</TH></TR>
<TR><TD>POST</TD> <TD>/order</TD> <TD>Create a new order, and upon success, receive a Locationheader specifying the new order's URI.</TD></TR>
<TR><TD>GET</TD> <TD>/order/{orderId}</TD> <TD>Request the current state of the order specified by the URI.</TD></TR>
<TR><TD>PUT</TD> <TD>/order/{orderId}</TD> <TD>Update an order at the given URI with new information, providing the full representation.</TD></TR>
<TR><TD>DELETE</TD> <TD>/order/{orderId}</TD> <TD>Logically remove the order identified by the given URI.</TD></TR>
</table>
Resource Represenation
----------------------
The previous tables shows a contrat, the URI or URI template, allows us to indentify resources, now we will chose a
representacion, for this particular case we will use JSON.
Note: <br/>
1. *A resource can have multiple URIs*.<br/>
2. *A resource can have multiple Representations*.<br/>
RESTBUCKS_SERVER
----------------
This class implement the main entry of our REST CRUD service, we are using a default connector (Nino Connector,
using a WebServer written in Eiffel).
We are inheriting from URI_TEMPLATE_ROUTED_SERVICE, this allows us to map our service contrat, as is shown in the previous
table, the mapping is defined in the feature setup_router, this also show that the class ORDER_HANDLER will be encharge
of to handle different type of request to the ORDER resource.
class
RESTBUCKS_SERVER
inherit
ANY
URI_TEMPLATE_ROUTED_SERVICE
DEFAULT_SERVICE
-- Here we are using a default connector using the default Nino Connector,
-- but it's possible to use other connector (CGI or FCGI).
create
make
feature {NONE} -- Initialization
make
-- Initialize the router (this will have the request handler and
-- their context).
do
initialize_router
make_and_launch
end
create_router
do
create router.make (2)
end
setup_router
local
order_handler: ORDER_HANDLER [REQUEST_URI_TEMPLATE_HANDLER_CONTEXT]
do
create order_handler
router.map_with_request_methods ("/order", order_handler, <<"POST">>)
router.map_with_request_methods ("/order/{orderid}", order_handler, <<"GET", "DELETE", "PUT">>)
end
feature -- Execution
execute_default (req: WSF_REQUEST; res: WSF_RESPONSE)
-- I'm using this method to handle the method not allowed response
-- in the case that the given uri does not have a corresponding http method
-- to handle it.
local
h : HTTP_HEADER
l_description : STRING
l_api_doc : STRING
do
if req.content_length_value > 0 then
req.input.read_string (req.content_length_value.as_integer_32)
end
create h.make
h.put_status ({HTTP_STATUS_CODE}.method_not_allowed)
h.put_content_type_text_plain
l_api_doc := "%NPlease check the API%NURI:/order METHOD: POST%NURI:/order/{orderid} METHOD: GET, PUT, DELETE%N"
l_description := req.request_method + req.request_uri + " is not allowed" + "%N" + l_api_doc
h.put_content_length (l_description.count)
h.put_current_date
res.set_status_code ({HTTP_STATUS_CODE}.method_not_allowed)
res.write_header_text (h.string)
res.write_string (l_description)
end
end
How to Create an order with POST
--------------------------------
Here is the convention that we are using:
POST is used for creation and the server determines the URI of the created resource.
If the request POST is SUCCESS, the server will create the order and will response with
201 CREATED, the Location header will contains the newly created order's URI,
if the request POST is not SUCCESS, the server will response with
400 BAD REQUEST, the client send a bad request or
500 INTERNAL_SERVER_ERROR, when the server can deliver the request.
POST /order HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Content-Length: 196
Origin: chrome-extension://fhjcajmcbmldlhcimfajhfbgofnpcjmb
Content-Type: application/json
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: es-419,es;q=0.8,en;q=0.6
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
{
"location":"takeAway",
"items":[
{
"name":"Late",
"option":"skim",
"size":"Small",
"quantity":1
}
]
}
Response success
HTTP/1.1 201 Created
Status 201 Created
Content-Type application/json
Content-Length 123
Location http://localhost:8080/order/1
Date FRI,09 DEC 2011 20:34:20.00 GMT
{
"location" : "takeAway",
"status" : "submitted",
"items" : [ {
"name" : "late",
"size" : "small",
"quantity" : 1,
"option" : "skim"
} ]
}
note:
curl -vv http://localhost:9090/order -H "Content-Type: application/json" -d "{\"location\":\"takeAway\",\"items\":[{\"name\":\"Late\",\"option\":\"skim\",\"size\":\"Small\",\"quantity\":1}]}" -X POST
How to Read an order with GET
-----------------------------
Using GET to retrieve resource information.
If the GET request is SUCCESS, we response with 200 OK, and a representation of the order
If the GET request is not SUCCESS, we response with 404 Resource not found
If is a Conditional GET and the resource does not change we send a 304, Resource not modifed
GET /order/1 HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: es-419,es;q=0.8,en;q=0.6
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
If-None-Match: 6542EF270D91D3EAF39CFB382E4CEBA7
Response
HTTP/1.1 200 OK
Status 200 OK
Content-Type application/json
Content-Length 123
Date FRI,09 DEC 2011 20:53:46.00 GMT
etag 2ED3A40954A95D766FC155682DC8BB52
{
"location" : "takeAway",
"status" : "submitted",
"items" : [ {
"name" : "late",
"size" : "small",
"quantity" : 1,
"option" : "skim"
} ]
}
note:
curl -vv http://localhost:9090/order/1
How to Update an order with PUT
-------------------------------
A successful PUT request will not create a new resource, instead it will change the state of the resource identified by the current uri.
If success we response with 200 and the updated order.
404 if the order is not found
400 in case of a bad request
500 internal server error
If the request is a Conditional PUT, and it does not mat we response 415, precondition failed.
Suposse that we had created an Order with the values shown in the _How to create an order with POST_
But we change our decision and we want to stay in the shop.
PUT /order/1 HTTP/1.1
Content-Length: 122
Content-Type: application/json; charset=UTF-8
Host: localhost:8080
Connection: Keep-Alive
Expect: 100-Continue
{
"location" : "in shop",
"status" : "submitted",
"items" : [ {
"name" : "late",
"size" : "small",
"quantity" : 1,
"option" : "skim"
} ]
}
Response success
HTTP/1.1 200 OK
Status 200 OK
Content-Type application/json
Date FRI,09 DEC 2011 21:06:26.00 GMT
etag 8767F900674B843E1F3F70BCF3E62403
Content-Length 122
{
"location" : "in shop",
"status" : "submitted",
"items" : [ {
"name" : "late",
"size" : "small",
"quantity" : 1,
"option" : "skim"
} ]
}
How to Delete an order with DELETE
----------------------------------
Here we use DELETE to cancel an order, if that order is in state where it can still be canceled.
204 if is ok
404 Resource not found
405 if consumer and service's view of the resouce state is inconsisent
500 if we have an internal server error
DELETE /order/1 HTTP/1.1
Host: localhost:8080
Connection: Keep-Alive
Response success
HTTP/1.1 204 No Content
Status 204 No Content
Content-Type application/json
Date FRI,09 DEC 2011 21:10:51.00 GMT
If we want to check that the resource does not exist anymore we can try to retrieve a GET /order/1 and we will receive a
404 No Found
GET /order/1 HTTP/1.1
Host: localhost:8080
Connection: Keep-Alive
Response
HTTP/1.1 404 Not Found
Status 404 Not Found
Content-Type application/json
Content-Length 44
Date FRI,09 DEC 2011 21:14:17.79 GMT
The following resource/order/1 is not found
References
----------
1. [How to get a cup of coffe](http://www.infoq.com/articles/webber-rest-workflow)
2. [Rest in Practice] (http://restinpractice.com/default.aspx)

View File

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

View File

@@ -1,26 +0,0 @@
note
description: "Summary description for {DATABASE_API}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
DATABASE_API
create
make
feature -- Initialization
make
do
create orders.make (10)
end
feature -- Access
orders: HASH_TABLE [ORDER, STRING]
;note
copyright: "2011-2012, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,19 +0,0 @@
note
description: "Summary description for {SHARED_DATABASE_API}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
SHARED_DATABASE_API
feature -- Access
db_access: DATABASE_API
once
create Result.make
end
note
copyright: "2011-2012, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,90 +0,0 @@
note
description: "Summary description for {ITEM}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
ITEM
inherit
ITEM_CONSTANTS
create
make
feature -- Initialization
make ( a_name : STRING_32 ; a_size:STRING_32; a_option: STRING_32; a_quantity:INTEGER_8)
do
set_name (a_name)
set_size (a_size)
set_option (a_option)
set_quantity (a_quantity)
end
feature -- Access
name : STRING
-- product name type of Coffee(Late, Cappuccino, Expresso)
option : STRING
-- customization option Milk (skim, semi, whole)
size : STRING
-- small, mediumm large
quantity :INTEGER
feature -- Element Change
set_name (a_name: STRING)
require
valid_name: is_valid_coffee_type (a_name)
do
name := a_name
ensure
name_assigned : name.same_string(a_name)
end
set_size (a_size: STRING)
require
valid_size : is_valid_size_option (a_size)
do
size := a_size
ensure
size_assigned : size.same_string(a_size)
end
set_option (an_option: STRING)
require
valid_option : is_valid_milk_type (an_option)
do
option := an_option
ensure
option_assigned : option.same_string (an_option)
end
set_quantity (a_quantity: INTEGER)
require
valid_quantity : a_quantity > 0
do
quantity := a_quantity
ensure
quantity_assigned : quantity = a_quantity
end
feature -- Report
hash_code: INTEGER
--Hash code value
do
Result := option.hash_code + name.hash_code + size.hash_code + quantity.hash_code
end
invariant
valid_size : is_valid_size_option (size)
valid_coffe : is_valid_coffee_type (name)
valid_customization : is_valid_milk_type (option)
valid_quantity : quantity > 0
note
copyright: "2011-2012, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,54 +0,0 @@
note
description: "Summary description for {ITEM_CONSTANTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
ITEM_CONSTANTS
feature -- Access
is_valid_coffee_type (a_type: STRING) : BOOLEAN
--is `a_type' a valid coffee type
do
a_type.to_lower
coffe_types.compare_objects
Result := coffe_types.has (a_type)
end
Coffe_types : ARRAY[STRING]
-- List of valid Coffee types
once
Result := <<"late","cappuccino", "expresso">>
end
is_valid_milk_type (a_type: STRING) : BOOLEAN
--is `a_type' a valid milk type
do
a_type.to_lower
milk_types.compare_objects
Result := milk_types.has (a_type)
end
Milk_types : ARRAY[STRING]
-- List of valid Milk types
once
Result := <<"skim","semi", "whole">>
end
is_valid_size_option (an_option: STRING) : BOOLEAN
--is `an_option' a valid size option
do
an_option.to_lower
size_options.compare_objects
Result := size_options.has (an_option)
end
Size_options : ARRAY[STRING]
-- List of valid Size_options
once
Result := <<"small","mediumn", "large">>
end
note
copyright: "2011-2012, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,176 +0,0 @@
note
description: "Summary description for {JSON_ORDER_CONVERTER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
JSON_ORDER_CONVERTER
inherit
JSON_CONVERTER
create
make
feature -- Initialization
make
do
create object.make ("","","")
end
feature -- Access
object : ORDER
value : detachable JSON_OBJECT
feature -- Conversion
from_json (j: attached like value): detachable like object
-- Convert from JSON value. Returns Void if unable to convert
local
s_id, s_location, s_status: detachable STRING_32
q: INTEGER_8
o: ORDER
i : ITEM
l_array : detachable ARRAYED_LIST [JSON_VALUE]
is_valid_from_json : BOOLEAN
do
is_valid_from_json := True
if attached {STRING_32} json.object (j.item (id_key), Void) as l_id then
s_id := s_id
end
if attached {STRING_32} json.object (j.item (location_key), Void) as l_location then
s_location := l_location
end
if attached {STRING_32} json.object (j.item (status_key), Void) as l_status then
s_status := l_status
end
create o.make ("", s_location, s_status)
if attached {JSON_ARRAY} j.item (items_key) as l_val then
l_array := l_val.array_representation
from
l_array.start
until
l_array.after
loop
if attached {JSON_OBJECT} l_array.item_for_iteration as jv then
if attached {INTEGER_8} json.object (jv.item (quantity_key), Void) as l_integer then
q := l_integer
else
q := 0
end
if
attached {STRING_32} json.object (jv.item (name_key), Void) as s_name and then
attached {STRING_32} json.object (jv.item (size_key), Void) as s_key and then
attached {STRING_32} json.object (jv.item (option_key), Void) as s_option
then
if is_valid_item_customization (s_name, s_key, s_option,q) then
create i.make (s_name, s_key, s_option, q)
o.add_item (i)
else
is_valid_from_json := False
end
else
is_valid_from_json := False
end
end
l_array.forth
end
end
if not is_valid_from_json or o.items.is_empty then
Result := Void
else
Result := o
end
end
to_json (o: like object): like value
-- Convert to JSON value
local
ja : JSON_ARRAY
i : ITEM
jv: JSON_OBJECT
do
create Result.make
-- Result.put (json.value (o.id), id_key)
Result.put (json.value (o.location), location_key)
Result.put (json.value (o.status), status_key)
from
create ja.make_empty
o.items.start
until
o.items.after
loop
i := o.items.item_for_iteration
create jv.make
jv.put (json.value (i.name), name_key)
jv.put (json.value (i.size),size_key)
jv.put (json.value (i.quantity), quantity_key)
jv.put (json.value (i.option), option_key)
ja.add (jv)
o.items.forth
end
Result.put (ja, items_key)
end
feature {NONE} -- Implementation
id_key: JSON_STRING
once
create Result.make_from_string ("id")
end
location_key: JSON_STRING
once
create Result.make_from_string ("location")
end
status_key: JSON_STRING
once
create Result.make_from_string ("status")
end
items_key : JSON_STRING
once
create Result.make_from_string ("items")
end
name_key : JSON_STRING
once
create Result.make_from_string ("name")
end
size_key : JSON_STRING
once
create Result.make_from_string ("size")
end
quantity_key : JSON_STRING
once
create Result.make_from_string ("quantity")
end
option_key : JSON_STRING
once
create Result.make_from_string ("option")
end
feature -- Validation
is_valid_item_customization ( name : STRING_32; size: STRING_32; option : STRING_32; quantity : INTEGER_8 ) : BOOLEAN
local
ic : ITEM_CONSTANTS
do
create ic
Result := ic.is_valid_coffee_type (name) and ic.is_valid_milk_type (option) and ic.is_valid_size_option (size) and quantity > 0
end
note
copyright: "2011-2015, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,114 +0,0 @@
note
description: "Summary description for {ORDER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
ORDER
create
make
feature -- Initialization
make ( an_id : detachable STRING_32; a_location: detachable STRING_32; a_status: detachable STRING_32)
do
create {ARRAYED_LIST [ITEM]} items.make (10)
if an_id /= Void then
set_id (an_id)
else
set_id ("")
end
if a_location /= Void then
set_location (a_location)
else
set_location ("")
end
if a_status /= Void then
set_status (a_status)
else
set_status ("")
end
revision := 0
end
feature -- Access
id : STRING_32
location : STRING_32
items: LIST[ITEM]
status : STRING_32
revision : INTEGER
feature -- element change
set_id (an_id : STRING_32)
do
id := an_id
ensure
id_assigned : id.same_string (an_id)
end
set_location (a_location : STRING_32)
do
location := a_location
ensure
location_assigned : location.same_string (a_location)
end
set_status (a_status : STRING_32)
do
status := a_status
ensure
status_asigned : status.same_string (a_status)
end
add_item (a_item : ITEM)
require
valid_item: a_item /= Void
do
items.force (a_item)
ensure
has_item : items.has (a_item)
end
add_revision
do
revision := revision + 1
ensure
revision_incremented : old revision + 1 = revision
end
feature -- Etag
etag : STRING_32
-- Etag generation for Order objects
do
Result := hash_code.out + revision.out
end
feature -- Output
feature -- Report
hash_code: INTEGER_32
-- Hash code value
do
from
items.start
Result := items.item.hash_code
until
items.off
loop
Result:= ((Result \\ 8388593) |<< 8) + items.item.hash_code
items.forth
end
if items.count > 1 then
Result := Result \\ items.count
end
end
note
copyright: "2011-2012, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,56 +0,0 @@
note
description: "Summary description for {ORDER_TRANSITIONS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
ORDER_VALIDATION
feature -- Access
is_valid_status_state (a_status: STRING) : BOOLEAN
--is `a_status' a valid coffee order state
do
a_status.to_lower
Order_states.compare_objects
Result := Order_states.has (a_status)
end
Order_states : ARRAY[STRING]
-- List of valid status states
once
Result := <<"submitted","pay","payed", "cancel","canceled","prepare","prepared","deliver","completed">>
end
is_valid_transition (order:ORDER a_status : STRING) :BOOLEAN
-- Given the current order state, determine if the transition is valid
do
a_status.to_lower
if order.status.same_string ("submitted") then
Result := a_status.same_string ("pay") or a_status.same_string ("cancel") or order.status.same_string (a_status)
elseif order.status.same_string ("pay") then
Result := a_status.same_string ("payed") or order.status.same_string (a_status)
elseif order.status.same_string ("cancel") then
Result := a_status.same_string ("canceled") or order.status.same_string (a_status)
elseif order.status.same_string ("payed") then
Result := a_status.same_string ("prepared") or order.status.same_string (a_status)
elseif order.status.same_string ("prepared") then
Result := a_status.same_string ("deliver") or order.status.same_string (a_status)
elseif order.status.same_string ("deliver") then
Result := a_status.same_string ("completed") or order.status.same_string (a_status)
end
end
is_state_valid_to_update ( a_status : STRING) : BOOLEAN
-- Given the current state `a_status' of an order, is possible to update the order?
do
if a_status.same_string ("submitted") or else a_status.same_string ("pay") or else a_status.same_string ("payed") then
Result := true
end
end
note
copyright: "2011-2012, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,19 +0,0 @@
note
description: "Summary description for {SHARED_ORDER_VALIDATION}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
SHARED_ORDER_VALIDATION
feature
order_validation : ORDER_VALIDATION
once
create Result
end
note
copyright: "2011-2012, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,574 +0,0 @@
note
description: "{ORDER_HANDLER} handle the resources that we want to expose"
author: ""
date: "$Date$"
revision: "$Revision$"
class ORDER_HANDLER
inherit
WSF_SKELETON_HANDLER
SHARED_DATABASE_API
SHARED_EJSON
REFACTORING_HELPER
SHARED_ORDER_VALIDATION
WSF_RESOURCE_HANDLER_HELPER
rename
execute_options as helper_execute_options,
handle_internal_server_error as helper_handle_internal_server_error
end
create
make_with_router
feature -- Execution variables
Order_execution_variable: STRING = "ORDER"
-- Execution variable used by application
Generated_content_execution_variable: STRING = "GENERATED_CONTENT"
-- Execution variable used by application
Extracted_order_execution_variable: STRING = "EXTRACTED_ORDER"
-- Execution variable used by application
feature -- Documentation
description: READABLE_STRING_GENERAL
-- General description for self-generated documentation;
-- The specific URI templates supported will be described automatically
do
Result := "Create, Read, Update or Delete an ORDER."
end
feature -- Access
is_chunking (req: WSF_REQUEST): BOOLEAN
-- Will the response to `req' using chunked transfer encoding?
do
-- No.
end
includes_response_entity (req: WSF_REQUEST): BOOLEAN
-- Does the response to `req' include an entity?
-- Method will be DELETE, POST, PUT or an extension method.
do
Result := False
-- At present, there is no support for this except for DELETE.
end
conneg (req: WSF_REQUEST): SERVER_CONTENT_NEGOTIATION
-- Content negotiatior for all requests
once
create Result.make ({HTTP_MIME_TYPES}.application_json, "en", "UTF-8", "identity")
end
mime_types_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept header that `Current' can serve
do
create {ARRAYED_LIST [STRING]} Result.make_from_array (<<{HTTP_MIME_TYPES}.application_json>>)
Result.compare_objects
end
languages_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept-Language header that `Current' can serve
do
create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"en">>)
Result.compare_objects
end
charsets_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept-Charset header that `Current' can serve
do
create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"UTF-8">>)
Result.compare_objects
end
encodings_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept-Encoding header that `Current' can serve
do
create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"identity">>)
Result.compare_objects
end
max_age (req: WSF_REQUEST): NATURAL
-- Maximum age in seconds before response to `req` is considered stale;
-- This is used to generate a Cache-Control: max-age header.
-- Return 0 to indicate already expired.
-- Return Never_expires to indicate never expires.
do
-- All our responses are considered stale.
end
is_freely_cacheable (req: WSF_REQUEST): BOOLEAN
-- Should the response to `req' be freely cachable in shared caches?
-- If `True', then a Cache-Control: public header will be generated.
do
-- definitely not!
end
private_headers (req: WSF_REQUEST): detachable LIST [READABLE_STRING_8]
-- Header names intended for a single user.
-- If non-Void, then a Cache-Control: private header will be generated.
-- Returning an empty list prevents the entire response from being served from a shared cache.
do
create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (0)
end
non_cacheable_headers (req: WSF_REQUEST): detachable LIST [READABLE_STRING_8]
-- Header names that will not be sent from a cache without revalidation;
-- If non-Void, then a Cache-Control: no-cache header will be generated.
-- Returning an empty list prevents the response being served from a cache
-- without revalidation.
do
create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (0)
end
is_sensitive (req: WSF_REQUEST): BOOLEAN
-- Is the response to `req' of a sensitive nature?
-- If `True' then a Cache-Control: no-store header will be generated.
do
Result := True
-- since it's commercial data.
end
allowed_cross_origins (req: WSF_REQUEST): detachable STRING
-- Value for Access-Control-Allow-Origin header;
-- If supplied, should be a single URI, or the values "*" or "null".
-- This is currently supported only for GET requests, and POSTs that functions as GET.
do
if req.is_get_head_request_method then
Result := "*"
end
end
matching_etag (req: WSF_REQUEST; a_etag: READABLE_STRING_32; a_strong: BOOLEAN): BOOLEAN
-- Is `a_etag' a match for resource requested in `req'?
-- If `a_strong' then the strong comparison function must be used.
local
l_id: STRING
l_etag_util: ETAG_UTILS
do
l_id := order_id_from_request (req)
if db_access.orders.has_key (l_id) then
check attached db_access.orders.item (l_id) as l_order then
-- postcondition of `has_key'
create l_etag_util
Result := a_etag.same_string (l_etag_util.md5_digest (l_order.out).as_string_32)
end
end
end
etag (req: WSF_REQUEST): detachable READABLE_STRING_8
-- Optional Etag for `req' in the requested variant
local
l_etag_utils: ETAG_UTILS
do
create l_etag_utils
if attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then
Result := l_etag_utils.md5_digest (l_order.out)
end
end
last_modified (req: WSF_REQUEST): detachable DATE_TIME
-- When representation of resource selected in `req' was last modified;
-- SHOULD be set whenever it can reasonably be determined.
do
end
modified_since (req: WSF_REQUEST; a_date_time: DATE_TIME): BOOLEAN
-- Has resource requested in `req' been modified since `a_date_time' (UTC)?
do
-- We don't track this information. It is safe to always say yes.
Result := True
end
feature -- Measurement
content_length (req: WSF_REQUEST): NATURAL
-- Length of entity-body of the response to `req'
do
check attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) as l_response then
-- postcondition generated_content_set_for_get_head of `ensure_content_available'
-- We only call this for GET/HEAD in this example.
Result := l_response.count.as_natural_32
end
end
allow_post_to_missing_resource (req: WSF_REQUEST): BOOLEAN
-- The resource named in `req' does not exist, and this is a POST. Do we allow it?
do
-- No.
end
feature -- Status report
finished (req: WSF_REQUEST): BOOLEAN
-- Has the last chunk been generated for `req'?
do
-- precondition is never met
end
feature -- Execution
check_resource_exists (req: WSF_REQUEST; a_helper: WSF_METHOD_HELPER)
-- Call `a_helper.set_resource_exists' to indicate that `req.path_translated'
-- is the name of an existing resource.
-- We also put the order into `req.execution_variable (Order_execution_variable)' for GET or HEAD responses.
local
l_id: STRING
do
if req.is_post_request_method then
a_helper.set_resource_exists
-- because only /order is defined to this handler for POST
else
-- the request is of the form /order/{orderid}
l_id := order_id_from_request (req)
if db_access.orders.has_key (l_id) then
a_helper.set_resource_exists
if req.is_get_head_request_method then
check attached db_access.orders.item (l_id) as l_order then
-- postcondition `item_if_found' of `has_key'
req.set_execution_variable (Order_execution_variable, l_order)
end
end
end
end
ensure then
order_saved_only_for_get_head: attached {ORDER} req.execution_variable (Order_execution_variable) implies req.is_get_head_request_method
end
feature -- GET/HEAD content
ensure_content_available (req: WSF_REQUEST)
-- Commence generation of response text (entity-body).
-- If not chunked, then this will create the entire entity-body so as to be available
-- for a subsequent call to `content'.
-- If chunked, only the first chunk will be made available to `next_chunk'. If chunk extensions
-- are used, then this will also generate the chunk extension for the first chunk.
-- We save the text in `req.execution_variable (Generated_content_execution_variable)'
-- We ignore the results of content negotiation, as there is only one possible combination.
do
check attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then
-- precondition get_or_head and postcondition order_saved_only_for_get_head of `check_resource_exists' and
if attached {JSON_VALUE} json.value (l_order) as jv then
req.set_execution_variable (Generated_content_execution_variable, jv.representation)
else
req.set_execution_variable (Generated_content_execution_variable, "")
end
end
ensure then
generated_content_set_for_get_head: req.is_get_head_request_method implies
attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable)
end
content (req: WSF_REQUEST): READABLE_STRING_8
-- Non-chunked entity body in response to `req';
-- We only call this for GET/HEAD in this example.
do
check attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) as l_response then
-- postcondition generated_content_set_for_get_head of `ensure_content_available'
Result := l_response
end
end
next_chunk (req: WSF_REQUEST): TUPLE [a_chunk: READABLE_STRING_8; a_extension: detachable READABLE_STRING_8]
-- Next chunk of entity body in response to `req';
-- The second field of the result is an optional chunk extension.
do
-- precondition `is_chunking' is never met, but we need a dummy `Result'
-- to satisfy the compiler in void-safe mode
Result := ["", Void]
end
generate_next_chunk (req: WSF_REQUEST)
-- Prepare next chunk (including optional chunk extension) of entity body in response to `req'.
-- This is not called for the first chunk.
do
-- precondition `is_chunking' is never met
end
feature -- DELETE
delete (req: WSF_REQUEST)
-- Delete resource named in `req' or set an error on `req.error_handler'.
local
l_id: STRING
do
l_id := order_id_from_request (req)
if db_access.orders.has_key (l_id) then
if is_valid_to_delete (l_id) then
delete_order (l_id)
else
req.error_handler.add_custom_error ({HTTP_STATUS_CODE}.method_not_allowed, "DELETE not valid",
"There is conflict while trying to delete the order, the order could not be deleted in the current state")
end
else
req.error_handler.add_custom_error ({HTTP_STATUS_CODE}.not_found, "DELETE not valid",
"There is no such order to delete")
end
end
delete_queued (req: WSF_REQUEST): BOOLEAN
-- Has resource named by `req' been queued for deletion?
do
-- No
end
feature -- PUT/POST
is_entity_too_large (req: WSF_REQUEST): BOOLEAN
-- Is the entity stored in `req.execution_variable (Request_entity_execution_variable)' too large for the application?
do
-- No. We don't care for this example.
end
check_content_headers (req: WSF_REQUEST)
-- Check we can support all content headers on request entity.
-- Set `req.execution_variable (Content_check_code_execution_variable)' to {NATURAL} zero if OK, or 415 or 501 if not.
do
-- We don't bother for this example. Note that this is equivalent to setting zero.
end
create_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Create new resource in response to a PUT request when `check_resource_exists' returns `False'.
-- Implementor must set error code of 200 OK or 500 Server Error.
do
-- We don't support creating a new resource with PUT. But this can't happen
-- with our router mappings, so we don't bother to set a 500 response.
end
append_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Create new resource in response to a POST request.
-- Implementor must set error code of 200 OK or 204 No Content or 303 See Other or 500 Server Error.
do
if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then
save_order (l_order)
compute_response_post (req, res, l_order)
else
handle_bad_request_response ("Not a valid order", req, res)
end
end
check_conflict (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Check we can support all content headers on request entity.
-- Set `req.execution_variable (Conflict_check_code_execution_variable)' to {NATURAL} zero if OK, or 409 if not.
-- In the latter case, write the full error response to `res'.
do
if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then
if not is_valid_to_update (l_order) then
req.set_execution_variable (Conflict_check_code_execution_variable, {NATURAL} 409)
handle_resource_conflict_response (l_order.out +"%N There is conflict while trying to update the order, the order could not be update in the current state", req, res)
end
else
req.set_execution_variable (Conflict_check_code_execution_variable, {NATURAL} 409)
--| This ought to be a 500, as if attached should probably be check attached. But as yet I lack a proof.
handle_resource_conflict_response ("There is conflict while trying to update the order, the order could not be update in the current state", req, res)
end
end
check_request (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Check that the request entity is a valid request.
-- The entity is available as `req.execution_variable (Conflict_check_code_execution_variable)'.
-- Set `req.execution_variable (Request_check_code_execution_variable)' to {NATURAL} zero if OK, or 400 if not.
-- In the latter case, write the full error response to `res'.
local
l_order: detachable ORDER
l_id: STRING
do
if attached {READABLE_STRING_8} req.execution_variable (Request_entity_execution_variable) as l_request then
l_order := extract_order_request (l_request)
if req.is_put_request_method then
l_id := order_id_from_request (req)
if l_order /= Void and then db_access.orders.has_key (l_id) then
l_order.set_id (l_id)
req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 0)
req.set_execution_variable (Extracted_order_execution_variable, l_order)
else
req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 400)
handle_bad_request_response (l_request +"%N is not a valid ORDER, maybe the order does not exist in the system", req, res)
end
else
req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 0)
req.set_execution_variable (Extracted_order_execution_variable, l_order)
end
else
req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 400)
handle_bad_request_response ("Request is not a valid ORDER", req, res)
end
end
update_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Perform the update requested in `req'.
-- Write a response to `res' with a code of 204 or 500.
do
if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then
update_order (l_order)
compute_response_put (req, res, l_order)
else
handle_internal_server_error (res)
end
end
feature -- HTTP Methods
compute_response_put (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
local
h: HTTP_HEADER
joc : JSON_ORDER_CONVERTER
etag_utils : ETAG_UTILS
do
create h.make
create joc.make
create etag_utils
json.add_converter(joc)
create h.make
h.put_content_type_application_json
if attached req.request_time as time then
h.add_header ("Date:" +time.formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT")
end
h.add_header ("etag:" + etag_utils.md5_digest (l_order.out))
if attached {JSON_VALUE} json.value (l_order) as jv then
h.put_content_length (jv.representation.count)
res.set_status_code ({HTTP_STATUS_CODE}.ok)
res.put_header_text (h.string)
res.put_string (jv.representation)
end
end
compute_response_post (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
local
h: HTTP_HEADER
l_msg : STRING
l_location : STRING
joc : JSON_ORDER_CONVERTER
do
create h.make
create joc.make
json.add_converter(joc)
h.put_content_type_application_json
if attached {JSON_VALUE} json.value (l_order) as jv then
l_msg := jv.representation
h.put_content_length (l_msg.count)
if attached req.http_host as host then
l_location := "http://" + host + req.request_uri + "/" + l_order.id
h.put_location (l_location)
end
if attached req.request_time as time then
h.put_utc_date (time)
end
res.set_status_code ({HTTP_STATUS_CODE}.created)
res.put_header_text (h.string)
res.put_string (l_msg)
end
end
feature {NONE} -- URI helper methods
order_id_from_request (req: WSF_REQUEST): STRING
-- Value of "orderid" template URI variable in `req'
require
req_attached: req /= Void
do
if attached {WSF_VALUE} req.path_parameter ("orderid") as l_value then
Result := l_value.as_string.value.as_string_8
else
Result := ""
end
end
feature {NONE} -- Implementation Repository Layer
retrieve_order ( id : STRING) : detachable ORDER
-- get the order by id if it exist, in other case, Void
do
Result := db_access.orders.item (id)
end
save_order (an_order: ORDER)
-- save the order to the repository
local
i : INTEGER
do
from
i := 1
until
not db_access.orders.has_key ((db_access.orders.count + i).out)
loop
i := i + 1
end
an_order.set_id ((db_access.orders.count + i).out)
an_order.set_status ("submitted")
an_order.add_revision
db_access.orders.force (an_order, an_order.id)
end
is_valid_to_delete ( an_id : STRING) : BOOLEAN
-- Is the order identified by `an_id' in a state whre it can still be deleted?
do
if attached retrieve_order (an_id) as l_order then
if order_validation.is_state_valid_to_update (l_order.status) then
Result := True
end
end
end
is_valid_to_update (an_order: ORDER) : BOOLEAN
-- Check if there is a conflict while trying to update the order
do
if attached retrieve_order (an_order.id) as l_order then
if order_validation.is_state_valid_to_update (l_order.status) and then order_validation.is_valid_status_state (an_order.status) and then
order_validation.is_valid_transition (l_order, an_order.status) then
Result := True
end
end
end
update_order (an_order: ORDER)
-- update the order to the repository
do
an_order.add_revision
db_access.orders.force (an_order, an_order.id)
end
delete_order (an_order: STRING)
-- update the order to the repository
do
db_access.orders.remove (an_order)
end
extract_order_request (l_post : STRING) : detachable ORDER
-- extract an object Order from the request, or Void
-- if the request is invalid
local
parser : JSON_PARSER
joc : JSON_ORDER_CONVERTER
do
create joc.make
json.add_converter(joc)
create parser.make_with_string (l_post)
parser.parse_content
if parser.is_valid and then attached parser.parsed_json_value as jv then
if attached {like extract_order_request} json.object (jv, "ORDER") as res then
Result := res
end
end
end
note
copyright: "2011-2015, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,403 +0,0 @@
note
description: "{ORDER_HANDLER} handle the resources that we want to expose"
author: ""
date: "$Date$"
revision: "$Revision$"
class ORDER_HANDLER
inherit
WSF_URI_TEMPLATE_HANDLER
WSF_RESOURCE_HANDLER_HELPER
redefine
do_get,
do_post,
do_put,
do_delete
end
SHARED_DATABASE_API
SHARED_EJSON
REFACTORING_HELPER
SHARED_ORDER_VALIDATION
WSF_SELF_DOCUMENTED_HANDLER
create
make_with_router
feature {NONE} -- Initialization
make_with_router (a_router: WSF_ROUTER)
-- Initialize `router'.
require
a_router_attached: a_router /= Void
do
router := a_router
ensure
router_aliased: router = a_router
end
feature -- Router
router: WSF_ROUTER
-- Associated router that could be used for advanced strategy
feature -- Execute
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute request handler
do
execute_methods (req, res)
end
feature -- API DOC
api_doc : STRING = "URI:/order METHOD: POST%N URI:/order/{orderid} METHOD: GET, PUT, DELETE%N"
feature -- Documentation
mapping_documentation (m: WSF_ROUTER_MAPPING; a_request_methods: detachable WSF_REQUEST_METHODS): WSF_ROUTER_MAPPING_DOCUMENTATION
do
create Result.make (m)
if a_request_methods /= Void then
if a_request_methods.has_method_post then
Result.add_description ("URI:/order METHOD: POST")
elseif
a_request_methods.has_method_get
or a_request_methods.has_method_put
or a_request_methods.has_method_delete
then
Result.add_description ("URI:/order/{orderid} METHOD: GET, PUT, DELETE")
end
end
end
feature -- HTTP Methods
do_get (req: WSF_REQUEST; res: WSF_RESPONSE)
-- <Precursor>
local
id: STRING
do
if attached req.path_info as l_path_info then
id := get_order_id_from_path (l_path_info)
if attached retrieve_order (id) as l_order then
if is_conditional_get (req, l_order) then
handle_resource_not_modified_response ("The resource" + l_path_info + "does not change", req, res)
else
compute_response_get (req, res, l_order)
end
else
handle_resource_not_found_response ("The following resource" + l_path_info + " is not found ", req, res)
end
end
end
is_conditional_get (req : WSF_REQUEST; l_order : ORDER) : BOOLEAN
-- Check if If-None-Match is present and then if there is a representation that has that etag
-- if the representation hasn't changed, we return TRUE
-- then the response is a 304 with no entity body returned.
local
etag_util : ETAG_UTILS
do
if attached req.meta_string_variable ("HTTP_IF_NONE_MATCH") as if_none_match then
create etag_util
if if_none_match.same_string (etag_util.md5_digest (l_order.out).as_string_32) then
Result := True
end
end
end
compute_response_get (req: WSF_REQUEST; res: WSF_RESPONSE; l_order: ORDER)
local
h: HTTP_HEADER
l_msg : STRING
etag_utils : ETAG_UTILS
do
create h.make
create etag_utils
h.put_content_type_application_json
if attached {JSON_VALUE} json.value (l_order) as jv then
l_msg := jv.representation
h.put_content_length (l_msg.count)
if attached req.request_time as time then
h.add_header ("Date:" + time.formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT")
end
h.add_header ("etag:" + etag_utils.md5_digest (l_order.out))
res.set_status_code ({HTTP_STATUS_CODE}.ok)
res.put_header_text (h.string)
res.put_string (l_msg)
end
end
do_put (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Updating a resource with PUT
-- A successful PUT request will not create a new resource, instead it will
-- change the state of the resource identified by the current uri.
-- If success we response with 200 and the updated order.
-- 404 if the order is not found
-- 400 in case of a bad request
-- 500 internal server error
-- If the request is a Conditional PUT, and it does not mat we response
-- 415, precondition failed.
local
l_put: STRING
l_order : detachable ORDER
id : STRING
do
if attached req.path_info as l_path_info then
id := get_order_id_from_path (l_path_info)
l_put := retrieve_data (req)
l_order := extract_order_request(l_put)
if l_order /= Void and then db_access.orders.has_key (id) then
l_order.set_id (id)
if is_valid_to_update(l_order) then
if is_conditional_put (req, l_order) then
update_order( l_order)
compute_response_put (req, res, l_order)
else
handle_precondition_fail_response ("", req, res)
end
else
--| FIXME: Here we need to define the Allow methods
handle_resource_conflict_response (l_put +"%N There is conflict while trying to update the order, the order could not be update in the current state", req, res)
end
else
handle_bad_request_response (l_put +"%N is not a valid ORDER, maybe the order does not exist in the system", req, res)
end
end
end
is_conditional_put (req : WSF_REQUEST; order : ORDER) : BOOLEAN
-- Check if If-Match is present and then if there is a representation that has that etag
-- if the representation hasn't changed, we return TRUE
local
etag_util : ETAG_UTILS
do
if attached retrieve_order (order.id) as l_order then
if attached req.meta_string_variable ("HTTP_IF_MATCH") as if_match then
create etag_util
if if_match.same_string (etag_util.md5_digest (l_order.out).as_string_32) then
Result := True
end
else
Result := True
end
end
end
compute_response_put (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
local
h: HTTP_HEADER
joc : JSON_ORDER_CONVERTER
etag_utils : ETAG_UTILS
do
create h.make
create joc.make
create etag_utils
json.add_converter(joc)
create h.make
h.put_content_type_application_json
if attached req.request_time as time then
h.add_header ("Date:" +time.formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT")
end
h.add_header ("etag:" + etag_utils.md5_digest (l_order.out))
if attached {JSON_VALUE} json.value (l_order) as jv then
h.put_content_length (jv.representation.count)
res.set_status_code ({HTTP_STATUS_CODE}.ok)
res.put_header_text (h.string)
res.put_string (jv.representation)
end
end
do_delete (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Here we use DELETE to cancel an order, if that order is in state where
-- it can still be canceled.
-- 200 if is ok
-- 404 Resource not found
-- 405 if consumer and service's view of the resouce state is inconsisent
-- 500 if we have an internal server error
local
id: STRING
do
if attached req.path_info as l_path_info then
id := get_order_id_from_path (l_path_info)
if db_access.orders.has_key (id) then
if is_valid_to_delete (id) then
delete_order( id)
compute_response_delete (req, res)
else
--| FIXME: Here we need to define the Allow methods
handle_method_not_allowed_response (l_path_info + "%N There is conflict while trying to delete the order, the order could not be deleted in the current state", req, res)
end
else
handle_resource_not_found_response (l_path_info + " not found in this server", req, res)
end
end
end
compute_response_delete (req: WSF_REQUEST; res: WSF_RESPONSE)
local
h : HTTP_HEADER
do
create h.make
h.put_content_type_application_json
if attached req.request_time as time then
h.put_utc_date (time)
end
res.set_status_code ({HTTP_STATUS_CODE}.no_content)
res.put_header_text (h.string)
end
do_post (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Here the convention is the following.
-- POST is used for creation and the server determines the URI
-- of the created resource.
-- If the request post is SUCCESS, the server will create the order and will response with
-- HTTP_RESPONSE 201 CREATED, the Location header will contains the newly created order's URI
-- if the request post is not SUCCESS, the server will response with
-- HTTP_RESPONSE 400 BAD REQUEST, the client send a bad request
-- HTTP_RESPONSE 500 INTERNAL_SERVER_ERROR, when the server can deliver the request
local
l_post: STRING
do
l_post := retrieve_data (req)
if attached extract_order_request (l_post) as l_order then
save_order (l_order)
compute_response_post (req, res, l_order)
else
handle_bad_request_response (l_post +"%N is not a valid ORDER", req, res)
end
end
compute_response_post (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
local
h: HTTP_HEADER
l_msg : STRING
l_location : STRING
joc : JSON_ORDER_CONVERTER
do
create h.make
create joc.make
json.add_converter(joc)
h.put_content_type_application_json
if attached {JSON_VALUE} json.value (l_order) as jv then
l_msg := jv.representation
h.put_content_length (l_msg.count)
if attached req.http_host as host then
l_location := "http://" + host + req.request_uri + "/" + l_order.id
h.put_location (l_location)
end
if attached req.request_time as time then
h.put_utc_date (time)
end
res.set_status_code ({HTTP_STATUS_CODE}.created)
res.put_header_text (h.string)
res.put_string (l_msg)
end
end
feature {NONE} -- URI helper methods
get_order_id_from_path (a_path: READABLE_STRING_32) : STRING
do
Result := a_path.split ('/').at (3)
end
feature {NONE} -- Implementation Repository Layer
retrieve_order ( id : STRING) : detachable ORDER
-- get the order by id if it exist, in other case, Void
do
Result := db_access.orders.item (id)
end
save_order (an_order: ORDER)
-- save the order to the repository
local
i : INTEGER
do
from
i := 1
until
not db_access.orders.has_key ((db_access.orders.count + i).out)
loop
i := i + 1
end
an_order.set_id ((db_access.orders.count + i).out)
an_order.set_status ("submitted")
an_order.add_revision
db_access.orders.force (an_order, an_order.id)
end
is_valid_to_delete ( an_id : STRING) : BOOLEAN
-- Is the order identified by `an_id' in a state whre it can still be deleted?
do
if attached retrieve_order (an_id) as l_order then
if order_validation.is_state_valid_to_update (l_order.status) then
Result := True
end
end
end
is_valid_to_update (an_order: ORDER) : BOOLEAN
-- Check if there is a conflict while trying to update the order
do
if attached retrieve_order (an_order.id) as l_order then
if order_validation.is_state_valid_to_update (l_order.status) and then order_validation.is_valid_status_state (an_order.status) and then
order_validation.is_valid_transition (l_order, an_order.status) then
Result := True
end
end
end
update_order (an_order: ORDER)
-- update the order to the repository
do
an_order.add_revision
db_access.orders.force (an_order, an_order.id)
end
delete_order (an_order: STRING)
-- update the order to the repository
do
db_access.orders.remove (an_order)
end
extract_order_request (l_post : STRING) : detachable ORDER
-- extract an object Order from the request, or Void
-- if the request is invalid
local
parser : JSON_PARSER
joc : JSON_ORDER_CONVERTER
do
create joc.make
json.add_converter(joc)
create parser.make_with_string (l_post)
parser.parse_content
if
parser.is_valid and then
attached parser.parsed_json_value as jv
then
if attached {like extract_order_request} json.object (jv, "ORDER") as res then
Result := res
end
end
end
note
copyright: "2011-2015, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,58 +0,0 @@
note
description : "REST Buck server"
date : "$Date$"
revision : "$Revision$"
class RESTBUCKS_SERVER
inherit
WSF_ROUTED_SKELETON_SERVICE
undefine
requires_proxy
end
WSF_URI_TEMPLATE_HELPER_FOR_ROUTED_SERVICE
WSF_HANDLER_HELPER
WSF_DEFAULT_SERVICE
WSF_NO_PROXY_POLICY
create
make
feature {NONE} -- Initialization
make
do
initialize_router
set_service_option ("port", 9090)
make_and_launch
end
setup_router
local
order_handler: ORDER_HANDLER
doc: WSF_ROUTER_SELF_DOCUMENTATION_HANDLER
do
create order_handler.make_with_router (router)
router.handle_with_request_methods ("/order", order_handler, router.methods_POST)
router.handle_with_request_methods ("/order/{orderid}", order_handler, router.methods_GET + router.methods_DELETE + router.methods_PUT)
create doc.make_hidden (router)
router.handle_with_request_methods ("/api/doc", doc, router.methods_GET)
end
note
copyright: "2011-2013, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

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

View File

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

View File

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

View File

@@ -2,8 +2,8 @@ verbose=true
verbose_level=ALERT
port=9090
#max_concurrent_connections=100
keep_alive_timeout=3
#max_tcp_clients=100
socket_timeout=60
socket_recv_timeout=15
#keep_alive_timeout=5
#max_keep_alive_requests=300
#max_tcp_clients=100
#socket_timeout=60
#socket_recv_timeout=15

View File

@@ -0,0 +1,4 @@
port=9090
verbose=true
socket_recv_timeout=15
keep_alive_timeout=30

View File

@@ -0,0 +1,26 @@
note
description : "simple application root class"
date : "$Date$"
revision : "$Revision$"
class
SERVICE_COMPRESSION
inherit
WSF_DEFAULT_SERVICE [SERVICE_COMPRESSION_EXECUTION]
redefine
initialize
end
create
make_and_launch
feature {NONE} -- Initialization
initialize
do
Precursor
import_service_options (create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI}.make_from_file ("service.ini"))
end
end

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="service_compression" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486">
<target name="service_compression">
<root class="SERVICE_COMPRESSION" feature="make_and_launch"/>
<option warning="true" void_safety="all">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="scoop"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone-safe.ecf"/>
<library name="http" location="..\..\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf" readonly="false"/>
<library name="wsf_compression" location="..\..\library\server\wsf\wsf_compression-safe.ecf" readonly="false"/>
<cluster name="service_compression" location=".\" recursive="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
</cluster>
</target>
</system>

View File

@@ -0,0 +1,68 @@
note
description: "Simple file execution, serving home.html, ewf.png and 404.html"
date: "$Date$"
revision: "$Revision$"
class
SERVICE_COMPRESSION_EXECUTION
inherit
WSF_ROUTED_EXECUTION
redefine
initialize,
execute_default
end
create
make
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
Precursor
initialize_router
end
setup_router
local
fhdl_with_compression: WSF_FILE_SYSTEM_HANDLER_WITH_COMPRESSION
fhdl: WSF_FILE_SYSTEM_HANDLER
do
create fhdl_with_compression.make_hidden ("www")
fhdl_with_compression.set_directory_index (<<"index.html">>)
fhdl_with_compression.compression.set_default_compression_format
fhdl_with_compression.compression.enable_compression_for_media_type ({HTTP_MIME_TYPES}.image_jpg)
fhdl_with_compression.set_not_found_handler (agent (ia_uri: READABLE_STRING_8; ia_req: WSF_REQUEST; ia_res: WSF_RESPONSE)
do
execute_default (ia_req, ia_res)
end)
router.handle ("/compressed/", fhdl_with_compression, router.methods_GET)
create fhdl.make_hidden ("www")
fhdl.set_directory_index (<<"index.html">>)
fhdl.set_not_found_handler (agent (ia_uri: READABLE_STRING_8; ia_req: WSF_REQUEST; ia_res: WSF_RESPONSE)
do
execute_default (ia_req, ia_res)
end)
router.handle ("/", fhdl, router.methods_GET)
end
execute_default (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Dispatch requests without a matching handler.
local
not_found: WSF_NOT_FOUND_RESPONSE
mesg: WSF_RESPONSE_MESSAGE
do
create not_found.make (request)
not_found.add_suggested_location (request.absolute_script_url (""), "Home", "Back to home page")
mesg := not_found
res.send (mesg)
end
end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,27 @@
<html>
<head>
<title>EWF simple_file example</title>
</head>
<body>
<h1>EWF simple_file example</h1>
<p>This is a static html file served by EWF.</p>
<p>Try to <a href="nowhere.html">get lost</a>.</p>
<div width="45%" style="display: inline-block; border: solid 1px black; padding: 10px; margin: 10px;">
<h2>Without any compression</h2>
<a href="ewf.png"><img src="ewf.png"/></a>
<p>This is the real Eiffel tower.</p>
<a href="eiffel.jpg"><img src="eiffel.jpg"/></a>
<p>Try to <a href="big_file2.html">load a big file</a>.</p>
</div>
<div width="45%" style="display: inline-block; border: solid 1px black; padding: 10px; margin: 10px;">
<h2>With gzip compression</h2>
<a href="compressed/ewf.png"><img src="compressed/ewf.png"/></a>
<p>This is the real Eiffel tower.</p>
<a href="compressed/eiffel.jpg"><img src="compressed/eiffel.jpg"/></a>
<p>Try to <a href="compressed/big_file2.html">load a compressed big file</a>.</p>
</div>
</body>
</html>

View File

@@ -10,6 +10,9 @@ inherit
WSF_WEBSOCKET_EXECUTION
WEB_SOCKET_EVENT_I
redefine
on_timer
end
create
make
@@ -23,22 +26,27 @@ feature -- Basic operations
do
-- To send a response we need to setup, the status code and
-- the response headers.
if request.path_info.same_string_general ("/app") then
s := websocket_app_html (9090)
if request.path_info.same_string_general ("/favicon.ico") then
response.put_header ({HTTP_STATUS_CODE}.not_found, <<["Content-Length", "0"]>>)
else
s := "Hello World!"
create dt.make_now_utc
s.append (" (UTC time is " + dt.rfc850_string + ").")
s.append ("<p><a href=%"/app%">Websocket demo</a></p>")
if request.path_info.same_string_general ("/app") then
s := websocket_app_html (request.server_name, request.server_port)
else
s := "Hello World!"
create dt.make_now_utc
s.append (" (UTC time is " + dt.rfc850_string + ").")
s.append ("<p><a href=%"/app%">Websocket demo</a></p>")
end
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", s.count.out]>>)
response.set_status_code ({HTTP_STATUS_CODE}.ok)
response.header.put_content_type_text_html
response.header.put_content_length (s.count)
if request.is_keep_alive_http_connection then
response.header.put_connection_keep_alive
end
response.put_string (s)
end
response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", s.count.out]>>)
response.set_status_code ({HTTP_STATUS_CODE}.ok)
response.header.put_content_type_text_html
response.header.put_content_length (s.count)
if request.is_keep_alive_http_connection then
response.header.put_connection_keep_alive
end
response.put_string (s)
end
feature -- Websocket execution
@@ -52,6 +60,9 @@ feature -- Websocket execution
on_open (ws: WEB_SOCKET)
do
initialize_commands
set_timer_delay (1) -- Every 1 second.
ws.put_error ("Connecting")
ws.send (Text_frame, "Hello, this is a simple demo with Websocket using Eiffel. (/help for more information).%N")
end
@@ -62,12 +73,26 @@ feature -- Websocket execution
end
on_text (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
local
i: INTEGER
cmd_name: READABLE_STRING_8
do
if a_message.same_string_general ("/help") then
-- Echo the message for testing.
ws.send (Text_frame, "Help: available commands%N - /time : return the server UTC time.%N")
elseif a_message.starts_with_general ("/time") then
ws.send (Text_frame, "Server time is " + (create {HTTP_DATE}.make_now_utc).string)
if a_message.starts_with_general ("/") then
from
i := 1
until
i >= a_message.count or else a_message[i + 1].is_space
loop
i := i + 1
end
cmd_name := a_message.substring (2, i)
if attached command (cmd_name) as cmd then
cmd (ws, a_message.substring (i + 1, a_message.count))
elseif a_message.same_string_general ("/help") then
on_help_command (ws, Void)
else
ws.send (Text_frame, "Error: unknown command '/" + cmd_name + "'!%N")
end
else
-- Echo the message for testing.
ws.send (Text_frame, a_message)
@@ -80,9 +105,91 @@ feature -- Websocket execution
ws.put_error ("Connection closed")
end
on_timer (ws: WEB_SOCKET)
-- <Precursor>.
-- If ever the file ".stop" exists, stop gracefully the connection.
local
fut: FILE_UTILITIES
f: RAW_FILE
do
if fut.file_exists (".stop") then
ws.send_text ("End of the communication ...%N")
ws.send_connection_close ("")
create f.make_with_name (".stop")
f.delete
end
end
feature -- Command
initialize_commands
do
register_command (agent on_help_command, "help", "Display this help.")
register_command (agent on_time_command, "time", "Return the server UTC time.")
register_command (agent on_shutdown_command, "shutdown", "Shutdown the service (ends the websocket).")
end
register_command (a_cmd: attached like command; a_name: READABLE_STRING_8; a_description: READABLE_STRING_8)
local
tb: like commands
do
tb := commands
if tb = Void then
create tb.make_caseless (1)
commands := tb
end
tb.force ([a_cmd, a_name, a_description], a_name)
end
commands: detachable STRING_TABLE [TUPLE [cmd: attached like command; name, description: READABLE_STRING_8]]
command (a_name: READABLE_STRING_GENERAL): detachable PROCEDURE [TUPLE [ws: WEB_SOCKET; args: detachable READABLE_STRING_GENERAL]]
do
if
attached commands as tb and then
attached tb.item (a_name) as d
then
Result := d.cmd
end
end
on_help_command (ws: WEB_SOCKET; args: detachable READABLE_STRING_GENERAL)
local
s: STRING
do
create s.make_from_string ("Help: available commands:%N")
if attached commands as tb then
across
tb as ic
loop
s.append ("<li> /")
s.append (ic.item.name)
s.append (" : ")
s.append (ic.item.description)
s.append ("</li>%N")
end
end
ws.send_text (s)
end
on_time_command (ws: WEB_SOCKET; args: detachable READABLE_STRING_GENERAL)
do
ws.send_text ("Server time is " + (create {HTTP_DATE}.make_now_utc).string)
end
on_shutdown_command (ws: WEB_SOCKET; args: detachable READABLE_STRING_GENERAL)
local
f: RAW_FILE
do
ws.send_text ("Active websockets will end soon.%N")
create f.make_create_read_write (".stop")
f.put_string ("stop%N")
f.close
end
feature -- HTML Resource
websocket_app_html (a_port: INTEGER): STRING
websocket_app_html (a_host: STRING; a_port: INTEGER): STRING
do
Result := "[
<!DOCTYPE html>
@@ -96,7 +203,7 @@ $(document).ready(function() {
function connect(){
var host = "##WSSCHEME##://127.0.0.1:##PORTNUMBER##/app";
var host = "##WSSCHEME##://##HOSTNAME##:##PORTNUMBER##/app";
try{
@@ -178,6 +285,7 @@ body {font-family:Arial, Helvetica, sans-serif;}
</body>
</html>
]"
Result.replace_substring_all ("##HOSTNAME##", a_host)
Result.replace_substring_all ("##PORTNUMBER##", a_port.out)
if request.is_https then
Result.replace_substring_all ("##HTTPSCHEME##", "https")
@@ -188,5 +296,4 @@ body {font-family:Arial, Helvetica, sans-serif;}
end
end
end

View File

@@ -3,9 +3,9 @@
<target name="websocket_app">
<root class="APPLICATION" feature="make_and_launch"/>
<file_rule>
<exclude>/\.svn$</exclude>
<exclude>/CVS$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/\.svn$</exclude>
</file_rule>
<option warning="true">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
@@ -17,6 +17,14 @@
<library name="wsf" location="..\..\library\server\wsf\wsf.ecf"/>
<cluster name="app" location=".\" recursive="true"/>
</target>
<target name="websocket_app_st" extends="websocket_app">
<description>Single thread solution.
Warning: as it can not handle concurrent request, it is recommended to set Keep-Alive-Timeout to very low value, as browser will keep persistent connection open too long.
</description>
<capability>
<concurrency use="none"/>
</capability>
</target>
<target name="websocket_app_ssl" extends="websocket_app">
<variable name="ssl_enabled" value="true"/>
</target>

View File

@@ -1,13 +1,20 @@
verbose=true
verbose_level=INFORMATION
port=9090
#Socket and timeout
max_concurrent_connections=100
keep_alive_timeout=35
max_tcp_clients=100
socket_timeout=30
socket_recv_timeout=5
max_keep_alive_requests=300
socket_recv_timeout=100 ms
#Persistent connections
keep_alive_timeout=50 ms
max_keep_alive_requests=-1
#SSL
is_secure=false
secure_certificate=ca.crt
secure_certificate_key=ca.key
#Debug
verbose=true
verbose_level=INFORMATION

View File

@@ -10,6 +10,9 @@ It provides simple routine to perform http requests, and get response.
- Eiffel Net library
- and optionally Eiffel NetSSL library to support `https://...`
* Note: set ciphers setting is supported only with libcurl implementation for now, net implementation
set all the ciphers as part of the OpenSSL initialization.
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:

View File

@@ -7,8 +7,6 @@
<exclude>/\.svn$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL.ecf">
<condition>
@@ -32,7 +30,8 @@
</library>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
<cluster name="src" location=".\src\">
<cluster name="implementation" location="$|implementation" recursive="true" hidden="true"/>
<cluster name="implementation" location="$|implementation\" recursive="true" hidden="true"/>
<cluster name="parameters" location="$|parameters\" recursive="true"/>
<cluster name="spec_null" location="$|spec\null\" recursive="true"/>
<cluster name="spec_net" location="$|spec\net\">
<condition>

View File

@@ -7,8 +7,6 @@
<exclude>/\.svn$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true">
</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"/>
@@ -16,6 +14,8 @@
<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="implementation" location="$|implementation\" recursive="true" hidden="true"/>
<cluster name="parameters" location="$|parameters\" recursive="true"/>
<cluster name="spec_libcurl" location="$|spec\libcurl\" recursive="true"/>
<cluster name="default_libcurl" location="$|default\libcurl\"/>
</cluster>

View File

@@ -7,8 +7,6 @@
<exclude>/\.svn$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="encoder" location="..\..\text\encoder\encoder.ecf"/>
<library name="http" location="..\protocol\http\http.ecf"/>
@@ -25,6 +23,8 @@
</library>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
<cluster name="src" location=".\src\">
<cluster name="implementation" location="$|implementation\" recursive="true" hidden="true"/>
<cluster name="parameters" location="$|parameters\" recursive="true"/>
<cluster name="spec_net" location="$|spec\net\">
<cluster name="net_implementation" location="$|implementation\" hidden="true"/>
</cluster>

View File

@@ -1,22 +1,20 @@
package http_client
project
http_client = "http_client-safe.ecf"
http_client = "http_client.ecf"
libcurl_http_client = "libcurl_http_client-safe.ecf"
libcurl_http_client = "libcurl_http_client.ecf"
net_http_client = "net_http_client-safe.ecf"
net_http_client = "net_http_client.ecf"
note
title: HTTP client
description: "[
Provides simple routines to perform http requests, and get associated response.
It has two implementations:
- using Eiffel cURL (i.e libcurl)
- using EiffelNET (and the EiffelNET SSL extension)
]"
collection:EWF
description: "[
Provides simple routines to perform http requests, and get associated response.
It has two implementations:
- using Eiffel cURL (i.e libcurl)
- using EiffelNET (and the EiffelNET SSL extension)
]"
collection: EWF
tags: http,client,network,request,web,curl,EWF
copyright: 1984-2016 Eiffel Software and others
license: Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)

View File

@@ -16,8 +16,19 @@ feature -- Access
deferred
end
get (a_url: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
do
Result := new_session (a_url).get ("", ctx)
end
custom (a_method: READABLE_STRING_8; a_url: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
-- Response for `a_method' request based on `a_url' and optional `ctx'.
do
Result := new_session (a_url).custom (a_method, "", ctx)
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, 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

@@ -30,8 +30,19 @@ feature {NONE} -- Initialization
-- Initialize Current with `a_url' and `ctx'.
-- This can be used to reset/reinitialize Current with new url
-- in the case of redirection.
local
i: INTEGER
do
url := a_url
i := a_url.substring_index ("://", 1)
if i > 0 then
check
a_url.head (i - 1).same_string ("http")
or a_url.head (i - 1).same_string ("https")
end
url := a_url
else
url := session.url (a_url, Void)
end
headers := session.headers.twin
if ctx /= Void then
context := ctx
@@ -140,14 +151,14 @@ feature -- Settings
timeout: INTEGER
-- HTTP transaction timeout in seconds.
--| 0 means it nevers timeout
--| 0 means it never timeouts
do
Result := session.timeout
end
connect_timeout: INTEGER
-- HTTP connection timeout in seconds.
--| 0 means it nevers timeout
-- HTTP connection timeout in milliseconds.
--| 0 means it never timeouts
do
Result := session.connect_timeout
end
@@ -197,7 +208,7 @@ feature -- Settings
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2018, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -58,11 +58,11 @@ feature -- Access
-- Specific headers to use in addition to the one set in the related HTTP_CLIENT_SESSION
--| note: the value from Current context override the one from the session in case of conflict
query_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
query_parameters: HTTP_CLIENT_REQUEST_QUERY_PARAMETERS
-- Query parameters to be appended to the url
--| note: if the url already contains a query_string, the `query_parameters' will be appended to the url
form_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
form_parameters: HTTP_CLIENT_REQUEST_FORM_PARAMETERS
-- Form parameters
upload_data: detachable READABLE_STRING_8
@@ -145,13 +145,25 @@ feature -- Element change
add_query_parameter (k: READABLE_STRING_GENERAL; v: READABLE_STRING_GENERAL)
-- Add a query parameter `k=v'.
do
query_parameters.force (v.to_string_32, k.to_string_32)
query_parameters.force (create {HTTP_CLIENT_REQUEST_STRING_PARAMETER}.make (k, v))
end
add_form_parameter (k: READABLE_STRING_GENERAL; v: READABLE_STRING_GENERAL)
-- Add a form parameter `k'= `v'.
do
form_parameters.force (v.to_string_32, k.to_string_32)
form_parameters.force (create {HTTP_CLIENT_REQUEST_STRING_PARAMETER}.make (k, v))
end
add_file_form_parameter (k: READABLE_STRING_GENERAL; a_location: READABLE_STRING_GENERAL; a_content_type: detachable READABLE_STRING_8)
-- Add a form file parameter named `k`, located at `a_location`, with optional content type `a_content_type`.
require
has_no_upload_data_or_filename: not has_upload_data and not has_upload_filename
local
param: HTTP_CLIENT_REQUEST_FILE_PARAMETER
do
create param.make_with_path (k, create {PATH}.make_from_string (a_location))
param.set_content_type (a_content_type)
form_parameters.force (param)
end
set_credentials_required (b: BOOLEAN)
@@ -164,7 +176,8 @@ feature -- Element change
-- Set `upload_data' to `a_data'
--| note: the Current context can have upload_data XOR upload_filename, but not both.
require
has_upload_filename: (a_data /= Void and then not a_data.is_empty) implies not has_upload_filename
has_no_upload_filename: (a_data /= Void and then not a_data.is_empty) implies not has_upload_filename
has_no_form_data: (a_data /= Void and then not a_data.is_empty) implies not has_form_data
do
if a_data = Void or else a_data.is_empty then
upload_data := Void
@@ -180,6 +193,7 @@ feature -- Element change
--| note: the Current context can have upload_data XOR upload_filename, but not both.
require
has_no_upload_data: (a_fn /= Void and then not a_fn.is_empty) implies not has_upload_data
has_no_form_data: (a_fn /= Void and then not a_fn.is_empty) implies not has_form_data
do
if a_fn = Void or else a_fn.is_empty then
upload_filename := Void
@@ -266,9 +280,9 @@ feature -- URL helpers
a_url.append_character ('&')
end
l_first_param := False
uri_percent_encoder.append_query_name_encoded_string_to (ic.key, a_url)
uri_percent_encoder.append_query_name_encoded_string_to (ic.item.name, a_url)
a_url.append_character ('=')
uri_percent_encoder.append_query_value_encoded_string_to (ic.item, a_url)
ic.item.append_query_value_encoded_to (a_url)
end
end
end
@@ -315,38 +329,35 @@ feature {NONE} -- Implementation
end
end
parameters_to_uri_percent_encoded_string (ht: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]): STRING_8
-- Build query urlencoded string using parameters from `ht'.
parameters_to_uri_percent_encoded_string (a_params: HTTP_CLIENT_REQUEST_PARAMETERS [HTTP_CLIENT_REQUEST_PARAMETER]): STRING_8
-- Build query urlencoded string using parameters from `a_params'.
do
create Result.make (64)
across
ht as ic
a_params as ic
loop
if not Result.is_empty then
Result.append_character ('&')
end
uri_percent_encoder.append_query_name_encoded_string_to (ic.key, Result)
uri_percent_encoder.append_query_name_encoded_string_to (ic.item.name, Result)
Result.append_character ('=')
uri_percent_encoder.append_query_value_encoded_string_to (ic.item, Result)
ic.item.append_query_value_encoded_to (Result)
end
end
parameters_to_x_www_form_urlencoded_string (ht: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]): STRING_8
-- Build x-www-form-urlencoded string using parameters from `ht'.
parameters_to_x_www_form_urlencoded_string (a_params: HTTP_CLIENT_REQUEST_PARAMETERS [HTTP_CLIENT_REQUEST_PARAMETER]): STRING_8
-- Build x-www-form-urlencoded string using parameters from `a_params'.
do
create Result.make (64)
from
ht.start
until
ht.after
across
a_params as ic
loop
if not Result.is_empty then
Result.append_character ('&')
end
Result.append (x_www_form_url_encoder.encoded_string (ht.key_for_iteration))
x_www_form_url_encoder.append_percent_encoded_string_to (ic.item.name, Result)
Result.append_character ('=')
Result.append (x_www_form_url_encoder.encoded_string (ht.item_for_iteration))
ht.forth
ic.item.append_form_url_encoded_to (Result)
end
end

View File

@@ -61,12 +61,40 @@ feature -- Access
url (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): STRING_8
-- Url computed from Current and `ctx' data.
do
Result := base_url + a_path
if is_absolute_url (a_path) then
-- Is Absolute url
Result := a_path
else
Result := base_url + a_path
end
if ctx /= Void then
ctx.append_query_parameters_to_url (Result)
end
end
is_absolute_url (s: READABLE_STRING_GENERAL): BOOLEAN
-- Does `s` represent an absolute url?
local
i, pos: INTEGER
sch: READABLE_STRING_GENERAL
do
pos := s.substring_index ("://", 1)
if pos > 0 then
sch := s.head (pos - 1)
if not sch.is_whitespace then
from
i := 1
Result := True
until
not Result or i > sch.count
loop
Result := sch[i].is_alpha_numeric
i := i + 1
end
end
end
end
feature {NONE} -- Access: verbose
verbose_mode: INTEGER
@@ -272,6 +300,14 @@ feature -- Authentication
-- Associated optional credentials value.
-- Computed as `username':`password'.
ciphers_setting: detachable READABLE_STRING_8
-- SSL cipher preference lists
-- examples: DEFAULT, ALL, TLSv1
-- check https://www.openssl.org/docs/man1.1.0/apps/ciphers.html
-- Warning: At the moment only used for LIB_CURL_HTTP_CLIENT
-- Net implementation set all the ciphers using the OpenSSL at
-- initialization time.
feature -- Status setting
set_is_debug (b: BOOLEAN)
@@ -401,6 +437,14 @@ feature -- Element change
chunk_size := a_size
end
set_ciphers_setting (a_ciphers_setting: READABLE_STRING_8)
-- Set 'ciphers_setting' with 'a_ciphers_setting'.
do
create {STRING_8} ciphers_setting.make_from_string (a_ciphers_setting)
ensure
ciphers_setting_set: attached ciphers_setting as c_setting and then c_setting.same_string (a_ciphers_setting)
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"

View File

@@ -0,0 +1,34 @@
note
description: "Summary description for {HTTP_CLIENT_REQUEST_FORM_PARAMETERS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
HTTP_CLIENT_REQUEST_FORM_PARAMETERS
inherit
HTTP_CLIENT_REQUEST_PARAMETERS [HTTP_CLIENT_REQUEST_PARAMETER]
create
make
feature -- Status report
has_file_parameter: BOOLEAN
-- Has any file parameter?
do
Result := across items as ic some attached {HTTP_CLIENT_REQUEST_FILE_PARAMETER} ic.item end
end
note
copyright: "2011-2017, 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,83 @@
note
description: "Summary description for {HTTP_CLIENT_REQUEST_PARAMETER}."
author: ""
date: "$Date$"
revision: "$Revision$"
deferred class
HTTP_CLIENT_REQUEST_PARAMETER
inherit
DEBUG_OUTPUT
feature -- Access
name: READABLE_STRING_32
content_type: detachable READABLE_STRING_8
count: INTEGER
-- Integer representing the length of source value.
deferred
end
feature -- Status report
debug_output: STRING_32
do
create Result.make_empty
Result.append (name)
Result.append ("=...")
end
feature -- Conversion
append_form_url_encoded_to (a_output: STRING_8)
-- Append as form url encoded string to `a_output`.
deferred
end
append_query_value_encoded_to (a_output: STRING_8)
deferred
end
append_as_mime_encoded_to (a_output: STRING_8)
deferred
end
feature -- Element change
set_name (a_name: READABLE_STRING_GENERAL)
do
name := a_name.as_string_32
end
set_content_type (ct: detachable READABLE_STRING_8)
do
content_type := ct
end
feature {NONE} -- Implementation
x_www_form_url_encoder: X_WWW_FORM_URL_ENCODER
-- Shared x-www-form-urlencoded encoder.
once
create Result
end
uri_percent_encoder: URI_PERCENT_ENCODER
once
create Result
end
note
copyright: "2011-2017, 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,67 @@
note
description: "Summary description for {HTTP_CLIENT_REQUEST_PARAMETERS}."
author: ""
date: "$Date$"
revision: "$Revision$"
deferred class
HTTP_CLIENT_REQUEST_PARAMETERS [G -> HTTP_CLIENT_REQUEST_PARAMETER]
inherit
ITERABLE [G]
feature {NONE} -- Initialization
make (nb: INTEGER)
do
create items.make (nb)
end
feature -- Access
is_empty: BOOLEAN
do
Result := items.is_empty
end
count: INTEGER
do
Result := items.count
end
has (a_parameter_name: READABLE_STRING_GENERAL): BOOLEAN
do
Result := across items as ic some a_parameter_name.same_string (ic.item.name) end
end
feature -- Element change
extend, force (i: G)
do
items.force (i)
end
feature -- Iteration
new_cursor: ARRAYED_LIST_ITERATION_CURSOR [G]
-- <Precursor>
do
Result := items.new_cursor
end
feature {NONE} -- Implementation
items: ARRAYED_LIST [G]
invariant
note
copyright: "2011-2017, 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

@@ -1,23 +1,20 @@
note
description: "Summary description for {WSF_DEFAULT_SERVICE_LAUNCHER}."
description: "Summary description for {HTTP_CLIENT_REQUEST_QUERY_PARAMETERS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
WSF_DEFAULT_SERVICE_LAUNCHER
HTTP_CLIENT_REQUEST_QUERY_PARAMETERS
inherit
WSF_LIBFCGI_SERVICE_LAUNCHER
HTTP_CLIENT_REQUEST_PARAMETERS [HTTP_CLIENT_REQUEST_STRING_PARAMETER]
create
make,
make_and_launch,
make_callback,
make_callback_and_launch
make
note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
@@ -26,5 +23,4 @@ note
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,155 @@
note
description: "Summary description for {HTTP_CLIENT_REQUEST_FILE_PARAMETER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
HTTP_CLIENT_REQUEST_FILE_PARAMETER
inherit
HTTP_CLIENT_REQUEST_PARAMETER
create
make_with_path
feature {NONE} -- Initialization
make_with_path (a_name: READABLE_STRING_GENERAL; a_path: PATH)
do
set_name (a_name)
location := a_path
if attached a_path.entry as e then
file_name := e.name
end
set_content_type ("application/octet-stream") -- Default
end
feature -- Access
count: INTEGER
local
f: RAW_FILE
do
create f.make_with_path (location)
if f.exists and then f.is_access_readable then
Result := f.count
end
end
location: PATH
file_name: detachable READABLE_STRING_32
feature -- Element change
set_file_name (fn: detachable READABLE_STRING_GENERAL)
do
if fn = Void then
file_name := Void
else
file_name := fn.to_string_32
end
end
feature -- Status report
exists: BOOLEAN
local
fut: FILE_UTILITIES
do
Result := fut.file_path_exists (location)
end
feature {NONE} -- Data
file_content: detachable STRING_8
require
exists: exists
local
f: RAW_FILE
do
create f.make_with_path (location)
if f.exists and then f.is_access_readable then
create Result.make (f.count)
f.open_read
from
until
f.exhausted or f.end_of_file
loop
f.read_stream_thread_aware (2_048)
Result.append (f.last_string)
end
f.close
end
end
feature -- Data
append_file_content_to (a_output: STRING)
-- Append content of file located at `location`to `a_output'.
require
exists: exists
local
f: RAW_FILE
l_buffer_size: INTEGER
do
create f.make_with_path (location)
if f.exists and then f.is_access_readable then
f.open_read
from
l_buffer_size := 2_048
until
f.exhausted or f.end_of_file
loop
f.read_stream_thread_aware (l_buffer_size)
a_output.append (f.last_string)
end
f.close
end
end
feature -- Conversion
append_form_url_encoded_to (a_output: STRING_8)
-- Append as form url encoded string to `a_output`.
do
if exists and then attached file_content as s then
x_www_form_url_encoder.append_percent_encoded_string_to (s, a_output)
else
check exists: False end
end
end
append_query_value_encoded_to (a_output: STRING_8)
do
if exists and then attached file_content as s then
uri_percent_encoder.append_query_value_encoded_string_to (s, a_output)
else
check exists: False end
end
end
append_as_mime_encoded_to (a_output: STRING_8)
-- Encoded unicode string for mime value.
-- For instance uploaded filename, or form data key or values.
do
-- FIXME: find the proper encoding!
if exists then
append_file_content_to (a_output)
else
check exists: False end
end
end
note
copyright: "2011-2017, 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,68 @@
note
description: "Summary description for {HTTP_CLIENT_REQUEST_STRING_PARAMETER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
HTTP_CLIENT_REQUEST_STRING_PARAMETER
inherit
HTTP_CLIENT_REQUEST_PARAMETER
create
make
feature {NONE} -- Initialization
make (a_name, a_value: READABLE_STRING_GENERAL)
do
set_name (a_name)
value := a_value.as_string_32
end
feature -- Access
value: READABLE_STRING_32
count: INTEGER
do
Result := value.count
end
feature -- Conversion
append_form_url_encoded_to (a_output: STRING_8)
-- Append as form url encoded string to `a_output`.
do
x_www_form_url_encoder.append_percent_encoded_string_to (value, a_output)
end
append_query_value_encoded_to (a_output: STRING_8)
do
uri_percent_encoder.append_query_value_encoded_string_to (value, a_output)
end
append_as_mime_encoded_to (a_output: STRING_8)
-- 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!
utf.utf_32_string_into_utf_8_string_8 (value, a_output)
end
invariant
note
copyright: "2011-2017, 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

@@ -31,9 +31,12 @@ feature {NONE} -- Initialization
apply_workaround
-- Due to issue with Eiffel cURL on Windows 32bits
-- we need to do the following workaround
-- we need to do the following workaround.
local
add: detachable INET_ADDRESS
once
if attached (create {INET_ADDRESS_FACTORY}).create_localhost then
if {PLATFORM}.is_windows then
add := (create {INET_ADDRESS_FACTORY}).create_localhost
end
end
@@ -58,7 +61,6 @@ feature -- Execution
ctx: like context
p_slist: POINTER
retried: BOOLEAN
l_form_data: detachable HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
l_upload_data: detachable READABLE_STRING_8
l_upload_filename: detachable READABLE_STRING_GENERAL
l_headers: like headers
@@ -151,70 +153,19 @@ feature -- Execution
--| Credentials not provided ...
end
end
if ctx.has_upload_data then
l_upload_data := ctx.upload_data
end
if ctx.has_upload_filename then
l_upload_filename := ctx.upload_filename
end
if ctx.has_form_data then
l_form_data := ctx.form_parameters
check non_empty_form_data: not l_form_data.is_empty end
if l_upload_data = Void and l_upload_filename = Void then
-- Send as form-urlencoded
if
attached l_headers.item ("Content-Type") as l_ct
then
if l_ct.starts_with ("application/x-www-form-urlencoded") then
-- Content-Type is already application/x-www-form-urlencoded
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
elseif l_ct.starts_with ("multipart/form-data") then
l_use_curl_form := True
else
-- Not supported, use libcurl form.
l_use_curl_form := True
end
else
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
end
else
l_use_curl_form := True
end
if l_use_curl_form then
create l_form.make
create l_last.make
from
l_form_data.start
until
l_form_data.after
loop
curl.formadd_string_string (l_form, l_last,
{CURL_FORM_CONSTANTS}.curlform_copyname, l_form_data.key_for_iteration,
{CURL_FORM_CONSTANTS}.curlform_copycontents, l_form_data.item_for_iteration,
{CURL_FORM_CONSTANTS}.curlform_end
)
l_form_data.forth
end
if l_upload_filename /= Void then
curl.formadd_string_string (l_form, l_last,
{CURL_FORM_CONSTANTS}.curlform_copyname, "file",
{CURL_FORM_CONSTANTS}.curlform_file, l_upload_filename,
{CURL_FORM_CONSTANTS}.curlform_end
)
l_upload_filename := Void
end
l_last.release_item
curl_easy.setopt_form (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httppost, l_form)
end
end
if l_upload_data /= Void then
check
post_or_put_request_method: request_method.is_case_insensitive_equal ("POST")
or request_method.is_case_insensitive_equal ("PUT")
or request_method.is_case_insensitive_equal ("PATCH")
end
check no_form_data: not ctx.has_form_data end
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_postfields, l_upload_data)
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_postfieldsize, l_upload_data.count)
@@ -224,6 +175,7 @@ feature -- Execution
or request_method.is_case_insensitive_equal ("PUT")
or request_method.is_case_insensitive_equal ("PATCH")
end
check no_form_data: not ctx.has_form_data end
create l_upload_file.make_with_name (l_upload_filename)
if l_upload_file.exists and then l_upload_file.is_readable then
@@ -238,12 +190,62 @@ feature -- Execution
l_upload_file.open_read
curl_easy.set_curl_function (l_custom_function)
end
elseif
ctx.has_form_data and
attached ctx.form_parameters as l_form_data
then
check non_empty_form_data: not l_form_data.is_empty end
-- Send as form-urlencoded
if
attached l_headers.item ("Content-Type") as l_ct
then
if l_ct.starts_with ("application/x-www-form-urlencoded") then
-- Content-Type is already application/x-www-form-urlencoded
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
elseif l_ct.starts_with ("multipart/form-data") or l_form_data.has_file_parameter then
l_use_curl_form := True
else
-- Not supported, use libcurl form.
l_use_curl_form := True
end
else
l_headers.force ("application/x-www-form-urlencoded", "Content-Type")
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_postfields, l_upload_data)
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_postfieldsize, l_upload_data.count)
end
if l_use_curl_form then
create l_form.make
create l_last.make
across
l_form_data as ic
loop
if attached {HTTP_CLIENT_REQUEST_STRING_PARAMETER} ic.item as strparam then
curl.formadd_string_string (l_form, l_last,
{CURL_FORM_CONSTANTS}.curlform_copyname, strparam.name,
{CURL_FORM_CONSTANTS}.curlform_copycontents, strparam.value,
{CURL_FORM_CONSTANTS}.curlform_end
)
elseif attached {HTTP_CLIENT_REQUEST_FILE_PARAMETER} ic.item as fileparam then
curl.formadd_string_string (l_form, l_last,
{CURL_FORM_CONSTANTS}.curlform_copyname, "file",
{CURL_FORM_CONSTANTS}.curlform_file, fileparam.location.name,
{CURL_FORM_CONSTANTS}.curlform_end
)
else
check supported_parameter_type: False end
end
end
l_last.release_item
curl_easy.setopt_form (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httppost, l_form)
end
else
check no_upload_data: l_upload_data = Void and l_upload_filename = Void end
-- No form, or upload data to send!
check no_data: not (ctx.has_upload_data or ctx.has_upload_filename or ctx.has_form_data) end
end
end -- ctx /= Void
--| Header
--| Header
across
l_headers as curs
loop
@@ -376,6 +378,11 @@ feature -- Execution
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_ssl_verifypeer, 0)
end
--| Cipher List
if attached session.ciphers_setting as c_list then
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_ssl_cipher_list, c_list )
end
--| Request method
if request_method.is_case_insensitive_equal ("GET") then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpget, 1)
@@ -445,7 +452,7 @@ feature {NONE} -- Implementation
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2018, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -64,7 +64,7 @@ feature {NONE} -- Internal
create Result.make_client_by_port (a_port, a_host)
end
Result.set_connect_timeout (connect_timeout)
Result.set_timeout (timeout)
Result.set_timeout_ns (Result.seconds_to_nanoseconds (timeout))
Result.connect
end
end
@@ -91,8 +91,8 @@ feature -- Access
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_ct: detachable READABLE_STRING_8
l_upload_file: detachable RAW_FILE
l_upload_filename: detachable READABLE_STRING_GENERAL
l_form_string: STRING
@@ -113,6 +113,7 @@ feature -- Access
-- Get URL data
l_is_https := url.starts_with_general ("https://")
create l_uri.make_from_string (url)
check valid_url: l_uri.is_valid end
l_port := l_uri.port
if l_port = 0 then
if l_is_https then
@@ -149,7 +150,7 @@ feature -- Access
then
create l_authorization.make_basic_auth (u_name, u_pass)
if attached l_authorization.http_authorization as auth then
headers.extend (auth, "Authorization")
headers.force (auth, "Authorization")
end
check headers.has_key ("Authorization") end
end
@@ -176,7 +177,7 @@ feature -- Access
else
l_platform := "Unknown"
end
headers.extend ("eiffelhttpclient/" + net_http_client_version + " (" + l_platform + ")", "User-Agent")
headers.force ("eiffelhttpclient/" + net_http_client_version + " (" + l_platform + ")", "User-Agent")
end
-- handle sending data
@@ -191,67 +192,52 @@ feature -- Access
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
if
attached headers.item ("Content-Type") as l_ct
then
if l_ct.starts_with ("application/x-www-form-urlencoded") then
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
elseif l_ct.starts_with ("multipart/form-data") then
-- create form using multipart/form-data encoding
l_boundary := new_mime_boundary (l_form_data)
headers.extend ("multipart/form-data; boundary=" + l_boundary, "Content-Type")
l_upload_data := form_date_and_uploaded_files_to_mime_string (l_form_data, l_upload_filename, l_boundary)
else
-- not supported !
-- Send as form-urlencoded
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
end
else
-- Send as form-urlencoded
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
end
headers.extend (l_upload_data.count.out, "Content-Length")
if l_is_chunked_transfer_encoding then
-- Discard chunked transfer encoding
headers.remove ("Transfer-Encoding")
l_is_chunked_transfer_encoding := False
end
elseif l_form_data /= Void then
check l_upload_data = Void end
-- create form using multipart/form-data encoding
l_boundary := new_mime_boundary (l_form_data)
headers.extend ("multipart/form-data; boundary=" + l_boundary, "Content-Type")
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")
if l_is_chunked_transfer_encoding then
-- Discard chunked transfer encoding
headers.remove ("Transfer-Encoding")
l_is_chunked_transfer_encoding := False
end
end
elseif l_upload_data /= Void then
if l_upload_data /= Void then
check ctx.has_upload_data end
check no_form_data: not ctx.has_form_data end
if not headers.has ("Content-Type") then
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
headers.force ("application/x-www-form-urlencoded", "Content-Type")
end
if not l_is_chunked_transfer_encoding then
headers.extend (l_upload_data.count.out, "Content-Length")
headers.force (l_upload_data.count.out, "Content-Length")
end
elseif l_upload_filename /= Void then
check ctx.has_upload_filename end
check no_form_data: not ctx.has_form_data end
create l_upload_file.make_with_name (l_upload_filename)
if l_upload_file.exists and then l_upload_file.is_access_readable then
if not l_is_chunked_transfer_encoding then
headers.extend (l_upload_file.count.out, "Content-Length")
headers.force (l_upload_file.count.out, "Content-Length")
end
end
check l_upload_file /= Void end
elseif
ctx.has_form_data and
attached ctx.form_parameters as l_form_data
then
l_ct := headers.item ("Content-Type")
if l_ct /= Void and then l_ct.starts_with ("application/x-www-form-urlencoded") then
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
elseif
(l_ct /= Void and then l_ct.starts_with ("multipart/form-data"))
or l_form_data.has_file_parameter
then
-- create form using multipart/form-data encoding
l_boundary := new_mime_boundary (l_form_data)
headers.force ("multipart/form-data; boundary=" + l_boundary, "Content-Type")
l_upload_data := form_date_and_uploaded_files_to_mime_string (l_form_data, l_boundary)
else
-- not supported !
-- Send as form-urlencoded
headers.force ("application/x-www-form-urlencoded", "Content-Type")
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
end
headers.force (l_upload_data.count.out, "Content-Length")
if l_is_chunked_transfer_encoding then
-- Discard chunked transfer encoding
headers.remove ("Transfer-Encoding")
l_is_chunked_transfer_encoding := False
end
end
end
@@ -482,14 +468,9 @@ feature {NONE} -- Helpers
Result := a_status >= 300 and a_status < 400
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_date_and_uploaded_files_to_mime_string (a_form_parameters: ITERABLE [HTTP_CLIENT_REQUEST_PARAMETER]; 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
@@ -500,48 +481,26 @@ feature {NONE} -- Helpers
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 (string_to_mime_encoded_string (ic.item.name))
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
attached {HTTP_CLIENT_REQUEST_FILE_PARAMETER} ic.item as fileparam and then
attached fileparam.file_name as fn
then
l_mime_type := l_mt
else
l_mime_type := "application/octet-stream"
Result.append ("; filename=")
Result.append_character ('%"')
Result.append (string_to_mime_encoded_string (fn))
Result.append_character ('%"')
end
Result.append ("--")
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
if attached ic.item.content_type as ct then
Result.append (http_end_of_header_line)
Result.append ("Content-Type: ")
Result.append (ct)
end
Result.append (http_end_of_header_line)
Result.append (http_end_of_header_line)
ic.item.append_as_mime_encoded_to (Result)
Result.append (http_end_of_header_line)
end
Result.append ("--")
Result.append (a_mime_boundary)
@@ -893,7 +852,7 @@ feature {NONE} -- Helpers
end
end
new_mime_boundary (a_data: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]): STRING
new_mime_boundary (a_data: ITERABLE [HTTP_CLIENT_REQUEST_PARAMETER]): STRING
-- New MIME boundary.
local
s: STRING
@@ -904,7 +863,7 @@ feature {NONE} -- Helpers
across
a_data as ic
loop
i := i + ic.item.count + ic.key.count
i := i + ic.item.count + ic.item.name.count
end
create ran.set_seed (i) -- FIXME: use a real random seed.
ran.start
@@ -928,7 +887,7 @@ feature {NONE} -- Helpers
invariant
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2018, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -13,7 +13,8 @@
<capability>
<concurrency support="thread"/>
</capability>
<variable name="netssl_http_client_enabled" value="false"/>
<variable name="ssl_enabled" value="true"/>
<variable name="netssl_http_client_enabled" value="true"/>
<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.ecf"/>

View File

@@ -74,6 +74,22 @@ feature -- Test routines
end
end
test_abs_url
local
sess: like new_session
h: STRING_8
l_url: STRING
do
sess := new_session ("https://www.eiffel.org")
l_url := "/foo/bar"
assert ("abs rel", sess.url (l_url, Void).same_string (sess.base_url + l_url))
l_url := "https://www.eiffel.org/foo/bar"
assert ("abs 1", sess.url (l_url, Void).same_string (l_url))
l_url := "https://example.com/foo/bar"
assert ("abs 2", sess.url (l_url, Void).same_string (l_url))
end
test_headers
local
res: HTTP_CLIENT_RESPONSE

View File

@@ -32,6 +32,11 @@ feature -- Tests
test_http_client_ssl
end
test_libcurl_abs_url
do
test_abs_url
end
test_libcurl_headers
do
test_headers

View File

@@ -59,6 +59,11 @@ feature -- Tests
test_post_with_file_and_form_data
end
libcurl_test_post_with_multiple_file_and_form_data
do
test_post_with_multiple_file_and_form_data
end
libcurl_test_get_with_redirection
do
test_get_with_redirection

View File

@@ -32,6 +32,11 @@ feature -- Tests
test_http_client_ssl
end
test_net_abs_url
do
test_abs_url
end
test_net_headers
do
test_headers

View File

@@ -59,6 +59,11 @@ feature -- Tests
test_post_with_file_and_form_data
end
net_test_post_with_multiple_file_and_form_data
do
test_post_with_multiple_file_and_form_data
end
net_test_get_with_redirection
do
test_get_with_redirection

View File

@@ -21,8 +21,7 @@ feature -- Initialization
on_prepare
do
Precursor
global_requestbin_path := "/s0jkhhs0"
if global_requestbin_path = Void then
if is_using_requestbin and global_requestbin_path = Void then
global_requestbin_path := new_requestbin_path
end
end
@@ -33,7 +32,13 @@ feature -- Factory
deferred
end
feature -- Requestbin
feature -- Requestbin
is_using_requestbin: BOOLEAN = False
is_using_mockbincom: BOOLEAN
do
Result := not is_using_requestbin
end
global_requestbin_path: detachable READABLE_STRING_8
@@ -42,7 +47,7 @@ feature -- Requestbin
i,j: INTEGER
do
if
attached new_session ("http://requestb.in") as sess and then
attached new_session ("https://requestb.in") as sess and then
attached sess.post ("/api/v1/bins", Void, Void) as resp
then
if resp.error_occurred then
@@ -67,13 +72,30 @@ feature -- Requestbin
if not Result.starts_with ("/") then
Result.prepend_character ('/')
end
print ("new_requestbin_path => http://requestb.in" + Result + "?inspect%N")
print ("new_requestbin_path => " + sess.base_url + Result + "?inspect%N")
end
end
end
end
end
new_web_session: like new_session
do
if is_using_mockbincom then
Result := new_session ("http://mockbin.com/request")
end
if Result = Void and is_using_requestbin then
if attached global_requestbin_path as l_path then
Result := new_session ("https://requestb.in" + l_path)
else
assert ("Has requestbin path", False)
end
end
if Result = Void then
Result := new_session ("http://mockbin.com/request") -- Default
end
end
feature -- Factory
test_post_url_encoded
@@ -81,288 +103,200 @@ feature -- Factory
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)
-- 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_web_session
if
attached sess.post ("", Void, "Hello World") as res
then
check_response (res)
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.add_form_parameter ("First Key", "First Value")
l_ctx.add_form_parameter ("Second Key", "Second Value")
l_ctx.add_form_parameter ("unicode", {STRING_32} "Hello / 你好 !")
l_ctx.add_form_parameter ({STRING_32} "Field 你好 !", "How are you?")
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)
-- POST REQUEST WITH FORM DATA
-- check requestbin to ensure the form parameters are correctly received
sess := new_web_session
create l_ctx.make
l_ctx.add_form_parameter ("First Key", "First Value")
l_ctx.add_form_parameter ("Second Key", "Second Value")
l_ctx.add_form_parameter ("unicode", {STRING_32} "Hello / 你好 !")
l_ctx.add_form_parameter ({STRING_32} "Field 你好 !", "How are you?")
if
attached sess.post ("", l_ctx, "") as res
then
check_response (res)
end
end
test_post_with_uncommon_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_web_session
create l_ctx.make
-- 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.add_form_parameter ("title", "Eiffel World!") -- space and !
l_ctx.add_form_parameter ("path", "foo/bar") -- slash
l_ctx.add_form_parameter ("unreserved", ":!@[]{}()*") -- ...
l_ctx.add_form_parameter ("reserved", "+=?&_#_") -- ...
l_ctx.add_form_parameter ("a=b", "a=b") -- equal sign
l_ctx.add_form_parameter ("test", "!$&'()*") --
l_ctx.add_form_parameter ("lst[a][b]", "[123][456]") -- brackets
l_ctx.add_form_parameter ("pos{1,2}", "loc{a,b}") -- curly brackets
l_ctx.add_form_parameter ("?foo", "?bar") -- question mark
l_ctx.add_form_parameter ("?", "?") -- question mark
l_ctx.add_form_parameter ("&bar", "&bar") -- ampersand
l_ctx.add_form_parameter ("&", "&") -- ampersand
l_ctx.add_form_parameter ("title", "Eiffel World!") -- space and !
l_ctx.add_form_parameter ("path", "foo/bar") -- slash
l_ctx.add_form_parameter ("unreserved", ":!@[]{}()*") -- ...
l_ctx.add_form_parameter ("reserved", "+=?&_#_") -- ...
l_ctx.add_form_parameter ("a=b", "a=b") -- equal sign
l_ctx.add_form_parameter ("test", "!$&'()*") --
l_ctx.add_form_parameter ("lst[a][b]", "[123][456]") -- brackets
l_ctx.add_form_parameter ("pos{1,2}", "loc{a,b}") -- curly brackets
l_ctx.add_form_parameter ("?foo", "?bar") -- question mark
l_ctx.add_form_parameter ("?", "?") -- question mark
l_ctx.add_form_parameter ("&bar", "&bar") -- ampersand
l_ctx.add_form_parameter ("&", "&") -- ampersand
assert ("form data well generated", l_ctx.form_parameters_to_x_www_form_url_encoded_string.same_string ("title=Eiffel+World!&path=foo%%2Fbar&unreserved=%%3A!%%40%%5B%%5D%%7B%%7D()*&reserved=%%2B%%3D%%3F%%26_%%23_&a%%3Db=a%%3Db&test=!%%24%%26'()*&lst%%5Ba%%5D%%5Bb%%5D=%%5B123%%5D%%5B456%%5D&pos%%7B1%%2C2%%7D=loc%%7Ba%%2Cb%%7D&%%3Ffoo=%%3Fbar&%%3F=%%3F&%%26bar=%%26bar&%%26=%%26"))
assert ("form data well generated", l_ctx.form_parameters_to_x_www_form_url_encoded_string.same_string ("title=Eiffel+World!&path=foo%%2Fbar&unreserved=%%3A!%%40%%5B%%5D%%7B%%7D()*&reserved=%%2B%%3D%%3F%%26_%%23_&a%%3Db=a%%3Db&test=!%%24%%26'()*&lst%%5Ba%%5D%%5Bb%%5D=%%5B123%%5D%%5B456%%5D&pos%%7B1%%2C2%%7D=loc%%7Ba%%2Cb%%7D&%%3Ffoo=%%3Fbar&%%3F=%%3F&%%26bar=%%26bar&%%26=%%26"))
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)
if
attached sess.post ("", l_ctx, "") as res
then
check_response (res)
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, 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)
-- POST REQUEST WITH A FILE
-- check requestbin to ensure the form parameters are correctly received
-- set filename to a local file
sess := new_web_session
create l_ctx.make
l_ctx.set_upload_filename ("test.txt")
if
attached sess.post ("", l_ctx, Void) as res
then
check_response (res)
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)
-- PUT REQUEST WITH A FILE
-- check requestbin to ensure the file is correctly received
-- set filename to a local file
sess := new_web_session
create l_ctx.make
l_ctx.set_upload_filename ("test.txt")
if
attached sess.put ("", l_ctx, Void) as res
then
check_response (res)
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 ("name=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)
-- PUT REQUEST WITH A FILE
-- check requestbin to ensure the file is correctly received
-- set filename to a local file
sess := new_web_session
create l_ctx.make
l_ctx.set_upload_data ("name=This is a test for http client.%N")
if
attached sess.put ("", l_ctx, Void) as res
then
check_response (res)
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_web_session
create l_ctx.make
-- l_ctx.add_file_form_parameter ("image", "test.txt", "image/jpeg")
l_ctx.add_file_form_parameter ("text", "test.txt", "plain/text")
l_ctx.add_form_parameter ("First", "Value")
l_ctx.add_form_parameter ("Second", "and last value")
if
attached sess.post ("", l_ctx, Void) as res
then
check_response (res)
end
end
-- 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")
-- sess := new_session ("http://localhost:9090")
create l_ctx.make
-- l_ctx.set_upload_filename ("logo.jpg")
l_ctx.set_upload_filename ("test.txt")
l_ctx.add_form_parameter ("First", "Value")
l_ctx.add_form_parameter ("Second", "and last value")
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)
test_post_with_multiple_file_and_form_data
local
sess: HTTP_CLIENT_SESSION
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
-- 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_web_session
create l_ctx.make
l_ctx.add_header ("Content-Type", "multipart/form-data")
l_ctx.add_file_form_parameter ("first_file", "test.txt", "plain/text")
l_ctx.add_file_form_parameter ("image", "logo.jpg", "image/jpeg")
l_ctx.add_form_parameter ("First", "Value")
l_ctx.add_form_parameter ("Second", "and last value")
l_ctx.add_file_form_parameter ("last_file", "test.txt", Void)
if
attached sess.post ("", l_ctx, Void) as res
then
check_response (res)
end
end
test_post_with_file_using_chunked_transfer_encoding
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.add_header ("Transfer-Encoding", "chunked")
l_ctx.set_upload_filename ("logo.jpg")
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)
-- 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_web_session
create l_ctx.make
l_ctx.add_header ("Transfer-Encoding", "chunked")
l_ctx.set_upload_filename ("logo.jpg")
if
attached sess.post ("", l_ctx, Void) as res
then
check_response (res)
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
assert("was redirected", res.redirections_count > 0)
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)
-- GET REQUEST, Forwarding (google's first answer is a forward)
-- check headers received (printed in console)
sess := new_session ("http://google.com")
if attached sess.get ("/", Void) as res then
check_response (res)
assert("was redirected", res.redirections_count > 0)
end
end
@@ -377,6 +311,7 @@ feature -- Factory
sess.set_credentials ("test", "test")
create ctx.make_with_credentials_required
if attached sess.get ("/password-ok.php", ctx) as res then
check_response (res)
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
@@ -388,50 +323,58 @@ feature -- Factory
test_get_with_query_parameters
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
q: STRING
do
if attached global_requestbin_path as requestbin_path then
-- GET 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.add_query_parameter ("?", "?first&arg")
l_ctx.add_query_parameter ("title", "Eiffel World!")
l_ctx.add_query_parameter ("path", "foo/bar")
l_ctx.add_query_parameter ("reserved", "+=&?")
l_ctx.add_query_parameter ("unreserved", ":!@'()*")
l_ctx.add_query_parameter ("unsafe", "%"[]{}")
l_ctx.add_query_parameter ("test", "!$&'()*")
l_ctx.add_query_parameter ("a&b", "a&b")
l_ctx.add_query_parameter ("lst[a][b]", "[abc][123]")
l_ctx.add_query_parameter ("foo(a,b)", "bar(1,2)*pi")
create q.make_empty
l_ctx.append_query_parameters_to_url (q)
assert("query", q.same_string ("??=?first%%26arg&title=Eiffel+World!&path=foo/bar&reserved=%%2B=%%26?&unreserved=:!@'()*&unsafe=%%22%%5B%%5D%%7B%%7D&test=!$%%26'()*&a%%26b=a%%26b&lst%%5Ba%%5D%%5Bb%%5D=%%5Babc%%5D%%5B123%%5D&foo(a,b)=bar(1,2)*pi"))
-- GET 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_web_session
create l_ctx.make
l_ctx.add_query_parameter ("?", "?first&arg")
l_ctx.add_query_parameter ("title", "Eiffel World!")
l_ctx.add_query_parameter ("path", "foo/bar")
l_ctx.add_query_parameter ("reserved", "+=&?")
l_ctx.add_query_parameter ("unreserved", ":!@'()*")
l_ctx.add_query_parameter ("unsafe", "%"[]{}")
l_ctx.add_query_parameter ("test", "!$&'()*")
l_ctx.add_query_parameter ("a&b", "a&b")
l_ctx.add_query_parameter ("lst[a][b]", "[abc][123]")
l_ctx.add_query_parameter ("foo(a,b)", "bar(1,2)*pi")
create q.make_empty
l_ctx.append_query_parameters_to_url (q)
assert("query", q.same_string ("??=?first%%26arg&title=Eiffel+World!&path=foo/bar&reserved=%%2B=%%26?&unreserved=:!@'()*&unsafe=%%22%%5B%%5D%%7B%%7D&test=!$%%26'()*&a%%26b=a%%26b&lst%%5Ba%%5D%%5Bb%%5D=%%5Babc%%5D%%5B123%%5D&foo(a,b)=bar(1,2)*pi"))
create h.make_empty
if
attached sess.get (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)
if
attached sess.get ("", l_ctx) as res
then
check_response (res)
end
end
feature {NONE} -- Implementation
check_response (res: HTTP_CLIENT_RESPONSE)
local
h: STRING
do
assert ("ok", 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
print (h)
if attached res.body as b then
print (b)
end
end
end

View File

@@ -7,117 +7,44 @@
<exclude>/\.svn$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true">
</option>
<external_include location="$ECF_CONFIG_PATH/spec/include">
<condition>
<version type="compiler" min="16.9.9.9124"/>
</condition>
</external_include>
<external_include location="$ECF_CONFIG_PATH/spec/include_until_16_05">
<condition>
<version type="compiler" max="16.9.9.9124"/>
</condition>
</external_include>
<external_include location="$(ISE_LIBRARY)/unstable/library/network/socket/netssl/spec/include">
<condition>
<platform excluded_value="windows"/>
<version type="compiler" max="16.9.9.9124"/>
<custom name="ssl_enabled" value="true"/>
</condition>
</external_include>
<external_include location="$(ISE_LIBRARY)\unstable\library\network\socket\netssl\spec\include">
<condition>
<platform value="windows"/>
<version type="compiler" max="16.9.9.9124"/>
<custom name="ssl_enabled" value="true"/>
</condition>
</external_include>
<external_cflag value="-D_WINSOCKAPI_">
<condition>
<platform value="windows"/>
<version type="compiler" max="16.9.9.9124"/>
<custom name="ssl_enabled" value="true"/>
</condition>
</external_cflag>
<external_include location="$ECF_CONFIG_PATH/spec/include"/>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl.ecf">
<condition>
<custom name="ssl_enabled" value="true"/>
</condition>
</library>
<library name="openssl" location="$ISE_LIBRARY\unstable\library\network\openssl\openssl.ecf">
<condition>
<custom name="net_ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
<version type="compiler" min="17.10.0.0"/>
<custom name="ssl_enabled" value="true"/>
</condition>
</library>
<cluster name="network" location=".\src\">
<file_rule>
<exclude>/http_stream_socket_ext.e$</exclude>
<condition>
<version type="compiler" max="17.02"/>
</condition>
</file_rule>
<cluster name="disabled_ssl_network" location="$|no_ssl\" recursive="true">
<condition>
<custom name="ssl_enabled" excluded_value="true"/>
<custom name="net_ssl_enabled" excluded_value="true"/>
<custom name="httpd_ssl_enabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="ssl_network" location="$|ssl\" recursive="true">
<cluster name="ssl_network" location="$|ssl\" recursive="false">
<condition>
<custom name="ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="net_ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
</condition>
<file_rule>
<exclude>/http_stream_secure_socket_ext.e$</exclude>
<exclude>/http_stream_secure_socket_ext\.e$</exclude>
<condition>
<version type="compiler" max="16.9.9.9124"/>
<custom name="ssl_enabled" value="true"/>
</condition>
<condition>
<version type="compiler" max="16.9.9.9124"/>
<custom name="net_ssl_enabled" value="true"/>
</condition>
<condition>
<version type="compiler" max="16.9.9.9124"/>
<custom name="httpd_ssl_enabled" value="true"/>
<version type="compiler" max="18.05.0.0"/>
</condition>
</file_rule>
</cluster>
</cluster>
<cluster name="network_until_16_05" location=".\src\until_16_05\">
<condition>
<version type="compiler" max="16.9.9.9124"/>
</condition>
<cluster name="ssl_network_until_16_05" location="$|ssl\" recursive="true">
<cluster name="ssl_network_until_18_05" location="$|ssl\until_18.05" recursive="true">
<condition>
<version type="compiler" max="16.9.9.9124"/>
<custom name="ssl_enabled" value="true"/>
</condition>
<condition>
<version type="compiler" max="16.9.9.9124"/>
<custom name="net_ssl_enabled" value="true"/>
</condition>
<condition>
<version type="compiler" max="16.9.9.9124"/>
<custom name="httpd_ssl_enabled" value="true"/>
<version type="compiler" max="18.05.0.0"/>
</condition>
</cluster>
</cluster>
<cluster name="network_until_17_01" location=".\src\until_17_01\">
<condition>
<version type="compiler" min="16.9.9.9124" max="17.02"/>
</condition>
</cluster>
</target>
<target name="http_network_ssl" extends="http_network">
<variable name="ssl_enabled" value="true"/>

View File

@@ -1,52 +0,0 @@
/*
indexing
description: "Functions used by the EiffelWeb http networking classes. "
copyright: "Copyright (c) 2011-2016, Jocelyn Fiat, 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
]"
*/
#ifndef _ew_network_h_
#define _ew_network_h_
#include "eif_config.h"
#ifdef EIF_WINDOWS
# ifndef _WINSOCKAPI_
# define FD_SETSIZE 256
# include <winsock2.h>
# include <Ws2tcpip.h>
# include <stdio.h>
# endif
#else /* unix-specific */
# include <sys/socket.h>
# include <unistd.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* extern declarations ... */
#ifdef EIF_WINDOWS
extern int setsockopt(int, int, int, char*, int);
extern int recv(int, char *, int, int);
extern int send(int, char *, int, int);
#else
extern int setsockopt(int, int, int, const void*, socklen_t);
extern ssize_t recv(int, void *, size_t, int);
extern ssize_t send(int, const void *, size_t, int);
#endif
#ifdef __cplusplus
}
#endif
#endif

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