惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cyberwarzone
Cyberwarzone
V
Vulnerabilities – Threatpost
T
Tenable Blog
Forbes - Security
Forbes - Security
Simon Willison's Weblog
Simon Willison's Weblog
AWS News Blog
AWS News Blog
G
GRAHAM CLULEY
Know Your Adversary
Know Your Adversary
S
Securelist
C
Cybersecurity and Infrastructure Security Agency CISA
Project Zero
Project Zero
C
CXSECURITY Database RSS Feed - CXSecurity.com
V
Visual Studio Blog
WordPress大学
WordPress大学
Latest news
Latest news
K
Kaspersky official blog
T
Tailwind CSS Blog
T
Threat Research - Cisco Blogs
B
Blog RSS Feed
C
Cisco Blogs
博客园 - 聂微东
Martin Fowler
Martin Fowler
T
The Blog of Author Tim Ferriss
小众软件
小众软件
L
LangChain Blog
阮一峰的网络日志
阮一峰的网络日志
L
LINUX DO - 热门话题
Stack Overflow Blog
Stack Overflow Blog
罗磊的独立博客
P
Proofpoint News Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
P
Privacy International News Feed
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
C
CERT Recently Published Vulnerability Notes
Cisco Talos Blog
Cisco Talos Blog
S
SegmentFault 最新的问题
Security Latest
Security Latest
Y
Y Combinator Blog
爱范儿
爱范儿
aimingoo的专栏
aimingoo的专栏
P
Privacy & Cybersecurity Law Blog
L
LINUX DO - 最新话题
月光博客
月光博客
The GitHub Blog
The GitHub Blog
博客园 - 三生石上(FineUI控件)
S
Security Affairs
P
Proofpoint News Feed
D
DataBreaches.Net
有赞技术团队
有赞技术团队
云风的 BLOG
云风的 BLOG

f2h2h1's blog

使用yii3实现一个微框架 claw养殖技术 计算机网络基础知识 定时任务 ACME的使用经验 magento2加上varnish缓存 开发Magento2的模块 在magento2中使用persisted-query socket编程 一些开发笔记 一段CSDN文章主要内容的油猴脚本 电子邮件的不完整总结 git的笔记 在Windows下配置PHP服务器 终端,控制台和外壳 PHP各种运行方式的不完整总结 把网页导出成PDF 和颜色相关的笔记 HTTP认证方式的不完整总结 SEO的经验 密码学入门简明指南 文件的上传和下载 用纯CSS3实现的滑动按钮 在VSCode里调试PHP Linux的GUI 关于字符编码的一些坑 nc的使用和原理 在Windows下安装Magento2 对JS原型链的理解 使用docker-compose部署magento2 浏览器和服务器通讯方式的不完整总结 观察网站性能 一些关于Linux的笔记 telnet的不完整总结 在Windows下安装pear MySQL的时间类型和时间相关的函数 Windows下通过PEB读取进程的环境变量 关于 在VSCode里使用Xdebug远程调试PHP 在Windows下搭建git服务 关于环境变量的不完整总结 使用shell实现的kv数据库 如何完成以xx管理系统为选题的毕业设计 数字号码资源 各种标记语言 kind相关经验 DNSSEC简介 nginx+ffmpeg+websocket实现的直播例子 使用Tesseract识别字符验证码 使用docker部署nuxt FirstData后台的设置 paypal,firtdata,支付宝的不完整接入指南 微信支付的不完整接入指南 用docker-compose部署lnmp环境 mongodb分片 练习
使用PowerShell实现的http服务器
2022-07-30 · via f2h2h1's blog

这篇文章最后更新的时间在六个月之前,文章所叙述的内容可能已经失效,请谨慎参考!

这是一个使用 PowerShell 实现的 http 服务器。 可以显示目录页,下载静态文件和响应 cgi 请求。

核心是调用 .NET 的类库

  • System.Net.HttpListener
  • System.Web
  • System.Diagnostics.Process

PSVersion 5.1

可以像这样运行

.\http-server.ps1
# 然后可以在浏览器里这样访问 http://localhost:8080/

参考了这几个仓库的实现

参考文档

源码


$websiteRoot = $(Get-Location).Path

$mimeHash = [ordered]@{
    ".html" = "text/html";
    ".htm" = "text/html";
    ".shtml" = "text/html";
    ".shtm" = "text/html";
    ".css" = "text/css";
    ".xml" = "text/xml";
    # ".csv" = "text/csv";
    ".gif" = "image/gif";
    ".jpeg" = "image/jpeg";
    ".jpg" = "image/jpeg";
    ".js" = "text/javascript";
    ".txt" = "text/plain";
    ".json" = "application/json";
    ".pdf" = "application/pdf";
    ".png" = "image/png";
    ".svg" = "image/svg+xml";
    ".webp" = "image/webp";
    ".ico" = "image/x-icon";
    ".bmp" = "image/x-ms-bmp";
    ".woff" = "font/woff";
    ".woff2" = "font/woff2";
    ".der" = "application/x-x509-ca-cert";
    ".pem" = "application/x-x509-ca-cert";
    ".crt" = "application/x-x509-ca-cert";
    ".xhtml" = "application/xhtml+xml";
    ".zip" = "application/zip";
    ".mid" = "audio/midi";
    ".midi" = "audio/midi";
    ".kar" = "audio/midi";
    ".mp3" = "audio/mpeg";
    ".ogg" = "audio/ogg";
    ".3gpp" = "video/3gpp";
    ".3gp" = "video/3gpp";
    ".mp4" = "video/mp4";
    ".mpeg" = "video/mpeg";
    ".mpg" = "video/mpeg";
    ".mov" = "video/quicktime";
    ".webm" = "video/webm";
    ".flv" = "video/x-flv";
    ".wmv" = "video/x-ms-wmv";
    ".avi" = "video/x-msvideo";
    ".md" = "text/plain";
}

$cgiHash = [ordered]@{
    ".php" = "php"
    ".pl" = "perl"
    ".py" = "python"
    ".rb" = "ruby"
    ".cgi" = "executablefile"
    ".exe" = "executablefile"
}

Add-Type -AssemblyName System.Web

# Http Server
$http = [System.Net.HttpListener]::new() 

# Hostname and port to listen on
$http.Prefixes.Add("http://localhost:8080/")

# Start the Http Server 
$http.Start()

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

try {
# 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
    $contextTask = $http.GetContextAsync()

    # Waits in 200ms increments for a request. We do this to allow pipeline stops to be processed (i.e. CTRL+C)
    # Credit: https://www.reddit.com/r/PowerShell/comments/9n2q03/comment/e7ju5w4/?utm_source=share&utm_medium=web2x&context=3
    while (-not $contextTask.AsyncWaitHandle.WaitOne(200)) { }
    $context = $contextTask.GetAwaiter().GetResult()

    $dir = [System.Web.HttpUtility]::UrlDecode($context.Request.RawUrl)

    write-host "$($context.Request.UserHostAddress)  =>  $($context.Request.Url)" -f 'mag'

    $staticFilePath = $websiteRoot + $dir
    if ($context.Request.HttpMethod -eq 'GET' -and (Test-Path $staticFilePath) -and (Get-Item $staticFilePath) -is [IO.DirectoryInfo]) {

        $isRoot = $False
        if ($context.Request.RawUrl -gt '/') {
            $isRoot = $True
        }

        $li = "<li><a href=""../"">..</a></li>"
        ForEach ($item in Get-ChildItem -Path $staticFilePath) {
            if ($isRoot -eq $True) {
                $itemPath = $staticFilePath + $item.Name
            } else {
                $itemPath = $staticFilePath + "/" + $item.Name
            }
            if ((Get-Item $itemPath) -is [IO.DirectoryInfo]) {
                $li += "<li><a href=""$($item)/"">"+$item.Name+"</a></li>"
            } else {
                $li += "<li><a href=""$($item)"">"+$item.Name+"</a></li>"
            }
        }

        $title = "Directory listing for " + $dir
        $html = "<!DOCTYPE html><html><head><meta charset=""utf-8""> <title>$title</title></head><body><h1>$title</h1><hr><ul>$li</ul><hr></body></html>"

        #resposed to the request
        $buffer = [System.Text.Encoding]::UTF8.GetBytes($html) # convert htmtl to bytes
        $context.Response.ContentType = "text/html; charset=utf-8"
        $context.Response.ContentLength64 = $buffer.Length
        $context.Response.OutputStream.Write($buffer, 0, $buffer.Length) #stream to broswer
        $context.Response.OutputStream.Close() # close the response
        continue
    }

    if ($context.Request.RawUrl -match '^/cgi-bin(.*)|^/htcgi(.*)' -and (Test-Path $staticFilePath) -and (Get-Item $staticFilePath) -is [IO.fileinfo]) {

        $isExecutablefile = $false
        $ext = [System.IO.Path]::GetExtension($staticFilePath)
        $cgi = $cgiHash[$ext]
        if ($cgi -gt $null) {
            if ($cgi -eq ".cgi" -or $cgi -eq ".exe") {
                $isExecutablefile = $true
            }
        } else {
            $isExecutablefile = $true
        }

        $standardInput = [System.IO.StreamReader]::new($context.Request.InputStream).ReadToEnd()
        $p = [System.Diagnostics.Process]::new()

        if ($isExecutablefile -eq $false) {
            $p.StartInfo.FileName = $cgi
            $p.StartInfo.Arguments = $staticFilePath
        } else {
            $p.StartInfo.FileName = $staticFilePath
        }

        $p.StartInfo.UseShellExecute = $false
        $p.StartInfo.RedirectStandardOutput = $true
        $p.StartInfo.RedirectStandardInput = $true

         # 这里是设置环境变量
        $headers = $context.Request.Headers
        $headers.Add("REQUEST_METHOD", $($context.Request.HttpMethod))
        $headers.Add("SERVER_PROTOCOL", "HTTP/$($context.Request.ProtocolVersion)")
        ForEach ($key in $headers.AllKeys) {
            $p.StartInfo.Environment.Add($key, $headers[$key])
        }

        $p.Start()
        $pw = $p.StandardInput;
        $pw.WriteLine($standardInput) # 这里是设置标准输入
        $pw.Close()
        $responseRaw = $p.StandardOutput.ReadToEnd() # 这里是获得标准输出

        if ($p.ExitCode -eq 0) {

            # 分割响应头和响应报文
            $t = $responseRaw.Split("`r`n`r`n", 2)
            # $t = $responseRaw -split "`r`n`r`n", 2
            # 这里应该还需要一些容错的处理
            $responseHeaders = $t[0]
            $responseBody = $t[1]

            ForEach($headerLine in $responseHeaders.Split("`r`n")) {
                $keyValue = $headerLine.Split(": ")
                if ($keyValue.Length -gt 2) {
                    continue
                }
                $key = $keyValue[0]
                $value = $keyValue[1]
                $context.Response.AddHeader($key, $value)
            }

            $buffer = [System.Text.Encoding]::UTF8.GetBytes($responseBody) # convert htmtl to bytes
            # $context.Response.ContentType = "text/html; charset=utf-8"
            $context.Response.ContentLength64 = $buffer.Length
            $context.Response.OutputStream.Write($buffer, 0, $buffer.Length) #stream to broswer
            $context.Response.OutputStream.Close() # close the response
        } else {
            [string]$html = "<h1>500 Server Error</h1>"
            $buffer = [System.Text.Encoding]::UTF8.GetBytes($html) # convert htmtl to bytes
            $context.Response.StatusCode = 500
            $context.Response.StatusDescription = "Server Error"
            $context.Response.ContentType = "text/html; charset=utf-8"
            $context.Response.ContentLength64 = $buffer.Length
            $context.Response.OutputStream.Write($buffer, 0, $buffer.Length) #stream to broswer
            $context.Response.OutputStream.Close() # close the response
        }

        continue
    }

    if ($context.Request.HttpMethod -eq 'GET' -and (Test-Path $staticFilePath) -and (Get-Item $staticFilePath) -is [IO.fileinfo]) {

        $buffer = [System.Text.Encoding]::UTF8.GetBytes((Get-Content -Raw -Encoding utf8 $staticFilePath))
        $mime = $mimeHash[[System.IO.Path]::GetExtension($staticFilePath)]

        if ($mime -gt $null) {
            # $context.Response.ContentType = $mime + "; charset=utf-8"
            if ($mime -match '^text') {
                $mime += "; charset=utf-8"
            }
            $context.Response.ContentType = $mime
        }
        $context.Response.ContentLength64 = $buffer.Length
        $context.Response.OutputStream.Write($buffer, 0, $buffer.Length) #stream to broswer
        $context.Response.OutputStream.Close() # close the response
        continue
    }

    [string]$html = "<h1>404 Not Found</h1>"
    $buffer = [System.Text.Encoding]::UTF8.GetBytes($html) # convert htmtl to bytes
    $context.Response.StatusCode = 404
    $context.Response.StatusDescription = "Not Found"
    $context.Response.ContentType = "text/html; charset=utf-8"
    $context.Response.ContentLength64 = $buffer.Length
    $context.Response.OutputStream.Write($buffer, 0, $buffer.Length) #stream to broswer
    $context.Response.OutputStream.Close() # close the response
    continue
} 
}
catch {
  Write-Host "An error occurred:"
  Write-Host $_
}
finally {
    $http.Stop()
}

在笔者原本的设想里,是写一个纯 PowerShell 的脚本,即使要调用 .NET 的类库起码也要保证跨平台能用。 但后来笔者发觉,这个设想是实现起来有点困难,最后还是通过调用 .NET 的类库实现。 PowerShell 的强大主要是体现在可以很方便地调用 .NET 的类库,基本没有障碍。 但离开 .NET 的类库, PowerShell 的表现力其实也没有比 bash 好多少。