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
WGI_RESPONSE_STATUS_CODES
feature -- execute
execute (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER)
-- Execute request handler
do
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
-- pre_process_put (ctx, a_format, a_args)
process_put (ctx,req,res)
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
process_post (ctx,req,res)
else
-- TODO HANDLE METHOD NOT SUPPORTED
end
end
feature -- API DOC
api_doc : STRING = "URI:/order METHOD: POST%N URI:/order/{orderid} METHOD: GET, PUT, DELETE%N"
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)
-- Here the convention is the following.
-- POST is used for creation and the server determines the URI
-- of the created resource.
local
l_values: HASH_TABLE [STRING_32, STRING]
l_missings: LINKED_LIST [STRING]
@@ -46,8 +176,7 @@ feature -- HTTP Methods
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 ("Refactor the code, We need an Extract Method tool :)")
if req.content_length_value > 0 then
req.input.read_stream (req.content_length_value.as_integer_32)
l_post := req.input.last_string
@@ -63,18 +192,21 @@ feature -- HTTP Methods
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
l_location := "http://"+host +req.request_uri+"/" + l_order.id
h.add_header ("Location:"+ l_location)
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.write_headers_string (h.string)
res.write_string (l_msg)
end
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
else
-- handle_bad_request_response("Bad request, content_lenght empty",ctx.output)
handle_bad_request_response("Bad request, content_lenght empty",res)
end
end
@@ -99,18 +231,18 @@ feature -- Implementation
db_access.orders.force (an_order, an_order.id)
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
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
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
@@ -132,17 +264,35 @@ feature -- Implementation
end
-- handle_bad_request_response (a_description:STRING; an_output: HTTPD_SERVER_OUTPUT )
-- local
-- rep: detachable REST_RESPONSE
-- do
-- create rep.make (path)
-- rep.headers.put_status (rep.headers.bad_request)
-- rep.headers.put_content_type_application_json
-- rep.set_message (a_description)
-- an_output.put_string (rep.string)
-- rep.recycle
-- end
handle_bad_request_response (a_description:STRING; res: WGI_RESPONSE_BUFFER )
local
h : EWF_HEADER
do
create h.make
h.put_status (bad_request)
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 (bad_request)
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 )
-- local
@@ -157,17 +307,19 @@ feature -- Implementation
-- end
-- handle_resource_not_found_response (a_description:STRING; an_output: HTTPD_SERVER_OUTPUT )
-- local
-- rep: detachable REST_RESPONSE
-- do
-- create rep.make (path)
-- rep.headers.put_status (rep.headers.not_found)
-- rep.headers.put_content_type_application_json
-- rep.set_message (a_description)
-- an_output.put_string (rep.string)
-- rep.recycle
-- end
handle_resource_not_found_response (a_description:STRING; res: WGI_RESPONSE_BUFFER)
local
h : EWF_HEADER
do
create h.make
h.put_status (not_found)
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_found)
res.write_headers_string (h.string)
res.write_string (a_description)
end
-- handle_method_not_supported_response (ctx :REST_REQUEST_CONTEXT)

View File

@@ -16,6 +16,9 @@ inherit
DEFAULT_WGI_APPLICATION
WGI_RESPONSE_STATUS_CODES
create
make
@@ -45,146 +48,29 @@ feature {NONE} -- Initialization
feature -- Execution
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
h : EWF_HEADER
l_url: STRING
e: EXECUTION_ENVIRONMENT
n: INTEGER
i: INTEGER
l_description : STRING
l_api_doc : STRING
do
if req.content_length_value > 0 then
req.input.read_stream (req.content_length_value.as_integer_32)
end
create h.make
l_url := req.script_url ("/home")
n := 3
h.put_refresh (l_url, 5)
res.set_status_code (200)
h.put_status (method_not_allowed)
h.put_content_type ("application/json")
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.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)
from
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")
res.write_string (l_description)
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
copyright: "2011-2011, Eiffel Software and others"