Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

proof of concept: nginx + luajit + zmq = zeroconf router

  • Loading branch information...
commit 19b276f9a28a62559468a808164ab6c13084f0d2 1 parent 9f4ca71
Ilya Grigorik authored
42 nginx/README.md
Source Rendered
... ... @@ -0,0 +1,42 @@
  1 +# Nginx zeroconf router: via ZMQ, and back
  2 +
  3 +Proof of concept: Nginx + Luajit + ZMQ for "zeroconfig router". Meaning, when Nginx is started, our `zmqrouter.lua` module is loaded into the Nginx worker, which in turn opens an XREQ ZeroMQ socket. When a request comes in, we extract the user agent, the body of the request, push it into the XREQ socket, and then wait for the response.
  4 +
  5 +The response is served by a simple ruby worker which accepts ZeroMQ messages and sends an echo response back - this, of course, could be implemented in any language you like.
  6 +
  7 +## Getting started
  8 +
  9 +- Install [ZeroMQ](http://www.zeromq.org/intro:get-the-software)
  10 +- Install [super-nginx build with luajit](https://github.com/ezmobius/super-nginx)
  11 +- Start nginx: `/path/to/super-nginx/sbin/nginx -c /zeroconf-router/nginx/nginx.conf`
  12 +- Start worker: `ruby appserver.rb` (as many as you like... :-))
  13 +- Issue a request to nginx: `curl -v localhost:3000/zmq -d "some post body"`
  14 +
  15 +```
  16 +* About to connect() to localhost port 3000 (#0)
  17 +* Trying 127.0.0.1... connected
  18 +* Connected to localhost (127.0.0.1) port 3000 (#0)
  19 +> POST /zmq HTTP/1.1
  20 +> User-Agent: curl/7.19.7 (i386-apple-darwin10.4.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3
  21 +> Host: localhost:3000
  22 +> Accept: */*
  23 +> Content-Length: 19
  24 +> Content-Type: application/x-www-form-urlencoded
  25 +>
  26 +< HTTP/1.1 200 OK
  27 +< Server: nginx/0.8.54
  28 +< Date: Sat, 07 May 2011 16:51:24 GMT
  29 +< Content-Type: text/plain
  30 +< Transfer-Encoding: chunked
  31 +< Connection: keep-alive
  32 +<
  33 +ruby echo: curl/7.19.7 (i386-apple-darwin10.4.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3 > sending a post body
  34 +* Connection #0 to host localhost left intact
  35 +* Closing connection #0
  36 +```
  37 +
  38 +Voila! Nginx parsed the request, pushed it to one of our Ruby workers via ZMQ and returned the response back to the client.
  39 +
  40 +## Notes
  41 +
  42 +This is a minimal proof of concept at best - there is a number of issues with the above example. First, the ZMQ socket will block the nginx reactor as implemented: it needs to be integrated into the nginx loop. Further, once you integrate the ZMQ socket into the run loop you'll have to keep track of all the incoming streams and respond to the correct client (not to mention properly handle the header relay, etc).
24 nginx/appserver.rb
... ... @@ -0,0 +1,24 @@
  1 +require 'ffi-rzmq'
  2 +
  3 +link = 'tcp://127.0.0.1:5555'
  4 +
  5 +ctx = ZMQ::Context.new 1
  6 +s1 = ctx.socket ZMQ::XREP
  7 +
  8 +s1.connect(link)
  9 +
  10 +puts "Worker connected to: #{link}"
  11 +
  12 +loop do
  13 + id = s1.recv_string
  14 + sp = s1.recv_string
  15 + bd = s1.recv_string
  16 +
  17 + puts "Received from Nginx: [#{bd}]"
  18 +
  19 + s1.send_string id, ZMQ::SNDMORE
  20 + s1.send_string '', ZMQ::SNDMORE
  21 + s1.send_string ['ruby echo:', bd].join(' ')
  22 +
  23 + puts "Response sent"
  24 +end
44 nginx/nginx.conf
... ... @@ -0,0 +1,44 @@
  1 +#
  2 +# Requires super-nginx build with luajit: https://github.com/ezmobius/super-nginx
  3 +#
  4 +
  5 +worker_processes 1;
  6 +error_log logs/error.log;
  7 +
  8 +events {
  9 + worker_connections 1024;
  10 +}
  11 +
  12 +http {
  13 + server {
  14 + listen 3000;
  15 + server_name localhost;
  16 +
  17 + location /zmq {
  18 + default_type 'text/plain';
  19 +
  20 + # force reading request body
  21 + lua_need_request_body on;
  22 + client_max_body_size 100k;
  23 + client_body_in_single_buffer on;
  24 +
  25 + content_by_lua '
  26 + -- modules are loaded once into each worker process and are shared by all
  27 + -- subsequent requests, hence we can share a single ZMQ socket
  28 + local router = require("zmqrouter")
  29 +
  30 + -- forward the user agent + request body of the request to our handler
  31 + router.send(ngx.var.http_user_agent .. " > " .. ngx.var.request_body)
  32 +
  33 + -- wait to hear a response from one of our downstream ZMQ workers
  34 + --
  35 + -- WARNING: this is a terrible way to do it.. since we are blocking
  36 + -- the nginx reactor while we are waiting for the response. it is a
  37 + -- working proof of concept.. but the ZMQ socket needs to be integrated
  38 + -- into nginx epoll loop
  39 +
  40 + ngx.say(router.response())
  41 + ';
  42 + }
  43 + }
  44 +}
17 nginx/zmqrouter.lua
... ... @@ -0,0 +1,17 @@
  1 +module("zmqrouter", package.seeall)
  2 +require("zmq")
  3 +
  4 +local ctx = zmq.init(1)
  5 +local s = ctx:socket(zmq.XREQ)
  6 +
  7 +s:bind("tcp://127.0.0.1:5555")
  8 +
  9 +function send(data)
  10 + s:send("", zmq.SNDMORE)
  11 + s:send(data)
  12 +end
  13 +
  14 +function response()
  15 + s:recv() -- separator
  16 + return s:recv() -- response
  17 +end

0 comments on commit 19b276f

Please sign in to comment.
Something went wrong with that request. Please try again.