powershelllocalserver

Why is my powershell localhost server so slow?


I bodged together a simple local server for hosting a HTML-only web app. I need it because some features, like AJAX requests, do not work from HTML directly open from filesystem.

It looks like this and actually works.

# source: https://gist.github.com/19WAS85/5424431

$http = [System.Net.HttpListener]::new()
# Hostname and port to listen on
$http.Prefixes.Add("http://localhost:8080/")
# Start the Http Server
$http.Start()

Add-Type -AssemblyName System.Web
# Log ready message to terminal
if ($http.IsListening) {
    write-host "HTTP Server Ready!  " -f 'black' -b 'gre'
    write-host "$($http.Prefixes)" -f 'y'
    #write-host "then try going to $($http.Prefixes)other/path" -f 'y'
}

#New-PSDrive -Name MyPowerShellSite -PSProvider FileSystem -Root $PWD.Path
# INFINTE LOOP
# Used to listen for requests
while ($http.IsListening) {
    # Get Request Url
    # When a request is made in a web browser the GetContext() method will return a request object
    # Our route examples below will use the request object properties to decide how to respond
    $context = $http.GetContext()

    if ($context.Request.HttpMethod -eq 'GET') {

        # We can log the request to the terminal
        write-host "$($context.Request.UserHostAddress)  =>  $($context.Request.Url)" -f 'mag'


        $URL = $context.Request.Url.LocalPath

        # Redirect root to index.html
        if($URL -eq "/") {
          $URL = "/index.html"
        }
        $Content = Get-Content -Encoding Byte -Path "web/$URL"
        $Context.Response.ContentType = [System.Web.MimeMapping]::GetMimeMapping("web/$URL")
        $Context.Response.OutputStream.Write($Content, 0, $Content.Length)
        $Context.Response.Close()

    }
    # powershell will continue looping and listen for new requests...

}

However the requests take an insane amount of time. There's something wrong with how the files are read and written to the output stream.

Is there a fix? It's so slow that it's basically useless. To try it, make sure you run it somewhere where a web/ sub folder exists. The low speed can be easily tested by trying to open a photo for example.


Solution

  • The key was using [System.IO.File]::OpenRead to make a stream which will then copy everything it reads directly into the socket which sends it to the browser.

    Here's the entire Powershell local directory HTTP server. If you want to serve directly the directory it runs in, change "web/$URL" to just $URL:

    $http = [System.Net.HttpListener]::new()
    # Hostname and port to listen on
    $http.Prefixes.Add("http://localhost:8080/")
    # Start the Http Server
    $http.Start()
    
    Add-Type -AssemblyName System.Web
    # Log ready message to terminal
    if ($http.IsListening) {
        write-host "HTTP Server Ready!  " -f 'black' -b 'gre'
        write-host "$($http.Prefixes)" -f 'y'
    }
    
    # INFINTE LOOP
    # Used to listen for requests
    while ($http.IsListening) {
        # Get Request Url
        # When a request is made in a web browser the GetContext() method will return a request object
        # Our route examples below will use the request object properties to decide how to respond
        $context = $http.GetContext()
    
        if ($context.Request.HttpMethod -eq 'GET') {
    
            # We can log the request to the terminal
            write-host "$($context.Request.UserHostAddress)  =>  $($context.Request.Url)" -f 'mag'
    
    
            $URL = $context.Request.Url.LocalPath
    
            # Redirect root to index.html
            if($URL -eq "/") {
              $URL = "/index.html"
            }
    
            $ContentStream = [System.IO.File]::OpenRead( "web/$URL" );
            $Context.Response.ContentType = [System.Web.MimeMapping]::GetMimeMapping("web/$URL")
            $ContentStream.CopyTo( $Context.Response.OutputStream );
            $Context.Response.Close()
        }
        # powershell will continue looping and listen for new requests...
    }