Ejemplo n.º 1
0
        /// <summary>
        /// Used internally. Feeds a <see cref="Record">Record</see> to this request for processing.
        /// </summary>
        /// <param name="record">The record to feed.</param>
        /// <returns>Returns true iff the request is completely received.</returns>
        internal bool HandleRecord(Record record)
        {
            switch (record.Type)
            {
                case Record.RecordType.Params:

                    if (record.ContentLength == 0)
                    {
                        ParamStream.Seek(0, SeekOrigin.Begin);
                        Parameters = Record.ReadNameValuePairs(ParamStream);
                    }
                    else
                    {
                        // If the params are not yet finished, write the contents to the ParamStream.
                        ParamStream.Write(record.ContentData, 0, record.ContentLength);
                    }
                    break;
                case Record.RecordType.Stdin:
                    string data = Encoding.ASCII.GetString(record.ContentData);
                    Body += data;

                    // Finished requests are indicated by an empty stdin record
                    if (record.ContentLength == 0)
                        return true;

                    break;
            }

            return false;
        }
Ejemplo n.º 2
0
        public void FCGIApp_Connections()
        {
            // Create an app that actually listens on the loopback interface
            var app = new FCGIApplication();
            app.Timeout = 100;

            try
            {
                app.Listen(new IPEndPoint(IPAddress.Loopback, 21511));
                Request receivedRequest = null;
                app.OnRequestReceived += (sender, request) => {
                    receivedRequest = request;
                    request.WriteResponseASCII("Hello Server!");
                    request.Close();
                };

                app.Process();

                var serverSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
                var asyncResult = serverSocket.BeginConnect(new IPEndPoint(IPAddress.Loopback, 21511), (r) => { }, null);

                while(!asyncResult.IsCompleted)
                    app.Process();

                serverSocket.EndConnect(asyncResult);

                var serverStream = new NetworkStream(serverSocket);

                // Now send a request to the app and make sure it is received correctly.

                var requestId = 5172;

                var beginRequestContent = new byte[]
                {
                    0x00, // role byte 1
                    (byte)Constants.FCGI_RESPONDER, // role byte 2
                    0x00, // flags
                    0x00, 0x00, 0x00, 0x00, 0x00 // reserved bytes
                };

                var beginRequest = new Record
                {
                    Type = Record.RecordType.BeginRequest,
                    RequestId = requestId,
                    ContentLength = beginRequestContent.Length,
                    ContentData = beginRequestContent
                };

                beginRequest.WriteToStream(serverStream);

                var stdinRequest = new Record
                {
                    Type = Record.RecordType.Stdin,
                    RequestId = requestId,
                    ContentLength = 0,
                    ContentData = new byte[0]
                };

                stdinRequest.WriteToStream(serverStream);

                serverStream.Flush();

                // Make the app digest everything
                while(receivedRequest == null)
                {
                    app.Process();
                }
                    

                // Do we have the correct request?
                Assert.AreEqual(requestId, receivedRequest.RequestId);

                // And did the response work?
                var responseRecord = Record.ReadRecord(serverStream);
                Assert.AreEqual(Record.RecordType.Stdout, responseRecord.Type);
                Assert.AreEqual("Hello Server!", Encoding.ASCII.GetString(responseRecord.ContentData));

                // Change the timeout while still connected
                app.Timeout = 100;

                app.StopListening();
                Assert.IsFalse(app.Connected);

            }
            // If the port is already in use, an execution can be thrown.
            // Report the test as inconclusive then.
            catch(SocketException e)
            {
                Assert.Inconclusive("SocketException: " + e.Message);
            }
        }
Ejemplo n.º 3
0
        public void FCGIApp_LargeRequest()
        {
            var app = new FCGIApplication();

            var expectedLength = 128317;

            app.OnRequestReceived += (sender, request) =>
            {
                var responseBody = new byte[expectedLength];
                request.WriteResponse(responseBody);
                request.Close();
            };

            // Connect to the app, impoersonating a webserver
            var inputStream = new MemoryStream();
            inputStream.Capacity = 4096;
            var outputStream = new MemoryStream();
            outputStream.Capacity = 4096 + expectedLength;

            // Send a request to it and make sure it responds to requests.

            var requestId = 5172;

            var beginRequestContent = new byte[]
            {
                0x00, // role byte 1
                (byte)Constants.FCGI_RESPONDER, // role byte 2
                Constants.FCGI_KEEP_CONN, // flags
                0x00, 0x00, 0x00, 0x00, 0x00 // reserved bytes
            };

            var beginRequest = new Record
            {
                Type = Record.RecordType.BeginRequest,
                RequestId = requestId,
                ContentLength = beginRequestContent.Length,
                ContentData = beginRequestContent
            };

            beginRequest.WriteToStream(inputStream);

            // Empty stdin indicates that the request is fully transmitted
            var stdinRequest = new Record
            {
                Type = Record.RecordType.Stdin,
                RequestId = requestId,
                ContentLength = 0,
                ContentData = new byte[0]
            };

            stdinRequest.WriteToStream(inputStream);

            inputStream.Seek(0, SeekOrigin.Begin);

            app.ProcessStream(inputStream, outputStream);

            outputStream.Seek(0, SeekOrigin.Begin);

            // Now the app should respond with a large response, splitted into 64KB stdout records

            var response = Record.ReadRecord(outputStream);

            Assert.AreEqual(65535, response.ContentLength);

            var totalLength = 0;

            while (response.ContentLength > 0)
            {
                Assert.AreEqual(Record.RecordType.Stdout, response.Type);
                Assert.AreEqual(requestId, response.RequestId);

                totalLength += response.ContentLength;

                response = Record.ReadRecord(outputStream);
            }

            Assert.AreEqual(expectedLength, totalLength);

            // Then, an empty stdout record should indicate the end of the response body
            Assert.AreEqual(Record.RecordType.Stdout, response.Type);
            Assert.AreEqual(requestId, response.RequestId);
            Assert.AreEqual(0, response.ContentLength);

            // And finally, a EndRequest record should close the request.
            response = Record.ReadRecord(outputStream);
            Assert.AreEqual(Record.RecordType.EndRequest, response.Type);
            Assert.AreEqual(requestId, response.RequestId);
        }
Ejemplo n.º 4
0
        public void FCGIApp_Request()
        {
            var app = new FCGIApplication();

            app.OnRequestReceived += (sender, request) =>
            {
                request.WriteResponseASCII("Hello!");
                request.Close();
            };

            // Connect to the app, impoersonating a webserver
            var streamServerToApp = new MemoryStream();
            streamServerToApp.Capacity = 4096;
            var streamAppToServer = new MemoryStream();
            streamAppToServer.Capacity = 4096;

            // Send a request to it and make sure it responds to requests.

            var requestId = 5172;

            var beginRequestContent = new byte[]
            {
                0x00, // role byte 1
                (byte)Constants.FCGI_RESPONDER, // role byte 2
                Constants.FCGI_KEEP_CONN, // flags
                0x00, 0x00, 0x00, 0x00, 0x00 // reserved bytes
            };

            var beginRequest = new Record
            {
                Type = Record.RecordType.BeginRequest,
                RequestId = requestId,
                ContentLength = beginRequestContent.Length,
                ContentData = beginRequestContent
            };

            beginRequest.WriteToStream(streamServerToApp);

            // Empty stdin indicates that the request is fully transmitted
            var stdinRequest = new Record
            {
                Type = Record.RecordType.Stdin,
                RequestId = requestId,
                ContentLength = 0,
                ContentData = new byte[0]
            };

            stdinRequest.WriteToStream(streamServerToApp);

            streamServerToApp.Seek(0, SeekOrigin.Begin);

            app.ProcessStream(streamServerToApp, streamAppToServer);;

            streamAppToServer.Seek(0, SeekOrigin.Begin);

            // Now the app should respond with 'Hello!'

            var response = Record.ReadRecord(streamAppToServer);

            var expectedBytes = Encoding.ASCII.GetBytes("Hello!");

            Assert.AreEqual(Record.RecordType.Stdout, response.Type);
            Assert.AreEqual(requestId, response.RequestId);
            Assert.AreEqual(expectedBytes.Length, response.ContentLength);
            Assert.AreEqual(expectedBytes, response.ContentData);

            // Then, an empty stdout record should indicate the end of the response body
            response = Record.ReadRecord(streamAppToServer);
            Assert.AreEqual(Record.RecordType.Stdout, response.Type);
            Assert.AreEqual(requestId, response.RequestId);
            Assert.AreEqual(0, response.ContentLength);

            // And finally, a EndRequest record should close the request.
            response = Record.ReadRecord(streamAppToServer);
            Assert.AreEqual(Record.RecordType.EndRequest, response.Type);
            Assert.AreEqual(requestId, response.RequestId);
        }
Ejemplo n.º 5
0
        public void FCGIApp_GetValues()
        {
            var app = new FCGIApplication();

            // Connect to the app, impoersonating a webserver
            var inputStream = new MemoryStream();
            inputStream.Capacity = 4096;
            var outputStream = new MemoryStream();
            outputStream.Capacity = 4096;

            // Send a GetValues record
            var getValues = new Record
            {
                Type = Record.RecordType.GetValues,
                RequestId = 0,
                ContentLength = 0,
            };

            getValues.WriteToStream(inputStream);
            inputStream.Seek(0, SeekOrigin.Begin);
            app.ProcessStream(inputStream, outputStream);
            outputStream.Seek(0, SeekOrigin.Begin);

            // Now the app should respond with a GetValuesResult
            var response = Record.ReadRecord(outputStream);

            Assert.AreEqual(Record.RecordType.GetValuesResult, response.Type);
            Assert.AreEqual(0, response.RequestId);

            var responseValues = response.GetNameValuePairs();

            // Response should include these three values
            Assert.Contains("FCGI_MAX_CONNS", responseValues.Keys);
            Assert.Contains("FCGI_MAX_REQS", responseValues.Keys);
            Assert.Contains("FCGI_MPXS_CONNS", responseValues.Keys);

            // No other values should be included
            Assert.AreEqual(3, responseValues.Count);

            int responseMaxConns, responseMaxReqs, responseMultiplexing;

            // Make sure the returned values look plausible
            var sMaxConns = Encoding.ASCII.GetString(responseValues["FCGI_MAX_CONNS"]);
            var sMaxReqs = Encoding.ASCII.GetString(responseValues["FCGI_MAX_REQS"]);
            var sMultiplexing = Encoding.ASCII.GetString(responseValues["FCGI_MPXS_CONNS"]);

            Assert.IsTrue(int.TryParse(sMaxConns, out responseMaxConns));
            Assert.IsTrue(int.TryParse(sMaxReqs, out responseMaxReqs));
            Assert.IsTrue(int.TryParse(sMultiplexing, out responseMultiplexing));

            Assert.GreaterOrEqual(responseMaxConns, 0);
            Assert.GreaterOrEqual(responseMaxReqs, 0);
            Assert.GreaterOrEqual(responseMultiplexing, 0);
            Assert.LessOrEqual(responseMultiplexing, 1);
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Tries to read and handle a <see cref="Record"/> from inputStream and writes responses to outputStream.
        /// </summary>
        /// <remarks>
        /// Use <see cref="ProcessStream"/> to process all records on a stream.
        /// Returns true if a record was read, false otherwise.
        /// </remarks>
        public bool ProcessSingleRecord(Stream inputStream, Stream outputStream)
        {
            if (!inputStream.CanRead)
            {
                return(false);
            }

            Record r = Record.ReadRecord(inputStream);

            // No record found on the stream?
            if (r == null)
            {
                return(false);
            }

            if (r.Type == Record.RecordType.BeginRequest)
            {
                if (OpenRequests.ContainsKey(r.RequestId))
                {
                    OpenRequests.Remove(r.RequestId);
                }

                var content = new MemoryStream(r.ContentData);

                var role = Record.ReadInt16(content);
                // Todo: Refuse requests for other roles than FCGI_RESPONDER

                var flags     = content.ReadByte();
                var keepAlive = (flags & Constants.FCGI_KEEP_CONN) != 0;
                var request   = new Request(r.RequestId, outputStream, this, keepAlive: keepAlive);
                OpenRequests.Add(request.RequestId, request);

                var incomingHandler = OnRequestIncoming;
                if (incomingHandler != null)
                {
                    incomingHandler(this, request);
                }
            }
            else if (r.Type == Record.RecordType.AbortRequest || r.Type == Record.RecordType.EndRequest)
            {
                OpenRequests.Remove(r.RequestId);
            }
            else if (r.Type == Record.RecordType.GetValues)
            {
                var getValuesResult = Record.CreateGetValuesResult(1, 1, false);
                getValuesResult.Send(outputStream);
            }
            else
            {
                if (OpenRequests.ContainsKey(r.RequestId))
                {
                    var  request      = OpenRequests[r.RequestId];
                    bool requestReady = request.HandleRecord(r);
                    if (requestReady)
                    {
                        var evh = OnRequestReceived;
                        if (evh != null)
                        {
                            OnRequestReceived(this, request);
                        }
                    }
                }
            }

            return(true);
        }
Ejemplo n.º 7
0
        public void FCGIApp_Params()
        {
            var app = new FCGIApplication();

            Request receivedRequest = null;

            app.OnRequestReceived += (sender, request) =>
            {
                receivedRequest = request;
            };

            // Connect to the app, impoersonating a webserver
            var streamServerToApp = new MemoryStream();
            streamServerToApp.Capacity = 4096;
            var streamAppToServer = new MemoryStream();
            streamAppToServer.Capacity = 4096;

            // Send a request to it and make sure it responds to requests.

            var requestId = 5172;

            var beginRequestContent = new byte[]
            {
                            0x00, // role byte 1
                            (byte)Constants.FCGI_RESPONDER, // role byte 2
                            Constants.FCGI_KEEP_CONN, // flags
                            0x00, 0x00, 0x00, 0x00, 0x00 // reserved bytes
            };

            var beginRequest = new Record
            {
                Type = Record.RecordType.BeginRequest,
                RequestId = requestId,
                ContentLength = beginRequestContent.Length,
                ContentData = beginRequestContent
            };

            beginRequest.WriteToStream(streamServerToApp);

            var paramDict = new Dictionary<string, byte[]>
            {
                {"SOME_NAME", Encoding.UTF8.GetBytes("SOME_KEY") },
                {"SOME_OTHER_NAME", Encoding.UTF8.GetBytes("SOME_KEY") },
                //{"ONE_MEGABYTE_OF_ZEROS", new byte[1024 * 1024] },
                {"EMPTY_VALUE", new byte[0] },
                {"1", Encoding.UTF8.GetBytes("☕☳üß - \n \r\n .,;(){}%$!") },
                {"2", Encoding.UTF8.GetBytes("") },
                {"3", Encoding.UTF8.GetBytes(" ") },
                {"4", Encoding.UTF8.GetBytes("\n") },
                {"5", Encoding.UTF8.GetBytes(";") },
            };

            var paramsRequest = new Record
            {
                Type = Record.RecordType.Params,
                RequestId = requestId
            };

            paramsRequest.SetNameValuePairs(paramDict);
            paramsRequest.WriteToStream(streamServerToApp);

            // Empty params record indicates that the parameters are fully transmitted
            var paramsCloseRequest = new Record
            {
                Type = Record.RecordType.Params,
                RequestId = requestId,
                ContentLength = 0,
                ContentData = new byte[0]
            };
            paramsCloseRequest.WriteToStream(streamServerToApp);


            // Empty stdin indicates that the request is fully transmitted
            var stdinRequest = new Record
            {
                Type = Record.RecordType.Stdin,
                RequestId = requestId,
                ContentLength = 0,
                ContentData = new byte[0]
            };

            stdinRequest.WriteToStream(streamServerToApp);

            streamServerToApp.Seek(0, SeekOrigin.Begin);

            app.ProcessStream(streamServerToApp, streamAppToServer);

            // Now the app should have received the request. Make sure the Parameters correctly decoded
            Assert.IsNotNull(receivedRequest);
            Assert.AreEqual(paramDict.Count, receivedRequest.Parameters.Count);

            foreach (var entry in paramDict)
            {
                Assert.Contains(entry.Key, receivedRequest.Parameters.Keys);
                Assert.AreEqual(entry.Value, receivedRequest.Parameters[entry.Key]);
            }
        }
Ejemplo n.º 8
0
        public void Records_ToString()
        {
            var someRecord = new Record
            {
                Version = Constants.FCGI_VERSION_1,
                Type = Record.RecordType.Stderr,
                RequestId = 123,
                ContentLength = 15,
                ContentData = new byte[15]
            };

            string stringified = someRecord.ToString();
            Assert.AreEqual("{Record type: Stderr, requestId: 123}", stringified);
        }
Ejemplo n.º 9
0
        public void Records_Equals()
        {
            var recordA1 = new Record
            {
                Version = Constants.FCGI_VERSION_1,
                Type = Record.RecordType.Stderr,
                RequestId = 123,
                ContentLength = 1,
                ContentData = new byte[1] { 1 }
            };

            var recordA2 = new Record
            {
                Version = Constants.FCGI_VERSION_1,
                Type = Record.RecordType.Stderr,
                RequestId = 123,
                ContentLength = 1,
                ContentData = new byte[1] { 1 }
            };

            var recordB1 = new Record
            {
                Version = Constants.FCGI_VERSION_1,
                Type = Record.RecordType.BeginRequest,
                RequestId = 321,
                ContentLength = 1,
                ContentData = new byte[1] { 1 }
            };

            var recordB2 = new Record
            {
                Version = Constants.FCGI_VERSION_1,
                Type = Record.RecordType.BeginRequest,
                RequestId = 321,
                ContentLength = 1,
                ContentData = new byte[1] { 1 }
            };

            var recordC1 = new Record
            {
                Version = Constants.FCGI_VERSION_1,
                Type = Record.RecordType.BeginRequest,
                RequestId = 321,
                ContentLength = 1,
                ContentData = new byte[1] { 2 }
            };

            var recordC2 = new Record
            {
                Version = Constants.FCGI_VERSION_1,
                Type = Record.RecordType.BeginRequest,
                RequestId = 321,
                ContentLength = 1,
                ContentData = new byte[1] { 2 }
            };

            Assert.IsTrue(recordA1.Equals(recordA2));
            Assert.IsTrue(recordB1.Equals(recordB2));
            Assert.IsTrue(recordC1.Equals(recordC2));

            Assert.IsFalse(recordA1.Equals(recordB2));
            Assert.IsFalse(recordA1.Equals(recordB1));
            Assert.IsFalse(recordA1.Equals(recordC1));

            Assert.IsFalse(recordB1.Equals(recordA1));
            Assert.IsFalse(recordB1.Equals(recordA2));
            Assert.IsFalse(recordB1.Equals(recordC1));

            Assert.IsFalse(recordB1.Equals(null));

            Assert.IsFalse(recordB1.Equals(5));
        }
Ejemplo n.º 10
0
        public void Records_BrokenRecords()
        {
            // Create some broken records and make sure the right things happen
            Record result;

            // An empty stream should simply result in a null record
            var emptyStream = new MemoryStream(new byte[0]);
            result = Record.ReadRecord(emptyStream);
            Assert.IsNull(result);

            // A wrong version number bytes should throw a InvalidDataException
            var wrongVersion = new MemoryStream(new byte[1] { Constants.FCGI_VERSION_1 + 1 });
            Assert.Throws(typeof(InvalidDataException), () => { Record.ReadRecord(wrongVersion); });


            // So should a bunch of zeroes
            var zeroes = new MemoryStream(new byte[4] { 0, 0, 0, 0 });
            Assert.Throws(typeof(InvalidDataException), () => { Record.ReadRecord(zeroes); });

            // And a correct version number with an incomplete header
            var incomplete = new MemoryStream(new byte[4] { Constants.FCGI_VERSION_1, 0, 0, 0 });
            Assert.Throws(typeof(InvalidDataException), () => { Record.ReadRecord(incomplete); });


            // Writing should fail for content above 64KB
            var tooLong = new Record
            {
                Version = Constants.FCGI_VERSION_1,
                Type = Record.RecordType.Stderr,
                RequestId = 123,
                ContentLength = 127000,
                ContentData = new byte[127000]
            };
            Assert.Throws(typeof(InvalidOperationException), () => { tooLong.WriteToStream(new MemoryStream()); });
        }
Ejemplo n.º 11
0
        /// <summary>
        /// Creates a GetValuesResult record from the given config values.
        /// </summary>
        public static Record CreateGetValuesResult(int maxConnections, int maxRequests, bool multiplexing)
        {
            var nameValuePairs = new Dictionary<string, byte[]>();

            // Names and values are encoded as strings.
            nameValuePairs.Add(Constants.FCGI_MAX_CONNS, Encoding.ASCII.GetBytes(maxConnections.ToString(CultureInfo.InvariantCulture)));
            nameValuePairs.Add(Constants.FCGI_MAX_REQS, Encoding.ASCII.GetBytes(maxRequests.ToString(CultureInfo.InvariantCulture)));
            nameValuePairs.Add(Constants.FCGI_MPXS_CONNS, Encoding.ASCII.GetBytes(multiplexing ? "1" : "0"));

            var record = new Record
            {
                RequestId = 0,
                Type = RecordType.GetValuesResult
            };

            record.SetNameValuePairs(nameValuePairs);

            return record;
        }
Ejemplo n.º 12
0
        /// <summary>
        /// Reads a single Record from the given stream.
        /// </summary>
        /// <remarks>
        /// Returns the retreived record or null if no record could be read.
        /// Will block if a partial record is on the stream, until the full record has arrived or a timeout occurs.
        /// </remarks>
        public static Record ReadRecord(Stream stream)
        {
            Record r = new Record();

            try
            {
                // Try to read a byte from the stream
                int firstByte = stream.ReadByte();

                // Reached end of stream?
                if (firstByte == -1)
                    return null;

                // Otherwise, that first byte should be the version byte of a record.
                // We now assume that we can safely read the rest of the record.
                // If the rest of the record has not yet been received, it should follow soon.
                r.Version = (byte)firstByte;

                if (r.Version != Constants.FCGI_VERSION_1)
                    throw new InvalidDataException("Invalid version number in FastCGI record header. Possibly corrupted data.");

                r.Type = (Record.RecordType)ReadByte(stream);
                r.RequestId = ReadInt16(stream);
                r.ContentLength = ReadInt16(stream);
                byte paddingLength = ReadByte(stream);

                // Skip reserved byte
                ReadByte(stream);

                r.ContentData = new byte[r.ContentLength];

                // Read content
                if(r.ContentLength > 0)
                    stream.Read(r.ContentData, 0, r.ContentLength);

                // Skip padding data
                if (paddingLength > 0)
                {
                    byte[] ignoredBuf = new byte[paddingLength];
                    stream.Read(ignoredBuf, 0, paddingLength);
                }

            }
            catch (EndOfStreamException e)
            {
                throw new InvalidDataException("Unexpected end of stream. Incomplete record transmitted or corrupted data.");
            }
            catch (IOException e)
            {
                // Connection has been closed, or an other error occured whie reading a record. Return a null record.
                return null;
            }

            return r;
        }