続・玩式草子 ―戯れせんとや生まれけん―

第9回ウィスキー・ボトル・ブルース(3番)

「令和」の時代となってそろそろ1ヵ月、新しい元号にも次第に馴染んできたころでしょうか。

天皇陛下の生前退位や新元号の事前発表にはさまざまな意見があったものの、昭和天皇の崩御による自粛ムード一色で始まった「平成」に対し、国民全員が明るいお祝いムードで迎えることができた「令和」は、新しい時代の始まりを強く感じます。

その勢いに便乗して(笑⁠⁠、約1年ぶりのバージョンアップとなるPlamo Linux 7.1をリリースしました。Plamo-7.1では、メンテナの加藤さんや田向さんのおかげで、glibcやnotofontの新元号対応も取り込んでいます。

図1 Plamo-7.1の新元号対応
図1 Plamo-7.1の新元号対応

さて、Plamo-7.1については回を改めて紹介することとして、今回はBottleで簡単なウェブサービスを作ってみましょう。

Bottleと画像ファイル

Bottleをイジっていて「あれ?」と思ったのが、画像データのような静的ファイルの扱いです。

たとえば、"./Photo"ディレクトリ中に配置した画像ファイルをBottle経由で表示するには、こんなコードが必要になります。

 1  #!/usr/bin/python
 2  
 3  from bottle import route, run, static_file
 4  @route('/show/<filename>')
 5  def show_img(filename):
 6      return static_file(filename, root='./Photo/')
 7  
 8  run(host='localhost', port=8080, reloader=True, debug=True)

表示したい画像ファイルを"img01.jpg"とすると、このスクリプトを実行して、ブラウザから"http://localhost:8080/show/img01.jpg"にアクセスすれば、その画像が表示されます。

図2 Bottleでの画像表示例
図2 Bottleでの画像表示例

リストの3行目でimportしている"static_file"が静的ファイルを扱うためのメソッドで、ブラウザに画像データを送信する場合は、6行目のようにこのメソッドを経由した結果をreturnで返すことになります。

このあたり、apache httpdでは、URLにファイル名を指定するだけで画像を表示できますし、許可しておけばディレクトリにあるファイルの一覧表示もしてくれます。そういう記憶があったので、Bottleの画像表示はずいぶん面倒なんだな、と思ったものの、すこし考えてみるとこの処理は必須なことに気づきました。

というのも、HTTPでデータをクライアントに送る場合、生のデータをそのまま送り付けてもダメで、そのデータがどういう種類かを示すContent-Typeや文字コードを示すcharsetサイズがどれだけかを示すContent-Lengthといった情報(レスポンスヘッダ)を付ける必要があり、既存のファイルに対してこれらレスポンスヘッダを用意するのが"static_file()"というメソッドの仕事というわけです。

さて、画像を表示する方法はわかったので、このコードを元に機能を追加していきましょう。まず、ファイル名を指定せずにアクセスすれば、ディレクトリ内のファイルリストを返すようにしてみます。

ディレクトリにあるファイルのリストはPythonのos.listdir()を使えばよさそうなので、とりあえずimport osして、os.listdir()の結果を返すコードを書いてみました。

 1  #!/usr/bin/python
 2  
 3  import os
 4  from bottle import route, run
 5  
 6  @route('/show')
 7  def show_list():
 8      files = os.listdir('./Photo')
 9      return (files)
10  
11  run(host='localhost', port=8080, reloader=True, debug=True)

このスクリプトを動かして、"http://localhost:8080/show"にアクセスすると、確かにファイル名のリストが返ってきます。

図3 os.listdir()の結果をそのまま表示
図3 os.listdir()の結果をそのまま表示

さすがにこれだけでは寂しいので、templateを使って出力結果を整形しましょう。まず、ファイルリストをテーブルに組むようなテンプレートファイルを用意しました。このテンプレートファイルを"show_list.tpl"という名前にしておきます。

 1  <P> Photos </P>
 2  
 3  <table>
 4  % for i in photos:
 5    <tr> <td> {{i}} </td> </tr>
 6  % end
 7  </table>

スクリプトの方では、4行目のimport対象に"template"を追加し、9行目をこのテンプレートファイルを使うように修正します。

 9      return(template('show_list', photos=files))

これでファイル名の一覧が見やすくなりました。

図4 os.listdir()の結果をtemplate機能で表示
図4 os.listdir()の結果をtemplate機能で表示

このファイルリストを、先に紹介したstatic_file()を使う"/show/<filename>"のルーティングと組み合わせれば、ファイル名の代りに実際の画像を表示することができます。

まず、"show_list.tpl" の5行目を以下のように修正し、リンクや<img>タグの部分に画像を表示するためのルーティングを組み込みました。

 1  <P> Photos </P>
 2  
 3  <table>
 4  % for i in photos:
 5    <tr> <td> <a href="/show/{{i}}"> <img src="/show/{{i}}" width="200"> {{i}} </a>  </td> </tr>
 6  % end
 7  </table>

一方、Bottleを使ったスクリプト本体はこうなりました。

 1  #!/usr/bin/python
 2  
 3  import os
 4  from bottle import route, run, static_file, template
 5  
 6  @route('/show/<filename>')
 7  def show_img(filename):
 8      return static_file(filename, root='./Photo/')
 9  
10  @route('/show')
11  def show_list():
12      files = os.listdir('./Photo')
13      return(template('show_list', photos=files))
14  
15  run(host='localhost', port=8080, reloader=True, debug=True)

これくらいのコードで、Photo/以下にある画像の一覧表示と、選択した画像の全画面表示ができるようになりました。

図5 画像の一覧表示
図5 画像の一覧表示

画像のアップロード

画像の表示ができるようになったので、次は画像ファイルをアップロードする機能を作ってみます。

ファイルのアップロードは、HTMLの<form>タグの中で<input type="file">を使えばいいでしょう。この<form>からアップロードされたファイルはBottleのrequest.files.get()メソッドを使って受け取ります。

前回紹介したように、BottleではHTTPのリクエストメソッドごとにルーティングを設定できるので、"/upload"をGETでアクセスすると<form>タグを送り、POSTならばファイルを受けとる、ということにすると、こんなコードが書けます。

15  @route('/upload', method="GET")
16  def upload_file():
17      return '''
18  upload photo
19  
20  <form action="/upload" method="POST" enctype="multipart/form-data">
21     <input type="file" name="img_file"> <P>
22     <input type="submit" value="アップロード">
23  </form>
24  '''
25  
26  @route('/upload', method="POST")
27  def get_file():
28      upload = request.files.get('img_file')
29      upload.save("./Photo/")
30      return 'Upload OK. FilePath: %s' % (upload.filename)

前半のupload_file()は、アップロード用の<form>タグを送る処理で、後半のget_file()が送られてきたファイルをPhoto/以下にセーブする処理です。20行目で指定しているenctype="multipart/form-data"は、送信するデータが複数のパートから構成されていることを示し、ファイルのアップロードの際には必須の指定になります。

後半の処理では、<form>経由で送られてくるデータを保持しているrequest.filesオブジェクトを、name属性で指定した'img_file'という名前をキーにuploadにコピーし、このオブジェクトが持つsaveメソッドを使って、指定したディレクトリ("./Photo/")以下に受け取ったデータをセーブしています。

このコードを先に紹介した画像表示用のスクリプトに追加して、"http://localhost:8080/upload/"にアクセスすると、upload_file()から送られたファイルアップロード用の画面(<form>タグ)が表示されます。この画面の"Browse…"ボタンを押すと、ブラウザ側の機能でサムネイル表示を有したファイル選択ウィンドウが開きます。

図6 ファイルアップロード用の選択ウィンドウ
図6 ファイルアップロード用の選択ウィンドウ

このウィンドウでファイルを選択し、⁠アップロード」ボタンを押すと、POSTメソッドでサーバにファイルが送信され、29行目のupload.save()で"./Photo/"ディレクトリにファイルがセーブされて、"Upload OK"のメッセージが返されます。

この後、http://localhost:8080/show にアクセスすると、新しくアップロードした画像もちゃんと表示されました。

図7 一覧リストの更新
図7 一覧リストの更新

今回のスクリプトの最終形は以下の通りで、30行ほどのコード(と10行弱のテンプレートファイル)で簡単なウェブアルバムが作成できました。

 1  #!/usr/bin/python
 2  
 3  import os
 4  from bottle import route, run, static_file, template, request
 5  
 6  @route('/show/<filename>')
 7  def server_static(filename):
 8      return static_file(filename, root='./Photo/')
 9  
10  @route('/show')
11  def list():
12      files = os.listdir('./Photo')
13      return(template('show_list', photos=files))
14  
15  @route('/upload', method="GET")
16  def upload_file():
17      return '''
18  upload photo
19  
20  <form action="/upload" method="POST" enctype="multipart/form-data">
21     <input type="file" name="img_file"> <P>
22     <input type="submit" value="アップロード">
23  </form>
24  '''
25  
26  @route('/upload', method="POST")
27  def get_file():
28      upload = request.files.get('img_file')
29      upload.save("./Photo/")
30      return 'Upload OK. FilePath: %s' % (upload.filename)
31  
32  run(host='localhost', port=8080, reloader=True, debug=True)

このスクリプトは必要最低限の機能を実装しただけで実用性は低いものの、Bottleの便利さと開発の簡単さは感じていただけたのではないでしょうか。


今回は画像ファイルをやりとりしたものの、Bottleのstatic_file()メソッドは画像ファイル専用ではなく、動画や音声ファイルでも適切なHTTPレスポンスヘッダを作成してくれます。

加えて、firefoxやchromeのようなブラウザは、さまざまな音声/動画CODECにネイティブで対応しているので、今回のコードを元に、マルチメディア・アルバムみたいなのも簡単に作れそうです。

このように、事前にあれこれ準備する手間なく、アイデアを簡単に試してみれるのがBottleの最大の魅力でしょう。

おすすめ記事

記事・ニュース一覧