Request processing

1. Introduction

Processing of user requests is an essential part of web applications. Common tasks around this topic are:

  • Catch the request: This is typically done at a central entry point to easily setup configuration, filtering, and logging (DRY and boot-strapping principle).
  • Validation and processing of user input: This step is often used to filter user input preventing XSS or SQL injection attacks.
  • Initialization of the application: User input is now used to initialize the application to i.e. determine which page is to be displayed. This is especially important for dynamic applications.
  • Output generation: The main part of the application executes all necessary logic and takes care of the output generation. This includes HTML generation but also dynamic parts (e.g. images, CSS, Java Script, AJAX responses).
  • Sending the response: After execution of the application the answer is sent back to the user. This step often includes additional steps such as filtering and/or processing of the output.

The APF supports all steps mentioned above with approved solutions based on state-of-the-art software design patterns.

Catching the request can be easily realized using the boot-strapping concept in combination with the Front controller. The latter one takes care of the entire life cycle of a user request and provides many additional functionality.

Validation and processing of user input can be done with Filters easily. Besides the implementations shipped with the APF that already cover a major part of common use cases you can create custom implementations matching the requirement of your application in no time.

Initialization of the application is supported by the Front controller action concept. Such software components can be used to provide common information with a central application model. Details can be found in the Front controller chapter.

Output generation is supported by an extensive set of tools. Especially the Page controller allows to write generic and re-usable modules based on the HMVC pattern. Please refer to Components documentation for more details on other tools. Sending the response back to the client can be supported by Filters as well.

2. Basics

Processing an HTTP Request and of course answering using an HTTP Response is managed by the Front controller. While processing the request both front controller and it's registered actions as well as input and output Filter make use of theRequest and Response abstraction.

APF's abstraction of the plain PHP mechanism provides several advantages:

  • Instead of using global arrays (i.e. $_REQUEST) and native functions a well-defined object-oriented API can be used.
  • Functionality that is often used (e.g. reading parameters including fallback values) is already there.
  • Common pitfalls using the mechanisms and functions provided by PHP are covered by a clearly structured API that hides issued behind it.

APF's abstraction of Request and Response contains multiple parts: interface definitions, the implementations RequestImpl and ResponseImpl, and an mixin (Trait) GetRequestResponse.

To change the Request and Response implementation please add the following code to your bootstrap file after including APF/core/bootstrap.php:
PHP code
Frontcontroller::$requestImplClass = 'VENDOR\request\CustomRequestImpl'; Frontcontroller::$responseImplClass = 'VENDOR\request\CustomResponseImpl';
GetRequestResponse::getRequest() and GetRequestResponse::getResponse() now return your implementation instead of APF's RequestImpl and ResponseImpl.

The following chapters describe usage of the Request and Response abstraction within your application in detail.

3. The request

Interface Request described ths structure of an HTTP request within an APF-based application. It contains constants for frequently used definitions as well as methods to query and manipulate request content. The following code block contains the interface definition (shortened):

PHP code
namespace APF\core\http; interface Request { const METHOD_GET = 'GET'; const METHOD_POST = 'POST'; ... const DEFAULT_PROTOCOL_IDENTIFIER = 'http'; const SECURE_PROTOCOL_IDENTIFIER = 'https'; ... public function getParameter($name, $default = null); public function getGetParameter($name, $default = null); public function getPostParameter($name, $default = null); public function getParameters(); public function getGetParameters(); public function getPostParameters(); public function setParameter($name, $value); public function setGetParameter($name, $value); public function setPostParameter($name, $value); ... public function getRequestUri(); public function getHost(); public function getPath(); public function getHeaders(); public function isSecure(); public function getUrl($absolute = false); public function getReferrerUrl($absolute = false); public function getCookies(); public function getCookie($name); ... }

The implementation of the interface - class RequestImpl - contains additional functionality and convenience methods to ease implementation. Details can be taken from the subsequent chapters.

3.1. Obtaining the request

The instance of the Request is unique within the entire application. For this reason it is created via the Singleton implementation. Details can be taken from Creation of objects.

In all classes that inherit from

  • APF\core\pagecontroller\Document,
  • APF\core\pagecontroller\BaseDocumentController,
  • APF\core\frontcontroller\AbstractFrontcontrollerAction oder
  • APF\tools\form\validator\AbstractFormValidator

you can obtain the instance using static method getRequest(). In all other classes you may want to use the APF\core\http\mixins\GetRequestResponse trait that provides this functionality.

In case you want to obtain the instance of the current request you may want to use the following code:

PHP code
$request = $this->getRequest();

Variable $request now contains an instance of RequestImpl.

If you intend to obtain an instance outside any class please use the following code snippet:

PHP code
$request = Singleton::getInstance(RequestImpl::class);

3.2. Requesting parameters

Querying parameters RequestImpl offers various options. In case you want to retrieve a parameter value regardless of the request method (e.g. GET or POST) you should use getParameter():

PHP code
$foo = $request->getParameter('foo');

Method getParameter() supports applying a default/fallback value that is returned in case the current request does not contain the requested parameter:

PHP code
$foo = $request->getParameter('foo', 'bar');

If you want to retrieve the parameter only in case a special request method has been chosen (e.g. POST) you can use getPostParameter():

PHP code
$foo = $request->getPostParameter('foo');

Variable $foo will only contain a value unlike null in case parameter foo is contained in the POST request. The same is true for getGetParameter() with GET requests.

Evaluating a certain value for PUT requests only can be done as follows without a convenience method:

PHP code
$foo = $request->isPut() ? $request->getParameter('foo') : null;

Requesting all parameters of the current request can be done by using getParameters() or getPostParameters() and getGetParameters(). All methods return an associative array of parameter names and their respective values.

To read all GET parameters please use the following code block:

PHP code
$params = $request->getGetParameters();

To check whether a parameter has been sent along with the current request you can use the following check:

PHP code
if($request->hasParameter('foo')) { ... }

For GET and POST requests you can use hasGetParameter() and hasPostParameter() to check whether a parameter has been sent along.

Both getGetParameter() and getPostParameter() support applying standard/fallback values.

3.3. Manipulation of the request

Hiding details of your application from the outside world implementing appropriate input and output Filter is an excellent way. With their help internal information can be transformed into an external representation and while processing the incoming request the external model can be used to re-store the internal state.

Manipulating the Request the interface and the corresponding implementation - class RequestImpl - contain all necessary functionality.

In order to set a dedicated parameter with each request (e.g. the current request time) you can add the following code to an input filter:

PHP code
$request = $this->getRequest(); $request->setParameter('request-time', time());

Within your application, the parameter can be retrieved using getParameter(). In case you want to specify the parameter for a certain request method only you may want to apply the approach described in chapter 3.2 and use setGetParameter() or setPostParameter().

Deleting parameters can either be done for a dedicated parameter or for the entire set of parameters of the current request. Also with this use case you can delete parameters according to the request method. The following code box shows how to delete a dedicated parameter:

PHP code
$request = $this->getRequest(); // Deletion of a request parameter $request->deleteParameter('foo'); // Deletion of a GET request parameter (POST parameter remains) $request->deleteGetParameter('foo'); // Deletion of a POST request parameter (GET parameter remains) $request->deletePostParameter('foo');

To delete a list of parameters please us the following code:

PHP code
$request = $this->getRequest(); // Deletion of multiple request parameters $request->deleteParameters(['foo', 'bar', 'baz']); // Deletion of multiple GET request parameters (POST parameters remain) $request->deleteGetParameter(['foo', 'bar', 'baz']); // Deletion of multiple POST request parameters (GET parameters remain) $request->deletePostParameter(['foo', 'bar', 'baz']);

To delete all parameters please use the following methods:

PHP code
$request = $this->getRequest(); // Deletion of all request parameters $request->resetParameters(); // Deletion of all GET request parameters (POST parameters remain) $request->resetGetParameters(); // Deletion of all POST request parameters (GET parameters remain) $request->resetPostParameters();

3.4. Reading cookies

Each request can contain any number of Cookies. retrieving cookie information you are provided the getCookies() and getCookie() methods. The first one returns all cookies sent along wit the request and the second one returns a dedicated cookie.

The following code block demonstrates how to retrieve a cookie value:

PHP code
$request = $this->getRequest(); $cookie = $request->getCookie('name_of_the_cookie'); $value = null; if($cookie !== null){ $value = $cookie->getValue(); }

In case the requested cookie is not present getCookie() returns null. To ease implementation with applying default values you can use getValue()'s optional default/fallback value argument.

The list of all cookies can be printed as follows:

PHP code
$request = $this->getRequest(); foreach($request->getCookies() as $cookie) { echo $cookie->getName() . ':' . $cookie->getValue() . PHP_EOL; }

All cookies sent along with the request do only contain the name and the value of the cookie. Attributes such as domain, life time and information on the security level are not contained.

This means that a cookie that is returned by getCookies() or getCookie() cannot be sent back with the Response directly. Due to missing attributes this would lead to unwanted behaviour or even errors.

Please note the hints in chapter 4.5 how to handle this case.

3.5. Reading URL and headers

Each request instance contains the current and referring URL as well as all HTTP headers sent by the client as additional attributes.

The current URL can be retrieved by method getUrl() or getRequestUri(). getUrl() returns an instance of class Url and getRequestUri() returns a string only. The following code example shows how to use both methods:

PHP code
$request = $this->getRequest(); // Generation of a target URL using the URL abstraction $url = $request->getUrl(); $successPageUrl = LinkGenerator::generateUrl($url->setQueryParameter('view', 'success')); // Generation of a target URL using the URL string $url = Url::fromString($request->getRequestUri()); $successPageUrl = LinkGenerator::generateUrl($url->setQueryParameter('view', 'success'));

Details on link generation can be taken from chapter Links.

Using getReferrerUrl() or getReferrer() returns the referring page of the current request. Again, getReferrerUrl() returns an instance of class Url similar to getUrl() and getReferrer() returns a string only.

Headers of the current request can be gathered by getHeaders() and getHeader(). In order to determine whether a request should be answered with a certain character set you may want to use the following code:

PHP code
$request = $this->getRequest(); $requestedCharset = 'utf-8'; $header = $request->getHeader('Accept-Charset'); if($header !== null) { $requestedCharset = $header->getValue(); }

A list of all headers can be printed with the following code snippet:

PHP code
$request = $this->getRequest(); foreach($request->getHeaders() as $header) { echo $header->getName() . ':' . $header->getValue() . PHP_EOL; }

3.6. Further methods

Besides the methods listed in the previous chapters, Request implementation RequestImpl offers a couple of more methods to ease implementation of web applications. The following list contains all additional functions along with a short description.

  • getSessionId(): Returns the current session ID.
  • isSecure(): Returns true in case the request is initiated over a secure connection (SSL, TLS).
  • getMethod(): Returns the request method. All values are represented by the Request::METHOD_* constants.
  • isGet(): Returns true in case the current request is a GET request.
  • isPost(): Returns true in case the current request is a POST request
  • isPut(): Returns true in case the current request is a PUT request.
  • isDelete(): Returns true in case the current request is a DELETE request.
  • isAjax(): Returns true in case the current request is a Ajax request (e.g. initiated by a Java-Script library).
  • isImage(): Returns true in case the current request is an image request.
  • isHtml(): Returns true in case the current request is a simple page request.
  • isGzipSupported(): This method can be used to determine whether the client supports gzip compression.
  • isDeflateSupported(): This method can be used to determine whether the client supports deflate compression.

Further details be taken from the API documentation.

4. The response

Similar to the Request definition interface Response describes the structure of a HTTP response for APF-based applications. It contains constants for frequently used definitions as well as methods to query and manipulate response content. The following code block contains the interface definition (shortened):

PHP code
interface Response { const VERSION_1_0 = '1.0'; const VERSION_1_1 = '1.1'; ... const CODE_OK = 200; ... const CODE_MOVED_PERMANENTLY = 301; const CODE_FOUND = 302; const CODE_SEE_OTHER = 303; const CODE_NOT_MODIFIED = 304; const CODE_USE_PROXY = 305; const CODE_TEMPORARY_REDIRECT = 307; ... const CODE_UNAUTHORIZED = 401; ... public function getVersion(); public function setVersion($version); public function getStatusCode(); public function setStatusCode($code); public function getReasonPhrase(); public function setReasonPhrase($phrase); public function getBody(); public function setBody($body, $append = false); public function send($exit = true); public function forward($url, $exitAfterForward = true); public function redirect($url, $permanent = false, $exitAfterForward = true); public function sendNotFound($exitAfterForward = true); public function sendServerError($exitAfterForward = true); public function isSent(); ... public function getHeaders(); public function setHeader(Header $header); public function deleteHeader(Header $header); public function getCookies(); public function setCookie(Cookie $cookie); public function deleteCookie(Cookie $cookie);

The implementation of the interface - class ResponseImpl - contains additional functionality and convenience methods to ease implementation. Details can be taken from the subsequent chapters.

4.1. Obtaining the response

The instance of the Response is unique within the entire application. For this reason it is created via the Singleton implementation. Details can be taken from Creation of objects.

In all classes that inherit from

  • APF\core\pagecontroller\Document,
  • APF\core\pagecontroller\BaseDocumentController,
  • APF\core\frontcontroller\AbstractFrontcontrollerAction oder
  • APF\tools\form\validator\AbstractFormValidator

you can obtain the instance using static method getResponse(). In all other classes you may want to use the APF\core\http\mixins\GetRequestResponse trait that provides this functionality.

In case you want to obtain the instance of the current request you may want to use the following code:

PHP code
$response = $this->getResponse();

Variable $response now contains an instance of ResponseImpl.

If you intend to obtain an instance outside any class please use the following code snippet:

PHP code
$response = Singleton::getInstance(ResponseImpl::class);

4.2. Definition of standard properties

Each response of an HTTP communication contains dedicated parts such as protocol version, the response status, and a description of the response status. Besides, several meta information are contained wrapped in headers are contained. Details on headers can be taken from chapter 4.3.

The version of your response can be defined with method setVersion(). Without any changes the response is always sent with version HTTP/1.1. Defining a different version is as follows:

PHP code
$response = $this->getResponse(); $response->setVersion('1.0');

More interesting is the definition of the status code along with an appropriate description. Without any change the client is sent a HTTP/1.1 200 OK response. In case you want to change the status code to indicate a certain event (e.g. page not found) you can use the following code snippet:

PHP code
$response = $this->getResponse(); $response->setStatusCode(Response::CODE_NOT_FOUND);

All standard codes are automatically translated into their description test. Sending a 404 answer the text is Not Found. In case you want to send a custom test maybe containing additional information you can define this by using setReasonPhrase():

PHP code
$response = $this->getResponse(); $response->setStatusCode(Response::CODE_NOT_FOUND); $response->setReasonPhrase('Page ' . $_SERVER['REQUEST_URI'] . ' not found!);

Your client then gets:

Code
HTTP/1.1 404 Page /foo not found!

4.3. Definition of headers

Besides the standard properties of an HTTP response you can add any number of meta properties using HTTP headers. Implementing a web application the content type of the answer is important (e.g. HTML, CSS, image) as well as the validity of a document or redirection. To redirect clients special methods are provided as described in chapter 4.6.

The data type of the HTTP response can be defined with convenience method setContentType() or via setHeader() natively:

PHP code
$response = $this->getResponse(); // Using the convenience method $response->setContentType('image/png'); // Using the native implementation $response->setHeader(new HeaderImpl(HeaderImpl::CONTENT_TYPE, 'image/png'));

Using the native implementation any kind of headers can be defined for the HTTP response. The following code box shows a couple of examples:

PHP code
$response = $this->getResponse(); // Defines the date of the response $response->setHeader(new HeaderImpl('Date', date('%Y-%m-%d %H:%i:%s'))); // Allows caching of the document on clients ans proxies $response->setHeader(new HeaderImpl('Cache-Control ', 'public')); // Defines the last modification time $response->setHeader(new HeaderImpl('Last-Modified', $lastModificationDate));
In case you want to overwrite a header that has already been set just use setHeader() again. The new definition will overwrite the old one.

To delete a header definition that has been set before please use deleteHeader():

PHP code
$response = $this->getResponse(); $response->deleteHeader(new HeaderImpl('Date', null));

To delete all headers that have been set before please use resetHeaders():

PHP code
$response = $this->getResponse(); $response->resetHeaders();

4.4. Filling the body

In general, your response will contain content (e.g. HTML-Code). In ordert to fill the Response with content you can use setBody(). This method both supports setting the (entire) content as well as appending further content:

PHP code
$response = $this->getResponse(); // Define the content (overwrites existing content) $response->setBody('...'); // Appends content $response->setBody('...', true);

Reading the content that has been added/set so far can be done wit the getBody() function.

Definition of the content is managed by the Front controller during the request processing for you. As a general rule, explicitly setting/adding content is only necessary in front controller actions. Details can be taken from chapter 4.7.

4.5. Set and delete cookies

In case you want to store certain information of your application within cookies you can use function setCookie(). First, create the desired cookie and then hand it over to the Response instance:

PHP code
$cookie = new Cookie('name_of_the_cookie', Cookie::DEFAULT_EXPIRATION_TIME, 'www.example.com'); $cookie->setValue('Desired value'); $response = $this->getResponse(); $response->setCookie($cookie);
Please note, that the value of a Cookie is always a string. If you intend to save more complex data structures you must take care of de- and encoding yourself. For this reason, you may want to use the serialize() and unserialize() functions.
Further, please note, that the size of Cookies directly impacts the request performance. This is because Cookies are contained within the HTTP request header. Hence, it is strongly recommended to keep the amount of data small. You may want to use a custom data format or compressing functions.

In case you are planning to change a cookie that has been sent previously this can be done with the following code:

PHP code
$request = $this->getRequest(); $cookie = $request->getCookie('name_of_the_cookie'); $cookie ->setExpireTime(86400 * 2) ->setDomain('other.example.com') ->setPath('/foo/bar') ->setHttpOnly(true) ->setSecure(false); $response = $this->getResponse(); $response->setCookie($cookie);

Please note, that not all relevant information are contained in the request for a cookies that has been set previously. For this reason, if relevant, set the parameters contained in the following list:

  • Cookie::expireTime
  • Cookie::domain
  • Cookie::path
  • Cookie::secure
  • Cookie::httpOnly

Please ensure to set the same values as when setting the cookie for the first time.

Details can be taken from chapter 3.4.

Deleting cookies can be done using Response::deleteCookie(). This method marks an applied cookie for deletion with the client:

PHP code
$response = $this->getResponse(); $response->deleteCookie(new Cookie('name_of_the_cookie'));
Please ensure that the values of the cookie are identical to the values you have set when creating the cookie. This especially applies to domain and path. In case they have been set to custom values before, please adapt them before calling deleteCookie().

4.6. Redirecting

Another typical use case within web applications is redirecting users to various target pages - e.g. a log-in page - or sending certain HTTP status information.

Redirecting a user to another target page you are provided forward() and redirect(). Using forward() you can initiate redirects with status code 303 (see other) whereas redirect() returns status code 301 or 302 respectively.

The following code block shows some typical use cases for both methods:

PHP code
$request = $this->getRequest(); $response = $this->getResponse(); $link = LinkGenerator::generateUrl( $request->getUrl()->mergeQuery(array('param1' => '','param2' => 'new_value')) ); // Forwards to another page $response->forward($link); // Temporarily redirects to another page $response->redirect($link); // Permanently redirects to another page $response->redirect($link, true);

Both methods forward() and redirect() abort further code execution using exit() to avoid code injection (see Overview of Execution After Redirect Web Application Vulnerabilities).

To switch off the default behaviour you may want to use ResponseImpl's deactivateExitAfterForward() or activateExitAfterForward() methods to either globally switch this feature off or on:

PHP code
$response = $this->getResponse(); // Terminate request processing after redirect $response->activateExitAfterForward(); // Continue request processing after redirect (e.g. for unit tests) $response->deactivateExitAfterForward();

To deactivate abortion with a dedicated forward() or redirect() call you can apply the optional $exitAfterForward parameter set to false.

Invoking sendNotFound() and sendServerError() the client can be informed about certain error states within the application. sendNotFound() sends status code 404 to indicate the requested page cannot be found and sendServerError() sends status code 500 (server error).

4.7. Sending the response

In order to send the response to the client there are two options: directly send the Response with an echo or calling send().

Using echo you can easily send Hello World! to the client as follows:

PHP code
$response = $this->getResponse(); $response->setBody('Hello World!'); echo $response;

In case you need to control the request processing - e.g. to terminate request processing within a front controller action - it is recommended to use send():

PHP code
$response = $this->getResponse(); $response->setBody('Hello World!'); $response->send();

Given the use case that you need to manipulate the response before sending it to the client within the bootstrap file you may want to use the following code:

PHP code
$fC = Singleton::getInstance(Frontcontroller::class); $response = $fC->start('...', '...'); $version = '3.0'; $response->setBody('<!-- Software version: ' . $version . ' -->', true); $response->setHeader(new HeaderImpl('X-Software-Version', $version)); echo $response;

4.8. Debugging

Method dump() can be used to print the entire content of the response for debugging reasons. You can use it in your bootstrap for instance to display everything that is sent to the client:

PHP code
$fC = Singleton::getInstance(Frontcontroller::class); $response = $fC->start('...', '...'); echo $response->dump();
Please note that the page will potentially not be rendered correctly as HTTP headers are not delivered as with normal responses. Thus use dump() carefully and only for debugging purposes.

Comments

Do you want to add a comment to the article above, or do you want to post additional hints? So please click here. Comments already posted can be found below.
There are no comments belonging to this article.