一切のインストール権限の無いPCをあてがわれる可哀想な人がPowerShellを使ってwikiを作ってみる１（仮）

#えっ、こんなのってありなの…？
私フィズ、今とある所で事務職をしているの。
元々プログラミングが好きで、本当はそういう事したかったんだけどね。

このお仕事はとっても大変。オンラインのディレクトリに頻繁にアクセスさせるのにWi-Fiしか無いし
、そのWi-Fiも貧弱でよくネットワークが切れてしまうの。
しかも業務マニュアルもエクセルの上に書かれててとっても読みにくい！
個人がチクチク内職をして作ったような微妙な代物しかなくって、こんなのを頼りにお仕事しなさい、って…

なんだか悲しくなってきたわ。

だから私これらのマニュアル類をwebpageとして綺麗に見やすく纏めて、変更にも強いwikiを作りたいな、と思ったの。けれどここのPCインストール権限すら無いのよね。

でも一つ気づいたの。PowerShellが動くって！

PowerShellって実は.NETのモジュールがそのまま呼び出せるの。私も最近知ったんだけどね。
これがあれば、NginxやApacheが無くてもサーバーっぽいものが作れるわ。(AWSから作れるらしいけど詳しくないから今回は触らないわ。

じゃあ早速ソースコードを載せてみるね。

##PS-Serverの解説
```powershell:psav.ps1
#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()からコールバックが帰ってくるのを待つ関数。
```powershell:Http-Listen
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リスナーを作成してオブジェクトを返す関数。
```powershell:Http-CreateListener
function Http-CreateListener($url){
    $listener = New-Object System.Net.HttpListener
    $listener.Prefixes.Add($url)
    return $listener
}
```
特に言うことはないけれど、これがないと始まらないわね。
`HttpListener`オブジェクトを生成して返します。

###与えられたファイルパスから判別してContentTypeを返す関数
```powershell:Http-GetCType
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に代入して返却する関数。
```powershell:Http-SetContents
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などを読み出して出力する関数。
```powershell:Http-OutputFile
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として出力する関数。
```powershell:Http-OutputInnerHTML
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を解釈し出力方法を選択する関数。
```powershell:Http-InternalRequest
#'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`について解説するわ。またね(*･ω･)ﾉ))ﾌﾘﾌﾘ