HTTP requests and responses – Part 1
Nette offers two layers of abstraction to work with HTTP. The
first, low-level, is provided by namespace classes Nette\Http
. They
offer a nice API to handle HTTP headers, pull URLs, incoming parameters and
files, or set a response numeric code. We are almost shielded from this layer in
the Nette application.
The second layer encapsulates the first layer in order. Classes are located
in the namespace Nette\Application
and are useful if you are
working with the Nette application (and now I mean literally
Nette\Application\Application
) and its presenters and actions. The
class object Nette\Application\Request
is the result of the URL
router's work. The router (Nette\Aplication\IRouter
) takes the
HTTP request and rebuilds it to Nette\Application\Request
according
to the defined routes. It carries information which
:module:presenter:action
to go. Furthermore, it is not very
interesting. Far more interesting is Nette\Application\IResponse
.
In particular, this short series of three articles will be devoted to this.
HTTP responses from the application
Before we get to manipulating Nette\Application\IResponse
,
I will summarize the tools for controlling the HTTP protocol, which we have at
presenter run. Descendants of class Nette\Application\UI\Presenter
,
probably all our presenters, have a lot of them.
You know the method redirect()
from tutorials. But what exactly
does the following code do in terms of the HTTP protocol?
public function actionDefault(): void
{
$this->redirect(':Front:Homepage:help');
}
Finally, it sets the HTTP response code to 302 (or 303), sets the HTTP header
Location
to the URL it creates according to the pattern
:Front:Homepage:help
, terminates the presenter run and sends the
HTTP response to the client. A client, such as a web browser, reads the
response and retrieves the new page from the URL it received. Very comfortable
for us.
Next, we have a method redirectPermanent()
that works the same
but uses 301 as a response code, a permanent redirect. Choosing the right code
is sometimes a science.
And thirdly, the method redirectUrl()
. We pass the URL and
optionally the redirect code to the method. For example:
public function actionDefault(): void
{
$this->redirectUrl('https://my.new.web/');
}
Another helper, method error()
. Everything revolves around the
return code of the HTTP response. The method defaults to 404, ie “page not
found”. When to use the method error()
? It depends on you.
Suppose we have a blog and someone wants to read a nonexistent article
https://example.com/read/123456
.
public function actionRead(int $id): void
{
try {
$article = $this->articles->get($id);
} catch (ArticleNotFoundException $e) {
# Article doesn't exist, what now?
# We can notify users and redirect them to the article list.
$this->flashMessage("Sorry, article with ID '$id' does not exist. Try another one.", 'warning');
$this->redirect('Blog:list');
# Or use HTTP code 404 - Not Found
$this->error("Article with ID '$id' was not found.");
}
}
The biggest difference is what is displayed to the user. In the first case,
the list of existing articles, in case of a call error()
,
ErrorPresenter is displayed and the 404 error is logged by the webserver. That
may come in handy. Calling error()
seems to be more correct, better
describes the situation.
EDIT: Jakub Vrána commented in the comments that redirecting from a non-existent article is an antipattern. This makes it difficult to manually type a URL in a typo and indexes bad links as a list of articles. Clear arguments for using 404.
The second parameter of the method error()
is the error code.
HTTP error codes are a lot of. For example, “forbidden”:
public function actionRead(int $id): void
{
try {
$article = $this->articles->get($id);
} catch (PermissionDeniedException $e) {
$this->error('You Shall Not Pass!', Nette\HTTP\IResponse::S403_FORBIDDEN);
}
}
Another presenter method is used for sending data in JSON format. It is
called sendJson()
and the use could not be more intuitive:
public function actionGetUserInfo(string $username): void
{
$this->sendJson([
'username' => ...,
'firstName' => ...,
'lastName' => ...,
]);
}
We don't have to worry about anything. The field is correctly converted to
JSON format, HTTP headers Content-Type
and
Content-Length
are set correctly, the client will be satisfied.
The other two presenter methods, getHttpRequest()
and
getHttpResponse()
. The names suggest a little. They return the
objects Nette\Http\IRequest
and Nette\Http\IResponse
,
the low-level HTTP API I mentioned in the introduction. Thanks to all the
methods I have described above, we hardly need them. But one simplified example
I should.
Imagine that our blog, our Nette application, is to provide one REST API endpoint. It will return the author's name, article title, and publishing time. But it won't be for everyone, just for clients with a mysterious token.
public function actionArticleData(int $id): void
{
$request = $this->getHttpRequest();
if ($request->getHeader('Token') !== 'eb0f21d63594e58d6b9995a7d2ac156c') {
$this->error('Invalid or missing access token.', Nette\HTTP\IResponse::S403_FORBIDDEN);
die();
}
try {
$article = $this->articles->get($id);
} catch (ArticleNotFoundException $e) {
$this->error('Article not found.');
die();
}
$this->sendJson([
'id' => $id,
'author' => $article->author->fullName,
'title' => $article->title,
'publishedAt' => $article->publishedAt->format(\DateTime::ISO8601),
]);
}
You may have noticed a call die()
after
error()
. Not necessary. All the methods I have written about will
end processing and will not continue. I use it because when I first look at
the method code, I see where its run can end.
And that's all. Only the method sendResponse()
remains and we
will look at it in the next episode.
Sign in to submit a comment