Added first draft for RESTful library

note: the interfaces are likely to change in the future
This commit is contained in:
Jocelyn Fiat
2011-09-13 17:08:40 +02:00
parent 92105ca7b3
commit 512f2d2ce5
49 changed files with 2972 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
note
description: "Summary description for {APP_ACCOUNT_VERIFY_CREDENTIAL}."
date: "$Date$"
revision: "$Revision$"
class
APP_ACCOUNT_VERIFY_CREDENTIAL
inherit
APP_REQUEST_HANDLER
redefine
initialize,
execute_unauthorized
end
create
make
feature {NONE} -- Initialization
make
do
description := "Verify credentials"
initialize
end
initialize
do
Precursor
enable_request_method_get
enable_format_json
enable_format_xml
enable_format_text
end
feature -- Access
authentication_required: BOOLEAN = True
feature -- Execution
execute_unauthorized (a_hdl_context: APP_REQUEST_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
local
s: STRING
lst: LIST [STRING]
do
res.set_status_code ({HTTP_STATUS_CODE}.unauthorized)
res.write_header ({HTTP_STATUS_CODE}.unauthorized, <<["WWW-Authenticate", "Basic realm=%"My Silly demo auth, password must be the same as login such as foo:foo%""]>>)
res.write_string ("Unauthorized")
end
execute_application (ctx: APP_REQUEST_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
local
l_full: BOOLEAN
h: EWF_HEADER
l_login: STRING_8
s: STRING
content_type_supported: ARRAY [STRING]
l_format_id: INTEGER
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
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
l_login := log.as_string_8
create h.make
create s.make_empty
inspect l_format_id
when {HTTP_FORMAT_CONSTANTS}.json then
h.put_content_type_text_plain
s.append_string ("{ %"login%": %"" + l_login + "%" }%N")
when {HTTP_FORMAT_CONSTANTS}.xml then
h.put_content_type_text_xml
s.append_string ("<login>" + l_login + "</login>%N")
when {HTTP_FORMAT_CONSTANTS}.text then -- Default
h.put_content_type_text_plain
s.append_string ("login: " + l_login + "%N")
else
execute_content_type_not_allowed (req, res, content_type_supported,
<<{HTTP_FORMAT_CONSTANTS}.json_name, {HTTP_FORMAT_CONSTANTS}.html_name, {HTTP_FORMAT_CONSTANTS}.xml_name, {HTTP_FORMAT_CONSTANTS}.text_name>>
)
end
if not s.is_empty then
res.set_status_code ({HTTP_STATUS_CODE}.ok)
res.write_headers_string (h.string)
res.write_string (s)
end
else
send_error (ctx.path, 0, "User/password unknown", Void, ctx, req, res)
end
else
send_error (ctx.path, 0, "Authentication rejected", Void, ctx, req, res)
end
end
note
copyright: "Copyright (c) 1984-2011, 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,95 @@
note
description: "Summary description for {APP_TEST}."
date: "$Date$"
revision: "$Revision$"
class
APP_TEST
inherit
APP_REQUEST_HANDLER
redefine
initialize
end
create
make
feature {NONE} -- Initialization
make
do
description := "Return a simple test output "
initialize
end
initialize
do
Precursor
enable_request_method_get
enable_format_text
end
feature -- Access
authentication_required: BOOLEAN = False
feature -- Execution
execute_application (ctx: APP_REQUEST_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
-- Execute request handler
local
s: STRING
h: EWF_HEADER
do
create h.make
h.put_content_type_text_plain
create s.make_empty
s.append_string ("test")
if attached req.meta_variable ("REQUEST_COUNT") as l_request_count then
s.append_string ("(request_count="+ l_request_count +")%N")
end
-- ctx.request_format_id ("format", Void)
if attached ctx.request_format ("format", Void) as l_format then
s.append_string (" format=" + l_format + "%N")
end
if attached ctx.parameter ("op") as l_op then
s.append_string (" op=" + l_op)
if l_op.same_string ("crash") then
(create {DEVELOPER_EXCEPTION}).raise
elseif l_op.starts_with ("env") then
s.append_string ("%N%NAll variables:")
s.append (string_hash_table_string_string (req.parameters.new_cursor, 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
s.append_string ("Authenticated: login=" + l_login.as_string_8 + "<br/>%N")
end
end
else
s.append ("%N Try " + ctx.script_absolute_url (req.path_info + "?op=env") + " to display all variables%N")
s.append ("%N Try " + ctx.script_absolute_url (req.path_info + "?op=crash") + " to demonstrate exception trace%N")
end
res.set_status_code (200)
res.write_headers_string (h.string)
res.write_string (s)
end
note
copyright: "Copyright (c) 1984-2011, 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,148 @@
note
description: "Summary description for {APP_SERVER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
APP_SERVER
inherit
APP_APPLICATION
redefine
execute
end
REST_APPLICATION_GATEWAY
create
make
feature {NONE} -- Initialization
make
-- Initialize `Current'.
do
initialize_router
build_gateway_and_launch
end
feature {NONE} -- Handlers
create_router
-- Create `router'
do
create router.make (5)
end
setup_router
local
h: APP_REQUEST_HANDLER
rah: APP_REQUEST_AGENT_HANDLER
gh: APP_REQUEST_ROUTING_HANDLER
do
create {APP_ACCOUNT_VERIFY_CREDENTIAL} h.make
router.map ("/account/verify_credentials", h)
router.map ("/account/verify_credentials.{format}", h)
create {APP_TEST} h.make
create gh.make (4)
router.map ("/test", gh)
gh.map_default (h)
-- gh.map ("/test", h)
gh.map ("/test/{op}", h)
gh.map ("/test.{format}", h)
gh.map ("/test.{format}/{op}", h)
create rah.make (agent execute_exit_application)
h := rah
h.set_description ("tell the REST server to exit (in FCGI context, this is used to reload the FCGI server)")
h.enable_request_method_get
h.enable_format_text
router.map ("/debug/exit", h)
router.map ("/debug/exit.{format}", h)
end
feature -- Execution
execute (req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
do
request_count := request_count + 1
Precursor (req, res)
end
execute_default (req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
local
rqst_uri: detachable STRING
l_path_info: detachable STRING
h: EWF_HEADER
s: STRING
l_redir_url: STRING
do
create h.make
-- h.put_refresh (ctx.script_url ("/doc"), 2, {HTTP_STATUS_CODE}.temp_redirect)
l_redir_url := "/doc"
h.put_refresh (l_redir_url, 2, {HTTP_STATUS_CODE}.temp_redirect)
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.write_headers_string (h.string)
res.write_string (s)
end
-- execute_rescue (ctx: like new_request_context)
-- -- Execute the default rescue behavior
-- do
-- execute_exception_trace (ctx)
-- end
feature -- Implementation
-- execute_exception_trace (ctx: like new_request_context)
-- local
-- h: EWF_HEADER
-- s: STRING
-- do
-- create h.make
-- h.put_content_type_text_plain
-- ctx.output.put_string (h.string)
-- ctx.output.put_string ("Error occurred .. rq="+ request_count.out +"%N")
-- if attached (create {EXCEPTIONS}).exception_trace as l_trace then
-- ctx.output.put_string ("<pre>" + l_trace + "</pre>")
-- end
-- h.recycle
-- exit_with_code (-1)
-- end
execute_exit_application (ctx: APP_REQUEST_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
local
s: STRING
do
res.set_status_code (200)
res.write_header (200, <<["Content-Type", "text/html"]>>)
create s.make_empty
s.append_string ("Exited")
s.append_string (" <a href=%"" + ctx.script_url ("/") + "%">start again</a>%N")
res.write_string (s)
exit_with_code (0)
end
note
copyright: "Copyright (c) 1984-2011, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,24 @@
deferred class
REST_APPLICATION_GATEWAY
inherit
WGI_APPLICATION
feature -- Access
build_gateway_and_launch
local
cgi: EWF_CGI_CONNECTOR
do
create cgi.make (Current)
cgi.launch
end
gateway_name: STRING = "CGI"
exit_with_code (a_code: INTEGER)
do
(create {EXCEPTIONS}).die (a_code)
end
end

View File

@@ -0,0 +1,35 @@
deferred class
REST_APPLICATION_GATEWAY
inherit
WGI_APPLICATION
feature -- Access
build_gateway_and_launch
local
libfcgi: EWF_LIBFCGI_CONNECTOR
do
create libfcgi.make (Current)
libfcgi.launch
end
gateway_name: STRING = "libFCGI"
exit_with_code (a_code: INTEGER)
do
(create {EXCEPTIONS}).die (a_code)
end
note
copyright: "Copyright (c) 1984-2011, 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,44 @@
deferred class
REST_APPLICATION_GATEWAY
inherit
WGI_APPLICATION
feature -- Access
build_gateway_and_launch
local
app: NINO_APPLICATION
port_number: INTEGER
base_url: STRING
do
port_number := 8080
base_url := ""
debug ("nino")
print ("Example: start a Nino web server on port " + port_number.out +
", %Nand reply Hello World for any request such as http://localhost:" + port_number.out + "/" + base_url + "%N")
end
create app.make_custom (agent execute, base_url)
app.force_single_threaded
app.listen (port_number)
end
gateway_name: STRING = "NINO"
exit_with_code (a_code: INTEGER)
do
(create {EXCEPTIONS}).die (a_code)
end
note
copyright: "Copyright (c) 1984-2011, 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,30 @@
note
description: "Summary description for {APP_APPLICATION}."
author: ""
date: "$Date$"
revision: "$Revision$"
deferred class
APP_APPLICATION
inherit
REST_APPLICATION [APP_REQUEST_HANDLER, APP_REQUEST_HANDLER_CONTEXT]
redefine
router
end
feature {NONE} -- Router
router: APP_REQUEST_ROUTER
;note
copyright: "Copyright (c) 1984-2011, 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,32 @@
note
description: "Summary description for REST_REQUEST_AGENT_HANDLER."
author: ""
date: "$Date$"
revision: "$Revision$"
class
APP_REQUEST_AGENT_HANDLER
inherit
APP_REQUEST_HANDLER
undefine
execute, pre_execute, post_execute
end
REST_REQUEST_AGENT_HANDLER [APP_REQUEST_HANDLER_CONTEXT]
create
make
note
copyright: "Copyright (c) 1984-2011, 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,60 @@
note
description : "Objects that ..."
date : "$Date$"
revision : "$Revision$"
deferred class
APP_REQUEST_HANDLER
inherit
REST_REQUEST_HANDLER [APP_REQUEST_HANDLER_CONTEXT]
APP_REQUEST_HELPER
feature {NONE} -- Implementation
string_hash_table_string_string (ht: HASH_TABLE_ITERATION_CURSOR [READABLE_STRING_GENERAL, READABLE_STRING_GENERAL]; using_pre: BOOLEAN): STRING_8
do
create Result.make (100)
if using_pre then
Result.append ("<pre>")
end
from
ht.start
until
ht.after
loop
Result.append_string (ht.key.as_string_8 + " = " + ht.item.as_string_8 + "%N")
ht.forth
end
if using_pre then
Result.append ("</pre>")
end
end
feature -- Helpers
format_id (s: detachable STRING): INTEGER
do
Result := {HTTP_FORMAT_CONSTANTS}.text
if s /= Void then
Result := format_constants.format_id (s)
end
end
exit_with_code (a_code: INTEGER)
do
(create {EXCEPTIONS}).die (a_code)
end
note
copyright: "Copyright (c) 1984-2011, 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,86 @@
note
description : "Objects that ..."
author : "$Author$"
date : "$Date$"
revision : "$Revision$"
class
APP_REQUEST_HANDLER_CONTEXT
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
get_format_id (a_format_variable_name: detachable READABLE_STRING_GENERAL; a_content_type_supported: detachable ARRAY [STRING_8])
do
if internal_format_id = 0 then
internal_format_id := request_format_id (a_format_variable_name, a_content_type_supported)
end
end
get_format_name (a_format_variable_name: detachable READABLE_STRING_GENERAL; a_content_type_supported: detachable ARRAY [STRING_8])
do
if internal_format_name = Void then
internal_format_name := request_format (a_format_variable_name, a_content_type_supported)
end
end
format_id: INTEGER
do
if internal_format_id = 0 then
get_format_id (Void, Void)
end
Result := internal_format_id
end
format_name: detachable READABLE_STRING_8
do
Result := internal_format_name
if Result = Void then
Result := request_format (Void, Void)
internal_format_name := Result
end
end
feature {NONE} -- Internal
internal_format_id: like format_id
internal_format_name: like format_name
;note
copyright: "Copyright (c) 1984-2011, 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,101 @@
note
description: "Summary description for {APP_REQUEST_HELPER}."
author: ""
date: "$Date$"
revision: "$Revision$"
deferred class
APP_REQUEST_HELPER
feature -- Helpers
send_error (a_path: STRING; a_error_id: INTEGER; a_error_name: STRING; a_error_message: detachable STRING; ctx: APP_REQUEST_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
local
s: STRING
i,nb: INTEGER
rep_data: REST_RESPONSE
do
res.set_status_code ({HTTP_STATUS_CODE}.expectation_failed)
create rep_data.make (a_path)
rep_data.headers.put_content_type_text_plain
create s.make_empty
inspect ctx.format_id
when {HTTP_FORMAT_CONSTANTS}.json then
rep_data.headers.put_content_type_text_plain
s := "{%"application%": %"" + a_path + "%""
s.append_string (", %"error%": {")
s.append_string ("%"id%": " + a_error_id.out)
s.append_string (",%"name%": %"" + a_error_name + "%"")
if a_error_message /= Void then
s.append_string (",%"message%": %"")
if a_error_message.has ('%N') then
from
i := s.count
s.append_string (a_error_message)
nb := s.count
until
i > nb
loop
inspect s[i]
when '%R' then
if s.valid_index (i+1) and then s[i+1] = '%N' then
s[i] := '\'
s[i+1] := 'n'
i := i + 1
end
when '%N' then
s.insert_character ('\', i)
s[i] := 'n'
else
end
i := i + 1
end
else
s.append_string (a_error_message)
end
s.append_string ("%"")
end
s.append_string ("}") -- end error
s.append_string ("}") -- end global object
rep_data.set_message (s)
when {HTTP_FORMAT_CONSTANTS}.xml then
rep_data.headers.put_content_type_text_xml
s := "<application path=%"" + a_path + "%"><error id=%"" + a_error_id.out + "%" name=%""+ a_error_name +"%">"
if a_error_message /= Void then
s.append_string (a_error_message)
end
s.append_string ("</error></application>")
rep_data.set_message (s)
when {HTTP_FORMAT_CONSTANTS}.html then
rep_data.headers.put_content_type_text_html
s := "<strong>application</strong>: " + a_path + "<br/>%N<strong>Error</strong> (" + a_error_id.out + ") %"" + a_error_name + "%"<br/>%N"
if a_error_message /= Void then
s.append_string ("<blockquote>" + a_error_message + "</blockquote>")
end
rep_data.set_message (s)
when {HTTP_FORMAT_CONSTANTS}.text then -- Default
s := "Application: " + a_path + "<br/>%N"
s.append_string ("Error (" + a_error_id.out + ") %"" + a_error_name + "%"%N")
if a_error_message /= Void then
s.append_string ("%T" + a_error_message + "%N")
end
rep_data.set_message (s)
end
rep_data.send (res)
rep_data.recycle
end
note
copyright: "Copyright (c) 1984-2011, 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,26 @@
note
description: "Summary description for {REST_REQUEST_URI_TEMPLATE_ROUTER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
APP_REQUEST_ROUTER
inherit
REST_REQUEST_URI_TEMPLATE_ROUTER [APP_REQUEST_HANDLER, APP_REQUEST_HANDLER_CONTEXT]
create
make
note
copyright: "Copyright (c) 1984-2011, 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,40 @@
note
description: "Summary description for {APP_REQUEST_ROUTING_HANDLER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
APP_REQUEST_ROUTING_HANDLER
inherit
APP_REQUEST_HANDLER
undefine
execute,
pre_execute,
post_execute
end
REST_REQUEST_URI_TEMPLATE_ROUTING_HANDLER [APP_REQUEST_HANDLER, APP_REQUEST_HANDLER_CONTEXT]
redefine
router
end
create
make
feature {NONE} -- Routing
router: APP_REQUEST_ROUTER
;note
copyright: "Copyright (c) 1984-2011, 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