🕸

web.pyでwebアプリを作ってみよう

9 min read

はじめに

皆様、お疲れ様です。
この記事では、pythonで動作するwebフレームワークであるweb.pyを用いたアプリケーション開発の基礎的な部分について記述しています。

まず手始めに、web.pyの公式ページの説明を読んでみましょう。

web.py is a web framework for Python that is as simple as it is powerful. web.py is in the public domain, you can use it for whatever purpose with absolutely no restrictions.

  • pythonで動くシンプルでパワフルなwebフレームワークだよ。
  • パブリックドメインなので、制限なし・どんな理由であっても利用することができるよ。

...らしいです。なんだかワクワクしてきましたね。

それでは、インストールから始めましょう。今回参考にする資料はweb.pyの公式チュートリアル、およびCookbookです。

インストールしよう

私がweb.pyをはじめて触ったときはpython2にしか対応していなかったのですが、しばらく見ないうちにpython3にも対応していたようです。最新バージョンは0.61、pipを用いてインストールします。

python3対応の最新バージョンをインストール!
$ pip install web.py==0.61

python2.7でサーバーを立てる場合は、バージョン0.51をインストールしましょう。0.51以後のバージョンはpython3でしか動作しないようです。

python2のときはこっちでインストール!
$ pip install web.py==0.51

サーバを記述しよう

インストールができたら、早速サーバを建てましょう。とりあえず、web.pyの便利機能が詰まっているwebパッケージをインポートします。

app.py
import web

次に、URLのハンドリング(このURLにアクセスされたら、この処理をする、という対応関係)を定義していきましょう。今のところは、localhost:8080/(ルート)にアクセスされたら、indexに記述された処理を実行するように設定をしておきましょう。

app.py
urls = (
    "/", "index"
)

それでは、処理部分であるindexをカッチリさせましょう。web.pyでは、URLに対応する処理をクラスとして記述します。先ほどルートに対応させたindexを記述しましょう。

クラス内のGETメソッドが、GETリクエストに対する処理が記述されている場所です。また、戻り値の内容がレスポンスの内容に対応します。以下のコードの場合、GETリクエストに対して、「Hello,world!」という文字列をレスポンスすることになります。

app.py
class index:
    def GET(self):
        return "Hello, world!"

最後に、サーバを建てるためのmainメソッドを書きましょう。web.applicationメソッドの第2引数は、クラスのリスト(今回だったらindexクラス)をサーバに登録するためにglobals()になっています。

app.py
if __name__ == "__main__":
    app = web.application(urls, globals())
    app.run()

ここまでの作業が完了したら、app.pyの中身は以下のようになっているはずです。とりあえず、これだけでサーバは建ちます。

app.py
import web

urls = (
    "/", "index"
)

class index:
    def GET(self):
        return "Hello, world!"

if __name__ == "__main__":
    app = web.application(urls, globals())
    app.run()

サーバを起動しよう

サーバも記述できたので、早速起動しましょう。コンソールに戻っていただいて、以下のように実行します。今回はpython3を使用することを想定しているので、python2で実行するときはpython2コマンドを使用してください。コマンドを実行すると、以下のように表示されます。

$ python3 app.py
http://0.0.0.0:8080/

ここまで完了したら、あとはこのURLにアクセスするだけです。アクセスしたら、ブラウザに「Hello,world!」と表示されます。

私はWSL2でこの開発をしていたのですが、単純にlocalhost:8080にアクセスしてもうんともすんともいいません...。「なぜなのじゃ...」と困り果てていた所にこのサイトが手を差し伸べてくれました。

調べてみると、WSL2にはホストマシンと異なるIPアドレスが割り振られているようです。異なるIPアドレスのlocalhostなので、普通にlocalhost(つまり127.0.0.1)にアクセスすると「そんなサーバ建っていない!」と怒られちゃうんですね。

なので、以下のコマンドでIPアドレスを調べて、アクセスしましょう。

$ ip a

htmlファイルを表示させよう

ここまでで、単純な内容(文字列)をレスポンスするサーバが完成しました。しかし、これだけでは皆様が思い浮かべるアプリケーションとは呼べません。最低でも、あらかじめ用意したhtmlファイルを表示させたいですね。そこで、templatesディレクトリに配置したindex.htmlを、localhost:8080/にアクセスしたときに表示させるように、サーバを改良していきましょう。ここからは、以下のディレクトリ構造を想定しています。

.
├─ templates
│  └─ index.html
└─ app.py

まずは、templatesディレクトリをhtmlファイルの配置場所として登録しましょう。

app.py
render = web.template.render('templates/')

続いて、先ほどまで「Hello, world!」とレスポンスしていたところを、index.htmlをレスポンスするように改良しましょう。

app.py
# GETメソッドのreturn文
return render.index()

ここで注意したいのは、登録したtemplatesディレクトリ内のhtmlファイルが、web.template.renderのメソッドとして登録されていることです。今回の場合、index.htmlから拡張子を抜いた名前であるindexがメソッド名になります。

ここまで改良できたら、app.pyは以下のように記述されているはずです。

app.py
import web

render = web.template.render('templates/')

urls = (
    "/", "index"
)

class index:
    def GET(self):
        return render.index()

if __name__ == "__main__":
    app = web.application(urls, globals())
    app.run()

早速、サーバを起動して動作を確認してみましょう。今回、index.htmlは以下のように記述してみましょう。

index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Sample application</title>
    </head>
    <body>
        HTMLファイルからHello, world!
    </body>
</html>

localhost:8080/にアクセスして、「HTMLファイルからHello, world!」と表示されれば成功です。

リダイレクトさせよう

ここまで、リクエストに対応するレスポンスを返すようにしていましたが、場合によってはリダイレクトしたいときもあると思います(ログイン履歴があったらマイページにリダイレクトとか)。そこで利用できるのがweb.seeotherメソッドです。それでは、localhost:8080/redirectにアクセスしたらlocalhost:8080/にリダイレクトするように改良しましょう。

まずは、/redirectとクラスの対応関係をurlsに追加しましょう。今回は、/redirectredirectクラスを対応させます。

app.py
urls = (
    "/", "index",
    "/redirect", "redirect" #ここを追加しました。
)

次に、redirectクラスを記述しましょう。ここで注意したいのは、returnを用いてレスポンスするのではなく、raiseを用いて例外処理としてリダイレクトを実現するということです。

app.py
class redirect:
    def GET(self):
        raise web.seeother("/")

raise web.seeother("/")は、クライアントに対してSee Other(HTTPステータス303)を返し、localhost:8080/にリダイレクトするように命令しています。

ここまで改良できたら、app.pyは以下のように記述されているはずです。

app.py
import web

render = web.template.render('templates/')

urls = (
    "/", "index",
    "/redirect", "redirect"
)

class index:
    def GET(self):
        return render.index()

class redirect:
    def GET(self):
        raise web.seeother("/")

if __name__ == "__main__":
    app = web.application(urls, globals())
    app.run()

それでは、サーバを起動してlocalhost:8080/redirectにアクセスしてみましょう。見かけ上はいきなりlocalhost:8080/にアクセスしているように見えますが、開発者ツール(Chromeの場合F12で見ることができる画面)を用いると、localhost:8080/redirectからlocalhost:8080/にリダイレクトされていることがわかると思います。

静的ファイルを活用しよう

ここまでは、サーバとクライアントの間でやり取りされている情報は文字、具体的にはhtmlファイルでしたが、アプリケーションでは装飾用の画像や、様々な機能を付与するためのjavascript、ページの体裁を整えるためのcssなど(静的ファイル)を活用したい気持ちになります。しかし、適当にassetsディレクトリを作成して、その中に静的ファイルを配置したとしても、localhost:8080/assets/hoge.pngで画像を表示することはできません。

このディレクトリ構成だとhoge.pngを活用できない!
.
├─ assets
│  └─ hoge.png
├─ templates
│  └─ index.html
└─ app.py

解決方法は簡単です。web.pyを用いたアプリケーションではstaticという名前のディレクトリを作成し、その中に静的ファイルを配置する必要があります。staticディレクトリ配下に配置された静的ファイルは、クライアント側でlocalhost:8080/static/hoge.pngのように活用することが可能です。

このディレクトリ構成だとhoge.pngを活用できる!
.
├─ static
│  └─ hoge.png
├─ templates
│  └─ index.html
└─ app.py

異なる機能を別のファイルに記述したい

ここまで、「HTMLファイルからHello, world!」と表示する機能を実現するサーバを記述してきましたが、実際のアプリケーションでは様々な機能を用意する必要があります。それら様々な機能を1つのpythonファイルで管理すると、不具合が起きた際のデバッグ作業が少々面倒になります。

例えば、アプリケーションに必要な100個の機能がすべてapp.pyの中に記述されていた場合、ある機能に不具合が起きた際にapp.pyの中から該当箇所を探す手間がかかります。また、app.pyがgitなどでバージョン管理されていると、該当箇所のデバッグが完了するまで他の機能の実装がストップするという問題につながります。このような問題を回避するために、アプリケーションに必要な機能を複数のpythonファイルに分散して実装したい気持ちになります。

ここからは、2つの機能を2つのpythonファイルに分割して実装し、アプリケーションを実現する方法を習得しましょう。ここで実装する2つの機能は以下の通りです。

  1. 「Hello! This is sub-application 1!」という文字列を表示する。
  2. 「Hello! This is sub-application 2!」という文字列を表示する。

それでは、機能1から実装していきましょう。簡単のため、文字列をそのままレスポンスするように実装していきます。

subapp1.py
import web

urls = (
    "", "redirect",
    "/", "index"
)

class index:
    def GET(self):
        return "Hello! This is sub-application 1!"

class redirect:
    def GET(self):
        raise web.seeother("/")

subapp2 = web.application(urls, globals())

ここで注意したいのは、変数urls""が登録されていることです。これが登録されていないと、例えばlocalhost:8080/subapp1にアクセスした際に「そんなページはない」と怒られます。

続いて、機能1と同様に機能2を実装しましょう。

subapp2.py
import web

urls = (
    "", "redirect",
    "/", "index"
)

class index:
    def GET(self):
        return "Hello! This is sub-application 2!"

class redirect:
    def GET(self):
        raise web.seeother("/")

subapp2 = web.application(urls, globals())

最後に、これら2つの機能を統合するためのintegration.pyを記述しましょう。ここまで記述してきたsubapp1.pysubapp2.pyをインポートし、変数urlsにURLのパターンと、それぞれのsubappで定義したweb.applicationを登録しましょう。

integration.py
import web
import subapp1 # 機能1をインポート
import subapp2 # 機能2をインポート

render = web.template.render('templates/')

urls = (
    "/subapp1", subapp1.subapp1, # 機能1を登録
    "/subapp2", subapp2.subapp2, # 機能2を登録
    "/", "index",
    "/redirect", "redirect"
)

class index:
    def GET(self):
        return render.index()

class redirect:
    def GET(self):
        raise web.seeother("/")

if __name__ == "__main__":
    app = web.application(urls, globals())
    app.run()

ここまで記述出来たら、以下のようにコマンドを実行してサーバを起動しましょう。

$ python3 integration.py
http://0.0.0.0:8080/

サーバの起動が完了したら、localhost:8080/subapp1/localhost:8080/subapp2/にアクセスしてみましょう。想定した文字列が表示されていれば成功です。

さいごに

ここまでで、web.pyを用いたwebアプリケーション作成の基礎的な部分を説明してきました。web.py特有の表現方法は随所にあらわれましたが、案外記述しやすい文法になっているのかなぁと思います。今回扱った内容のほかにも、PostgreDBと接続する方法だったり、電子メールを扱ったりする機能もあるみたいなので、興味のある方はCookbookを見ながら遊んでみると楽しいと思います。また、時間ができたときにこの記事の内容を拡張できたらいいなと思っています。

こんなところで、今回の記事を終わりにします。ここまで読んでいただきありがとうございました。