Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Simple HTTP Server in VisualBasic

5.00/5 (5 votes)
1 Jan 2016CPOL4 min read 34.2K   2.4K  
This is an alternative for Simple HTTP Server in C#

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:

VB.NET
''' <summary>
''' You can customize your 404 error page at here.
''' </summary>
''' <returns></returns>
Public Property _404Page As String

''' <summary>
''' 404
''' </summary>
Public Sub writeFailure(ex As String)
    On Error Resume Next

    ' this is an http 404 failure response
    Call outputStream.WriteLine("HTTP/1.0 404 Not Found")
    ' these are the HTTP headers
    '   Call outputStream.WriteLine("Connection: close")
    ' ..add your own headers here

    Dim _404 As String

    If String.IsNullOrEmpty(_404Page) Then
        _404 = ex
    Else
       ' 404 page html file usually located in the root directory of the site, 
       ' If the Then file exists the read the page And replace the 
       ' Exception message With the Placeholder %Exception%

        _404 = Me._404Page.Replace("%EXCEPTION%", ex)
    End If

    Call outputStream.WriteLine(_404)
    Call outputStream.WriteLine("")         ' this terminates the HTTP headers.
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:

VB.NET
' Transfer HTML document.
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.

VB.NET
''' <summary>
''' 为什么不需要添加content-type说明??
''' </summary>
''' <param name="p"></param>
''' <param name="ext"></param>
''' <param name="buf"></param>
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.writeSuccess(contentType.MIMEType)
    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:

VB.NET
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. 

VB.NET
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 ' Transfer HTML document.
        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:

Image 1

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.

VB.NET
''' <summary>
''' 为什么不需要添加<see cref="HttpProcessor.writeSuccess(String)"/>方法???
''' </summary>
''' <param name="p"></param>
Public Overrides Sub handleGETRequest(p As HttpProcessor)
     Dim res As String = p.http_url

     If String.Equals(res, "/") Then
         res = "index.html"
     End If

     ' The file content is null or not exists, that possible means this is a GET REST request not a Get file request.
     ' This if statement makes the file GET request compatible with the REST API
     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:

VB.NET
''' <summary>
''' 枚举所有非法的路径字符
''' </summary>
''' <remarks></remarks>
Public Const ILLEGAL_PATH_CHARACTERS_ENUMERATION As String = ":*?""<>|"
Public Const ILLEGAL_FILENAME_CHARACTERS As String = "\/" & ILLEGAL_PATH_CHARACTERS_ENUMERATION

''' <summary>
''' File path illegal?
''' </summary>
''' <param name="path"></param>
''' <returns></returns>
<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

VB.NET
''' <summary>
''' handle the GET/POST request at here
''' </summary>
''' <param name="p"></param>
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

    ' Gets the argument value, value is after the API name from the ? character
    ' Actually the Reflection operations method can be used at here to calling 
    ' the different API 
    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)

    ' write API compute result to the browser
    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://127.0.0.1/rest-example?query=12345&subject=2346

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.

Image 2

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.

VB.NET
Imports Microsoft.VisualBasic.CommandLine.Reflection

Module Program

    Public Function Main() As Integer
        Return GetType(CLI).RunCLI(App.CommandLine)
    End Function
End Module

Module CLI

    ''' <summary>
    ''' Run the http server
    ''' </summary>
    ''' <param name="args"></param>
    ''' <returns></returns>
    <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:

BAT
httpd /start

 

Image 3

My own http server running smoothly on the console.

Image 4

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)