Initial Commit
commit
0ce3b34093
|
@ -0,0 +1,41 @@
|
|||
# Autosave files
|
||||
*~
|
||||
|
||||
# build
|
||||
[Oo]bj/
|
||||
[Bb]in/
|
||||
packages/
|
||||
TestResults/
|
||||
|
||||
# globs
|
||||
Makefile.in
|
||||
*.DS_Store
|
||||
*.sln.cache
|
||||
*.suo
|
||||
*.cache
|
||||
*.pidb
|
||||
*.userprefs
|
||||
*.usertasks
|
||||
config.log
|
||||
config.make
|
||||
config.status
|
||||
aclocal.m4
|
||||
install-sh
|
||||
autom4te.cache/
|
||||
*.user
|
||||
*.tar.gz
|
||||
tarballs/
|
||||
test-results/
|
||||
Thumbs.db
|
||||
.vs/
|
||||
|
||||
# Mac bundle stuff
|
||||
*.dmg
|
||||
*.app
|
||||
|
||||
# resharper
|
||||
*_Resharper.*
|
||||
*.Resharper
|
||||
|
||||
# dotCover
|
||||
*.dotCover
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using appsrv.server;
|
||||
using appsrv.protocol;
|
||||
using System.Threading;
|
||||
using appsrv.resources;
|
||||
using System.IO;
|
||||
using appsrv.test;
|
||||
|
||||
namespace appsrv
|
||||
{
|
||||
class MainClass
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
ApplicationServer server = new ApplicationServer();
|
||||
|
||||
Resource root = new DirectoryResource(new DirectoryInfo("./www"));
|
||||
server.AddRoot("localhost",root);
|
||||
|
||||
StaticClassResource staticClassResource = new StaticClassResource(typeof(StaticTest),root);
|
||||
|
||||
|
||||
Http http = new Http(server);
|
||||
|
||||
http.Start();
|
||||
|
||||
Thread.Sleep(10000);
|
||||
|
||||
http.Stop();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// Information about this assembly is defined by the following attributes.
|
||||
// Change them to the values specific to your project.
|
||||
|
||||
[assembly: AssemblyTitle("appsrv")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
|
||||
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
|
||||
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
// The following attributes are used to specify the signing key for the assembly,
|
||||
// if desired. See the Mono documentation for more information about signing.
|
||||
|
||||
//[assembly: AssemblyDelaySign(false)]
|
||||
//[assembly: AssemblyKeyFile("")]
|
|
@ -0,0 +1,83 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||
<ProjectGuid>{FD508FE5-5879-4C60-91D8-CA408E06361F}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>appsrv</RootNamespace>
|
||||
<AssemblyName>appsrv</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ExternalConsole>true</ExternalConsole>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ExternalConsole>true</ExternalConsole>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="connector\Http.cs" />
|
||||
<Compile Include="server\ApplicationServer.cs" />
|
||||
<Compile Include="connector\Connector.cs" />
|
||||
<Compile Include="server\Application.cs" />
|
||||
<Compile Include="server\HttpRequest.cs" />
|
||||
<Compile Include="exceptions\IllegalRequestException.cs" />
|
||||
<Compile Include="resources\Resource.cs" />
|
||||
<Compile Include="resources\FileResource.cs" />
|
||||
<Compile Include="resources\DirectoryResource.cs" />
|
||||
<Compile Include="exceptions\ApplicationServerException.cs" />
|
||||
<Compile Include="exceptions\ResourceNotFoundException.cs" />
|
||||
<Compile Include="mime\MimeHelper.cs" />
|
||||
<Compile Include="http\HttpStatusCodes.cs" />
|
||||
<Compile Include="resources\ResourceLink.cs" />
|
||||
<Compile Include="resources\StaticClassResource.cs" />
|
||||
<Compile Include="attributes\WebCallable.cs" />
|
||||
<Compile Include="test\StaticTest.cs" />
|
||||
<Compile Include="http\QueryStringParameters.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="connector\" />
|
||||
<Folder Include="server\" />
|
||||
<Folder Include="exceptions\" />
|
||||
<Folder Include="resources\" />
|
||||
<Folder Include="www\" />
|
||||
<Folder Include="www\gfx\" />
|
||||
<Folder Include="mime\" />
|
||||
<Folder Include="http\" />
|
||||
<Folder Include="attributes\" />
|
||||
<Folder Include="test\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="www\index.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
<ProjectExtensions>
|
||||
<MonoDevelop>
|
||||
<Properties>
|
||||
<Policies>
|
||||
<DotNetNamingPolicy ResourceNamePolicy="FileFormatDefault" DirectoryNamespaceAssociation="PrefixedHierarchical" />
|
||||
</Policies>
|
||||
</Properties>
|
||||
</MonoDevelop>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
namespace appsrv.attributes
|
||||
{
|
||||
public enum Serialization {
|
||||
PLAIN,
|
||||
JSON,
|
||||
XML
|
||||
}
|
||||
|
||||
public class WebCallable : Attribute
|
||||
{
|
||||
public String Name { get; set; }
|
||||
public Serialization Serialization { get; set; } = Serialization.PLAIN;
|
||||
|
||||
public WebCallable()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using appsrv.server;
|
||||
namespace appsrv.connector
|
||||
{
|
||||
public abstract class Connector
|
||||
{
|
||||
public ApplicationServer ApplicationServer { get; private set; }
|
||||
|
||||
public Connector(ApplicationServer applicationServer)
|
||||
{
|
||||
this.ApplicationServer = applicationServer;
|
||||
}
|
||||
|
||||
public abstract void Start();
|
||||
public abstract void Stop();
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using appsrv.connector;
|
||||
using appsrv.server;
|
||||
using System.Threading;
|
||||
|
||||
namespace appsrv.protocol
|
||||
{
|
||||
public class Http : Connector
|
||||
{
|
||||
public static int backlog = 5;
|
||||
public static int defaultPort = 8080;
|
||||
public static bool exclusivePortListener = false;
|
||||
|
||||
IPEndPoint endPoint;
|
||||
TcpListener tcpListener;
|
||||
Thread tAccept;
|
||||
|
||||
public Http(ApplicationServer appServer)
|
||||
:this(appServer,new IPEndPoint(IPAddress.Any, defaultPort))
|
||||
{
|
||||
}
|
||||
|
||||
public Http(ApplicationServer appServer,IPEndPoint endPoint)
|
||||
: base(appServer)
|
||||
{
|
||||
this.endPoint = endPoint;
|
||||
this.tcpListener = new TcpListener(this.endPoint);
|
||||
this.tcpListener.ExclusiveAddressUse = exclusivePortListener;
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
if (
|
||||
(tAccept == null) ||
|
||||
(!tAccept.IsAlive))
|
||||
{
|
||||
this.tcpListener.Start(backlog);
|
||||
tAccept = new Thread(new ThreadStart(() => acceptRequests()));
|
||||
tAccept.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
this.tcpListener.Stop();
|
||||
}
|
||||
|
||||
private void acceptRequests(){
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
while (true)
|
||||
{
|
||||
TcpClient client = this.tcpListener.AcceptTcpClient();
|
||||
|
||||
try
|
||||
{
|
||||
HttpRequest request = new HttpRequest(this.ApplicationServer, client);
|
||||
|
||||
Console.WriteLine("new request: {0}",request);
|
||||
|
||||
Thread t = new Thread(() => request.Handle());
|
||||
t.Start();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("Exception: {0}", e);
|
||||
}
|
||||
|
||||
}
|
||||
} catch (SocketException e){
|
||||
Console.WriteLine("Http connector interupted");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
namespace appsrv.exceptions
|
||||
{
|
||||
public class ApplicationServerException : Exception
|
||||
{
|
||||
public int StatusCode { get; } = 500;
|
||||
|
||||
public ApplicationServerException(String message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
public ApplicationServerException(String message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
public ApplicationServerException(int statusCode,String message)
|
||||
: base(message)
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
}
|
||||
public ApplicationServerException(int statusCode,String message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
namespace appsrv.exceptions
|
||||
{
|
||||
public class IllegalRequestException : Exception
|
||||
{
|
||||
public IllegalRequestException(String requestLine)
|
||||
:base(requestLine)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
namespace appsrv.exceptions
|
||||
{
|
||||
public class ResourceNotFoundException : ApplicationServerException
|
||||
{
|
||||
public ResourceNotFoundException(String resourcePath, String nextResource)
|
||||
: base(404, String.Format("Could not find resource \"{0}\" within \"{1}\"", nextResource, resourcePath))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
namespace appsrv.http
|
||||
{
|
||||
public class HttpStatusCodes
|
||||
{
|
||||
static Dictionary<int, string> statusMessages = new Dictionary<int, string>()
|
||||
{
|
||||
{ 200, "Ok" },
|
||||
{ 403, "Access denied" },
|
||||
{ 404, "Not Found" }
|
||||
};
|
||||
|
||||
public static String GetStatusMessage(int code){
|
||||
if (statusMessages.ContainsKey(code))
|
||||
return statusMessages[code];
|
||||
return "Unknown Status";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
namespace appsrv.http
|
||||
{
|
||||
public class QueryStringParameters : IDictionary<String,String>
|
||||
{
|
||||
Dictionary<string, string> parameters = new Dictionary<string, string>();
|
||||
|
||||
public QueryStringParameters(String query)
|
||||
{
|
||||
if (query.StartsWith("?"))
|
||||
query = query.Substring(1);
|
||||
|
||||
String[] pairs = query.Split(new char[] { '&' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (String pair in pairs)
|
||||
{
|
||||
String[] kv = pair.Split(new char[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries);
|
||||
string key = Uri.UnescapeDataString(kv[0].Replace('+',' '));
|
||||
string value = kv.Length == 2 ? Uri.UnescapeDataString(kv[1].Replace('+',' ')) : "";
|
||||
if (!key.Equals(String.Empty)){
|
||||
parameters[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string this[string key] {
|
||||
get => parameters[key];
|
||||
set => throw new NotImplementedException(); }
|
||||
|
||||
public ICollection<string> Keys => parameters.Keys;
|
||||
public ICollection<string> Values => parameters.Values;
|
||||
public int Count => parameters.Count;
|
||||
public bool IsReadOnly => true;
|
||||
|
||||
public void Add(string key, string value) => throw new NotImplementedException();
|
||||
public void Add(KeyValuePair<string, string> item) => throw new NotImplementedException();
|
||||
public void Clear() => throw new NotImplementedException();
|
||||
public bool Remove(string key) => throw new NotImplementedException();
|
||||
public bool Remove(KeyValuePair<string, string> item) => throw new NotImplementedException();
|
||||
|
||||
public bool Contains(KeyValuePair<string, string> item) => parameters.Contains(item);
|
||||
public bool ContainsKey(string key) => parameters.ContainsKey(key);
|
||||
public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex) => ((IDictionary<string, string>)parameters).CopyTo(array, arrayIndex);
|
||||
public IEnumerator<KeyValuePair<string, string>> GetEnumerator() => parameters.GetEnumerator();
|
||||
public bool TryGetValue(string key, out string value) => parameters.TryGetValue(key, out value);
|
||||
IEnumerator IEnumerable.GetEnumerator() => parameters.GetEnumerator();
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
stringBuilder.Append("[Query");
|
||||
foreach (String key in parameters.Keys){
|
||||
stringBuilder.AppendFormat(" {0}={1}", key, parameters[key]);
|
||||
}
|
||||
stringBuilder.Append("]");
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
namespace appsrv.mime
|
||||
{
|
||||
public static class MimeHelper
|
||||
{
|
||||
static Dictionary<string, string> extToMIME = new Dictionary<string, string>()
|
||||
{
|
||||
{"html","text/html"},
|
||||
{"htm","text/html"},
|
||||
{"xml","text/xml"},
|
||||
{"txt","text/plain"}
|
||||
};
|
||||
|
||||
|
||||
|
||||
public static String GuessMIMEFromFilename(String filename){
|
||||
foreach (String ext in extToMIME.Keys){
|
||||
if (filename.EndsWith(ext))
|
||||
return extToMIME[ext];
|
||||
}
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using appsrv.server;
|
||||
using System.Collections.Generic;
|
||||
using appsrv.exceptions;
|
||||
|
||||
namespace appsrv.resources
|
||||
{
|
||||
public class DirectoryResource : Resource
|
||||
{
|
||||
public DirectoryInfo DirectoryInfo { get; }
|
||||
public bool IndexingEnabled { get; set; }
|
||||
|
||||
public DirectoryResource(DirectoryInfo directoryInfo)
|
||||
:this(directoryInfo,null)
|
||||
{
|
||||
}
|
||||
public DirectoryResource(DirectoryInfo directoryInfo,Resource container)
|
||||
:base(directoryInfo.Name,container)
|
||||
{
|
||||
DirectoryInfo = directoryInfo;
|
||||
}
|
||||
|
||||
public override void Hit(Stack<string> requestPath, HttpRequest request)
|
||||
{
|
||||
if (requestPath.Count > 0)
|
||||
{
|
||||
String nextResourceName = requestPath.Pop();
|
||||
|
||||
DirectoryInfo[] directoryInfos = DirectoryInfo.GetDirectories(nextResourceName);
|
||||
if (directoryInfos.Length == 1){
|
||||
DirectoryResource directoryResource = new DirectoryResource(directoryInfos[0],this);
|
||||
directoryResource.Request(requestPath, request);
|
||||
} else {
|
||||
FileInfo[] fileInfos = DirectoryInfo.GetFiles(nextResourceName);
|
||||
if (fileInfos.Length == 1){
|
||||
FileResource fileResource = new FileResource(fileInfos[0],this);
|
||||
fileResource.Request(requestPath, request);
|
||||
} else {
|
||||
throw new ResourceNotFoundException(Path, nextResourceName);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (IndexingEnabled){
|
||||
|
||||
// ToDo: Create index...
|
||||
|
||||
} else {
|
||||
base.Hit(requestPath, request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
using System;
|
||||
using appsrv.server;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using appsrv.exceptions;
|
||||
using appsrv.mime;
|
||||
|
||||
namespace appsrv.resources
|
||||
{
|
||||
public class FileResource : Resource
|
||||
{
|
||||
public FileInfo FileInfo { get; }
|
||||
public bool DiscardRequestPath { get; set; }
|
||||
|
||||
public FileResource(FileInfo fileInfo,Resource container)
|
||||
:base(fileInfo.Name,container)
|
||||
{
|
||||
FileInfo = fileInfo;
|
||||
}
|
||||
|
||||
public override void Hit(Stack<string> requestPath, HttpRequest request)
|
||||
{
|
||||
if ((requestPath.Count > 0) && !DiscardRequestPath){
|
||||
throw new ApplicationServerException(String.Format("No resources below {0}",Path));
|
||||
} else {
|
||||
|
||||
request.SetResponseHeader("Content-Type", MimeHelper.GuessMIMEFromFilename(FileInfo.Name));
|
||||
|
||||
using (FileStream fileStream = new FileStream(FileInfo.FullName,FileMode.Open))
|
||||
{
|
||||
fileStream.CopyTo(request.ResponseStream);
|
||||
fileStream.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
using System;
|
||||
using appsrv.server;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using appsrv.exceptions;
|
||||
using System.Linq;
|
||||
namespace appsrv.resources
|
||||
{
|
||||
public abstract class Resource
|
||||
{
|
||||
public Resource Container { get; }
|
||||
public String Name { get; }
|
||||
|
||||
Dictionary<String, Resource> resources = new Dictionary<string, Resource>();
|
||||
|
||||
|
||||
public Resource(String name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
public Resource(String name,Resource container)
|
||||
{
|
||||
Name = name;
|
||||
Container = container;
|
||||
if (container != null)
|
||||
container.Add(this);
|
||||
}
|
||||
|
||||
protected virtual void Add(Resource resource){
|
||||
resources.Add(resource.Name, resource);
|
||||
}
|
||||
protected virtual void Remove(Resource resource){
|
||||
if (resources.ContainsValue(resource)){
|
||||
resources.Remove(resource.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(String resName)
|
||||
{
|
||||
return resources.ContainsKey(resName);
|
||||
}
|
||||
public bool Contains(Resource resource)
|
||||
{
|
||||
return resources.ContainsValue(resource);
|
||||
}
|
||||
|
||||
public ISet<Resource> Resources { get => new HashSet<Resource>(resources.Values); }
|
||||
|
||||
public Resource Root {
|
||||
get {
|
||||
if (Container == null)
|
||||
return this;
|
||||
else
|
||||
return Container.Root;
|
||||
}
|
||||
}
|
||||
|
||||
public IList<String> PathList {
|
||||
get {
|
||||
if (Container != null)
|
||||
{
|
||||
IList<String> pl = Container.PathList;
|
||||
pl.Add(Name);
|
||||
return pl;
|
||||
} else {
|
||||
return new List<String>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String Path {
|
||||
get {
|
||||
return String.Format("/{0}",String.Join("/",PathList));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual Resource this[string name]{
|
||||
get => resources[name];
|
||||
}
|
||||
|
||||
public virtual void Request(Stack<String> requestPath, HttpRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
if ((requestPath.Count > 0) && (Contains(requestPath.Peek())))
|
||||
{
|
||||
this[requestPath.Pop()].Request(requestPath, request);
|
||||
}
|
||||
else
|
||||
{
|
||||
Hit(requestPath, request);
|
||||
}
|
||||
}
|
||||
catch (ApplicationServerException ase)
|
||||
{
|
||||
HandleException(ase);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Hit(Stack<String> requestPath, HttpRequest request){
|
||||
if (requestPath.Count > 0){
|
||||
throw new ResourceNotFoundException(Path, requestPath.Peek());
|
||||
} else {
|
||||
throw new ApplicationServerException("unimplemented resource has been hit");
|
||||
}
|
||||
}
|
||||
|
||||
public Resource FindByPath(String path)
|
||||
{
|
||||
String[] pathTokens = path.Split(new char[]{'/'},StringSplitOptions.RemoveEmptyEntries);
|
||||
if (path[0] == '/')
|
||||
return Root.FindByPath(pathTokens);
|
||||
else
|
||||
return FindByPath(pathTokens);
|
||||
}
|
||||
public Resource FindByPath(IEnumerable<String> path)
|
||||
{
|
||||
return FindByPath(path.GetEnumerator());
|
||||
}
|
||||
public Resource FindByPath(IEnumerator<String> path)
|
||||
{
|
||||
if (path.MoveNext())
|
||||
{
|
||||
return this[path.Current].FindByPath(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected void HandleException(ApplicationServerException ase){
|
||||
Console.WriteLine("ASE: " + ase.ToString());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using appsrv.server;
|
||||
|
||||
namespace appsrv.resources
|
||||
{
|
||||
public class ResourceLink : Resource
|
||||
{
|
||||
Resource Target { get; }
|
||||
|
||||
public ResourceLink(String name,Resource container,Resource target)
|
||||
:base(name,container)
|
||||
{
|
||||
Target = target;
|
||||
}
|
||||
|
||||
protected override void Add(Resource resource)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("This resource can't have children");
|
||||
}
|
||||
|
||||
public override void Request(Stack<string> requestPath, HttpRequest request)
|
||||
{
|
||||
Target.Request(requestPath, request);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using appsrv.attributes;
|
||||
using appsrv.server;
|
||||
using System.Text;
|
||||
|
||||
namespace appsrv.resources
|
||||
{
|
||||
public class StaticClassResource : Resource
|
||||
{
|
||||
Type Type { get; }
|
||||
|
||||
|
||||
Dictionary<string, FieldInfo> callableFields = new Dictionary<string, FieldInfo>();
|
||||
|
||||
public StaticClassResource(Type type,Resource container)
|
||||
:base(type.Name,container)
|
||||
{
|
||||
Type = type;
|
||||
|
||||
foreach (MethodInfo methodInfo in Type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)){
|
||||
WebCallable webCallable = methodInfo.GetCustomAttribute<WebCallable>();
|
||||
if (webCallable != null){
|
||||
new CallableMethodResource(methodInfo, this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
class CallableMethodResource : Resource
|
||||
{
|
||||
MethodInfo MethodInfo { get; }
|
||||
WebCallable WebCallable { get; }
|
||||
|
||||
public CallableMethodResource(MethodInfo methodInfo,Resource container)
|
||||
:base(
|
||||
methodInfo.GetCustomAttribute<WebCallable>() != null ?
|
||||
(methodInfo.GetCustomAttribute<WebCallable>().Name != null) ? methodInfo.GetCustomAttribute<WebCallable>().Name : methodInfo.Name
|
||||
:methodInfo.Name,
|
||||
container
|
||||
)
|
||||
{
|
||||
MethodInfo = methodInfo;
|
||||
WebCallable = methodInfo.GetCustomAttribute<WebCallable>();
|
||||
}
|
||||
|
||||
private object[] ReadParameters(HttpRequest request)
|
||||
{
|
||||
ParameterInfo[] parameters = MethodInfo.GetParameters();
|
||||
object[] p = new object[ parameters.Length ];
|
||||
|
||||
for (int n = 0; n < parameters.Length;n++)
|
||||
{
|
||||
ParameterInfo parameterInfo = parameters[n];
|
||||
object pvalue = request.Query[parameterInfo.Name];
|
||||
p[n] = Convert.ChangeType(pvalue, parameterInfo.ParameterType );
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
private void SerializeResult(HttpRequest request,object result){
|
||||
switch (WebCallable.Serialization){
|
||||
case Serialization.PLAIN:
|
||||
SerializePlain(request, result);
|
||||
break;
|
||||
case Serialization.JSON:
|
||||
break;
|
||||
case Serialization.XML:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void SerializePlain(HttpRequest request,object result){
|
||||
request.SetResponseHeader("Content-Type", "text/plain");
|
||||
byte[] plain = Encoding.UTF8.GetBytes(result.ToString());
|
||||
request.ResponseStream.Write(plain, 0, plain.Length);
|
||||
}
|
||||
|
||||
public override void Hit(Stack<string> requestPath, HttpRequest request)
|
||||
{
|
||||
object[] p = ReadParameters(request);
|
||||
object result = MethodInfo.Invoke(null, p);
|
||||
SerializeResult(request, result);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
namespace appsrv.server
|
||||
{
|
||||
public class Application
|
||||
{
|
||||
public ApplicationServer ApplicationServer { get; private set; }
|
||||
|
||||
public Application(ApplicationServer applicationServer)
|
||||
{
|
||||
this.ApplicationServer = applicationServer;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using appsrv.resources;
|
||||
using System.Linq;
|
||||
namespace appsrv.server
|
||||
{
|
||||
public class ApplicationServer
|
||||
{
|
||||
public Resource DefaultRoot { get; set; }
|
||||
|
||||
|
||||
Dictionary<String, Resource> roots = new Dictionary<string, Resource>();
|
||||
|
||||
public ApplicationServer()
|
||||
{
|
||||
}
|
||||
|
||||
public void AddRoot(String name,Resource rootResource){
|
||||
roots.Add(name, rootResource);
|
||||
if (DefaultRoot == null){
|
||||
DefaultRoot = rootResource;
|
||||
}
|
||||
}
|
||||
|
||||
public Resource FindRoot(String rootName)
|
||||
{
|
||||
if (roots.ContainsKey(rootName))
|
||||
return roots[rootName];
|
||||
|
||||
return DefaultRoot;
|
||||
}
|
||||
|
||||
public void HandleRequest(HttpRequest request){
|
||||
Resource rootResource = FindRoot(request.Hostname);
|
||||
Stack<String> requestPath = new Stack<string>(request.URI.AbsolutePath.Split(new char[] { '/' },StringSplitOptions.RemoveEmptyEntries).Reverse());
|
||||
|
||||
rootResource.Request(requestPath, request);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using appsrv.exceptions;
|
||||
using appsrv.http;
|
||||
|
||||
namespace appsrv.server
|
||||
{
|
||||
public class HttpRequest
|
||||
{
|
||||
private Stream stream;
|
||||
private StreamReader streamReader;
|
||||
private List<HttpRequest> currentRequests = new List<HttpRequest>();
|
||||
|
||||
private MemoryStream responseStream;
|
||||
private StreamWriter responseWriter;
|
||||
|
||||
Dictionary<String, String> requestHeaders = new Dictionary<string, string>();
|
||||
Dictionary<String, String> responseHeaders = new Dictionary<string, string>();
|
||||
|
||||
public EndPoint Client { get; private set; }
|
||||
|
||||
public ApplicationServer ApplicationServer { get; private set; }
|
||||
public Uri URI { get; private set; }
|
||||
|
||||
public String Method { get; private set; }
|
||||
public String RequestURL { get; private set; }
|
||||
public String Protocol { get; private set; }
|
||||
|
||||
public String Hostname { get; private set; }
|
||||
public int Port { get; private set; }
|
||||
|
||||
public QueryStringParameters Query { get; private set; }
|
||||
|
||||
|
||||
public int StatusCode { get; set; } = 200;
|
||||
|
||||
public HttpRequest(ApplicationServer applicationServer, TcpClient client)
|
||||
{
|
||||
this.ApplicationServer = applicationServer;
|
||||
|
||||
this.Client = client.Client.RemoteEndPoint;
|
||||
this.stream = client.GetStream();
|
||||
this.streamReader = new StreamReader(this.stream);
|
||||
|
||||
String rLine = this.streamReader.ReadLine();
|
||||
String[] rTokens = SplitWhitespace(
|
||||
rLine
|
||||
);
|
||||
Console.WriteLine("Tokens: {0}", rTokens);
|
||||
|
||||
if (rTokens.Length != 3){
|
||||
throw new IllegalRequestException(rLine);
|
||||
}
|
||||
|
||||
this.Method = rTokens[0];
|
||||
this.RequestURL = rTokens[1];
|
||||
this.Protocol = rTokens[2];
|
||||
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[HttpRequest: Client={0}, ApplicationServer={1}, Hostname={6} Port={7} URI={2}, Method={3}, RequestURL={4}, Protocol={5} Query={8}]", Client, ApplicationServer, URI, Method, RequestURL, Protocol, Hostname, Port,Query);
|
||||
}
|
||||
|
||||
public String GetRequestHeader(String name, String def = "")
|
||||
{
|
||||
name = name.ToLowerInvariant();
|
||||
|
||||
if (requestHeaders.ContainsKey(name))
|
||||
return requestHeaders[name];
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
public String GetResponseHeader(String name, String def = "")
|
||||
{
|
||||
name = name.ToLowerInvariant();
|
||||
|
||||
if (responseHeaders.ContainsKey(name))
|
||||
return responseHeaders[name];
|
||||
|
||||
return def;
|
||||
}
|
||||
public void SetResponseHeader(String name,String value){
|
||||
name = name.ToLowerInvariant();
|
||||
if (value == null)
|
||||
{
|
||||
this.responseHeaders.Remove(name);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.responseHeaders[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream ResponseStream
|
||||
{
|
||||
get {
|
||||
if (responseStream == null)
|
||||
responseStream = new MemoryStream();
|
||||
|
||||
return responseStream;
|
||||
}
|
||||
}
|
||||
|
||||
public TextWriter ResponseWriter
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.responseWriter == null)
|
||||
{
|
||||
this.responseWriter = new StreamWriter(this.responseStream);
|
||||
}
|
||||
return this.responseWriter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private String[] SplitWhitespace(String line){
|
||||
LinkedList<String> tokens = new LinkedList<string>();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int n = 0; n < line.Length;)
|
||||
{
|
||||
for (; n < line.Length && !Char.IsWhiteSpace(line[n]); n++)
|
||||
sb.Append(line[n]);
|
||||
|
||||
tokens.AddLast(sb.ToString());
|
||||
sb.Clear();
|
||||
|
||||
for (; n < line.Length && Char.IsWhiteSpace(line[n]); n++) {}
|
||||
}
|
||||
return tokens.ToArray();
|
||||
}
|
||||
|
||||
private void ReadHttpHeaders(){
|
||||
String headerLine = this.streamReader.ReadLine();
|
||||
String hName = null;
|
||||
|
||||
while (!headerLine.Equals(String.Empty)){
|
||||
if (Char.IsWhiteSpace(headerLine[0]) && hName != null)
|
||||
{
|
||||
requestHeaders[hName] = requestHeaders[hName] + "," + headerLine.Trim();
|
||||
} else {
|
||||
String[] split = headerLine.Split(new char[] { ':' }, 2);
|
||||
if (split.Length != 2){
|
||||
throw new IllegalRequestException("malformed header");
|
||||
}
|
||||
|
||||
hName = split[0].ToLowerInvariant();
|
||||
requestHeaders[hName] = split[1];
|
||||
}
|
||||
|
||||
headerLine = this.streamReader.ReadLine();
|
||||
}
|
||||
|
||||
foreach (String hname in requestHeaders.Keys.ToArray()){
|
||||
requestHeaders[hname] = requestHeaders[hname].Trim();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void InterpretRequestHeaders()
|
||||
{
|
||||
String host = GetRequestHeader("host");
|
||||
String[] hostTokens = host.Split(':');
|
||||
Hostname = hostTokens[0];
|
||||
if (hostTokens.Length > 1){
|
||||
Port = int.Parse(hostTokens[1]);
|
||||
}
|
||||
|
||||
URI = new Uri(String.Format("http://{0}:{1}/{2}", Hostname, Port, RequestURL));
|
||||
|
||||
Query = new QueryStringParameters(URI.Query);
|
||||
|
||||
}
|
||||
|
||||
private void SendResponse()
|
||||
{
|
||||
using (StreamWriter writer = new StreamWriter(this.stream))
|
||||
{
|
||||
ResponseStream.Position = 0;
|
||||
SetResponseHeader("Content-Length", responseStream.Length.ToString());
|
||||
|
||||
writer.WriteLine("{0} {1} {2}", Protocol, StatusCode, HttpStatusCodes.GetStatusMessage(StatusCode));
|
||||
foreach (String rhName in responseHeaders.Keys){
|
||||
writer.WriteLine("{0}: {1}", rhName, responseHeaders[rhName]);
|
||||
}
|
||||
writer.WriteLine();
|
||||
writer.Flush();
|
||||
|
||||
responseStream.CopyTo(this.stream);
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(){
|
||||
this.ReadHttpHeaders();
|
||||
this.InterpretRequestHeaders();
|
||||
|
||||
Console.WriteLine("Request Handled: {0}",this);
|
||||
|
||||
foreach (String key in this.requestHeaders.Keys){
|
||||
Console.WriteLine("HH: {0} = {1}",key,this.requestHeaders[key]);
|
||||
}
|
||||
|
||||
ApplicationServer.HandleRequest(this);
|
||||
|
||||
SendResponse();
|
||||
|
||||
this.streamReader.Close();
|
||||
this.stream.Close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using appsrv.attributes;
|
||||
namespace appsrv.test
|
||||
{
|
||||
|
||||
public class StaticTest
|
||||
{
|
||||
|
||||
[WebCallable]
|
||||
public static int Add(int a,int b){
|
||||
Console.WriteLine("StaticTest.Add({0},{1})", a, b);
|
||||
return a + b;
|
||||
}
|
||||
|
||||
[WebCallable]
|
||||
public static void Debug()
|
||||
{
|
||||
Console.WriteLine("StaticTest.Debug() has been called");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>appsrv test page</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>appsrv test page</h1>
|
||||
<p>A paragraph to see the file is displayed.</p>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue