Browse files

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

  • Loading branch information...
1 parent 9f4ca71 commit 19b276f9a28a62559468a808164ab6c13084f0d2 @igrigorik committed May 7, 2011
Showing with 127 additions and 0 deletions.
  1. +42 −0 nginx/README.md
  2. +24 −0 nginx/appserver.rb
  3. +44 −0 nginx/nginx.conf
  4. +17 −0 nginx/zmqrouter.lua
View
42 nginx/README.md
@@ -0,0 +1,42 @@
+# Nginx zeroconf router: via ZMQ, and back
+
+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.
+
+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.
+
+## Getting started
+
+- Install [ZeroMQ](http://www.zeromq.org/intro:get-the-software)
+- Install [super-nginx build with luajit](https://github.com/ezmobius/super-nginx)
+- Start nginx: `/path/to/super-nginx/sbin/nginx -c /zeroconf-router/nginx/nginx.conf`
+- Start worker: `ruby appserver.rb` (as many as you like... :-))
+- Issue a request to nginx: `curl -v localhost:3000/zmq -d "some post body"`
+
+```
+* About to connect() to localhost port 3000 (#0)
+* Trying 127.0.0.1... connected
+* Connected to localhost (127.0.0.1) port 3000 (#0)
+> POST /zmq HTTP/1.1
+> User-Agent: curl/7.19.7 (i386-apple-darwin10.4.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3
+> Host: localhost:3000
+> Accept: */*
+> Content-Length: 19
+> Content-Type: application/x-www-form-urlencoded
+>
+< HTTP/1.1 200 OK
+< Server: nginx/0.8.54
+< Date: Sat, 07 May 2011 16:51:24 GMT
+< Content-Type: text/plain
+< Transfer-Encoding: chunked
+< Connection: keep-alive
+<
+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
+* Connection #0 to host localhost left intact
+* Closing connection #0
+```
+
+Voila! Nginx parsed the request, pushed it to one of our Ruby workers via ZMQ and returned the response back to the client.
+
+## Notes
+
+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).
View
24 nginx/appserver.rb
@@ -0,0 +1,24 @@
+require 'ffi-rzmq'
+
+link = 'tcp://127.0.0.1:5555'
+
+ctx = ZMQ::Context.new 1
+s1 = ctx.socket ZMQ::XREP
+
+s1.connect(link)
+
+puts "Worker connected to: #{link}"
+
+loop do
+ id = s1.recv_string
+ sp = s1.recv_string
+ bd = s1.recv_string
+
+ puts "Received from Nginx: [#{bd}]"
+
+ s1.send_string id, ZMQ::SNDMORE
+ s1.send_string '', ZMQ::SNDMORE
+ s1.send_string ['ruby echo:', bd].join(' ')
+
+ puts "Response sent"
+end
View
44 nginx/nginx.conf
@@ -0,0 +1,44 @@
+#
+# Requires super-nginx build with luajit: https://github.com/ezmobius/super-nginx
+#
+
+worker_processes 1;
+error_log logs/error.log;
+
+events {
+ worker_connections 1024;
+}
+
+http {
+ server {
+ listen 3000;
+ server_name localhost;
+
+ location /zmq {
+ default_type 'text/plain';
+
+ # force reading request body
+ lua_need_request_body on;
+ client_max_body_size 100k;
+ client_body_in_single_buffer on;
+
+ content_by_lua '
+ -- modules are loaded once into each worker process and are shared by all
+ -- subsequent requests, hence we can share a single ZMQ socket
+ local router = require("zmqrouter")
+
+ -- forward the user agent + request body of the request to our handler
+ router.send(ngx.var.http_user_agent .. " > " .. ngx.var.request_body)
+
+ -- wait to hear a response from one of our downstream ZMQ workers
+ --
+ -- WARNING: this is a terrible way to do it.. since we are blocking
+ -- the nginx reactor while we are waiting for the response. it is a
+ -- working proof of concept.. but the ZMQ socket needs to be integrated
+ -- into nginx epoll loop
+
+ ngx.say(router.response())
+ ';
+ }
+ }
+}
View
17 nginx/zmqrouter.lua
@@ -0,0 +1,17 @@
+module("zmqrouter", package.seeall)
+require("zmq")
+
+local ctx = zmq.init(1)
+local s = ctx:socket(zmq.XREQ)
+
+s:bind("tcp://127.0.0.1:5555")
+
+function send(data)
+ s:send("", zmq.SNDMORE)
+ s:send(data)
+end
+
+function response()
+ s:recv() -- separator
+ return s:recv() -- response
+end

0 comments on commit 19b276f

Please sign in to comment.