diff --git a/contrib/ise_library/cURL b/contrib/ise_library/cURL index f17f785e..2b7043f6 160000 --- a/contrib/ise_library/cURL +++ b/contrib/ise_library/cURL @@ -1 +1 @@ -Subproject commit f17f785ee36db5b424ce40dfcd7bbd41924613bc +Subproject commit 2b7043f670f6efc70ebed5fbf62d83a22c095ffe diff --git a/contrib/library/text/parser/json b/contrib/library/text/parser/json index 5ec1bdd2..24d08d4f 160000 --- a/contrib/library/text/parser/json +++ b/contrib/library/text/parser/json @@ -1 +1 @@ -Subproject commit 5ec1bdd2ea01358153de6887f572bdb2e70c02dd +Subproject commit 24d08d4fcedf1d54f1c8b4eb6526f2dd1b2fb943 diff --git a/examples/restbucks/client/src/restbuck_client.e b/examples/restbucks/client/src/restbuck_client.e deleted file mode 100644 index dec021d0..00000000 --- a/examples/restbucks/client/src/restbuck_client.e +++ /dev/null @@ -1,72 +0,0 @@ -note - description : "Objects that ..." - author : "$Author$" - date : "$Date$" - revision : "$Revision$" - -class - RESTBUCK_CLIENT - -create - make - -feature {NONE} -- Initialization - - make - -- Initialize `Current'. - local - h: LIBCURL_HTTP_CLIENT - sess: HTTP_CLIENT_SESSION - s: READABLE_STRING_8 - j: JSON_PARSER - id: detachable STRING - do - create h.make - sess := h.new_session ("http://127.0.0.1") - s := "[ - { - "location":"takeAway", - "items":[ - { - "name":"Late", - "option":"skim", - "size":"Small", - "quantity":1 - } - ] - } - ]" - - if attached sess.post ("/order", Void, s) as r then - if attached r.body as m then - create j.make_parser (m) - - if j.is_parsed and attached j.parse_object as j_o then - if attached {JSON_STRING} j_o.item ("id") as l_id then - id := l_id.item - end - print (m) - io.put_new_line - - end - end - end - - if id /= Void and then attached sess.get ("/order/" + id, Void) as r then - print (r.body) - io.put_new_line - end - end - -feature -- Status - -feature -- Access - -feature -- Change - -feature {NONE} -- Implementation - -invariant --- invariant_clause: True - -end diff --git a/examples/restbucks/readme.txt b/examples/restbucks/readme.txt deleted file mode 100644 index dfff1b30..00000000 --- a/examples/restbucks/readme.txt +++ /dev/null @@ -1,51 +0,0 @@ -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/client/README.txt b/examples/restbucksCRUD/client/README.txt similarity index 100% rename from examples/restbucks/client/README.txt rename to examples/restbucksCRUD/client/README.txt diff --git a/examples/restbucks/client/client-safe.ecf b/examples/restbucksCRUD/client/client-safe.ecf similarity index 100% rename from examples/restbucks/client/client-safe.ecf rename to examples/restbucksCRUD/client/client-safe.ecf diff --git a/examples/restbucks/client/client.ecf b/examples/restbucksCRUD/client/client.ecf similarity index 100% rename from examples/restbucks/client/client.ecf rename to examples/restbucksCRUD/client/client.ecf diff --git a/examples/restbucksCRUD/client/src/restbuck_client.e b/examples/restbucksCRUD/client/src/restbuck_client.e new file mode 100644 index 00000000..2e0cd8b2 --- /dev/null +++ b/examples/restbucksCRUD/client/src/restbuck_client.e @@ -0,0 +1,170 @@ +note + description : "Objects that ..." + author : "$Author$" + date : "$Date$" + revision : "$Revision$" + +class + RESTBUCK_CLIENT + +create + make + +feature {NONE} -- Initialization + + make + -- Initialize `Current'. + local + h: LIBCURL_HTTP_CLIENT + sess: HTTP_CLIENT_SESSION + resp : HTTP_CLIENT_RESPONSE + l_location : detachable READABLE_STRING_8 + body : STRING + do + create h.make + sess := h.new_session ("http://127.0.0.1:8080") + -- Create Order + print ("%N Create Order %N") + resp := create_order (sess) + + -- Read the Order + print ("%N Read Order %N") + l_location := resp.headers.at ("Location") + resp := read_order (sess, l_location) + + + -- Update the Order + + if attached resp.body as l_body then + body := l_body.as_string_8 + body.replace_substring_all ("takeAway", "in Shop") + print ("%N Update Order %N") + resp := update_order (sess, l_location, body) + end + end + + update_order ( sess: HTTP_CLIENT_SESSION; uri : detachable READABLE_STRING_8; a_body : STRING) : HTTP_CLIENT_RESPONSE + local + l_headers: HASH_TABLE [READABLE_STRING_8,READABLE_STRING_8] + do + create Result.make + if attached uri as l_uri then + sess.set_base_url (l_uri) + Result := sess.put ("", Void, a_body ) + if attached Result as r then + -- Show headers + l_headers := r.headers + from + l_headers.start + until + l_headers.after + loop + print (l_headers.key_for_iteration) + print (":") + print (l_headers.item_for_iteration) + l_headers.forth + io.put_new_line + end + + -- Show body + print (r.body) + io.put_new_line + end + end + end + + + read_order ( sess: HTTP_CLIENT_SESSION; uri : detachable READABLE_STRING_8) : HTTP_CLIENT_RESPONSE + local + l_headers: HASH_TABLE [READABLE_STRING_8,READABLE_STRING_8] + do + create Result.make + if attached uri as l_uri then + sess.set_base_url (l_uri) + Result := sess.get ("", Void) + if attached Result as r then + -- Show headers + l_headers := r.headers + from + l_headers.start + until + l_headers.after + loop + print (l_headers.key_for_iteration) + print (":") + print (l_headers.item_for_iteration) + l_headers.forth + io.put_new_line + end + + -- Show body + print (r.body) + io.put_new_line + end + end + end + + + + create_order (sess: HTTP_CLIENT_SESSION) : HTTP_CLIENT_RESPONSE + local + s: READABLE_STRING_8 + j: JSON_PARSER + id: detachable STRING + context : HTTP_CLIENT_REQUEST_CONTEXT + l_headers: HASH_TABLE [READABLE_STRING_8,READABLE_STRING_8] + do + s := "[ + { + "location":"takeAway", + "items":[ + { + "name":"Late", + "option":"skim", + "size":"Small", + "quantity":1 + } + ] + } + ]" + + create context.make + context.headers.put ("application/json", "Content-Type") + Result := sess.post ("/order", context, s) + if attached Result as r then + -- Show the Headers + l_headers := r.headers + from + l_headers.start + until + l_headers.after + loop + print (l_headers.key_for_iteration) + print (":") + print (l_headers.item_for_iteration) + l_headers.forth + io.put_new_line + end + + + -- Show the Response body + if attached r.body as m then + create j.make_parser (m) + if j.is_parsed and attached j.parse_object as j_o then + if attached {JSON_STRING} j_o.item ("id") as l_id then + id := l_id.item + end + print (m) + io.put_new_line + end + end + end + end + + +feature {NONE} -- Implementation + +invariant +-- invariant_clause: True + +end diff --git a/examples/restbucks/license.lic b/examples/restbucksCRUD/license.lic similarity index 100% rename from examples/restbucks/license.lic rename to examples/restbucksCRUD/license.lic diff --git a/examples/restbucksCRUD/readme.md b/examples/restbucksCRUD/readme.md new file mode 100644 index 00000000..c833a939 --- /dev/null +++ b/examples/restbucksCRUD/readme.md @@ -0,0 +1,293 @@ +Restbuck Eiffel Implementation based on the book of REST in Practice +==================================================================== +This is an ihmplementation of CRUD pattern for manipulate resources, this is the first step to use +the HTTP protocol as an application protocol instead of a transport protocol. + +Restbuck Protocol +----------------- + + + + + + + +
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.
+ +Resource Represenation +---------------------- +The previous tables shows a contrat, the URI or URI template, allows us to indentify resources, now we will chose a +representacion, for this particular case we will use JSON. + +Note:
+1. *A resource can have multiple URIs*.
+2. *A resource can have multiple Representations*.
+ +RESTBUCKS_SERVER +---------------- +This class implement the main entry of our REST CRUD service, we are using a default connector (Nino Connector, +using a WebServer written in Eiffel). +We are inheriting from URI_TEMPLATE_ROUTED_SERVICE, this allows us to map our service contrat, as is shown in the previous +table, the mapping is defined in the feature setup_router, this also show that the class ORDER_HANDLER will be encharge +of to handle different type of request to the ORDER resource. + + + class + RESTBUCKS_SERVER + + inherit + ANY + + URI_TEMPLATE_ROUTED_SERVICE + + DEFAULT_SERVICE + -- Here we are using a default connector using the default Nino Connector, + -- but it's possible to use other connector (CGI or FCGI). + + create + make + + feature {NONE} -- Initialization + + make + -- Initialize the router (this will have the request handler and + -- their context). + do + initialize_router + make_and_launch + end + + create_router + do + create router.make (2) + end + + setup_router + local + order_handler: ORDER_HANDLER [REQUEST_URI_TEMPLATE_HANDLER_CONTEXT] + do + create order_handler + router.map_with_request_methods ("/order", order_handler, <<"POST">>) + router.map_with_request_methods ("/order/{orderid}", order_handler, <<"GET", "DELETE", "PUT">>) + end + + feature -- Execution + + execute_default (req: WSF_REQUEST; res: WSF_RESPONSE) + -- 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 : HTTP_HEADER + l_description : STRING + l_api_doc : STRING + do + if req.content_length_value > 0 then + req.input.read_string (req.content_length_value.as_integer_32) + end + create h.make + h.put_status ({HTTP_STATUS_CODE}.method_not_allowed) + h.put_content_type_text_plain + 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.put_current_date + res.set_status_code ({HTTP_STATUS_CODE}.method_not_allowed) + res.write_header_text (h.string) + res.write_string (l_description) + end + + end + + + +How to Create an order with POST +-------------------------------- + +Here is the convention that we are using: +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 +201 CREATED, the Location header will contains the newly created order's URI, +if the request POST is not SUCCESS, the server will response with +400 BAD REQUEST, the client send a bad request or +500 INTERNAL_SERVER_ERROR, when the server can deliver the request. + + POST /order HTTP/1.1 + Host: 127.0.0.1:8080 + Connection: keep-alive + Content-Length: 196 + Origin: chrome-extension://fhjcajmcbmldlhcimfajhfbgofnpcjmb + Content-Type: application/json + Accept: */* + Accept-Encoding: gzip,deflate,sdch + Accept-Language: es-419,es;q=0.8,en;q=0.6 + Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 + + { + "location":"takeAway", + "items":[ + { + "name":"Late", + "option":"skim", + "size":"Small", + "quantity":1 + } + ] + } + +Response success + + HTTP/1.1 201 Created + Status 201 Created + Content-Type application/json + Content-Length 123 + Location http://localhost:8080/order/1 + Date FRI,09 DEC 2011 20:34:20.00 GMT + + { + "location" : "takeAway", + "status" : "submitted", + "items" : [ { + "name" : "late", + "size" : "small", + "quantity" : 1, + "option" : "skim" + } ] + } + + +How to Read an order with GET +----------------------------- +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 +If is a Conditional GET and the resource does not change we send a 304, Resource not modifed + + GET /order/1 HTTP/1.1 + Host: 127.0.0.1:8080 + Connection: keep-alive + Accept: */* + Accept-Encoding: gzip,deflate,sdch + Accept-Language: es-419,es;q=0.8,en;q=0.6 + Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 + If-None-Match: 6542EF270D91D3EAF39CFB382E4CEBA7 + +Response + HTTP/1.1 200 OK + + Status 200 OK + Content-Type application/json + Content-Length 123 + Date FRI,09 DEC 2011 20:53:46.00 GMT + etag 2ED3A40954A95D766FC155682DC8BB52 + + { + "location" : "takeAway", + "status" : "submitted", + "items" : [ { + "name" : "late", + "size" : "small", + "quantity" : 1, + "option" : "skim" + } ] + } + + + +How to Update an order with PUT +------------------------------- +A successful PUT request will not create a new resource, instead it will change the state of the resource identified by the current uri. +If success we response with 200 and the updated order. +404 if the order is not found +400 in case of a bad request +500 internal server error +If the request is a Conditional PUT, and it does not mat we response 415, precondition failed. + +Suposse that we had created an Order with the values shown in the _How to create an order with POST_ +But we change our decision and we want to stay in the shop. + + + + PUT /order/1 HTTP/1.1 + Content-Length: 122 + Content-Type: application/json; charset=UTF-8 + Host: localhost:8080 + Connection: Keep-Alive + Expect: 100-Continue + + { + "location" : "in shop", + "status" : "submitted", + "items" : [ { + "name" : "late", + "size" : "small", + "quantity" : 1, + "option" : "skim" + } ] + } + + +Response success + + HTTP/1.1 200 OK + Status 200 OK + Content-Type application/json + Date FRI,09 DEC 2011 21:06:26.00 GMT + etag 8767F900674B843E1F3F70BCF3E62403 + Content-Length 122 + + { + "location" : "in shop", + "status" : "submitted", + "items" : [ { + "name" : "late", + "size" : "small", + "quantity" : 1, + "option" : "skim" + } ] + } + +How to Delete an order with DELETE +---------------------------------- +Here we use DELETE to cancel an order, if that order is in state where it can still be canceled. +204 if is ok +404 Resource not found +405 if consumer and service's view of the resouce state is inconsisent +500 if we have an internal server error + + + DELETE /order/1 HTTP/1.1 + Host: localhost:8080 + Connection: Keep-Alive + +Response success + + HTTP/1.1 204 No Content + + Status 204 No Content + Content-Type application/json + Date FRI,09 DEC 2011 21:10:51.00 GMT + +If we want to check that the resource does not exist anymore we can try to retrieve a GET /order/1 and we will receive a +404 No Found + + GET /order/1 HTTP/1.1 + Host: localhost:8080 + Connection: Keep-Alive + +Response + + HTTP/1.1 404 Not Found + + Status 404 Not Found + Content-Type application/json + Content-Length 44 + Date FRI,09 DEC 2011 21:14:17.79 GMT + + The following resource/order/1 is not found + + +References +---------- +1. [How to get a cup of coffe](http://www.infoq.com/articles/webber-rest-workflow) +2. [Rest in Practice] (http://restinpractice.com/default.aspx) diff --git a/examples/restbucksCRUD/readme.txt b/examples/restbucksCRUD/readme.txt new file mode 100644 index 00000000..e69de29b diff --git a/examples/restbucks/restbucks-safe.ecf b/examples/restbucksCRUD/restbucks-safe.ecf similarity index 100% rename from examples/restbucks/restbucks-safe.ecf rename to examples/restbucksCRUD/restbucks-safe.ecf diff --git a/examples/restbucks/restbucks.rc b/examples/restbucksCRUD/restbucks.rc similarity index 100% rename from examples/restbucks/restbucks.rc rename to examples/restbucksCRUD/restbucks.rc diff --git a/examples/restbucks/src/database/database_api.e b/examples/restbucksCRUD/src/database/database_api.e similarity index 100% rename from examples/restbucks/src/database/database_api.e rename to examples/restbucksCRUD/src/database/database_api.e diff --git a/examples/restbucks/src/database/shared_database_api.e b/examples/restbucksCRUD/src/database/shared_database_api.e similarity index 100% rename from examples/restbucks/src/database/shared_database_api.e rename to examples/restbucksCRUD/src/database/shared_database_api.e diff --git a/examples/restbucks/src/domain/item.e b/examples/restbucksCRUD/src/domain/item.e similarity index 100% rename from examples/restbucks/src/domain/item.e rename to examples/restbucksCRUD/src/domain/item.e diff --git a/examples/restbucks/src/domain/item_constants.e b/examples/restbucksCRUD/src/domain/item_constants.e similarity index 100% rename from examples/restbucks/src/domain/item_constants.e rename to examples/restbucksCRUD/src/domain/item_constants.e diff --git a/examples/restbucks/src/domain/json_order_converter.e b/examples/restbucksCRUD/src/domain/json_order_converter.e similarity index 97% rename from examples/restbucks/src/domain/json_order_converter.e rename to examples/restbucksCRUD/src/domain/json_order_converter.e index d911bf6a..711f6dd3 100644 --- a/examples/restbucks/src/domain/json_order_converter.e +++ b/examples/restbucksCRUD/src/domain/json_order_converter.e @@ -38,7 +38,7 @@ feature -- Conversion s_location ?= json.object (j.item (location_key), Void) s_status ?= json.object (j.item (status_key), Void) - create o.make (s_id, s_location, s_status) + create o.make ("", s_location, s_status) if attached {JSON_ARRAY} j.item (items_key) as l_val then l_array := l_val.array_representation @@ -87,7 +87,7 @@ feature -- Conversion jv: JSON_OBJECT do create Result.make - Result.put (json.value (o.id), id_key) +-- Result.put (json.value (o.id), id_key) Result.put (json.value (o.location), location_key) Result.put (json.value (o.status), status_key) from diff --git a/examples/restbucks/src/domain/order.e b/examples/restbucksCRUD/src/domain/order.e similarity index 100% rename from examples/restbucks/src/domain/order.e rename to examples/restbucksCRUD/src/domain/order.e diff --git a/examples/restbucks/src/domain/order_validation.e b/examples/restbucksCRUD/src/domain/order_validation.e similarity index 100% rename from examples/restbucks/src/domain/order_validation.e rename to examples/restbucksCRUD/src/domain/order_validation.e diff --git a/examples/restbucks/src/domain/shared_order_validation.e b/examples/restbucksCRUD/src/domain/shared_order_validation.e similarity index 100% rename from examples/restbucks/src/domain/shared_order_validation.e rename to examples/restbucksCRUD/src/domain/shared_order_validation.e diff --git a/examples/restbucks/src/resource/order_handler.e b/examples/restbucksCRUD/src/resource/order_handler.e similarity index 90% rename from examples/restbucks/src/resource/order_handler.e rename to examples/restbucksCRUD/src/resource/order_handler.e index b66b9fe5..561e31ac 100644 --- a/examples/restbucks/src/resource/order_handler.e +++ b/examples/restbucksCRUD/src/resource/order_handler.e @@ -66,9 +66,9 @@ feature -- HTTP Methods local etag_util : ETAG_UTILS do - if attached req.meta_variable ("HTTP_IF_NONE_MATCH") as if_none_match then + if attached req.meta_string_variable ("HTTP_IF_NONE_MATCH") as if_none_match then create etag_util - if if_none_match.as_string.same_string (etag_util.md5_digest (l_order.out).as_string_32) then + if if_none_match.same_string (etag_util.md5_digest (l_order.out).as_string_32) then Result := True end end @@ -108,26 +108,31 @@ feature -- HTTP Methods -- If the request is a Conditional PUT, and it does not mat we response -- 415, precondition failed. local - l_post: STRING + l_put: STRING l_order : detachable ORDER + id : STRING do - req.input.read_string (req.content_length_value.as_integer_32) - l_post := req.input.last_string - l_order := extract_order_request(l_post) - if l_order /= Void and then db_access.orders.has_key (l_order.id) then - if is_valid_to_update(l_order) then - if is_conditional_put (req, l_order) then - update_order( l_order) - compute_response_put (ctx, req, res, l_order) + if attached req.orig_path_info as orig_path then + id := get_order_id_from_path (orig_path) + req.input.read_string (req.content_length_value.as_integer_32) + l_put := req.input.last_string + l_order := extract_order_request(l_put) + if l_order /= Void and then db_access.orders.has_key (id) then + l_order.set_id (id) + if is_valid_to_update(l_order) then + if is_conditional_put (req, l_order) then + update_order( l_order) + compute_response_put (ctx, req, res, l_order) + else + handle_precondition_fail_response ("", ctx, req, res) + end else - handle_precondition_fail_response ("", ctx, req, res) + --| FIXME: Here we need to define the Allow methods + handle_resource_conflict_response (l_put +"%N There is conflict while trying to update the order, the order could not be update in the current state", ctx, req, res) end else - --| FIXME: Here we need to define the Allow methods - handle_resource_conflict_response (l_post +"%N There is conflict while trying to update the order, the order could not be update in the current state", ctx, req, res) + handle_bad_request_response (l_put +"%N is not a valid ORDER, maybe the order does not exist in the system", ctx, req, res) end - else - handle_bad_request_response (l_post +"%N is not a valid ORDER, maybe the order does not exist in the system", ctx, req, res) end end @@ -138,9 +143,9 @@ feature -- HTTP Methods etag_util : ETAG_UTILS do if attached retrieve_order (order.id) as l_order then - if attached req.meta_variable ("HTTP_IF_MATCH") as if_match then + if attached req.meta_string_variable ("HTTP_IF_MATCH") as if_match then create etag_util - if if_match.as_string.same_string (etag_util.md5_digest (l_order.out).as_string_32) then + if if_match.same_string (etag_util.md5_digest (l_order.out).as_string_32) then Result := True end else diff --git a/examples/restbucks/src/restbucks_server.e b/examples/restbucksCRUD/src/restbucks_server.e similarity index 100% rename from examples/restbucks/src/restbucks_server.e rename to examples/restbucksCRUD/src/restbucks_server.e diff --git a/examples/restbucks/src/utils/etag_utils.e b/examples/restbucksCRUD/src/utils/etag_utils.e similarity index 100% rename from examples/restbucks/src/utils/etag_utils.e rename to examples/restbucksCRUD/src/utils/etag_utils.e diff --git a/examples/simple/application.e b/examples/simple/application.e new file mode 100644 index 00000000..619de17e --- /dev/null +++ b/examples/simple/application.e @@ -0,0 +1,30 @@ +note + description : "simple application root class" + date : "$Date$" + revision : "$Revision$" + +class + APPLICATION + +create + make + +feature {NONE} -- Initialization + + make + -- Run application. + local + s: DEFAULT_SERVICE + do + create s.make_and_launch (agent execute) + end + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + do + -- To send a response we need to setup, the status code and + -- the response headers. + res.write_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/plain"], ["Content-Length", "11"]>>) + res.write_string ("Hello World") + end + +end diff --git a/examples/simple/simple.ecf b/examples/simple/simple.ecf new file mode 100644 index 00000000..e225d468 --- /dev/null +++ b/examples/simple/simple.ecf @@ -0,0 +1,23 @@ + + + + + + + + + + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + + + + diff --git a/examples/simple/simple.rc b/examples/simple/simple.rc new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/simple/simple.rc @@ -0,0 +1 @@ + diff --git a/examples/simple_file/home.html b/examples/simple_file/home.html new file mode 100644 index 00000000..35ab8098 --- /dev/null +++ b/examples/simple_file/home.html @@ -0,0 +1,13 @@ + + + Eiffel REST services + + + + Welcome to the Eiffel REST services site, here you will find a lot of
+ resources about REST and our solution + + + + + \ No newline at end of file diff --git a/examples/simple_file/service_file.e b/examples/simple_file/service_file.e new file mode 100644 index 00000000..df365de6 --- /dev/null +++ b/examples/simple_file/service_file.e @@ -0,0 +1,29 @@ +note + description : "simple application root class" + date : "$Date$" + revision : "$Revision$" + +class + SERVICE_FILE + +create + make + +feature {NONE} -- Initialization + + make + -- Run application. + local + s: DEFAULT_SERVICE + do + create s.make_and_launch (agent execute) + end + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + local + f: WSF_FILE_RESPONSE + do + create f.make_html ("home.html") + res.put_response (f) + end +end diff --git a/examples/simple_file/service_file.ecf b/examples/simple_file/service_file.ecf new file mode 100644 index 00000000..1471600d --- /dev/null +++ b/examples/simple_file/service_file.ecf @@ -0,0 +1,24 @@ + + + + + + + + + + + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + + + + diff --git a/examples/simple_file/service_file.rc b/examples/simple_file/service_file.rc new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/simple_file/service_file.rc @@ -0,0 +1 @@ + diff --git a/library/client/http_client/src/http_client_request.e b/library/client/http_client/src/http_client_request.e index 597ce816..8749640c 100644 --- a/library/client/http_client/src/http_client_request.e +++ b/library/client/http_client/src/http_client_request.e @@ -12,16 +12,25 @@ inherit feature {NONE} -- Initialization - make (a_url: READABLE_STRING_8; a_session: like session) + make (a_url: READABLE_STRING_8; a_session: like session; ctx: like context) -- Initialize `Current'. do session := a_session url := a_url headers := session.headers.twin + if ctx /= Void then + context := ctx + import (ctx) + end + ensure + context_set: context = ctx + ctx_header_set: ctx /= Void implies across ctx.headers as ctx_h all attached headers.item (ctx_h.key) as v and then v.same_string (ctx_h.item) end end session: HTTP_CLIENT_SESSION + context: detachable HTTP_CLIENT_REQUEST_CONTEXT + feature -- Access request_method: READABLE_STRING_8 @@ -32,14 +41,23 @@ feature -- Access headers: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8] -feature -- Execution +feature {HTTP_CLIENT_SESSION} -- Execution import (ctx: HTTP_CLIENT_REQUEST_CONTEXT) + local + l_headers: like headers do - headers.fill (ctx.headers) + l_headers := headers + across + ctx.headers as ctx_headers + loop + --| fill header from `ctx' + --| and use `force' to overwrite the "session" value if any + l_headers.force (ctx_headers.item, ctx_headers.key) + end end - execute (ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE + execute: HTTP_CLIENT_RESPONSE deferred end diff --git a/library/client/http_client/src/http_client_response.e b/library/client/http_client/src/http_client_response.e index bafa8523..5685ff73 100644 --- a/library/client/http_client/src/http_client_response.e +++ b/library/client/http_client/src/http_client_response.e @@ -41,8 +41,43 @@ feature -- Access raw_header: READABLE_STRING_8 -- Raw http header of the response. + header (a_name: READABLE_STRING_8): detachable READABLE_STRING_8 + -- Header entry value related to `a_name' + -- if multiple entries, just concatenate them using comma character + --| See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html + --| Multiple message-header fields with the same field-name MAY be present in a message + --| if and only if the entire field-value for that header field is defined as a comma-separated list [i.e., #(values)]. + --| It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair, + --| without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma. + --| The order in which header fields with the same field-name are received is therefore significant + --| to the interpretation of the combined field value, and thus a proxy MUST NOT change the order of + --| these field values when a message is forwarded. + local + s: detachable STRING_8 + k,v: READABLE_STRING_8 + do + across + headers as hds + loop + k := hds.item.key + if k.same_string (a_name) then + v := hds.item.value + if s = Void then + create s.make_from_string (v) + else + s.append_character (',') + s.append (v) + end + end + end + Result := s + end + headers: LIST [TUPLE [key: READABLE_STRING_8; value: READABLE_STRING_8]] -- Computed table of http headers of the response. + --| We use a LIST since one might have multiple message-header fields with the same field-name + --| Then the user can handle those case using default or custom concatenation + --| (note: `header' is concatenating using comma) local tb: like internal_headers pos, l_start, l_end, n, c: INTEGER @@ -126,6 +161,6 @@ feature -- Change feature {NONE} -- Implementation internal_headers: detachable ARRAYED_LIST [TUPLE [key: READABLE_STRING_8; value: READABLE_STRING_8]] - -- Internal cached value for the headers + -- Internal cached value for the headers end diff --git a/library/client/http_client/src/spec/libcurl/libcurl_http_client_request.e b/library/client/http_client/src/spec/libcurl/libcurl_http_client_request.e index b29bfeee..160f6715 100644 --- a/library/client/http_client/src/spec/libcurl/libcurl_http_client_request.e +++ b/library/client/http_client/src/spec/libcurl/libcurl_http_client_request.e @@ -20,9 +20,9 @@ create feature {NONE} -- Initialization - make (a_url: READABLE_STRING_8; a_request_method: like request_method; a_session: like session) + make (a_url: READABLE_STRING_8; a_request_method: like request_method; a_session: like session; ctx: like context) do - make_request (a_url, a_session) + make_request (a_url, a_session, ctx) request_method := a_request_method end @@ -34,7 +34,7 @@ feature -- Access feature -- Execution - execute (ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE + execute: HTTP_CLIENT_RESPONSE local l_result: INTEGER l_curl_string: CURL_STRING @@ -45,7 +45,9 @@ feature -- Execution curl: CURL_EXTERNALS curl_easy: CURL_EASY_EXTERNALS curl_handle: POINTER + ctx: like context do + ctx := context curl := session.curl curl_easy := session.curl_easy @@ -167,7 +169,15 @@ feature -- Execution p := curl.slist_append (p, curs.key + ": " + curs.item) end end - + if ctx /= Void then + if attached ctx.headers as l_headers_2 then + across + l_headers_2 as curs_2 + loop + p := curl.slist_append (p, curs_2.key + ": " + curs_2.item) + end + end + end p := curl.slist_append (p, "Expect:") curl_easy.setopt_slist (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpheader, p) diff --git a/library/client/http_client/src/spec/libcurl/libcurl_http_client_session.e b/library/client/http_client/src/spec/libcurl/libcurl_http_client_session.e index 24ccce32..7853657b 100644 --- a/library/client/http_client/src/spec/libcurl/libcurl_http_client_session.e +++ b/library/client/http_client/src/spec/libcurl/libcurl_http_client_session.e @@ -27,16 +27,16 @@ feature -- Basic operation local req: HTTP_CLIENT_REQUEST do - create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "GET", Current) - Result := execute_request (req, ctx) + create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "GET", Current, ctx) + Result := req.execute end head (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE local req: HTTP_CLIENT_REQUEST do - create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "HEAD", Current) - Result := execute_request (req, ctx) + create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "HEAD", Current, ctx) + Result := req.execute end post (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE @@ -54,7 +54,6 @@ feature -- Basic operation req: HTTP_CLIENT_REQUEST ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT do - create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "POST", Current) ctx := a_ctx if data /= Void then if ctx = Void then @@ -68,7 +67,8 @@ feature -- Basic operation end ctx.set_upload_filename (fn) end - Result := execute_request (req, ctx) + create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "POST", Current, ctx) + Result := req.execute end put (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE @@ -76,7 +76,6 @@ feature -- Basic operation req: HTTP_CLIENT_REQUEST ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT do - create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "PUT", Current) ctx := a_ctx if data /= Void then if ctx = Void then @@ -84,7 +83,8 @@ feature -- Basic operation end ctx.set_upload_data (data) end - Result := execute_request (req, ctx) + create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "PUT", Current, ctx) + Result := req.execute end put_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE @@ -92,7 +92,6 @@ feature -- Basic operation req: HTTP_CLIENT_REQUEST ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT do - create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "PUT", Current) ctx := a_ctx if fn /= Void then if ctx = Void then @@ -100,25 +99,16 @@ feature -- Basic operation end ctx.set_upload_filename (fn) end - Result := execute_request (req, ctx) + create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "PUT", Current, ctx) + Result := req.execute end delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE local req: HTTP_CLIENT_REQUEST do - create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "DELETE", Current) - Result := execute_request (req, ctx) - end - -feature {NONE} -- Implementation - - execute_request (req: HTTP_CLIENT_REQUEST; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE - do - if ctx /= Void then - req.import (ctx) - end - Result := req.execute (ctx) + create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "DELETE", Current, ctx) + Result := req.execute end feature {LIBCURL_HTTP_CLIENT_REQUEST} -- Curl implementation diff --git a/library/protocol/http/src/http_header.e b/library/protocol/http/src/http_header.e index 19385b1c..441eeb5a 100644 --- a/library/protocol/http/src/http_header.e +++ b/library/protocol/http/src/http_header.e @@ -196,13 +196,13 @@ feature -- Content related header put_transfer_encoding_binary -- Put "Transfer-Encoding: binary" header do - put_transfer_encoding ("binary") + put_transfer_encoding (str_binary) end put_transfer_encoding_chunked -- Put "Transfer-Encoding: chunked" header do - put_transfer_encoding ("chunked") + put_transfer_encoding (str_chunked) end put_content_disposition (a_type: READABLE_STRING_8; a_params: detachable READABLE_STRING_8) @@ -354,6 +354,35 @@ feature -- Cookie feature -- Status report + header_named_value (a_name: READABLE_STRING_8): detachable STRING_8 + -- Has header item for `n'? + require + has_header: has_header_named (a_name) + local + c: like headers.new_cursor + n: INTEGER + l_line: READABLE_STRING_8 + do + from + n := a_name.count + c := headers.new_cursor + until + c.after or Result /= Void + loop + l_line := c.item + if l_line.starts_with (a_name) then + if l_line.valid_index (n + 1) then + if l_line [n + 1] = ':' then + Result := l_line.substring (n + 2, l_line.count) + Result.left_adjust + Result.right_adjust + end + end + end + c.forth + end + end + has_header_named (a_name: READABLE_STRING_8): BOOLEAN -- Has header item for `n'? local @@ -378,11 +407,26 @@ feature -- Status report end has_content_length: BOOLEAN - -- Has header "content_length" + -- Has header "Content-Length" do Result := has_header_named ({HTTP_HEADER_NAMES}.header_content_length) end + has_content_type: BOOLEAN + -- Has header "Content-Type" + do + Result := has_header_named ({HTTP_HEADER_NAMES}.header_content_type) + end + + has_transfer_encoding_chunked: BOOLEAN + -- Has "Transfer-Encoding: chunked" header + do + if has_header_named ({HTTP_HEADER_NAMES}.header_transfer_encoding) then + Result := attached header_named_value ({HTTP_HEADER_NAMES}.header_transfer_encoding) as v and then v.same_string (str_chunked) + end + end + + feature {NONE} -- Implementation: Header force_header_by_name (n: detachable READABLE_STRING_8; h: READABLE_STRING_8) @@ -456,6 +500,9 @@ feature {NONE} -- Implementation feature {NONE} -- Constants + str_binary: STRING = "binary" + str_chunked: STRING = "chunked" + colon_space: STRING = ": " semi_colon_space: STRING = "; " diff --git a/library/server/wsf/src/response/wsf_page_response.e b/library/server/wsf/src/response/wsf_page_response.e index 25773eef..bbe85b38 100644 --- a/library/server/wsf/src/response/wsf_page_response.e +++ b/library/server/wsf/src/response/wsf_page_response.e @@ -11,7 +11,11 @@ inherit WSF_RESPONSE_MESSAGE create - make + make, + make_with_body + +convert + make_with_body ({READABLE_STRING_8, STRING_8, IMMUTABLE_STRING_8}) feature {NONE} -- Initialization @@ -21,6 +25,12 @@ feature {NONE} -- Initialization create header.make end + make_with_body (a_body: READABLE_STRING_8) + do + make + body := a_body + end + feature -- Status status_code: INTEGER @@ -34,10 +44,24 @@ feature -- Header feature -- Output send_to (res: WSF_RESPONSE) + local + b: like body + h: like header do + h := header + b := body res.set_status_code (status_code) - res.write_header_text (header.string) - if attached body as b then + + if b /= Void then + if not h.has_content_length then + h.put_content_length (b.count) + end + if not h.has_content_type then + h.put_content_type_text_plain + end + end + res.write_header_text (h.string) + if b /= Void then res.write_string (b) end end diff --git a/library/server/wsf/src/wsf_request.e b/library/server/wsf/src/wsf_request.e index ea92efd8..0db688c9 100644 --- a/library/server/wsf/src/wsf_request.e +++ b/library/server/wsf/src/wsf_request.e @@ -26,19 +26,22 @@ convert feature {NONE} -- Initialization make_from_wgi (r: WGI_REQUEST) + local + tb: like meta_variables_table do wgi_request := r if attached r.meta_variables as l_vars then - create meta_variables_table.make (l_vars.count) + create tb.make (l_vars.count) across l_vars as c loop - meta_variables_table.force (new_string_value (c.key, c.item), c.item) + tb.force (new_string_value (c.key, c.item), c.key) end else - create meta_variables_table.make (0) + create tb.make (0) end - meta_variables := meta_variables_table + meta_variables_table := tb + meta_variables := tb create error_handler.make create uploaded_files.make (0) raw_post_data_recorded := True