LinuxでHTTPサーバー lighttpdを動かす。ビルド～cgi作成まで

HTTPサーバー、特にlighttpdで色々試したいことがあったので環境構築しました。
メジャーなOSSのHTTPサーバーは普及率シェア1位のApache, Nginx等ですが、何故かlighttpd。組み込み向け(?)に軽量化された面白いサーバーです。

以下3点に記事を分けて紹介していきたいと思います。今回は導入～cgiまで。
HTTPS導入によるセキュリティ強化については[こちらの方](https://qiita.com/z80/items/62225e6468d823fc73c9)が紹介されているので、どちらかというとQualys SSL LabsのSSL Server Testの深堀記事になると思います。

- 導入～cgi
- プラグイン導入の紹介
- HTTPS導入によるセキュリティ強化

## **lighttpdとは**

Wikipediaより

>lighttpd （ライトティーピーディー; "Lighty"とも呼ばれる）は高速性の重視される環境に最適化された、安全・高速で標準に準拠し、柔軟であることを指向して設計されたWebサーバである。
メモリ消費量が少なく、CPU負荷の少ない高速動作が目的となっているため、負荷が問題な場合や、静的コンテンツを動的コンテンツと区別して送信する場合などに適しているとされる。

高機能がため肥大化しやすいHTTPサーバーにあって、**安全・高速・軽量** Lightyをうたうサーバー。組み込み系の製品との通信をパケットで覗くと、たまーに見かけることがあります。

また、[こちら](https://qiita.com/developer-kikikaikai/items/8a954edd6d446737bbc9)で軽く紹介したように、`Chain of Responsibility`の考え方や、FD登録によるイベント駆動があったり、マクロの使い方が面白かったリ、コードを覗くと結構面白いです。
しかも**確かシングルスレッドで実現してるんですよ**。それで高速が売りとなっている要素はなんだろう？その辺りも興味深いです。

[公式wiki](http://redmine.lighttpd.net/projects/lighttpd/wiki)もありますので、興味がある方はどうぞ。

## **構築環境**

Ubuntu 18.04 デスクトップ

## **インストール**

なんと、**apt install**でデフォルトインストールできます！

```
apt install lighttpd
```

ただ今回はソースからインストール。バージョンはlighttpd-1.4.48にします。

### **ビルド～インストール**

1 . ダウンロード [ここ](https://www.lighttpd.net/download/)から。
2 . 必要なアプリをインストール。※1

```
sudo apt-get install libpcre3-dev bzip2
```

3 . コード内で以下を実行

```
./configure
make
make install
```

結果、/usr/local/以下にアプリがインストールされます。lighttpd本体は`/usr/local/sbin/`にあります。
インストールはこれでOK

※1どちらも--withoutオプションで無効化は出来ますが、これで動かなかったら嫌なので今回は必要アプリも導入です。

注意:
1.4.48からかな？最新版だとopenssl用のモジュール、mod_opensslが導入されています。
ただし、こちらはデフォルト無効なのでご注意を。

### **起動**

こんな感じ。`-f`で設定ファイルを指定。`-D`はフォアグラウンド実行です。

```
sudo /usr/local/sbin/lighttpd -f /usr/local/www//lighttpd.conf -D
```

## **設定**

設定ファイルのサンプルはソースの`lighttpd-1.4.48/doc/config/`, `lighttpd-1.4.48/doc/config/conf.d`に色々あります。
今回の設定をざっと説明。

### **最初の設定:ドキュメントルートを用意してトップページを開く**

**lighttpdを便利に使うための機能の一つ、プラグイン**。導入方法等は別途しますが、デフォルトでも色々なプラグインが用意されています。
設定は以下のような形。最低限この二つがあればTOPページは表示できます。

```conf:lighttpd.conf
server.modules = (
        "mod_access",
        "mod_alias",
)
```

**ドキュメントルートはserver.document-rootで設定**します。

```conf:lighttpd.conf
var.server_root = "/usr/local/www/"

server.document-root        = server_root + "/html"
server.upload-dirs          = ( "/var/cache/lighttpd/uploads" )
server.errorlog             = "/var/log/lighttpd/error.log"
server.pid-file             = "/var/run/lighttpd.pid"
server.username             = "www-data"
server.groupname            = "www-data"
server.port                 = 80

index-file.names            = ( "index.php", "index.html" )
```

とりあえずHello Worldレベルのシンプルなものならindex.htmlが開くと思います。
でもこれだけだとindex.htmlの中にpngやjavascript等があると動作しません。

**mimetypeの設定が無いと、面白いページは作れません。**
コードのサンプルからそのまま持ってきましょう。

```conf:lighttpd.conf
## mimetype mapping
include "conf.d/mime.conf"
```

```conf:mime.conf
mimetype.assign = (
        ".pcf.Z" => "application/x-font-pcf",
        ".tar.bz2" => "application/x-gtar-compressed",
        ".tar.gz" => "application/x-gtar-compressed",
中略...
        "README" => "text/plain; charset=utf-8",
        "Makefile" => "text/x-makefile; charset=utf-8",

        # enable caching for unknown mime types:
        "" => "application/octet-stream"
)
```

用意されているサンプル、**トータル552行分のmimetype定義！**
もうmimetype定義はlighttdのmime.confで調べよう！ってレベルですね。

こちらを読み込めば、画像でもjsでもなんでも来いです！
後は設定した`/usr/local/www/html`配下にページを持ってくればTOPページはOK!
自分はDoxgen出力ファイルでTOPの確認しました。

### **cgiの設定**

**手軽にHTTPサーバーを活用しよう！となったらまずはcgi**じゃないでしょうか？
**スクリプト言語のプログラムをhttp経由で実行が可能にする機能**です。

まずは設定。以下3点でOK。こちらも用意されているサンプルのconfから引っ張ってきました。

- **server.modulesにmod_cgiプラグインを導入**(デフォルトで用意されています)
- **alias.url**⇒`http://IP/cgi-bin`と環境内のフォルダを紐づけ
- **cgi.assign**⇒使用できる拡張子を指定

```conf:lighttpd.conf
## cgi.assign
server.modules += ( "mod_cgi" )
alias.url = ( "/cgi-bin" => "/usr/local/www/cgi-bin" )
$HTTP["url"] =~ "^/cgi-bin" {
        cgi.assign                 = ( ".cgi" => "/bin/sh",
                                       ".py"  => "/usr/bin/python3.6")
}
```

これで**`http://IP/cgi-bin/スクリプト名`で、"/usr/local/www/cgi-bin/スクリプト名"の全スクリプトが実行されます。**
(間違っても/binとかを指定しないでくださいね。)

#### **cgi仕様**

**HTTPリクエスト**

- クエリ⇒環境変数の`QUERY_STRING`に格納されます。
- ボディ⇒標準入力に入力されます。

**HTTPレスポンス**

```
ヘッダー
\r\n
ボディ
```

の形式で標準出力すればOKです。**ヘッダーを省略しても、勝手にlighttpdが`Content-Type:text/plain`でヘッダー追加してくれます。**
簡単ですね！

#### **サンプル1. QUERY_STRINGの使い方**

※Python3.6でのサンプルです。

QUERY_STRINGは環境変数に設定されるので、こんな感じで取れます。
このサンプルではクエリをkey => valueの形式で表示させてます。
(別cgi用にhelpを出力できるようにしてます。)

```python:show_query.py
import sys, os

query_string=os.environ.get("QUERY_STRING")

sys.path.append('..')
from util.command import show_help
command=[""]
help_str='Show query string'

if show_help([help_str] + command):
    sys.exit()

result=''
if not query_string:
    print('Empty query')
else:
    queries=query_string.split("&")
    for i, query in enumerate(queries):
        print(i,  '=>' , query)
```

例えば
`http://IP/cgi-bin/show_query.py?test=1&test2=2`でアクセスすると、こんな感じになります。

```
0 => test=1
1 => test2=2
```

#### **サンプル2. テキスト以外のContent-Typeも使えます！**

また、テキストだけではなくて別のContent-Typeも指定できます。
例えば**htmlを表示させて`Content-Type: text/html`を指定するスクリプトを書けば、html形式になります**。
こちらのサンプルでは、導入しているcgiのヘルプを`--help`で取得して、htmlのテーブルにして返します。

```python:help.py
import sys, re
#テーブルを作る
def create_table(fname, help_list):
    result='<tr>\n'
    result+='  <td>' + fname + "</td>\n"
    result+='  <td>' + help_list[0] + "</td>\n"
    result+='  <td> '
    for i, cmd in enumerate(help_list):
        if i == 0:
            continue
        result+=cmd + ' '
    result+='  </td>\n'
    result+='</tr>\n'
    return result
```

中略

```python:help.py
#htmlべた書き
html='''<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    </head>
    <style>
    table{
      border-collapse:collapse;
      margin:0 auto;
    }
    td,th{
      padding:10px;
      border-bottom:solid #ccc;
    }
    th{
      border-bottom:5px solid #ccc;
    }
    table tr th:nth-child(odd),
    table tr td:nth-child(odd){
      background:#e6f2ff;
    }
    </style>
    <body>
      <table>
        <thead>
        <tr>
         <th>cgi name</td>
         <th>usage</td>
         <th>use system command</td>
       </tr>
       </thead>
'''
#cgi分のヘルプメッセージをテーブル化
for cgi, help_list in help_dict.items():
    html+=create_table(cgi, help_list)

html+='''
       </table>
    </body>
</html>'''

#HTTPヘッダーを表示
print("Content-Length: " , len(html))
print("Content-Type: text/html")
print("")
#HTTPボディとして作成したhtmlを表示
print(str(html))
```

出力はこんな感じになります。
<img width="750" alt="help.png" src="https://qiita-image-store.s3.amazonaws.com/0/252277/2d268489-a6b3-7c9c-b546-a60bbb18a43e.png">

UI関連はド素人ですが、せっかくのhtmlサンプルなのでこちらのデザインを~~丸パク~~参考にしました。
[オシャレなTableデザインサンプル15点](https://cotodama.co/table-design/)

## **設定・cgiサンプル**

ヘルプで出しているcgiサンプル、設定ファイルは以下にコミットしています。
https://github.com/developer-kikikaikai/lighttpd_setting

## **導入についての感想**

環境構築系はハマるイメージがあるんですが、**ソース内のdoc/config/conf.dが充実している**為、すんなり導入出来ました。
元々知ってるってのもありますが。lighttpdのwikiが意外とメンテされていない部分があったりするので、この充実度合いは意外でした。

また、**minetypeの記載が凄い！**まとめサイトでは表現しきれない充実度がありました。
いつもググって調べてたんですが、これからはこのlighttpdの設定ファイルを覗くようにしよう。

後**python、超楽しい！**
これでクラス設計も出来るしCや他の言語との親和性もいいと聞くので、なかなか興味深いですね。

## **参考**

参考がpython初心者の導入tipsみたいになってます

**インストール時**
[インストール時の参考](http://d.hatena.ne.jp/kitokitoki/20091012/p1)

**一部cgi化した便利コマンド一覧**
[Linuxリソースの監視・確認に関するコマンド逆引きメモ](https://qiita.com/rsooo/items/42f0902d42bab6ecf175)

**htmlの参考**
[オシャレなTableデザインサンプル15点](https://cotodama.co/table-design)

**Pythonの参考達**
・文字列、配列等基本的な情報
[【Python入門】split関数で文字列の分割の仕方](https://qiita.com/Morio/items/38701038ad098dd5dc3d)
[Pythonらしいコードの書き方](http://kesin.hatenablog.com/entry/2013/05/12/004541)
[Pythonでリスト（配列）に要素を追加するappend, extend, insert](https://note.nkmk.me/python-list-append-extend-insert/)

・ファイル操作
[Pythonでのファイル操作](https://qiita.com/zaburo/items/0ba12417dfb39fcb1555)
[Pythonでファイル、ディレクトリ（フォルダ）のサイズを取得](https://note.nkmk.me/python-os-path-getsize)

・標準コマンド実行系
[python上でunixコマンドを実行する](https://qiita.com/tdrk/items/9b23ad6a58ac4032bb3b)
[python3のbytes型とstr型の比較と変換方法](https://python.civic-apps.com/python3-bytes-str-convert/)

・別pythonファイルインクルード
[Pythonのimportについてまとめる](https://qiita.com/suzuki-hoge/items/f951d56290617df4279e)

・バイナリデータ入出力(cgiでのmp4動画配信を試してみた際に調査)
[バイナリデータ読み書きなど、詳細](https://docs.python.jp/3/tutorial/inputoutput.html)
[標準出力へのバイナリモード出力(linux)](https://bugs.python.org/issue18512)