Welcome back to our "Extending NGINX with Lua and OpenResty" course! In the previous lesson, we learned how to embed Lua code in NGINX configuration and generate dynamic responses. Now, in this second lesson, we'll take the next step and explore how to manipulate HTTP traffic itself: reading and modifying headers, working with query parameters, implementing conditional routing, and controlling response headers. These capabilities transform NGINX into a powerful traffic management tool that can inspect and modify requests on the fly.
As you may recall from the previous lesson, OpenResty gives us access to different phases of request processing. Now we'll leverage this to not just read request information, but actively modify it. This opens up possibilities such as:
- Adding authentication tokens based on query parameters
- Routing requests to different endpoints based on client characteristics
- Enriching requests with custom headers before they reach backend services
- Controlling cache behavior and security headers in responses
The key insight is that HTTP requests and responses are just structured data. With Lua, we can inspect this data, make decisions based on it, and transform it before it continues its journey. This gives us fine-grained control over how our server handles traffic.
Let's start by examining how to read incoming request headers. OpenResty provides the ngx.req.get_headers() function, which returns a Lua table containing all headers:
Here's what happens in this code:
ngx.req.get_headers(): Returns a Lua table snapshot of the incoming headers. Editing this table won’t change the actual request headers; usengx.req.set_header()for that. We copy into a new table mainly to normalize/filter the data (e.g., for JSON output).- We iterate through all headers using
pairs()and copy them to a new table require "cjson": Loads the JSON encoding librarycjson.encode(result): Converts our Lua table to a JSON string
This endpoint will return all request headers as JSON, making it easy to inspect what the client sent. Note that header names are automatically converted to lowercase for consistency.
Now let's see something more powerful: modifying request headers before they're processed. The access_by_lua_block phase is perfect for this because it runs before content generation:
This demonstrates header modification:
ngx.req.set_header(): Sets or overwrites a request header with the specified value- The modified headers persist throughout the request lifecycle
- In the content phase, we can see our modifications reflected when we read the headers again
This pattern is useful for adding metadata, tracking information, or authentication tokens that downstream systems can use.
Query parameters are another important part of requests. OpenResty provides ngx.req.get_uri_args() to parse the query string into a Lua table:
This code combines parameter extraction with header modification:
ngx.req.get_uri_args(): Returns a table where keys are parameter names and values are parameter values- We check if a
tokenparameter exists using simple table access ..is Lua's string concatenation operator- If present, we convert it into a standard Authorization header
This is a practical pattern for accepting authentication tokens as query parameters and converting them to proper HTTP headers before processing continues.
One powerful feature is internal routing based on request attributes. We can inspect the request and redirect it to different handlers without involving the client:
Let's break down this routing logic:
ngx.var.http_user_agent: Accesses the User-Agent header through NGINX variablesor "": Provides a default empty string if the header doesn't existstring.match(): Performs pattern matching on the user agent string:lower(): Converts the string to lowercase for case-insensitive matchingngx.exec(): Performs an internal redirect to another location
The ngx.exec() function is key here: it transfers control to a different location block within the same server, preserving the original request context. This happens entirely server-side without any client interaction.
For our routing to work, we need to define the destination locations:
These simple locations demonstrate the routing outcome. In a real application, each would handle requests differently: perhaps /mobile returns simplified responses, while /v2 uses a newer API format. The important point is that a single entry point /route intelligently directs traffic based on request characteristics.
So far, we've focused on request manipulation. We can also modify response headers using the header_filter_by_lua_block phase, which runs after the response is generated but before it's sent to the client:
Response header manipulation works through the ngx.header table:
- Setting
ngx.header["Header-Name"]adds or modifies a response header - We calculate response time by subtracting the request start time from the current time
- Common headers like
Cache-Controlcan be set to control browser and proxy behavior - Setting a header to
nilwould remove it from the response
This phase is particularly useful for adding security headers, cache directives, or custom metadata that clients need to understand how to handle the response.
The techniques we've covered enable several real-world use cases:
- API Gateway Pattern: Accept tokens in various formats (query params, custom headers) and normalize them into standard Authorization headers
- A/B Testing: Route requests to different backend versions based on user segments or feature flags
- Mobile Optimization: Detect mobile devices and serve optimized content
- Security Enhancement: Add security headers like CORS, CSP, or HSTS to all responses
- Performance Monitoring: Calculate and log request processing times
The combination of reading, modifying, and routing based on request attributes makes OpenResty a powerful traffic management layer.
We've explored how to manipulate HTTP traffic using Lua in OpenResty. We learned to read request headers with ngx.req.get_headers(), modify them with ngx.req.set_header(), work with query parameters through ngx.req.get_uri_args(), implement intelligent routing with ngx.exec(), and control response headers via header_filter_by_lua_block. These capabilities let us build sophisticated traffic management logic directly in NGINX, transforming it from a simple web server into a programmable gateway.
Time to get hands-on! The upcoming exercises will challenge you to build your own request and response manipulation logic, putting these powerful techniques to work in real scenarios.
