Initial Commit

master
Harald Wolff 2018-07-20 14:40:58 +02:00
commit 0ce3b34093
23 changed files with 1136 additions and 0 deletions

41
.gitignore vendored 100644
View File

@ -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

33
Program.cs 100644
View File

@ -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();
}
}
}

View File

@ -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("")]

83
appsrv.csproj 100644
View File

@ -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>

View File

@ -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()
{
}
}
}

View File

@ -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();
}
}

84
connector/Http.cs 100644
View File

@ -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");
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace appsrv.exceptions
{
public class IllegalRequestException : Exception
{
public IllegalRequestException(String requestLine)
:base(requestLine)
{
}
}
}

View File

@ -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))
{
}
}
}

View File

@ -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";
}
}
}

View File

@ -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();
}
}
}

26
mime/MimeHelper.cs 100644
View File

@ -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";
}
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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();
}
}
}
}
}

View File

@ -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());
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}
}

21
test/StaticTest.cs 100644
View File

@ -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");
}
}
}

9
www/index.html 100644
View File

@ -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>