1
0
Fork 0
mirror of https://gitlab.com/sbrl/GalleryShare.git synced 2018-06-12 22:45:16 +00:00

Initial routing rewrite.

The routes aren't functioning correctly yet.
This commit is contained in:
Starbeamrainbowlabs 2016-07-16 15:46:53 +01:00
parent de2117f9bf
commit 60b7560d21
10 changed files with 408 additions and 143 deletions

View file

@ -10,26 +10,17 @@ using System.Xml;
using System.Reflection;
using System.Drawing;
using GalleryShare.RequestRouter;
namespace GalleryShare
{
enum OutputFunction
public class GalleryServer
{
None,
SpecialFile,
DirectoryListing,
SendFile
}
class GalleryServer
{
static MimeSharp.Mime mimeDB = new MimeSharp.Mime();
int port;
string servingDirectory = Environment.CurrentDirectory;
Size thumbnailSize = new Size(300, 200);
HttpListener server = new HttpListener();
MasterHttpRouter router;
string prefix;
Dictionary<string, string> pathReplacements = new Dictionary<string, string>()
@ -66,6 +57,11 @@ namespace GalleryShare
/// </summary>
public async Task Start()
{
Console.Write("Setting up router...");
router = new MasterHttpRouter(this, "GalleryShare");
router.UrlTransformer = GetFullReqestedPath;
Console.WriteLine("done.");
server.Start();
Console.WriteLine("Listening for requests on {0}.", prefix);
Console.WriteLine("Serving from {0}. Browser url: http://localhost:{1}/", servingDirectory, Port);
@ -82,36 +78,7 @@ namespace GalleryShare
/// <param name="cycle">The Http request to handle.</param>
private async Task Handle(HttpListenerContext cycle)
{
OutputFunction outFunction = OutputFunction.None;
if (cycle.Request.RawUrl.StartsWith("/!"))
outFunction = OutputFunction.SpecialFile;
string requestedPath = GetFullReqestedPath(cycle.Request.RawUrl);
if (Directory.Exists(requestedPath))
outFunction = OutputFunction.DirectoryListing;
if (File.Exists(requestedPath))
outFunction = OutputFunction.SendFile;
switch(outFunction)
{
case OutputFunction.SpecialFile:
await sendSpecialFile(cycle);
break;
case OutputFunction.DirectoryListing:
cycle.Response.ContentType = "application/xml";
await sendDirectoryListing(cycle.Response.OutputStream, cycle.Request.RawUrl, requestedPath);
break;
case OutputFunction.SendFile:
await sendFile(cycle, requestedPath);
break;
default:
await sendMessage(cycle, 404, "Error: File or directory '{0}' not found.", requestedPath);
break;
}
await router.RouteRequest(cycle);
logCycle(cycle);
cycle.Response.Close();
@ -127,16 +94,6 @@ namespace GalleryShare
return result;
}
private async Task sendMessage(HttpListenerContext cycle, int statusCode, string message, params object[] paramObjects)
{
StreamWriter responseData = new StreamWriter(cycle.Response.OutputStream);
cycle.Response.StatusCode = statusCode;
await responseData.WriteLineAsync(string.Format(message, paramObjects));
/*responseData.Close();
cycle.Response.Close();*/
}
private void logCycle(HttpListenerContext cycle)
{
Console.WriteLine("[{0}] [{1}] [{2}] {3} {4}",
@ -147,97 +104,6 @@ namespace GalleryShare
cycle.Request.RawUrl
);
}
private async Task sendDirectoryListing(Stream outgoingData, string rawUrl, string requestedPath)
{
List<string> dirFiles = new List<string>(Directory.GetFiles(requestedPath));
List<string> dirDirectories = new List<string>(Directory.GetDirectories(requestedPath));
XmlWriterSettings writerSettings = new XmlWriterSettings();
writerSettings.Async = true;
writerSettings.Indent = true;
writerSettings.IndentChars = "\t";
XmlWriter xmlData = XmlWriter.Create(outgoingData, writerSettings);
await xmlData.WriteStartDocumentAsync();
await xmlData.WriteProcessingInstructionAsync("xml-stylesheet", "type=\"text/xsl\" href=\"/!Transform-DirListing.xslt\"");
await xmlData.WriteStartElementAsync(null, "DirectoryListing", null);
await xmlData.WriteElementStringAsync(null, "CurrentDirectory", null, rawUrl);
await xmlData.WriteStartElementAsync(null, "Contents", null);
foreach (string directoryName in dirDirectories)
{
await xmlData.WriteStartElementAsync(null, "ListingEntry", null);
await xmlData.WriteAttributeStringAsync(null, "Type", null, "Directory");
await xmlData.WriteElementStringAsync(null, "Name", null, "/" + directoryName.Substring(servingDirectory.Length));
await xmlData.WriteElementStringAsync(null, "ItemCount", null, Directory.GetFileSystemEntries(directoryName).Length.ToString());
// TODO: Write out thumbnail url
await xmlData.WriteEndElementAsync();
}
foreach (string filename in dirFiles)
{
await xmlData.WriteStartElementAsync(null, "ListingEntry", null);
await xmlData.WriteAttributeStringAsync(null, "Type", null, "File");
await xmlData.WriteElementStringAsync(null, "Name", null, "/" + filename.Substring(servingDirectory.Length));
await xmlData.WriteEndElementAsync();
}
await xmlData.WriteEndDocumentAsync();
await xmlData.FlushAsync();
}
private async Task sendSpecialFile(HttpListenerContext cycle)
{
string specialFileName = cycle.Request.RawUrl.Substring(2);
string outputFileName = string.Empty;
switch(specialFileName)
{
case "Transform-DirListing.xslt":
cycle.Response.ContentType = "text/xsl";
outputFileName = @"GalleryShare.Embed.DirectoryListing.xslt";
break;
case "Theme.css":
cycle.Response.ContentType = "text/css";
outputFileName = @"GalleryShare.Embed.Theme.css";
break;
}
if (outputFileName == string.Empty)
{
await sendMessage(cycle, 404, "Error: Unknown special file '{0}' requested.", specialFileName);
return;
}
/*string[] resNames = Assembly.GetExecutingAssembly().GetManifestResourceNames();
foreach (string resName in resNames)
Console.WriteLine(resName);*/
byte[] xsltData = await Utilities.GetEmbeddedResourceContent(outputFileName);
await cycle.Response.OutputStream.WriteAsync(xsltData, 0, xsltData.Length);
}
private async Task sendFile(HttpListenerContext cycle, string requestedPath)
{
if(cycle.Request.QueryString["type"] == "thumbnail")
{
// Send a thumbnail!
Console.WriteLine("Sending thumbnail for '{0}'", requestedPath);
await ThumbnailGenerator.SendThumbnailPng(requestedPath, thumbnailSize, cycle);
return;
}
// Send the raw file
cycle.Response.ContentType = mimeDB.Lookup(requestedPath);
Stream fileData = File.OpenRead(requestedPath);
await fileData.CopyToAsync(cycle.Response.OutputStream);
}
}
}

View file

@ -44,11 +44,19 @@
<Compile Include="GalleryServer.cs" />
<Compile Include="Utilities.cs" />
<Compile Include="ThumbnailGenerator.cs" />
<Compile Include="RequestRouter\IRequestRoute.cs" />
<Compile Include="RequestRouter\MasterHttpRouter.cs" />
<Compile Include="RequestRouter\HttpRequestRouteAttribute.cs" />
<Compile Include="RequestRouter\RouteSendFile.cs" />
<Compile Include="RequestRouter\RouteSpecialFile.cs" />
<Compile Include="RequestRouter\RouteDirectoryListing.cs" />
<Compile Include="RequestRouter\RouteDefault.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="packages\Magick.NET-Q8-x64.7.0.2.100\build\net40-client\Magick.NET-Q8-x64.targets" Condition="Exists('packages\Magick.NET-Q8-x64.7.0.2.100\build\net40-client\Magick.NET-Q8-x64.targets')" />
<ItemGroup>
<Folder Include="Embed\" />
<Folder Include="RequestRouter\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Embed\**" />

View file

@ -0,0 +1,22 @@
using System;
namespace GalleryShare.RequestRouter
{
/// <summary>
/// Defines a class to be a http reqeust router that's part of the given routing group.
/// </summary>
/// <remarks>
/// This class is an attribute. Please don't try to inherit from it.
/// To create a new request router, please implement GalleryShare.RequestRouter.IRequestRoute, not this class.
/// </remarks>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class HttpRequestRoute : Attribute
{
public string RoutingGroup;
public HttpRequestRoute(string inRoutingGroup)
{
RoutingGroup = inRoutingGroup;
}
}
}

View file

@ -0,0 +1,34 @@
using System;
using System.Threading.Tasks;
using System.Net;
using System.Dynamic;
namespace GalleryShare.RequestRouter
{
public interface IRequestRoute
{
/// <summary>
/// The priority of the request route.
/// Higher priority routes will always be chosen to handle requests over lower priority ones.
/// Note that only 1 route may handle any given request.
/// </summary>
int Priority { get; }
void SetParentServer(GalleryServer inGalleryServer);
/// <summary>
/// Works out whether the request route hander can handle a request to the given path.
/// </summary>
/// <param name="urlPath">The path to check to see if it can be handled.</param>
/// <returns>Whether the request route can handle a request to the given path.</returns>
bool CanHandle(string rawUrl, string requestedPath);
/// <summary>
/// Handles a HTTP request asynchronously.
/// Note that the master request router will close the request for you, so you don't need to bother.
/// </summary>
/// <param name="cycle">The Http request to handle.</param>
/// <param name="requestedPath">The transformed url path of the given request.</param>
Task HandleRequestAsync(HttpListenerContext cycle, string requestedPath);
}
}

View file

@ -0,0 +1,78 @@
using System;
using System.Net;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Reflection;
using System.Security.Claims;
using System.Linq;
using System.Diagnostics;
namespace GalleryShare.RequestRouter
{
public delegate string UrlPathTransformer(string rawUrl);
public class MasterHttpRouter
{
public bool DebugMode = true;
List<IRequestRoute> requestRoutes = new List<IRequestRoute>();
UrlPathTransformer urlTransformer;
public UrlPathTransformer UrlTransformer
{
get { return urlTransformer; }
set { urlTransformer = value; }
}
public MasterHttpRouter(GalleryServer parentServer, string routingGroup)
{
// Add the default route
requestRoutes.Add(new RouteDefault());
// Search for and add the rest of the routes
foreach(Type currentType in getRequestRouters(routingGroup))
{
IRequestRoute nextRoute = (IRequestRoute)Activator.CreateInstance(currentType);
nextRoute.SetParentServer(parentServer);
requestRoutes.Add(nextRoute);
}
// Sort the request reoutes by priority
requestRoutes.Sort((routeA, routeB) => -routeA.Priority.CompareTo(routeB.Priority));
}
public async Task RouteRequest(HttpListenerContext cycle)
{
foreach(IRequestRoute currentRoute in requestRoutes)
{
if (DebugMode) Console.Write("Trying {0} (Priority {1}) - ", currentRoute.GetType(), currentRoute.Priority);
string transformedUrl = UrlTransformer(cycle.Request.RawUrl);
if (!currentRoute.CanHandle(cycle.Request.RawUrl, transformedUrl))
{
if(DebugMode) Console.WriteLine("false. Trying next route.");
continue;
}
if(DebugMode) Console.WriteLine("true. Sending to route.");
await currentRoute.HandleRequestAsync(cycle, transformedUrl);
break;
}
}
private IEnumerable<Type> getRequestRouters(string routingGroup)
{
foreach(Type requestRoute in Assembly.GetCallingAssembly().GetExportedTypes().Where(cType =>
cType.GetInterfaces().Contains(typeof(IRequestRoute))))
{
foreach(HttpRequestRoute attr in requestRoute.GetCustomAttributes())
{
if(attr.RoutingGroup == routingGroup)
{
yield return requestRoute;
break;
}
}
}
}
}
}

View file

@ -0,0 +1,39 @@
using System;
using GalleryShare.RequestRouter;
using System.Threading.Tasks;
using System.Net;
using System.IO;
namespace GalleryShare
{
public class RouteDefault : IRequestRoute
{
public int Priority { get; } = 0;
public string DefaultResponse { get; set; } = "Error: 404 - No route was found to handle the specified url.\n";
public RouteDefault()
{
}
public bool CanHandle(string rawUrl, string requestedPath)
{
return true;
}
public void SetParentServer(GalleryServer inParentServer)
{
}
public async Task HandleRequestAsync(HttpListenerContext cycle, string requestedPath)
{
cycle.Response.StatusCode = 404;
cycle.Response.ContentType = "text/plain";
cycle.Response.ContentLength64 = DefaultResponse.Length;
StreamWriter responseData = new StreamWriter(cycle.Response.OutputStream) { AutoFlush = true };
await responseData.WriteLineAsync(DefaultResponse);
}
}
}

View file

@ -0,0 +1,79 @@
using System;
using System.Net;
using System.Xml;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
namespace GalleryShare.RequestRouter
{
[HttpRequestRoute("GalleryShare")]
public class RouteDirectoryListing : IRequestRoute
{
GalleryServer parentServer;
public int Priority { get; } = 5;
public RouteDirectoryListing()
{
}
public bool CanHandle(string rawUrl, string requestedPath)
{
if(Directory.Exists(requestedPath))
return true;
return false;
}
public void SetParentServer(GalleryServer inParentServer)
{
parentServer = inParentServer;
}
public async Task HandleRequestAsync(HttpListenerContext cycle, string requestedPath)
{
cycle.Response.ContentType = "application/xml";
List<string> dirFiles = new List<string>(Directory.GetFiles(requestedPath));
List<string> dirDirectories = new List<string>(Directory.GetDirectories(requestedPath));
XmlWriterSettings writerSettings = new XmlWriterSettings();
writerSettings.Async = true;
writerSettings.Indent = true;
writerSettings.IndentChars = "\t";
XmlWriter xmlData = XmlWriter.Create(cycle.Response.OutputStream, writerSettings);
await xmlData.WriteStartDocumentAsync();
await xmlData.WriteProcessingInstructionAsync("xml-stylesheet", "type=\"text/xsl\" href=\"/!Transform-DirListing.xslt\"");
await xmlData.WriteStartElementAsync(null, "DirectoryListing", null);
await xmlData.WriteElementStringAsync(null, "CurrentDirectory", null, cycle.Request.RawUrl);
await xmlData.WriteStartElementAsync(null, "Contents", null);
foreach (string directoryName in dirDirectories)
{
await xmlData.WriteStartElementAsync(null, "ListingEntry", null);
await xmlData.WriteAttributeStringAsync(null, "Type", null, "Directory");
await xmlData.WriteElementStringAsync(null, "Name", null, "/" + directoryName.Substring(parentServer.ServingDirectory.Length));
await xmlData.WriteElementStringAsync(null, "ItemCount", null, Directory.GetFileSystemEntries(directoryName).Length.ToString());
// TODO: Write out thumbnail url
await xmlData.WriteEndElementAsync();
}
foreach (string filename in dirFiles)
{
await xmlData.WriteStartElementAsync(null, "ListingEntry", null);
await xmlData.WriteAttributeStringAsync(null, "Type", null, "File");
await xmlData.WriteElementStringAsync(null, "Name", null, "/" + filename.Substring(parentServer.ServingDirectory.Length));
await xmlData.WriteEndElementAsync();
}
await xmlData.WriteEndDocumentAsync();
await xmlData.FlushAsync();
}
}
}

View file

@ -0,0 +1,56 @@
using System;
using System.Threading.Tasks;
using System.Net;
using System.Drawing;
using System.IO;
namespace GalleryShare.RequestRouter
{
[HttpRequestRoute("GalleryShare")]
public class RouteSendFile : IRequestRoute
{
static MimeSharp.Mime mimeDB = new MimeSharp.Mime();
Size thumbnailSize = new Size(300, 200);
public int Priority { get; } = 5;
public RouteSendFile()
{
}
public void SetParentServer(GalleryServer inParentServer)
{
}
/// <remarks>
/// The SendFile route can only handle requests to paths that are a valid file on disk.
/// </remarks>
public bool CanHandle(string rawUrl, string requestedPath)
{
if (File.Exists(requestedPath))
return true;
return false;
}
public async Task HandleRequestAsync(HttpListenerContext cycle, string requestedPath)
{
if(cycle.Request.QueryString["type"] == "thumbnail")
{
// Send a thumbnail!
Console.WriteLine("Sending thumbnail for '{0}'", requestedPath);
await ThumbnailGenerator.SendThumbnailPng(requestedPath, thumbnailSize, cycle);
return;
}
// Send the raw file
cycle.Response.ContentType = mimeDB.Lookup(requestedPath);
Stream fileData = File.OpenRead(requestedPath);
await fileData.CopyToAsync(cycle.Response.OutputStream);
}
}
}

View file

@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Net;
namespace GalleryShare.RequestRouter
{
[HttpRequestRoute("GalleryShare")]
public class RouteSpecialFile : IRequestRoute
{
public int Priority { get; } = 5;
/// <summary>
/// A dictionary mapping special file names to their actual filenames and content types.
/// </summary>
Dictionary<string, SpecialFileEntry> specialFileMap = new Dictionary<string, SpecialFileEntry>()
{
{ "Transform-DirListing.xslt", new SpecialFileEntry(@"GalleryShare.Embed.DirectoryListing.xslt", "text/xsl") },
{ "Theme.css", new SpecialFileEntry(@"GalleryShare.Embed.Theme.css", "text/css") }
};
public RouteSpecialFile()
{
}
public void SetParentServer(GalleryServer inParentServer)
{
}
public bool CanHandle(string rawUrl, string requestedPath)
{
if (rawUrl.StartsWith("/!"))
return true;
return false;
}
public async Task HandleRequestAsync(HttpListenerContext cycle, string requestedPath)
{
string specialFileName = cycle.Request.RawUrl.Substring(2);
string outputFileName = string.Empty;
if(specialFileMap.ContainsKey(specialFileName))
{
outputFileName = specialFileMap[specialFileName].FileName;
cycle.Response.ContentType = specialFileMap[specialFileName].ContentType;
}
if (outputFileName == string.Empty)
{
await Utilities.SendMessage(cycle, 404, "Error: Unknown special file '{0}' requested.", specialFileName);
return;
}
byte[] xsltData = await Utilities.GetEmbeddedResourceContent(outputFileName);
await cycle.Response.OutputStream.WriteAsync(xsltData, 0, xsltData.Length);
}
}
class SpecialFileEntry
{
public string FileName;
public string ContentType;
public SpecialFileEntry(string inFileName, string inContentType)
{
FileName = inFileName;
ContentType = inContentType;
}
}
}

View file

@ -4,6 +4,7 @@ using System.Linq;
using System.Reflection;
using System.IO;
using System.Net.Mime;
using System.Net;
namespace GalleryShare
{
@ -46,6 +47,17 @@ namespace GalleryShare
stream.Dispose();
return embeddedContent;
}
public static async Task SendMessage(HttpListenerContext cycle, int statusCode, string message, params object[] paramObjects)
{
StreamWriter responseData = new StreamWriter(cycle.Response.OutputStream);
cycle.Response.StatusCode = statusCode;
await responseData.WriteLineAsync(string.Format(message, paramObjects));
/*responseData.Close();
cycle.Response.Close();*/
}
}
}