Patch for ruby-fcgi

Current Ruby FastCGI module doesn’t allow you to specify port number or unix domain socket file path of connection.

I have create a patch to enable to specify port number or socket file path. If you have applied the patch, the following code is available.

require 'rubygems'
require 'fcgi'
arg = "/tmp/mysocket.sock"   # or arg = 8001   # port number
FCGI.each(arg) do |req|
  req.out.print "Content-Type: text/html\r\n"
  req.out.print "\r\n"
  req.out.print "<html><body>Hello World!</body></html>\n"
  req.finish
end

The following is a patch for ruby-fcgi/ext/fcgi/fcgi.c:

--- fcgi.c.orig 2006-06-25 13:06:42.000000000 +0900
+++ fcgi.c  2007-12-17 00:11:14.000000000 +0900
@@ -49,15 +49,50 @@
   free(data);
 }

-static VALUE fcgi_s_accept(VALUE self)
-{
+static VALUE _fcgi_s_accept(VALUE self, int socket_fd);
+
+/**
+ *  accept request.
+ *
+ *  usage:  FCGI.accept(socket=nil)
+ *    - socket:  socket (Fixnum). if nil then 0 (stdin) is used.
+ *
+ *  ex1.
+ *    listen_socket = FCGI.open(8081)  # port number or socket file path
+ *    while (socket = FCGI.accept(listen_socket)) != nil
+ *      ...
+ *    end
+ *
+ *  ex2.
+ *    while (socket = FCGI.accept()) != nil    # default socket is 0 (stdin)
+ *      ...
+ *    end
+ *
+ */
+static VALUE fcgi_s_accept(int argc, VALUE argv[], VALUE self) {
+  VALUE socket;
+  int socket_fd;
+  if (argc == 0 || socket == Qnil) {
+    socket_fd = 0;       /* use stdin as default socket file descriptor */
+  } else {
+    rb_scan_args(argc, argv, "01", &socket);
+    Check_Type(socket, T_FIXNUM);
+    socket_fd = FIX2INT(socket);
+  }
+  return _fcgi_s_accept(self, socket_fd);
+}
+
+/*
+ *  body of fcgi_s_accept() and fcgi_s_accept2()
+ */
+static VALUE _fcgi_s_accept(VALUE self, int socket_fd) {
   int status;
   FCGX_Request *req;
   fd_set readfds;
-  
+
   req = ALLOC(FCGX_Request);
-  
-  status = FCGX_InitRequest(req,0,0);
+
+  status = FCGX_InitRequest(req, socket_fd, 0);
   if (status != 0) {
     rb_raise(eFCGIError, "FCGX_Init() failed");
     return Qnil;
@@ -110,14 +145,122 @@
   }
 }

-static VALUE fcgi_s_each(VALUE self)
-{
-  VALUE fcgi;
-  
-  while ((fcgi = fcgi_s_accept(self)) != Qnil) {
-    rb_yield(fcgi);
-  }
-  return Qnil;
+
+/**
+ *  open socket with port number or socket file path.
+ *
+ *  usage: FCGI.open(port_or_path, backlog=0)
+ *    - port_or_path: port number or socket file path. 'nil' means $stdin.
+ *    - backlog: number of backlog to pass listen(2)
+ *
+ *  ex.
+ *    listen_socket = FCGI.open(8081)  # port number
+ *
+ *  ex2.
+ *    listen_socket = FCGI.open('/tmp/test1.sock')  # socket file path
+ *
+ */
+static VALUE fcgi_s_open(int argc, VALUE *argv, VALUE self) {
+    VALUE arg, backlog;
+    int socket_fd;
+    rb_scan_args(argc, argv, "11", &arg, &backlog);
+    if (arg == Qnil) {
+        socket_fd = 0;  /* default value of FCGX_OpenSocket() */
+    }
+    else {
+        char buf[10];
+        char *name;
+        int bklog;
+        /* port number or path name */
+        if (FIXNUM_P(arg)) {  /* port number */
+            int port = FIX2INT(arg);
+            if (port >= (1 << 16)) {
+                rb_raise(rb_eArgError, "port number is too large.");
+                return Qnil;
+            }
+            if (port <= 0) {
+                rb_raise(rb_eArgError, "invalid port number.");
+                return Qnil;
+            }
+            sprintf(buf, ":%d\0", port);
+            name = buf;
+        }
+        else {                /* path of unix domain socket */
+            Check_Type(arg, T_STRING);
+            name = RSTRING(arg)->ptr;
+        }
+        /* backlog for listen(2) */
+        if (backlog == Qnil) {
+            bklog = 0;
+        }
+        else {
+            Check_Type(backlog, T_FIXNUM);
+            bklog = FIX2INT(backlog);
+        }
+        /* create socket */
+        socket_fd = FCGX_OpenSocket(name, bklog);
+        if (socket_fd < -1) {
+            rb_raise(eFCGIError, "can't open socket.");
+            return Qnil;
+        }
+    }
+    return INT2FIX(socket_fd);
+}
+
+/**
+ *  close socket
+ *
+ *  usage: FCGI.close(socket)
+ *    - socket: socket (Fixnum)
+ */
+static VALUE fcgi_s_close(VALUE self, VALUE socket) {
+    int socket_fd;
+    Check_Type(socket, T_FIXNUM);
+    socket_fd = FIX2INT(socket);
+    close(socket_fd);
+    return Qnil;
+}
+
+/**
+ *  each-method with port number or socket file path
+ *
+ *  usage: FCGI.each(port_or_path=nil, backlog=0)
+ *    - port_or_path:   port number or socket file path
+ *    - backlog:        number of backlog to pass to listen(2)
+ *
+ *  ex.
+ *    FCGI.each_with(8081) do |request|
+ *      request.out.print "Content-Type: text/plain\r\n"
+ *      request.out.print "\r\n"
+ *      request.out.print "Hello World!\n"
+ *      request.finish
+ *    end
+ *
+ */
+static VALUE fcgi_s_each(int argc, VALUE argv[], VALUE self)
+{
+    VALUE fcgi, socket;
+    int socket_fd;
+    if (argc == 0) {
+        socket_fd = 0;  /* use stdin as default socket file descriptor */
+    }
+    else {
+        socket = fcgi_s_open(argc, argv, self);
+        socket_fd = FIX2INT(socket);
+        /* change mode of unix socket file to 0777 */
+        /*
+        if (rb_type(argv[0]) == T_STRING && RSTRING(argv[0])->ptr[0] != ':') {
+            chmod(RSTRING(argv[0])->ptr, 0777);
+        }
+        */
+    }
+    while ((fcgi = _fcgi_s_accept(self, socket_fd)) != Qnil) {
+        rb_yield(fcgi);
+    }
+    if (argc != 0) {
+        fcgi_s_close(self, socket);
+    }
+    return Qnil;
 }

 static VALUE fcgi_s_iscgi(VALUE self)
@@ -513,9 +656,11 @@

   cFCGI = rb_define_class("FCGI", rb_cObject);
   eFCGIError =rb_define_class_under(cFCGI, "Error", rb_eStandardError);
-  rb_define_singleton_method(cFCGI, "accept", fcgi_s_accept, 0);
-  rb_define_singleton_method(cFCGI, "each", fcgi_s_each, 0);
-  rb_define_singleton_method(cFCGI, "each_request", fcgi_s_each, 0);
+  rb_define_singleton_method(cFCGI, "accept", fcgi_s_accept, -1);
+  rb_define_singleton_method(cFCGI, "open", fcgi_s_open, -1);
+  rb_define_singleton_method(cFCGI, "close", fcgi_s_close, 1);
+  rb_define_singleton_method(cFCGI, "each", fcgi_s_each, -1);
+  rb_define_singleton_method(cFCGI, "each_request", fcgi_s_each, -1);
   rb_define_singleton_method(cFCGI, "is_cgi?", fcgi_s_iscgi, 0);
   rb_define_method(cFCGI, "in",  fcgi_in, 0);
   rb_define_method(cFCGI, "out", fcgi_out, 0);

Leave a reply