Url Mapping with C# Httplistener

URL mapping with C# HttpListener

You can get a similar effect without attributes

HttpListener listener = new HttpListener();
listener.Prefixes.Add("http://*:8080/");
listener.Start();
while (true)
{
HttpListenerContext ctx = listener.GetContext();
ThreadPool.QueueUserWorkItem((_) =>
{
string methodName = ctx.Request.Url.Segments[1].Replace("/", "");
string[] strParams = ctx.Request.Url
.Segments
.Skip(2)
.Select(s=>s.Replace("/",""))
.ToArray();

var method = this.GetType().GetMethod(methodName);
object[] @params = method.GetParameters()
.Select((p, i) => Convert.ChangeType(strParams[i], p.ParameterType))
.ToArray();

object ret = method.Invoke(this, @params);
string retstr = JsonConvert.SerializeObject(ret);
});

Usage would be:

http://localhost:8080/getPersonHandler/333

if you really want to use Attributes then

HttpListener listener = new HttpListener();
listener.Prefixes.Add("http://*:8080/");
listener.Start();
while (true)
{
HttpListenerContext ctx = listener.GetContext();
ThreadPool.QueueUserWorkItem((_) =>
{
string methodName = ctx.Request.Url.Segments[1].Replace("/", "");
string[] strParams = ctx.Request.Url
.Segments
.Skip(2)
.Select(s=>s.Replace("/",""))
.ToArray();

var method = this.GetType()
.GetMethods()
.Where(mi => mi.GetCustomAttributes(true).Any(attr => attr is Mapping && ((Mapping)attr).Map == methodName))
.First();

object[] @params = method.GetParameters()
.Select((p, i) => Convert.ChangeType(strParams[i], p.ParameterType))
.ToArray();

object ret = method.Invoke(this, @params);
string retstr = JsonConvert.SerializeObject(ret);
});
}

Then you can use as http://localhost:8080/Person/333 and your definitions would be

class Mapping : Attribute
{
public string Map;
public Mapping(string s)
{
Map = s;
}
}

[Mapping("Person")]
public void getPersonHandler(int id)
{
Console.WriteLine("<<<<" + id);
}

HttpListener how to serve images

I figured it out. I created a class to hold the data, content type and the request.RawUrl. Then, where I was passing a string, I changed it to pass the object I created.

So, for my WebServer class, my Run method looks like this:

public void Run()
{
ThreadPool.QueueUserWorkItem((o) =>
{
Console.WriteLine("StackLight Web Server is running...");

try
{
while (_listener.IsListening)
{
ThreadPool.QueueUserWorkItem((c) =>
{
var ctx = c as HttpListenerContext;

try
{
// set the content type
ctx.Response.Headers[HttpResponseHeader.ContentType] = SetContentType(ctx.Request.RawUrl);
WebServerRequestData data = new WebServerRequestData();

// store html content in a byte array
data = _responderMethod(ctx.Request);

string res = "";
if(data.ContentType.Contains("text"))
{
char[] chars = new char[data.Content.Length/sizeof(char)];
System.Buffer.BlockCopy(data.Content, 0, chars, 0, data.Content.Length);
res = new string(chars);
data.Content = Encoding.UTF8.GetBytes(res);
}

// this writes the html out from the byte array
ctx.Response.ContentLength64 = data.Content.Length;
ctx.Response.OutputStream.Write(data.Content, 0, data.Content.Length);
}
catch (Exception ex)
{
ConfigLogger.Instance.LogCritical(LogCategory, ex);
}
finally
{
ctx.Response.OutputStream.Close();
}
}, _listener.GetContext());
}
}
catch (Exception ex)
{
ConfigLogger.Instance.LogCritical(LogCategory, ex);
}
});
}

And my SendResponse method looks like this:

private static WebServerRequestData SendResponse(HttpListenerRequest request)
{
string dir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
string[] fileUrl = request.RawUrl.Split('/');

// routes
if (request.RawUrl.Contains("/"))
{
// this is the main page ('/'), all other routes can be accessed from here (including css, js, & images)
if (request.RawUrl.ToLower().Contains(".png") || request.RawUrl.ToLower().Contains(".ico") || request.RawUrl.ToLower().Contains(".jpg") || request.RawUrl.ToLower().Contains(".jpeg"))
{
try
{
string path = dir + Properties.Settings.Default.ImagesPath + fileUrl[fileUrl.Length - 1];

FileInfo fileInfo = new FileInfo(path);
path = dir + @"\public\imgs\" + fileInfo.Name;

byte[] output = File.ReadAllBytes(path);

_data = new WebServerRequestData() {Content = output, ContentType = "image/png", RawUrl = request.RawUrl};
//var temp = System.Text.Encoding.UTF8.GetString(output);

//return Convert.ToBase64String(output);
return _data;
}
catch(Exception ex)
{
ConfigLogger.Instance.LogError(LogCategory, "File could not be read.");
ConfigLogger.Instance.LogCritical(LogCategory, ex);
_errorString = string.Format("<html><head><title>Test</title></head><body>There was an error processing your request:<br />{0}</body></html>", ex.Message);
_byteData = new byte[_errorString.Length * sizeof(char)];
System.Buffer.BlockCopy(_errorString.ToCharArray(), 0, _byteData, 0, _byteData.Length);

_data = new WebServerRequestData() { Content = _byteData, ContentType = "text/html", RawUrl = request.RawUrl };
return _data;
}
}

I'm still cleaning up the code a bit but it now serves the images!

Oh... And here is the object I'm using:

public class WebServerRequestData
{
public string RawUrl { get; set; }
public string ContentType { get; set; }
public byte[] Content { get; set; }
public string RawData { get; set; }
}

C# HttpListener without using netsh to register a URI

I wrote this to elevate perms and add http ACL entries through netsh.

Users will get prompted to make changes top their system but it's better than nothing. You might want to do this in response to an AddressAccessDeniedException

public static class NetAclChecker
{
public static void AddAddress(string address)
{
AddAddress(address, Environment.UserDomainName, Environment.UserName);
}

public static void AddAddress(string address, string domain, string user)
{
string args = string.Format(@"http add urlacl url={0} user={1}\{2}", address, domain, user);

ProcessStartInfo psi = new ProcessStartInfo("netsh", args);
psi.Verb = "runas";
psi.CreateNoWindow = true;
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.UseShellExecute = true;

Process.Start(psi).WaitForExit();
}
}


Related Topics



Leave a reply



Submit