Merge remote-tracking branch 'jocelynEWF/master'

This commit is contained in:
jvelilla
2011-09-28 07:36:35 -03:00
31 changed files with 528 additions and 166 deletions

View File

@@ -1,9 +1,28 @@
History for Eiffel-Web-Framework
[2011-09-23] Jocelyn
* library "ewsgi":
- NEW simple autotest cases using Nino web server
-fixed issue with RAW_POST_DATA being added in form_data_parameters
instead of meta_variables ...
- Implemented WGI_VALUE for parameter's type (query_parameter,
form_data_parameter, item ...)
* Nino connector: added feature to shutdown the server from the WGI application
* NEW library "http_client": a new library to perform simple http requests
such as get, head, post, put, ... (currently implemented with Eiffel cURL)
* NEW library "http_authorization": added simple library to support
HTTP_AUTHORIZATION. For now only "Basic" auth type is supported ..
[2011-09-22] Javier
* NEW Example: added partial Restbuck example
[2011-09-21] Jocelyn
* Nino connector: fixed an issue with missing value for Content-Type and Content-Length
[2011-09-13] Jocelyn
* library "router": now using a generic design to allow customization of
request handler context class.
* library "server/request/rest": first attempt to provide a library to
* NEW library "server/request/rest": first attempt to provide a library to
help building RESTful application (the interfaces are likely to change
soon) EXPERIMENTAL

View File

@@ -30,6 +30,8 @@ feature {NONE} -- Initialization
create_router
do
-- (create {EXCEPTIONS}).raise ("ouch")
check False end
create router.make (5)
end
@@ -75,10 +77,10 @@ feature -- Execution
n: INTEGER
i: INTEGER
do
create h.make
l_url := req.script_url ("/home")
n := 3
h.put_refresh (l_url, 5, 200)
create h.make
h.put_refresh (l_url, 5)
res.set_status_code (200)
res.write_headers_string (h.string)
from

View File

@@ -55,7 +55,7 @@ feature -- Execution
create h.make
l_url := req.script_url ("/home")
n := 3
h.put_refresh (l_url, 5, 200)
h.put_refresh (l_url, 5)
res.set_status_code (200)
res.write_headers_string (h.string)
from

View File

@@ -0,0 +1 @@
Simple HTTP client implemented using Eiffel cURL library

View File

@@ -7,7 +7,7 @@
<exclude>/EIFGENs$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" full_class_checking="true" void_safety="none" syntax="standard">
<option warning="true" full_class_checking="true" void_safety="none" syntax="provisional">
</option>
<library name="base" location="$ISE_LIBRARY/library/base/base.ecf"/>
<library name="cURL" location="$ISE_LIBRARY/library/cURL/cURL.ecf"/>

View File

@@ -21,6 +21,17 @@ feature {NONE} -- Initialization
feature -- Status
error_occurred: BOOLEAN
-- Error occurred during request
feature {HTTP_CLIENT_REQUEST} -- Status setting
set_error_occurred (b: BOOLEAN)
-- Set `error_occurred' to `b'
do
error_occurred := b
end
feature -- Access
status: INTEGER assign set_status

View File

@@ -108,6 +108,11 @@ feature -- Change
timeout := n
end
set_connect_timeout (n: like connect_timeout)
do
connect_timeout := n
end
set_user_agent (v: READABLE_STRING_8)
do
add_header ("User-Agent", v)

View File

@@ -15,7 +15,6 @@ inherit
session
end
create
make
@@ -174,19 +173,20 @@ feature -- Execution
create Result.make
l_result := curl_easy.perform (curl_handle)
create a_data.put (Void)
l_result := curl_easy.getinfo (curl_handle, {CURL_INFO_CONSTANTS}.curlinfo_response_code, a_data)
if l_result = 0 and then attached {INTEGER} a_data.item as l_http_status then
Result.status := l_http_status
if l_result = {CURL_CODES}.curle_ok then
create a_data.put (Void)
l_result := curl_easy.getinfo (curl_handle, {CURL_INFO_CONSTANTS}.curlinfo_response_code, a_data)
if l_result = 0 and then attached {INTEGER} a_data.item as l_http_status then
Result.status := l_http_status
else
Result.status := 0
end
Result.body := l_curl_string.string
else
Result.status := 0
Result.set_error_occurred (True)
end
-- last_api_call := l_url
curl_easy.cleanup (curl_handle)
Result.body := l_curl_string.string
end
end

View File

@@ -7,42 +7,87 @@ note
class
HTTP_AUTHORIZATION
inherit
REFACTORING_HELPER
create
make
make,
make_basic_auth,
make_custom_auth
feature {NONE} -- Initialization
make (a_http_authorization: detachable READABLE_STRING_GENERAL)
-- Initialize `Current'.
local
p: INTEGER
s: STRING_8
i: INTEGER
t, s: STRING_8
u,p: READABLE_STRING_8
do
if attached a_http_authorization as l_auth then
s := l_auth.as_string_8
if not s.is_empty then
p := 1
if s[p] = ' ' then
p := p + 1
i := 1
if s[i] = ' ' then
i := i + 1
end
p := s.index_of (' ', p)
if p > 0 then
s := (create {BASE64}).decoded_string (s.substring (p + 1, s.count))
p := s.index_of (':', 1) --| Let's assume ':' is forbidden in login ...
if p > 0 then
login := s.substring (1, p - 1).as_string_32
password := s.substring (p + 1, s.count).as_string_32
i := s.index_of (' ', i)
if i > 0 then
t := s.substring (1, i - 1).as_lower
t.right_adjust; t.left_adjust
type := t
if t.same_string ("basic") then
s := (create {BASE64}).decoded_string (s.substring (i + 1, s.count))
i := s.index_of (':', 1) --| Let's assume ':' is forbidden in login ...
if i > 0 then
u := s.substring (1, i - 1).as_string_32
p := s.substring (i + 1, s.count).as_string_32
login := u
password := p
check
(create {HTTP_AUTHORIZATION}.make_custom_auth (u, p, t)).http_authorization ~ http_authorization
end
end
elseif t.same_string ("digest") then
to_implement ("HTTP Authorization %"digest%", not yet implemented")
else
to_implement ("HTTP Authorization %""+ t +"%", not yet implemented")
end
end
end
end
end
make_basic_auth (u: READABLE_STRING_32; p: READABLE_STRING_32)
do
make_custom_auth (u, p, "basic")
end
make_custom_auth (u: READABLE_STRING_32; p: READABLE_STRING_32; a_type: READABLE_STRING_8)
local
t: STRING_8
do
login := u
password := p
create t.make_from_string (a_type.as_lower)
t.left_adjust; t.right_adjust
type := t
if t.same_string ("basic") then
http_authorization := "Basic " + (create {BASE64}).encoded_string (u + ":" + p)
else
to_implement ("HTTP Authorization %""+ t +"%", not yet implemented")
end
end
feature -- Access
type: detachable READABLE_STRING_8
login: detachable READABLE_STRING_32
password: detachable READABLE_STRING_32
http_authorization: detachable READABLE_STRING_32
end

View File

@@ -53,6 +53,12 @@ feature -- Server
connector.launch
end
shutdown
-- Shutdown the server
do
connector.server.shutdown_server
end
note
copyright: "2011-2011, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"

View File

@@ -14,6 +14,7 @@ inherit
create
make_with_value,
make_with_array,
make_with_string
feature {NONE} -- Initialization
@@ -25,6 +26,26 @@ feature {NONE} -- Initialization
add_value (a_value)
end
make_with_array (arr: ARRAY [WGI_VALUE])
require
arr_not_empty: not arr.is_empty
all_same_name: across arr as c all c.item.name.same_string (arr[arr.lower].name) end
local
i,up: INTEGER
do
up := arr.upper
i := arr.lower
make_with_value (arr[i])
from
i := i + 1
until
i > up
loop
add_value (arr[i])
i := i + 1
end
end
make_with_string (a_name: like name; a_string: READABLE_STRING_32)
do
make_with_value (create {WGI_STRING_VALUE}.make (a_name, a_string))
@@ -75,7 +96,7 @@ feature -- Helper
across
string_values as c
loop
if Result.count = 1 then
if Result.count > 1 then
Result.append_character (',')
end
Result.append_string (c.item)

View File

@@ -106,7 +106,7 @@ feature -- Access: CGI meta variables
end
end
meta_variables: ITERATION_CURSOR [WGI_VALUE]
meta_variables: ITERABLE [WGI_VALUE]
-- These variables are specific to requests made with HTTP.
-- Interpretation of these variables may depend on the value of
-- SERVER_PROTOCOL.
@@ -587,7 +587,7 @@ feature -- Extra CGI environment variables
feature -- Query string Parameters
query_parameters: ITERATION_CURSOR [WGI_VALUE]
query_parameters: ITERABLE [WGI_VALUE]
-- Variables extracted from QUERY_STRING
deferred
end
@@ -601,7 +601,7 @@ feature -- Query string Parameters
feature -- Form fields and related
form_data_parameters: ITERATION_CURSOR [WGI_VALUE]
form_data_parameters: ITERABLE [WGI_VALUE]
-- Variables sent by POST request
deferred
end
@@ -626,7 +626,7 @@ feature -- Form fields and related
feature -- Cookies
cookies: ITERATION_CURSOR [WGI_VALUE]
cookies: ITERABLE [WGI_VALUE]
-- Expanded cookies variable
deferred
end
@@ -638,7 +638,7 @@ feature -- Cookies
deferred
end
feature -- Access: global variable
feature -- Access: all variables
parameters: like items
obsolete "use items"
@@ -652,7 +652,7 @@ feature -- Access: global variable
Result := item (a_name)
end
items: ITERATION_CURSOR [WGI_VALUE]
items: ITERABLE [WGI_VALUE]
-- Table containing all the various variables
-- Warning: this is computed each time, if you change the content of other containers
-- this won't update this Result's content, unless you query it again

View File

@@ -37,7 +37,7 @@ feature -- Status report
feature {WGI_RESPONSE_BUFFER} -- Core output operation
write (s: STRING)
write (s: READABLE_STRING_8)
-- Send the string `s'
-- this can be used for header and body
deferred
@@ -69,7 +69,7 @@ feature -- Status setting
feature -- Header output operation
write_headers_string (a_headers: STRING)
write_headers_string (a_headers: READABLE_STRING_8)
require
status_set: status_is_set
header_not_committed: not header_committed
@@ -80,7 +80,7 @@ feature -- Header output operation
message_writable: message_writable
end
write_header (a_status_code: INTEGER; a_headers: detachable ARRAY [TUPLE [key: STRING; value: STRING]])
write_header (a_status_code: INTEGER; a_headers: detachable ARRAY [TUPLE [key: READABLE_STRING_8; value: READABLE_STRING_8]])
-- Send headers with status `a_status', and headers from `a_headers'
require
status_not_set: not status_is_set
@@ -94,21 +94,21 @@ feature -- Header output operation
feature -- Output operation
write_string (s: STRING)
write_string (s: READABLE_STRING_8)
-- Send the string `s'
require
message_writable: message_writable
deferred
end
write_substring (s: STRING; a_begin_index, a_end_index: INTEGER)
write_substring (s: READABLE_STRING_8; a_begin_index, a_end_index: INTEGER)
-- Send the substring `s[a_begin_index:a_end_index]'
require
message_writable: message_writable
deferred
end
write_file_content (fn: STRING)
write_file_content (fn: READABLE_STRING_8)
-- Send the content of file `fn'
require
message_writable: message_writable

View File

@@ -16,14 +16,13 @@ feature -- Execution
execute (req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
-- Execute the request
-- See `req.input' for input stream
-- `req.environment' for the Gateway environment
-- `req.meta_variables' for the CGI meta variable
-- and `res' for output buffer
require
res_status_unset: not res.status_is_set
deferred
ensure
res_status_set: res.status_is_set
-- res_committed: res.message_committed
end
feature -- Process request

View File

@@ -174,9 +174,9 @@ feature {NONE} -- Access: CGI meta parameters
feature -- Access: CGI meta parameters
meta_variables: ITERATION_CURSOR [WGI_VALUE]
meta_variables: ITERABLE [WGI_VALUE]
do
Result := meta_variables_table.new_cursor
Result := meta_variables_table
end
meta_variable (a_name: READABLE_STRING_GENERAL): detachable WGI_VALUE
@@ -429,9 +429,9 @@ feature {NONE} -- Query parameters
feature -- Query parameters
query_parameters: ITERATION_CURSOR [WGI_VALUE]
query_parameters: ITERABLE [WGI_VALUE]
do
Result := query_parameters_table.new_cursor
Result := query_parameters_table
end
query_parameter (a_name: READABLE_STRING_GENERAL): detachable WGI_VALUE
@@ -448,6 +448,7 @@ feature {NONE} -- Query parameters: implementation
n, p, i, j: INTEGER
s: STRING
l_name,l_value: STRING_32
v: WGI_VALUE
do
if a_content = Void then
create Result.make (0)
@@ -479,7 +480,17 @@ feature {NONE} -- Query parameters: implementation
l_name := url_encoder.decoded_string (l_name)
l_value := url_encoder.decoded_string (l_value)
end
Result.force (new_string_value (l_name, l_value), l_name)
v := new_string_value (l_name, l_value)
if Result.has_key (l_name) and then attached Result.found_item as l_existing_value then
if attached {WGI_MULTIPLE_STRING_VALUE} l_existing_value as l_multi then
l_multi.add_value (v)
else
Result.force (create {WGI_MULTIPLE_STRING_VALUE}.make_with_array (<<l_existing_value, v>>), l_name)
check replaced: Result.found and then Result.found_item ~ l_existing_value end
end
else
Result.force (v, l_name)
end
end
end
end
@@ -515,7 +526,7 @@ feature {NONE} -- Form fields and related
vars := urlencoded_parameters (s, True)
end
if raw_post_data_recorded then
vars.force (new_string_value ("RAW_POST_DATA", s), "RAW_POST_DATA")
set_meta_string_variable ("RAW_POST_DATA", s)
end
else
create vars.make (0)
@@ -527,9 +538,9 @@ feature {NONE} -- Form fields and related
feature -- Form fields and related
form_data_parameters: ITERATION_CURSOR [WGI_VALUE]
form_data_parameters: ITERABLE [WGI_VALUE]
do
Result := form_data_parameters_table.new_cursor
Result := form_data_parameters_table
end
form_data_parameter (a_name: READABLE_STRING_GENERAL): detachable WGI_VALUE
@@ -595,9 +606,9 @@ feature {NONE} -- Cookies
feature -- Cookies
cookies: ITERATION_CURSOR [WGI_VALUE]
cookies: ITERABLE [WGI_VALUE]
do
Result := cookies_table.new_cursor
Result := cookies_table
end
cookie (a_name: READABLE_STRING_GENERAL): detachable WGI_VALUE
@@ -612,57 +623,40 @@ feature {NONE} -- Access: global variable
-- Table containing all the various variables
-- Warning: this is computed each time, if you change the content of other containers
-- this won't update this Result's content, unless you query it again
local
vars: ITERATION_CURSOR [WGI_VALUE]
do
create Result.make (100)
vars := meta_variables
from
-- vars.start
until
vars.after
across
meta_variables as vars
loop
Result.force (vars.item, vars.item.name)
vars.forth
end
vars := query_parameters
from
-- vars.start
until
vars.after
across
query_parameters as vars
loop
Result.force (vars.item, vars.item.name)
vars.forth
end
vars := form_data_parameters
from
-- vars.start
until
vars.after
across
form_data_parameters as vars
loop
Result.force (vars.item, vars.item.name)
vars.forth
end
vars := cookies
from
-- vars.start
until
vars.after
across
cookies as vars
loop
Result.force (vars.item, vars.item.name)
vars.forth
end
end
feature -- Access: global variable
items: ITERATION_CURSOR [WGI_VALUE]
items: ITERABLE [WGI_VALUE]
do
Result := items_table.new_cursor
Result := items_table
end
item (a_name: READABLE_STRING_GENERAL): detachable WGI_VALUE

View File

@@ -48,7 +48,7 @@ feature -- Status report
feature {NONE} -- Core output operation
write (s: STRING)
write (s: READABLE_STRING_8)
-- Send the content of `s'
-- this can be used for header and body
do
@@ -76,13 +76,13 @@ feature -- Status setting
feature -- Header output operation
write_headers_string (a_headers: STRING)
write_headers_string (a_headers: READABLE_STRING_8)
do
write (a_headers)
header_committed := True
end
write_header (a_status_code: INTEGER; a_headers: detachable ARRAY [TUPLE [key: STRING; value: STRING]])
write_header (a_status_code: INTEGER; a_headers: detachable ARRAY [TUPLE [key: READABLE_STRING_8; value: READABLE_STRING_8]])
-- Send headers with status `a_status', and headers from `a_headers'
local
h: EWF_HEADER
@@ -107,20 +107,20 @@ feature -- Header output operation
feature -- Output operation
write_string (s: STRING)
write_string (s: READABLE_STRING_8)
-- Send the string `s'
do
write (s)
end
write_substring (s: STRING; start_index, end_index: INTEGER)
write_substring (s: READABLE_STRING_8; start_index, end_index: INTEGER)
-- Send the substring `start_index:end_index]'
--| Could be optimized according to the target output
do
output.put_substring (s, start_index, end_index)
end
write_file_content (fn: STRING)
write_file_content (fn: READABLE_STRING_8)
-- Send the content of file `fn'
do
output.put_file_content (fn)

View File

@@ -28,7 +28,7 @@ feature {NONE} -- Initialization
make
-- Initialize current
do
create {ARRAYED_LIST [STRING]} headers.make (3)
create {ARRAYED_LIST [READABLE_STRING_8]} headers.make (3)
end
feature -- Recycle
@@ -40,7 +40,7 @@ feature -- Recycle
feature -- Access
headers: LIST [STRING]
headers: LIST [READABLE_STRING_8]
-- Header's lines
string: STRING
@@ -67,24 +67,24 @@ feature -- Access
feature -- Header change: general
add_header (h: STRING)
add_header (h: READABLE_STRING_8)
do
headers.force (h)
end
put_header (h: STRING)
put_header (h: READABLE_STRING_8)
-- Add header `h' or replace existing header of same header name
do
force_header_by_name (header_name (h), h)
end
add_header_key_value (k,v: STRING)
add_header_key_value (k,v: READABLE_STRING_8)
-- Add header `k:v', or replace existing header of same header name/key
do
add_header (k + colon_space + v)
end
put_header_key_value (k,v: STRING)
put_header_key_value (k,v: READABLE_STRING_8)
-- Add header `k:v', or replace existing header of same header name/key
do
put_header (k + colon_space + v)
@@ -104,23 +104,23 @@ feature -- Content related header
put_header_key_value ("Status", s)
end
put_content_type (t: STRING)
put_content_type (t: READABLE_STRING_8)
do
put_header_key_value (name_content_type, t)
end
add_content_type (t: STRING)
add_content_type (t: READABLE_STRING_8)
-- same as `put_content_type', but allow multiple definition of "Content-Type"
do
add_header_key_value (name_content_type, t)
end
put_content_type_with_name (t: STRING; n: STRING)
put_content_type_with_name (t: READABLE_STRING_8; n: READABLE_STRING_8)
do
put_header_key_value (name_content_type, t + "; name=%"" + n + "%"")
end
add_content_type_with_name (t: STRING; n: STRING)
add_content_type_with_name (t: READABLE_STRING_8; n: READABLE_STRING_8)
-- same as `put_content_type_with_name', but allow multiple definition of "Content-Type"
do
add_header_key_value (name_content_type, t + "; name=%"" + n + "%"")
@@ -158,7 +158,7 @@ feature -- Content related header
put_header_key_value (name_content_length, n.out)
end
put_content_transfer_encoding (a_mechanism: STRING)
put_content_transfer_encoding (a_mechanism: READABLE_STRING_8)
-- Put "Content-Transfer-Encoding" header with for instance "binary"
--| encoding := "Content-Transfer-Encoding" ":" mechanism
--|
@@ -173,7 +173,7 @@ feature -- Content related header
put_header_key_value ("Content-Transfer-Encoding", a_mechanism)
end
put_content_disposition (a_type: STRING; a_params: detachable STRING)
put_content_disposition (a_type: READABLE_STRING_8; a_params: detachable READABLE_STRING_8)
-- Put "Content-Disposition" header
--| See RFC2183
--| disposition := "Content-Disposition" ":"
@@ -212,13 +212,13 @@ feature -- Others
put_header_key_value ("Expires", n.out)
end
put_cache_control (s: STRING)
put_cache_control (s: READABLE_STRING_8)
-- `s' could be for instance "no-cache, must-revalidate"
do
put_header_key_value ("Cache-Control", s)
end
put_pragma (s: STRING)
put_pragma (s: READABLE_STRING_8)
do
put_header_key_value ("Pragma", s)
end
@@ -230,13 +230,13 @@ feature -- Others
feature -- Redirection
put_redirection (a_location: STRING; a_code: INTEGER)
-- Tell the client to redirect to page with `a_location' right away
put_location (a_location: READABLE_STRING_8)
-- Tell the client the new location `a_location'
do
put_header_key_value ("Location", a_location)
end
put_refresh (a_location: STRING; a_timeout: INTEGER; a_code: INTEGER)
put_refresh (a_location: READABLE_STRING_8; a_timeout: INTEGER)
-- Tell the client to refresh page with `a_location' after `a_timeout' in seconds
do
put_header_key_value ("Refresh", a_timeout.out + "; url=" + a_location)
@@ -244,7 +244,7 @@ feature -- Redirection
feature -- Cookie
put_cookie (key, value: STRING_8; expiration, path, domain, secure: detachable STRING_8)
put_cookie (key, value: READABLE_STRING_8; expiration, path, domain, secure: detachable READABLE_STRING_8)
-- Set a cookie on the client's machine
-- with key 'key' and value 'value'.
require
@@ -270,7 +270,7 @@ feature -- Cookie
feature -- Status report
has_header_named (a_name: STRING): BOOLEAN
has_header_named (a_name: READABLE_STRING_8): BOOLEAN
-- Has header item for `n'?
local
c: like headers.new_cursor
@@ -295,7 +295,7 @@ feature -- Status report
feature {NONE} -- Implementation: Header
force_header_by_name (n: detachable STRING; h: STRING)
force_header_by_name (n: detachable READABLE_STRING_8; h: READABLE_STRING_8)
-- Add header `h' or replace existing header of same header name `n'
require
h_has_name_n: (n /= Void and attached header_name (h) as hn) implies n.same_string (hn)
@@ -321,36 +321,38 @@ feature {NONE} -- Implementation: Header
end
end
header_name (h: STRING): detachable STRING
header_name (h: READABLE_STRING_8): detachable READABLE_STRING_8
-- If any, header's name with colon
--| ex: for "Foo-bar: something", this will return "Foo-bar:"
local
s: detachable STRING_8
i,n: INTEGER
c: CHARACTER
do
from
i := 1
n := h.count
create Result.make (10)
create s.make (10)
until
i > n or c = ':' or Result = Void
i > n or c = ':' or s = Void
loop
c := h[i]
inspect c
when ':' then
Result.extend (c)
s.extend (c)
when '-', 'a' .. 'z', 'A' .. 'Z' then
Result.extend (c)
s.extend (c)
else
Result := Void
s := Void
end
i := i + 1
end
Result := s
end
feature {NONE} -- Implementation
append_line_to (s, h: STRING)
append_line_to (s: READABLE_STRING_8; h: like string)
do
h.append_string (s)
append_end_of_line_to (h)

View File

@@ -0,0 +1,208 @@
note
description: "[
Eiffel tests that can be executed by testing tool.
]"
author: "EiffelStudio test wizard"
date: "$Date$"
revision: "$Revision$"
testing: "type/manual"
class
TEST_EWSGI_REQUEST
inherit
EQA_TEST_SET
redefine
on_prepare,
on_clean
end
feature {NONE} -- Events
web_app: detachable NINO_APPLICATION
port_number: INTEGER
base_url: detachable STRING
on_prepare
-- <Precursor>
local
app: NINO_APPLICATION
wt: WORKER_THREAD
e: EXECUTION_ENVIRONMENT
do
port_number := 8087
base_url := "test/"
create app.make_custom (agent execute, base_url)
web_app := app
create wt.make (agent app.listen (port_number))
wt.launch
create e
e.sleep (1_000_000_000 * 5)
end
execute (req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
local
q: detachable STRING_32
do
if attached req.request_uri as l_uri then
if l_uri.starts_with (test_url ("get/01")) then
res.write_header (200, <<["Content-Type", "text/plain"]>>)
res.write_string ("get-01")
create q.make_empty
across
req.query_parameters as qcur
loop
if not q.is_empty then
q.append_character ('&')
end
q.append (qcur.item.name.as_string_32 + "=" + qcur.item.as_string)
end
if not q.is_empty then
res.write_string ("(" + q + ")")
end
elseif l_uri.starts_with (test_url ("post/01")) then
res.write_header (200, <<["Content-Type", "text/plain"]>>)
res.write_string ("post-01")
create q.make_empty
across
req.query_parameters as qcur
loop
if not q.is_empty then
q.append_character ('&')
end
q.append (qcur.item.name.as_string_32 + "=" + qcur.item.as_string)
end
if not q.is_empty then
res.write_string ("(" + q + ")")
end
create q.make_empty
across
req.form_data_parameters as fcur
loop
if not q.is_empty then
q.append_character ('&')
end
q.append (fcur.item.name.as_string_32 + "=" + fcur.item.as_string)
end
if not q.is_empty then
res.write_string (" : " + q )
end
else
res.write_header (200, <<["Content-Type", "text/plain"]>>)
res.write_string ("Hello")
end
else
res.write_header (200, <<["Content-Type", "text/plain"]>>)
res.write_string ("Bye")
end
end
test_url (a_query_url: READABLE_STRING_8): READABLE_STRING_8
local
b: like base_url
do
b := base_url
if b = Void then
b := ""
end
Result := "/" + b + a_query_url
end
on_clean
-- <Precursor>
do
if attached web_app as app then
app.shutdown
end
end
http_session: detachable HTTP_CLIENT_SESSION
get_http_session
local
h: LIBCURL_HTTP_CLIENT
b: like base_url
do
create h.make
b := base_url
if b = Void then
b := ""
end
if attached {HTTP_CLIENT_SESSION} h.new_session ("localhost:" + port_number.out + "/" + b) as sess then
http_session := sess
sess.set_timeout (-1)
sess.set_connect_timeout (-1)
end
end
test_get_request (a_url: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; a_expected_body: READABLE_STRING_8)
do
get_http_session
if attached http_session as sess then
if attached sess.get (a_url, ctx) as res and then not res.error_occurred and then attached res.body as l_body then
assert ("Good answer got=%""+l_body+"%" expected=%""+a_expected_body+"%"", l_body.same_string (a_expected_body))
else
assert ("Request %""+a_url+"%" failed", False)
end
end
end
test_post_request (a_url: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; a_expected_body: READABLE_STRING_8)
do
get_http_session
if attached http_session as sess then
if attached sess.post (a_url, ctx) as res and then not res.error_occurred and then attached res.body as l_body then
assert ("Good answer got=%""+l_body+"%" expected=%""+a_expected_body+"%"", l_body.same_string (a_expected_body))
else
assert ("Request %""+a_url+"%" failed", False)
end
end
end
feature -- Test routines
test_get_request_01
-- New test routine
do
get_http_session
if attached http_session as sess then
test_get_request ("get/01", Void, "get-01")
test_get_request ("get/01/?foo=bar", Void, "get-01(foo=bar)")
test_get_request ("get/01/?foo=bar&abc=def", Void, "get-01(foo=bar&abc=def)")
test_get_request ("get/01/?lst=a&lst=b", Void, "get-01(lst=[a,b])")
else
assert ("not_implemented", False)
end
end
test_post_request_01
-- New test routine
local
ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
get_http_session
if attached http_session as sess then
create ctx.make
ctx.add_form_data_parameter ("id", "123")
test_post_request ("post/01", ctx, "post-01 : id=123")
test_post_request ("post/01/?foo=bar", ctx, "post-01(foo=bar) : id=123")
test_post_request ("post/01/?foo=bar&abc=def", ctx, "post-01(foo=bar&abc=def) : id=123")
test_post_request ("post/01/?lst=a&lst=b", ctx, "post-01(lst=[a,b]) : id=123")
else
assert ("not_implemented", False)
end
end
end

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-8-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-8-0 http://www.eiffel.com/developers/xml/configuration-1-8-0.xsd" name="tests" uuid="4C432110-F2DA-4D69-B80B-268CC1253B78">
<target name="tests">
<root class="ANY" feature="default_create"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="ewsgi_nino" location="..\default\ewsgi_nino-safe.ecf" readonly="false"/>
<library name="ewsgi" location="..\ewsgi-safe.ecf" readonly="false"/>
<library name="http_client" location="..\..\..\client\http_client\http_client-safe.ecf"/>
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread-safe.ecf"/>
<tests name="src" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-8-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-8-0 http://www.eiffel.com/developers/xml/configuration-1-8-0.xsd" name="tests" uuid="4C432110-F2DA-4D69-B80B-268CC1253B78">
<target name="tests">
<root class="ANY" feature="default_create"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" full_class_checking="true" void_safety="none" syntax="standard">
</option>
<setting name="concurrency" value="none"/>
<library name="ewsgi_spec" location="..\ewsgi_spec.ecf"/>
<library name="ewsgi_nino" location="..\default\ewsgi_nino.ecf"/>
<library name="base" location="$ISE_LIBRARY/library/base/base.ecf"/>
<library name="testing" location="$ISE_LIBRARY/library/testing/testing.ecf"/>
<tests name="src" location="." recursive="true"/>
</target>
</system>

View File

@@ -25,7 +25,14 @@ create
feature -- Status report
authentication_required: BOOLEAN
authentication_required (req: WGI_REQUEST): BOOLEAN
do
Result := internal_authentication_required
end
feature {NONE} -- Implementation
internal_authentication_required: BOOLEAN
feature -- Execution

View File

@@ -63,9 +63,9 @@ feature -- Execution
do
content_type_supported := <<{HTTP_CONSTANTS}.json_app, {HTTP_CONSTANTS}.xml_text, {HTTP_CONSTANTS}.plain_text>>
l_format_id := ctx.request_format_id ("format", content_type_supported)
if ctx.authenticated then
if authenticated (ctx) then
l_full := attached ctx.query_parameter ("details") as v and then v.is_case_insensitive_equal ("true")
if attached ctx.authenticated_identifier as log then
if attached authenticated_identifier (ctx) as log then
l_login := log.as_string_8
create h.make

View File

@@ -65,12 +65,12 @@ feature -- Execution
(create {DEVELOPER_EXCEPTION}).raise
elseif l_op.starts_with ("env") then
s.append_string ("%N%NAll variables:")
s.append (wgi_value_iteration_to_string (req.parameters, False))
s.append (wgi_value_iteration_to_string (req.items, False))
s.append_string ("<br/>script_url(%"" + req.path_info + "%")=" + ctx.script_url (req.path_info) + "%N")
-- if attached ctx.http_authorization_login_password as t then
-- s.append_string ("Check login=" + t.login + "<br/>%N")
-- end
if ctx.authenticated and then attached ctx.authenticated_identifier as l_login then
if authenticated (ctx) and then attached authenticated_identifier (ctx) as l_login then
s.append_string ("Authenticated: login=" + l_login.as_string_8 + "<br/>%N")
end
end

View File

@@ -83,15 +83,15 @@ feature -- Execution
l_redir_url: STRING
do
create h.make
-- h.put_refresh (ctx.script_url ("/doc"), 2, {HTTP_STATUS_CODE}.temp_redirect)
-- h.put_refresh (ctx.script_url ("/doc"), 2)
l_redir_url := "/doc"
h.put_refresh (l_redir_url, 2, {HTTP_STATUS_CODE}.temp_redirect)
h.put_refresh (l_redir_url, 2)
h.put_content_type_text_html
create s.make_empty
s := "Request [" + req.path_info + "] is not available. <br/>%N";
s.append ("You are being redirected to <a href=%"" + l_redir_url + "%">/doc</a> in 2 seconds ...%N")
h.put_content_length (s.count)
res.set_status_code (200)
res.set_status_code ({HTTP_STATUS_CODE}.temp_redirect)
res.write_headers_string (h.string)
res.write_string (s)
end

View File

@@ -14,6 +14,9 @@ inherit
end
REST_REQUEST_AGENT_HANDLER [APP_REQUEST_HANDLER_CONTEXT]
undefine
authenticated
end
create
make

View File

@@ -8,6 +8,9 @@ deferred class
inherit
REST_REQUEST_HANDLER [APP_REQUEST_HANDLER_CONTEXT]
redefine
authenticated
end
APP_REQUEST_HELPER
@@ -17,27 +20,43 @@ feature {NONE} -- Initialization
-- Initialize various attributes
do
end
feature {NONE} -- Implementation
wgi_value_iteration_to_string (cur: ITERATION_CURSOR [WGI_VALUE]; using_pre: BOOLEAN): STRING_8
wgi_value_iteration_to_string (v: ITERABLE [WGI_VALUE]; using_pre: BOOLEAN): STRING_8
do
create Result.make (100)
if using_pre then
Result.append ("<pre>")
end
from
until
cur.after
across
v as cur
loop
Result.append_string (cur.item.name.as_string_8 + " = " + cur.item.as_string.as_string_8 + "%N")
cur.forth
end
if using_pre then
Result.append ("</pre>")
end
end
feature -- Auth
authenticated (ctx: APP_REQUEST_HANDLER_CONTEXT): BOOLEAN
-- Is authenticated?
do
--| To redefine if needed
if attached ctx.request.http_authorization as l_http_authorization then
Result := True
end
end
authenticated_identifier (ctx: APP_REQUEST_HANDLER_CONTEXT): detachable READABLE_STRING_32
do
if attached ctx.request.http_authorization as l_http_authorization then
Result := "foo" -- Implement it as you want
end
end
feature -- Helpers
format_id (s: detachable STRING): INTEGER

View File

@@ -9,29 +9,10 @@ class
inherit
REST_REQUEST_URI_TEMPLATE_HANDLER_CONTEXT
redefine
authenticated,
authenticated_identifier
end
create
make
feature -- Auth
authenticated: BOOLEAN
do
if attached request.http_authorization as l_http_auth then
Result := True
end
end
authenticated_identifier: detachable READABLE_STRING_32
do
if authenticated then
Result := "foo"
end
end
feature -- Format

View File

@@ -10,12 +10,14 @@ class
inherit
APP_REQUEST_HANDLER
undefine
execute,
pre_execute,
post_execute
execute
end
REST_REQUEST_URI_TEMPLATE_ROUTING_HANDLER_I [APP_REQUEST_HANDLER, APP_REQUEST_HANDLER_CONTEXT]
undefine
authenticated,
pre_execute,
post_execute
redefine
router
end

View File

@@ -14,31 +14,28 @@ feature -- Helper
execute_content_type_not_allowed (req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER; a_content_types: detachable ARRAY [STRING]; a_uri_formats: detachable ARRAY [STRING])
local
s, uri_s: detachable STRING
accept_s, uri_s: detachable STRING
i, n: INTEGER
h: EWF_HEADER
do
create h.make
h.put_status ({HTTP_STATUS_CODE}.unsupported_media_type)
h.put_content_type_text_plain
if a_content_types /= Void then
create s.make (10)
create accept_s.make (10)
from
i := a_content_types.lower
n := a_content_types.upper
until
i > n
loop
s.append_string (a_content_types[i])
accept_s.append_string (a_content_types[i])
if i < n then
s.append_character (',')
s.append_character (' ')
accept_s.append_character (',')
accept_s.append_character (' ')
end
i := i + 1
end
h.put_header_key_value ("Accept", s)
else
accept_s := "*/*"
end
if a_uri_formats /= Void then
create uri_s.make (10)
from
@@ -56,9 +53,9 @@ feature -- Helper
end
end
res.set_status_code ({HTTP_STATUS_CODE}.unsupported_media_type)
res.write_headers_string (h.string)
if s /= Void then
res.write_string ("Unsupported request content-type, Accept: " + s + "%N")
res.write_header ({HTTP_STATUS_CODE}.unsupported_media_type, << ["Content-Type", "text/plain"], ["Accept", accept_s]>>)
if accept_s /= Void then
res.write_string ("Unsupported request content-type, Accept: " + accept_s + "%N")
end
if uri_s /= Void then
res.write_string ("Unsupported request format from the URI: " + uri_s + "%N")