Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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

  • Loading branch information...
commit 19b276f9a28a62559468a808164ab6c13084f0d2 1 parent 9f4ca71
Ilya Grigorik authored
42 nginx/README.md
View
@@ -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).
24 nginx/appserver.rb
View
@@ -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
44 nginx/nginx.conf
View
@@ -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())
+ ';
+ }
+ }
+}
17 nginx/zmqrouter.lua
View
@@ -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
Please sign in to comment.
Something went wrong with that request. Please try again.