Nginx processes HTTP requests in multiple phases. In each of the phases there might be 0 or more handlers called. In the Nginx source code phases have specific constants associated with them. Here is a list of all phases:
- NGX_HTTP_SERVER_REWRITE_PHASE — the phase of request URI transformation on virtual server level;
- NGX_HTTP_FIND_CONFIG_PHASE — the phase of configuration location lookup;
- NGX_HTTP_REWRITE_PHASE — the phase of request URI transformation on location level;
- NGX_HTTP_POST_REWRITE_PHASE — request URI transformation post-processing phase;
- NGX_HTTP_PREACCESS_PHASE — access restrictions check preprocessing phase;
- NGX_HTTP_ACCESS_PHASE — access restrictions check phase;
- NGX_HTTP_POST_ACCESS_PHASE — access restrictions check post-processing phase;
- NGX_HTTP_TRY_FILES_PHASE — try_files directive processing phase;
- NGX_HTTP_CONTENT_PHASE — content generation phase;
- NGX_HTTP_LOG_PHASE — logging phase.
On every phase you can register any number of your handlers. Exceptions are following phases:
- NGX_HTTP_FIND_CONFIG_PHASE. On this phase no handlers are called, instead a search for configuration location is performed and “Location” request header is filled.
-
NGX_HTTP_POST_ACCESS_PHASE. On this phase no handlers are called,
only the result of access checks is interpreted and applied. The phase is required to implement directive satisfy all/any. -
NGX_HTTP_POST_REWRITE_PHASE. On this phase no handlers are called,
instead request URI transformation post-processing is performed; -
NGX_HTTP_TRY_FILES_PHASE. On this phase no handlers are called,
instead Nginx processes the try_files directive.
Each phase has a list of handlers associated with it. Once registered on a phase, handler can return one of the following values:
- NGX_OK — the request has been successfully processed, request must be routed to the next phase;
- NGX_DECLINED — request must be routed to the next handler;
- NGX_AGAIN, NGX_DONE — the request has been successfully processed, the request must be suspended until some event (e.g., subrequest finishes, socket becomes writeable or timeout occurs) and handler must be called again;
- NGX_ERROR, NGX_HTTP_… — an error has occurred while processing the request.
In order to register a handler you need to find the configuration of ngx_http_core_module and add the handler to one of elements of phases vector. Example:
static ngx_int_t ngx_http_sample_module_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_sample_module_handler; return NGX_OK; }
The vector phases has one entry for each phase. Each entry contains a field handlers which is a vector of handlers that are registered on this phase.
Handlers are called in reverse order. Therefore the last handler registered at configuration time will be called first at runtime.
As you can see, the order of actions in request processing has nothing to do with the order of directives in configuration file. Phase handlers are called regardless of what configuration the user has specified. Hence a phase handler must be able to determine when it is applicable and return NGX_DECLINED when it is not and do it as fast as possible to avoid performance penalties.
The phase NGX_HTTP_ACCESS_PHASE calls handlers that restrict access to resources. In this phase the order in which handlers are called is determined by directive satisfy. The values, that handlers return, have additional meaning:
- NGX_OK — handler allows to access the resource specified by request URI;
- NGX_HTTP_FORBIDDEN, NGX_HTTP_UNAUTHORIZED — handler does not allow to request the resource specified by request URI.
In case of satisfy all all handlers must return NGX_OK in order to proceed with the next phase.
In case of satisfy any at least one handler must return NGX_OK in order to proceed with the next phase.
Nginx uses the phase NGX_HTTP_CONTENT_PHASE to generate a response. Whenever a location configuration of ngx_http_core_module has handler field initialized, all requests on content phase are routed to this handler. The handler that is specified by handler field is hence called content handler. When content handler is not set, request is routed to handlers of content phase in main configuration.
How to override content handler? Here is an example:
static char * ngx_http_sample_module_command(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); clcf->handler = ngx_http_sample_handler; return NGX_CONF_OK; }
The content handler has following specific properties:
- It is not resumable. That is, it’s not going to be called again if it returns NGX_AGAIN or NGX_DONE. Instead the handler must change read and/or write handlers of the request;
- Each location can have it’s own dedicated content handler;
- If content handler returns NGX_DECLINED, Nginx routes request to content phase handlers.
How do you find out which phase to put your handler on?
Although this blog is not to criticize Nginx, here seems to be a bit of a problem. According to my feelings and to Igor Sysoev comments, phases are legacy from Apache. They are not as flexible for a module developer as you would expect. Clearly this point could be improved (and it will be), but for the moment here are suggestions, that you can use in order to figure out what phase you need to put your handler on:
- Your handler needs to be manageable by satisfy directive (e.g. access restriction checks) — put it on access phase;
- Your handler does resource limitations — put it on pre-access phase;
- Your handler changes URI or manipulates variables than need to be accessible in set directive or rewrites — put it on rewrite phase;
- Your handler generates content — put it on content phase, take care of the handler registration order;
- Your handler does logging — put it on logging phase;
When you should use content handlers?
What is the difference between content phase handler and content handler?
- the content phase handler is promiscuous: it is called for every request that reaches the content phase and this particular handler. The content handler is called only for those requests that reach a location with configured content handler;
- more than one content phase handler can be called per location. Only one content handler can be called per location.
A combination of these two types of handlers are employed by nginx mogilefs module for doing PUT requests:
Main location is handled by a content phase handler. This handler has 3 stages that correspond to create_open command, storing the resource on storage node and create_close command. On each stage content phase handler does a subrequest. When subrequest finishes, it wakes up the main content phase handler. In case of create_open and create_close commands subrequest is routed to a hidden location that has a content handler set up in it. This handler implements communication with MogileFS tracker using upstream module.
A blog about nginx internals was a common dream for years
Thank you, Valery!
Two questions about NGX_AGAIN. How does the handler know which event (subrequest, socket, timer etc.) has occurred? And is there a full list of event types that leads to re-calling the phase handler?
Thank you again!
When subrequest terminates, it calls post-subrequest handler (handler field of ngx_http_post_subrequest_t). This handler receives a pointer that you have specified upon initialization of this structure. This pointer allows you to manipulate the context of the main request for example. This must be sufficient to point the phase handler to the relevant event type.
When timeout occurs, the write event r->connection->write switches to timedout state (timeout == 1).
This should be sufficient to distinguish the event type.
Now the list of events is determined by the list of events that trigger invocation of ngx_http_core_run_phases. So far I know only 3 of and you have already mentioned them. In the future there might be more (e.g. an event that signals that other worker has finished fetching some resource) and nothing stops you from implementing your own.
Thank you for your nginx guts blog!
Just noted a small nit: it’s incorrect to confuse NGX_AGAIN and NGX_DONE for the return values of a phase handler. It’s processed very different at least for the rewrite phase handlers since nginx 0.8.54, as seen in the ngx_http_core_rewrite_phase function in src/http/ngx_http_core_module.c:
if (rc == NGX_DONE) {
return NGX_OK;
}
/* NGX_OK, NGX_AGAIN, NGX_ERROR, NGX_HTTP_... */
ngx_http_finalize_request(r, rc);
return NGX_OK;
That is, NGX_DONE will trigger re-invocation of the current phase handler while NGX_AGAIN and others will not (it just terminates the request, just like the content handler). And it was also the reason why your ngx_eval module cannot work with nginx 0.8.54+(not sure if you have already fixed it )
You are correct as always, agentzh! This difference between NGX_DONE and NGX_AGAIN is worth of a new article. Perhaps you can help me with shedding light on this topic.
And the problem in ngx_eval_module is already fixed.
Terrific work! This is the type of information that should be shared around the web. Shame on the search engines for not positioning this post higher!
I’ve been looking for a Nginx Internals blog for years.
Thanks, keep it going!