Adways Advent Calendar 2017 2日目の記事です。
http://blog.engineer.adways.net/entry/advent_calendar_2017
こんにちは!奥村です!
以前に、「awkを使いこなしたい」という記事を書かせて頂きました。
この記事を一言でいうと「一流のシェル芸人になりたい」という内容です。
現在もシェル芸人になるための勉強を続けています。
今回はワンライナーでHTTPサーバをたててみたいと思います。
前準備
今回はリクエストの待ち受け部分に「netcat(nc)」コマンドを使用します。
UNIX標準のコマンドではないため、事前にインストールしておきます。
Redhat系
$ sudo yum install nc
Debian系
$ sudo apt-get install netcat
実行
以下の例では、コマンドを実行するホストに「192.168.33.12」というIPを割り当てました。
$ (echo -e HTTP/1.1 200 OK\n; echo; printf "Hello World\n") | nc -l 8080
このコマンドを実行すると、8080番ポートでHTTPリクエストを待ち受けます。
- ブラウザからの確認
ブラウザからも表示を確認することができます。
実行したシェル側では
- シェル
GET / HTTP/1.1 Host: 192.168.33.12:8080 Upgrade-Insecure-Requests: 1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 User-Agent: Mozilla/5.0 (Macintosh; ....) Accept-Language: ja-jp Accept-Encoding: gzip, deflate Connection: keep-alive
のようなサーバへのリクエストが表示されています。
$ (echo -e HTTP/1.1 200 OK\n;echo -e Content-Type: text/html; echo; printf "Hello World\n") | nc -l 8080
のようにすると、ステータスコードの一行下にヘッダを書くこともできます。
# 別のターミナルでncコマンドを使って確認 $ echo -en "GET / HTTP/1.1\n\n" | nc localhost 8080 HTTP/1.1 200 OKn Content-Type: text/html Hello World
ただ、このコマンドは一度リクエストを受け付けると待ち受けを終了してしまいます。
ですので、while
を使ってnc
コマンドを再実行するようにします。
$ while :; do (echo -e HTTP/1.1 200 OK\n;echo -e Content-Type: text/html; echo; printf "Hello World\n") | nc -l 8080; done
これで常にリクエストを待ち続けるHTTPサーバができました。
応用
上の例を見てわかる通りレスポンスボディの部分は
printf "Hello World\n"
となっています。
ということはこの部分を変更すれば、任意のコマンドの実行結果をHTTPレスポンスとして返すことができるということです。 blog.engineer.adways.net で作ったシェルの出力結果をJSON形式で出力するコマンドを指定してみます。 (loj(LineOutputJson)コマンドとしてパスを通しておきます。lojの詳細は上記のリンクから確認できます。)
while :; do (echo -e HTTP/1.1 200 OK\n;echo -e Content-Type: text/html; echo; ps aux | grep nc | loj -k 2 -c "2,PID 1,USER 11,CMD") | nc -l 8080; done
これで起動します。
早速curlを実行してみたいと思います。
$ curl 192.168.33.12:8080 { "22": {"CMD": "[async/mgr]","USER": "root","PID": "22"},"24": {"CMD": "[sync_supers]","USER": "root","PID": "24"},"9164": {"CMD": "nc","USER": "vagrant","PID": "9164"},"9166": {"CMD": "grep","USER": "vagrant","PID": "9166"}}
無事JSON形式でレスポンスを取得することができました。
おまけ
せっかくHTTPリクエストで処理を受け付けれるのだから、URLを使って任意のコマンドを実行し情報がほしくなるものですね。 せっかくなので、一行書いてみることにしました
$ while :; do (sleep 4s; echo -e HTTP/1.1 200 Ok\n; echo; cmd=`cat request | head -n 1 | awk '{ print $2 }' | sed -e 's/\// /g' | sed -e 's/^\s//' | sed -e 's/pipe/|/g'`; eval $cmd) | nc -l 8080 > request; done
ファイル書き出しと読み込み、変数代入を行っているので、シェル芸的には落第レベルですが、想定していた動作はします。 アドレス以下に「/」区切りでコマンドを与えるとコマンドを実行し、その結果がレスポンスとして返ってきます。
$ curl 192.168.33.12:8080/ps/aux/pipe/grep/nc/ root 22 0.0 0.0 0 0 ? S Nov27 0:00 [async/mgr] root 24 0.0 0.0 0 0 ? S Nov27 0:04 [sync_supers] vagrant 9890 0.0 0.0 11992 696 pts/8 S+ 18:30 0:00 nc -l 8080 vagrant 9900 0.0 0.0 103320 908 pts/8 R+ 18:30 0:00 grep nc
パイプ(|)をそのままURL部分で渡してしまうとcurl等の実行元でパイプと認識されてしまいます。ですのでパイプは「pipe」を置換するようにしました。
また、ncコマンドで受け取ったリクエストをそのまま使う方法が見つからなかったので一度、ファイル名「request」として書き出すようになっています。
そのため、「2回目の実行時にようやく結果が返ってくる」という風になってしまいました。
一流のシェル芸人ならどうにかしてリクエスト文まで、ワンライナーで取得できるのではないでしょうか。 まだまだ、修行が足りません。
まとめ
コマンドを組み合わせていろいろな事ができるようになってきました。
ですが、僕の目指している「一流のシェル芸人」はこんなものじゃありません。
色んなコマンドがいつでも頭から出てくるように、日々の修行(ワンライナーの問題をすること)を欠かさず、
ls usr/bin/
, ls /bin/
, man
を継続的に実行していきたいと思います。
最後までご覧頂きありがとうございました。