管理者権限のないプレーンなWindowsでWebサーバを立てる戦い

# ある事務職エンジニアの嘆き
非IT企業に勤務する事務職という名のエンジニアの皆様におかれましては、以下のようなきわめて過酷な環境に置かれていることと思います。

- 管理者権限のないWindows
- ソフトウェアのダウンロード・インストール不可。
- Webは仮想環境でしか閲覧不可。
- PHPもPythonもRubyもNode.jsも使用不可。

当然VisualStudioもVSCodeもSublimeTextさえ使えず、**あるものといえばWordとExcelとメモ帳と付箋**くらい。

この牢獄のような環境下で、事務職エンジニアはExcelVBAやHTA、WSH、コマンドプロンプト、PowerShellといった、プレーンなWindowsでも使える技術を手に、日夜メモ帳と戦っていることでしょう。

特にわたしはWebが好きで、html/css/javascriptが得意なので、HTA（Webの技術で超簡単にデスクトップアプリがつくれるやつ。）であれこれとアプリをつくっていました。

しかし、IEも開発が終了し、HTAのようなレガシー技術も廃れに廃れて、いつまでサポートされるかも分からないご時世。

やっぱり、そろそろHTAを捨てて移住先を探さなければ…！

## しかし環境は過酷だ
前提として、わたしのPC環境は以下のとおりです。

- Windows7
- 管理者権限は使えません。
- Webは仮想環境でしかアクセスできません（ローカルとは完全分離）。
- Webアプリはフィルタリングされて一切使えません。
- アプリケーションのダウンロードやインストールはできません。

時代はクラウドですがWebアプリは一切使うことが許されていません。
Windowsに標準で入ってる「付箋」をデスクトップに貼り付けてタスク管理ツールとして使うというナレッジが口伝でシェアされているような職場です。
思うところはありますが、ここはクリエイターとしてアプリ開発に熱があがると前向きに捉えましょう('､3_ヽ)_

## Firefoxが入ってる
わたしのPCにはIE9の他にFirefoxが入っています。
Firefoxならば、最新のJavaScriptとそのフレームワークを使って、ローカルで動作するいい感じのWebアプリがつくれるはず。
もう**「クラス構文が使えない〜」**とか**「アロー関数が使えない〜」**とか悩む必要はないのです。
~~Babel？　そんなの使えるわけないでしょッ！~~
わたしはHTAを捨てて、最新のブラウザと共に歩む道を選ぶのだ！！

## しかしローカルファイルにはアクセスできない。
ブラウザの宿命ですが、ローカルファイルにはアクセスが許されません。
データを保存するだけだったらLocalStorageとかIndexedDBとかで事足りますが、これらはドメイン単位で保存されているため、ローカルで使う場合はファイルの置き場所が変わったらアクセスできなくなります。
そしてやっぱり、ブラウザの用意するストレージではなくて、ローカルのファイルにアクセスしたいという欲も出てきます。
IEに実装されているActiveXという技術のことは…もう忘れましょう。


## 越えるしかない。HTTPの壁を…！
ローカルファイルを自在に操るには、HTTPの壁を超えてサーバサイドと連携するしかありません。
localhostに立てたサーバを使って、サーバ経由でローカルファイルを操作するのです。
そしてそのためには、HTTPを越えるためのWebサーバが必要不可欠です。

# プレーンなWindowsでWebサーバを立てる
茶番が長かったですが、ここから本題です。
サーバサイドの言語がインストールされていれば、httpサーバ程度ならワンライナーで立てられるのですが、最初に説明したとおり、わたしのPCには何の言語もインストールされていません。

しかしこの環境下でも、httpサーバを立てる手段はあります。
.NETの中に、HttpListenerというシンプルなhttpサーバを立てられるクラスがあるらしいので、これを使います。

## PowerShellでサーバを立てる
PowerShellは最近のWindowsには標準で入っているスクリプト言語かつコマンドプロンプトの全コマンドを網羅するという完全上位互換のシェルです。
PowerShellのやばいところは、.NETのクラスにアクセスして使うことができるというところで、何でもできるのですが**キメラ化**していて底知れぬ不気味さがあります。
これを使ってlocalhostにWebサーバを立ててみましょう。

```powershell:server.ps1
$listener = New-Object Net.HttpListener
$listener.Prefixes.Add("http://localhost:8000/")
try {
    $listener.Start()
    while ($true) {
        $context = $listener.GetContext()
        $response = $context.Response
        $content = [System.Text.Encoding]::UTF8.GetBytes('hello world!')
        $response.OutputStream.Write($content, 0, $content.Length)
        $response.Close()
    }
}
catch {
    Write-Error($_.Exception)
}
```

上のコードは、`localhost:8000/`以下にアクセスすれば何があっても`hello world!`と返すだけのおめでたいWebサーバの例です。
powershellを開いて`.\server.ps1`で起動させれば、環境によっては上の例だけでもブラウザから動作を確認できると思います。

## が、動かない…！
試しに上のコードを書いてFirefoxで覗いてみたところ、「プロキシサーバへの接続を拒否されました」とのエラーが。
手元にあるIE9では正常に見れるのに…。
~~IEで見れても仕方ないんだよ…。~~

調査したところ、社内のプロキシサーバ側の設定で`localhost`へのアクセスが許可されていない（？）ようでした。
当然、しがない事務職のわたしにはプロキシサーバをいじくる権限などありません。

Firefox側の設定を変更して、`localhost`にアクセスする際はプロキシを使わないようにすることもできるはずなのですが、この操作には管理者権限が必要とされているらしく、ユーザ権限ではプロキシの設定をいじれませんでした。


### IPアドレスでのアクセスを試みる
`localhost`がダメならば、ループバックアドレスか自分のIPで直接アクセスすればいいじゃん！
ということで、Firefoxから`127.0.0.1:8000`へアクセスしてみました。

**が、ダメッ！！**

今度はブラウザに`Bad Request -Invalid Hostname`の表示が…。
どうやらこれは、上のコードで書いた

```powershell:server.ps1
$listener.Prefixes.Add("http://localhost:8000/")
```

の設定により、「リクエストされたホスト名が設定と違うからダメ！」と怒られてしまっているようです。
しぶといですね…('､3_ヽ)_

### HttpListenerの設定を変える
「`localhost:8000`と設定されているのに、`127.0.0.1`とか`10.x.x.x`とかでアクセスしてるからダメ」ということならば、こっちの設定を変えてしまいましょう。

```powershell:server.ps1
$listener.Prefixes.Add("http://127.0.0.1:8000/")
```

こっちに変えて試してみます。
今度こそいけそうな気がします。

**が、ダメッ！！**

今度はそもそもHttpListenerを立てる段階で以下のようなエラーが出てしまいました。

```
"0" 個  の引数を指定して "Start" を呼び出し中に例外が発生しました: 
"アクセスが拒否されました。"
```

調べたところ、ユーザ権限では`127.0.0.1`を指定してポートを開放することが許可されていない（？）みたいです。
~~localhostならいけるのに、なんでやねん！！~~
自分のIP（`10.x.x.x`）でも試してみましたが、こちらも同様の結果。
ユーザ権限でポートを開放できるように設定を書き換えることもできるみたいですが、こちらもやはり管理者権限が必要とされるらしいです。

### 絶望
|試した|結果|
|:--|:--|
|localhostでサーバを立ててlocalhostにアクセス|プロキシの設定でアクセスが拒否される。|
|localhostでプロキシを使わないようFirefoxの設定を変える|管理者権限が必要で操作できない。|
|localhostでサーバを立ててループバックアドレスにアクセス|Invalid Hostname|
|自分のIPかループバックアドレスでサーバを立てる|ポートを開放する権限がない。|
|ユーザにポート開放の権限を与える|管理者権限が必要で操作できない。|

八方塞がりだ…('､3_ヽ)_

見たところ、localhostで立ててループバックアドレスか自分のIPアドレスでアクセスした場合であれば、HttpListenerまではリクエストが飛んできているみたいなので、これが一番芽がありそうです。
他のは…プロキシの設定変えたり管理者権限を入手したりしないとダメっぽいので、どうにもならなさそう。

### http://+:80/Temporary_Listen_Addresses/
ここまでやってダメなのか～…と半ば諦めながらググっていたところ、StackOverflowで耳寄りな情報を発見。どうやら

> http://+:80/Temporary_Listen_Addresses/なら誰でもアクセスできるよ！
> でも他のアプリと競合する場合があるから気をつけて！

ということらしい。
試しにこの設定でHttpListenerを立ててみます。

```powershell:server.ps1
$listener.Prefixes.Add("http://+:80/Temporary_Listen_Addresses/")
```

起動すると、何事もなくサーバがListenを開始します。
さらにこの状態で、FireFoxから`127.0.0.1/Temporary_Listen_Addresses/`にアクセスすると、**ついに`hello world!`の文字が…！**

長かったけどこれで無事、管理者権限のないWindowsでlocalhostにWebサーバを立てることができました。
あとはリクエストURLを参照してファイルを返したりRESTなAPIを構築したりと、やりたい放題ですね！(۶•̀ᴗ•́)۶

# オマケ(C#でHttpListenerを構築する)
PowerShellが嫌な場合は、C#で書いたコードをWindows標準で入ってるcsc.exeを使って実行ファイルにすることができます。
やってることはPowerShellとほとんど同じなのですが、いちおうサンプルを乗っけておきます。

```cs:server.cs
using System;
using System.Net;
using System.Text;

class Server
{
    static void Main()
    {
        try
        {
            HttpListener listener = new HttpListener();
            listener.Prefixes.Add("http://+:80/Temporary_Listen_Addresses/");
            listener.Start();

            while (true)
            {
                HttpListenerContext context = listener.GetContext();
                HttpListenerResponse response = context.Response;
                byte[] content = Encoding.UTF8.GetBytes("hello world!");
                response.OutputStream.Write(content, 0, content.Length);
                response.Close();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error: " + ex.Message);
            Console.ReadKey();
        }
    }
}
```
これをcsc.exeでコンパイルしてserver.exeをつくります。

```
 C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe server.cs
```

# あとがき
せめてPythonだけでも使えれば…('､3_ヽ)_

※ 今回は試みとしてやってみましたが、LAN内とはいえ勝手にWebサーバを立てて運用するのはセキュリティリスクになりますので、賢い人はこんなことしてないで情シスに陳情して何とかしてもらいましょう。