nginx redis lua - record post params
Generally with payment gateways, we receive an http request to confirm a
transaction. This transaction is important and the http requests must be
processed. For example, we want our stack to stay available during a
deployment.
Our goal is to record the payload received from a payment pingback in a Redis
queue, without involving the application code.
nginx allows lua scripting with thirdparty modules
lua-nginx-module.
For Redis we will use the
lua-resty-redis module.
If you are on a Debian system and don't want to recompile nginx you can use the
nginx-extras
and lua-nginx-redis
packages.
aptitude install redis nginx-extras lua-nginx-redis lua-json
Then we can create a server with this nginx config:
<span class="c1"># on Debian the lua-nginx-redis is installed here</span><span class="k">lua_package_path</span> <span class="s">"/usr/share/lua/5.1/nginx/?.lua</span><span class="p">;</span>;<span class="k">"</span><span class="p">;</span><span class="k">server</span> <span class="p">{</span> <span class="kn">server_name</span> <span class="s">payment.local</span><span class="p">;</span> <span class="kn">location</span> <span class="s">/</span> <span class="p">{</span> <span class="kn">content_by_lua</span> <span class="s">'</span><span class="s">--</span> <span class="s">connect</span> <span class="s">to</span> <span class="s">redis</span><span class="s">local</span> <span class="s">redis</span> <span class="p">=</span> <span class="s">require</span> <span class="s">"redis"</span><span class="s">local</span> <span class="s">json</span> <span class="p">=</span> <span class="s">require</span> <span class="s">"json"</span><span class="s">local</span> <span class="s">red</span> <span class="p">=</span> <span class="s">redis:new()</span><span class="s">red:set_timeout(1000)</span> <span class="s">--</span> <span class="mi">1</span> <span class="s">sec</span><span class="s">local</span> <span class="s">ok,</span> <span class="s">err</span> <span class="p">=</span> <span class="s">red:connect("127.0.0.1",</span> <span class="mi">6379</span><span class="s">)</span><span class="s">if</span> <span class="s">not</span> <span class="s">ok</span> <span class="s">then</span> <span class="s">ngx.say("failed</span> <span class="s">to</span> <span class="s">connect:</span> <span class="s">",</span> <span class="s">err)</span> <span class="s">return</span><span class="s">end</span><span class="s">--</span> <span class="s">read</span> <span class="s">post</span> <span class="s">data</span><span class="s">ngx.req.read_body()</span><span class="s">local</span> <span class="s">args</span> <span class="p">=</span> <span class="s">ngx.req.get_post_args()</span><span class="s">if</span> <span class="s">not</span> <span class="s">args</span> <span class="s">then</span> <span class="s">ngx.say("failed</span> <span class="s">to</span> <span class="s">get</span> <span class="s">post</span> <span class="s">args:</span> <span class="s">",</span> <span class="s">err)</span> <span class="s">return</span><span class="s">end</span><span class="s">local</span> <span class="s">args_to_redis</span> <span class="p">=</span> <span class="s">json.encode(args)</span><span class="s">--</span> <span class="s">record</span> <span class="s">it</span> <span class="s">to</span> <span class="s">redis</span><span class="s">local</span> <span class="s">ans,</span> <span class="s">err</span> <span class="p">=</span> <span class="s">red:lpush("payment",</span> <span class="s">args_to_redis)</span><span class="s">if</span> <span class="s">not</span> <span class="s">ans</span> <span class="s">then</span> <span class="s">ngx.say("failed</span> <span class="s">to</span> <span class="s">run</span> <span class="s">rpush:</span> <span class="s">",</span> <span class="s">err)</span> <span class="s">return</span><span class="s">end</span><span class="s">red:close()</span><span class="s">'</span><span class="p">;</span> <span class="p">}</span><span class="p">}</span>
Once nginx is reloaded, it can be tested with this curl command:
curl -v -XPOST <span class="se">\</span> -d <span class="s2">"TPE=1234567&date=05%2f12%2f2006%5fa%5f11%3a55%3a23&montant=62%2e75EUR&reference=ABERTYP00145&MAC=e4359a2c18d86cf2e4b0e646016c202e89947b04&texte-libre=LeTexteLibre&code-retour=payetest&cvx=oui&vld=1208&brand=VI&status3ds=1&numauto=010101&originecb=FRA&bincb=010101&hpancb=74E94B03C22D786E0F2C2CADBFC1C00B004B7C45&ipclient=127%2e0%2e0%2e1&originetr=FRA&veres=Y&pares=Y"</span> <span class="se">\</span> <span class="s2">"http://payment.local/"</span>
We can get the params, for example, on a Ruby script that parses the recorded
post from nginx.
<span class="nb">require</span> <span class="s1">'redis'</span><span class="nb">require</span> <span class="s1">'json'</span><span class="n">redis</span> <span class="o">=</span> <span class="no">Redis</span><span class="o">.</span><span class="n">new</span><span class="n">list</span><span class="p">,</span> <span class="n">element</span> <span class="o">=</span> <span class="n">redis</span><span class="o">.</span><span class="n">blpop</span><span class="p">(</span><span class="s2">"payment"</span><span class="p">)</span><span class="no">JSON</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">element</span><span class="p">)</span>