From 19b5edd9b46972b30c1d2a723b348e103120acb1 Mon Sep 17 00:00:00 2001 From: jvelilla Date: Fri, 23 Sep 2011 09:06:23 -0300 Subject: [PATCH 1/3] Added validations. --- .../restbucks/src/resource/order_handler.e | 95 +++++++++++++++---- 1 file changed, 75 insertions(+), 20 deletions(-) diff --git a/examples/restbucks/src/resource/order_handler.e b/examples/restbucks/src/resource/order_handler.e index d7c240f6..372abbf6 100644 --- a/examples/restbucks/src/resource/order_handler.e +++ b/examples/restbucks/src/resource/order_handler.e @@ -14,26 +14,47 @@ 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 + handle_method_not_allowed (req.request_method + " " + req.request_uri +"%N API Contract %N" +api_doc, res) 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) + do + handle_not_implemented ("GET: "+ req.request_uri + "%N Not implemented", res) + end + + process_put (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + handle_not_implemented ("PUT: "+ req.request_uri + "%N Not implemented", res) + end + + process_delete (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + handle_not_implemented ("DELETE: "+ req.request_uri + "%N Not implemented", res) + 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 +67,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 +83,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 @@ -132,17 +155,49 @@ 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_method_not_allowed (a_description:STRING; res: WGI_RESPONSE_BUFFER ) + local + h : EWF_HEADER + do + create h.make + h.put_status (method_not_allowed) + 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 (method_not_allowed) + 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 From 5adf40a9edfda79da67d7b717d856645ef19f4a6 Mon Sep 17 00:00:00 2001 From: jvelilla Date: Wed, 28 Sep 2011 08:54:12 -0300 Subject: [PATCH 2/3] Updated Restbucks examples, handle not method allowed in a better way, added the readme file. --- examples/restbucks/readme.txt | 51 ++++++ .../restbucks/src/resource/order_handler.e | 84 ++++++---- examples/restbucks/src/restbucks_server.e | 158 +++--------------- 3 files changed, 129 insertions(+), 164 deletions(-) create mode 100644 examples/restbucks/readme.txt diff --git a/examples/restbucks/readme.txt b/examples/restbucks/readme.txt new file mode 100644 index 00000000..dfff1b30 --- /dev/null +++ b/examples/restbucks/readme.txt @@ -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’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 + diff --git a/examples/restbucks/src/resource/order_handler.e b/examples/restbucks/src/resource/order_handler.e index 372abbf6..c1204e89 100644 --- a/examples/restbucks/src/resource/order_handler.e +++ b/examples/restbucks/src/resource/order_handler.e @@ -27,8 +27,6 @@ feature -- execute process_delete (ctx,req,res) elseif req.request_method.same_string ("POST") then process_post (ctx,req,res) - else - handle_method_not_allowed (req.request_method + " " + req.request_uri +"%N API Contract %N" +api_doc, res) end end @@ -37,12 +35,54 @@ feature -- API DOC 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 - handle_not_implemented ("GET: "+ req.request_uri + "%N Not implemented", res) + 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 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) do + if req.content_length_value > 0 then + req.input.read_stream (req.content_length_value.as_integer_32) + end handle_not_implemented ("PUT: "+ req.request_uri + "%N Not implemented", res) end @@ -170,20 +210,6 @@ feature -- Implementation end - handle_method_not_allowed (a_description:STRING; res: WGI_RESPONSE_BUFFER ) - local - h : EWF_HEADER - do - create h.make - h.put_status (method_not_allowed) - 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 (method_not_allowed) - res.write_headers_string (h.string) - res.write_string (a_description) - end - handle_not_implemented (a_description:STRING; res: WGI_RESPONSE_BUFFER ) local @@ -212,17 +238,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) diff --git a/examples/restbucks/src/restbucks_server.e b/examples/restbucks/src/restbucks_server.e index cfca360a..8f331f93 100644 --- a/examples/restbucks/src/restbucks_server.e +++ b/examples/restbucks/src/restbucks_server.e @@ -16,6 +16,9 @@ inherit DEFAULT_WGI_APPLICATION + WGI_RESPONSE_STATUS_CODES + + create make @@ -45,147 +48,30 @@ 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 + h : EWF_HEADER + l_description : STRING + l_api_doc : STRING do - create h.make - l_url := req.script_url ("/home") - n := 3 - h.put_refresh (l_url, 5) - res.set_status_code (200) - 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") - end - res.flush - from - i := 1 - until - i = 1001 - loop - res.write_string (".") - if i \\ 100 = 0 then - res.write_string ("%N") + if req.content_length_value > 0 then + req.input.read_stream (req.content_length_value.as_integer_32) 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 + create h.make + 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) + res.write_string (l_description) 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 ("Hello World ?!%N") - res.write_string ("

Please try the following links

%N") - - if attached req.item ("REQUEST_COUNT") as rqc then - res.write_string ("request #"+ rqc.as_string + "%N") - end - res.write_string ("%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 := "/hello" + msg + "%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" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" From 7ba3eb9ecd4652ef356e72db94171803f4b38466 Mon Sep 17 00:00:00 2001 From: jvelilla Date: Mon, 3 Oct 2011 09:26:01 -0300 Subject: [PATCH 3/3] Updated support for PUT. Now the example support GET, POST, PUT, DELETE. --- .../restbucks/src/resource/order_handler.e | 101 +++++++++++++++--- 1 file changed, 85 insertions(+), 16 deletions(-) diff --git a/examples/restbucks/src/resource/order_handler.e b/examples/restbucks/src/resource/order_handler.e index c1204e89..4a8acd7c 100644 --- a/examples/restbucks/src/resource/order_handler.e +++ b/examples/restbucks/src/resource/order_handler.e @@ -62,7 +62,9 @@ feature -- HTTP Methods 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 @@ -79,16 +81,83 @@ feature -- HTTP Methods 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 - if req.content_length_value > 0 then + 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) - end - handle_not_implemented ("PUT: "+ req.request_uri + "%N Not implemented", res) + 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 - handle_not_implemented ("DELETE: "+ req.request_uri + "%N Not implemented", res) + 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) @@ -162,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