This commit is contained in:
jvelilla
2013-08-21 08:33:53 -03:00
52 changed files with 3318 additions and 112 deletions

View File

@@ -15,25 +15,15 @@ For download, check
* https://github.com/EiffelWebFramework/EWF/downloads
## Requirements
* Compiling from EiffelStudio 7.0
* Developped using EiffelStudio 7.1 (on Windows, Linux)
* Tested using EiffelStudio 7.1 with "jenkins" CI server (not anymore compatible with 6.8 due to use of `TABLE_ITERABLE')
* Compiling from EiffelStudio 7.2
* Developped using EiffelStudio 7.3 (on Windows, Linux)
* Tested using EiffelStudio 7.2 with "jenkins" CI server (not anymore compatible with 6.8 due to use of `TABLE_ITERABLE')
* The code have to allow __void-safe__ compilation and non void-safe system (see [more about void-safety](http://docs.eiffel.com/book/method/void-safe-programming-eiffel) )
## How to get the source code?
Using git version >= 1.6.5
* git clone --recursive https://github.com/EiffelWebFramework/EWF.git
Otherwise, try
Using git
* git clone https://github.com/EiffelWebFramework/EWF.git
* cd Eiffel-Web-Framework
* git submodule update --init
* git submodule foreach --recursive git checkout master
An alternative to the last 2 instructions is to use the script from tools folder:
* cd tools
* update_git_working_copy
* And to build the required and related Clibs
* cd contrib/ise_library/cURL

View File

@@ -29,7 +29,6 @@
<target name="_build_tpl_" >
<argument name="_target_name" />
<geant target="${_target_name}" dir="cURL" file="build.eant" reuse_variables="true" />
</target>
</project>

View File

@@ -0,0 +1,28 @@
# Using the policy driven framework
**This describes a new facility that is not yet in the EWF release**
## Introduction
The aim of the policy-driven framework is to allow authors of web-servers to concentrate on the business logic (e.g., in the case of a GET request, generating the content), without having to worry about the details of the HTTP protocol (such as headers and response codes). However, there are so many possibilities in the HTTP protocol, that it is impossible to correctly guess what to do in all cases. Therefore the author has to supply policy decisions to the framework, in areas such as caching decisions. These are implemented as a set of deferred classes for which the author needs to provide effective implementations.
We aim to provide unconditional compliance [See HTTP/1.1 specification](http://www.w3.org/Protocols/rfc2616/rfc2616-sec1.html#sec1) for you. Note that byte-ranges are not yet supported.
## Mapping the URI space
The authors first task is to decide which URIs the server will respond to (we do this using [URI templates](http://tools.ietf.org/html/rfc6570) ) and which methods are supported for each template.This is done in the class that that defines the service (which is often the root class for the application). This class must be a descendant of WSF_ROUTED_SKELETON_SERVICE. Throughout this tutorial, we will refer to the restbucksCRUD example application, which can be found in the EWF distribution in the examples directory. It's root class, RESTBUCKS_SERVER, inherits from WSF_ROUTED_SKELETON_SERVICE, as well as WSF_DEFAULT_SERVICE. The latter class means that you must specify in the ECF which connector you will use by default.This means you can easily change connectors just by changing the ECF and recompiling.
### Declaring your URI templates
In order to map your URI space to handlers (which you will write), you need to implement the routine setup_router. You can see in the example that the ORDER_HANDLER handler is associated with two URI templates. The URI /order is associated with the POST method (only). Any requests to /order with the GET method (or any other method) will result in an automatically generated compliant response being sent on your behalf to the client. The other principle methods (you get compliant responses to the HEAD method for free whenever you allow the GET method) are associated with the URI template /order/{orderid}. Here, orderid is a template variable. It's value for any given request is provided to your application as {WSF_REQUEST}.path_parameter ("orderid"). If the client passes a URI of /order/21, then you will see the value 21. If the client passes /order/fred, you will see the value fred. But if the client passes /order/21/new, he will see a compliant error response generated by the framework.
## Declaring your policy in responding to OPTIONS
WSF_ROUTED_SKELETON_SERVICE inherits from WSF_SYSTEM_OPTIONS_ACCESS_POLICY. This policy declares that the framework will provide a compliant default response to OPTIONS * requests. If you prefer to not respond to OPTIONS * requests (and I am doubtful if it is fully compliant to make that choice), then you can redefine
is_system_options_forbidden.
## Declaring your policy on requiring use of a proxy server
WSF_ROUTED_SKELETON_SERVICE also inherits from WSF_PROXY_USE_POLICY. This determines if the server will require clients to use a proxy server. By default, it will do so for HTTP/1.0 clients. This is a sensible default, as the framework assumes an HTTP/1.1 client throughout. If you are sure that you will only ever have HTTP/1.1 clients, then you can instead inherit from WSF_NO_PROXY_POLICY, as RESTBUCKS_SERVER does. If not, then you need to implement proxy_server.
Next you have to [write your handler(s)](./Writing-the-handlers)

View File

@@ -0,0 +1,3 @@
# Implementing routines in WSF_OPTIONS_POLICY
This class provides a default response to OPTIONS requests other than OPTIONS *. So you don't have to do anything. The default response just includes the mandatory Allow headers for all the methods that are allowed for the request URI. if you want to include a body text, or additional header, then you should redefine this routine.

View File

@@ -0,0 +1,223 @@
# Writing the handlers
Now you have to implement each handler. You need to inherit from WSF_SKELETON_HANDLER (as ORDER_HANDLER does). This involves implementing a lot of deferred routines. There are other routines for which default implementations are provided, which you might want to override. This applies to both routines defined in this class, and those declared in the three policy classes from which it inherits.
## Communicating between routines
Depending upon the connector (Nino, CGI, FastCGI etc.) that you are using, your handler may be invoked concurrently for multiple requests. Therefore it is unsafe to save state in normal attributes. WSF_REQUEST has a pair of getter/setter routines, execution_variable/set_execution_variable, which you can use for this purpose.
Internally, the framework uses the following execution variable names, so you must avoid them:
1. REQUEST_ENTITY
1. NEGOTIATED_LANGUAGE
1. NEGOTIATED_CHARSET
1. NEGOTIATED_MEDIA_TYPE
1. NEGOTIATED_ENCODING
1. NEGOTIATED_HTTP_HEADER
1. CONFLICT_CHECK_CODE
1. CONTENT_CHECK_CODE
1. REQUEST_CHECK_CODE
The first one makes the request entity from a PULL or POST request available to your routines.
The next four make the results of content negotiation available to your routines. The sixth one makes an HTTP_HEADER available to your routines. You should use this rather than create your own, as it may contain a **Vary** header as a by-product of content negotiation.
The last three are for reporting the result from check_conflict, check_content and check_request.
All names are defined as constants in WSF_SKELETON_HANDLER, to make it easier for you to refer to them.
## Implementing the routines declared directly in WSF_SKELETON_HANDLER
### check_resource_exists
Here you check for the existence of the resource named by the request URI. If it does, then you need to call set_resource_exists on the helper argument.
Note that if you support multiple representations through content negotiation, then etags are dependent upon
the selected variant. If you support etags, then you will need to make the response entity available at this point, rather than in ensure_content_available.
### is_chunking
HTTP/1.1 supports streaming responses (and providing you have configured your server to use a proxy server in WSF_PROXY_USE_POLICY, this framework guarantees you have an HTTP/1.1 client to deal with). It is up to you whether or not you choose to make use of it. If so, then you have to serve the response one chunk at a time (but you could generate it all at once, and slice it up as you go). In this routine you just say whether or not you will be doing this. So the framework n=knows which other routines to call.
Currently we only support chunking for GET or HEAD routines. This might change in the future, so if you intend to return True, you should call req.is_get_head_request_method.
Note that currently this framework does not support writing a trailer.
### includes_response_entity
The response to a DELETE, PUT or POST will include HTTP headers. It may or may not include a body. It is up to you, and this is where you tell the framework.
### conneg
[The HTTP/1.1 specification](http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.1) defines server-driven content negotiation. Based on the Accept* headers in the request, we can determine whether we have a format for the response entity that is acceptable to the client. You need to indicate what formats you support. The framework does the rest. Normally you will have the same options for all requests, in which case you can use a once object.
### mime_types_supported
Here you need to indicate which media types you support for responses. One of the entries must be passed to the creation routine for conneg.
### languages_supported
Here you need to indicate which languages you support for responses. One of the entries must be passed to the creation routine for conneg.
### charsets_supported
Here you need to indicate which character sets you support for responses. One of the entries must be passed to the creation routine for conneg.
### encodings_supported
Here you need to indicate which compression encodings you support for responses. One of the entries must be passed to the creation routine for conneg.
### additional_variant_headers
The framework will write a Vary header if conneg indicates that different formats are supported. This warns caches that they may not be able to use a cached response if the Accept* headers in the request differ. If the author knows that the response may be affected by other request headers in addition to these, then they must be indicated here, so they can be included in a Vary header with the response.
### predictable_response
If the response may vary in other ways not predictable from the request headers, then redefine this routine to return True. In that case we will generate a Vary: * header to inform the cache that the response is not necessarily repeatable.
### matching_etag
An **ETag** header is a kind of message digest. Clients can use etags to avoid re-fetching responses for unchanged resources, or to avoid updating a resource that may have changed since the client last updated it.
You must implement this routine to test for matches **if and only if** you return non-Void responses for the etag routine.
Note that if you support multiple representations through content negotiation, then etags are dependent upon
the selected variant. Therefore you will need to have the response entity available for this routine. This can be done in check_resource_exists.
### etag
You are strongly encouraged to return non-Void for this routine. See [Validation Model](http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3) for more details.
Note that if you support multiple representations through content negotiation, then etags are dependent upon
the selected variant. Therefore you will need to have the response entity available for this routine. This can be done in check_resource_exists.
### modified_since
You need to implement this. If you do not have information about when a resource was last modified, then return True as a precaution. Of course, you return false for a static resource.
### treat_as_moved_permanently
This routine when a PUT request is made to a resource that does not exist. See [PUT](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6) in the HTTP/1.1 specification for why you might want to return zero.
### allow_post_to_missing_resource
POST requests are normally made to an existing entity. However it is possible to create new resources using a POST, if the server allows it. This is where you make that decision.
If you return True, and the resource is created, a 201 Created response will be returned.
### content_length
If you are not streaming the result, the the HTTP protocol requires that the length of the entity is known. You need to implement this routine to provide that information.
### finished
If you are streaming the response, then you need to tell the framework when the last chunk has been sent.
To implement this routine, you will probably need to call req.set_execution_variable (some-name, True) in ensure_content_avaiable and generate_next_chunk, and call attached {BOOLEAN} req.execution_variable (some-name) in this routine.
### description
This is for the automatically generated documentation that the framework will generate in response to a request that you have not mapped into an handler.
### delete
This routine is for carrying out a DELETE request to a resource. If it is valid to delete the named resource, then you should either go ahead and do it, or queue a deletion request somewhere (if you do that then you will probably need to call req.set_execution_variable (some-name-or-other, True). Otherwise you should call req.error_handler.add_custom_error to explain why the DELETE could not proceed (you should also do this if the attempt to delete the resource fails).
Of course, if you have not mapped any DELETE requests to the URI space of this handler, then you can just do nothing.
### delete_queued
If in the delete routine, you elected to queue the request, then you need to return True here. You will probably need to check the execution variable you set in the delete routine.
### ensure_content_available
This routine is called for GET and DELETE (when a entity is provided in the response) processing. It's purpose is to make the text of the entity (body of the response) available for future routines (if is_chunking is true, then only the first chunk needs to be made available, although if you only serve, as opposed to generate, the result in chunks, then you will make the entire entity available here). This is necessary so that we can compute the length before we start to serve the response. You would normally save it in an execution variable on the request object (as ORDER_HANDLER does). Note that this usage of execution variables ensures your routines can successfully cope with simultaneous requests. If you encounter a problem generating the content, then add an error to req.error_handler.
As well as the request object, we provide the results of content negotiation, so you can generate the entity in the agreed format. If you only support one format (i.e. all of mime_types_supported, charsets_supported, encodings_supported and languages_supported are one-element lists), then you are guaranteed that this is what you are being asked for, and so you can ignore them.
Note that if you support multiple representations through content negotiation, then etags are dependent upon
the selected variant. Therefore you will need to have the response entity available for this routine. In such cases, this will have to be done in check_resource_exists, rather than here, as this routine is called later on.
### content
When not streaming, this routine provides the entity to the framework (for GET or DELETE). Normally you would just access the execution variable that you set in ensure_content_available. Again, the results of content negotiation are made available, but you probably don't need them at this stage. If you only stream responses (for GET), and if you don't support DELETE, then you don't need to do anything here.
### generate_next_chunk
When streaming the response, this routine is called to enable you to generate chunks beyond the first, so that you can incrementally generate the response entity. If you generated the entire response entity in
ensure_content_available, then you do nothing here. Otherwise, you will generate the next chunk, and save it in the same execution variable that you use in ensure_content_available (or add an error to req.error_handler). If you don't support streaming, then you don't need to do anything here.
### next_chunk
When streaming the response, the framework calls this routine to provides the contents of each generated chunk. If you generated the entire response entity in ensure_content_available, then you need to slice it in this routine (you will have to keep track of where you are with execution variables). If instead you generate the response incrementally, then your task is much easier - you just access the execution variable saved in ensure_content_available/generate_next_chunk.
As in all these content-serving routines, we provide the results of content negotiation. This might be necessary, for instance, if you were compressing an incrementally generated response (it might be more convenient to do the compression here rather than in both ensure_content_available and generate_next_chunk).
### read_entity
This is called for PUT and POST processing, to read the entity provided in the request. A default implementation is provided. This assumes that no decoding (e.g. decompression or character set conversion) is necessary. And it saves it in the execution variable REQUEST_ENTITY.
Currently the framework provides very little support for PUT and POST requests (so you may well need to redefine this routine). There are several reasons for this:
1. I personally don't have much experience with PUT and POST.
1. It has taken a long time to develop this framework, and to some extent I was working in the dark (I couldn't check what I was doing until the entire framework was written - it wouldn't even compile before then).
1. The idea for the framework came from a code review process on servers I had written for the company that I work for. I had acquired a lot of knowledge of the HTTP protocol in the process, and some of it showed in the code that I had written. It was thought that it would be a good idea if this knowledge were encapsulated in Eiffel, so other developers would be able to write servers without such knowledge. So this framework has been developed in company time. However, at present, we are only using GET requests.
Experience with converting the restbucksCRUD example to use the framework, shows that it is certainly possible to do POST and PUT processing with it. But enhancements are needed, especially in the area of decoding the request entity.
### is_entity_too_large
If your application has limits on the size of entities that it can store, then you implement them here.
### check_content_headers
This is called after is_entity_too_large returns False. You are supposed to check the following request headers, and take any appropriate actions (such as setting an error, decompression the entity, or converting it to a different character set):
* Content-Encoding
* Content-Language
* Content-MD5
* Content-Range
* Content-Type
At the moment, your duty is to set the execution variable CONTENT_CHECK_CODE to zero, or an HTTP error status code. A future enhancement of the framework might be to provide more support for this.
### content_check_code
This simply accesses the execution variable CONTENT_CHECK_CODE set in check_content_headers. if you want to use some other mechanism, then you can redefine this routine.
### create_resource
This routine is called when a PUT request is made with a URI that refers to a resource that does not exist (PUT is normally used for updating an existing resource), and you have already decided to allow this.
In this routine you have the responsibilities of:
1. Creating the resource using the entity in REQUEST_ENTITY (or some decoded version that you have stored elsewhere).
1. Writing the entire response yourself (as I said before, support for PUT and POST processing is poor at present), including setting the status code of 201 Created or 303 See Other or 500 Internal server error).
### append_resource
This routine is called for POST requests on an existing resource (normal usage).
In this routine you have the responsibilities of:
1. Storing the entity from REQUEST_ENTITY (or some decoded version that you have stored elsewhere), or whatever other action is appropriate for the semantics of POST requests to this URI.
1. Writing the entire response yourself (as I said before, support for PUT and POST processing is poor at present), including setting the status code of 200 OK, 204 No Content, 303 See Other or 500 Internal server error).
### check_conflict
This is called for a normal (updating) PUT request. You have to check to see if the current state of the resource makes updating impossible. If so, then you need to write the entire response with a status code of 409 Conflict, and set the execution variable CONFLICT_CHECK_CODE to 409.
Otherwise you just set the execution variable CONFLICT_CHECK_CODE to 0.
See [the HTTP/1.1 specification](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.10) for when you are allowed to use the 409 response, and what to write in the response entity. If this is not appropriate then a 500 Internal server error would be more appropriate (and set CONFLICT_CHECK_CODE to 500 - the framework only tests for non-zero).
### conflict_check_code
This is implemented to check CONFLICT_CHECK_CODE from the previous routine. If you choose to use a different mechanism, then you need to redefine this.
### check_request
This is called for PUT and POST requests. You need to check that the request entity (available in the execution variable REQUEST_ENTITY) is valid for the semantics of the request URI. You should set the execution variable REQUEST_CHECK_CODE to 0 if it is OK. If not, set it to 400 and write the full response, including a status code of 400 Bad Request.
### request_check_code
This routine just checks REQUEST_CHECK_CODE. if you choose to use a different mechanism, then redefine it.
### update_resource
This routine is called for a normal (updating) PUT request. You have to update the state of the resource using the entity saved in the execution environment variable REQUEST_ENTITY (or more likely elsewhere - see what ORDER_HANDLER does). Then write the entire response including a status code of 204 No Content or 500 Internal server error.
## Implementing the policies
* [WSF_OPTIONS_POLICY](./WSF_OPTIONS_POLICY)
* [WSF_PREVIOUS_POLICY](./Wsf-previous-policy)
* [WSF_CACHING_POLICY](./Wsf-caching-policy)

View File

@@ -0,0 +1,52 @@
# Implementing WSF_CACHING_POLICY
This class contains a large number of routines, some of which have sensible defaults.
## age
This is used to generate a **Cache-Control: max-age** header. It says how old the response can before a cache will consider it stale (and therefore will need to revalidate with the server). Common values are zero (always consider it stale) and Never_expires (never always mean up to one year) and 1440 (one day).
## shared_age
This defaults to the same as age, so you only have to redefine it if you want a different value. If different from age, then we generate a **Cache-Control: s-max-age** header. This applies to shared caches only. Otherwise it has the same meaning as age. This overrides the value specified in age for shared caches.
## http_1_0_age
This generates an **Expires** header, and has the same meaning as age, but is understood by HTTP/1.0 caches. By default it has the same value as age. You only need to redefine this if you want to treat HTTP/1.0 caches differently (you might not trust them so well, so you might want to return 0 here).
## is_freely_cacheable
This routine says whether a shared cache can use this response for all client. If True, then it generates a **Cache-Control: public** header. If your data is at all sensitive, then you want to return False here.
## is_transformable
Non-transparent proxies are allowed to make some modifications to headers. If your application relies on this _not_ happening, then you want to return False here. This is the default, so you don't have to do anything. This means a **Cache-Control: no-transform** header will be generated.
But most applications can return True.
## must_revalidate
Some clients request that their private cache ignores server expiry times (and so freely reuse stale responses). If you want to force revalidation anyway in such circumstances, then redefine to return True. In which case, we generate a **Cache-Control: must-revalidate** header.
## must_proxy_revalidate
This is the same as must_revalidate, but only applies to shared caches that are configured to serve stale responses. If you redefine to return True, then we generate a **Cache-Control: proxy-revalidate** header.
## private_headers
This is used to indicate that parts (or all) of a response are considered private to a single user, and should not be freely served from a shared cache. You must implement this routine. Your choices are:
1. Return Void. None of the response is considered private.
1. Return and empty list. All of the response is considered private.
1. Return a list of header names.
If you don't return Void, then a **Cache-Control: private** header will be generated.
## non_cacheable_headers
This is similar to private_headers, and you have the same three choices. the difference is that it is a list of headers (or the whole response) that will not be sent from a cache without revalidation.
If you don't return Void, then a **Cache-Control: no-cache** header will be generated.
## is_sensitive
Is the response to be considered of a sensitive nature? If so, then it will not be archived from a cache. We generate a **Cache-Control: no-store** header.

View File

@@ -0,0 +1,19 @@
# WSF_PREVIOUS_POLICY
This class deals with resources that have moved or gone. The default assumes no such resources. It exists as a separate class, rather than have the routines directly in WSF_SKELETON_HANDLER, as sub-classing it may be convenient for an organisation.
## resource_previously_existed
Redefining this routine is always necessary if you want to deal with any previous resources.
## resource_moved_permanently
Redefine this routine for any resources that have permanently changed location. The framework will generate a 301 Moved Permanently response, and the user agent will automatically redirect the request to (one of) the new location(s) you provide. The user agent will use the new URI for future requests.
## resource_moved_temporarily
This is for resource that have only been moved for a short period. The framework will generate a 302 Found response. The only substantial difference between this and resource_moved_permanently, is that the agent will use the old URI for future requests.
## previous_location
When you redefine resource_moved_permanently or resource_moved_temporarily, the framework will generate a Location header for the new URI, and a hypertext document to the new URI(s). You **must** redefine this routine to provide those locations (the first one you provide will be in the location header).

View File

@@ -0,0 +1,3 @@
The current example has a main target for the server: "restbucks"
But we also provide "policy_driven_restbucks" target which is using the
policy-driven framework than help coder fulfill HTTP expectations.

View File

@@ -1,16 +1,11 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="restbucks" uuid="7C9887BD-4AE4-47F2-A0AA-4BBB6736D433" library_target="restbucks">
<target name="restbucks">
<root class="RESTBUCKS_SERVER" feature="make"/>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="restbucks" uuid="2773FEAA-448F-410E-BEDE-9298C4749066" library_target="restbucks">
<target name="restbucks_common">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/\.git$</exclude>
<exclude>/\.svn$</exclude>
</file_rule>
<option debug="true" warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
<debug name="nino" enabled="true"/>
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="connector_nino" location="..\..\library\server\ewsgi\connectors\nino\nino-safe.ecf" readonly="false">
@@ -18,14 +13,41 @@
<debug name="nino" enabled="true"/>
</option>
</library>
<library name="conneg" location="..\..\library\network\protocol\CONNEG\conneg-safe.ecf"/>
<library name="default_nino" location="..\..\library\server\wsf\default\nino-safe.ecf" readonly="false"/>
<library name="eel" location="$ISE_LIBRARY\contrib\library\text\encryption\eel\eel-safe.ecf" readonly="false"/>
<library name="encoder" location="..\..\library\text\encoder\encoder-safe.ecf" readonly="false"/>
<library name="http" location="..\..\library\network\protocol\http\http-safe.ecf" readonly="false"/>
<library name="json" location="..\..\contrib\library\text\parser\json\library\json-safe.ecf" readonly="false"/>
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
<library name="uri_template" location="..\..\library\text\parser\uri_template\uri_template-safe.ecf" readonly="false"/>
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf" readonly="false"/>
<library name="wsf_extension" location="..\..\library\server\wsf\wsf_extension-safe.ecf" readonly="false"/>
<cluster name="src" location="src\" recursive="true"/>
</target>
<target name="restbucks" extends="restbucks_common">
<root class="RESTBUCKS_SERVER" feature="make"/>
<option debug="true" warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
<debug name="nino" enabled="true"/>
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<cluster name="src" location="src\" recursive="true">
<file_rule>
<exclude>/policy_driven_resource$</exclude>
</file_rule>
</cluster>
</target>
<target name="policy_driven_restbucks" extends="restbucks_common">
<root class="RESTBUCKS_SERVER" feature="make"/>
<option debug="true" warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
<debug name="nino" enabled="true"/>
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="wsf_policy_driven" location="..\..\library\server\wsf\wsf_policy_driven-safe.ecf" readonly="false"/>
<cluster name="src" location="src\" recursive="true">
<file_rule>
<exclude>/resource$</exclude>
</file_rule>
</cluster>
</target>
</system>

View File

@@ -0,0 +1,558 @@
note
description: "{ORDER_HANDLER} handle the resources that we want to expose"
author: ""
date: "$Date$"
revision: "$Revision$"
class ORDER_HANDLER
inherit
WSF_SKELETON_HANDLER
SHARED_DATABASE_API
SHARED_EJSON
REFACTORING_HELPER
SHARED_ORDER_VALIDATION
WSF_RESOURCE_HANDLER_HELPER
rename
execute_options as helper_execute_options,
handle_internal_server_error as helper_handle_internal_server_error
end
create
make_with_router
feature -- Execution variables
Order_execution_variable: STRING = "ORDER"
-- Execution variable used by application
Generated_content_execution_variable: STRING = "GENERATED_CONTENT"
-- Execution variable used by application
Extracted_order_execution_variable: STRING = "EXTRACTED_ORDER"
-- Execution variable used by application
feature -- Documentation
description: READABLE_STRING_GENERAL
-- General description for self-generated documentation;
-- The specific URI templates supported will be described automatically
do
Result := "Create, Read, Update or Delete an ORDER."
end
feature -- Access
is_chunking (req: WSF_REQUEST): BOOLEAN
-- Will the response to `req' using chunked transfer encoding?
do
-- No.
end
includes_response_entity (req: WSF_REQUEST): BOOLEAN
-- Does the response to `req' include an entity?
-- Method will be DELETE, POST, PUT or an extension method.
do
Result := False
-- At present, there is no support for this except for DELETE.
end
conneg (req: WSF_REQUEST): CONNEG_SERVER_SIDE
-- Content negotiatior for all requests
once
create Result.make ({HTTP_MIME_TYPES}.application_json, "en", "UTF-8", "identity")
end
mime_types_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept header that `Current' can serve
do
create {ARRAYED_LIST [STRING]} Result.make_from_array (<<{HTTP_MIME_TYPES}.application_json>>)
Result.compare_objects
end
languages_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept-Language header that `Current' can serve
do
create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"en">>)
Result.compare_objects
end
charsets_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept-Charset header that `Current' can serve
do
create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"UTF-8">>)
Result.compare_objects
end
encodings_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept-Encoding header that `Current' can serve
do
create {ARRAYED_LIST [STRING]} Result.make_from_array (<<"identity">>)
Result.compare_objects
end
max_age (req: WSF_REQUEST): NATURAL
-- Maximum age in seconds before response to `req` is considered stale;
-- This is used to generate a Cache-Control: max-age header.
-- Return 0 to indicate already expired.
-- Return Never_expires to indicate never expires.
do
-- All our responses are considered stale.
end
is_freely_cacheable (req: WSF_REQUEST): BOOLEAN
-- Should the response to `req' be freely cachable in shared caches?
-- If `True', then a Cache-Control: public header will be generated.
do
-- definitely not!
end
private_headers (req: WSF_REQUEST): detachable LIST [READABLE_STRING_8]
-- Header names intended for a single user.
-- If non-Void, then a Cache-Control: private header will be generated.
-- Returning an empty list prevents the entire response from being served from a shared cache.
do
create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (0)
end
non_cacheable_headers (req: WSF_REQUEST): detachable LIST [READABLE_STRING_8]
-- Header names that will not be sent from a cache without revalidation;
-- If non-Void, then a Cache-Control: no-cache header will be generated.
-- Returning an empty list prevents the response being served from a cache
-- without revalidation.
do
create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (0)
end
is_sensitive (req: WSF_REQUEST): BOOLEAN
-- Is the response to `req' of a sensitive nature?
-- If `True' then a Cache-Control: no-store header will be generated.
do
Result := True
-- since it's commercial data.
end
matching_etag (req: WSF_REQUEST; a_etag: READABLE_STRING_32; a_strong: BOOLEAN): BOOLEAN
-- Is `a_etag' a match for resource requested in `req'?
-- If `a_strong' then the strong comparison function must be used.
local
l_id: STRING
l_etag_util: ETAG_UTILS
do
l_id := order_id_from_request (req)
if db_access.orders.has_key (l_id) then
check attached db_access.orders.item (l_id) as l_order then
-- postcondition of `has_key'
create l_etag_util
Result := a_etag.same_string (l_etag_util.md5_digest (l_order.out).as_string_32)
end
end
end
etag (req: WSF_REQUEST): detachable READABLE_STRING_8
-- Optional Etag for `req' in the requested variant
local
l_etag_utils: ETAG_UTILS
do
create l_etag_utils
if attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then
Result := l_etag_utils.md5_digest (l_order.out)
end
end
modified_since (req: WSF_REQUEST; a_date_time: DATE_TIME): BOOLEAN
-- Has resource requested in `req' been modified since `a_date_time' (UTC)?
do
-- We don't track this information. It is safe to always say yes.
Result := True
end
feature -- Measurement
content_length (req: WSF_REQUEST): NATURAL
-- Length of entity-body of the response to `req'
do
check attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) as l_response then
-- postcondition generated_content_set_for_get_head of `ensure_content_available'
-- We only call this for GET/HEAD in this example.
Result := l_response.count.as_natural_32
end
end
allow_post_to_missing_resource (req: WSF_REQUEST): BOOLEAN
-- The resource named in `req' does not exist, and this is a POST. Do we allow it?
do
-- No.
end
feature -- Status report
finished (req: WSF_REQUEST): BOOLEAN
-- Has the last chunk been generated for `req'?
do
-- precondition is never met
end
feature -- Execution
check_resource_exists (req: WSF_REQUEST; a_helper: WSF_METHOD_HELPER)
-- Call `a_helper.set_resource_exists' to indicate that `req.path_translated'
-- is the name of an existing resource.
-- We also put the order into `req.execution_variable (Order_execution_variable)' for GET or HEAD responses.
local
l_id: STRING
do
if req.is_post_request_method then
a_helper.set_resource_exists
-- because only /order is defined to this handler for POST
else
-- the request is of the form /order/{orderid}
l_id := order_id_from_request (req)
if db_access.orders.has_key (l_id) then
a_helper.set_resource_exists
if req.is_get_head_request_method then
check attached db_access.orders.item (l_id) as l_order then
-- postcondition `item_if_found' of `has_key'
req.set_execution_variable (Order_execution_variable, l_order)
end
end
end
end
ensure then
order_saved_only_for_get_head: req.is_get_head_request_method =
attached {ORDER} req.execution_variable (Order_execution_variable)
end
feature -- GET/HEAD content
ensure_content_available (req: WSF_REQUEST)
-- Commence generation of response text (entity-body).
-- If not chunked, then this will create the entire entity-body so as to be available
-- for a subsequent call to `content'.
-- If chunked, only the first chunk will be made available to `next_chunk'. If chunk extensions
-- are used, then this will also generate the chunk extension for the first chunk.
-- We save the text in `req.execution_variable (Generated_content_execution_variable)'
-- We ignore the results of content negotiation, as there is only one possible combination.
do
check attached {ORDER} req.execution_variable (Order_execution_variable) as l_order then
-- precondition get_or_head and postcondition order_saved_only_for_get_head of `check_resource_exists' and
if attached {JSON_VALUE} json.value (l_order) as jv then
req.set_execution_variable (Generated_content_execution_variable, jv.representation)
else
req.set_execution_variable (Generated_content_execution_variable, "")
end
end
ensure then
generated_content_set_for_get_head: req.is_get_head_request_method implies
attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable)
end
content (req: WSF_REQUEST): READABLE_STRING_8
-- Non-chunked entity body in response to `req';
-- We only call this for GET/HEAD in this example.
do
check attached {READABLE_STRING_8} req.execution_variable (Generated_content_execution_variable) as l_response then
-- postcondition generated_content_set_for_get_head of `ensure_content_available'
Result := l_response
end
end
next_chunk (req: WSF_REQUEST): TUPLE [a_chunk: READABLE_STRING_8; a_extension: detachable READABLE_STRING_8]
-- Next chunk of entity body in response to `req';
-- The second field of the result is an optional chunk extension.
do
-- precondition `is_chunking' is never met, but we need a dummy `Result'
-- to satisfy the compiler in void-safe mode
Result := ["", Void]
end
generate_next_chunk (req: WSF_REQUEST)
-- Prepare next chunk (including optional chunk extension) of entity body in response to `req'.
-- This is not called for the first chunk.
do
-- precondition `is_chunking' is never met
end
feature -- DELETE
delete (req: WSF_REQUEST)
-- Delete resource named in `req' or set an error on `req.error_handler'.
local
l_id: STRING
do
l_id := order_id_from_request (req)
if db_access.orders.has_key (l_id) then
if is_valid_to_delete (l_id) then
delete_order (l_id)
else
req.error_handler.add_custom_error ({HTTP_STATUS_CODE}.method_not_allowed, "DELETE not valid",
"There is conflict while trying to delete the order, the order could not be deleted in the current state")
end
else
req.error_handler.add_custom_error ({HTTP_STATUS_CODE}.not_found, "DELETE not valid",
"There is no such order to delete")
end
end
delete_queued (req: WSF_REQUEST): BOOLEAN
-- Has resource named by `req' been queued for deletion?
do
-- No
end
feature -- PUT/POST
is_entity_too_large (req: WSF_REQUEST): BOOLEAN
-- Is the entity stored in `req.execution_variable (Request_entity_execution_variable)' too large for the application?
do
-- No. We don't care for this example.
end
check_content_headers (req: WSF_REQUEST)
-- Check we can support all content headers on request entity.
-- Set `req.execution_variable (Content_check_code_execution_variable)' to {NATURAL} zero if OK, or 415 or 501 if not.
do
-- We don't bother for this example. Note that this is equivalent to setting zero.
end
create_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Create new resource in response to a PUT request when `check_resource_exists' returns `False'.
-- Implementor must set error code of 200 OK or 500 Server Error.
do
-- We don't support creating a new resource with PUT. But this can't happen
-- with our router mappings, so we don't bother to set a 500 response.
end
append_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Create new resource in response to a POST request.
-- Implementor must set error code of 200 OK or 204 No Content or 303 See Other or 500 Server Error.
do
if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then
save_order (l_order)
compute_response_post (req, res, l_order)
else
handle_bad_request_response ("Not a valid order", req, res)
end
end
check_conflict (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Check we can support all content headers on request entity.
-- Set `req.execution_variable (Conflict_check_code_execution_variable)' to {NATURAL} zero if OK, or 409 if not.
-- In the latter case, write the full error response to `res'.
do
if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then
if not is_valid_to_update (l_order) then
req.set_execution_variable (Conflict_check_code_execution_variable, {NATURAL} 409)
handle_resource_conflict_response (l_order.out +"%N There is conflict while trying to update the order, the order could not be update in the current state", req, res)
end
else
req.set_execution_variable (Conflict_check_code_execution_variable, {NATURAL} 409)
--| This ought to be a 500, as if attached should probably be check attached. But as yet I lack a proof.
handle_resource_conflict_response ("There is conflict while trying to update the order, the order could not be update in the current state", req, res)
end
end
check_request (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Check that the request entity is a valid request.
-- The entity is available as `req.execution_variable (Conflict_check_code_execution_variable)'.
-- Set `req.execution_variable (Request_check_code_execution_variable)' to {NATURAL} zero if OK, or 400 if not.
-- In the latter case, write the full error response to `res'.
local
l_order: detachable ORDER
l_id: STRING
do
if attached {READABLE_STRING_8} req.execution_variable (Request_entity_execution_variable) as l_request then
l_order := extract_order_request (l_request)
if req.is_put_request_method then
l_id := order_id_from_request (req)
if l_order /= Void and then db_access.orders.has_key (l_id) then
l_order.set_id (l_id)
req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 0)
req.set_execution_variable (Extracted_order_execution_variable, l_order)
else
req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 400)
handle_bad_request_response (l_request +"%N is not a valid ORDER, maybe the order does not exist in the system", req, res)
end
else
req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 0)
req.set_execution_variable (Extracted_order_execution_variable, l_order)
end
else
req.set_execution_variable (Request_check_code_execution_variable, {NATURAL} 400)
handle_bad_request_response ("Request is not a valid ORDER", req, res)
end
end
update_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Perform the update requested in `req'.
-- Write a response to `res' with a code of 204 or 500.
do
if attached {ORDER} req.execution_variable (Extracted_order_execution_variable) as l_order then
update_order (l_order)
compute_response_put (req, res, l_order)
else
handle_internal_server_error (res)
end
end
feature -- HTTP Methods
compute_response_put (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
local
h: HTTP_HEADER
joc : JSON_ORDER_CONVERTER
etag_utils : ETAG_UTILS
do
create h.make
create joc.make
create etag_utils
json.add_converter(joc)
create h.make
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
h.add_header ("etag:" + etag_utils.md5_digest (l_order.out))
if attached {JSON_VALUE} json.value (l_order) as jv then
h.put_content_length (jv.representation.count)
res.set_status_code ({HTTP_STATUS_CODE}.ok)
res.put_header_text (h.string)
res.put_string (jv.representation)
end
end
compute_response_post (req: WSF_REQUEST; res: WSF_RESPONSE; l_order : ORDER)
local
h: HTTP_HEADER
l_msg : STRING
l_location : STRING
joc : JSON_ORDER_CONVERTER
do
create h.make
create joc.make
json.add_converter(joc)
h.put_content_type_application_json
if attached {JSON_VALUE} json.value (l_order) as jv 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.put_location (l_location)
end
if attached req.request_time as time then
h.put_utc_date (time)
end
res.set_status_code ({HTTP_STATUS_CODE}.created)
res.put_header_text (h.string)
res.put_string (l_msg)
end
end
feature {NONE} -- URI helper methods
order_id_from_request (req: WSF_REQUEST): STRING
-- Value of "orderid" template URI variable in `req'
require
req_attached: req /= Void
do
if attached {WSF_VALUE} req.path_parameter ("orderid") as l_value then
Result := l_value.as_string.value.as_string_8
else
Result := ""
end
end
feature {NONE} -- Implementation Repository Layer
retrieve_order ( id : STRING) : detachable ORDER
-- get the order by id if it exist, in other case, Void
do
Result := db_access.orders.item (id)
end
save_order (an_order: ORDER)
-- save the order to the repository
local
i : INTEGER
do
from
i := 1
until
not db_access.orders.has_key ((db_access.orders.count + i).out)
loop
i := i + 1
end
an_order.set_id ((db_access.orders.count + i).out)
an_order.set_status ("submitted")
an_order.add_revision
db_access.orders.force (an_order, an_order.id)
end
is_valid_to_delete ( an_id : STRING) : BOOLEAN
-- Is the order identified by `an_id' in a state whre it can still be deleted?
do
if attached retrieve_order (an_id) as l_order then
if order_validation.is_state_valid_to_update (l_order.status) then
Result := True
end
end
end
is_valid_to_update (an_order: ORDER) : BOOLEAN
-- Check if there is a conflict while trying to update the order
do
if attached retrieve_order (an_order.id) as l_order then
if order_validation.is_state_valid_to_update (l_order.status) and then order_validation.is_valid_status_state (an_order.status) and then
order_validation.is_valid_transition (l_order, an_order.status) then
Result := True
end
end
end
update_order (an_order: ORDER)
-- update the order to the repository
do
an_order.add_revision
db_access.orders.force (an_order, an_order.id)
end
delete_order (an_order: STRING)
-- update the order to the repository
do
db_access.orders.remove (an_order)
end
extract_order_request (l_post : STRING) : detachable ORDER
-- extract an object Order from the request, or Void
-- if the request is invalid
local
parser : JSON_PARSER
joc : JSON_ORDER_CONVERTER
do
create joc.make
json.add_converter(joc)
create parser.make_parser (l_post)
if attached parser.parse as jv and parser.is_parsed then
if attached {like extract_order_request} json.object (jv, "ORDER") as res then
Result := res
end
end
end
note
copyright: "2011-2013, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -27,6 +27,26 @@ inherit
WSF_SELF_DOCUMENTED_HANDLER
create
make_with_router
feature {NONE} -- Initialization
make_with_router (a_router: WSF_ROUTER)
-- Initialize `router'.
require
a_router_attached: a_router /= Void
do
router := a_router
ensure
router_aliased: router = a_router
end
feature -- Router
router: WSF_ROUTER
-- Associated router that could be used for advanced strategy
feature -- Execute
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
@@ -65,16 +85,16 @@ feature -- HTTP Methods
local
id: STRING
do
if attached req.orig_path_info as orig_path then
id := get_order_id_from_path (orig_path)
if attached req.path_info as l_path_info then
id := get_order_id_from_path (l_path_info)
if attached retrieve_order (id) as l_order then
if is_conditional_get (req, l_order) then
handle_resource_not_modified_response ("The resource" + orig_path + "does not change", req, res)
handle_resource_not_modified_response ("The resource" + l_path_info + "does not change", req, res)
else
compute_response_get (req, res, l_order)
end
else
handle_resource_not_found_response ("The following resource" + orig_path + " is not found ", req, res)
handle_resource_not_found_response ("The following resource" + l_path_info + " is not found ", req, res)
end
end
end
@@ -131,8 +151,8 @@ feature -- HTTP Methods
l_order : detachable ORDER
id : STRING
do
if attached req.orig_path_info as orig_path then
id := get_order_id_from_path (orig_path)
if attached req.path_info as l_path_info then
id := get_order_id_from_path (l_path_info)
l_put := retrieve_data (req)
l_order := extract_order_request(l_put)
if l_order /= Void and then db_access.orders.has_key (id) then
@@ -209,18 +229,18 @@ feature -- HTTP Methods
local
id: STRING
do
if attached req.orig_path_info as orig_path then
id := get_order_id_from_path (orig_path)
if attached req.path_info as l_path_info then
id := get_order_id_from_path (l_path_info)
if db_access.orders.has_key (id) then
if is_valid_to_delete (id) then
delete_order( id)
compute_response_delete (req, res)
else
--| FIXME: Here we need to define the Allow methods
handle_method_not_allowed_response (orig_path + "%N There is conflict while trying to delete the order, the order could not be deleted in the current state", req, res)
handle_method_not_allowed_response (l_path_info + "%N There is conflict while trying to delete the order, the order could not be deleted in the current state", req, res)
end
else
handle_resource_not_found_response (orig_path + " not found in this server", req, res)
handle_resource_not_found_response (l_path_info + " not found in this server", req, res)
end
end
end
@@ -374,6 +394,6 @@ feature {NONE} -- Implementation Repository Layer
end
note
copyright: "2011-2012, Javier Velilla and others"
copyright: "2011-2013, Javier Velilla and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -37,7 +37,7 @@ feature {NONE} -- Initialization
order_handler: ORDER_HANDLER
doc: WSF_ROUTER_SELF_DOCUMENTATION_HANDLER
do
create order_handler
create order_handler.make_with_router (router)
router.handle_with_request_methods ("/order", order_handler, router.methods_POST)
router.handle_with_request_methods ("/order/{orderid}", order_handler, router.methods_GET + router.methods_DELETE + router.methods_PUT)
create doc.make_hidden (router)

View File

@@ -350,7 +350,9 @@ feature -- Header change: general
end
add_header_key_value (k,v: READABLE_STRING_8)
-- Add header `k:v', or replace existing header of same header name/key
-- Add header `k:v'.
-- If it already exists, there will be multiple header with same name
-- which can also be valid
local
s: STRING_8
do
@@ -509,6 +511,18 @@ feature -- Content related header
put_header_key_value ({HTTP_HEADER_NAMES}.header_content_transfer_encoding, a_mechanism)
end
put_content_language (a_lang: READABLE_STRING_8)
-- Put "Content-Language" header of value `a_lang'.
do
put_header_key_value ({HTTP_HEADER_NAMES}.header_content_language, a_lang)
end
put_content_encoding (a_enc: READABLE_STRING_8)
-- Put "Content-Encoding" header of value `a_enc'.
do
put_header_key_value ({HTTP_HEADER_NAMES}.header_content_encoding, a_enc)
end
put_transfer_encoding (a_enc: READABLE_STRING_8)
-- Put "Transfer-Encoding" header with for instance "chunked"
do

View File

@@ -54,6 +54,18 @@ feature -- Access
http_if_match: STRING = "HTTP_IF_MATCH"
http_if_modified_since: STRING = "HTTP_IF_MODIFIED_SINCE"
http_if_none_match: STRING = "HTTP_IF_NONE_MATCH"
http_if_range: STRING = "HTTP_IF_RANGE"
http_if_unmodified_since: STRING = "HTTP_IF_UNMODIFIED_SINCE"
http_last_modified: STRING = "HTTP_LAST_MODIFIED"
http_range: STRING = "HTTP_RANGE"
gateway_interface: STRING = "GATEWAY_INTERFACE"
auth_type: STRING = "AUTH_TYPE"

View File

@@ -609,6 +609,36 @@ feature -- HTTP_*
deferred
end
http_if_modified_since: detachable READABLE_STRING_8
-- Modification check on resource
deferred
end
http_if_none_match: detachable READABLE_STRING_8
-- Existence check on resource
deferred
end
http_if_range: detachable READABLE_STRING_8
-- Range check on resource
deferred
end
http_if_unmodified_since: detachable READABLE_STRING_8
-- Modification check on resource
deferred
end
http_last_modified: detachable READABLE_STRING_8
-- Modification time of resource
deferred
end
http_range: detachable READABLE_STRING_8
-- Requested byte-range of resource
deferred
end
feature -- Extra CGI environment variables
request_uri: READABLE_STRING_8

View File

@@ -253,6 +253,42 @@ feature -- Access: HTTP_* CGI meta parameters - 1.1
do
Result := meta_string_variable ({WGI_META_NAMES}.http_if_match)
end
http_if_modified_since: detachable READABLE_STRING_8
-- Modification check on resource
do
Result := meta_string_variable ({WGI_META_NAMES}.http_if_modified_since)
end
http_if_none_match: detachable READABLE_STRING_8
-- Existence check on resource
do
Result := meta_string_variable ({WGI_META_NAMES}.http_if_none_match)
end
http_if_range: detachable READABLE_STRING_8
-- Range check on resource
do
Result := meta_string_variable ({WGI_META_NAMES}.http_if_range)
end
http_if_unmodified_since: detachable READABLE_STRING_8
-- Modification check on resource
do
Result := meta_string_variable ({WGI_META_NAMES}.http_if_unmodified_since)
end
http_last_modified: detachable READABLE_STRING_8
-- Modification time of resource
do
Result := meta_string_variable ({WGI_META_NAMES}.http_last_modified)
end
http_range: detachable READABLE_STRING_8
-- Requested byte-range of resource
do
Result := meta_string_variable ({WGI_META_NAMES}.http_range)
end
feature -- Access: Extension to CGI meta parameters - 1.1

View File

@@ -1,6 +1,6 @@
== libFCGI for Eiffel libFCGI wrapper library ==
# libFCGI for Eiffel libFCGI wrapper library
=== On Windows ===
## On Windows
The Eiffel libFCGI wrapper needs a modified version of libFCGI (provided by http://www.fastcgi.com/devkit/libfcgi/)
@@ -12,10 +12,15 @@ And then to build the needed .dll and .lib file, use either:
build_win32.bat
or build_win64.bat
=== On other platorms ===
## On other platorms
You can use the original version of libfcgi
For instance, on Ubuntu (or any debian):
### Debian based system (Ubuntu, ...)
On Ubuntu (or any Debian based system):
> sudo apt-get install libfcgi-dev
### Mac OS X
On Mac OS X:
> sudo port install fcgi

View File

@@ -1,13 +1,16 @@
== Eiffel libFCGI wrapper library ==
# Eiffel libFCGI wrapper library
This Eiffel library wraps the libFCGI devkit from http://www.fastcgi.com/devkit/libfcgi/
=== Windows ===
## Windows
To compile your own binaries .lib and .dll on __Windows__: [read more](Clib/README.md)
=== Others ===
## Others
==== Debian based system (ubuntu, ...) ====
To install the fcgi lib on debian, you can use
### Debian based system (ubuntu, ...)
To install the fcgi lib on Ubuntu (or any Debian based system), you can use
> sudo apt-get install libfcgi-dev
### Mac OS X
To install the fcgi lib on Mac OS X, you can use [MacPorts](http://www.macports.org/)
> sudo port install fcgi

View File

@@ -0,0 +1,144 @@
note
description: "Wrappers around FastCGI C API."
legal: "See notice at end of class."
status: "See notice at end of class."
date: "$Date$"
revision: "$Revision$"
class
FCGI_C_API
feature -- Connection
accept: INTEGER
-- Accept a Fast CGI connection.
-- Return 0 for successful calls, -1 otherwise.
external
"C inline use %"fcgi_stdio.h%""
alias
"return FCGI_Accept();"
end
environ: POINTER
-- Get the (char**) environ variable from the DLL.
external
"C inline use %"crt_externs.h%""
alias
"return (char*) _NSGetEnviron();"
end
finish
-- Finished current request from HTTP server started from
-- the most recent call to `fcgi_accept'.
external
"C inline use %"fcgi_stdio.h%""
alias
"FCGI_Finish();"
end
set_exit_status (v: INTEGER)
-- Set the exit status for the most recent request
external
"C inline use %"fcgi_stdio.h%""
alias
"FCGI_SetExitStatus($v);"
end
feature -- Input
read_content_into (a_buffer: POINTER; a_length: INTEGER): INTEGER
-- Read content stream into `a_buffer' but no more than `a_length' character.
external
"C inline use %"fcgi_stdio.h%""
alias
"[
{
size_t n;
if (! FCGI_feof(FCGI_stdin)) {
n = FCGI_fread($a_buffer, 1, $a_length, FCGI_stdin);
} else {
n = 0;
}
return n;
}
]"
end
feature {FCGI_IMP} -- Internal
feof (v: POINTER): INTEGER
-- FCGI_feof()
external
"C inline use %"fcgi_stdio.h%""
alias
"FCGI_feof"
end
feature {NONE} -- Input
gets (s: POINTER): POINTER
-- gets() reads a line from stdin into the buffer pointed to
-- by s until either a terminating newline or EOF, which it
-- replaces with '\0'
-- No check for buffer overrun is performed
external
"C inline use %"fcgi_stdio.h%""
alias
"return FCGI_gets($s);"
end
feature -- Output
put_string (v: POINTER; n: INTEGER)
external
"C inline use %"fcgi_stdio.h%""
alias
"FCGI_fwrite($v, 1, $n, FCGI_stdout);"
end
feature -- Error
put_error (v: POINTER; n: INTEGER)
external
"C inline use %"fcgi_stdio.h%""
alias
"FCGI_fwrite($v, 1, $n, FCGI_stderr);"
end
feature -- Access
stdout: POINTER
-- FCGI_stdout() return pointer on output FCGI_FILE
external
"C inline use %"fcgi_stdio.h%""
alias
"FCGI_stdout"
end
stdin: POINTER
-- FCGI_stdin() return pointer on input FCGI_FILE
external
"C inline use %"fcgi_stdio.h%""
alias
"FCGI_stdin"
end
stderr: POINTER
-- FCGI_stderr() return pointer on error FCGI_FILE
external
"C inline use %"fcgi_stdio.h%""
alias
"FCGI_stderr"
end
note
copyright: "Copyright (c) 1984-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

View File

@@ -0,0 +1,179 @@
note
description: "Implementation for the FCGI_I interface"
legal: "See notice at end of class."
status: "See notice at end of class."
date: "$Date$"
revision: "$Revision$"
deferred class FCGI_IMP
inherit
FCGI_I
STRING_HANDLER
feature {NONE} -- Initialization
make
-- Initialize FCGI interface
do
create fcgi
end
fcgi: FCGI_C_API
-- FastCGI C API primitives
feature -- Access
fcgi_environ: POINTER
do
Result := fcgi.environ
end
fcgi_end_of_input: BOOLEAN
do
Result := fcgi.feof (fcgi.stdin) = 0
end
feature -- FCGI Connection
fcgi_listen: INTEGER
-- Listen to the FCGI input stream
-- Return 0 for successful calls, -1 otherwise.
do
Result := {FCGI_C_API}.accept
end
fcgi_finish
-- Finish current request from HTTP server started from
-- the most recent call to `fcgi_accept'.
do
{FCGI_C_API}.finish
end
set_fcgi_exit_status (v: INTEGER)
do
{FCGI_C_API}.set_exit_status (-2)
end
feature -- FCGI output
put_string (a_str: READABLE_STRING_8)
-- Put `a_str' on the FastCGI stdout.
local
l_c_str: C_STRING
do
l_c_str := c_buffer
l_c_str.set_string (a_str)
{FCGI_C_API}.put_string (l_c_str.item, l_c_str.count)
end
feature -- Error
put_error (a_message: READABLE_STRING_8)
-- Put error message `a_message' on the FastCGI stderr
local
l_c_str: C_STRING
do
l_c_str := c_buffer
l_c_str.set_string (a_message)
fcgi.put_error (l_c_str.item, l_c_str.count)
end
feature -- FCGI Input
copy_from_stdin (n: INTEGER; tf: FILE)
-- Read up to n bytes from stdin and write to given file
local
l_c_str: C_STRING
num, readsize, writecount: INTEGER
done: BOOLEAN
do
readsize := n.min (K_input_bufsize)
l_c_str := c_buffer
from
until done or writecount >= n
loop
num := {FCGI_C_API}.read_content_into (l_c_str.item, readsize)
if num = 0 then
-- EOF
done := True
else
tf.put_managed_pointer (c_buffer.managed_data, 0, num)
writecount := writecount + num
end
end
end
feature {NONE} -- Implementation: FCGI Input
fill_pointer_from_stdin (p: POINTER; n: INTEGER): INTEGER
-- Read up to `n' bytes from stdin and store in pointer `p'
-- and return number of bytes read.
do
Result := {FCGI_C_API}.read_content_into (p, n)
end
feature -- I/O Routines
--RFO read_stdin_into (a_str: STRING)
--RFO -- Read a string from the `stdin' into `a_str'.
--RFO require
--RFO a_str_not_void: a_str /= Void
--RFO local
--RFO l_c_str: C_STRING
--RFO n: INTEGER
--RFO do
--RFO l_c_str := c_buffer
--RFO n := {FCGI_C_API}.read_content_into (l_c_str.item, l_c_str.capacity)
--RFO a_str.set_count (n)
--RFO l_c_str.read_substring_into (a_str, 1, n)
--RFO end
--RFO read_string_into (a_str: STRING)
--RFO require
--RFO exists: a_str /= Void
--RFO local
--RFO l_c_str: C_STRING
--RFO p: POINTER
--RFO do
--RFO create l_c_str.make_empty (1024)
--RFO p := {FCGI_C_API}.gets (l_c_str.item)
--RFO-- if p /= default_pointer and p = l_c_str.item then
--RFO a_str.resize (l_c_str.count)
--RFO l_c_str.read_string_into (a_str)
--RFO-- else
--RFO-- put_error_line ("Bad pointer from gets")
--RFO-- end
--RFO end
--RFO read_line
--RFO -- Read up to the next end of line, or end of input
--RFO -- Leave result in last_string
--RFO -- Newline character is absent from result
--RFO do
--RFO if last_string = Void then
--RFO create Result.make (K_input_bufsize)
--RFO else
--RFO last_string.wipe_out
--RFO end
--RFO-- if input_filename /= Void then
--RFO-- io.read_line
--RFO-- last_string.append (io.last_string)
--RFO-- else
--RFO read_string_into (last_string)
--RFO-- end
--RFO end
note
copyright: "Copyright (c) 1984-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

View File

@@ -26,7 +26,12 @@
</external_library>
<external_library location="/usr/lib/libfcgi.so">
<condition>
<platform excluded_value="windows"/>
<platform value="unix"/>
</condition>
</external_library>
<external_library location="/opt/local/lib/libfcgi.dylib">
<condition>
<platform value="macintosh"/>
</condition>
</external_library>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
@@ -34,6 +39,7 @@
<file_rule>
<exclude>/linux$</exclude>
<exclude>/fake$</exclude>
<exclude>/mac$</exclude>
<condition>
<platform value="windows"/>
</condition>
@@ -41,8 +47,17 @@
<file_rule>
<exclude>/fake$</exclude>
<exclude>/windows$</exclude>
<exclude>/mac$</exclude>
<condition>
<platform excluded_value="windows"/>
<platform value="unix"/>
</condition>
</file_rule>
<file_rule>
<exclude>/fake$</exclude>
<exclude>/windows$</exclude>
<exclude>/linux$</exclude>
<condition>
<platform value="macintosh"/>
</condition>
</file_rule>
</cluster>

View File

@@ -27,7 +27,12 @@
</external_library>
<external_library location="/usr/lib/libfcgi.so">
<condition>
<platform excluded_value="windows"/>
<platform value="unix"/>
</condition>
</external_library>
<external_library location="/opt/local/lib/libfcgi.dylib">
<condition>
<platform value="macintosh"/>
</condition>
</external_library>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
@@ -36,6 +41,7 @@
<file_rule>
<exclude>/linux$</exclude>
<exclude>/fake$</exclude>
<exclude>/mac$</exclude>
<condition>
<platform value="windows"/>
</condition>
@@ -43,8 +49,17 @@
<file_rule>
<exclude>/windows$</exclude>
<exclude>/fake$</exclude>
<exclude>/mac$</exclude>
<condition>
<platform excluded_value="windows"/>
<platform value="unix"/>
</condition>
</file_rule>
<file_rule>
<exclude>/fake$</exclude>
<exclude>/windows$</exclude>
<exclude>/linux$</exclude>
<condition>
<platform value="macintosh"/>
</condition>
</file_rule>
</cluster>

View File

@@ -0,0 +1,66 @@
note
description: "[
Policy-driven helpers to implement the DELETE method.
]"
date: "$Date$"
revision: "$Revision$"
class WSF_DELETE_HELPER
inherit
WSF_METHOD_HELPER
feature {NONE} -- Implementation
send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_new_resource: BOOLEAN)
-- Write response to deletion of resource named by `req' into `res'.
-- Upto four execution variables may be set on `req':
-- "NEGOTIATED_MEDIA_TYPE"
-- "NEGOTIATED_LANGUAGE"
-- "NEGOTIATED_CHARSET"
-- "NEGOTIATED_ENCODING"
local
l_dt: STRING
l_ok: BOOLEAN
do
a_handler.delete (req)
l_ok := a_handler.response_ok (req)
if l_ok then
if a_handler.includes_response_entity (req) then
a_handler.ensure_content_available (req)
l_ok := a_handler.response_ok (req)
if l_ok then
a_header.put_content_length (a_handler.content_length (req).as_integer_32)
end
-- we don't bother supporting chunked responses for DELETE.
else
a_header.put_content_length (0)
end
if attached req.request_time as l_time then
l_dt := (create {HTTP_DATE}.make_from_date_time (l_time)).rfc1123_string
a_header.put_header_key_value ({HTTP_HEADER_NAMES}.header_date, l_dt)
generate_cache_headers (req, a_handler, a_header, l_time)
end
if a_handler.delete_queued (req) then
res.set_status_code ({HTTP_STATUS_CODE}.accepted)
res.put_header_text (a_header.string)
res.put_string (a_handler.content (req))
elseif a_handler.deleted (req) then
if a_handler.includes_response_entity (req) then
res.set_status_code ({HTTP_STATUS_CODE}.ok)
res.put_header_text (a_header.string)
res.put_string (a_handler.content (req))
else
res.set_status_code ({HTTP_STATUS_CODE}.no_content)
res.put_header_text (a_header.string)
end
end
end
if not l_ok then
write_error_response (req, res)
end
end
end

View File

@@ -0,0 +1,72 @@
note
description: "[
Policy-driven helpers to implement processing of GET and HEAD requests.
]"
date: "$Date$"
revision: "$Revision$"
class WSF_GET_HELPER
inherit
WSF_METHOD_HELPER
feature {NONE} -- Implementation
send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_new_resource: BOOLEAN)
-- Write response to `req' into `res' in accordance with `a_media_type' etc.
-- Upto four execution variables may be set on `req':
-- "NEGOTIATED_MEDIA_TYPE"
-- "NEGOTIATED_LANGUAGE"
-- "NEGOTIATED_CHARSET"
-- "NEGOTIATED_ENCODING"
local
l_chunked, l_ok: BOOLEAN
l_dt: STRING
do
a_handler.ensure_content_available (req)
l_ok := a_handler.response_ok (req)
if l_ok then
l_chunked := a_handler.is_chunking (req)
if l_chunked then
a_header.put_transfer_encoding_chunked
else
a_header.put_content_length (a_handler.content_length (req).as_integer_32)
end
if attached req.request_time as l_time then
l_dt := (create {HTTP_DATE}.make_from_date_time (l_time)).rfc1123_string
a_header.put_header_key_value ({HTTP_HEADER_NAMES}.header_date, l_dt)
generate_cache_headers (req, a_handler, a_header, l_time)
end
l_ok := a_handler.response_ok (req)
end
if l_ok then
res.set_status_code ({HTTP_STATUS_CODE}.ok)
else
write_error_response (req, res)
end
if attached a_handler.etag (req) as l_etag then
a_header.put_header_key_value ({HTTP_HEADER_NAMES}.header_etag, l_etag)
end
res.put_header_text (a_header.string)
if l_ok then
if l_chunked then
send_chunked_response (req, res, a_handler, a_header)
else
res.put_string (a_handler.content (req))
end
end
end
note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, 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

View File

@@ -0,0 +1,598 @@
note
description: "[
Policy-driven helpers to implement a method.
]"
date: "$Date$"
revision: "$Revision$"
deferred class WSF_METHOD_HELPER
inherit
HTTP_STATUS_CODE_MESSAGES
SHARED_HTML_ENCODER
export {NONE} all end
feature -- Access
resource_exists: BOOLEAN
-- Does the requested resource (request URI) exist?
feature -- Setting
set_resource_exists
-- Set `resource_exists' to `True'.
do
resource_exists := True
ensure
set: resource_exists
end
feature -- Basic operations
execute_new_resource (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
-- Write response to non-existing resource requested by `req.' into `res'.
-- Policy routines are available in `a_handler'.
-- Upto four execution variables may be set on `req':
-- "NEGOTIATED_MEDIA_TYPE"
-- "NEGOTIATED_LANGUAGE"
-- "NEGOTIATED_CHARSET"
-- "NEGOTIATED_ENCODING"
-- An HTTP_HEADER is also available as the execution variable "NEGOTIATED_HTTP_HEADER".
-- It includes the Vary header (if any)
require
req_attached: req /= Void
res_attached: res /= Void
a_handler_attached: a_handler /= Void
local
l_locs: LIST [URI]
do
if a_handler.resource_previously_existed (req) then
if a_handler.resource_moved_permanently (req) then
l_locs := a_handler.previous_location (req)
handle_redirection_error (req, res, l_locs, {HTTP_STATUS_CODE}.moved_permanently)
elseif a_handler.resource_moved_temporarily (req) then
l_locs := a_handler.previous_location (req)
handle_redirection_error (req, res, l_locs, {HTTP_STATUS_CODE}.found)
else
check attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable) as h then
-- postcondition header_attached of `handle_content_negotiation'
h.put_content_type_text_plain
h.put_current_date
h.put_content_length (0)
res.set_status_code ({HTTP_STATUS_CODE}.gone)
res.put_header_lines (h)
end
end
else
check attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable) as h then
-- postcondition header_attached of `handle_content_negotiation'
h.put_content_type_text_plain
h.put_current_date
h.put_content_length (0)
res.set_status_code ({HTTP_STATUS_CODE}.not_found)
res.put_header_lines (h)
end
end
end
execute_existing_resource (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
-- Write response to existing resource requested by `req' into `res'.
-- Policy routines are available in `a_handler'.
-- Upto four execution variables may be set on `req':
-- "NEGOTIATED_MEDIA_TYPE"
-- "NEGOTIATED_LANGUAGE"
-- "NEGOTIATED_CHARSET"
-- "NEGOTIATED_ENCODING"
require
req_attached: req /= Void
res_attached: res /= Void
a_handler_attached: a_handler /= Void
not_if_match_star: attached req.http_if_match as l_if_match implies not l_if_match.same_string ("*")
local
l_etags: LIST [READABLE_STRING_8]
l_failed: BOOLEAN
l_date: HTTP_DATE
do
if attached req.http_if_match as l_if_match then
-- also if-range when we add support for range requests
if not l_if_match.same_string ("*") then
l_etags := l_if_match.split (',')
l_failed := not across l_etags as i_etags some a_handler.matching_etag (req, i_etags.item, True) end
end
end
if l_failed then
handle_precondition_failed (req, res)
else
if attached req.http_if_unmodified_since as l_if_unmodified_since then
if l_if_unmodified_since.is_string_8 then
create l_date.make_from_string (l_if_unmodified_since.as_string_8)
if not l_date.has_error then
if a_handler.modified_since (req, l_date.date_time) then
handle_precondition_failed (req, res)
l_failed := True
end
end
end
end
if not l_failed then
if attached req.http_if_none_match as l_if_none_match then
l_etags := l_if_none_match.split (',')
l_failed := l_if_none_match.same_string ("*") or
across l_etags as i_etags some a_handler.matching_etag (req, i_etags.item, False) end
end
if l_failed then
handle_if_none_match_failed (req, res, a_handler)
else
if attached req.http_if_modified_since as l_if_modified_since then
if l_if_modified_since.is_string_8 then
create l_date.make_from_string (l_if_modified_since.as_string_8)
if not l_date.has_error then
if not a_handler.modified_since (req, l_date.date_time) then
handle_not_modified (req, res, a_handler)
l_failed := True
end
end
end
end
end
if not l_failed then
check attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable) as h then
-- postcondition header_attached of `handle_content_negotiation'
send_response (req, res, a_handler, h, False)
end
end
end
end
end
feature -- Content negotiation
handle_content_negotiation (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
-- Negotiate acceptable content for, then write, response requested by `req' into `res'.
-- Policy routines are available in `a_handler'.
--
-- Either a 406 Not Acceptable error is sent, or upto four execution variables may be set on `req':
-- "NEGOTIATED_MEDIA_TYPE"
-- "NEGOTIATED_LANGUAGE"
-- "NEGOTIATED_CHARSET"
-- "NEGOTIATED_ENCODING"
-- An HTTP_HEADER is also saved as the execution variable "NEGOTIATED_HTTP_HEADER".
-- It includes the Vary header (if any)
require
req_attached: req /= Void
res_attached: res /= Void
a_handler_attached: a_handler /= Void
local
l_conneg: CONNEG_SERVER_SIDE
h: HTTP_HEADER
l_media: MEDIA_TYPE_VARIANT_RESULTS
l_lang: LANGUAGE_VARIANT_RESULTS
l_charset: CHARACTER_ENCODING_VARIANT_RESULTS
l_encoding: COMPRESSION_VARIANT_RESULTS
l_mime_types, l_langs, l_charsets, l_encodings: LIST [STRING]
l_vary_star: BOOLEAN
do
create h.make
l_vary_star := not a_handler.predictable_response (req)
if l_vary_star then
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, "*")
elseif attached a_handler.additional_variant_headers (req) as l_additional then
across l_additional as i_additional loop
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, i_additional.item)
end
end
l_conneg := a_handler.conneg (req)
l_mime_types := a_handler.mime_types_supported (req)
l_media := l_conneg.media_type_preference (l_mime_types, req.http_accept)
if not l_vary_star and l_mime_types.count > 1 and attached l_media.variant_header as l_media_variant then
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, l_media_variant)
end
if not l_media.is_acceptable then
handle_not_acceptable ("None of the requested ContentTypes were acceptable", l_mime_types, req, res)
else
l_langs := a_handler.languages_supported (req)
l_lang := l_conneg.language_preference (l_langs, req.http_accept_language)
if not l_vary_star and l_langs.count > 1 and attached l_lang.variant_header as l_lang_variant then
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, l_lang_variant)
end
if not l_lang.is_acceptable then
handle_not_acceptable ("None of the requested languages were acceptable", l_langs, req, res)
else
if attached l_lang.language_type as l_language_type then
h.put_content_language (l_language_type)
req.set_execution_variable (a_handler.Negotiated_language_execution_variable, l_language_type)
end
l_charsets := a_handler.charsets_supported (req)
l_charset := l_conneg.charset_preference (l_charsets, req.http_accept_charset)
if not l_vary_star and l_charsets.count > 1 and attached l_charset.variant_header as l_charset_variant then
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, l_charset_variant)
end
if not l_charset.is_acceptable then
handle_not_acceptable ("None of the requested character encodings were acceptable", l_charsets, req, res)
else
if attached l_media.media_type as l_media_type then
if attached l_charset.character_type as l_character_type then
h.put_content_type (l_media_type + "; charset=" + l_character_type)
req.set_execution_variable (a_handler.Negotiated_charset_execution_variable, l_charset)
else
h.put_content_type (l_media_type)
end
req.set_execution_variable (a_handler.Negotiated_media_type_execution_variable, l_media_type)
end
l_encodings := a_handler.encodings_supported (req)
l_encoding := l_conneg.encoding_preference (l_encodings, req.http_accept_encoding)
if not l_vary_star and l_encodings.count > 1 and attached l_encoding.variant_header as l_encoding_variant then
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_vary, l_encoding_variant)
end
if not l_encoding.is_acceptable then
handle_not_acceptable ("None of the requested transfer encodings were acceptable", l_encodings, req, res)
else
if attached l_encoding.compression_type as l_compression_type then
h.put_content_encoding (l_compression_type)
req.set_execution_variable (a_handler.Negotiated_encoding_execution_variable, l_compression_type)
end
end
end
end
end
req.set_execution_variable (a_handler.Negotiated_http_header_execution_variable, h)
ensure
header_attached: attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable)
end
feature {NONE} -- Implementation
send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_new_resource: BOOLEAN)
-- Write response to `req' into `res'.
-- Upto four execution variables may be set on `req':
-- "NEGOTIATED_MEDIA_TYPE"
-- "NEGOTIATED_LANGUAGE"
-- "NEGOTIATED_CHARSET"
-- "NEGOTIATED_ENCODING"
require
req_attached: req /= Void
res_attached: res /= Void
a_handler_attached: a_handler /= Void
a_header_attached: a_header /= Void
deferred
end
send_chunked_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER)
-- Write response in chunks to `req'.
-- Upto four execution variables may be set on `req':
-- "NEGOTIATED_MEDIA_TYPE"
-- "NEGOTIATED_LANGUAGE"
-- "NEGOTIATED_CHARSET"
-- "NEGOTIATED_ENCODING"
require
req_attached: req /= Void
res_attached: res /= Void
a_handler_attached: a_handler /= Void
local
l_chunk: TUPLE [a_chunk: READABLE_STRING_8; a_extension: detachable READABLE_STRING_8]
do
from
if a_handler.response_ok (req) then
l_chunk := a_handler.next_chunk (req)
res.put_chunk (l_chunk.a_chunk, l_chunk.a_extension)
else
write_error_response (req, res)
end
until
a_handler.finished (req) or not a_handler.response_ok (req)
loop
a_handler.generate_next_chunk (req)
if a_handler.response_ok (req) then
l_chunk := a_handler.next_chunk (req)
res.put_chunk (l_chunk.a_chunk, l_chunk.a_extension)
else
write_error_response (req, res)
end
end
if a_handler.finished (req) then
-- In future, add support for trailers
res.put_chunk_end
end
end
generate_cache_headers (req: WSF_REQUEST; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_request_time: DATE_TIME)
-- Write headers affecting caching for `req' into `a_header'.
require
req_attached: req /= Void
a_handler_attached: a_handler /= Void
a_header_attached: a_header /= Void
a_request_time_attached: a_request_time /= Void
local
l_age, l_age_1, l_age_2: NATURAL
l_dur: DATE_TIME_DURATION
l_dt, l_field_names: STRING
do
l_age := a_handler.http_1_0_age (req)
create l_dur.make (0, 0, 0, 0, 0, l_age.as_integer_32)
l_dt := (create {HTTP_DATE}.make_from_date_time (a_request_time + l_dur)).rfc1123_string
a_header.put_header_key_value ({HTTP_HEADER_NAMES}.header_expires, l_dt)
l_age_1 := a_handler.max_age (req)
if l_age_1 /= l_age then
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "max-age=" + l_age_1.out)
end
l_age_2 := a_handler.shared_age (req)
if l_age_2 /= l_age_1 then
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "s-maxage=" + l_age_2.out)
end
if a_handler.is_freely_cacheable (req) then
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "public")
elseif attached a_handler.private_headers (req) as l_fields then
l_field_names := "="
if not l_fields.is_empty then
append_field_name (l_field_names, l_fields)
end
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "private" + l_field_names)
end
if attached a_handler.non_cacheable_headers (req) as l_fields then
l_field_names := "="
if not l_fields.is_empty then
append_field_name (l_field_names, l_fields)
end
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "no-cache" + l_field_names)
end
if not a_handler.is_transformable (req) then
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "no-transform")
end
if a_handler.must_revalidate (req) then
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "must-revalidate")
end
if a_handler.must_proxy_revalidate (req) then
a_header.add_header_key_value ({HTTP_HEADER_NAMES}.header_cache_control, "proxy-revalidate")
end
end
append_field_name (a_field_names: STRING; a_fields: LIST [READABLE_STRING_8])
-- Append all of `a_fields' as a comma-separated list to `a_field_names'.
require
a_field_names_attached: a_field_names /= Void
a_fields_attached: a_fields /= Void
do
across a_fields as i_fields loop
a_field_names.append_string (i_fields.item)
if not i_fields.is_last then
a_field_names.append_character (',')
end
end
end
feature -- Error reporting
write_error_response (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Write an error response to `res' using `req.error_handler'.
require
req_attached: req /= Void
res_attached: res /= Void
req_has_error: req.has_error
local
h: HTTP_HEADER
m: READABLE_STRING_8
do
m := req.error_handler.as_string_representation
create h.make
h.put_content_type_text_plain
h.put_content_length (m.count)
res.set_status_code (req.error_handler.primary_error_code)
res.put_header_lines (h)
res.put_string (m)
end
handle_redirection_error (req: WSF_REQUEST; res: WSF_RESPONSE; a_locations: LIST [URI]; a_status_code: INTEGER)
-- Write `a_status_code' error to `res'.
-- Include all of `a_locations' in the headers, and hyperlink to the first one in the body.
require
res_attached: res /= Void
req_attached: req /= Void
a_locations_attached: a_locations /= Void
a_location_not_empty: not a_locations.is_empty
a_status_code_code: is_valid_http_status_code (a_status_code)
local
h: HTTP_HEADER
s: STRING
do
if attached http_status_code_message (a_status_code) as l_msg then
create h.make
across a_locations as i_location loop
h.add_header_key_value ({HTTP_HEADER_NAMES}.header_location, i_location.item.string)
end
if req.is_content_type_accepted ({HTTP_MIME_TYPES}.text_html) then
s := "<html lang=%"en%"><head>"
s.append ("<title>")
s.append (html_encoder.encoded_string (req.request_uri))
s.append ("Error " + a_status_code.out + " (" + l_msg + ")")
s.append ("</title>%N")
s.append ("[
<style type="text/css">
div#header {color: #fff; background-color: #000; padding: 20px; text-align: center; font-size: 2em; font-weight: bold;}
div#message { margin: 40px; text-align: center; font-size: 1.5em; }
div#suggestions { margin: auto; width: 60%;}
div#suggestions ul { }
div#footer {color: #999; background-color: #eee; padding: 10px; text-align: center; }
div#logo { float: right; margin: 20px; width: 60px height: auto; font-size: 0.8em; text-align: center; }
div#logo div.outer { padding: 6px; width: 60px; border: solid 3px #500; background-color: #b00;}
div#logo div.outer div.inner1 { display: block; margin: 10px 15px; width: 30px; height: 50px; color: #fff; background-color: #fff; border: solid 2px #900; }
div#logo div.outer div.inner2 { margin: 10px 15px; width: 30px; height: 15px; color: #fff; background-color: #fff; border: solid 2px #900; }
</style>
</head>
<body>
]")
s.append ("<div id=%"header%">Error " + a_status_code.out + " (" + l_msg + ")</div>")
s.append ("<div id=%"logo%">")
s.append ("<div class=%"outer%"> ")
s.append ("<div class=%"inner1%"></div>")
s.append ("<div class=%"inner2%"></div>")
s.append ("</div>")
s.append ("The current location for this resource is <a href=%"" + a_locations.first.string + "%">here</a>")
s.append ("Error " + a_status_code.out + " (" + l_msg + ")</div>")
s.append ("<div id=%"message%">Error " + a_status_code.out + " (" + l_msg + "): <code>" + html_encoder.encoded_string (req.request_uri) + "</code></div>")
s.append ("<div id=%"footer%"></div>")
s.append ("</body>%N")
s.append ("</html>%N")
h.put_content_type_text_html
else
s := "Error " + a_status_code.out + " (" + l_msg + "): "
s.append (req.request_uri)
s.append_character ('%N')
s.append ("The current location for this resource is " + a_locations.first.string)
h.put_content_type_text_plain
end
h.put_content_length (s.count)
res.put_header_lines (h)
res.put_string (s)
res.flush
end
end
handle_not_acceptable (a_message: READABLE_STRING_8; a_supported: LIST [STRING]; req: WSF_REQUEST; res: WSF_RESPONSE)
-- Write a Not Acceptable response to `res'.
require
req_attached: req /= Void
res_attached: res /= Void
a_message_attached: a_message /= Void
a_supported_attached: a_supported /= Void
local
h: HTTP_HEADER
s: STRING
do
create h.make
h.put_content_type_text_plain
h.put_current_date
s := a_message
s.append_character ('%N')
s.append_character ('%N')
s.append_string ("We accept the following:%N%N")
across a_supported as i_supported loop
s.append_string (i_supported.item)
s.append_character ('%N')
end
h.put_content_length (s.count)
res.set_status_code ({HTTP_STATUS_CODE}.not_acceptable)
res.put_header_lines (h)
res.put_string (s)
res.flush
end
handle_if_none_match_failed (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
-- Write a Not Modified response to `res'.
require
req_attached: req /= Void
res_attached: res /= Void
a_handler_attached: a_handler /= Void
do
handle_not_modified (req, res, a_handler)
end
handle_not_modified (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
-- Write a Not Modified response to `res'.
-- Upto four execution variables may be set on `req':
-- "NEGOTIATED_MEDIA_TYPE"
-- "NEGOTIATED_LANGUAGE"
-- "NEGOTIATED_CHARSET"
-- "NEGOTIATED_ENCODING"
require
req_attached: req /= Void
res_attached: res /= Void
a_handler_attached: a_handler /= Void
local
h: HTTP_HEADER
do
create h.make
h.put_content_type_text_plain
h.put_content_length (0)
if attached a_handler.etag (req) as l_etag then
h.put_header_key_value ({HTTP_HEADER_NAMES}.header_etag, l_etag)
end
generate_cache_headers (req, a_handler, h, create {DATE_TIME}.make_now_utc)
res.set_status_code ({HTTP_STATUS_CODE}.not_modified)
res.put_header_lines (h)
end
handle_precondition_failed (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Write a Precondition Failed response for `req' to `res'.
require
req_attached: req /= Void
res_attached: res /= Void
local
h: HTTP_HEADER
do
create h.make
h.put_content_type_text_plain
h.put_current_date
h.put_content_length (0)
res.set_status_code ({HTTP_STATUS_CODE}.precondition_failed)
res.put_header_lines (h)
end
handle_unsupported_media_type (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Write a Unsupported Media Type response for `req' to `res'.
require
req_attached: req /= Void
res_attached: res /= Void
local
h: HTTP_HEADER
do
create h.make
h.put_content_type_text_plain
h.put_current_date
h.put_content_length (0)
res.set_status_code ({HTTP_STATUS_CODE}.unsupported_media_type)
res.put_header_lines (h)
end
handle_not_implemented (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Write a Not Implemented response for `req' to `res'.
require
req_attached: req /= Void
res_attached: res /= Void
local
h: HTTP_HEADER
do
create h.make
h.put_content_type_text_plain
h.put_current_date
h.put_content_length (0)
res.set_status_code ({HTTP_STATUS_CODE}.not_implemented)
res.put_header_lines (h)
end
handle_request_entity_too_large (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
-- Write a Request Entity Too Large response for `req' to `res'.
require
req_attached: req /= Void
res_attached: res /= Void
a_handler_attached: a_handler /= Void
local
h: HTTP_HEADER
do
create h.make
h.put_content_type_text_plain
h.put_current_date
h.put_content_length (0)
res.set_status_code ({HTTP_STATUS_CODE}.request_entity_too_large)
res.put_header_lines (h)
-- FIXME: Need to check if condition is temporary. This needs a new query
-- on the handler. For now we can claim compliance by saying the condition
-- is always permenent :-) - author's might not like this though.
end
note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, 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

View File

@@ -0,0 +1,42 @@
note
description: "[
Default factory for policy-driven method helpers.
Extension methods can be implemented here.
]"
date: "$Date$"
revision: "$Revision$"
class WSF_METHOD_HELPER_FACTORY
feature -- Factory
new_method_helper (a_method: READABLE_STRING_8): detachable WSF_METHOD_HELPER
-- New object for processing `a_method';
-- Redefine this routine to implement extension methods.
require
a_method_attached: a_method /= Void
do
if a_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_get) or
a_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_head) then
create {WSF_GET_HELPER} Result
elseif a_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_put) then
create {WSF_PUT_HELPER} Result
elseif a_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_post) then
create {WSF_POST_HELPER} Result
elseif a_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_delete) then
create {WSF_DELETE_HELPER} Result
end
end
note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, 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

View File

@@ -0,0 +1,80 @@
note
description: "[
Policy-driven helpers to implement POST.
]"
date: "$Date$"
revision: "$Revision$"
class WSF_POST_HELPER
inherit
WSF_METHOD_HELPER
redefine
execute_new_resource
end
feature -- Basic operations
execute_new_resource (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
-- Write response to non-existing resource requested by `req.' into `res'.
-- Policy routines are available in `a_handler'.
do
if a_handler.allow_post_to_missing_resource (req) then
check attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable) as h then
-- postcondition header_attached of `handle_content_negotiation'
send_response (req, res, a_handler, h, True)
end
else
res.send (create {WSF_NOT_FOUND_RESPONSE}.make(req))
end
end
feature {NONE} -- Implementation
send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_new_resource: BOOLEAN)
-- Write response to `req' into `res' in accordance with `a_media_type' etc. as a new URI.
-- Upto four execution variables may be set on `req':
-- "NEGOTIATED_MEDIA_TYPE"
-- "NEGOTIATED_LANGUAGE"
-- "NEGOTIATED_CHARSET"
-- "NEGOTIATED_ENCODING"
local
l_code: NATURAL
do
a_handler.read_entity (req)
if a_handler.is_entity_too_large (req) then
handle_request_entity_too_large (req, res, a_handler)
else
a_handler.check_content_headers (req)
l_code := a_handler.content_check_code (req)
if l_code /= 0 then
if l_code = 415 then
handle_unsupported_media_type (req, res)
else
handle_not_implemented (req, res)
end
else
a_handler.check_request (req, res)
if a_handler.request_check_code (req) = 0 then
a_handler.append_resource (req, res)
-- 200 or 204 or 303 or 500 (add support for this?)
-- FIXME: more support, such as includes_response_entity
end
end
end
end
note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, 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

View File

@@ -0,0 +1,82 @@
note
description: "[
Policy-driven helpers to implement PUT.
]"
date: "$Date$"
revision: "$Revision$"
class WSF_PUT_HELPER
inherit
WSF_METHOD_HELPER
redefine
execute_new_resource
end
feature -- Basic operations
execute_new_resource (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER)
-- Write response to non-existing resource requested by `req.' into `res'.
-- Policy routines are available in `a_handler'.
do
if a_handler.treat_as_moved_permanently (req) then
handle_redirection_error (req, res, a_handler.previous_location (req), {HTTP_STATUS_CODE}.moved_permanently)
else
check attached {HTTP_HEADER} req.execution_variable (a_handler.Negotiated_http_header_execution_variable) as h then
-- postcondition header_attached of `handle_content_negotiation'
send_response (req, res, a_handler, h, True)
end
end
end
feature {NONE} -- Implementation
send_response (req: WSF_REQUEST; res: WSF_RESPONSE; a_handler: WSF_SKELETON_HANDLER; a_header: HTTP_HEADER; a_new_resource: BOOLEAN)
-- Write response to `req' into `res' in accordance with `a_media_type' etc. as a new URI.
local
l_code: NATURAL
do
a_handler.read_entity (req)
if a_handler.is_entity_too_large (req) then
handle_request_entity_too_large (req, res, a_handler)
else
a_handler.check_content_headers (req)
l_code := a_handler.content_check_code (req)
if l_code /= 0 then
if l_code = 415 then
handle_unsupported_media_type (req, res)
else
handle_not_implemented (req, res)
end
else
a_handler.check_request (req, res)
if a_handler.request_check_code (req) = 0 then
if a_new_resource then
a_handler.create_resource (req, res)
-- 201 or 500 (add support for this?)
else
a_handler.check_conflict (req, res)
if a_handler.conflict_check_code (req) = 0 then
a_handler.update_resource (req, res)
-- 204 or 500 (add support for this?)
-- FIXME: more support, such as includes_response_entity
end
end
end
end
end
end
note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, 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

View File

@@ -0,0 +1,550 @@
note
description: "[
Policy-driven handlers.
Implementers only need to concentrate on creating content.
]"
date: "$Date$"
revision: "$Revision$"
deferred class WSF_SKELETON_HANDLER
inherit
WSF_URI_TEMPLATE_HANDLER
redefine
execute
end
WSF_OPTIONS_POLICY
WSF_PREVIOUS_POLICY
WSF_CACHING_POLICY
WSF_METHOD_HELPER_FACTORY
WSF_SELF_DOCUMENTED_HANDLER
feature {NONE} -- Initialization
make_with_router (a_router: WSF_ROUTER)
-- Initialize `router'.
require
a_router_attached: a_router /= Void
do
router := a_router
ensure
router_aliased: router = a_router
end
feature -- Router
router: WSF_ROUTER
-- So that WSF_OPTIONS_POLICY can find the allowed methods
feature -- Execution variables
Negotiated_language_execution_variable: STRING = "NEGOTIATED_LANGUAGE"
-- Execution variable set by framework
Negotiated_charset_execution_variable: STRING = "NEGOTIATED_CHARSET"
-- Execution variable set by framework
Negotiated_media_type_execution_variable: STRING = "NEGOTIATED_MEDIA_TYPE"
-- Execution variable set by framework
Negotiated_encoding_execution_variable: STRING = "NEGOTIATED_ENCODING"
-- Execution variable set by framework
Negotiated_http_header_execution_variable: STRING = "NEGOTIATED_HTTP_HEADER"
-- Execution variable set by framework
Request_entity_execution_variable: STRING = "REQUEST_ENTITY"
-- Execution variable set by framework
Conflict_check_code_execution_variable: STRING = "CONFLICT_CHECK_CODE"
-- Execution variable set by framework
Content_check_code_execution_variable: STRING = "CONTENT_CHECK_CODE"
-- Execution variable set by framework
Request_check_code_execution_variable: STRING = "REQUEST_CHECK_CODE"
-- Execution variable set by framework
feature -- Access
is_chunking (req: WSF_REQUEST): BOOLEAN
-- Will the response to `req' using chunked transfer encoding?
require
req_attached: req /= Void
deferred
end
includes_response_entity (req: WSF_REQUEST): BOOLEAN
-- Does the response to `req' include an entity?
-- Method will be DELETE, OUT, POST or an extension method.
require
req_attached: req /= Void
deferred
end
conneg (req: WSF_REQUEST): CONNEG_SERVER_SIDE
-- Content negotiation for `req';
-- This would normally be a once object, ignoring `req'.
require
req_attached: req /= Void
deferred
end
mime_types_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept header that `Current' can serve
require
req_attached: req /= Void
deferred
ensure
mime_types_supported_includes_default: Result.has (conneg (req).mime_default)
end
languages_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept-Language header that `Current' can serve
require
req_attached: req /= Void
deferred
ensure
languages_supported_includes_default: Result.has (conneg (req).language_default)
end
charsets_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept-Charset header that `Current' can serve
require
req_attached: req /= Void
deferred
ensure
charsets_supported_includes_default: Result.has (conneg (req).charset_default)
end
encodings_supported (req: WSF_REQUEST): LIST [STRING]
-- All values for Accept-Encoding header that `Current' can serve
require
req_attached: req /= Void
deferred
ensure
encodings_supported_includes_default: Result.has (conneg (req).encoding_default)
end
additional_variant_headers (req: WSF_REQUEST): detachable LIST [STRING]
-- Header other than Accept, Accept-Language, Accept-Charset and Accept-Encoding,
-- which might affect the response
do
end
predictable_response (req: WSF_REQUEST): BOOLEAN
-- Does the response to `req' vary only on the dimensions of ContentType, Language, Charset and Transfer encoding,
-- plus those named in `additional_variant_headers'?
do
Result := True
-- redefine to return `False', so as to induce a Vary: * header
end
matching_etag (req: WSF_REQUEST; a_etag: READABLE_STRING_32; a_strong: BOOLEAN): BOOLEAN
-- Is `a_etag' a match for resource requested in `req'?
-- If `a_strong' then the strong comparison function must be used.
require
req_attached: req /= Void
deferred
end
etag (req: WSF_REQUEST): detachable READABLE_STRING_8
-- Optional Etag for response entity to `req';
-- Upto four execution variables may be set on `req':
-- "NEGOTIATED_MEDIA_TYPE"
-- "NEGOTIATED_LANGUAGE"
-- "NEGOTIATED_CHARSET"
-- "NEGOTIATED_ENCODING"
require
req_attached: req /= Void
deferred
end
modified_since (req: WSF_REQUEST; a_date_time: DATE_TIME): BOOLEAN
-- Has resource requested in `req' been modified since `a_date_time' (UTC)?
require
req_attached: req /= Void
deferred
end
treat_as_moved_permanently (req: WSF_REQUEST): BOOLEAN
-- Rather than store as a new entity, do we treat it as an existing entity that has been moved?
require
req_attached: req /= Void
put_request: req.is_request_method ({HTTP_REQUEST_METHODS}.method_put)
do
-- No. Redefine this if needed.
end
allow_post_to_missing_resource (req: WSF_REQUEST): BOOLEAN
-- The resource named in `req' does not exist, and this is a POST. Do we allow it?
require
req_attached: req /= Void
deferred
end
feature -- Measurement
content_length (req: WSF_REQUEST): NATURAL
-- Length of entity-body of the response to `req'
require
req_attached: req /= Void
not_chunked: not is_chunking (req)
deferred
end
feature -- Status report
finished (req: WSF_REQUEST): BOOLEAN
-- Has the last chunk been generated for `req'?
require
req_attached: req /= Void
chunked: is_chunking (req)
deferred
end
feature -- Documentation
mapping_documentation (m: WSF_ROUTER_MAPPING; a_request_methods: detachable WSF_REQUEST_METHODS): WSF_ROUTER_MAPPING_DOCUMENTATION
-- Documentation associated with Current handler, in the context of the mapping `m' and methods `a_request_methods'
--| `m' and `a_request_methods' are useful to produce specific documentation when the handler is used for multiple mapping.
do
create Result.make (m)
Result.add_description (description)
end
description: READABLE_STRING_GENERAL
-- General description for self-generated documentation;
-- The specific URI templates supported will be described automatically
deferred
ensure
description_attached: Result /= Void
end
feature -- DELETE
delete (req: WSF_REQUEST)
-- Delete resource named in `req' or set an error on `req.error_handler'.
require
req_attached: req /= Void
deferred
ensure
error_or_queued_or_deleted: not req.error_handler.has_error implies (delete_queued (req) or deleted (req))
end
deleted (req: WSF_REQUEST): BOOLEAN
-- Has resource named by `req' been deleted?
require
req_attached: req /= Void
do
if not req.error_handler.has_error then
Result := True
end
ensure
negative_implication: not Result implies req.error_handler.has_error
end
delete_queued (req: WSF_REQUEST): BOOLEAN
-- Has resource named by `req' been queued for deletion?
require
req_attached: req /= Void
deferred
ensure
entity_available: includes_response_entity (req)
end
feature -- GET/HEAD content
ensure_content_available (req: WSF_REQUEST)
-- Commence generation of response text (entity-body) (if not already done in `check_resource_exists').
-- If not chunked, then this will create the entire entity-body so as to be available
-- for a subsequent call to `content'.
-- If chunked, only the first chunk will be made available to `next_chunk'. If chunk extensions
-- are used, then this will also generate the chunk extension for the first chunk.
-- Upto four execution variables may be set on `req':
-- "NEGOTIATED_MEDIA_TYPE"
-- "NEGOTIATED_LANGUAGE"
-- "NEGOTIATED_CHARSET"
-- "NEGOTIATED_ENCODING"
-- If you support etags, and you have more than one possible representation
-- for the resource (so that your etag depends upon the particular representation),
-- then you will probably have already created the response entity in `check_resource_exists'.
require
req_attached: req /= Void
get_or_head_or_delete: req.is_get_head_request_method or req.is_delete_request_method
deferred
end
response_ok (req: WSF_REQUEST): BOOLEAN
-- Has generation of the response (so-far, if chunked) proceeded witout error?
require
req_attached: req /= Void
do
Result := not req.error_handler.has_error
ensure
last_error_set: Result = not req.error_handler.has_error
end
content (req: WSF_REQUEST): READABLE_STRING_8
-- Non-chunked entity body in response to `req';
-- Upto four execution variables may be set on `req':
-- "NEGOTIATED_MEDIA_TYPE"
-- "NEGOTIATED_LANGUAGE"
-- "NEGOTIATED_CHARSET"
-- "NEGOTIATED_ENCODING"
require
req_attached: req /= Void
head_get_or_delete: req.is_get_head_request_method or req.is_delete_request_method
no_error: response_ok (req)
not_chunked: not is_chunking (req)
deferred
end
generate_next_chunk (req: WSF_REQUEST)
-- Prepare next chunk (including optional chunk extension) of entity body in response to `req'.
-- This is not called for the first chunk.
-- Upto four execution variables may be set on `req':
-- "NEGOTIATED_MEDIA_TYPE"
-- "NEGOTIATED_LANGUAGE"
-- "NEGOTIATED_CHARSET"
-- "NEGOTIATED_ENCODING"
require
req_attached: req /= Void
no_error: response_ok (req)
chunked: is_chunking (req)
deferred
end
next_chunk (req: WSF_REQUEST): TUPLE [a_check: READABLE_STRING_8; a_extension: detachable READABLE_STRING_8]
-- Next chunk of entity body in response to `req';
-- The second field of the result is an optional chunk extension.
-- Four execution variables are set on `req':
-- "NEGOTIATED_MEDIA_TYPE"
-- "NEGOTIATED_LANGUAGE"
-- "NEGOTIATED_CHARSET"
-- "NEGOTIATED_ENCODING"
require
req_attached: req /= Void
no_error: response_ok (req)
chunked: is_chunking (req)
deferred
end
feature -- PUT/POST
read_entity (req: WSF_REQUEST)
-- Read request body and set as `req.execution_variable (Request_entity_execution_variable)'.
require
req_attached: req /= Void
local
l_body: STRING
do
create l_body.make_empty
req.read_input_data_into (l_body)
if not l_body.is_empty then
req.set_execution_variable (Request_entity_execution_variable, l_body)
end
end
is_entity_too_large (req: WSF_REQUEST): BOOLEAN
-- Is the entity stored in `req.execution_variable (Request_entity_execution_variable)' too large for the application?
require
req_attached: req /= Void
deferred
end
check_content_headers (req: WSF_REQUEST)
-- Check we can support all content headers on request entity.
-- Set `req.execution_variable (Content_check_code_execution_variable)' to {NATURAL} zero if OK, or 415 or 501 if not.
require
req_attached: req /= Void
deferred
end
content_check_code (req: WSF_REQUEST): NATURAL
-- Code set by `check_content_headers'.
require
req_attached: req /= Void
do
if attached {NATURAL} req.execution_variable (Content_check_code_execution_variable) as l_code then
Result := l_code
end
end
create_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Create new resource in response to a PUT request when `check_resource_exists' returns `False'.
-- Implementor must set error code of 200 OK or 500 Server Error.
require
req_attached: req /= Void
res_attached: res /= Void
put_request: req.is_put_request_method
deferred
end
append_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Create new resource in response to a POST request.
-- Implementor must set error code of 200 OK or 204 No Content or 303 See Other or 500 Server Error.
require
req_attached: req /= Void
res_attached: res /= Void
post_request: req.is_post_request_method
deferred
end
check_conflict (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Check to see if updating the resource is problematic due to the current state of the resource.
-- Set `req.execution_variable (Conflict_check_code_execution_variable)' to {NATURAL} zero if OK, or 409 if not.
-- In the latter case, write the full error response to `res'.
require
req_attached: req /= Void
res_attached: res /= Void
deferred
end
conflict_check_code (req: WSF_REQUEST): NATURAL
-- Code set by `check_conflict'.
require
req_attached: req /= Void
do
if attached {NATURAL} req.execution_variable (Conflict_check_code_execution_variable) as l_code then
Result := l_code
end
end
check_request (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Check that the request entity is a valid request.
-- The entity is available as `req.execution_variable (Conflict_check_code_execution_variable)'.
-- Set `req.execution_variable (Request_check_code_execution_variable)' to {NATURAL} zero if OK, or 400 if not.
-- In the latter case, write the full error response to `res'.
require
req_attached: req /= Void
res_attached: res /= Void
put_or_post: req.is_put_post_request_method
deferred
end
request_check_code (req: WSF_REQUEST): NATURAL
-- Code set by `check_request'.
require
req_attached: req /= Void
do
if attached {NATURAL} req.execution_variable (Request_check_code_execution_variable) as l_code then
Result := l_code
end
end
update_resource (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Perform the update requested in `req'.
-- Write a response to `res' with a code of 204 or 500.
require
req_attached: req /= Void
res_attached: res /= Void
put_request: req.is_put_request_method
deferred
end
feature -- Execution
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- <Precursor>
do
check
known_method: router.allowed_methods_for_request (req).has (req.request_method)
not_trace: not req.is_request_method ({HTTP_REQUEST_METHODS}.method_trace)
not_connect: not req.is_request_method ({HTTP_REQUEST_METHODS}.method_connect)
end
if req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) then
execute_options (req, res, router)
else
if attached new_method_helper (req.request_method) as l_helper then
execute_method (req, res, l_helper)
else
handle_internal_server_error (res)
end
end
end
execute_method (req: WSF_REQUEST; res: WSF_RESPONSE; a_helper: WSF_METHOD_HELPER)
-- Write response to `req' into `res', using `a_helper' as a logic helper.
require
req_attached: req /= Void
res_attached: res /= Void
a_helper_attached: a_helper /= Void
do
a_helper.handle_content_negotiation (req, res, Current)
if not res.status_is_set or else res.status_code /= {HTTP_STATUS_CODE}.Not_acceptable then
check_resource_exists (req, a_helper)
if a_helper.resource_exists then
a_helper.execute_existing_resource (req, res, Current)
else
if attached req.http_if_match as l_if_match and then l_if_match.same_string ("*") then
a_helper.handle_precondition_failed (req, res)
else
a_helper.execute_new_resource (req, res, Current)
end
end
end
end
check_resource_exists (req: WSF_REQUEST; a_helper: WSF_METHOD_HELPER)
-- Call `a_helper.set_resource_exists' to indicate that `req.path_translated'
-- is the name of an existing resource.
-- Upto four execution variables may be set on `req':
-- "NEGOTIATED_MEDIA_TYPE"
-- "NEGOTIATED_LANGUAGE"
-- "NEGOTIATED_CHARSET"
-- "NEGOTIATED_ENCODING"
-- If you support etags, and you have more than one possible representation
-- for the resource (so that your etag depends upon the particular representation),
-- then you will probably need to create the response entity at this point, rather
-- than in `ensure_content_available'.
require
req_attached: req /= Void
a_helper_attached: a_helper /= Void
deferred
end
feature {NONE} -- Implementation
handle_internal_server_error (res: WSF_RESPONSE)
-- Write "Internal Server Error" response to `res'.
require
res_attached: res /= Void
local
h: HTTP_HEADER
m: STRING_8
do
create h.make
h.put_content_type_text_plain
m := "Server failed to handle request properly"
h.put_content_length (m.count)
h.put_current_date
res.set_status_code ({HTTP_STATUS_CODE}.internal_server_error)
res.put_header_lines (h)
res.put_string (m)
ensure
response_status_is_set: res.status_is_set
status_is_service_unavailable: res.status_code = {HTTP_STATUS_CODE}.internal_server_error
body_sent: res.message_committed and then res.transfered_content_length > 0
end
note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, 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

View File

@@ -30,14 +30,14 @@ feature -- Execution
handle_unavailable (res)
elseif requires_proxy (req) then
handle_use_proxy (req, res)
elseif
maximum_uri_length > 0 and then
req.request_uri.count.to_natural_32 > maximum_uri_length
elseif
maximum_uri_length > 0 and then
req.request_uri.count.to_natural_32 > maximum_uri_length
then
handle_request_uri_too_long (res)
elseif
req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) and then
req.request_uri.same_string ("*")
elseif
req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) and then
req.request_uri.same_string ("*")
then
handle_server_options (req, res)
else

View File

@@ -0,0 +1,137 @@
note
description: "[
Policies for determing caching of responses.
]"
date: "$Date$"
revision: "$Revision$"
deferred class WSF_CACHING_POLICY
feature -- Access
Never_expires: NATURAL = 525600
-- 525600 = 365 * 24 * 60 * 60 = (almost) 1 year;
-- See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21 for an explanation of why this means never expire
max_age (req: WSF_REQUEST): NATURAL
-- Maximum age in seconds before response to `req` is considered stale;
-- This is used to generate a Cache-Control: max-age header.
-- Return 0 to indicate already expired.
-- Return `Never_expires' to indicate never expires.
require
req_attached: req /= Void
deferred
ensure
not_more_than_1_year: Result <= Never_expires
end
shared_age (req: WSF_REQUEST): NATURAL
-- Maximum age in seconds before response to `req` is considered stale in a shared cache;
-- This is used to generate a Cache-Control: s-maxage header.
-- If you wish to have different expiry ages for shared and provate caches, redefine this routine.
-- Return 0 to indicate already expired.
-- Return `Never_expires' to indicate never expires.
require
req_attached: req /= Void
do
Result := max_age (req)
ensure
not_more_than_1_year: Result <= Never_expires
end
http_1_0_age (req: WSF_REQUEST): NATURAL
-- Maximum age in seconds before response to `req` is considered stale;
-- This is used to generate an Expires header, which HTTP/1.0 caches understand.
-- If you wish to generate a different age for HTTP/1.0 caches, then redefine this routine.
-- Return 0 to indicate already expired.
-- Return `Never_expires' to indicate never expires. Note this will
-- make a result cachecable that would not normally be cacheable (such as as response
-- to a POST), unless overriden by cache-control headers, so be sure to check `req.request_method'.
require
req_attached: req /= Void
do
Result := max_age (req)
ensure
not_more_than_1_year: Result <= Never_expires
end
is_freely_cacheable (req: WSF_REQUEST): BOOLEAN
-- Should the response to `req' be freely cachable in shared caches?
-- If `True', then a Cache-Control: public header will be generated.
require
req_attached: req /= Void
deferred
end
is_transformable (req: WSF_REQUEST): BOOLEAN
-- Should a non-transparent proxy be allowed to modify headers of response to `req`?
-- The headers concerned are listed in http://www.w3.org/Protocols/rfc2616-sec14.html#sec14,9.
-- If `False' then a Cache-Control: no-transorm header will be generated.
require
req_attached: req /= Void
do
-- We choose a conservative default. But most applications can
-- redefine to return `True'.
end
must_revalidate (req: WSF_REQUEST): BOOLEAN
-- If a client has requested, or a cache is configured, to ignore server's expiration time,
-- should we force revalidation anyway?
-- If `True' then a Cache-Control: must-revalidate header will be generated.
require
req_attached: req /= Void
do
-- Redefine to force revalidation.
end
must_proxy_revalidate (req: WSF_REQUEST): BOOLEAN
-- If a shared cache is configured to ignore server's expiration time,
-- should we force revalidation anyway?
-- If `True' then a Cache-Control: proxy-revalidate header will be generated.
require
req_attached: req /= Void
do
-- Redefine to force revalidation.
end
private_headers (req: WSF_REQUEST): detachable LIST [READABLE_STRING_8]
-- Header names intended for a single user.
-- If non-Void, then a Cache-Control: private header will be generated.
-- Returning an empty list prevents the entire response from being served from a shared cache.
require
req_attached: req /= Void
deferred
ensure
not_freely_cacheable: Result /= Void implies not is_freely_cacheable (req)
end
non_cacheable_headers (req: WSF_REQUEST): detachable LIST [READABLE_STRING_8]
-- Header names that will not be sent from a cache without revalidation;
-- If non-Void, then a Cache-Control: no-cache header will be generated.
-- Returning an empty list prevents the response being served from a cache
-- without revalidation.
require
req_attached: req /= Void
deferred
end
is_sensitive (req: WSF_REQUEST): BOOLEAN
-- Is the response to `req' of a sensitive nature?
-- If `True' then a Cache-Control: no-store header will be generated.
require
req_attached: req /= Void
deferred
end
note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, 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

View File

@@ -0,0 +1,37 @@
note
description: "[
Default policy for responing to OPTIONS requests other than OPTIONS*
By overriding `execute_options', clients can add a body, for example.
]"
date: "$Date$"
revision: "$Revision$"
class WSF_OPTIONS_POLICY
feature -- Basic operations
execute_options (req: WSF_REQUEST; res: WSF_RESPONSE; a_router: WSF_ROUTER)
-- Write response to `req' into `res'.
require
req_attached: req /= Void
options_request: req.is_request_method ({HTTP_REQUEST_METHODS}.method_options)
res_attached: res /= Void
a_router_attached: a_router /= Void
local
l_methods: WSF_REQUEST_METHODS
h: HTTP_HEADER
do
create h.make
res.set_status_code ({HTTP_STATUS_CODE}.ok)
h.put_content_type ({HTTP_MIME_TYPES}.text_plain)
h.put_current_date
h.put_content_length (0)
l_methods := a_router.allowed_methods_for_request (req)
if not l_methods.is_empty then
h.put_allow (l_methods)
end
res.put_header_text (h.string)
end
end

View File

@@ -0,0 +1,63 @@
note
description: "[
Policies for deciding if a resource that currently doesn't exist used to do so.
This default implementation assumes that no resources used to exist.
]"
date: "$Date$"
revision: "$Revision$"
deferred class WSF_PREVIOUS_POLICY
feature -- Access
resource_previously_existed (req: WSF_REQUEST): BOOLEAN
-- Did `req.path_translated' exist previously?
require
req_attached: req /= Void
do
-- No. Override if this is not want you want.
end
resource_moved_permanently (req: WSF_REQUEST): BOOLEAN
-- Was `req.path_translated' moved permanently?
require
req_attached: req /= Void
previously_existed: resource_previously_existed (req)
do
-- No. Override if this is not want you want.
end
resource_moved_temporarily (req: WSF_REQUEST): BOOLEAN
-- Was `req.path_translated' moved temporarily?
require
req_attached: req /= Void
previously_existed: resource_previously_existed (req)
do
-- No. Override if this is not want you want.
end
previous_location (req: WSF_REQUEST): LIST [URI]
-- Previous location(s) for resource named by `req';
require
req_attached: req /= Void
previously_existed: resource_previously_existed (req)
moved: resource_moved_permanently (req) or resource_moved_temporarily (req)
do
create {LINKED_LIST [URI]} Result.make
ensure
previous_location_attached: Result /= Void
non_empty_list: not Result.is_empty
end
note
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, 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

View File

@@ -16,8 +16,7 @@ inherit
end
create
make,
make_with_router
make
feature -- Execution
@@ -29,7 +28,7 @@ feature -- Execution
end
note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -13,11 +13,10 @@ inherit
WSF_URI_HANDLER
create
make,
make_with_router
make
note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -13,11 +13,10 @@ inherit
WSF_URI_TEMPLATE_HANDLER
create
make,
make_with_router
make
note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -12,14 +12,9 @@ inherit
feature {NONE} -- Initialization
make_with_router (a_router: like router)
do
router := a_router
end
make (n: INTEGER)
do
make_with_router (create {like router}.make (n))
create router.make (n)
end
feature -- Access

View File

@@ -16,8 +16,7 @@ inherit
end
create
make,
make_with_router
make
feature -- Execution
@@ -27,7 +26,7 @@ feature -- Execution
end
note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -18,8 +18,7 @@ inherit
end
create
make,
make_with_router
make
feature -- Execution
@@ -31,7 +30,7 @@ feature -- Execution
end
note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -18,8 +18,7 @@ inherit
end
create
make,
make_with_router
make
feature -- Execution
@@ -31,7 +30,7 @@ feature -- Execution
end
note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -15,8 +15,7 @@ inherit
end
create
make,
make_with_router
make
feature -- Execution
@@ -26,7 +25,7 @@ feature -- Execution
end
note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -1,18 +1,18 @@
note
description: "[
Server request context of the httpd request
It includes CGI interface and a few extra values that are usually valuable
meta_variable (a_name: READABLE_STRING_GENERAL): detachable WSF_STRING
meta_string_variable (a_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
meta_variable (a_name: READABLE_STRING_GENERAL): detachable WSF_STRING
meta_string_variable (a_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
In addition it provides
query_parameter (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE
form_parameter (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE
cookie (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE
...
And also has
execution_variable (a_name: READABLE_STRING_GENERAL): detachable ANY
--| to keep value attached to the request
@@ -140,7 +140,6 @@ feature -- Destroy
end
content_length_value := 0
content_type := Void
execution_variables_table.wipe_out
internal_cookies_table := Void
internal_form_data_parameters_table := Void
@@ -157,7 +156,6 @@ feature -- Destroy
raw_input_data_recorded := False
request_method := empty_string_8
set_uploaded_file_path (Void)
-- wgi_request
end
feature -- Status report
@@ -208,7 +206,7 @@ feature -- Error handling
error_handler: ERROR_HANDLER
-- Error handler
-- By default initialized to new handler
-- By default initialized to new handler
feature -- Access: Input
@@ -219,7 +217,7 @@ feature -- Access: Input
end
is_chunked_input: BOOLEAN
-- Is request using chunked transfer-encoding?
-- Is request using chunked transfer-encoding?
-- If True, the Content-Length has no meaning
do
Result := wgi_request.is_chunked_input
@@ -311,18 +309,44 @@ feature -- Helper
Result := request_method.is_case_insensitive_equal (m.as_string_8)
end
is_put_request_method: BOOLEAN
-- Is Current a PUT request method?
do
Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_put)
end
is_post_request_method: BOOLEAN
-- Is Current a POST request method?
do
Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_post)
end
is_delete_request_method: BOOLEAN
-- Is Current a DELETE request method?
do
Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_delete)
end
is_get_request_method: BOOLEAN
-- Is Current a GET request method?
do
Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_get)
end
is_get_head_request_method: BOOLEAN
-- Is Current a GET or a HEAD request method?
do
Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_get) or
request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_head)
end
is_put_post_request_method: BOOLEAN
-- Is Current a PUT or a POST request method?
do
Result := request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_put) or
request_method.is_case_insensitive_equal ({HTTP_REQUEST_METHODS}.method_post)
end
is_content_type_accepted (a_content_type: READABLE_STRING_GENERAL): BOOLEAN
-- Does client accepts content_type for the response?
--| Based on header "Accept:" that can be for instance
@@ -350,6 +374,8 @@ feature -- Helper
end
end
Result := l_accept.has_substring (a_content_type)
else
Result := True
end
end
@@ -375,7 +401,7 @@ feature -- Eiffel WGI access
Result := wgi_request.wgi_connector
end
feature {WSF_REQUEST_EXPORTER} -- Override value
feature {WSF_REQUEST_EXPORTER} -- Override value
set_request_method (a_request_method: like request_method)
-- Set `request_method' to `a_request_method'
@@ -414,7 +440,7 @@ feature {NONE} -- Access: global variable
end
end
feature -- Access: global variables
feature -- Access: global variables
items: ITERABLE [WSF_VALUE]
do
@@ -654,9 +680,9 @@ feature -- Access: CGI Meta variables
feature {NONE} -- Access: CGI meta parameters
meta_variables_table: STRING_TABLE [WSF_STRING]
-- CGI Environment parameters
-- CGI Environment parameters
feature -- Access: CGI meta parameters - 1.1
feature -- Access: CGI meta parameters - 1.1
auth_type: detachable READABLE_STRING_8
-- This variable is specific to requests made via the "http"
@@ -1128,7 +1154,7 @@ feature -- HTTP_*
http_access_control_request_headers: detachable READABLE_STRING_8
-- Indicates which headers will be used in the actual request
-- as part of the preflight request
-- as part of the preflight request
do
Result := wgi_request.http_access_control_request_headers
end
@@ -1139,6 +1165,42 @@ feature -- HTTP_*
Result := wgi_request.http_if_match
end
http_if_modified_since: detachable READABLE_STRING_8
-- Modification check on resource
do
Result := wgi_request.http_if_modified_since
end
http_if_none_match: detachable READABLE_STRING_8
-- Existence check on resource
do
Result := wgi_request.http_if_none_match
end
http_if_range: detachable READABLE_STRING_8
-- Range check on resource
do
Result := wgi_request.http_if_range
end
http_if_unmodified_since: detachable READABLE_STRING_8
-- Modification check on resource
do
Result := wgi_request.http_if_unmodified_since
end
http_last_modified: detachable READABLE_STRING_8
-- Modification time of resource
do
Result := wgi_request.http_last_modified
end
http_range: detachable READABLE_STRING_8
-- Requested byte-range of resource
do
Result := wgi_request.http_range
end
feature -- Extra CGI environment variables
request_uri: READABLE_STRING_8
@@ -1319,7 +1381,7 @@ feature -- Query parameters
feature {NONE} -- Query parameters: implementation
query_parameters_table: STRING_TABLE [WSF_VALUE]
-- Parameters extracted from QUERY_STRING
-- Parameters extracted from QUERY_STRING
local
vars: like internal_query_parameters_table
p,e: INTEGER
@@ -1496,7 +1558,7 @@ feature {NONE} -- Form fields and related
uploaded_files_table: STRING_TABLE [WSF_UPLOADED_FILE]
get_form_parameters
-- Variables sent by POST, ... request
-- Variables sent by POST, ... request
local
vars: like internal_form_data_parameters_table
l_raw_data_cell: detachable CELL [detachable STRING_8]
@@ -1528,7 +1590,7 @@ feature {NONE} -- Form fields and related
end
form_parameters_table: STRING_TABLE [WSF_VALUE]
-- Variables sent by POST request
-- Variables sent by POST request
local
vars: like internal_form_data_parameters_table
do
@@ -1541,7 +1603,7 @@ feature {NONE} -- Form fields and related
Result := vars
end
feature {NONE} -- Implementation: smart parameter identification
feature {NONE} -- Implementation: smart parameter identification
add_value_to_table (a_name: READABLE_STRING_8; a_value: READABLE_STRING_8; a_table: STRING_TABLE [WSF_VALUE])
-- Add urlencoded parameter `a_name'=`a_value' to `a_table'
@@ -1749,7 +1811,7 @@ feature {NONE} -- Implementation: URL Utility
-- Server url
internal_url_base: detachable STRING
-- URL base of potential script
-- URL base of potential script
feature -- Element change
@@ -1909,7 +1971,7 @@ feature {NONE} -- Implementation
end
end
feature {NONE} -- Implementation: utilities
feature {NONE} -- Implementation: utilities
single_slash_starting_string (s: READABLE_STRING_32): STRING_32
-- Return the string `s' (or twin) with one and only one starting slash
@@ -1939,7 +2001,7 @@ feature {NONE} -- Implementation: utilities
check i >= 2 and i <= n end
Result := s.substring (i - 1, s.count)
else
--| starts with one '/' and only one
--| starts with one '/' and only one
Result := s
end
elseif n = 1 then

View File

@@ -18,7 +18,11 @@
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
<library name="uri_template" location="..\..\text\parser\uri_template\uri_template-safe.ecf"/>
<cluster name="router" location=".\router\" recursive="true"/>
<cluster name="router" location=".\router\" recursive="true">
<file_rule>
<exclude>/policy_driven$</exclude>
</file_rule>
</cluster>
<cluster name="src" location=".\src\" recursive="true"/>
</target>
</system>

View File

@@ -15,11 +15,16 @@
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
<library name="error" location="../../utility/general/error/error.ecf"/>
<library name="http" location="../../network/protocol/http/http.ecf"/>
<library name="uri_template" location="../../text/parser/uri_template/uri_template.ecf"/>
<library name="uri_template"
location="../../text/parser/uri_template/uri_template.ecf"/>
<library name="encoder"
location="..\..\text\encoder\encoder.ecf"/>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf" readonly="true"/>
<cluster name="src" location=".\src" recursive="true"/>
<cluster name="router" location=".\router" recursive="true"/>
<cluster name="router" location=".\router" recursive="true">
<file_rule>
<exclude>/policy_driven$</exclude>
</file_rule>
</cluster>
</target>
</system>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-11-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-11-0 http://www.eiffel.com/developers/xml/configuration-1-11-0.xsd" name="wsf_policy_driven" uuid="3FC00449-5101-461D-94C6-10920C30EBF4" library_target="wsf_policy_driven">
<target name="wsf_policy_driven">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="standard">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
<library name="ewsgi" location="..\ewsgi\ewsgi-safe.ecf"/>
<library name="conneg" location="../../network/protocol/CONNEG/conneg-safe.ecf"/>
<library name="encoder" location="..\..\text\encoder\encoder-safe.ecf"/>
<library name="http" location="..\..\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="wsf-safe.ecf"/>
<cluster name="policy" location=".\policy_driven\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-11-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-11-0 http://www.eiffel.com/developers/xml/configuration-1-11-0.xsd" name="wsf_policy_driven" uuid="3FC00449-5101-461D-94C6-10920C30EBF4" library_target="wsf_policy_driven">
<target name="wsf_policy_driven">
<root all_classes="true"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" full_class_checking="true" void_safety="none" syntax="standard">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
<library name="ewsgi" location="..\ewsgi\ewsgi.ecf"/>
<library name="conneg" location="../../network/protocol/CONNEG/conneg.ecf"/>
<library name="encoder" location="..\..\text\encoder\encoder.ecf"/>
<library name="http" location="..\..\network\protocol\http\http.ecf"/>
<library name="wsf" location="wsf.ecf"/>
<cluster name="policy" location=".\policy_driven\" recursive="true"/>
</target>
</system>

View File

@@ -25,6 +25,16 @@ feature {NONE} -- Initialization
create error_added_actions
end
feature -- Access
primary_error_code: INTEGER
-- Code of first error in `errors'
require
at_least_one_error: has_error
do
Result := errors.first.code
end
feature -- Status
has_error: BOOLEAN

View File

@@ -12,7 +12,6 @@
</option>
<library name="atom" location="..\draft\library\protocol\syndication\atom\atom-safe.ecf" readonly="false"/>
<library name="client" location="..\examples\restbucksCRUD\client\client-safe.ecf" readonly="false"/>
<library name="cms" location="..\draft\application\cms\cms-safe.ecf" readonly="false"/>
<library name="connector_cgi" location="..\library\server\ewsgi\connectors\cgi\cgi-safe.ecf" readonly="false"/>
<library name="connector_libfcgi" location="..\library\server\ewsgi\connectors\libfcgi\libfcgi-safe.ecf" readonly="false"/>
<library name="connector_nino" location="..\library\server\ewsgi\connectors\nino\nino-safe.ecf" readonly="false"/>
@@ -22,7 +21,6 @@
<library name="default_libfcgi" location="..\library\server\wsf\default\libfcgi-safe.ecf" readonly="false"/>
<library name="default_nino" location="..\library\server\wsf\default\nino-safe.ecf" readonly="false"/>
<library name="default_openshift" location="..\library\server\wsf\default\openshift-safe.ecf" readonly="false"/>
<library name="demo" location="..\draft\application\cms\example\cms_demo-safe.ecf" readonly="false"/>
<library name="demo-1" location="..\library\security\openid\consumer\demo\demo-safe.ecf" readonly="false"/>
<library name="encoder" location="..\library\text\encoder\encoder-safe.ecf" readonly="false"/>
<library name="error" location="..\library\utility\general\error\error-safe.ecf" readonly="false"/>
@@ -48,6 +46,7 @@
<library name="uri_template" location="..\library\text\parser\uri_template\uri_template-safe.ecf" readonly="false"/>
<library name="wizard" location="..\tools\ise_wizard\ewf_ise_wizard-safe.ecf" readonly="false"/>
<library name="wsf" location="..\library\server\wsf\wsf-safe.ecf" readonly="false"/>
<library name="wsf_policy_driven" location="..\library\server\wsf\wsf_policy_driven-safe.ecf" readonly="false"/>
<library name="wsf_all" location="..\library\server\wsf\connector\all-safe.ecf" readonly="false"/>
<library name="wsf_cgi" location="..\library\server\wsf\connector\cgi-safe.ecf" readonly="false"/>
<library name="wsf_extension" location="..\library\server\wsf\wsf_extension-safe.ecf" readonly="false"/>

View File

@@ -10,8 +10,6 @@
</file_rule>
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="standard">
</option>
<library name="cms" location="..\draft\application\cms\cms-safe.ecf" readonly="false"/>
<library name="demo" location="..\draft\application\cms\example\cms_demo-safe.ecf" readonly="false"/>
<library name="restbucks" location="..\examples\restbucksCRUD\restbucks-safe.ecf" readonly="false"/>
<library name="client" location="..\examples\restbucksCRUD\client\client-safe.ecf" readonly="false"/>
<library name="upload_image" location="..\examples\upload_image\upload_image-safe.ecf" readonly="false"/>
@@ -21,7 +19,6 @@
<library name="openid" location="..\library\security\openid\consumer\openid-safe.ecf" readonly="false"/>
<library name="demo-1" location="..\library\security\openid\consumer\demo\demo-safe.ecf" readonly="false"/>
<library name="http_authorization" location="..\library\server\authentication\http_authorization\http_authorization-safe.ecf" readonly="false"/>
<library name="ewf_support" location="..\library\server\ewf_support\ewf_support-safe.ecf" readonly="false"/>
<library name="ewsgi" location="..\library\server\ewsgi\ewsgi-safe.ecf" readonly="false"/>
<library name="ewsgi_spec" location="..\library\server\ewsgi\ewsgi_spec-safe.ecf" readonly="false"/>
<library name="connector_cgi" location="..\library\server\ewsgi\connectors\cgi\cgi-safe.ecf" readonly="false"/>