#えっ、こんなのってありなの…?
私フィズ、今とある所で事務職をしているの。
元々プログラミングが好きで、本当はそういう事したかったんだけどね。
このお仕事はとっても大変。オンラインのディレクトリに頻繁にアクセスさせるのにWi-Fiしか無いし
、そのWi-Fiも貧弱でよくネットワークが切れてしまうの。
しかも業務マニュアルもエクセルの上に書かれててとっても読みにくい!
個人がチクチク内職をして作ったような微妙な代物しかなくって、こんなのを頼りにお仕事しなさい、って…
なんだか悲しくなってきたわ。
だから私これらのマニュアル類をwebpageとして綺麗に見やすく纏めて、変更にも強いwikiを作りたいな、と思ったの。けれどここのPCインストール権限すら無いのよね。
でも一つ気づいたの。PowerShellが動くって!
PowerShellって実は.NETのモジュールがそのまま呼び出せるの。私も最近知ったんだけどね。
これがあれば、NginxやApacheが無くてもサーバーっぽいものが作れるわ。(AWSから作れるらしいけど詳しくないから今回は触らないわ。
じゃあ早速ソースコードを載せてみるね。
##PS-Serverの解説
#PS-Server
#paramater
param(
[string]$script:Port = 8080,
[string]$script:Default = 'index.html',
[string]$DocumentRoot = (Split-Path $MyInvocation.MyCommand.path) +'/htdocs',
[string]$Root = "http://localhost:$Port/"
)
#module
. .\module\http.ps1
. .\module\file.ps1
. .\module\string.ps1
. .\module\log.ps1
#main
function main{
[string[]]$head = @(
"*Port=$Port",
"*Default=$Default",
"*LocalRoot=$DocumentRoot",
"*TryListen=$Root"
)
Write-Output $head
Http-Listen $head $foot $Port $Default $Root $DocumentRoot
}
#entry
main
これが最初に呼び出すファイル、もちろんこれだけじゃ動かないわ。
#module
でわかると思うけど、機能別に分割してあるわ。
私苦手なのよ、いくら簡単な内容だからってやたら一つのファイルに沢山のコードが詰め込まれてるの。
じゃあ実際に分割されているモジュールについて書いていくね。
##\module\http.ps1
###listener.GetContext()からコールバックが帰ってくるのを待つ関数。
function Http-Listen($head,$foot,$Port,$Default,$Root,$parent){
try{
$listener = Http-CreateListener $Root
$listener.Start()
while($true){
$context = $listener.GetContext()
$url = $context.request.RawUrl
$path = $url.TrimStart('/').split("?")[0]
if(!$path){$path = $Default}
Log-Queue $head $foot $url $path 80 20 "hh:mm:ss>"
$response = Http-InternalRequest $parent $path $context
$response.Close()
}
}finally{
$listener.Dispose()
}
}
順番的にファイルの一番最後に書かれているのだけど、親モジュールから呼ばれるのはこれね。
PowerShellって呼び出し元より先に定義しないと関数を呼ぶことが出来ない仕様みたい。
あとから解説するけど$listener.Dispose()
で要らなくなったリスナーオブジェクトを破棄しているわ。
オブジェクト生成破棄の仕様詳しくないのでこれはおまじないね。
これが無いとscriptを終了してもリスナーが直ぐには止まらないみたい。
###与えられたURLのHTTPリスナーを作成してオブジェクトを返す関数。
function Http-CreateListener($url){
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add($url)
return $listener
}
特に言うことはないけれど、これがないと始まらないわね。
HttpListener
オブジェクトを生成して返します。
###与えられたファイルパスから判別してContentTypeを返す関数
function Http-GetCType($path){
$extention = [System.IO.Path]::GetExtension($path)
if($extention -eq '.html'){
$ctype = 'text/html; '
}elseif($extention -eq '.css'){
$ctype = 'text/css; '
}elseif($extention -eq '.js'){
$ctype = 'text/javascript; '
}
return $ctype
}
ContentType
ってちゃんと指定してあげないとだめなのかしら。
深く考えずにファイル拡張子で判定しているわ。
###引数に渡された値を$responseに代入して返却する関数。
function Http-SetContents($content,$ctype,$response){
$response.ContentLength64 = $content.Length
$response.ContentEncoding = [Text.Encoding]::UTF8
$response.ContentType = $ctype+'charset='+$response.ContentEncoding.HeaderName
$response.OutputStream.Write($content, 0, $content.Length)
return $response
}
引数で渡された$response
オブジェクトに必要なデータを渡してそのまま返しているわ。
###ファイルからHTMLなどを読み出して出力する関数。
function Http-OutputFile($path,$response){
$ctype = Http-GetCType $path
$content = File-Read $path
return Http-SetContents $content $ctype $response
}
ローカルのファイルパスを指定して、ファイル内容を読み出しているわ。
$content = File-Read $path
の所で\module\file.ps1
が必要になるわね。
###引数で与えられた文字列をUTF8でHTMLとして出力する関数。
function Http-OutputInnerHTML($source,$response){
$content = [System.Text.Encoding]::UTF8.GetBytes($source)
return Http-SetContents $content 'text/html; ' $response
}
これは引数に直接HTMLを指定した場合に、$response
に値を書き込むためにあるわ。
結局今回は一番初めにテストで出力する時以外使わなかったけれどね…
(2020/04/04追記)
全然そんな事なかったよ…後で出てくるHttp-InternalRequest
内19行目のFile-ReadAllFile
で、ディレクトリ内のファイルを結合した後、そのデータをHTMLとして読み込むのに使っていたよ。
自分で書いたのになんで忘れてるんだ…。
###URLを解釈し出力方法を選択する関数。
#'xdir.txt'があるフォルダはURLにフォルダを指定するとフォルダ内のファイルを結合したものを出力
function Http-InternalRequest($parent,$path,$context){
$fullPath = [System.IO.Path]::Combine($parent, $path)
$response = $context.Response
switch($true){
($path.Contains('xdir.txt')){
$response.StatusCode = 404
$fullPath = [System.IO.Path]::Combine($parent, 'html/404.html')
$response = Http-OutputFile $fullPath $response
break
}
([System.IO.File]::Exists($fullPath)){
$response.StatusCode = 200
$response = Http-OutputFile $fullPath $response
break
}
([System.IO.Directory]::Exists($fullPath)-and[System.IO.File]::Exists(($fullPath+'/xdir.txt'))){
$response.StatusCode = 200
$fullpath = $fullPath + '/'
$contetns = File-ReadAllFile $fullpath
$response = Http-OutputInnerHTML $contetns $response
}
default{
$response.StatusCode = 404
$fullPath = [System.IO.Path]::Combine($parent, 'html/404.html')
$response = Http-OutputFile $fullPath $response
}
}
return $response
}
ここで上で解説したHTML出力の関数たちが実際に呼び出されるわ。
$fullpath
に代入されたファイルパスが間違っていたらステータス404を返してエラーページを表示させるわ。
あとxdir.txt
というファイルに特別な意味をもたせているの。このファイルを直接指定してもエラーを返すわ。(でもチョットこの部分だけ動作が怪しいのよね。
実はHTMLファイルだけでなくディレクトリを指定することで、ディレクトリ内のHTML断片を結合して一つのファイルとして扱うこともできるの。指定ディレクトリ内にxdir.txt
が存在することが条件だけれどね。
今はファイル名順で結合するルールになっているけれど、そのうちxdir.txt
内に結合順を書いておけばその順番に結合されるように出来たらいいんじゃないかと考えているわ。
##\module\http.ps1解説おわり。
とりあえずhttp.ps1
の中身はこんなものね。今回はここまで。
次はfile.ps1
について解説するわ。またね(*・ω・)ノ))フリフリ