Commit 54097308 authored by Shani Elharrar's avatar Shani Elharrar

Added ModelBinder, bit of Refactor, Fixed a bug in HttpResponse.

parents 4b12be58 181bb352
......@@ -17,7 +17,6 @@
*/
using System;
using System.Net.Cache;
using System.Text;
using System.Threading.Tasks;
using uhttpsharp;
......
......@@ -23,10 +23,12 @@ using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using uhttpsharp;
using uhttpsharp.Handlers;
using uhttpsharp.Headers;
using uhttpsharp.Listeners;
using uhttpsharp.ModelBinders;
using uhttpsharp.RequestProviders;
using uhttpsharpdemo.Handlers;
......@@ -45,10 +47,11 @@ namespace uhttpsharpdemo
httpServer.Use(new TcpListenerAdapter(new TcpListener(IPAddress.Loopback, 82)));
//httpServer.Use(new ListenerSslDecorator(new TcpListenerAdapter(new TcpListener(IPAddress.Loopback, 443)), serverCertificate));
httpServer.Use(new SessionHandler<DateTime>(() => DateTime.Now));
//httpServer.Use(new SessionHandler<DateTime>(() => DateTime.Now));
httpServer.Use(new ExceptionHandler());
httpServer.Use(new TimingHandler());
httpServer.Use(new MyHandler());
httpServer.Use(new HttpRouter().With(string.Empty, new IndexHandler())
.With("about", new AboutHandler())
.With("strings", new RestHandler<string>(new StringsRestController(), new JsonResponseProvider())));
......@@ -68,6 +71,28 @@ namespace uhttpsharpdemo
}
}
class MyModel
{
public int MyProperty
{
get; set;
}
public MyModel Model
{
get; set;
}
}
internal class MyHandler : IHttpRequestHandler
{
public System.Threading.Tasks.Task Handle(IHttpContext context, Func<System.Threading.Tasks.Task> next)
{
var model = new ModelBinder(new ObjectActivator()).Get<MyModel>(context.Request.QueryString);
return Task.Factory.GetCompleted();
}
}
internal class SessionHandler<TSession> : IHttpRequestHandler
{
......
......@@ -2,7 +2,7 @@
<package>
<metadata>
<id>uHttpSharp</id>
<version>0.1.1</version>
<version>0.1.2</version>
<title>uHttpSharp</title>
<authors>Shani Elharrar, Joe White, Hüseyin Uslu</authors>
<owners>Shani Elharrar, Joe White, Hüseyin Uslu</owners>
......
......@@ -9,6 +9,7 @@ namespace uhttpsharp.Clients
Stream Stream { get; }
bool Connected { get; }
void Close();
EndPoint RemoteEndPoint { get; }
......
......@@ -31,6 +31,7 @@ namespace uhttpsharp.Handlers
static FileHandler()
{
DefaultMimeType = "text/plain";
MimeTypes = new Dictionary<string, string>
{
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using log4net.Filter;
namespace uhttpsharp.Headers
{
/// <summary>
/// A trivial implementation of <see cref="IHttpHeaders"/>
/// that is composed from multiple <see cref="IHttpHeaders"/>.
///
/// If value is found in more then one header,
/// Gets the first available value from by the order of the headers
/// given in the c'tor.
/// </summary>
public class CompositeHttpHeaders : IHttpHeaders
{
private static readonly IEqualityComparer<KeyValuePair<string, string>> HeaderComparer =
new KeyValueComparer<string, string, string>(k => k.Key, StringComparer.InvariantCultureIgnoreCase);
private readonly IEnumerable<IHttpHeaders> _children;
public CompositeHttpHeaders(IEnumerable<IHttpHeaders> children)
{
_children = children;
}
public CompositeHttpHeaders(params IHttpHeaders[] children)
{
_children = children;
}
public string GetByName(string name)
{
foreach (var child in _children)
{
string value;
if (child.TryGetByName(name, out value))
{
return value;
}
}
throw new KeyNotFoundException(string.Format("Header {0} was not found in any of the children headers.", name));
}
public bool TryGetByName(string name, out string value)
{
foreach (var child in _children)
{
if (child.TryGetByName(name, out value))
{
return true;
}
}
value = null;
return false;
}
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return _children.SelectMany(c => c).Distinct(HeaderComparer).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class KeyValueComparer<TKey, TValue, TOutput> : IEqualityComparer<KeyValuePair<TKey, TValue>>
{
private readonly Func<KeyValuePair<TKey, TValue>, TOutput> _outputFunc;
private readonly IEqualityComparer<TOutput> _outputComparer;
public KeyValueComparer(Func<KeyValuePair<TKey, TValue>, TOutput> outputFunc, IEqualityComparer<TOutput> outputComparer)
{
_outputFunc = outputFunc;
_outputComparer = outputComparer;
}
public bool Equals(KeyValuePair<TKey, TValue> x, KeyValuePair<TKey, TValue> y)
{
return _outputComparer.Equals(_outputFunc(x), _outputFunc(y));
}
public int GetHashCode(KeyValuePair<TKey, TValue> obj)
{
return _outputComparer.GetHashCode(_outputFunc(obj));
}
}
}
......@@ -3,11 +3,54 @@ using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace uhttpsharp.Headers
{
[DebuggerDisplay("{Count} Headers")]
[DebuggerTypeProxy(typeof(HttpHeadersDebuggerProxy))]
internal class ListHttpHeaders : IHttpHeaders
{
private readonly IList<KeyValuePair<string, string>> _values;
public ListHttpHeaders(IList<KeyValuePair<string, string>> values)
{
_values = values;
}
public string GetByName(string name)
{
return _values.Where(kvp => kvp.Key.Equals(name, StringComparison.InvariantCultureIgnoreCase)).Select(kvp => kvp.Value).First();
}
public bool TryGetByName(string name, out string value)
{
value = _values.Where(kvp => kvp.Key.Equals(name, StringComparison.InvariantCultureIgnoreCase)).Select(kvp => kvp.Value).FirstOrDefault();
return (value != default(string));
}
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return _values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
internal int Count
{
get
{
return _values.Count;
}
}
}
[DebuggerDisplay("{Count} Headers")]
[DebuggerTypeProxy(typeof(HttpHeadersDebuggerProxy))]
internal class HttpHeaders : IHttpHeaders
......
......@@ -16,6 +16,9 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.Remoting.Contexts;
using System.Text;
using log4net;
using System.Net;
......@@ -31,7 +34,7 @@ using uhttpsharp.RequestProviders;
namespace uhttpsharp
{
internal sealed class HttpClient
internal sealed class HttpClientHandler
{
private const string CrLf = "\r\n";
private static readonly byte[] CrLfBuffer = Encoding.UTF8.GetBytes(CrLf);
......@@ -39,23 +42,26 @@ namespace uhttpsharp
private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private readonly IClient _client;
private readonly Stream _stream;
private readonly IList<IHttpRequestHandler> _requestHandlers;
private readonly Func<IHttpContext, Task> _requestHandler;
private readonly IHttpRequestProvider _requestProvider;
private readonly EndPoint _remoteEndPoint;
private DateTime _lastOperationTime;
private readonly Stream _stream;
public HttpClient(IClient client, IList<IHttpRequestHandler> requestHandlers, IHttpRequestProvider requestProvider)
public HttpClientHandler(IClient client, Func<IHttpContext, Task> requestHandler, IHttpRequestProvider requestProvider)
{
_remoteEndPoint = client.RemoteEndPoint;
_client = client;
_requestHandlers = requestHandlers;
_requestProvider = requestProvider;
_requestHandler = requestHandler;
_requestProvider = requestProvider;
_stream = new BufferedStream(_client.Stream);
Logger.InfoFormat("Got Client {0}", _remoteEndPoint);
Task.Factory.StartNew(Process);
UpdateLastOperationTime();
}
private async void Process()
......@@ -64,48 +70,29 @@ namespace uhttpsharp
{
while (_client.Connected)
{
// TODO : Extract read and write limit to configuration.
var requestStream = new LimitedStream(_stream, readLimit: 1024*1024, writeLimit: 1024*1024);
var request = await _requestProvider.Provide(new StreamReader(requestStream)).ConfigureAwait(false);
// TODO : Configuration.
var limitedStream = new LimitedStream(_stream, readLimit: 1024*1024, writeLimit: 1024*1024);
var streamReader = new StreamReader(limitedStream);
var request = await _requestProvider.Provide(streamReader).ConfigureAwait(false);
if (request != null)
{
UpdateLastOperationTime();
var context = new HttpContext(request);
Logger.InfoFormat("{1} : Got request {0}", request.Uri, _client.RemoteEndPoint);
var getResponse = BuildHandlers(context)();
await getResponse.ConfigureAwait(false);
var response = context.Response;
await _requestHandler(context).ConfigureAwait(false);
if (response != null)
if (context.Response != null)
{
var streamWriter = new StreamWriter(requestStream);
// Headers
await response.WriteHeaders(streamWriter).ConfigureAwait(false);
// Cookies
if (context.Cookies.Touched)
{
await streamWriter.WriteAsync(context.Cookies.ToCookieData())
.ConfigureAwait(false);
await streamWriter.FlushAsync().ConfigureAwait(false);
}
// Empty Line
await requestStream.WriteAsync(CrLfBuffer, 0, CrLfBuffer.Length).ConfigureAwait(false);
// Body
await response.WriteResponse(streamWriter).ConfigureAwait(false);
if (!request.Headers.KeepAliveConnection() || response.CloseConnection)
{
_client.Close();
}
var streamWriter = new StreamWriter(limitedStream);
await WriteResponse(context, streamWriter);
}
UpdateLastOperationTime();
}
else
{
......@@ -113,33 +100,90 @@ namespace uhttpsharp
}
}
}
catch (SocketException)
catch (Exception e)
{
// Hate people who make bad calls.
Logger.Warn(string.Format("Error while serving : {0}", _remoteEndPoint), e);
_client.Close();
}
catch (IOException)
Logger.InfoFormat("Lost Client {0}", _remoteEndPoint);
}
private async Task WriteResponse(HttpContext context, StreamWriter writer)
{
IHttpResponse response = context.Response;
IHttpRequest request = context.Request;
// Headers
await response.WriteHeaders(writer).ConfigureAwait(false);
// Cookies
if (context.Cookies.Touched)
{
// Socket exceptions on read will be re-thrown as IOException by BufferedStream
// ALSO : LimitedStream throws IOException on limit exceed.
await writer.WriteAsync(context.Cookies.ToCookieData())
.ConfigureAwait(false);
await writer.FlushAsync().ConfigureAwait(false);
}
catch (Exception e)
// Empty Line
await writer.BaseStream.WriteAsync(CrLfBuffer, 0, CrLfBuffer.Length).ConfigureAwait(false);
// Body
await response.WriteResponse(writer).ConfigureAwait(false);
if (!request.Headers.KeepAliveConnection() || response.CloseConnection)
{
// Hate people who make bad calls.
Logger.Warn(string.Format("Error while serving : {0} : {1}{2}", _remoteEndPoint, Environment.NewLine, e));
_client.Close();
}
}
Logger.InfoFormat("Lost Client {0}", _remoteEndPoint);
public IClient Client
{
get { return _client; }
}
public void ForceClose()
{
_client.Close();
}
private Func<Task> BuildHandlers(IHttpContext context, int index = 0)
public DateTime LastOperationTime
{
if (index > _requestHandlers.Count)
get
{
return _lastOperationTime;
}
}
private void UpdateLastOperationTime()
{
// _lastOperationTime = DateTime.Now;
}
}
public static class RequestHandlersAggregateExtensions
{
public static Func<IHttpContext, Task> Aggregate(this IList<IHttpRequestHandler> handlers)
{
return handlers.Aggregate(0);
}
private static Func<IHttpContext, Task> Aggregate(this IList<IHttpRequestHandler> handlers, int index)
{
if (index == handlers.Count)
{
return null;
}
return () => _requestHandlers[index].Handle(context, BuildHandlers(context, index + 1));
var currentHandler = handlers[index];
var nextHandler = handlers.Aggregate(index + 1);
return context => currentHandler.Handle(context, () => nextHandler(context));
}
}
}
\ No newline at end of file
......@@ -75,8 +75,8 @@ namespace uhttpsharp
CacheHeaders(new StreamWriter(_headerStream));
}
public HttpResponse(HttpResponseCode code, byte[] contentStream, bool closeConnection)
: this (code, "text/html; charset=utf-8", new MemoryStream(contentStream), closeConnection)
public HttpResponse(HttpResponseCode code, byte[] contentStream, bool keepAliveConnection)
: this (code, "text/html; charset=utf-8", new MemoryStream(contentStream), keepAliveConnection)
{
}
......@@ -100,7 +100,8 @@ namespace uhttpsharp
{
ContentStream.Position = 0;
await ContentStream.CopyToAsync(writer.BaseStream).ConfigureAwait(false);
await writer.BaseStream.FlushAsync();
await writer.BaseStream.FlushAsync().ConfigureAwait(false);
}
public bool CloseConnection
......
......@@ -34,7 +34,6 @@ namespace uhttpsharp
private readonly IList<IHttpRequestHandler> _handlers = new List<IHttpRequestHandler>();
private readonly IList<IHttpListener> _listeners = new List<IHttpListener>();
private readonly IHttpRequestProvider _requestProvider;
......@@ -69,11 +68,13 @@ namespace uhttpsharp
private async void Listen(IHttpListener listener)
{
var aggregatedHandler = _handlers.Aggregate();
while (_isActive)
{
try
{
new HttpClient(await listener.GetClient().ConfigureAwait(false), _handlers, _requestProvider);
new HttpClientHandler(await listener.GetClient().ConfigureAwait(false), aggregatedHandler, _requestProvider);
}
catch (Exception e)
{
......

\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using uhttpsharp.Headers;
namespace uhttpsharp.ModelBinders
{
public interface IModelBinder
{
/// <summary>
/// Gets the object from the body of the given headers
/// </summary>
/// <param name="headers"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
T Get<T>(IHttpHeaders headers);
/// <summary>
/// Gets the object using the prefix from the given headers
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="headers"></param>
/// <param name="prefix"></param>
/// <returns></returns>
T Get<T>(IHttpHeaders headers, string prefix);
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using uhttpsharp.Headers;
namespace uhttpsharp.ModelBinders
{
public class ModelBinder : IModelBinder
{
private readonly IObjectActivator _activator;
public ModelBinder(IObjectActivator activator)
{
_activator = activator;
}
public T Get<T>(IHttpHeaders headers)
{
var retVal = _activator.Activate<T>(null);
foreach (var prop in retVal.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (prop.PropertyType.IsPrimitive)
{
string stringValue;
if (headers.TryGetByName(prop.Name, out stringValue))
{
var value = Convert.ChangeType(stringValue, prop.PropertyType);
prop.SetValue(retVal, value);
}
}
else
{
var value = Get(prop.PropertyType, headers, prop.Name);
prop.SetValue(retVal, value);
}
}
return retVal;
}
private object Get(Type type, IHttpHeaders headers, string prefix)
{
var retVal = _activator.Activate(type, null);
string val;
var settedValues =
retVal.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => headers.TryGetByName(prefix + "[" + p.Name + "]", out val)).ToList();
if (settedValues.Count == 0)
{
return null;
}
foreach (var prop in settedValues)
{
string stringValue;
if (headers.TryGetByName(prefix + "[" + prop.Name + "]", out stringValue))
{
object value = prop.PropertyType.IsPrimitive
? Convert.ChangeType(stringValue, prop.PropertyType)
: Get(prop.PropertyType, headers, prefix + "[" + prop.Name + "]");
prop.SetValue(retVal, value);
}
}
return retVal;
}
public T Get<T>(IHttpHeaders headers, string prefix)
{
return (T)Get(typeof(T), headers, prefix);
}
}
public class ObjectActivator : IObjectActivator
{
public object Activate(Type type, Func<string, Type, object> argumentGetter)
{
return Activator.CreateInstance(type);
}
}
public interface IObjectActivator
{
object Activate(Type type, Func<string, Type, object> argumentGetter);
}
public static class ObjectActivatorExtensions
{
public static T Activate<T>(this IObjectActivator activator, Func<string, Type, object> argumentGetter)
{
return (T) activator.Activate(typeof(T), argumentGetter);
}
}
}
......@@ -32,18 +32,21 @@ namespace uhttpsharp.RequestProviders
var queryString = GetQueryStringData(ref url);
var uri = new Uri(url, UriKind.Relative);
var headersRaw = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
var headersRaw = new List<KeyValuePair<string, string>>();
// get the headers
string line;
while (!string.IsNullOrEmpty((line = await streamReader.ReadLineAsync().ConfigureAwait(false))))
{
var headerKvp = SplitHeader(line);
headersRaw.Add(headerKvp.Key, headerKvp.Value);
{
string currentLine = line;
var headerKvp = SplitHeader(currentLine);
headersRaw.Add(headerKvp);
}
IHttpHeaders headers = new HttpHeaders(headersRaw);
IHttpHeaders post = await GetPostData(streamReader, headers);
IHttpHeaders headers = new ListHttpHeaders(headersRaw);
IHttpHeaders post = await GetPostData(streamReader, headers).ConfigureAwait(false);
return new HttpRequest(headers, httpMethod, httpProtocol, uri,
uri.OriginalString.Split(Separators, StringSplitOptions.RemoveEmptyEntries), queryString, post);
......@@ -70,7 +73,7 @@ namespace uhttpsharp.RequestProviders
IHttpHeaders post;
if (headers.TryGetByName("content-length", out postContentLength))
{
post = await HttpHeaders.FromPost(streamReader, postContentLength);
post = await HttpHeaders.FromPost(streamReader, postContentLength).ConfigureAwait(false);
}
else
{
......
......@@ -44,6 +44,7 @@
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Web" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
......@@ -61,6 +62,7 @@
<Compile Include="Handlers\IRestController.cs" />
<Compile Include="Handlers\JsonResponseProvider.cs" />
<Compile Include="Handlers\RestHandler.cs" />
<Compile Include="Headers\CompositeHttpHeaders.cs" />
<Compile Include="Headers\EmptyHttpHeaders.cs" />
<Compile Include="Headers\HttpHeaders.cs" />
<Compile Include="Headers\HttpHeadersDebuggerProxy.cs" />
......@@ -78,13 +80,14 @@
<Compile Include="HttpResponse.cs" />
<Compile Include="HttpServer.cs" />
<Compile Include="HttpServerExtensions.cs" />
<Compile Include="IEnumerable.cs" />
<Compile Include="IHttpContext.cs" />
<Compile Include="IHttpMethodProvider.cs" />
<Compile Include="LimitedStream.cs" />
<Compile Include="Listeners\IHttpListener.cs" />
<Compile Include="Listeners\SslListenerDecoerator.cs" />
<Compile Include="Listeners\TcpListenerAdapter.cs" />
<Compile Include="ModelBinders\IModelBinder.cs" />
<Compile Include="ModelBinders\ModelBinder.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="HttpResponseCode.cs" />
<Compile Include="RequestProviders\HttpRequestProvider.cs" />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment