Source

owin-aspnet / Owin.AspNet / OwinHttpHandler.cs

Full commit
/* **************************************************************************
 *
 * Copyright 2011 Jeff Hardy
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * *************************************************************************/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;

namespace Owin.AspNet
{
    using AppAction = Action<
                IDictionary<string, object>,
                Action<string, Dictionary<string, IList<string>>, IEnumerable<object>>,
                Action<Exception>>;
    using AsyncResponseAction = Action<Action<object>, Action<Exception>>;
    using RequestBodyAction = Action<byte[], int, int, Action<int>, Action<Exception>>;

    public class OwinHttpHandler : IHttpHandler
    {
        protected Exception error;
        
        public bool IsReusable {
            get { return false; }
        }

        public void ProcessRequest(HttpContext context) {
            var app = LoadApp();
            var env = CreateEnvironment(context);

            app(
                env,
                (status, headers, body) => Response(context, status, headers, body),
                (e) => Error(context, e)
            );

            if(error != null)
                throw error;
        }

        protected static AppAction LoadApp() {
            var config = OwinConfig.Load();

            var appType = Type.GetType(config.AppType);
            var method = appType.GetMethod(config.AppMethod);
            object obj = method.IsStatic ? null : Activator.CreateInstance(appType);
            var app = (AppAction)AppAction.CreateDelegate(typeof(AppAction), obj, method);
            return app;
        }

        protected static IDictionary<string, object> CreateEnvironment(HttpContext context)
        {
            var request = context.Request;

            return new Dictionary<string, object>()
            {
                { "owin.RequestMethod", request.HttpMethod },
                { "owin.RequestUri", string.Format("{0}?{1}", request.PathInfo, request.QueryString) },
                { "owin.RequestHeaders", request.Headers.Keys.Cast<string>().ToDictionary(k => k, k => new List<string>(request.Headers.GetValues(k))) },
                { "owin.RequestBody", new RequestBodyAction(
                    (buffer, offset, count, result, appError) => Body(context, buffer, offset, count, result, appError)
                ) },
                { "owin.BaseUri", request.ApplicationPath },
                { "owin.ServerName", request.ServerVariables["SERVER_NAME"] },
                { "owin.ServerPort", request.ServerVariables["SERVER_PORT"] },
                { "owin.UriScheme", request.Url.Scheme },
                { "owin.RemoteEndpoint", null },    // TODO Find the IPEndpoint of the client
                { "owin.Version", "1.0" }
            };
        }

        private static void Body(HttpContext context, byte[] buffer, int offset, int count, Action<int> result, Action<Exception> appError) {
            var request = context.Request;
            
            try
            {
                request.InputStream.BeginRead(buffer, offset, count, ar => {
                    try
                    {
                        result(request.InputStream.EndRead(ar));
                    }
                    catch(Exception e)
                    {
                        appError(e);
                    }
                }, null);
            }
            catch(Exception e)
            {
                appError(e);
            }
        }

        protected void Error(HttpContext context, Exception e)
        {
            this.error = e;
        }

        protected void Response(HttpContext context, string status, Dictionary<string, IList<string>> headers, IEnumerable<object> body)
        {
            var response = context.Response;

            response.Status = status;
            foreach (var header in headers) {
                if(header.Value == null)
                    continue;

                foreach (var value in header.Value) {
                    response.AddHeader(header.Key, value);
                }
            }

            foreach (var item in body) {
                ProcessItem(context, item);
                if(error != null)
                    return;
            }
        }

        private void ProcessItem(HttpContext context, object item) {
            if(ProcessItem(context, item as byte[]))
                return;

            if(item is ArraySegment<byte>)
            {
                ProcessItem(context, (ArraySegment<byte>)item);
                return;
            }

            if(ProcessItem(context, item as FileInfo))
                return;

            if(ProcessItem(context, item as AsyncResponseAction))
                return;
        }

        private bool ProcessItem(HttpContext context, byte[] item) {
            if (item == null)
                return false;
            
            context.Response.OutputStream.Write(item, 0, item.Length);
            return true;
        }

        private void ProcessItem(HttpContext context, ArraySegment<byte> item) {
            context.Response.OutputStream.Write(item.Array, item.Offset, item.Count);
        }

        private bool ProcessItem(HttpContext context, FileInfo item) {
            if (item == null)
                return false;
            
            context.Response.TransmitFile(item.FullName);
            return true;
        }

        private bool ProcessItem(HttpContext context, AsyncResponseAction item) {
            if(item == null)
                return false;

            item(nextItem => ProcessItem(context, nextItem), e => Error(context, e));
            return true;
        }
    }
}