Emmett provides several instruments to help you dealing with requests in your application. Let's see them.
When a request comes from a client, Emmett binds useful informations about it within the request
object, which can be accessed just with an import:
from emmett import request
It contains useful information about the current processing request, in particular:
attribute | description |
---|---|
scheme | could be http or https |
path | full path of the request |
host | hostname of the request |
method | the request HTTP method |
now | a pendulum Datetime object created with request |
headers | the headers of the request |
cookies | the cookies passed with the request |
body | the request body (if available) |
client | the IP Address of the client doing the request (if available) |
Please keep in mind that the now
attribute will always use the UTC timezone, by default.
If you need to access the local time of the request you can use now_local
:
# request datetime in local machine timezone
request.now_local
Note: since
now
is a pendulum Datetime object, you can easily change the timezone using thein_timezone
method, likerequest.now.in_timezone('Europe/Berlin')
.
Now, let's see how to deal with request variables.
Changed in version 2.6
Emmett's request
object also provides four important attributes about the active request:
attribute | awaitable | description |
---|---|---|
query_params | no | contains the URL query parameters |
body | yes | contains the raw (bytes) request body |
body_params | yes | contains parameters passed into the request body |
files | yes | contains files passed into the request body |
All the attributes but body
are sdict
objects and they work in the same way, within the exception of requiring await
or not, and an example may help you understand their dynamic:
from emmett import App, request
app = App(__name__)
@app.route("/post/<int:id>")
async def post(id):
editor = request.query_params.editor
if editor == "markdown":
text = (await request.body_params).text
elif editor == "html":
# code
#..
Now, when a client calls the URL /post/123?editor=markdown, the editor
parameter will be mapped into request.query_params
and we can access its value simply calling the parameter name as an attribute.
When the URL doesn't contain the query parameter you're trying to look at, this will be None
, so it's completely safe to call it. It won't raise any exception.
Now, what happens when the client does a POST request with the following body on the URL /post/123?editor=markdown?
{
"text": "this is an example post",
"date": "2014-10-15"
}
Simple: the request
's params attributes will look like this:
>>> request.query_params
<sdict {'editor': 'markdown'}>
>>> await request.body_params
<sdict {'date': '2014-10-15', 'text': 'this is a sample post'}>
You can always access the variables you need.
The files
attribute works in the same way of body_params
for multipart requests, but its values are objects wrapping the underlying file.
These objects have some useful attributes, specifically:
attribute | description |
---|---|
filename | name of the file |
content_type | MIME type of the file |
size | file size |
Also, these object provides two methods to interact with the file contents: the read
method, which allows you to load the file content, and the async save
method, which allows you to directly store the file contents into a file-like object.
@app.route()
async def multipart_load():
files = await request.files
# at this point you can either:
# i) read all the file contents
data = files.myfile.read()
# ii) read up to 4k of the file contents
data = files.myfile.read(4096)
# iii) store the file
await files.myfile.save(f"some/destination/{files.myfile.filename}")
The body
attribute gives you direct access to the request body. It's an awaitable object, so you can either load the whole body or iterate over it:
@app.route()
async def post():
raw_body = await request.body
@app.route()
async def iterpost():
async for raw_chunk in request.body:
# do something
Note: you cannot mix the two approaches. Also, directly interacting with
body
will prevent you to usebody_params
andfiles
within the same request.
Speaking of handling requests, you would like to perform specific actions on errors.
If we look at the given example for the parameters again, what happens when the user calls the URL without passing the editor
query parameter?
Maybe you want to redirect the client with a default parameter:
from emmett import redirect, url
@app.route("/post/<int:id>")
async def post(id):
editor = request.query_params.editor
if editor == "markdown":
# code
elif editor == "html":
# code
else:
redirect(url('post', id, params={'editor': 'markdown'}))
which means that, when the editor
var is missing, we force the user to markdown.
The redirect
function of Emmett accepts a string for the URL, and acts like an exception, interrupting the execution of your code.
Maybe, you prefer to show your 404 page:
from emmett import abort
@app.on_error(404)
async def not_found():
return app.render_template("404.html")
@app.route("/post/<int:id>")
async def post(id):
editor = request.query_params.editor
if editor == "markdown":
# code
elif editor == "html":
# code
else:
abort(404)
That's all it takes.
So you've just learned three handy aspects of Emmett:
redirect
and abort
allow you to stop the execution of your code;app.on_error()
;app.render_template()
to render a specific template without the presence of an exposed function or a specific context.Changed in version 2.5
Emmett supports serving requests following the HTTP/2 standard. In order for the browser to use HTTP/2 protocol, SSL should be enabled. SSL certificate and key should be passed to develop
and serve
commands using the relevant --ssl-certfile
and --ssl-keyfile
options.
Hint: a self-signed SSL certificate can be generated for development purposes using the
openssl
command, like:openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes
.
Server push allows for the server to send responses to the client before the client sends the request itself. This is useful when the server can predict what the client will request, thereby saving time at the possible cost of bandwidth if the prediction is wrong.
Emmett supports HTTP/2 server push promises thanks to the Request.push_promise
coroutine, which accepts as parameter the url of the static file to be sent. Here is an example:
from emmett import request, url
@app.route("/")
async def index():
await request.push_promise(url("static", "some_asset.js"))
return {}
Note: Emmett's integrated HTTP server doesn't support push promises. In order to use this feature you will need to serve your application with a 3rd party server like Hypercorn.