HttpFileDispatcher in a .Net server

Hi, is there an example of how to implement the HttpFileDispatcher in a .Net server? I want to build a small middleware server that feeds html and javascript files back to the browser.

Thanks,
Thom

Hello

You can either take a look at the HttpFileDispatcher source code and use it a s a starting point. Or you can just use the HttpFileDispatcher instance and hook its Request event to provide required data.

Regards

Hi Anton,

So I found this code. The browser URL I am going to use is http://localhost:8099/pllogin.html. How would I change the below code to handle this URL:

static void Main(string[] args)
{
	var serverChannel = new IpHttpServerChannel();
	serverChannel.Port = 8099;

	var dispatcher = new HttpFileDispatcher();
	dispatcher.Path = "/pdf";
	dispatcher.Request += Dispatcher_Request;
	dispatcher.Server = serverChannel;

	serverChannel.Open();

	Console.WriteLine("Press ENTER to exit");
	Console.ReadLine();
}

private static void Dispatcher_Request(object sender, RequestEventArgs e)
{
	// Parse path here and read the corresponding PDF file contents
	// then put that content into e.Data

	e.Path = "file.pdf";
	e.Data = new MemoryStream(File.ReadAllBytes(...here should be a path to a sample PDF file...));
}

// Thom

Hello

Unfortunately HttpFileDispatcher cannot serve requests without path. This means that it can be configured to serve requests like http://localhost:8099/html/pllogin.html , but it won’t allow to configure it for serve request like http://localhost:8099/pllogin.html. Otherwise it would conflict with the Server Channel infrastructure.

Instead you could implement your own simple HTTP server (even with TLS/SSL support) using code like this one:

using System;
using System.IO;
using System.Net;
using RemObjects.SDK;
using RemObjects.SDK.Http;
using RemObjects.SDK.Server;

namespace SimpleHttpServer
{
	class Program
	{
		static void Main(string[] args)
		{
			Console.WriteLine("Starting server");

			var server = new SimpleHttpServer();
			server.Port = 8000;
			server.RootPath = @"r:\Tutorials\Bootstrap"; // Path to the source folder

			/*
			// Enable TLS protection
			server.SslOptions.UseTls = true;
			server.SslOptions.Enabled = true;
			server.SslOptions.CertificateFileName = @"..certificate file name ...";
			*/

			server.Open();
			Console.WriteLine($"Server is listening to port {server.Port}");
			Console.WriteLine("Press ENTER to stop the server");
			Console.ReadLine();

			server.Close();
		}
	}

	public class SimpleHttpServer : HttpServer
	{
		public String RootPath { get; set; }

		#region Overriden Methods
		protected override void HandleHttpRequest(Connection connection, HttpServerRequest request, HttpServerResponse response)
		{
			base.HandleHttpRequest(connection, request, response);

			if (response.ContentSource != ContentSource.ContentNone)
			{
				return;
			}

			if (request.Header.RequestType != "GET")
			{
				response.SendError(HttpStatusCode.BadRequest, String.Format("Request Type '{0}' not supported.", request.Header.RequestType));
				return;
			}

			// TODO Check HttpFileDispatcher.TryServeFile for a requested file path check code
			// Or just extract requested file from request.Header.RequestPath and serve it from some preloaded files cache 
			// This cache would contain a link between requested filename and file contents and its ContentType

			String filename = this.RootPath + request.Header.RequestPath.Replace('/', Path.DirectorySeparatorChar);
			if (filename.IndexOf("..", StringComparison.Ordinal) != -1)
			{
				response.SendError(HttpStatusCode.Forbidden, String.Format("Bad Request: Path '{0}' contains '..' which is invalid.", filename));
				return;
			}

			if (!File.Exists(filename))
			{
				response.SendError(HttpStatusCode.NotFound, String.Format("File '{0}' not found.", filename));
				return;
			}

			response.Header.ContentType = "text/html"; // This ContentType is hardcoded!
			response.ContentStream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read); // response.ContentBytes can be used here as well
			response.CloseStream = true; /* Response will close stream once it's been sent */
			 
		}
		#endregion
	}
}

This sample code uses the same internal infrastructure as the IpHttpServerChannel class

Thanks for the detailed answer.

I do not have a problem with changing the URL to something like http://localhost:8099/html/pllogin.html. I would like to use one of your template generated servers since it is easy to expand. For example I would also like to use HttpApi to receive a POST request which contains a JSON in the request body and returns a JSON. Also it is easy to switch from testing in GUI mode to running it as a service.

So with regards to previous code I can use this URL:

http://localhost:8099/html/pllogin.html

And change this to:

var dispatcher = new HttpFileDispatcher();
dispatcher.Path = “/html”;
dispatcher.Request += Dispatcher_Request;
dispatcher.Server = serverChannel;

But it is what to assign to the e.Path below that I am not sure about:

private static void Dispatcher_Request(object sender, RequestEventArgs e)
{
// Parse path here and read the corresponding PDF file contents
// then put that content into e.Data

e.Path = "file.pdf";
e.Data = new MemoryStream(File.ReadAllBytes(...here should be a path to a sample PDF file...));

}

// Thom

Sample code:

using System;
using System.IO;
using RemObjects.SDK.Helpers;
using RemObjects.SDK.Server;

namespace ROServer4
{
	static class Program
	{
		public static int Main(string[] args)
		{
			ApplicationServer server = new ApplicationServer("ROServer4");

			var channel = new IpHttpServerChannel();

			var dispatcher = new HttpFileDispatcher();
			dispatcher.Server = channel;
			dispatcher.Path = "/html";
			dispatcher.Request += Dispatcher_Request;
			dispatcher.DefaultFile = "index.html";

			server.NetworkServer.ServerChannel = channel;
			server.NetworkServer.Port = 8090;

			server.Run(args);
			return 0;
		}

		private static void Dispatcher_Request(object sender, RequestEventArgs e)
		{
			e.Path = "index.html";

			e.Data = new MemoryStream();
			StreamHelpers.Utf8StringToStream("Test Value", e.Data);
			e.Data.Position = 0;
		}
	}
}

The e.Path is used to determine ContentType of the response. In other words dispatcher extracts file extension form e.Path and uses it to determine proper ContentType.
Predefined content types are:

  • “.xml” -> “text/xml”

  • .html -> text/html

  • .htm -> text/html

  • .js -> application/x-javascript

  • .css -> text/css

  • .jpg -> image/jpeg

  • .jpeg -> image/jpeg

  • .png -> image/png

  • .pdf -> application/pdf

  • .gz -> application/x-gzip

  • .zip -> application/zip

Unknown extensions are resolved into ContentType via Microsoft.Win32.Registry.ClassesRoot registry node.

While this approach works for a file-based dispatcher, it might be an overkill for the case when Request event handler is used to provide some generated data. So starting next Remoting SDK build it will allow to directly set ContentType in the Request event handler

Regards

Why do I need this line which converts a string to a stream?

StreamHelpers.Utf8StringToStream(“Test Value”, e.Data);

I just want to read the html file and send it over as is. Can I do something like this?

var fileName = @“C:\Temp\pllogin.html”;
e.Data = new MemoryStream(File.ReadAllBytes(@fileName));

or

e.Data = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);

// Thom