Python bottleの導入と使い方・ログイン機能付きの簡単なwebサーバを立ててみる

## はじめに  
Rasperry pi で IoT を作りたいと思っていたんですが、web 系・ソフト系の知識があまりないので困っていました。
Web アプリケーションを Python で実現する場合は Django や Zope, Pyramid などのフレームワークが使用されていますが、これらは多くの機能を備えている反面、自分のような初心者にはレベルが高すぎます。  

そこで、Bottle という Python の軽量フレームワークを勉強することにしました。 
フレームワークが1つのファイルに収まっているため、初心者が勉強するのにはうってつけらしいです。

__◎今回の目的__
まずは PC 環境で Bottle を使用して、Bottle の基本的な使い方について学ぶ。
ログインフォームつきのサーバを立てるところまでの備忘録

__◎環境__
WSL Ubuntu 18.04.3 LTS

## 概要
1. Bottle 導入  
    1.1. `bottle.py` のダウンロード  
    1.2. 動作確認  
2. ルーティング  
    2.1. 静的ルーティング  
    2.2. 動的ルーティング  
3. HTTP について  
    3.1. HTTP とは  
    3.2. HTTP のリクエストメソッド  
4. Bottle におけるメソッド対応  
    4.1. GET メソッド  
    4.2. GET 以外のメソッド
5. Request オブジェクト
6. ログインフォームを作ってみる

## 1. Bottle 導入
### 1.1. `bottle.py` のダウンロード

まずは適当に作業用のディレクトリを作成する。

```
$ mkdir bottle_test
$ cd bottle_test
```

以下のコマンドを実行して `bottle.py` をダウンロードする。  
※ Bottle は単一の python ファイルなので、`pip install bottle` でも `wget` でも変わらない。

```
$ wget http://bottlepy.org/bottle.py
```

### 1.2. 動作確認
Hello world で動作確認してみる。  
`bottle.py` と同じ階層（上記では bottle_test ）に以下の `index.py` を作成する。

```python:bottle_test/index.py
# -*- coding:utf-8 -*-

from bottle import route, run

@route('/hello')
def hello():
    return "Hello world!"

run(host='localhost', port=8080, debug=True)
```

これを実行すると、web サーバが立ち上がる。

```
$ python3 index.py
```

ブラウザで http://localhost:8080/hello へアクセスすると、 `Hello world!` と表示されるはず。（```ctrl + c``` で終了）

![kakunin.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/524292/fcf253b3-deda-eba6-44b8-c125b1061363.png)

これで、bottle の動作を確認することができた。

## 2. ルーティング

Bottle では、クライアント（ブラウザ）からリクエストされた URL に python の関数を紐づけることができる。これをルーティングという。  

例えば、下記のようなコードを使うと、`http://xxxxx/hoge` という URL がリクエストされると、関数 hogehoge() が実行されて、view フォルダ内部にある index.tpl (中身は HTML ) がクライアント（ブラウザ）に送信される。

```python:ルーティング
@route('/hoge')
def hogehoge():
    return template('view/index')
```
### 2.1. 静的ルーティング
実際に、ルーティング機能と html の組み合わせを試してみる。  
先ほどの `bottle_test/index.py` を以下のように書き換えて保存し、さらに `bottle_test/view/index.tpl` を作成する。

```python:bottle_test/index.py
# -*- coding:utf-8 -*-

from bottle import route, run, template

@route('/hello')
def hello():
    return template('view/index')

run(host='localhost', port=8080, debug=True)
```

```html:bottle_test/view/index.tpl
<html>
  <head>
  </head>
  <body>
    <p>Hello!</p>
  </body>
</html>
```

この状態で、`index.py` を実行し、実行中の PC のブラウザで http://localhost:8080/hello にアクセスすると、"Hello!" と表示されるはず。
※ちなみに `run` 関数は Bottle フレームワークの WEB サーバを起動するコマンド。`host = '0.0.0.0'` に変更して実行すれば、同じネットワーク内の端末からもアクセスできるようになる。その場合アクセスする URL は `http://PCのIPアドレス:8080/hello` となる。うまく接続できない場合、Firewallが8080番ポートを許可していないことなどが原因だと考えられる。
[ファイアウォールの設定でポート番号の通信を許可するには](https://www.pfu.fujitsu.com/imaging/downloads/manual/ss_webhelp/jp/help/webhelp/topic/refer_firewall.html)

```
$ python3 index.py
```

今試したものは、 `静的ルーティング` と呼ばれる。

### 2.2. 動的ルーティング

HTTP ではブラウザがリクエストする URL の末尾にパラメータを加えることができるが、Bottle ではそれを関数の引数としてとることができる。(HTTP については次章を参照)

先ほどの `bottle_test/index.py` と `bottle_test/view/index.tpl` を以下のように書き換えて保存する。

```python:bottle_test/index.py
# -*- coding:utf-8 -*-

from bottle import route, run, template

@route('/hello')
@route('/hello/<name>')
def hello(name = '知らない人'):
    return template('view/index', nm = name)

run(host='localhost', port=8080, debug=True)
```

```html:bottle_test/view/index.tpl
<html>
  <head>
  </head>
  <body>
    <p>Hello!{{nm}}</p>
  </body>
</html>
```

以下の表に示すように、URL の末尾につけるパラメータによって表示結果が変わる。
URL の末尾のパラメータが関数の引数として取られ、tpl ファイル中の {{nm}} が引数に置換されたものがブラウザに表示される。
関数の変数に初期値を設定することで、末尾にパラメータがない場合の動作も指定できる。

|リクエストする URL|表示結果|
|:---:|:---:|
|http://localhost:8080/hello|Hello!知らない人|
|http://localhost:8080/hello/tanaka|Hello!tanaka|
|http://localhost:8080/hello/12345|Hello!12345|

## 3. HTTP について

ここからの内容には HTTP に関する知識が必要だったため、少しだけ勉強した。軽くまとめる。

### 3.1. HTTP とは 

>Hypertext Transfer Protocol（ハイパーテキスト・トランスファー・プロトコル、略称 HTTP）とは、WebブラウザがWebサーバと通信する際に主として使用する通信プロトコルであり、インターネット・プロトコル・スイートのメンバである。HTMLなどのテキストによって記述されたWebページ等のコンテンツの送受信に用いられる。  [Wikipedia](https://ja.wikipedia.org/wiki/Hypertext_Transfer_Protocol)

HTTP は、サーバ・クライアントによるリクエスト・レスポンス通信を行う。クライアント（ブラウザ）がデータのリクエストを送信すると、リクエストを受けた Web サーバがレスポンスとしてデータを返す形式のこと。

### 3.2. HTTP のリクエストメソッド

クライアント側（ブラウザ）がサーバ側に送信するリクエストメソッドは数種類あるが、ここではよく使われる 5 つのメソッドについてまとめる。  
詳しくは [Web 初心者のための HTTP](https://myenigma.hatenablog.com/entry/2015/08/02/230752)

#### ① GET
URI のデータを取得するリクエストメソッドで、HTML、画像などのデータを取得するときに使用される。
簡単に言ってしまえば「このページを送信してくれ」というリクエストのこと。 
また、GET メソッドで指定した URI の後ろに ? + 文字列 をつけることで、リクエスト時にサーバ側にパラメータを送信することもできる。（例: `http://localhost:8080/index.html?hoge=hoge`）

#### ② POST
クライアント側からサーバ側にデータを送信するメソッド。
GET メソッドでは URI の末尾にパラメータを書くことでサーバにパラメータを送信するが、こちらはHTML のフォームからパラメータを送信する。
こちらは送ることができるデータのバイト数に制限はない。 
重要度の高いデータを暗号化して送信することもできる。  

#### ③ HEAD
URI のヘッダデータのみを取得するメソッド。
GET メソッドはデータの本体も同時に取得してしまうが、HEAD メソッドではデータのヘッダ部分（データの更新日時やデータサイズ）のみを取得することができる。

#### ④ PUT 
URI の内容を作成・置換するメソッド。
指定したリソースが存在しなければ新規作成し、指定したリソースが存在したら更新する。
PUT で作成するリソースの URI はクライアント側（ブラウザ）が決定する。

#### ⑤ DELETE
URI の内容を削除するメソッド。

## 4. Bottle におけるメソッド対応

### 4.1. GET メソッド

`@route` デコレータにオプションを渡さずに使った場合は、GET メソッドのアクセスに対する処理として扱われる。（`2. ルーティング` に記載したソースコードは全て GET メソッドのアクセスへの処理を記述している）

```python:GETメソッド
# GETメソッドで /hoge にアクセスされたときに hogehoge() を実行する

@route('/hoge')     # もしくは @get('/hoge')
def hogehoge():
    return template('<b>Hello World</b>!')
    
```

### 4.2. GET 以外のメソッド

GET 以外のメソッドに対応する場合、以下の2通りの記述方法がある。
以下に、 POST メソッドを例にした書き方を示す。

① `@route` デコレータにオプションを渡す

```python:
# 方法その１: @route デコレータにオプションを渡す
# POSTメソッドで /hoge にアクセスされたときに hogehoge() を実行する
from bottle import route, run, template

@route('/hoge', method = 'POST')
def hogehoge():
    return template('<b>Hello World</b>!')
```

② メソッドに対応したデコレータを使う

```python:
# 方法その２: メソッドに対応したデコレータを使う
# POSTメソッドで /hoge にアクセスされたときに hogehoge() を実行する
from bottle import post, run, template

@post('/hoge')
def hogehoge():
    return template('<b>Hello World</b>!')

```

__一覧__

|メソッド|方法①|方法②|
|:---:|:---:|:---:|
|GET|`@route('/hoge')`|`@get('/hoge')`|
|POST|`@route('/hoge', method = 'POST')`|`@post('/hoge')`|
|PUT|`@route('/hoge', method='PUT')`|`@put('/hoge')`|
|DELETE|`@route('/hoge', method='DELETE')`|`@delete('/hoga')`|

## 5. Request オブジェクト

`bottle.request` を使用すると、現在処理しているリクエストにアクセスできる。

```python
from bottle import request
```


+ `request.query` : GET メソッドでクライアントから送信されたパラメータ（クエリパラメータ）
+ `request.forms` : POST / PUT メソッドでクライアントから送信されたフォームパラメータ
+ `request.url` : アクセスされた URL
+ `request.files.get('name')` : 添付ファイル

詳細は[BottleのRequest/Responseオブジェクトをマスター](https://qiita.com/tomotaka_ito/items/62fc4d58d1be7867a158)

## 6. ログインフォームを作ってみる

ここまでの知識を使って、ログインフォームを作ってみる。

`bottle_test/index.py` と `bottle_test/view/index.tpl` を以下のように書き換えて保存する。

```python:bottle_test/index.py
# -*- coding:utf-8 -*-

from bottle import get, post, request, run, template

def check(password):    # ログイン判定
    if password == "abc":
        return True
    else:
        return False

@get('/login')  # GET メソッドで /login にアクセスしたときの処理
def login_form():
    return '''
        <form action = "/login" method = "post">
            Username: <input name = "username" type = "text" />
            Password: <input name = "password" type = "password" />
            <input value = "Login" type = "submit"/>
        </form>
    '''

@post('/login')
def login():
    #POSTされた情報にアクセス
    name = request.forms.get('username')
    pasw = request.forms.get('password')
    # 判定
    if check(pasw):
        return template('view/index', nm = name)
    else:
        return "<p>Login failed.</p>"

run(host='localhost', port=8080, debug=True)
```

```html:bottle_test/view/index.tpl
<html>
  <head>
  </head>
  <body>
    <p>Hello!{{nm}}</p>
  </body>
</html>
```

`index.py` を実行する。

```
$ python3 index.py
```

この状態でブラウザから http://localhost:8080/login にアクセスすると、ログイン画面が表示される。

![login.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/524292/d07b3e0f-ad34-b211-e653-f2b51e11fe14.png)

適当な Username と設定したパスワード（ abc ）を入力して `Login` を押すと、 `Hello!<入力したUsername>` 
と表示されるはず。


## 参考にさせていただいたサイト
インストール・動作確認・route の使い方 : [Pythonの軽量Webフレームワーク「Bottle」](https://symfoware.blog.fc2.com/blog-entry-1068.html)
Bottle の基本的な使い方 : [Python の軽量 Web サーバ Bottle の使い方](https://myenigma.hatenablog.com/entry/2015/06/27/112553)
Bottle 詳細 : [Python で一番小さい WEB フレームワーク bottle.py ](http://www.denzow.me/archive/category/Bottle)
HTTP について : [Web 初心者のための HTTP](https://myenigma.hatenablog.com/entry/2015/08/02/230752)
Bottle で画像ファイルのリンク生成 : [PythonのBottleフレームワークで静的ファイルのリンク生成](https://symfoware.blog.fc2.com/blog-entry-1069.html)
Bottle の Request/Response : [BottleのRequest/Responseオブジェクトをマスター](https://qiita.com/tomotaka_ito/items/62fc4d58d1be7867a158)