Merge branch 'master' of github.com:jocelyn/Eiffel-Web-Framework

This commit is contained in:
Jocelyn Fiat
2011-10-04 16:19:36 +02:00
3 changed files with 268 additions and 179 deletions

View File

@@ -0,0 +1,51 @@
Restbuck Eiffel Implementation based on the book of REST in Practice
Verb URI or template Use
POST /order Create a new order, and upon success, receive a Locationheader specifying the new order<65>s URI.
GET /order/{orderId} Request the current state of the order specified by the URI.
PUT /order/{orderId} Update an order at the given URI with new information, providing the full representation.
DELETE /order/{orderId} Logically remove the order identified by the given URI.
How to Create an order
* Uri: http://localhost:8080/order
* Method: POST
* Note: you will get in the response the "location" of the new your order.
* HEADERS:
Content-Type: application/json
* Example CONTENT
{
"location":"takeAway",
"items":[
{
"name":"Late",
"option":"skim",
"size":"Small",
"quantity":1
}
]
}
How to Read an order
* Uri: http://localhost:8080/order/{order_id}
* Method: GET
How to Update an order
* Uri: http://localhost:8080/order/{order_id}
* Method: PUT
How to Delete an order
* Uri: http://localhost:8080/order/{order_id}
* Method: DELETE

View File

@@ -14,26 +14,156 @@ inherit
SHARED_ORDER_VALIDATION SHARED_ORDER_VALIDATION
WGI_RESPONSE_STATUS_CODES WGI_RESPONSE_STATUS_CODES
feature -- execute feature -- execute
execute (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) execute (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
-- Execute request handler -- Execute request handler
do do
if req.request_method.same_string ("GET") then if req.request_method.same_string ("GET") then
-- pre_process_get (ctx, a_format, a_args) process_get (ctx,req,res)
elseif req.request_method.same_string ("PUT") then elseif req.request_method.same_string ("PUT") then
-- pre_process_put (ctx, a_format, a_args) process_put (ctx,req,res)
elseif req.request_method.same_string ("DELETE") then elseif req.request_method.same_string ("DELETE") then
-- pre_process_delete (ctx, a_format, a_args) process_delete (ctx,req,res)
elseif req.request_method.same_string ("POST") then elseif req.request_method.same_string ("POST") then
process_post (ctx,req,res) process_post (ctx,req,res)
else
-- TODO HANDLE METHOD NOT SUPPORTED
end end
end end
feature -- API DOC
api_doc : STRING = "URI:/order METHOD: POST%N URI:/order/{orderid} METHOD: GET, PUT, DELETE%N"
feature -- HTTP Methods feature -- HTTP Methods
process_get (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
local
l_values: HASH_TABLE [STRING_32, STRING]
l_missings: LINKED_LIST [STRING]
l_full: BOOLEAN
l_post: STRING
joc : JSON_ORDER_CONVERTER
parser : JSON_PARSER
l_order : detachable ORDER
jv : detachable JSON_VALUE
l_location, id : STRING
uri : LIST[READABLE_STRING_32]
h : EWF_HEADER
http_if_not_match : STRING
do
fixme ("TODO handle error conditions")
if attached req.orig_path_info as orig_path then
uri := orig_path.split ('/')
id := uri.at (3)
create joc.make
json.add_converter(joc)
if db_access.orders.has_key (id) then
l_order := db_access.orders.item (id)
jv ?= json.value (l_order)
if attached jv as j then
create h.make
h.put_status (ok)
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
if l_order /= Void then
h.add_header ("Etag: " + l_order.etag)
end
res.set_status_code (ok)
res.write_headers_string (h.string)
res.write_string (j.representation)
end
else
handle_resource_not_found_response ("The following resource"+ orig_path+ " is not found ", res)
end
end
end
process_put (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
local
l_values: HASH_TABLE [STRING_32, STRING]
l_missings: LINKED_LIST [STRING]
l_full: BOOLEAN
l_post: STRING
l_location : STRING
l_order : detachable ORDER
jv : detachable JSON_VALUE
h : EWF_HEADER
do
fixme ("TODO handle an Internal Server Error")
fixme ("Refactor the code, create new abstractions")
fixme ("Add Header Date to the response")
fixme ("Put implememntation is wrong!!!!")
if req.content_length_value > 0 then
req.input.read_stream (req.content_length_value.as_integer_32)
l_post := req.input.last_string
l_order := extract_order_request(l_post)
fixme ("TODO move to a service method")
if l_order /= Void and then db_access.orders.has_key (l_order.id) then
update_order( l_order)
create h.make
h.put_status (ok)
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
if attached req.http_host as host then
l_location := "http://"+host +req.request_uri+"/" + l_order.id
h.add_header ("Location:"+ l_location)
end
jv ?= json.value (l_order)
if jv /= Void then
h.put_content_length (jv.representation.count)
res.set_status_code (ok)
res.write_headers_string (h.string)
res.write_string (jv.representation)
end
else
handle_bad_request_response(l_post +"%N is not a valid ORDER, maybe the order does not exist in the system",res)
end
else
handle_bad_request_response("Bad request, content_lenght empty",res)
end
end
process_delete (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
local
l_values: HASH_TABLE [STRING_32, STRING]
uri: LIST [READABLE_STRING_32]
l_full: BOOLEAN
id: STRING
l_location : STRING
l_order : detachable ORDER
jv : detachable JSON_VALUE
h : EWF_HEADER
do
fixme ("TODO handle an Internal Server Error")
fixme ("Refactor the code, create new abstractions")
if attached req.orig_path_info as orig_path then
uri := orig_path.split ('/')
id := uri.at (3)
if db_access.orders.has_key (id) then
delete_order( id)
create h.make
h.put_status (no_content)
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
res.set_status_code (no_content)
res.write_headers_string (h.string)
else
handle_resource_not_found_response (orig_path + " not found in this server", res)
end
end
end
process_post (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) process_post (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
-- Here the convention is the following.
-- POST is used for creation and the server determines the URI
-- of the created resource.
local local
l_values: HASH_TABLE [STRING_32, STRING] l_values: HASH_TABLE [STRING_32, STRING]
l_missings: LINKED_LIST [STRING] l_missings: LINKED_LIST [STRING]
@@ -46,8 +176,7 @@ feature -- HTTP Methods
h : EWF_HEADER h : EWF_HEADER
do do
fixme ("TODO handle an Internal Server Error") fixme ("TODO handle an Internal Server Error")
fixme ("Refactor the code, create new abstractions") fixme ("Refactor the code, We need an Extract Method tool :)")
fixme ("Add Header Date to the response")
if req.content_length_value > 0 then if req.content_length_value > 0 then
req.input.read_stream (req.content_length_value.as_integer_32) req.input.read_stream (req.content_length_value.as_integer_32)
l_post := req.input.last_string l_post := req.input.last_string
@@ -63,18 +192,21 @@ feature -- HTTP Methods
l_msg := jv.representation l_msg := jv.representation
h.put_content_length (l_msg.count) h.put_content_length (l_msg.count)
if attached req.http_host as host then if attached req.http_host as host then
l_location := "http://"+host +"/" +req.request_uri+"/" + l_order.id l_location := "http://"+host +req.request_uri+"/" + l_order.id
h.add_header ("Location:"+ l_location) h.add_header ("Location:"+ l_location)
end end
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
res.set_status_code (created) res.set_status_code (created)
res.write_headers_string (h.string) res.write_headers_string (h.string)
res.write_string (l_msg) res.write_string (l_msg)
end end
else else
-- handle_bad_request_response(l_post +"%N is not a valid ORDER",ctx.output) handle_bad_request_response(l_post +"%N is not a valid ORDER",res)
end end
else else
-- handle_bad_request_response("Bad request, content_lenght empty",ctx.output) handle_bad_request_response("Bad request, content_lenght empty",res)
end end
end end
@@ -99,18 +231,18 @@ feature -- Implementation
db_access.orders.force (an_order, an_order.id) db_access.orders.force (an_order, an_order.id)
end end
-- update_order ( an_order : ORDER) update_order ( an_order : ORDER)
-- -- update the order to the repository -- update the order to the repository
-- do do
-- an_order.add_revision an_order.add_revision
-- db_access.orders.force (an_order, an_order.id) db_access.orders.force (an_order, an_order.id)
-- end end
-- delete_order ( an_order : STRING) delete_order ( an_order : STRING)
-- -- update the order to the repository -- update the order to the repository
-- do do
-- db_access.orders.remove (an_order) db_access.orders.remove (an_order)
-- end end
extract_order_request (l_post : STRING) : detachable ORDER extract_order_request (l_post : STRING) : detachable ORDER
-- extract an object Order from the request, or Void -- extract an object Order from the request, or Void
@@ -132,17 +264,35 @@ feature -- Implementation
end end
-- handle_bad_request_response (a_description:STRING; an_output: HTTPD_SERVER_OUTPUT ) handle_bad_request_response (a_description:STRING; res: WGI_RESPONSE_BUFFER )
-- local local
-- rep: detachable REST_RESPONSE h : EWF_HEADER
-- do do
-- create rep.make (path) create h.make
-- rep.headers.put_status (rep.headers.bad_request) h.put_status (bad_request)
-- rep.headers.put_content_type_application_json h.put_content_type ("application/json")
-- rep.set_message (a_description) h.put_content_length (a_description.count)
-- an_output.put_string (rep.string) h.add_header ("Date:"+ ((create{HTTP_DATE_TIME_UTILITIES}).now_utc).formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT")
-- rep.recycle res.set_status_code (bad_request)
-- end res.write_headers_string (h.string)
res.write_string (a_description)
end
handle_not_implemented (a_description:STRING; res: WGI_RESPONSE_BUFFER )
local
h : EWF_HEADER
do
create h.make
h.put_status (not_implemented)
h.put_content_type ("application/json")
h.put_content_length (a_description.count)
h.add_header ("Date:"+ ((create{HTTP_DATE_TIME_UTILITIES}).now_utc).formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT")
res.set_status_code (not_implemented)
res.write_headers_string (h.string)
res.write_string (a_description)
end
-- handle_conflic_request_response (a_description:STRING; an_output: HTTPD_SERVER_OUTPUT ) -- handle_conflic_request_response (a_description:STRING; an_output: HTTPD_SERVER_OUTPUT )
-- local -- local
@@ -157,17 +307,19 @@ feature -- Implementation
-- end -- end
-- handle_resource_not_found_response (a_description:STRING; an_output: HTTPD_SERVER_OUTPUT ) handle_resource_not_found_response (a_description:STRING; res: WGI_RESPONSE_BUFFER)
-- local local
-- rep: detachable REST_RESPONSE h : EWF_HEADER
-- do do
-- create rep.make (path) create h.make
-- rep.headers.put_status (rep.headers.not_found) h.put_status (not_found)
-- rep.headers.put_content_type_application_json h.put_content_type ("application/json")
-- rep.set_message (a_description) h.put_content_length (a_description.count)
-- an_output.put_string (rep.string) h.add_header ("Date:"+ ((create{HTTP_DATE_TIME_UTILITIES}).now_utc).formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT")
-- rep.recycle res.set_status_code (not_found)
-- end res.write_headers_string (h.string)
res.write_string (a_description)
end
-- handle_method_not_supported_response (ctx :REST_REQUEST_CONTEXT) -- handle_method_not_supported_response (ctx :REST_REQUEST_CONTEXT)

View File

@@ -16,6 +16,9 @@ inherit
DEFAULT_WGI_APPLICATION DEFAULT_WGI_APPLICATION
WGI_RESPONSE_STATUS_CODES
create create
make make
@@ -45,146 +48,29 @@ feature {NONE} -- Initialization
feature -- Execution feature -- Execution
execute_default (req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) execute_default (req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
-- 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 local
h: EWF_HEADER h : EWF_HEADER
l_url: STRING l_description : STRING
e: EXECUTION_ENVIRONMENT l_api_doc : STRING
n: INTEGER
i: INTEGER
do do
if req.content_length_value > 0 then
req.input.read_stream (req.content_length_value.as_integer_32)
end
create h.make create h.make
l_url := req.script_url ("/home") h.put_status (method_not_allowed)
n := 3 h.put_content_type ("application/json")
h.put_refresh (l_url, 5) l_api_doc := "%NPlease check the API%NURI:/order METHOD: POST%NURI:/order/{orderid} METHOD: GET, PUT, DELETE%N"
res.set_status_code (200) l_description := req.request_method + req.request_uri + " is not allowed" + "%N" + l_api_doc
h.put_content_length (l_description.count)
h.add_header ("Date:"+ ((create{HTTP_DATE_TIME_UTILITIES}).now_utc).formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT")
res.set_status_code (method_not_allowed)
res.write_headers_string (h.string) res.write_headers_string (h.string)
from res.write_string (l_description)
create e
until
n = 0
loop
if n > 1 then
res.write_string ("Redirected to " + l_url + " in " + n.out + " seconds :%N")
else
res.write_string ("Redirected to " + l_url + " in 1 second :%N")
end end
res.flush
from
i := 1
until
i = 1001
loop
res.write_string (".")
if i \\ 100 = 0 then
res.write_string ("%N")
end
res.flush
e.sleep (1_000_000)
i := i + 1
end
res.write_string ("%N")
n := n - 1
end
res.write_string ("You are now being redirected...%N")
res.flush
end
execute_home (ctx: REQUEST_URI_TEMPLATE_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
do
res.write_header (200, <<["Content-Type", "text/html"]>>)
res.write_string ("<html><body>Hello World ?!%N")
res.write_string ("<h3>Please try the following links</h3><ul>%N")
res.write_string ("<li><a href=%""+ req.script_url ("/") + "%">default</a></li>%N")
res.write_string ("<li><a href=%""+ req.script_url ("/hello") + "%">/hello</a></li>%N")
res.write_string ("<li><a href=%""+ req.script_url ("/hello.html/Joce") + "%">/hello.html/Joce</a></li>%N")
res.write_string ("<li><a href=%""+ req.script_url ("/hello.json/Joce") + "%">/hello.json/Joce</a></li>%N")
res.write_string ("<li><a href=%""+ req.script_url ("/hello/Joce.html") + "%">/hello/Joce.html</a></li>%N")
res.write_string ("<li><a href=%""+ req.script_url ("/hello/Joce.xml") + "%">/hello/Joce.xml</a></li>%N")
res.write_string ("<li><a href=%""+ req.script_url ("/hello/Joce") + "%">/hello/Joce</a></li>%N")
res.write_string ("</ul>%N")
if attached req.item ("REQUEST_COUNT") as rqc then
res.write_string ("request #"+ rqc.as_string + "%N")
end
res.write_string ("</body></html>%N")
end
execute_hello (req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER; a_name: detachable READABLE_STRING_32; ctx: REQUEST_HANDLER_CONTEXT)
local
l_response_content_type: detachable STRING
msg: STRING
h: EWF_HEADER
content_type_supported: ARRAY [STRING]
do
if a_name /= Void then
msg := "Hello %"" + a_name + "%" !%N"
else
msg := "Hello anonymous visitor !%N"
end
content_type_supported := <<{HTTP_CONSTANTS}.json_app, {HTTP_CONSTANTS}.html_text, {HTTP_CONSTANTS}.xml_text, {HTTP_CONSTANTS}.plain_text>>
inspect ctx.request_format_id ("format", content_type_supported)
when {HTTP_FORMAT_CONSTANTS}.json then
l_response_content_type := {HTTP_CONSTANTS}.json_app
msg := "{%N%"application%": %"/hello%",%N %"message%": %"" + msg + "%" %N}"
when {HTTP_FORMAT_CONSTANTS}.html then
l_response_content_type := {HTTP_CONSTANTS}.html_text
when {HTTP_FORMAT_CONSTANTS}.xml then
l_response_content_type := {HTTP_CONSTANTS}.xml_text
msg := "<response><application>/hello</application><message>" + msg + "</message></response>%N"
when {HTTP_FORMAT_CONSTANTS}.text then
l_response_content_type := {HTTP_CONSTANTS}.plain_text
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 l_response_content_type /= Void then
create h.make
h.put_status (200)
h.put_content_type (l_response_content_type)
h.put_content_length (msg.count)
res.set_status_code (200)
res.write_headers_string (h.string)
-- res.write_header (200, <<
-- ["Content-Type", l_response_content_type],
-- ["Content-Length", msg.count.out
-- >>)
res.write_string (msg)
end
end
handle_hello (ctx: REQUEST_URI_TEMPLATE_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
do
execute_hello (req, res, Void, ctx)
end
handle_anonymous_hello (ctx: REQUEST_URI_TEMPLATE_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
do
execute_hello (req, res, ctx.string_parameter ("name"), ctx)
end
handle_method_any (ctx: REQUEST_URI_TEMPLATE_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
do
execute_hello (req, res, req.request_method, ctx)
end
handle_method_get (ctx: REQUEST_URI_TEMPLATE_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
do
execute_hello (req, res, "GET", ctx)
end
handle_method_post (ctx: REQUEST_URI_TEMPLATE_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
do
execute_hello (req, res, "POST", ctx)
end
handle_method_get_or_post (ctx: REQUEST_URI_TEMPLATE_HANDLER_CONTEXT; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
do
execute_hello (req, res, "GET or POST", ctx)
end
note note
copyright: "2011-2011, Eiffel Software and others" copyright: "2011-2011, Eiffel Software and others"