This commit is contained in:
Andoni Morales Alastruey 2018-11-10 20:37:49 +01:00
Родитель b341070e1f
Коммит 75013dbb32
8 изменённых файлов: 0 добавлений и 600 удалений

Просмотреть файл

@ -1,185 +0,0 @@
//
// Copyright (C) 2018 Fluendo S.A.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace XAMLator.HttpServer
{
/// <summary>
/// Http host that handles HTTP request and dispatch them to a <see cref="IRequestProcessor"/>.
/// </summary>
public class HttpHost
{
readonly IRequestProcessor requestProcessor;
readonly string baseUri;
HttpListener listener;
public event EventHandler ClientConnected;
/// <summary>
/// Initializes a new instance of the <see cref="T:VAS.Services.HttpServer.HttpHost"/> class.
/// </summary>
/// <param name="baseUri">Base URI.</param>
/// <param name="requestProcessor">Request processor.</param>
public HttpHost(string baseUri, IRequestProcessor requestProcessor)
{
this.baseUri = baseUri;
this.requestProcessor = requestProcessor;
}
public static async Task<HttpHost> StartServer(IRequestProcessor processor, int port, int portsRange)
{
HttpHost host = null;
for (int i = 0; i < portsRange; i++)
{
try
{
host = new HttpHost($"http://+:{port}/", processor);
await host.StartListening();
break;
}
catch (Exception ex)
{
if (ex is SocketException || ex is HttpListenerException)
{
host = null;
port++;
Log.Exception(ex);
}
else
{
throw;
}
}
}
return host;
}
/// <summary>
/// Start the host.
/// </summary>
public Task<bool> StartListening()
{
var taskCompletion = new TaskCompletionSource<bool>();
Task.Factory.StartNew(() => Run(taskCompletion), TaskCreationOptions.LongRunning);
return taskCompletion.Task;
}
/// <summary>
/// Stop the host.
/// </summary>
public void StopListening()
{
listener.Stop();
}
async Task Run(TaskCompletionSource<bool> tcs)
{
try
{
listener = new HttpListener();
listener.Prefixes.Add(baseUri);
listener.Start();
}
catch (Exception ex)
{
tcs.SetException(ex);
}
Log.Information($"Http server listening at {baseUri}");
tcs.SetResult(true);
// Loop
for (; ; )
{
var c = await listener.GetContextAsync().ConfigureAwait(false);
try
{
ClientConnected?.Invoke(this, null);
await ProcessRequest(c).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Exception(ex);
}
}
}
async Task ProcessRequest(HttpListenerContext context)
{
HttpResponse response;
try
{
Log.Information($"Handling incomming request {context.Request.Url}");
var request = await GetRequest(context.Request);
response = await requestProcessor.HandleRequest(request);
Log.Information($"Request handled with response {response.StatusCode}");
}
catch (Exception ex)
{
Log.Error("Internal server error");
Log.Exception(ex);
response = new JsonHttpResponse { StatusCode = HttpStatusCode.InternalServerError };
}
try
{
await WriteResponse(response, context.Response);
}
catch (Exception ex)
{
Log.Exception(ex);
}
}
async Task WriteResponse(HttpResponse response, HttpListenerResponse httpListenerResponse)
{
httpListenerResponse.StatusCode = (int)response.StatusCode;
httpListenerResponse.ContentType = response.ContentType;
foreach (var header in response.Headers)
{
httpListenerResponse.AddHeader(header.Key, header.Value);
}
using (var output = httpListenerResponse.OutputStream)
{
await response.Stream(output);
}
}
async Task<HttpRequest> GetRequest(HttpListenerRequest request)
{
var headers = request.Headers.AllKeys.ToDictionary<string, string, IEnumerable<string>>(
key => key, request.Headers.GetValues);
if (request.ContentType == "application/json")
{
StreamReader sr = new StreamReader(request.InputStream, Encoding.UTF8);
string json = await sr.ReadToEndAsync();
return new JsonHttpRequest(request.HttpMethod, request.Url, request.InputStream, headers, json);
}
return new HttpRequest(request.HttpMethod, request.Url, request.InputStream, headers);
}
}
}

Просмотреть файл

@ -1,86 +0,0 @@
//
// Copyright (C) 2018 Fluendo S.A.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using Newtonsoft.Json.Linq;
namespace XAMLator.HttpServer
{
/// <summary>
/// Http request.
/// </summary>
public class HttpRequest
{
public HttpRequest(string httpMethod, Uri url, Stream stream, Dictionary<string, IEnumerable<string>> headers)
{
HttpMethod = httpMethod;
Url = url;
Body = stream;
Parameters = new ExpandoObject();
Headers = headers;
}
/// <summary>
/// Gets the Http method.
/// </summary>
/// <value>The Http method.</value>
public string HttpMethod { get; private set; }
/// <summary>
/// Gets the URL.
/// </summary>
/// <value>The URL.</value>
public Uri Url { get; private set; }
public Stream Body { get; private set; }
/// <summary>
/// Gets the query or url parameters of the request.
/// </summary>
/// <value>The parameters.</value>
public dynamic Parameters { get; private set; }
/// <summary>
/// Gets the headers of the request.
/// </summary>
/// <value>The headers.</value>
public Dictionary<string, IEnumerable<string>> Headers { get; private set; }
}
public class JsonHttpRequest : HttpRequest
{
public JsonHttpRequest(string httpMethod, Uri url, Stream stream, Dictionary<string,
IEnumerable<string>> headers, string json) :
base(httpMethod, url, stream, headers)
{
object data = Serializer.DeserializeJson(json);
if (data is JObject jData)
{
Data = jData.ToObject<Dictionary<string, string>>();
}
}
/// <summary>
/// Gets the data of the request, deseralized from the request body
/// </summary>
/// <value>The data.</value>
public object Data { get; private set; }
}
}

Просмотреть файл

@ -1,93 +0,0 @@
//
// Copyright (C) 2018 Fluendo S.A.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace XAMLator.HttpServer
{
/// <summary>
/// Http response.
/// </summary>
public abstract class HttpResponse
{
public HttpResponse()
{
Stream = WriteResponse;
Headers = new Dictionary<string, string>();
}
/// <summary>
/// Gets the response data.
/// </summary>
/// <value>The response data.</value>
public Func<Stream, Task> Stream { get; set; }
/// <summary>
/// Gets or sets the content-type of the response
/// </summary>
/// <value>The content-type.</value>
public string ContentType { get; set; }
/// <summary>
/// Gets or sets the status code of the response.
/// </summary>
/// <value>The status code.</value>
public HttpStatusCode StatusCode { get; set; } = HttpStatusCode.OK;
/// <summary>
/// Gets the headers of the request.
/// </summary>
/// <value>The headers.</value>
public Dictionary<string, string> Headers { get; private set; }
protected abstract Task WriteResponse(Stream stream);
}
/// <summary>
/// JSON Http response.
/// </summary>
public class JsonHttpResponse : HttpResponse
{
public JsonHttpResponse()
{
ContentType = "application/json";
}
/// <summary>
/// Gets the response data.
/// </summary>
/// <value>The response data.</value>
public object Data { get; set; }
protected override async Task WriteResponse(Stream stream)
{
if (Data != null)
{
var json = Serializer.SerializeJson(Data);
using (var sw = new StreamWriter(stream, new UTF8Encoding(false)))
{
await sw.WriteAsync(json);
}
}
}
}
}

Просмотреть файл

@ -1,34 +0,0 @@
//
// Copyright (C) 2018 Fluendo S.A.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
using System.Threading.Tasks;
namespace XAMLator.HttpServer
{
/// <summary>
/// Process HTTP request from the <see cref="HttpHost"/>.
/// </summary>
public interface IRequestProcessor
{
/// <summary>
/// Handles the Http request.
/// </summary>
/// <returns>The response.</returns>
/// <param name="request">The request.</param>
Task<HttpResponse> HandleRequest(HttpRequest request);
}
}

Просмотреть файл

@ -1,87 +0,0 @@
//
// Copyright (C) 2018 Fluendo S.A.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace XAMLator.HttpServer
{
/// <summary>
/// Process request based on their URL and the http method.
/// </summary>
public abstract class RequestProcessor : IRequestProcessor
{
const string GET = "GET";
const string POST = "POST";
const string PUT = "PUT";
readonly List<Route> routes = new List<Route>();
public async Task<HttpResponse> HandleRequest(HttpRequest request)
{
var route = routes.FirstOrDefault(r => r.IsMatch(request));
if (route == null)
{
return new JsonHttpResponse { StatusCode = HttpStatusCode.NotFound };
}
return await route.Invoke(request);
}
/// <summary>
/// Callbacks router for Get requests
/// </summary>
protected RouteBuilder Get { get { return new RouteBuilder(GET, this); } }
/// <summary>
/// Callbacks router for Post requests
/// </summary>
protected RouteBuilder Post { get { return new RouteBuilder(POST, this); } }
/// <summary>
/// Callbacks router for Put requests
/// </summary>
protected RouteBuilder Put { get { return new RouteBuilder(PUT, this); } }
protected class RouteBuilder
{
readonly string method;
readonly RequestProcessor requestProcessor;
public RouteBuilder(string method, RequestProcessor requestProcessor)
{
this.method = method;
this.requestProcessor = requestProcessor;
}
public Func<HttpRequest, Task<HttpResponse>> this[string path]
{
set
{
AddRoute(path, value);
}
}
void AddRoute(string path, Func<HttpRequest, Task<HttpResponse>> value)
{
requestProcessor.routes.Add(new Route(method, path, value));
}
}
}
}

Просмотреть файл

@ -1,105 +0,0 @@
//
// Copyright (C) 2018 Fluendo S.A.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace XAMLator.HttpServer
{
/// <summary>
/// Route that binds a handler for a resource based in the Http method and the path of the request.
/// </summary>
public class Route
{
static readonly Regex PathRegex = new Regex(@"\{(?<param>\S+)\}", RegexOptions.Compiled);
readonly string[] paramNames;
public Route(string method, string path, Func<HttpRequest, Task<HttpResponse>> action)
{
Method = method;
Path = path;
Action = action;
var paramNames = new List<string>();
Regex = new Regex("^" + PathRegex.Replace(path, m =>
{
var p = m.Groups["param"].Value; paramNames.Add(p);
return String.Format(@"(?<{0}>\S+)", p);
}) + "$");
this.paramNames = paramNames.ToArray();
}
/// <summary>
/// Gets the Http method.
/// </summary>
/// <value>The Http method.</value>
string Method { get; set; }
/// <summary>
/// Gets the resource path.
/// </summary>
/// <value>The path.</value>
string Path { get; set; }
/// <summary>
/// Gets the action that will handle the request.
/// </summary>
/// <value>The action.</value>
Func<HttpRequest, Task<HttpResponse>> Action { get; set; }
Regex Regex { get; set; }
/// <summary>
/// Checks if a request should be handled by this match.
/// </summary>
/// <returns><c>true</c>, if there is match, <c>false</c> otherwise.</returns>
/// <param name="request">Request.</param>
public bool IsMatch(HttpRequest request)
{
return request.HttpMethod == Method && Regex.IsMatch(request.Url.AbsolutePath);
}
/// <summary>
/// Calls the resource handler registered for this route.
/// </summary>
/// <returns>The response.</returns>
/// <param name="request">The request.</param>
public Task<HttpResponse> Invoke(HttpRequest request)
{
var match = Regex.Match(request.Url.AbsolutePath);
var urlParameters = paramNames.ToDictionary(k => k, k => match.Groups[k].Value);
//NameValueCollection queryParameters = HttpUtility.ParseQueryString(request.Url.Query);
NameValueCollection queryParameters = new NameValueCollection();
var parameters = request.Parameters as IDictionary<string, Object>;
foreach (var kv in urlParameters)
{
parameters.Add(kv.Key, kv.Value);
}
foreach (String key in queryParameters.AllKeys)
{
parameters.Add(key, queryParameters[key]);
}
return Action.Invoke(request);
}
}
}

Просмотреть файл

@ -15,16 +15,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Constants.cs" />
<Compile Include="$(MSBuildThisFileDirectory)EvalRequest.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ExtensionMethods.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HttpServer\HttpHost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HttpServer\HttpRequest.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HttpServer\HttpResponse.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HttpServer\IRequestProcessor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HttpServer\RequestProcessor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HttpServer\Route.cs" />
<Compile Include="$(MSBuildThisFileDirectory)NetworkUtils.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TcpCommunicator.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="$(MSBuildThisFileDirectory)HttpServer\" />
</ItemGroup>
</Project>

Просмотреть файл

@ -6,7 +6,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using XAMLator.HttpServer;
namespace XAMLator.Server
{