Download httpd.zip
Introduction
Recently I was writing a REST services system for the my GCModeller system, which it provides the online services similarity to the the annotation system on the KEGG server or online blast services on NCBI. Due to the limitations of the ASP.NET, I could not build a services system not so handed, or customize enough, so that I decided to developing my own http server system for GCModeller instead of ASP.NET.
Background
Actually, this article is an alternative for "Simple HTTP Server in C#", inspired by the great job of @David Jeske, then I can begin of coding this server system.
Using the code
I have made some improvements on the original source code of this http server core to facility my coding job:
The 404 page
I have modified the writeFailure
function in the David's source code so that I can customizing the 404 page:
Public Property _404Page As String
Public Sub writeFailure(ex As String)
On Error Resume Next
Call outputStream.WriteLine("HTTP/1.0 404 Not Found")
Dim _404 As String
If String.IsNullOrEmpty(_404Page) Then
_404 = ex
Else
_404 = Me._404Page.Replace("%EXCEPTION%", ex)
End If
Call outputStream.WriteLine(_404)
Call outputStream.WriteLine("")
End Sub
Methods for transfer data
There are two types of the data in this server that can be transfer: HTML text and the bytes stream. That is all! Simply enough!
HttpProcessor.outputStream.WriteLine
This method is using for transferring the HTML data to your browser, you can calling using the code show below:
Dim html As String = System.Text.Encoding.UTF8.GetString(buf)
Call p.writeSuccess()
Call p.outputStream.WriteLine(html)
HttpProcessor.outputStream.BaseStream.Write
This method is using for transfer other type of the data, including the css, js script, image file, and etc, except the HTML document.
Private Sub __transferData(p As HttpProcessor, ext As String, buf As Byte())
If Not Net.Protocol.ContentTypes.ExtDict.ContainsKey(ext) Then
ext = ".txt"
End If
Dim contentType = Microsoft.VisualBasic.Net.Protocol.ContentTypes.ExtDict(ext)
Call p.outputStream.BaseStream.Write(buf, Scan0, buf.Length)
Call $"Transfer data: {contentType.ToString} ==> [{buf.Length} Bytes]!".__DEBUG_ECHO
End Sub
FileSystem IO
Before we can start transfer the document data to the browser, we must located the file from the url request first, and from the constructor of the http server class, we have specific the wwwroot directory location, so that we can just combine the request url with the wwwroot directory path then we can located the file from the browser request:
Public ReadOnly Property HOME As DirectoryInfo
Private Function __requestStream(res As String) As Byte()
Dim file As String = $"{HOME.FullName}/{res}"
If Not FileExists(file) Then
If _nullExists Then
Call $"{file.ToFileURL} is not exists on the file system, returns null value...".__DEBUG_ECHO
Return New Byte() {}
End If
End If
Return IO.File.ReadAllBytes(file)
End Function
Due to the reason of the document we transfers is not only the text document like html, css or js, but also includs the image, zip, audio or video. so that this method reads all bytes in the file instead of reads all text in the original source code.
I not sure why the html document can not be transferd from the same method with other document, possibly this is a bug of the browser or not, I'm not so sure, so that I separate the transfer routes of the HTML document and other document type data.
Private Sub __handleFileGET(res As String, p As HttpProcessor)
Dim ext As String = FileIO.FileSystem.GetFileInfo(res).Extension.ToLower
Dim buf As Byte() = __requestStream(res)
If String.Equals(ext, ".html") Then
Dim html As String = System.Text.Encoding.UTF8.GetString(buf)
Call p.writeSuccess()
Call p.outputStream.WriteLine(html)
Else
Call __transferData(p, ext, buf)
End If
End Sub
From the code above, then we have the ability to construct a virtual file system in this http server program, and this is the basically function of a HTTP server to transfer the document from the server file system to the browser. then we can test this basically virtual file system:
Typing address 127.0.0.1 in my browser, then the HTTP request started from browser to my own HTTP server program. Oh, yeah, the home page of GCModeller.org appearing in my browser! Fantastic!!!
REST API Routine
I using a If statement to makes the REST API calling can be compatible with the file document GET request:
Usually the REST GET/POST request required of parameter, and the parameter value start from ? character after the API name. and the ? character is illegal in the Windows filesystem, so that we can using this property to Distinguish the file GET request and REST API request.
Public Overrides Sub handleGETRequest(p As HttpProcessor)
Dim res As String = p.http_url
If String.Equals(res, "/") Then
res = "index.html"
End If
If res.PathIllegal Then
Call __handleREST(p)
Else
Call __handleFileGET(res, p)
End If
End Sub
Determine path illegal
Determine the file path is illegal or not just easy using this extension function, just indexOf the illegal character in the path:
Public Const ILLEGAL_PATH_CHARACTERS_ENUMERATION As String = ":*?""<>|"
Public Const ILLEGAL_FILENAME_CHARACTERS As String = "\/" & ILLEGAL_PATH_CHARACTERS_ENUMERATION
<ExportAPI("Path.Illegal?")>
<Extension> Public Function PathIllegal(path As String) As Boolean
Dim Tokens As String() = path.Replace("\", "/").Split("/"c)
Dim fileName As String = Tokens.Last
For Each ch As Char In ILLEGAL_PATH_CHARACTERS_ENUMERATION
If fileName.IndexOf(ch) > -1 Then
Return True
End If
Next
For Each DIRBase As String In Tokens.Takes(Tokens.Length - 1)
For Each ch As Char In ILLEGAL_PATH_CHARACTERS_ENUMERATION
If fileName.IndexOf(ch) > -1 Then
Return True
End If
Next
Next
Return False
End Function
Process the API invoke
Private Sub __handleREST(p As HttpProcessor)
Dim pos As Integer = InStr(p.http_url, "?")
If pos <= 0 Then
Call p.writeFailure($"{p.http_url} have no parameter!")
Return
End If
Dim args As String = Mid(p.http_url, pos + 1)
Dim Tokens = args.requestParser
Dim query As String = Tokens("query")
Dim subject As String = Tokens("subject")
Dim result = LevenshteinDistance.ComputeDistance(query, subject)
Call p.writeSuccess()
Call p.outputStream.WriteLine(result.Visualize)
End Sub
Now the http server have the ability to process the REST API. And you can try this url to test the example REST api after you have started the demo application in this article:
http:
There are three element in this API address url:
rest-example
is the API name of this rest API test example
query=/string/ and subject=/string/
is the parameter name of the API which is named query
and subject
respectively and the parameter value can be modified from the URL that you inputs in the browser.
Example REST API test successful!
Accomplished the Server
Finally from added the program Main entry point and Cli interpreter, then we can complete this http server project.
Imports Microsoft.VisualBasic.CommandLine.Reflection
Module Program
Public Function Main() As Integer
Return GetType(CLI).RunCLI(App.CommandLine)
End Function
End Module
Module CLI
<ExportAPI("/start", Usage:="/start [/port <default:=80> /root <./wwwroot>]",
Info:="Start the simple http server.",
Example:="/start /root ~/.server/wwwroot/ /port 412")>
<ParameterInfo("/port", True,
Description:="The data port for this http server to bind.")>
<ParameterInfo("/root", True,
Description:="The wwwroot directory for your http html files, default location is the wwwroot directory in your App HOME directory.")>
Public Function Start(args As CommandLine.CommandLine) As Integer
Dim port As Integer = args.GetValue("/port", 80)
Dim root As String = args.GetValue("/root", App.HOME & "/wwwroot")
Dim httpd As New HttpInternal.HttpFileSystem(port, root, True)
Call httpd.Run()
Return 0
End Function
End Module
Try call the command from the cmd console to start our own http server:
httpd /start
My own http server running smoothly on the console.
The http server request the Internet access through your filewall, please enable it.
Points of Interest
Here just a very simple brief idea of building my own http server, from overrides the method of handleGETRequest(p As HttpProcessor)
and handlePOSTRequest(p As HttpProcessor, inputData As StreamReader),
then we are able to implements the function of http file request handler or implements your own http rest services server.
For some reason, this http server is not compatible with the Microsoft IE or Edge browser, I don't know why....... Sorry about this.