Merge changes from Javier

- update on RESTbuck examples
- new example
- fixed bad typo in WSF_REQUEST

Reverted some changes such as
- http_client_response: keep the headers as a list to handle multiple message-value with same message-name

Fixed simple and simple_file example
Improved HTTP_HEADER

Changed libcurl implementation for http client
- now the header from the context really overwrite any of the session headers
- better design which is more strict, and remove any doubt about context's header usage
This commit is contained in:
Jocelyn Fiat
2011-12-12 16:03:38 +01:00
38 changed files with 779 additions and 186 deletions

View File

@@ -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

View File

@@ -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<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

@@ -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

View File

@@ -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
-----------------
<table>
<TR><TH>Verb</TH> <TH>URI or template</TH> <TH>Use</TH></TR>
<TR><TD>POST</TD> <TD>/order</TD> <TD>Create a new order, and upon success, receive a Locationheader specifying the new order's URI.</TD></TR>
<TR><TD>GET</TD> <TD>/order/{orderId}</TD> <TD>Request the current state of the order specified by the URI.</TD></TR>
<TR><TD>PUT</TD> <TD>/order/{orderId}</TD> <TD>Update an order at the given URI with new information, providing the full representation.</TD></TR>
<TR><TD>DELETE</TD> <TD>/order/{orderId}</TD> <TD>Logically remove the order identified by the given URI.</TD></TR>
</table>
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: <br/>
1. *A resource can have multiple URIs*.<br/>
2. *A resource can have multiple Representations*.<br/>
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)

View File

View File

@@ -38,7 +38,7 @@ feature -- Conversion
s_location ?= json.object (j.item (location_key), Void) s_location ?= json.object (j.item (location_key), Void)
s_status ?= json.object (j.item (status_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 if attached {JSON_ARRAY} j.item (items_key) as l_val then
l_array := l_val.array_representation l_array := l_val.array_representation
@@ -87,7 +87,7 @@ feature -- Conversion
jv: JSON_OBJECT jv: JSON_OBJECT
do do
create Result.make 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.location), location_key)
Result.put (json.value (o.status), status_key) Result.put (json.value (o.status), status_key)
from from

View File

@@ -66,9 +66,9 @@ feature -- HTTP Methods
local local
etag_util : ETAG_UTILS etag_util : ETAG_UTILS
do 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 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 Result := True
end end
end end
@@ -108,26 +108,31 @@ feature -- HTTP Methods
-- If the request is a Conditional PUT, and it does not mat we response -- If the request is a Conditional PUT, and it does not mat we response
-- 415, precondition failed. -- 415, precondition failed.
local local
l_post: STRING l_put: STRING
l_order : detachable ORDER l_order : detachable ORDER
id : STRING
do do
req.input.read_string (req.content_length_value.as_integer_32) if attached req.orig_path_info as orig_path then
l_post := req.input.last_string id := get_order_id_from_path (orig_path)
l_order := extract_order_request(l_post) req.input.read_string (req.content_length_value.as_integer_32)
if l_order /= Void and then db_access.orders.has_key (l_order.id) then l_put := req.input.last_string
if is_valid_to_update(l_order) then l_order := extract_order_request(l_put)
if is_conditional_put (req, l_order) then if l_order /= Void and then db_access.orders.has_key (id) then
update_order( l_order) l_order.set_id (id)
compute_response_put (ctx, req, res, l_order) 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 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 end
else else
--| FIXME: Here we need to define the Allow methods handle_bad_request_response (l_put +"%N is not a valid ORDER, maybe the order does not exist in the system", ctx, req, res)
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)
end 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
end end
@@ -138,9 +143,9 @@ feature -- HTTP Methods
etag_util : ETAG_UTILS etag_util : ETAG_UTILS
do do
if attached retrieve_order (order.id) as l_order then 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 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 Result := True
end end
else else

View File

@@ -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

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-9-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-9-0 http://www.eiffel.com/developers/xml/configuration-1-9-0.xsd" name="simple" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486">
<target name="simple">
<root class="APPLICATION" feature="make"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="default_nino" location="..\..\library\server\wsf\default\nino-safe.ecf"/>
<library name="http" location="..\..\library\protocol\http\http-safe.ecf"/>
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf"/>
<cluster name="simple" location=".\" recursive="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
</cluster>
</target>
<target name="simple_dotnet" extends="simple">
<setting name="msil_generation" value="true"/>
</target>
</system>

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,13 @@
<html>
<head>
Eiffel REST services
</head>
<body>
Welcome to the Eiffel REST services site, here you will find a lot of <br/>
resources about REST and our solution
</body>
</html>

View File

@@ -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

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-9-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-9-0 http://www.eiffel.com/developers/xml/configuration-1-9-0.xsd" name="service_file" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486">
<target name="service_file">
<root class="SERVICE_FILE" feature="make"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="connector_nino" location="..\..\library\server\ewsgi\connectors\nino\nino-safe.ecf"/>
<library name="default_nino" location="..\..\library\server\wsf\default\nino-safe.ecf"/>
<library name="http" location="..\..\library\protocol\http\http-safe.ecf"/>
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf"/>
<cluster name="service_file" location=".\" recursive="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
</cluster>
</target>
<target name="service_file_dotnet" extends="service_file">
<setting name="msil_generation" value="true"/>
</target>
</system>

View File

@@ -0,0 +1 @@

View File

@@ -12,16 +12,25 @@ inherit
feature {NONE} -- Initialization 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'. -- Initialize `Current'.
do do
session := a_session session := a_session
url := a_url url := a_url
headers := session.headers.twin 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 end
session: HTTP_CLIENT_SESSION session: HTTP_CLIENT_SESSION
context: detachable HTTP_CLIENT_REQUEST_CONTEXT
feature -- Access feature -- Access
request_method: READABLE_STRING_8 request_method: READABLE_STRING_8
@@ -32,14 +41,23 @@ feature -- Access
headers: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8] headers: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]
feature -- Execution feature {HTTP_CLIENT_SESSION} -- Execution
import (ctx: HTTP_CLIENT_REQUEST_CONTEXT) import (ctx: HTTP_CLIENT_REQUEST_CONTEXT)
local
l_headers: like headers
do 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 end
execute (ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE execute: HTTP_CLIENT_RESPONSE
deferred deferred
end end

View File

@@ -41,8 +41,43 @@ feature -- Access
raw_header: READABLE_STRING_8 raw_header: READABLE_STRING_8
-- Raw http header of the response. -- 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]] headers: LIST [TUPLE [key: READABLE_STRING_8; value: READABLE_STRING_8]]
-- Computed table of http headers of the response. -- 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 local
tb: like internal_headers tb: like internal_headers
pos, l_start, l_end, n, c: INTEGER pos, l_start, l_end, n, c: INTEGER
@@ -126,6 +161,6 @@ feature -- Change
feature {NONE} -- Implementation feature {NONE} -- Implementation
internal_headers: detachable ARRAYED_LIST [TUPLE [key: READABLE_STRING_8; value: READABLE_STRING_8]] 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 end

View File

@@ -20,9 +20,9 @@ create
feature {NONE} -- Initialization 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 do
make_request (a_url, a_session) make_request (a_url, a_session, ctx)
request_method := a_request_method request_method := a_request_method
end end
@@ -34,7 +34,7 @@ feature -- Access
feature -- Execution feature -- Execution
execute (ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE execute: HTTP_CLIENT_RESPONSE
local local
l_result: INTEGER l_result: INTEGER
l_curl_string: CURL_STRING l_curl_string: CURL_STRING
@@ -45,7 +45,9 @@ feature -- Execution
curl: CURL_EXTERNALS curl: CURL_EXTERNALS
curl_easy: CURL_EASY_EXTERNALS curl_easy: CURL_EASY_EXTERNALS
curl_handle: POINTER curl_handle: POINTER
ctx: like context
do do
ctx := context
curl := session.curl curl := session.curl
curl_easy := session.curl_easy curl_easy := session.curl_easy
@@ -167,7 +169,15 @@ feature -- Execution
p := curl.slist_append (p, curs.key + ": " + curs.item) p := curl.slist_append (p, curs.key + ": " + curs.item)
end end
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:") p := curl.slist_append (p, "Expect:")
curl_easy.setopt_slist (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpheader, p) curl_easy.setopt_slist (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpheader, p)

View File

@@ -27,16 +27,16 @@ feature -- Basic operation
local local
req: HTTP_CLIENT_REQUEST req: HTTP_CLIENT_REQUEST
do do
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "GET", Current) create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "GET", Current, ctx)
Result := execute_request (req, ctx) Result := req.execute
end end
head (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE head (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
local local
req: HTTP_CLIENT_REQUEST req: HTTP_CLIENT_REQUEST
do do
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "HEAD", Current) create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "HEAD", Current, ctx)
Result := execute_request (req, ctx) Result := req.execute
end end
post (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE 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 req: HTTP_CLIENT_REQUEST
ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT
do do
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "POST", Current)
ctx := a_ctx ctx := a_ctx
if data /= Void then if data /= Void then
if ctx = Void then if ctx = Void then
@@ -68,7 +67,8 @@ feature -- Basic operation
end end
ctx.set_upload_filename (fn) ctx.set_upload_filename (fn)
end end
Result := execute_request (req, ctx) create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "POST", Current, ctx)
Result := req.execute
end end
put (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE 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 req: HTTP_CLIENT_REQUEST
ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT
do do
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "PUT", Current)
ctx := a_ctx ctx := a_ctx
if data /= Void then if data /= Void then
if ctx = Void then if ctx = Void then
@@ -84,7 +83,8 @@ feature -- Basic operation
end end
ctx.set_upload_data (data) ctx.set_upload_data (data)
end end
Result := execute_request (req, ctx) create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "PUT", Current, ctx)
Result := req.execute
end end
put_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE 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 req: HTTP_CLIENT_REQUEST
ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT
do do
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "PUT", Current)
ctx := a_ctx ctx := a_ctx
if fn /= Void then if fn /= Void then
if ctx = Void then if ctx = Void then
@@ -100,25 +99,16 @@ feature -- Basic operation
end end
ctx.set_upload_filename (fn) ctx.set_upload_filename (fn)
end end
Result := execute_request (req, ctx) create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "PUT", Current, ctx)
Result := req.execute
end end
delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
local local
req: HTTP_CLIENT_REQUEST req: HTTP_CLIENT_REQUEST
do do
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "DELETE", Current) create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "DELETE", Current, ctx)
Result := execute_request (req, ctx) Result := req.execute
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)
end end
feature {LIBCURL_HTTP_CLIENT_REQUEST} -- Curl implementation feature {LIBCURL_HTTP_CLIENT_REQUEST} -- Curl implementation

View File

@@ -196,13 +196,13 @@ feature -- Content related header
put_transfer_encoding_binary put_transfer_encoding_binary
-- Put "Transfer-Encoding: binary" header -- Put "Transfer-Encoding: binary" header
do do
put_transfer_encoding ("binary") put_transfer_encoding (str_binary)
end end
put_transfer_encoding_chunked put_transfer_encoding_chunked
-- Put "Transfer-Encoding: chunked" header -- Put "Transfer-Encoding: chunked" header
do do
put_transfer_encoding ("chunked") put_transfer_encoding (str_chunked)
end end
put_content_disposition (a_type: READABLE_STRING_8; a_params: detachable READABLE_STRING_8) put_content_disposition (a_type: READABLE_STRING_8; a_params: detachable READABLE_STRING_8)
@@ -354,6 +354,35 @@ feature -- Cookie
feature -- Status report 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_named (a_name: READABLE_STRING_8): BOOLEAN
-- Has header item for `n'? -- Has header item for `n'?
local local
@@ -378,11 +407,26 @@ feature -- Status report
end end
has_content_length: BOOLEAN has_content_length: BOOLEAN
-- Has header "content_length" -- Has header "Content-Length"
do do
Result := has_header_named ({HTTP_HEADER_NAMES}.header_content_length) Result := has_header_named ({HTTP_HEADER_NAMES}.header_content_length)
end 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 feature {NONE} -- Implementation: Header
force_header_by_name (n: detachable READABLE_STRING_8; h: READABLE_STRING_8) force_header_by_name (n: detachable READABLE_STRING_8; h: READABLE_STRING_8)
@@ -456,6 +500,9 @@ feature {NONE} -- Implementation
feature {NONE} -- Constants feature {NONE} -- Constants
str_binary: STRING = "binary"
str_chunked: STRING = "chunked"
colon_space: STRING = ": " colon_space: STRING = ": "
semi_colon_space: STRING = "; " semi_colon_space: STRING = "; "

View File

@@ -11,7 +11,11 @@ inherit
WSF_RESPONSE_MESSAGE WSF_RESPONSE_MESSAGE
create create
make make,
make_with_body
convert
make_with_body ({READABLE_STRING_8, STRING_8, IMMUTABLE_STRING_8})
feature {NONE} -- Initialization feature {NONE} -- Initialization
@@ -21,6 +25,12 @@ feature {NONE} -- Initialization
create header.make create header.make
end end
make_with_body (a_body: READABLE_STRING_8)
do
make
body := a_body
end
feature -- Status feature -- Status
status_code: INTEGER status_code: INTEGER
@@ -34,10 +44,24 @@ feature -- Header
feature -- Output feature -- Output
send_to (res: WSF_RESPONSE) send_to (res: WSF_RESPONSE)
local
b: like body
h: like header
do do
h := header
b := body
res.set_status_code (status_code) 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) res.write_string (b)
end end
end end

View File

@@ -26,19 +26,22 @@ convert
feature {NONE} -- Initialization feature {NONE} -- Initialization
make_from_wgi (r: WGI_REQUEST) make_from_wgi (r: WGI_REQUEST)
local
tb: like meta_variables_table
do do
wgi_request := r wgi_request := r
if attached r.meta_variables as l_vars then if attached r.meta_variables as l_vars then
create meta_variables_table.make (l_vars.count) create tb.make (l_vars.count)
across across
l_vars as c l_vars as c
loop 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 end
else else
create meta_variables_table.make (0) create tb.make (0)
end end
meta_variables := meta_variables_table meta_variables_table := tb
meta_variables := tb
create error_handler.make create error_handler.make
create uploaded_files.make (0) create uploaded_files.make (0)
raw_post_data_recorded := True raw_post_data_recorded := True