1 /** 2 * Support for implementing SCGI application servers. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <vladimir@thecybershadow.net> 12 */ 13 14 module ae.net.http.scgi.app; 15 16 import std.algorithm.searching : findSplit; 17 import std.conv : to; 18 import std.exception; 19 import std..string; 20 21 import ae.net.asockets; 22 import ae.net.http.cgi.common; 23 import ae.net.http.cgi.script; 24 import ae.net.http.common; 25 import ae.sys.log; 26 import ae.utils.array; 27 28 final class SCGIConnection 29 { 30 IConnection connection; 31 Logger log; 32 bool nph; 33 34 this(IConnection connection) 35 { 36 this.connection = connection; 37 connection.handleReadData = &onReadData; 38 } 39 40 private Data buffer; 41 42 void onReadData(Data data) 43 { 44 buffer ~= data; 45 46 while (true) 47 try 48 { 49 auto bufferStr = cast(char[])buffer.contents; 50 auto colonIdx = bufferStr.indexOf(':'); 51 if (colonIdx < 0) 52 return; 53 54 auto headerLenStr = bufferStr[0 .. colonIdx]; 55 auto headerLen = headerLenStr.to!size_t; 56 auto headerEnd = headerLenStr.length + 1 /*:*/ + headerLen + 1 /*,*/; 57 if (buffer.length < headerEnd) 58 return; 59 enforce(bufferStr[headerEnd - 1] == ',', "Expected ','"); 60 61 auto headersStr = bufferStr[headerLenStr.length + 1 .. headerEnd - 1]; 62 enum CONTENT_LENGTH = "CONTENT_LENGTH"; 63 enforce(headersStr.startsWith(CONTENT_LENGTH ~ "\0"), "Expected first header to be " ~ CONTENT_LENGTH); 64 auto contentLength = headersStr[CONTENT_LENGTH.length + 1 .. $].findSplit("\0")[0].to!size_t; 65 if (buffer.length < headerEnd + contentLength) 66 return; 67 68 // We now know we have all the data in the request 69 70 auto headers = parseHeaders(headersStr.idup); 71 enforce(headers.get("SCGI", null) == "1", "Unknown SCGI version"); 72 CGIRequest request; 73 request.vars = CGIVars.fromAA(headers); 74 request.headers = CGIRequest.decodeHeaders(headers, request.vars.serverProtocol ? request.vars.serverProtocol : "HTTP"); 75 request.data = [buffer[headerEnd .. headerEnd + contentLength]]; 76 buffer = buffer[headerEnd + contentLength .. $]; 77 handleRequest(request); 78 } 79 catch (Exception e) 80 { 81 if (log) log("Error handling request: " ~ e.toString()); 82 connection.disconnect(e.msg); 83 return; 84 } 85 } 86 87 static string[string] parseHeaders(string s) 88 { 89 string[string] headers; 90 while (s.length) 91 { 92 auto name = s.skipUntil('\0').enforce("Unterminated header name"); 93 auto value = s.skipUntil('\0').enforce("Unterminated header value"); 94 headers[name] = value; 95 } 96 return headers; 97 } 98 99 void sendResponse(HttpResponse r) 100 { 101 FastAppender!char headers; 102 if (nph) 103 writeNPHHeaders(r, headers); 104 else 105 writeCGIHeaders(r, headers); 106 connection.send([Data(headers.get)] ~ r.data); 107 connection.disconnect("Response sent"); 108 } 109 110 void delegate(ref CGIRequest) handleRequest; 111 }