324 lines
12 KiB
Markdown
324 lines
12 KiB
Markdown
Nav: [Workbook](../workbook.md) :: [Basic Concepts](../basics/basics.md) :: [Handling Requests: Header Fields](./headers.md)
|
|
|
|
# Handling Requests: Form/Query Data
|
|
|
|
##### Table of Contents
|
|
- [Reading Form Data](#read)
|
|
- [Query Parameters](#query)
|
|
- [Form Parameters](#form_parameters)
|
|
- [Uniform Read](#uniform)
|
|
- [Reading Parameters and Values](#reading_pv)
|
|
- [How to read all parameters names](#all_names)
|
|
- [How to read single values](#single_values)
|
|
- [How to read multiple values](#multiple_values)
|
|
- [How to read table values](#table_values)
|
|
- [Reading raw data](#raw_data)
|
|
- [Upload Files](#upload)
|
|
- [Examples](#examples)
|
|
|
|
|
|
An HTML Form can handle `GET` and `POST` requests.
|
|
When we use a form with method `GET`, the data is attached at the end of the url for example:
|
|
|
|
>http://wwww.example.com/?key1=value1&...keyn=valuen
|
|
|
|
If we use the method `POST`, the data is sent to the server in a different line.
|
|
|
|
Extracting form data from the server side is one of the most tedious parts. If you do it by hand, you will need
|
|
to parse the input, you'll have to URL-decode the value.
|
|
|
|
Here we will show you how to read input submitted by a user using a Form (`GET` and `POST`).
|
|
* How to handle missing values:
|
|
* client side validattion, server side validations, set default if it's a valid option.
|
|
* How to populate Eiffel objects from the request data.
|
|
|
|
<a name="read"></a>
|
|
|
|
## Reading Form Data
|
|
EWF `WSF_REQUEST` class, provides features to handling this form parsing automatically.
|
|
|
|
<a name="query"></a>
|
|
|
|
### Query Parameters
|
|
|
|
```eiffel
|
|
WSF_REQUEST.query_parameters: ITERABLE [WSF_VALUE]
|
|
-- All query parameters
|
|
|
|
WSF_REQUEST.query_parameter (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE
|
|
-- Query parameter for name `a_name'.
|
|
```
|
|
|
|
<a name="form_parameters"></a>
|
|
|
|
### Form Parameters
|
|
|
|
```eiffel
|
|
WSF_REQUEST.form_parameters: ITERABLE [WSF_VALUE]
|
|
-- All form parameters sent by a POST
|
|
|
|
WSF_REQUEST.form_parameter (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE
|
|
-- Field for name `a_name'.
|
|
```
|
|
|
|
The values supplied to `form_parameter` and `query_parameter` are _case_ _sensitive_.
|
|
|
|
<a name="uniform"></a>
|
|
|
|
### Read Data
|
|
The previous features, let you read the data one way for `GET` request and a different way for `POST` request. **WSF_REQUEST** provide a feature to read all the data in a uniform way.
|
|
|
|
```eiffel
|
|
WSF_REQUEST.item (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE
|
|
-- Variable named `a_name' from any of the variables container
|
|
-- and following a specific order: form_, query_ and path_ parameters
|
|
```
|
|
|
|
So, you can use **WSF_REQUEST.item** feature exactly the same way for `GET` and `POST` request.
|
|
|
|
>Note: if a query parameter has the same name as a form paramenter req.item will retrieve the form paramenter. Remember the precedence: `form` > `query` > `path`
|
|
|
|
|
|
<a name="reading_pv"></a>
|
|
|
|
## Reading Parameters and Values
|
|
|
|
Suppose we have the following HTML5 form using method `POST`. This HTML5 form has client side form validation using the new HTML5 `attribute`, you can do the same using Javascript. So in this case if the user does not fill the fields as expected the form will not be submitted to the server.
|
|
|
|
>Note: it is recommended to validate client side input on the server side (as a double check) because you can protect against the malicious user, who can easily bypass your JavaScript and submit dangerous input to the server.
|
|
|
|
```
|
|
<h1> EWF Handling Client Request: Form example </h1>
|
|
<form action="/" method="POST">
|
|
<fieldset>
|
|
<legend>Personal details</legend>
|
|
<div>
|
|
<label>First Name
|
|
<input id="given-name" name="given-name" type="text" placeholder="First name only" required autofocus>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<label>Last Name
|
|
<input id="family-name" name="family-name" type="text" placeholder="Last name only" required autofocus>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<label>Email
|
|
<input id="email" name="email" type="email" placeholder="example@domain.com" required>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<label>Languages
|
|
<input type="checkbox" name="languages" value="Spanish"> Spanish
|
|
<input type="checkbox" name="languages" value="English"> English
|
|
</label>
|
|
</div>
|
|
</fieldset>
|
|
<fieldset>
|
|
<div>
|
|
<button type=submit>Submit Form</button>
|
|
</div>
|
|
</fieldset>
|
|
</form>
|
|
```
|
|
<a name="all_names"></a>
|
|
|
|
### How to read all parameter names
|
|
To read all the parameters names we simple call **WSF_REQUEST.form_parameters**.
|
|
|
|
```eiffel
|
|
req: WSF_REQUEST
|
|
across req.form_parameters as ic loop show_parameter_name (ic.item.key) end
|
|
```
|
|
<a name="single_values"></a>
|
|
|
|
### How to read single values
|
|
To read a particular parameter, a single value, for example `given-name`, we simple call **WSF_REQUEST.form_parameter (a_name)** and we check if it's attached to **WSF_STRING** (represents a String parameter)
|
|
```eiffel
|
|
req: WSF_REQUEST
|
|
if attached {WSF_STRING} req.form_paramenter ('given-name') as l_given_name then
|
|
-- Work with the given parameter, for example populate an USER object
|
|
-- the argument is case sensitive
|
|
else
|
|
-- Value missing, check the name against the HTML form
|
|
end
|
|
```
|
|
<a name="multiple_values"></a>
|
|
|
|
### How to read multiple values
|
|
|
|
To read multiple values, for example in the case of `languages`, we simple call **WSF_REQUEST.form_parameter (a_name)** and we check if it's attached to **WSF_MULTIPLE_STRING** (represents a String parameter)
|
|
|
|
```eiffel
|
|
req: WSF_REQUEST
|
|
idioms: LIST[STRING]
|
|
-- the argument is case sensitive
|
|
if attached {WSF_MULTIPLE_STRING} req.form_paramenter ('languages') as l_languages then
|
|
-- Work with the given parameter, for example populate an USER object
|
|
-- Get all the associated values
|
|
create {ARRAYED_LIST[STRING]} idioms.make (2)
|
|
across l_languages as ic loop idioms.force (ic.item.value) end
|
|
elseif attached {WSF_STRING} req.form_paramenter ('languages') as l_language then
|
|
-- Value missing, check the name against the HTML form
|
|
create {ARRAYED_LIST[STRING]} idioms.make (1)
|
|
idioms.force (l_language.value)
|
|
else
|
|
-- Value missing
|
|
end
|
|
```
|
|
In this case we are handling strings values, but in some cases you will need to do a conversion, between the strings that came from the request to map them to your domain model.
|
|
|
|
<a name="table_values"></a>
|
|
|
|
### How to read table values
|
|
This is particularly useful when you have a request with the following format
|
|
|
|
```<a href="/link?tab[a]=1&tab[b]=2&tab[c]=foo">```
|
|
|
|
To read table values, for example in the case of `tab`, we simple call **WSF_REQUEST.form_parameter (a_name)** and we check if it's attached to **WSF_TABLE**.
|
|
|
|
```eiffel
|
|
if attached {WSF_TABLE} req.query_parameter ("tab") as l_tab then
|
|
l_parameter_names.append ("<br>")
|
|
l_parameter_names.append (l_tab.name)
|
|
from
|
|
l_tab.values.start
|
|
until
|
|
l_tab.values.after
|
|
loop
|
|
l_parameter_names.append ("<br>")
|
|
l_parameter_names.append (l_tab.values.key_for_iteration)
|
|
if attached {WSF_STRING} l_tab.value (l_tab.values.key_for_iteration) as l_value then
|
|
l_parameter_names.append ("=")
|
|
l_parameter_names.append (l_value.value)
|
|
end
|
|
l_tab.values.forth
|
|
end
|
|
end
|
|
```
|
|
|
|
<a name="raw_data"></a>
|
|
|
|
## Reading Raw Data
|
|
You can also access the data in raw format, it means you will need to parse and url-decode it, and also you will not be able to use the previous features, by default, to enable that, you will need to call `req.set_raw_input_data_recorded (True)`. This feature (reading raw data) is useful if you are reading `POST` data with JSON or XML formats, but it's not convinient for HTML forms.
|
|
|
|
To read raw data you need to do this
|
|
|
|
```eiffel
|
|
l_raw_data:STRING
|
|
|
|
req.set_raw_input_data_recorded (True)
|
|
create l_raw_data.make_empty
|
|
req.read_input_data_into (l_raw_data)
|
|
```
|
|
|
|
> given-name=testr&family-name=test&dob=1976-08-26&email=test%40gmail.com&url=http%3A%2F%2Fwww.eiffelroom.com&phone=455555555555&languages=Spanish&languages=English
|
|
|
|
<a name="upload"></a>
|
|
|
|
## Upload Files
|
|
How can we read data when the date come from an uploaded file/s?.
|
|
HTML supports a form element ```<input type="File" ... >``` to upload a single file and ```<input type="File" ... multiplr> ``` to upload multiple files.
|
|
|
|
So supose we have the following form
|
|
|
|
```
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>EWF Handling Client Request: File Upload Example</title>
|
|
</head>
|
|
<body>
|
|
<h1> EWF Handling Client Request: File Upload Example</h1>
|
|
<form action="/upload" enctype="multipart/form-data" method="POST">
|
|
<fieldset>
|
|
<legend>Upload file/s</legend>
|
|
<div>
|
|
<label>File
|
|
<input name="file-name[]" type="file" multiple>
|
|
</label>
|
|
<fieldset>
|
|
<div>
|
|
<button type=submit>Send</button>
|
|
</div>
|
|
</fieldset>
|
|
</form>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
The class **WSF_REQUEST** has a mechanism to work with uploaded files. We can call the query
|
|
|
|
```eiffel
|
|
WSF_REQUEST.has_uploaded_file: BOOLEAN
|
|
-- Has any uploaded file?
|
|
```
|
|
|
|
to check if the request form parameters has any uploaded file, we can call the feature
|
|
|
|
```eiffel
|
|
WSF_REQUEST.uploaded_files: ITERABLE [WSF_UPLOADED_FILE]
|
|
-- uploaded files values
|
|
--| filename: original path from the user
|
|
--| type: content type
|
|
--| tmp_name: path to temp file that resides on server
|
|
--| tmp_base_name: basename of `tmp_name'
|
|
--| error: if /= 0 , there was an error : TODO ...
|
|
--| size: size of the file given by the http request
|
|
```
|
|
to iterate over the uploaded files if any, and the details in the class **WSF_UPLOADED_FILE**.
|
|
|
|
The following snipet code show how to work with Uploaded files using EWF **WSF_REQUEST** class, in the example
|
|
we build a simple html answer with basic information, if there is not uploaded files, we send a 400 status code
|
|
and a simple message.
|
|
|
|
```eiffel
|
|
|
|
if req.path_info.same_string ("/upload") then
|
|
-- Check if we have an uploaded file
|
|
if req.has_uploaded_file then
|
|
-- iterate over all the uploaded files
|
|
create l_answer.make_from_string ("<h1>Uploaded File/s</h1><br>")
|
|
across req.uploaded_files as ic loop
|
|
l_answer.append ("<strong>FileName:</strong>")
|
|
l_answer.append (ic.item.filename)
|
|
l_answer.append ("<br><strong>Size:</strong>")
|
|
l_answer.append (ic.item.size.out)
|
|
l_answer.append ("<br>")
|
|
end
|
|
res.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-type","text/html"],["Content-lenght", l_answer.count.out]>>)
|
|
res.put_string (l_answer)
|
|
else
|
|
-- Here we should handle unexpected errors.
|
|
create l_answer.make_from_string ("<strong>No uploaded files</strong><br>")
|
|
create l_answer.append ("Back to <a href='/'>Home</a>")
|
|
res.put_header ({HTTP_STATUS_CODE}.bad_request, <<["Content-type","text/html"],["Content-lenght", l_answer.count.out]>>)
|
|
res.put_string (l_answer)
|
|
end
|
|
else
|
|
-- Handle error
|
|
end
|
|
```
|
|
The source code is available on Github. You can get it by running the command:
|
|
|
|
```git clone https://github.com/EiffelWebFramework/ewf.git```
|
|
|
|
The example is located in the directory $PATH/ewf/doc/workbook/upload_file where $PATH is where you run git clone.
|
|
|
|
|
|
<a name="examples"></a>
|
|
|
|
## Examples
|
|
The source code is available on Github. You can get it by running the command:
|
|
|
|
```git clone https://github.com/EiffelWebFramework/ewf.git```
|
|
|
|
The GET example is located in the directory $PATH/ewf/doc/workbook/form/get, and the post example is located in the directory $PATH/ewf_examples/workbook/form/post where $PATH is where you run git clone . To run open it using Eiffel Studio or just run theg following command
|
|
|
|
```estudio -config <ecf_name>.ecf -target <target_name>```
|
|
|
|
>Note: replace <ecf_name> and<target_name> with the corresponding values.
|
|
|
|
|
|
Nav: [Workbook](../workbook.md) :: [Basic Concepts](../basics/basics.md) :: [Handling Requests: Header Fields](./headers.md)
|