[memo] 簡易的なWebサーバーの起動方法

# はじめに

* 静的コンテンツ用の簡易的な Web サーバーを起動したいときのための個人的メモ
    * `*.js` でファイルを読み込む等、`file://` では動かないような場合を想定
    * `nc` やその他で固定のコンテンツのみ返す、みたいなものはとりあえず除外
* cgi とか https とかはあまり重視しない
* 基本的に Linux/Unix 系の環境がメイン（動くなら Windows でもよい）

# 簡易Webサーバー

## Python 3.x

```shell-session
python -m http.server 8000
```

https://docs.python.org/ja/3/library/http.server.html

* ディレクトリ指定時に `index.html` 等がない場合には、ファイル一覧を表示する
* IP アドレスは `--bind ADDR` で指定(3.4 以降追加でサポート / 3.8 以降で IPv6 もサポート)
* port は引数で指定
* ドキュメントルートはカレント (3.7 以降は `--directory DIR` 形式で指定可能)
* CGI を有効にするには `--cgi` オプションが必要. フォルダーは `/cgi-bin` と `/htbin`. 実行時の UID は `nobody` を使う

## Ruby

```shell-session
ruby -run -e httpd -- --port=8000 .
```

https://docs.ruby-lang.org/ja/latest/method/Kernel/i/httpd.html

* ディレクトリ指定時に `index.html` 等がない場合には、ファイル一覧を表示する
* IP アドレスは `--bind-address=ADDR` で指定
* port は `--port=PORT`
* ドキュメントルートは引数で指定
* SSL/TLS 証明書を使って https 化も可能

## PHP

```shell-session
php -S localhost:8000
```

https://www.php.net/manual/ja/features.commandline.webserver.php

* ディレクトリ指定時に `index.html` や `index.php` がない場合には再帰的に親ディレクトリを探すが、最終的になければ `404 Not Found` になる
* IP アドレス、port は `-S ADDR:PORT` で指定
* ドキュメントルートは `-t DIR` で指定
* あたりまえだけど `*.php` ファイルにも対応している

## Java 18 以降

```shell-session
jwebserver -b ADDR -p PORT -d DIR
```

https://openjdk.org/jeps/408

* IP アドレスは `-b ADDR` で指定
* port は `-p PORT` で指定
* ドキュメントルートは `-d DIR` で指定

## BusyBox

```shell-session
busybox httpd -f -p 8000
```

<details><summary>【参考】ヘルプメッセージ</summary>

```
$ busybox httpd --help
BusyBox v1.30.1 (Ubuntu 1:1.30.1-4ubuntu6.4) multi-call binary.

Usage: httpd [-ifv[v]] [-c CONFFILE] [-p [IP:]PORT] [-u USER[:GRP]] [-r REALM] [-h HOME]
or httpd -d/-e/-m STRING

Listen for incoming HTTP requests

        -i              Inetd mode
        -f              Don't daemonize
        -v[v]           Verbose
        -p [IP:]PORT    Bind to IP:PORT (default *:80)
        -u USER[:GRP]   Set uid/gid after binding to port
        -r REALM        Authentication Realm for Basic Authentication
        -h HOME         Home directory (default .)
        -c FILE         Configuration file (default {/etc,HOME}/httpd.conf)
        -m STRING       MD5 crypt STRING
        -e STRING       HTML encode STRING
        -d STRING       URL decode STRING
```
</details>

* `-f` をつけるとフォアグラウンドで動作 (付けないとデーモン化)
* ディレクトリ指定時に `index.html` 等がない場合には `404 Not Found` になる
* IP アドレスと port は `-p [ADDR:]PORT` で指定
* ドキュメントルートは `-h DIR` で指定
* `/cgi-bin` 配下を CGI として扱う

# 雑多なメモ

動作確認していないものとか、もう古い情報とか

## node.js + ライブラリ

### http-server

インストール方法（以下はローカルにインストールする例）

```shell-session
npm install http-server
```

実行方法

```shell-session
npx http-server -p 8000
```

https://www.npmjs.com/package/http-server

* IP アドレスは `-a ADDR` で指定
* port は `-p PORT` で指定
* ドキュメントルートは引数で指定. デフォルトは `./public` があればそれを使う. なければカレントディレクトリを使う
* SSL/TLS 証明書を使って https 化も可能

### serve

インストール方法

```shell-session
npm install --global serve
```

実行方法

```shell-session
serve -p 8000
```

https://www.npmjs.com/package/serve

* IP アドレスは `-l URI` で指定（なのかな？）
* port は `-p PORT` で指定
* ドキュメントルートは引数で指定. デフォルトはカレントディレクトリ
* SSL/TLS 証明書を使って https 化も可能

### light-server

インストール方法

```shell-session
npm install light-server
```

実行方法

```shell-session
npx light-server -s . -p 8000
```

https://www.npmjs.com/package/light-server

* IP アドレスは `-b ADDR` で指定
* port は `-p PORT` で指定
* ドキュメントルートは `-s DIR` で指定

## Python 2.4 以降

既に使わないと思うけど、念のため。

```shell-session
python -m SimpleHTTPServer 8000
```

https://docs.python.org/ja/2.7/library/simplehttpserver.html

## VSCode拡張「Live Server」

https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer

## PowerShell

Windows の場合 PowerShell で少しスクリプトを書くと、簡易的な Web サーバは作れるみたい。
動作確認はしていないけど、とりあえずメモ。

http://var.blog.jp/archives/81617977.html

https://qiita.com/kikuchi/items/785631cb24dfe5fa8d52

PowerShell で簡易 Web サーバを作ったよ、というページはほかにもいくつもありそう。

## GNU Awk

GNU Awk はソケットも扱えるみたいなので、簡易 Web サーバも作れるようです。

https://techlife.cookpad.com/entry/2009/10/23/awk/

↑この記事の下の方に、ワンライナーでの Web サーバが紹介されています。
ちょっとページが崩れているみたいなので、改めてソースを転載させてもらいます。
ついでにポート番号も `8000` に変更しました（元は `80` だった）。

<details><summary>ソースの転載</summary>

```shell
awk 'BEGIN{s="/inet/tcp/8000/0/0";"pwd" |& getline;r=$0;while((s |& getline) > 0){gsub(/[\r\n]/,"");if($0 ~ /^\s*$/){c="cat " r path;printf "HTTP/1.0 200 OK\r\n\r\n" |& s;while((c |& getline) > 0){print |& s}close(c);close(s)}else if($0 ~ /HTTP/){print;path=$2}}}'
```

改行を入れると以下のような感じですね。

```awk
BEGIN{
  s="/inet/tcp/8000/0/0"
  "pwd" |& getline
  r=$0
  while((s |& getline) > 0){
    gsub(/[\r\n]/,"")
    if($0 ~ /^\s*$/){
      c="cat " r path
      printf "HTTP/1.0 200 OK\r\n\r\n" |& s
      while((c |& getline) > 0){
        print |& s
      }
      close(c)
      close(s)
    }else if($0 ~ /HTTP/){
      print
      path=$2
    }
  }
}
```

</details>

おそらくは、ワンライナーに収めるためにかなり機能を限定しているようです。

* セキュリティチェックなどは入っていない（`GET /../../etc/passwd HTTP/1.0` みたいなことができちゃいそう）常用は厳禁
* `/` アクセスで `index.html` を返す機能はない
* Mime-Type なども返さない

最近の Git Bash (Git for Windows) に入っている Awk は GNU Awk 5.0 っぽいので、Git Bash が入っていれば Windows でも動くようですね。

ちょっと手を入れると便利に使えそう？　試しに実装してみました。まだ修正するかも。


```awk:HttpServer.awk
BEGIN {
  if (port == "") port = "8000"
  if (root == "") root = "."
  BINMODE = 1
  RS="\r\n"
  socket = "/inet/tcp/" port "/0/0"
  while((socket |& getline) > 0) {
    print
    path = root "/" $2
    gsub(/\?.*$/, "", path)
    if (path ~ /\/$/) path = path "index.html"
    if ($1 != "GET" || $3 !~ /^HTTP\// || path ~ /\/\.\.\//) {
      sendError("400 Bad Request")
      close(socket)
      continue
    }
    while((socket |& getline) > 0) if ($0 == "") break
    RS = "^$" # never match regex
    rc = getline content < path
    close(path)
    RS = "\r\n"
    if (rc < 0) {
      sendError("404 Not Found")
    } else {
      sendContent("200 OK", contextType(path), content)
    }
    close(socket)
  }
  close(socket)
  exit(1)
}

function contextType(path) {
  if (path ~ /\.html$/) return "text/html"
  if (path ~ /\.js$/) return "text/javascript"
  if (path ~ /\.css$/) return "text/css"
  if (path ~ /\.txt$/) return "text/plain"
  if (path ~ /\.xml$/) return "text/xml"
  if (path ~ /\.png$/) return "image/png"
  if (path ~ /\.jpg$/) return "image/jpeg"
  if (path ~ /\.gif$/) return "image/gif"
  if (path ~ /\.svg$/) return "image/svg+xml"
  if (path ~ /\.json$/) return "application/json"
  if (path ~ /\.zip$/) return "application/zip"
  return "application/octet-stream"
}

function sendError(code) {
  sendContent(code, "text/plain", code "\r\n")
}

function sendContent(code,type,content) {
  print "HTTP/1.0 " code
  printf "HTTP/1.0 %s\r\n",code |& socket
  printf "content-type: %s\r\n",type |& socket
  printf "\r\n" |& socket
  printf "%s",content |& socket
}
```

実行時には `awk -f HttpServer.awk` みたいな感じです。
ポート番号を変更するには `awk -v port=3000 -f HttpServer.awk` みたいに `-v port=PORT` を指定してください。

気になる点。

* `GET` メソッドしかサポートしていない。
    * `HEAD` ぐらいはサポートしないとまずい？
    * エラーも `400 Bad Request` になっている。 `501 Not Implemented` が正しい？
* ディレクトリを `/` 無しで指定された場合に `404 Not Found` になる。
    * 本当は `301 Moved Permanently` で `/` 有りに振り向けるのが正しい気がする
* URLエンコードされたパスに対応していない

## C# (Windows 標準のコンパイラ使用）

Windows ではデフォルトで C# のコンパイラが入っているみたいなので、それで簡易 Web サーバーを作ってみました。

https://qiita.com/yoshi389111/items/378318f09c1ad34a894f

# 参考にした記事

https://qiita.com/sudahiroshi/items/e74d61d939f18779970d

https://snowsystem.net/development/web-server-simple-launch/

https://qiita.com/nowokay/items/17d990aa8a5b1c5223c8

# 参考になるかもしれない記事

https://developer.mozilla.org/ja/docs/Learn/Server-side/Node_server_without_framework