Search code examples
nginxargumentsuri

nginx rewrite of arguments


I have an nginx load balancer handling http web requests to some backend servers. Due to some legacy reasons the backend servers cannot be changed and neither can the clients making the requests. The http requests contain arguments. I have a set of special characters (which are case sensitive) say S = ["Q", "A", "B", "C"] and these can appear in any order in a particular argument (lets call it "foo") any number of times. I want to replace each of those chars with a specific predesignated string for each character. Lets say Q gets replaced qx, A gets replaced by ay, B gets replaced by bz, C gets replaced by cz. As an example

  http:/localhost:5000/part?foo=QhelloAWorldBearthCLondon

This should transform to:

  http://localhost:5000/part?foo=qxhelloayWorldbzearthczLondon. 

Is there a way to do this via a rewrite from nginx? I only want the foo argument to be transformed, while other arguments are untouched. Tried a recursive approach but that gets rejected after 10 updates as nginx doesn't allow more than that.


Solution

  • Nginx maps can be cascaded, which means that the problem can be broken down into manageable pieces.

    No solution will give you an unlimited number of transformations, but the solution below is extensible so you can set a reasonable upper limit.

    For example:

    map $arg_foo $newfoo1 {
        ~^(?<a1>.*)A(?<a2>.*)A(?<a3>.*)A(?<a4>.*)$  ${a1}ax${a2}ax${a3}ax${a4};
        ~^(?<a1>.*)A(?<a2>.*)A(?<a3>.*)$            ${a1}ax${a2}ax${a3};
        ~^(?<a1>.*)A(?<a2>.*)$                      ${a1}ax${a2};
        default     $arg_foo;
    }
    
    map $newfoo1 $newfoo2 {
        ~^(?<b1>.*)B(?<b2>.*)B(?<b3>.*)B(?<b4>.*)$  ${b1}bx${b2}bx${b3}bx${b4};
        ~^(?<b1>.*)B(?<b2>.*)B(?<b3>.*)$            ${b1}bx${b2}bx${b3};
        ~^(?<b1>.*)B(?<b2>.*)$                      ${b1}bx${b2};
        default     $newfoo1;
    }
    
    map $newfoo2 $newfoo3 {
        ~^(?<c1>.*)C(?<c2>.*)C(?<c3>.*)C(?<c4>.*)$  ${c1}cx${c2}cx${c3}cx${c4};
        ~^(?<c1>.*)C(?<c2>.*)C(?<c3>.*)$            ${c1}cx${c2}cx${c3};
        ~^(?<c1>.*)C(?<c2>.*)$                      ${c1}cx${c2};
        default     $newfoo2;
    }
    
    map $newfoo3 $newfoo {
        ~^(?<d1>.*)Q(?<d2>.*)Q(?<d3>.*)Q(?<d4>.*)$  ${d1}qx${d2}qx${d3}qx${d4};
        ~^(?<d1>.*)Q(?<d2>.*)Q(?<d3>.*)$            ${d1}qx${d2}qx${d3};
        ~^(?<d1>.*)Q(?<d2>.*)$                      ${d1}qx${d2};
        default     $newfoo3;
    }
    
    map $args $newargs {
        default     $args;
        ~^(?<e1>(?:.+&|)foo=)[^&]+(?<e2>&.*)?$  $e1$newfoo$e2;
    }
    
    server {
        ...
        location /part {
            rewrite ^ $uri?$newargs? break;
            proxy_pass ...;
        }
    }
    

    Starting from the bottom, the fifth map replaces foo=x with foo=y without changing any other arguments (if any). The value of this map can be used to replace the arguments in the requested URL. In the example above, we use rewrite...break.

    The other maps each replace one or more occurrences of a single pattern. In the example, the maximum number of occurrences is three, but could easily be extended to any reasonable number.

    All variable names used for the maps and the captures should be unique to avoid inadvertently overwriting a value. Numeric captures should be avoided as the value is lost when the next regular expression is evaluated.