From ed04b7fba056405f9fe3f4205dd5f7e8d4c47d4b Mon Sep 17 00:00:00 2001 From: jvelilla Date: Thu, 6 Oct 2011 09:54:20 -0300 Subject: [PATCH] Added REQUEST_RESOURCE_HANDLER_HELPER class to contain common http method behavior. Updated ORDER_HANLDER to use this new class. --- .../restbucks/src/resource/order_handler.e | 259 ++++++------------ .../misc/request_resource_handler_helper.e | 259 ++++++++++++++++++ 2 files changed, 345 insertions(+), 173 deletions(-) create mode 100644 library/server/request/router/src/misc/request_resource_handler_helper.e diff --git a/examples/restbucks/src/resource/order_handler.e b/examples/restbucks/src/resource/order_handler.e index 4a8acd7c..46382ffb 100644 --- a/examples/restbucks/src/resource/order_handler.e +++ b/examples/restbucks/src/resource/order_handler.e @@ -8,48 +8,44 @@ class ORDER_HANDLER[C -> REQUEST_HANDLER_CONTEXT] inherit REQUEST_HANDLER[C] + REQUEST_RESOURCE_HANDLER_HELPER[C] + redefine + do_get, + do_post, + do_put, + do_delete + end SHARED_DATABASE_API SHARED_EJSON REFACTORING_HELPER 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 - process_get (ctx,req,res) - elseif req.request_method.same_string ("PUT") then - process_put (ctx,req,res) - elseif req.request_method.same_string ("DELETE") then - process_delete (ctx,req,res) - elseif req.request_method.same_string ("POST") then - process_post (ctx,req,res) - end + execute_methods(ctx,req,res) 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_get (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + -- Using GET to retrieve resource information. + -- If the GET request is SUCCESS, we response with + -- 200 OK, and a representation of the order + -- If the GET request is not SUCCESS, we response with + -- 404 Resource not found 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 + 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) @@ -73,18 +69,13 @@ feature -- HTTP Methods res.write_string (j.representation) end else - handle_resource_not_found_response ("The following resource"+ orig_path+ " is not found ", res) + handle_resource_not_found_response ("The following resource"+ orig_path+ " is not found ", req.content_type, res) end end - - end - process_put (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do_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 @@ -95,48 +86,39 @@ feature -- HTTP Methods 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) + 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("Bad request, content_lenght empty",res) + handle_bad_request_response(l_post +"%N is not a valid ORDER, maybe the order does not exist in the system",req.content_type,res) end - end + end - process_delete (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do_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") @@ -155,63 +137,64 @@ feature -- HTTP Methods 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) + handle_resource_not_found_response (orig_path + " not found in this server",req.content_type, res) end end end - process_post (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do_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. + -- If the request post is SUCCESS, the server will create the order and will response with + -- HTTP_RESPONSE 201 CREATED + -- if the request post is not SUCCESS, the server will response with + -- HTTP_RESPONSE 400 BAD REQUEST, the client send a bad request + -- HTTP_RESPONSE 500 INTERNAL_SERVER_ERROR, when the server can deliver the request local - l_values: HASH_TABLE [STRING_32, STRING] - l_missings: LINKED_LIST [STRING] - l_full: BOOLEAN l_post: STRING - l_location : STRING - l_msg : STRING l_order : detachable ORDER - jv : detachable JSON_VALUE - h : EWF_HEADER do - fixme ("TODO handle an Internal Server Error") - 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 - l_order := extract_order_request(l_post) - fixme ("TODO move to a service method") - if l_order /= Void then - save_order( l_order) - create h.make - h.put_status (created) - h.put_content_type ("application/json") - jv ?= json.value (l_order) - if jv /= Void then - 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 - 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",res) - end - else - handle_bad_request_response("Bad request, content_lenght empty",res) - end + req.input.read_stream (req.content_length_value.as_integer_32) + l_post := req.input.last_string + l_order := extract_order_request(l_post) + if l_order /= Void then + save_order( l_order) + compute_response_post (ctx, req, res, l_order) + else + handle_bad_request_response(l_post +"%N is not a valid ORDER", req.content_type,res) + end end -feature -- Implementation + compute_response_post (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER; l_order : ORDER) + local + h: EWF_HEADER + l_msg : STRING + jv : detachable JSON_VALUE + l_location : STRING + do + create h.make + h.put_status (created) + h.put_content_type ("application/json") + jv ?= json.value (l_order) + if jv /= Void then + 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 + 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 + end + +feature -- Implementation Repository Layer save_order ( an_order : ORDER) -- save the order to the repository @@ -263,74 +246,4 @@ feature -- Implementation end 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 --- rep: detachable REST_RESPONSE --- do --- create rep.make (path) --- rep.headers.put_status (rep.headers.conflict) --- 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) --- local --- rep: detachable REST_RESPONSE --- do --- create rep.make (path) --- rep.headers.put_status (rep.headers.method_not_allowed) --- rep.headers.put_content_type_application_json --- ctx.output.put_string (rep.string) --- rep.recycle --- end - end diff --git a/library/server/request/router/src/misc/request_resource_handler_helper.e b/library/server/request/router/src/misc/request_resource_handler_helper.e new file mode 100644 index 00000000..013507c5 --- /dev/null +++ b/library/server/request/router/src/misc/request_resource_handler_helper.e @@ -0,0 +1,259 @@ +note + description: "Work in progress Common abstraction to handle REST methods" + author: "" + date: "$Date$" + revision: "$Revision$" + +class + REQUEST_RESOURCE_HANDLER_HELPER[C -> REQUEST_HANDLER_CONTEXT] + +inherit + HTTP_STATUS_CODE + +feature -- Execute template + execute_methods (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + if req.request_method.same_string ("GET") then + execute_get (ctx,req,res) + elseif req.request_method.same_string ("PUT") then + execute_put (ctx,req,res) + elseif req.request_method.same_string ("DELETE") then + execute_delete (ctx,req,res) + elseif req.request_method.same_string ("POST") then + execute_post (ctx,req,res) + elseif req.request_method.same_string ("TRACE") then + execute_trace (ctx,req,res) + elseif req.request_method.same_string ("OPTIONS") then + execute_options (ctx,req,res) + elseif req.request_method.same_string ("HEAD") then + execute_head (ctx,req,res) + elseif req.request_method.same_string ("CONNECT") then + execute_connect (ctx,req,res) +-- elseif req.request_method.is_valid_extension_method then +-- execute_extension_method (req,res) + end + end + + +feature-- Method Post + execute_post (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + if req.content_length_value > 0 then + do_post (ctx,req,res) + else + handle_bad_request_response("Bad request, content_lenght empty", req.content_type, res) + end + rescue + handle_internal_server_error("Internal Server Error",req.content_type,res) + end + + do_post (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + handle_not_implemented ("Method POST not implemented", req.content_type, res) + end + + + +feature-- Method Put + execute_put (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + if req.content_length_value > 0 then + do_put (ctx,req,res) + else + handle_bad_request_response("Bad request, content_lenght empty", req.content_type, res) + end + rescue + handle_internal_server_error("Internal Server Error", req.content_type,res) + end + + do_put (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + handle_not_implemented ("Method PUT not implemented", req.content_type,res) + end + +feature -- Method Get + execute_get (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + do_get (ctx,req,res) + rescue + handle_internal_server_error("Internal Server Error",req.content_type,res) + end + + do_get (ctx: C;req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + handle_not_implemented ("Method HEAD not implemented", req.content_type, res) + end + +feature -- Method DELETE + execute_delete (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + do_delete (ctx,req,res) + rescue + handle_internal_server_error("Internal Server Error",req.content_type,res) + end + + do_delete (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + handle_not_implemented ("Method DELETE not implemented", req.content_type, res) + end + +feature -- Method CONNECT + execute_connect (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + do_connect (ctx,req,res) + rescue + handle_internal_server_error("Internal Server Error",req.content_type,res) + end + + do_connect (ctx: C;req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + handle_not_implemented ("Method CONNECT not implemented", req.content_type, res) + end + +feature -- Method HEAD + execute_head (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + do_head (ctx,req,res) + rescue + handle_internal_server_error("Internal Server Error", req.content_type, res) + end + + do_head (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + handle_not_implemented ("Method HEAD not implemented", req.content_type, res) + end + +feature -- Method OPTIONS + execute_options (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + do_options (ctx,req,res) + rescue + handle_internal_server_error("Internal Server Error",req.content_type,res) + end + + do_options (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + handle_not_implemented ("Method OPTIONS not implemented", req.content_type,res) + end + +feature -- Method TRACE + execute_trace (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + do_trace (ctx,req,res) + rescue + handle_internal_server_error("Internal Server Error", req.content_type, res) + end + + do_trace (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + handle_not_implemented ("Method TRACE not implemented", req.content_type,res) + end + +feature -- Method Extension Method + execute_extension_method (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + do_extension_method (ctx,req,res) + rescue + handle_internal_server_error("Internal Server Error",req.content_type,res) + end + + do_extension_method (ctx: C; req: WGI_REQUEST; res: WGI_RESPONSE_BUFFER) + do + handle_not_implemented ("Method EXTENSION METHOD not implemented",req.content_type, res) + end + +feature -- Handle responses + handle_bad_request_response (a_description:STRING; content_type : detachable READABLE_STRING_32 ; res: WGI_RESPONSE_BUFFER ) + local + h : EWF_HEADER + do + create h.make + h.put_status (bad_request) + if attached content_type as l_content_type then + h.put_content_type (l_content_type) + else + h.put_content_type ("*/*") + end + h.put_content_length (a_description.count) + h.add_header ("Date:"+ get_date) + res.set_status_code (bad_request) + res.write_headers_string (h.string) + res.write_string (a_description) + end + + + + handle_internal_server_error (a_description:STRING; content_type : detachable READABLE_STRING_32 ; res: WGI_RESPONSE_BUFFER ) + local + h : EWF_HEADER + do + create h.make + h.put_status (internal_server_error) + if attached content_type as l_content_type then + h.put_content_type (l_content_type) + else + h.put_content_type ("*/*") + end + h.put_content_length (a_description.count) + h.add_header ("Date:"+ get_date) + res.set_status_code (internal_server_error) + res.write_headers_string (h.string) + res.write_string (a_description) + end + + + + handle_not_implemented (a_description:STRING; content_type : detachable READABLE_STRING_32; res: WGI_RESPONSE_BUFFER ) + local + h : EWF_HEADER + do + create h.make + h.put_status (not_implemented) + if attached content_type as l_content_type then + h.put_content_type (l_content_type) + else + h.put_content_type ("*/*") + end + h.put_content_length (a_description.count) + h.add_header ("Date:"+ get_date) + res.set_status_code (not_implemented) + res.write_headers_string (h.string) + res.write_string (a_description) + end + + + handle_resource_not_found_response (a_description:STRING; content_type : detachable READABLE_STRING_32; res: WGI_RESPONSE_BUFFER) + local + h : EWF_HEADER + do + create h.make + h.put_status (not_found) + if attached content_type as l_content_type then + h.put_content_type (l_content_type) + else + h.put_content_type ("*/*") + end + h.put_content_length (a_description.count) + h.add_header ("Date:"+ get_date) + res.set_status_code (not_found) + res.write_headers_string (h.string) + res.write_string (a_description) + end + +feature -- Date Utilities + get_date : STRING + do + Result := ((create{HTTP_DATE_TIME_UTILITIES}).now_utc).formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT" + end + +note + copyright: "2011-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