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