1 // Written in the D programming language.
2 /*
3  * An interface to the FastCGI library provided by Open Market.
4  *
5  * Provides a function to loop through requests, utilising a number of
6  * threads created at startup.
7  *
8  * Also provides adapters to allow access to the input and output streams and
9  * the environment variables via D style interfaces.
10  *
11  * Copyright 2013 Jaypha
12  * Distributed under the Boost License V1.0.
13  * Authors: Jason den Dulk
14  */
15 
16 module jaypha.fcgi.loop;
17 
18 import std.concurrency;
19 import std.array;
20 
21 import jaypha.fcgi.c.fcgiapp;
22 
23 enum defaultThreadCount = 10;
24 
25 // Note: In HTTP, input and output are octet streams. That is, an array of ubytes.
26 
27 //---------------------------------------------------------------------------
28 // An adapter for outgoing FCGI streams that acts as an output range.
29 //-------------------------------------
30 struct FcgiOutStream
31 //-------------------------------------
32 {
33   FCGX_Stream* stream;
34 
35   this(FCGX_Stream* _stream)
36   {
37     stream = _stream;
38   }
39 
40   void put(const(ubyte)[] c)
41   {
42     FCGX_PutStr(cast(const(char)*)c.ptr, cast(int)c.length, stream);
43   }
44 
45   void put(const ubyte c)
46   {
47     FCGX_PutChar(cast(const(char))c, stream);
48   }
49 }
50 
51 //---------------------------------------------------------------------------
52 // An adapter for incoming FCGI streams that acts as an input range.
53 //-------------------------------------
54 struct FcgiInStream
55 //-------------------------------------
56 {
57   FCGX_Stream* stream;
58 
59   this(FCGX_Stream* _stream)
60   {
61     stream = _stream;
62     popFront();
63   }
64 
65   bool empty = false;
66   ubyte front;
67 
68   void popFront()
69   {
70     int next = FCGX_GetChar(stream);
71     if (next == -1)
72       empty = true;
73     front = cast(ubyte) next;
74   }
75 }
76 
77 //---------------------------------------------------------------------------
78 // Bundle up stuff used by a request.
79 //-------------------------------------
80 struct FcgiRequest
81 //-------------------------------------
82 {
83   private FCGX_Request request;
84 
85   FcgiOutStream fcgiOut;
86   FcgiOutStream fcgiErr;
87   FcgiInStream  fcgiIn;
88 
89   string[string] env;
90 
91   private void prepare()
92   {
93     fcgiOut = FcgiOutStream(request._out);
94     fcgiErr = FcgiOutStream(request._err);
95     fcgiIn  = FcgiInStream(request._in);
96     env = ppToAssoc(cast(const(char)**) request.envp);
97   }
98 }
99 
100 //---------------------------------------------------------------------------
101 // Main loop.
102 
103 void fcgiLoop(void function(ref FcgiRequest) callback, uint numThreads = defaultThreadCount)
104 {
105   // Spawn the required number of threads.
106   foreach(i;0..numThreads)
107     spawnLinked(&newThread, callback);
108 
109   // Each time a thread terminates, spawn a new one.
110   while (true)
111   {
112     auto m = receiveOnly!LinkTerminated();
113     spawnLinked(&newThread, callback);
114   }
115 }
116 
117 //---------------------------------------------------------------------------
118 // This function indefinitely loops through each request.
119 
120 private void newThread(void function(ref FcgiRequest) fp)
121 {
122   FcgiRequest rr;
123 
124   FCGX_Init();
125 
126   while (true)
127   {
128     FCGX_InitRequest(&rr.request, 0, 0);
129     FCGX_Accept_r(&rr.request);
130     scope(exit) { FCGX_Finish_r(&rr.request); }
131 
132     rr.prepare();
133     fp(rr);
134   }
135 }
136 
137 //---------------------------------------------------------------------------
138 
139 /*
140  * Takes the environment variables as given by the FCGI and puts them
141  * into an associative array.
142  *
143  * In FCGI, the environment variables are given in a two dimensional
144  * char array.
145  * The outer array is null terminated.
146  * The inner arrays are C strings of the format "<name>=<value>".
147  */
148 
149 
150 private string[string] ppToAssoc(const(char)** pp)
151 {
152   string[string] ass;
153 
154   for (const(char)** p = pp; *p !is null; ++p)
155   {
156     const(char)* c = *p;
157     auto napp = appender!string();
158     while (*c != '=')
159     {
160       napp.put(*c);
161       ++c;
162     }
163     ++c;
164     auto vapp = appender!string();
165     while (*c != '\0')
166     {
167       vapp.put(*c);
168       ++c;
169     }
170     ass[napp.data] = vapp.data;
171   }
172   return ass;
173 }
174 
175 //---------------------------------------------------------------------------
176 
177 unittest
178 {
179   import std.range, std.string;
180 
181   static assert(isOutputRange!(FcgiOutStream,const(ubyte)[]));
182   static assert(isOutputRange!(FcgiOutStream,const ubyte));
183 
184   const(char)*[] pp;
185   pp ~= std..string.toStringz("timber=alice");
186   pp ~= std..string.toStringz("usb=false");
187   pp ~= null;
188 
189   auto ass = ppToAssoc(pp.ptr);
190 
191   assert (ass.length == 2);
192   assert ("timber" in ass);
193   assert (ass["timber"] == "alice");
194   assert ("usb" in ass);
195   assert (ass["usb"] == "false");
196 }
197