HTTP requests and responses – Part 1

about a year ago by Miloslav Hůla     edit

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.