1 module dast.fcgi; 2 3 // dfmt off 4 import 5 dast.map, 6 std.array, 7 std.socket, 8 std.string; 9 10 size_t align8(size_t n) => (n + 7) & ~7; 11 12 size_t intToStr(char* buf, size_t value) { 13 char* p = buf; 14 for (;;) { 15 *(p++) = (value % 10) ^ '0'; 16 if (value < 10) 17 break; 18 value /= 10; 19 } 20 for(char* i = buf, j = p - 1; i < j; i++, j--) { 21 char t = *i; 22 *i = *j; 23 *j = t; 24 } 25 return p - buf; 26 } 27 28 unittest { 29 char[10] buf = void; 30 auto str = buf[]; 31 assert(intToStr(buf.ptr, 0)); 32 assert(str[0..1] == "0", str); 33 assert(intToStr(buf.ptr, 255)); 34 assert(str[0..3] == "255", str); 35 assert(intToStr(buf.ptr, 20170), str); 36 assert(str[0..5] == "20170", str); 37 assert(intToStr(buf.ptr, 4294967295), str); 38 assert(str == "4294967295", str); 39 } 40 41 enum { 42 FCGI_LISTENSOCK_FILENO = 0, 43 FCGI_KEEP_CONN = 1, 44 FCGI_MAX_LENGTH = 0xffff, 45 FCGI_HEADER_LEN = 8, 46 FCGI_VERSION_1 = 1, 47 FCGI_NULL_REQUEST_ID = 0 48 } 49 50 enum Role { 51 unknown, 52 responder = 1, 53 authorizer = 2, 54 filter = 3 55 } 56 57 enum RequestType { 58 begin = 1, 59 Abort = 2, 60 End = 3, 61 Params = 4, 62 Stdin = 5, 63 Stdout = 6, 64 Stderr = 7, 65 Data = 8, 66 GetValues = 9, 67 GetValuesResult = 10, 68 UnknownType = 11 69 } 70 71 struct Header { 72 ubyte 73 ver, 74 type, 75 requestIdB1, 76 requestIdB0, 77 contentLengthB1, 78 contentLengthB0, 79 paddingLength, 80 reserved; 81 82 this(RequestType reqType, size_t contentLength, int requestId, ubyte paddingLen) 83 in (contentLength <= FCGI_MAX_LENGTH) { 84 ver = FCGI_VERSION_1; 85 type = cast(ubyte) reqType; 86 requestIdB1 = (requestId >> 8) & 0xff; 87 requestIdB0 = (requestId ) & 0xff; 88 contentLengthB1 = (contentLength >> 8) & 0xff; 89 contentLengthB0 = (contentLength ) & 0xff; 90 paddingLength = paddingLen; 91 //reserved = 0; 92 } 93 } 94 95 struct BeginRequestBody { 96 ubyte roleB1, roleB0, 97 flags; 98 ubyte[5] reserved; 99 } 100 101 // dfmt on 102 103 struct EndRequestBody { 104 int appStatus; 105 ubyte protocolStatus; 106 ubyte[3] reserved; 107 } 108 109 class Request { 110 enum bufferLen = 4 << 10; 111 char[bufferLen] buffer = void; 112 size_t contentLength; 113 Map params; 114 115 protected Socket sock; 116 // dfmt off 117 ubyte paddingLength; 118 size_t next, 119 contentStop, 120 stop; 121 int requestId; 122 protected bool* ipcSockClosed; 123 // dfmt on 124 125 @property socket() => sock; 126 127 ubyte initialize(Socket socket, bool* sockClosed) { 128 params.data.clear(); 129 sock = socket; 130 ipcSockClosed = sockClosed; 131 next = 0; 132 stop = 0; 133 contentStop = 0; 134 fillBuffer(); 135 return processProtocol(); 136 } 137 138 protected bool fillBuffer() { 139 if (next == stop) { 140 auto readn = sock.receive(buffer[next .. $]); 141 if (readn == Socket.ERROR) 142 return false; 143 if (readn == 0) { 144 sock.close(); 145 *ipcSockClosed = true; 146 return false; 147 } 148 stop += readn; 149 } 150 return true; 151 } 152 153 protected ubyte processProtocol() { 154 ubyte keepConnection; 155 loop: for (;;) { 156 // process Header 157 if (next == contentStop) 158 fillBuffer(); 159 auto header = cast(Header*)(buffer.ptr + next); 160 next += Header.sizeof; 161 162 requestId = (header.requestIdB1 << 8) | header.requestIdB0; 163 contentLength = (header.contentLengthB1 << 8) | header.contentLengthB0; 164 165 contentStop = next + contentLength; 166 paddingLength = header.paddingLength; 167 // process Body 168 switch (header.type) { 169 case RequestType.begin: 170 auto rbody = cast(BeginRequestBody*)(buffer.ptr + next); 171 next += BeginRequestBody.sizeof; 172 keepConnection = rbody.flags & FCGI_KEEP_CONN; 173 int role = rbody.roleB1 << 8 | rbody.roleB0; 174 switch (role) { 175 case Role.responder: 176 params.FCGI_ROLE = "RESPONDER"; 177 break; 178 case Role.authorizer: 179 params.FCGI_ROLE = "AUTHORIZER"; 180 break; 181 case Role.filter: 182 params.FCGI_ROLE = "FILTER"; 183 break; 184 default: 185 params.FCGI_ROLE = "UNKNOWN"; 186 } 187 break; 188 case RequestType.Params: 189 while (next < contentStop) { 190 size_t nameLen = (buffer[next] & 0x80) ? 191 ((buffer[next++] & 0x7f) << 24) 192 | buffer[next++] << 16 193 | buffer[next++] << 8 194 | buffer[next++] : buffer[next++], 195 196 valLen = (buffer[next] & 0x80) ? 197 ((buffer[next++] & 0x7f) << 24) 198 | buffer[next++] << 16 199 | buffer[next++] << 8 200 | buffer[next++] : buffer[next++]; 201 202 auto name = buffer[next .. next + nameLen].idup; 203 next += nameLen; 204 params[name] = buffer[next .. next + valLen].idup; 205 next += valLen; 206 } 207 next += header.paddingLength; 208 goto default; 209 default: 210 break loop; 211 } 212 } 213 return keepConnection; 214 } 215 216 int read(void[] buf) { 217 if (next >= contentStop) { 218 fillBuffer; 219 processProtocol; 220 } 221 int len = cast(int)(contentStop - next); 222 if (buf.length < len) 223 len = cast(int)buf.length; 224 buf[0 .. len] = buffer[next .. len + next]; 225 next += len; 226 return len; 227 } 228 } 229 230 class Response { 231 enum { 232 bufferLen = 1024, 233 maxHeaderLen = 1024 234 } 235 bool headerSent; 236 237 protected { 238 import tame.buffer : Buffer = FixedBuffer; 239 240 bool* ipcSockClosed; 241 } 242 Buffer!maxHeaderLen head; 243 Buffer!bufferLen buf; 244 245 this() { 246 head = typeof(head)(&throwErr!"Headers"); 247 buf = typeof(buf)(&send); 248 } 249 250 int requestId; 251 ubyte keepConnection; 252 protected Socket sock; 253 254 void initialize(Socket socket, bool* sockClosed, int reqId) pure @nogc nothrow @safe { 255 sock = socket; 256 ipcSockClosed = sockClosed; 257 requestId = reqId; 258 headerSent = false; 259 head.clear(); 260 buf.clear(); 261 } 262 263 void writeHeader(in char[] header) { 264 head ~= header; 265 head ~= "\r\n"; 266 } 267 268 void write(in char[] data) { 269 buf ~= data; 270 } 271 272 alias put = write; 273 274 void flush() { 275 buf.flush(); 276 } 277 278 void send(in char[] s) { 279 if (!headerSent) { 280 if (!head.length) 281 writeHeader("Content-Type: text/html"); 282 head ~= "\r\n"; 283 putstr(head.data); 284 head.clear(); 285 headerSent = true; 286 } 287 putstr(s); 288 } 289 290 void finish() { 291 buf.flush(); 292 auto endHeader = Header(RequestType.End, EndRequestBody.sizeof, requestId, 0); 293 EndRequestBody endBody; 294 buf ~= endHeader; 295 buf ~= endBody; 296 sock.send(buf.data); 297 if (!keepConnection) { 298 sock.close(); 299 *ipcSockClosed = true; 300 } 301 } 302 303 protected void throwErr(string obj)(in char[]) { 304 throw new Exception(obj ~ " too long"); 305 } 306 307 protected int putstr(in char[] str) { 308 size_t contentLength = str.length, 309 alignLength = align8(contentLength); 310 auto header = Header(RequestType.Stdout, contentLength, requestId, cast(ubyte)( 311 alignLength - contentLength)); 312 return cast(int)(sock.send( 313 (cast(char*)&header)[0 .. Header.sizeof]) + 314 sock.send(str.ptr[0 .. alignLength])); 315 } 316 } 317 318 class FCGIServer { 319 import core.thread; 320 import std.concurrency; 321 322 alias Handler = void delegate(Request req, Response resp); 323 protected Handler handler; 324 void delegate(Exception e) onError; 325 bool running; 326 Socket listener; 327 Socket sock; 328 alias ipcSockClosed = running; 329 protected Tid mainTid; 330 Thread[] threads; 331 332 bool accept() { 333 if (ipcSockClosed) 334 synchronized (listener) { 335 try 336 sock = listener.accept(); 337 catch (SocketAcceptException) 338 return false; 339 ipcSockClosed = false; 340 } 341 342 return true; 343 } 344 345 this(Handler handler) { 346 this.handler = handler; 347 } 348 349 this(void function(Request req, Response resp) func) { 350 import std.functional; 351 352 handler = toDelegate(func); 353 } 354 355 void mainLoop() { 356 scope req = new Request; 357 scope resp = new Response; 358 359 while (accept()) { 360 resp.keepConnection = req.initialize(sock, &ipcSockClosed); 361 resp.initialize(sock, &ipcSockClosed, req.requestId); 362 try 363 handler(req, resp); 364 catch (Exception e) { 365 onError(e); 366 resp.writeHeader("Status: 500"); 367 resp << e.toString; 368 } 369 resp.finish(); 370 } 371 } 372 373 void start(ushort port = 9001, uint maxThread = 1) { 374 if (threads.length) 375 return; 376 377 listener = new TcpSocket; 378 listener.bind(new InternetAddress(INADDR_LOOPBACK, port)); 379 listener.listen(128); 380 running = true; 381 mainTid = thisTid; 382 if (!maxThread) 383 return mainLoop(); 384 385 threads = uninitializedArray!(Thread[])(maxThread); 386 foreach (ref thread; threads) { 387 thread = new Thread(&mainLoop); 388 thread.start(); 389 } 390 } 391 }