Have you ever wondered what happens behind the scenes when you click a link or submit a form on a Magento site? How does Magento know what to do with the request, and how to generate the appropriate response?
The answer lies in the request-response cycle, which is a complex orchestration of components and processes that work together to seamlessly deliver some output to the user's browser.
Understanding the request-response cycle is crucial for Magento developers, as it forms the foundation of how Magento works. By knowing what happens at each step of the process, you can better diagnose issues, optimize performance, and create custom extensions that integrate seamlessly with the architecture.
Note that we will get a bit detailed here, as we’re diving into some of the inner workings of Magento, including URL rewrites, dependency injection, and design patterns such as plugins and event observers. If you're a bit newer to Magento, some of these concepts may sound a bit foreign to you, but don't worry - I'll try to break things down into simple and easy-to-understand terms.
And if you're an experienced Magento dev, you may think you already know everything there is to know about the request-response cycle… but there is always more to learn! We'll explore a couple advanced topics and edge cases that you may not have encountered before, and share some tips on how to better optimize your Magento code.
And if any of this sounds overly complex, just remember that many architects and engineers have painstakingly diagrammed and plotted out how this entire process would work before any of the code was written. Magento was built to be extensible and flexible, so while it does have some inherent complexity, you also pick up much of the power of the framework because of the way the code has been structured.
If you’re ready… let’s get started from where it all starts: the web browser.
ℹ️ Requests can also start from executing a command line or making an API request. But in this article, we’ll be focusing solely on requests initiated from the web browser.
The Web Browser Request
When you open your favorite web browser, type in a URL, or click on a link, …you're setting off a chain of events. This ultimately leads to the display of some output on your screen.
But if you’ve ever stopped to think about what’s happening behind the scenes, it’s… a lot. But everything starts with that initial request.
When you enter a URL like https://www.example.com/products/awesome-widget
into your browser's address bar and submit it, it kicks off the process. The browser takes that URL and uses it to craft an HTTP request, that is then sent to a server. This request is made up of a few components.
The first part of this request is the HTTP method. This is usually GET
for standard web requests, or POST
when submitting a form post. The request also includes not only the URL, but the headers, which are sort of like metadata linked to the request.
These headers contain information about the request such as the type of browser that initiated the request, whether it was referred from another site, and the type of data that is being passed along with it.
The SSL Handshake
One interesting thing to note is that the request URL doesn't always exactly match the URL that was entered in the browser. If you're using HTTPS (which just about every request nowadays does by default), the browser will first try to establish a secure connection with the backend server.
This involves a process called the TLS handshake, which is when the browser and server talk to each other to exchange information. The cryptographic keys are linked, which encrypt any data that is sent between the browser and the server.
Once a secure connection is established, the browser then sends the actual request for the page. You may be thinking, “the server can now just send a request back, cool!”, but this is just the first step in the process.
Internet Randomness
When your request is sent, it needs to travel through many different servers, hopping over from one network and server to the other, until it reaches the correct server. Along the trip, it may come across proxy servers, firewalls, or other sorts of obstacles that can affect its path.
It’s like the digital version of Oregon Trail, except instead of dysentery, the request has to worry about network congestion and dropped packets.
But let's assume our request survives the journey and makes it to the final destination. That server then acts as a sort of dispatcher. It sorts through all incoming requests, including the one we just made, and figures out how to handle them.
In the case of Magento, the first stop of request is the web server, which is usually either Nginx or Apache (though it could be any other web server that also supports PHP). The web server acts as a greeter which then decides what to do with the request.
Most Magento sites these days use Nginx as their web server, as it is more performant, scalable, and easier to configure than Apache. We will assume this is the case with our journey.
Nginx: The First Point of Contact
So, our little HTTP request made its way across the internet and arrived at the web server. This is usually it’s first point of contact that acts as the gatekeeper to determine where to send the request.
In the world of Magento, Nginx is often used as a reverse proxy, which is a server that sits in front of the actual Magento instance and handles incoming requests.
Server Blocks
When Nginx receives the request, it looks at the server configuration to determine how to process it. The config expects a server block to be defined in this confuguration, which acts as a virtual host.
Back when I started coding, the concept of virtual hosts didn’t exist, and there could only be one website defined for one server. Since then, virtual hosts were created, which allow you to host multiple websites on a single server, which saves a lot of time and money so you don’t need to stand up a separate web server for each website.
Each of these server blocks has its own related configuration values, which tells Nginx how to handle requests for the related site.
Here’s the configuration used in my docker-magento project:
upstream fastcgi_backend {
server unix:/sock/docker.sock;
}
server {
listen 8000;
return 301 https://$host$request_uri;
}
server {
listen [::]:8443 ssl http2 ipv6only=on;
listen 8443 ssl http2;
ssl_certificate /etc/nginx/certs/nginx.crt;
ssl_certificate_key /etc/nginx/certs/nginx.key;
set $MAGE_ROOT /var/www/html;
fastcgi_buffer_size 64k;
fastcgi_buffers 8 128k;
location /livereload.js {
proxy_set_header Host $host;
proxy_pass http://phpfpm:35729/livereload.js;
}
location /livereload {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_pass http://phpfpm:35729/livereload;
}
include /var/www/html/nginx[.]conf;
}
Nginx matches the incoming request against one of these server blocks based on the server name, and it then listens on the specified port. Once it has matched a request to a server block, it then starts processing it according to the rules defined in the configuration.
Magento's Nginx Configuration
Note that the last line in that configuration loads any files within our project location, /var/www/html
, that start with nginx.conf
, which includes the nginx.conf.sample
file included in Magento (simplified below for brevity):
## ...
root $MAGE_ROOT/pub;
index index.php;
autoindex off;
charset UTF-8;
error_page 404 403 = /errors/404.php;
#add_header "X-UA-Compatible" "IE=Edge";
## ...
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location /pub/ {
location ~ ^/pub/media/(downloadable|customer|import|custom_options|theme_customization/.*\.xml) {
deny all;
}
alias $MAGE_ROOT/pub/;
add_header X-Frame-Options "SAMEORIGIN";
}
##...
The location /
block contains the line: try_files $uri $uri/ /index.php$is_args$args;
, which passes all requests to the index.php file, and then URL rewriting takes over, which is where things start to get interesting 🤓
URL Rewrites
Nginx comes with a built-in rewrite engine that is very powerful, and allows you to manipulate the requesting URL. This allows you to carry out “transforms”, such as rewrites.
A common rewrite in Magento is to remove the index.php
part of the URL. By default, Magento URLs include index.php
in the URL, like so:
https://www.example.com/index.php/products/awesome-widget
But with a rewrite rule, Nginx is able to strip out the index.php
part to make this into a “friendly” URL, such as https://www.example.com/products/awesome-widget
.
Nginx uses regular expressions to match URL patterns to transforms, sort of like a “Find and Replace”. But it can also carry out other actions on the request, such as compressing a response with gzip to speed up files transferring over the network, or serving static files like images and CSS, which bypasses Magento application code entirely.
After Nginx processes the request, it passes the baton over to the next service: PHP-FPM. This allows the web server to communicate with PHP. Nginx sends the requesting URL, method, and headers to PHP-FPM, takes that information, and then executes it in Magento, which then generates a response to return back to the user with Nginx.
Request Routing in Magento 2
Once the Magento app is bootstrapped, a fully initialized Magento app is ready to process the request. But you’re probably wondering… how does Magento know what to do with the request?
That’s where routing comes into play 😄
The Front Controller
In apps, routing is usually handled by what’s known as a “front controller”, which is the first place of entry for a request. Magento is no different. The front controller is responsible for taking the incoming request and figuring out how to handle it.
In Magento, the front controller class is located at Magento\Framework\App\FrontController
. This file is the brains behind the routing process. When the front controller receives a request, it works with some other components to determine the route that is responsible for handling it.
The dispatch
method of the FrontController
class is the part of code which executes an action to generate a response:
This code loops through all of the available routers and tries to match the request to a router. In Magento, the standard
router is the main router that will check for a related controller action, which will then handle the request.
To learn more about custom routers and advanced routing, check out the Magento 2 Router Wizardry course, which goes into this into extreme detail.
The Request Object
A key player in the routing process is the request object, which is an instance of Magento\Framework\App\Request\Http
. The request object contains information about the incoming request, including the URL, query params, HTTP headers, and more.
This class contains many helper functions which store information about the request, return full request names, and so on:
URL Rewrites
Before the front controller can determine the route, the request goes through the URL rewrite process within Magento’s application layer (which is another process that is a bit different from the one handled by Nginx). Remember that URL rewrites can transform the requested URL into something else, such as creating SEO-friendly URLs, or redirecting an old URL to a new one.
In Magento 2, the URL rewrite process is handled by the Magento\UrlRewrite\Controller\Router
class, and is processed before the standard router.
This URL rewrite router class has a match
method that takes the current request object as a parameter. This method is called by the front controller during the routing process, which then determines if the current requesting URL matches any of the available URL rewrites.
Here is that match
method from this router class:
In this method, the router tries to find a matching URL rewrite for the current request. If a match is found, it checks if the rewrite is a redirect and if so, the router processes the redirect and then returns a redirect response.
If it is not a redirect, the router modifies the request with the alias and rewrite target, and then continues processing it in Magento through a forward action.
This router class is configured in the etc/frontend/di.xml
file of the Magento_UrlRewrite
module:
As you can see, the Magento\UrlRewrite\Controller\Router
class has a sort order of 20.
The Magento Store module’s etc/frontend/di.xml
file also adds some additional routers to this list, including the standard router and default router, with the sort orders of 30 and 100, respectively:
This means that the UrlRewrite router (with the sort order of 20) is processed before the standard router, and that the Magento\Framework\App\Router\DefaultRouter
class with a sort order of 100 catches the request if it isn’t matched to any other routers.
Route Matching
After the URL rewrite process is complete, the front controller takes the modified request and then starts the process of matching it to a route. This is completed in the standard
router class (Magento\Framework\App\Router\Base
).
The standard router’s match
method parses the params, and then calls matchAction
:
…and then matchAction
loops through all of the available modules, and attempts to match the request to a controller action:
This is where the Magento\Framework\App\Router\ActionList
class comes into play. The action list is like a directory of all available controller actions in Magento.
Here's a simplified version of the ActionList
class:
The get
method takes the module name and action path, and tries to find the corresponding controller action class. If a match is found, it then creates an instance of the action class in the validateActionClass
method using the action factory.
The front controller (Magento\Framework\App\FrontController
) loops through the action list, comparing the requested URL against all of the defined routes. It's a game of "match the pattern," where the front controller tries to find the best match for the current request.
The Anatomy of a Magento URL
Each route in Magento 2 is defined using a combination of the front name, controller name, and action name. For example, a route for the product view page looks like catalog/product/view
. The front controller uses this information to determine which controller action will handle the request.
ℹ️ As an aside, sign up for Snippets to get a Magento code snippet like this in your inbox, one every week, for 6+ months! (unsubscribe anytime)
Dispatching the Request
Once a match is found, the front controller dispatches the request to the appropriate controller action. The controller action is responsible for processing the request, and then generating the response.
Here's an example of a very simple controller action class, which returns a Page
object:
The execute
method is where the action happens. It's responsible for processing the request, interacting with any models or services that it needs to, and then generating an appropriate response.
The “No Route” Handlers
If no match is found during the routing process, the front controller delegates the request to the “no route” handlers. These handlers act as a safety net that catch any requests that weren’t able to get matched to another router.
Magento 2 has separate no route handlers for the frontend and backend areas:
Frontend No Route Handler
Within the frontend no route handler’s process
method, it gets the configuration value of web/default/no_route
, which is declared in vendor/magento/module-backend/etc/adminhtml/system.xml
.
It then sends the request to that route for processing:
Backend No Route Handler
Similarly, the backend no route handler works in basically the same way, catching requests that fail to match a backend within a process
method. But rather than having the no route path defined within a system configuration value, it is hard-coded to adminhtml/noroute/index
:
CMS No Route Handler
CMS pages don’t officially have a “no route” handler, however there is a fallback mechanism defined that is important to mention. The CMS router (Magento\Cms\Controller\Router
) is responsible for handling routes specific for CMS pages.
This router is defined in the etc/frontend/di.xml
file of the Magento_Cms
module:
The CMS router checks if the request URL matches any CMS pages and, if a match is found, dispatches the request to the appropriate CMS controller action (for more info about this, see the match
method of this class).
The CMS module has a related fallback defined for non-matching routes. The “CMS No-Route Page” is defined in the web/default/cms_no_route
configuration setting declared in vendor/magento/module-cms/etc/adminhtml/system.xml
.
This is the route that will handle the request if no matching CMS pages are found.
Summary of the Routing Process
Before we close this article out, let’s look at a brief overview again of exactly what happens in Magento 2’s routing process:
- The web browser initiates an HTTP request, which includes the URL, HTTP method, headers, and other metadata.
- The request travels through the internet and reaches the web server, which is typically Nginx acting as a reverse proxy.
- Nginx processes the request based on the server configuration, including URL rewriting rules, and then passes the request to PHP-FPM.
- In Magento 2, the front controller (
Magento\Framework\App\FrontController
) is responsible for routing the request to the appropriate controller action. - The front controller first checks the URL rewrite rules handled by the
Magento\UrlRewrite\Controller\Router
class. - If no URL rewrite is found, the front controller then uses the
Magento\Framework\App\Router\Base
(the "standard" router) to match the request to a controller action. - The standard router looks up the available controller actions using the
Magento\Framework\App\Router\ActionList
class and creates an instance of the appropriate controller action. - The front controller then dispatches the request to the controller action, which processes the request and generates the response.
- If no matching route is found, the "no route" handlers (
Magento\Framework\App\Router\NoRouteHandler
andMagento\Backend\App\Router\NoRouteHandler
) take over to handle the request. - The generated response is then sent back through Nginx, which handles the response headers, caching, and delivery to the web browser.
Routing is the post critical aspect of Magento’s request-response lifecycle. It’s the process that determines which controller action handles the request, which leads to generating a response.
I hope this article was useful to you!
What’s next?
There are a few other actions that get carried out which can modify the response before it is returned to the browser. Be on the lookout for a subsequent article that goes over this process!
Until then, you may like my How To Build a Custom Router in Magento 2 for Dashes video on YouTube.
Building a custom router can be useful if you want Magento to do something that it does not do out of the box. In this case, it was handling special characters within URLs:
This article could have been a lot to take in. If it was, consider taking one of our courses to deep-dive into how everything in Magento works:
- Jumpstart into Magento free course (13,000+ students)
- Magento 2 advanced routing premium course (700+ students)
- Learn visually with blocks of code & inline comments (3,000+ students)