Exemplo n.º 1
0
        /// <summary>
        /// Private method that handles an incoming request.
        /// It sets up a RequestHandlerContext instance with the data from
        /// the incoming HTTP request, finds a suitable request handler to
        /// produce the response, and then sends the response as an HTTP
        /// response back to the client.
        /// Preconditions
        ///     "connection is open"
        ///     serviceRoot != null
        ///     serviceRoot.Length > 8
        ///     "serviceRoot starts with 'http://' and ends with '/'"
        ///     requestRouting != null
        /// </summary>
        /// <param name="connection">Open TCP/IP connection</param>
        /// <param name="serviceRoot">The absolute URI that is a prefix of
        /// all request URIs that this web service supports. It must start
        /// with "http://" and must end with "/".</param>
        /// <param name="relayDomain">Host name or Internet address of the
        /// relay to be used, or null if no relay is used</param>
        /// <param name="requestRouting">Collection of
        ///   { request pattern, request handler}
        /// pairs</param>
        /// <param name="connectionClose">Return parameter that indicates
        /// that the connection should be closed after this call. This may
        /// be because the incoming request has a "Connection: close"
        /// header, because the request handler has set the
        /// ConnectionClose property, or because some error occurred.
        /// </param>
        internal static void ConsumeRequest(Stream connection,
                                            string serviceRoot, string relayDomain,
                                            RequestRouting requestRouting,
                                            ref bool connectionClose)
        {
            Contract.Requires(connection != null);
            Contract.Requires(serviceRoot != null);
            Contract.Requires(serviceRoot.Length > 8);
            Contract.Requires(serviceRoot.Substring(0, 7) == "http://");
            Contract.Requires(serviceRoot[serviceRoot.Length - 1] != '/');
            Contract.Requires(requestRouting != null);

            // initialization --------------------------------------------
            HttpReader reader  = new HttpReader();
            HttpWriter writer  = new HttpWriter();
            var        context = new RequestHandlerContext(serviceRoot,
                                                           relayDomain);

            // Both client and server may request closing the connection.
            // Initially, we assume that neither one wants to close the
            // connection.
            context.ConnectionClose = false;

            // receive request -------------------------------------------
            reader.Attach(connection);

            // request line
            string httpMethod;
            string requestUri;
            string httpVersion;

            reader.ReadStringToBlank(out httpMethod);
            reader.ReadStringToBlank(out requestUri);
            reader.ReadFieldValue(out httpVersion);
            if (reader.Status != HttpStatus.BeforeContent)  // error
            {
                reader.Detach();
                connectionClose = true;
                return;
            }

            context.RequestMethod = httpMethod;
            context.RequestUri    = requestUri;
            // ignore version

            // headers
            string fieldName;
            string fieldValue;
            int    requestContentLength = -1;

            reader.ReadFieldName(out fieldName);
            while (reader.Status == HttpStatus.BeforeContent)
            {
                reader.ReadFieldValue(out fieldValue);
                if (fieldValue != null)
                {
                    Contract.Assert(reader.Status ==
                                    HttpStatus.BeforeContent);
                    if (fieldName == "Connection")
                    {
                        context.ConnectionClose =
                            ((fieldValue == "close") ||
                             (fieldValue == "Close"));
                    }
                    else if (fieldName == "Content-Type")
                    {
                        context.RequestContentType = fieldValue;
                    }
                    else if (fieldName == "Content-Length")
                    {
                        if (Utilities.TryParseUInt32(fieldValue,
                                                     out requestContentLength))
                        {
                            // content length is now known
                        }
                        else
                        {
                            reader.Status = HttpStatus.SyntaxError;
                            reader.Detach();
                            connectionClose = true;
                            return;
                        }
                    }
                }
                else
                {
                    // it's ok to skip header whose value is too long
                }
                Contract.Assert(reader.Status == HttpStatus.BeforeContent);
                reader.ReadFieldName(out fieldName);
            }
            if (reader.Status != HttpStatus.InContent)
            {
                reader.Detach();
                connectionClose = true;
                return;
            }

            // content
            if (requestContentLength > 0)
            {
                // receive content
                var requestContent = new byte[requestContentLength];
                int toRead         = requestContentLength;
                var read           = 0;
                while ((toRead > 0) && (read >= 0))
                {
                    // already read: requestContentLength - toRead
                    read = reader.ReadContent(requestContent,
                                              requestContentLength - toRead, toRead);
                    if (read < 0)
                    {
                        break;
                    }                           // timeout or shutdown
                    toRead = toRead - read;
                }
                try
                {
                    char[] chars = Encoding.UTF8.GetChars(requestContent);
                    context.RequestContent = new string(chars);
                }
                catch (Exception)
                {
                    context.RequestContentType = "text/plain";
                    context.RequestContent     = "request content is not in UTF8 format";
                    Debug.Print(context.RequestContent);
                    // TODO signal wrong content format through HTTP status code 400 (Bad Request)?
                }
            }

            reader.Detach();
            if (reader.Status != HttpStatus.InContent)
            {
                connectionClose = true;
                return;
            }

            // delegate request processing to a request handler ----------
            var match = false;

            foreach (Element e in requestRouting)
            {
                if (context.RequestMatch(e))
                {
                    bool connClose = context.ConnectionClose;
                    context.ResponseStatusCode = -1;                                    // undefined
                    Contract.Requires(context.ResponseContentType != null);
                    Contract.Requires(context.ResponseContentType == "text/plain");     // default
                    try
                    {
                        e.Handler(context);
                    }
                    catch (Exception h)
                    {
                        Debug.Print("exception in request handler: " + h); // TODO how to better handle handler exceptions?
                        throw;                                             // rethrow, to avoid masking of errors
                    }
                    Contract.Ensures(context.ResponseStatusCode >= 100);
                    Contract.Ensures(context.ResponseStatusCode < 600);
                    Contract.Ensures(context.ResponseContentType != null);
                    Contract.Ensures(context.ResponseContentType.Length > 0);
                    // make sure that handler has not reset connectionClose flag:
                    Contract.Ensures((!connClose) || (context.ConnectionClose));
                    match = true;
                    break;
                }
            }
            if (!match)
            {
                context.ResponseStatusCode  = 404;   // Not Found
                context.ResponseContentType = "text/plain";
                context.ResponseContent     = null;
            }

            uint availableMemory = Debug.GC(true);

            Debug.Print(context.RequestMethod + " " +
                        context.RequestUri + " -> " +
                        context.ResponseStatusCode + " [" + availableMemory + "]");

            // send response ---------------------------------------------
            writer.Attach(connection);

            // status line
            writer.WriteString("HTTP/1.1 ");
            writer.WriteString(context.ResponseStatusCode.ToString());
            writer.WriteLine(" ");  // omit optional reason phrase

            // headers
            if (connectionClose)   // TODO (context.ConnectionClose)
            {
                writer.WriteLine("Connection: close");
            }
            byte[] responseBuffer = null;
            var    responseLength = 0;

            if (context.ResponseContent != null)
            {
                responseBuffer =
                    Encoding.UTF8.GetBytes(context.ResponseContent);
                responseLength = responseBuffer.Length;
                writer.WriteString("Content-Type: ");
                writer.WriteLine(context.ResponseContentType);
            }
            else
            {
                responseLength = 0;
            }
            writer.WriteString("Content-Length: ");
            writer.WriteLine(responseLength.ToString());
            if (context.ResponseMaxAge > 0)
            {
                writer.WriteLine("Cache-Control: max-age=" + context.ResponseMaxAge);
            }
            else if (context.ResponseMaxAge == 0)
            {
                writer.WriteLine("Cache-Control: no-cache");
            }

            // content
            writer.WriteBeginOfContent();
            if (context.ResponseContent != null)    // send content
            {
                writer.WriteContent(responseBuffer, 0, responseLength);
            }

            writer.Detach();
            connectionClose = context.ConnectionClose;
        }
Exemplo n.º 2
0
        /// <summary>
        /// Private method that handles an incoming request.
        /// It sets up a RequestHandlerContext instance with the data from
        /// the incoming HTTP request, finds a suitable request handler to
        /// produce the response, and then sends the response as an HTTP
        /// response back to the client.
        /// Preconditions
        ///     "connection is open"
        ///     serviceRoot != null
        ///     serviceRoot.Length > 8
        ///     "serviceRoot starts with 'http://' and ends with '/'"
        ///     requestRouting != null
        /// </summary>
        /// <param name="connection">Open TCP/IP connection</param>
        /// <param name="serviceRoot">The absolute URI that is a prefix of
        /// all request URIs that this web service supports. It must start
        /// with "http://" and must end with "/".</param>
        /// <param name="relayDomain">Host name or Internet address of the
        /// relay to be used, or null if no relay is used</param>
        /// <param name="requestRouting">Collection of
        ///   { request pattern, request handler}
        /// pairs</param>
        /// <param name="connectionClose">Return parameter that indicates
        /// that the connection should be closed after this call. This may
        /// be because the incoming request has a "Connection: close"
        /// header, because the request handler has set the
        /// ConnectionClose property, or because some error occurred.
        /// </param>
        internal static void ConsumeRequest(Stream connection,
            string serviceRoot, string relayDomain,
            RequestRouting requestRouting,
            bool catchRequestFailures,
            ref bool connectionClose)
        {
            Contract.Requires(connection != null);
            Contract.Requires(serviceRoot != null);
            Contract.Requires(serviceRoot.Length > 8);
            Contract.Requires(serviceRoot.Substring(0, 7) == "http://");
            Contract.Requires(serviceRoot[serviceRoot.Length - 1] != '/');
            Contract.Requires(requestRouting != null);

            // initialization --------------------------------------------
            DebugPrint("HttpServer: ConsumeRequest - initialize");
            HttpReader reader = new HttpReader();
            HttpWriter writer = new HttpWriter();
            var context = new RequestHandlerContext(serviceRoot,
                relayDomain);

            // receive request -------------------------------------------
            reader.Attach(connection);

            // read request line
            DebugPrint("HttpServer: ConsumeRequest - read request line");
            string httpMethod;
            string requestUri;
            string httpVersion;

            reader.ReadStringToBlank(out httpMethod);
            reader.ReadStringToBlank(out requestUri);
            if (reader.Status == HttpStatus.RequestUriTooLong)
            {
                context.ResponseStatusCode = 414;    // Request-URI Too Long
                context.ResponseContentType = "text/plain";
                context.ResponseContent = "request URI too long";
                reader.Detach();
                connectionClose = true;
                DebugPrint("HttpServer: ConsumeRequest - request URI too long!");
                return;
            }
            reader.ReadFieldValue(out httpVersion);
            if (reader.Status != HttpStatus.BeforeContent)  // error
            {
                reader.Detach();
                connectionClose = true;
                DebugPrint("HttpServer: ConsumeRequest - could not read HTTP version!");
                return;
            }

            context.RequestMethod = httpMethod;
            context.RequestUri = requestUri;
            // ignore version

            // headers
            DebugPrint("HttpServer: ConsumeRequest - read headers");
            string fieldName;
            string fieldValue;
            int requestContentLength = -1;
            reader.ReadFieldName(out fieldName);
            while (reader.Status == HttpStatus.BeforeContent)
            {
                reader.ReadFieldValue(out fieldValue);
                if (fieldValue != null)
                {
                    Contract.Assert(reader.Status ==
                                    HttpStatus.BeforeContent);
                    if (fieldName == "Connection")
                    {
                        connectionClose =
                            (connectionClose ||
                            (fieldValue == "close") ||
                             (fieldValue == "Close"));
                    }
                    else if (fieldName == "Content-Type")
                    {
                        context.RequestContentType = fieldValue;
                        DebugPrint("HttpServer: ConsumeRequest - request Content-Type: " + fieldValue);
                    }
                    else if (fieldName == "Content-Length")
                    {
                        if (Utilities.TryParseUInt32(fieldValue,
                            out requestContentLength))
                        {
                            // content length is now known
                            DebugPrint("HttpServer: ConsumeRequest - request Content-Length: " + requestContentLength);
                        }
                        else
                        {
                            DebugPrint("HttpServer: ConsumeRequest - request syntax error");
                            //reader.Status = HttpStatus.SyntaxError;
                            reader.Detach();
                            connectionClose = true;
                            return;
                        }
                    }
                }
                else
                {
                    // it's ok to skip header whose value is too long
                }
                Contract.Assert(reader.Status == HttpStatus.BeforeContent);
                reader.ReadFieldName(out fieldName);
            }
            if (reader.Status != HttpStatus.InContent)
            {
                reader.Detach();
                connectionClose = true;
                return;
            }

            // content
            DebugPrint("HttpServer: ConsumeRequest - read content");
            context.RequestContentBytes = null;
            if (requestContentLength > 0)
            {
                // receive content
                context.RequestContentBytes = new byte[requestContentLength];
                int toRead = requestContentLength;
                var read = 0;
                while ((toRead > 0) && (read >= 0))
                {
                    // already read: requestContentLength - toRead
                    read = reader.ReadContent(context.RequestContentBytes,
                        requestContentLength - toRead, toRead);
                    if (read < 0) { break; }    // timeout or shutdown
                    toRead = toRead - read;
                }
            }

            reader.Detach();
            if (reader.Status != HttpStatus.InContent)
            {
                connectionClose = true;
                return;
            }

            // delegate request processing to a request handler ----------
            DebugPrint("HttpServer: ConsumeRequest - call request handler");
            var match = false;
            foreach (RoutingElement e in requestRouting)
            {
                if (context.RequestMatch(e))
                {
                    context.ConnectionClose = false;
                    context.ResponseStatusCode = -1;                                    // undefined
                    Contract.Requires(context.ResponseContentType != null);
                    Contract.Requires(context.ResponseContentType == "text/plain");     // default

                    e.Handler(context);
                    if (catchRequestFailures)
                    {
                        try
                        {
                            e.Handler(context);
                        }
                        catch (Exception h)
                        {
                            DebugPrint("HttpServer: exception in request handler: " + h);
                        }
                    }
                    else
                    {
                        e.Handler(context);
                    }

                    Contract.Ensures(context.ResponseStatusCode >= 100);
                    Contract.Ensures(context.ResponseStatusCode < 600);
                    Contract.Ensures(context.ResponseContentType != null);
                    Contract.Ensures(context.ResponseContentType.Length > 0);
                    connectionClose = connectionClose || context.ConnectionClose;
                    match = true;
                    break;
                }
            }
            if (!match)
            {
                context.ResponseStatusCode = 404;    // Not Found
                context.ResponseContentType = "text/plain";
                context.ResponseContent = "404 error - resource not found";
                DebugPrint("HttpServer: no matching request handler found");
            }
            Contract.Assert(context.ResponseContentType != null);

            // send response ---------------------------------------------
            DebugPrint("HttpServer: ConsumeRequest - send response");
            writer.Attach(connection);

            // status line
            DebugPrint("HttpServer: ConsumeRequest - send status line");
            writer.WriteString("HTTP/1.1 ");
            writer.WriteString(context.ResponseStatusCode.ToString());
            writer.WriteLine(" ");  // omit optional reason phrase

            // headers
            DebugPrint("HttpServer: ConsumeRequest - send headers");
            if (connectionClose)
            {
                writer.WriteLine("Connection: close");
            }
            if (context.ResponseMaxAge > 0)
            {
                writer.WriteLine("Cache-Control: max-age=" + context.ResponseMaxAge);
            }
            else if (context.ResponseMaxAge == 0)
            {
                writer.WriteLine("Cache-Control: no-cache");
            }
            writer.WriteString("Content-Type: ");
            writer.WriteLine(context.ResponseContentType);
            DebugPrint("HttpServer: ConsumeRequest - response Content-Type: " + context.ResponseContentType);

            if (context.ResponseContent != null)
            {
                DebugPrint("HttpServer: ConsumeRequest - response content is text");
                context.ResponseContentBytes =
                    Encoding.UTF8.GetBytes(context.ResponseContent);
                Contract.Assert(context.ResponseContentBytes != null);
            }
            if (context.ResponseContentBytes != null)
            {
                DebugPrint("HttpServer: ConsumeRequest - content available as binary");
                if (context.ResponseContentLength == -1)
                {
                    context.ResponseContentLength = context.ResponseContentBytes.Length;    // default is to take the entire buffer
                }
                writer.WriteString("Content-Length: ");
                writer.WriteLine(context.ResponseContentLength.ToString());
                DebugPrint("HttpServer: ConsumeRequest - response Content-Length: " + context.ResponseContentLength);
            }
            else
            {
                writer.WriteString("Content-Length: 0");
                DebugPrint("HttpServer: ConsumeRequest - response Content-Length: 0");
            }

            // content
            DebugPrint("HttpServer: ConsumeRequest - send content");
            writer.WriteBeginOfContent();
            if (context.ResponseContentBytes != null)    // send content
            {
                writer.WriteContent(context.ResponseContentBytes, 0, context.ResponseContentLength);
            }
            DebugPrint("HttpServer: response sent");

            writer.Detach();

            uint availableMemory = Microsoft.SPOT.Debug.GC(true);   // TODO define portable abstraction for free memory
            Trace.TraceInformation(context.RequestMethod + " " +
                                   context.RequestUri + " -> " +
                                   context.ResponseStatusCode + " [" + availableMemory + "]");
        }