カテゴリー: Web開発

  • CGI でレスポンス最速はどの言語なのか

    まだしぶとく WSGI に移行せずに CGI を書いています。ちょっと気になったので、言語によって CGI の応答速度はどれくらい違うのか、調べてみました。

    環境

    • ConoHa VPS 512 MB
    • Debian 9 Stretch x64

    言語は次の通り。

    • C (gcc version 6.3.0 20170516 (Debian 6.3.0-18+deb9u1))
    • JavaScript (Node.js v10.10.0)
    • Python2 (Python 2.7.13 (default, Nov 24 2017, 17:33:09) [GCC 6.3.0 20170516] on linux2)
    • Python3  (Python 3.5.3)
    • Perl (v5.24.1)

    測定方法

    いずれの言語も次のような内容を出力するだけの最も簡単な CGI としました。

    Content-type: text/html; charset=utf-8
    
    <p>Hello, world! written in *.</p>
    
    

    応答時間は Chrome の開発者ツールで Network タブの表示を見ました。「リクエスト開始からレスポンスの最終バイトを受け取るまでにかかった合計時間」だそうです。

    結果

    言語時間 [ms]
    Perl28
    C29
    Python238
    Python346
    JavaScript95

    まとめ

    Perl が C と拮抗するとは意外でした。昔は CGI といえば Perl の時代でしたから、それだけ最適化が進んでいるのでしょうか。

    今回は単純に 2 行のテキスト出力を行っているだけですが、これがもっと複雑な処理を行うとなると結果は異るでしょう。参考程度に。

  • Python の CGI で reCAPTCHA v2 を使う

    ちょっと前までは人間でも読解困難な文字を読ませていた CAPTCHA ですが、Google の reCAPTCHA v2 では人間である可能性が高い場合は「私はロボットではありません」というチェックを入れるだけとなって、かなりユーザへの負担が軽減されました。

    既にクリックすら必要ない reCAPTCHA v3 のベータテストが行われているので、もしかしたらすぐに v3 へ移行してしまうかもしれませんが、現時点ではまだベータ版ということで v2 を選んでいます。

    reCAPTCHA v2 を自分のサイトに導入するには、クライアントに表示されるページとサーバの両方にコードの追加が必要となります(実体は両方ともサーバにあるファイルですね)。

    流れとしては下記の通りです。

    1. reCAPTHA のページで登録を行う
    2. ユーザに表示する form にコードを埋め込む(JavaScript の読み込みと HTML)
    3. form からの値を Google に投げて判別するコードをサーバ側に追加する

    reCAPTCHA の登録

    https://www.google.com/recaptcha/ を開き、”My reCAPTCHA” から登録を行います。余談ですが右下に reCAPTCHA のマークが表示されているので、このページ自体も reCAPTCHA で保護されていますね。

    reCAPTCHA トップ画面
    reCAPTCHA トップ画面

    “Register a new site” から新しくサイトを登録します。自分の区別しやすい名前とドメイン名を入力するだけです。

    reCAPTCHA register

    登録したサイトを開くと設定の仕方が載っているので、これを参考にソースやコードを書き換えます。癖で secret にモザイクかけてしまいましたが、HTML に書かれるから隠す意味はありませんね。

    reCAPTCHA secret

    クライアントに表示される HTML の変更

    2 点だけです。

    head 要素に JavaScript の読み込みを追加

    <script src='https://www.google.com/recaptcha/api.js'></script>

    form 要素に div 要素を追加する

    <div class="g-recaptcha" data-sitekey="XXXXXXXXYOURSECRETXXXXXXXXX"></div>

    これが reCAPTCHA v2 のチェックボックスになります。

    ここまでで次のような表示が現れたら OK です(ID: の入力欄は関係ありません)。

    reCAPTCHA form

    サーバ側 Python コードの修正

    今回は昔ながらの手法で POST された form を読み取る CGI を対象としています。

    使うモジュールは次の通り。

    import cgi
    import urllib, urllib2
    import json

    cgi は form の値を読み取るために、urllib, urllib2 は Google の API にアクセスするために、json は API の返答を読み取るために使います。

    後は通常の form のように ‘g-recaptcha-response’ の値を取得して、’https://www.google.com/recaptcha/api/siteverify’ に対して secret と ‘g-recaptcha-response’ の値を POST します。

    # form から g-recaptcha-response の値を取得
    form = cgi.FieldStorage()
    response = form.getfirst('g-recaptcha-response', '')
    
    # API で値を検証する
    url = 'https://www.google.com/recaptcha/api/siteverify'
    secret = 'XXXXXXXXYOURSECRETXXXXXXXXX'
    params = urllib.urlencode({'secret': secret, 'response': response})
    req = urllib2.Request(url, params)
    res = json.loads(urllib2.urlopen(req).read())
    
    # 検証結果
    if res['success']:
        # ここに reCAPTCHA 成功時の処理を書く
        print('Passed reCAPTCHA')
    else:
        # ここに reCAPTCHA 失敗時の処理を書く
        print('Failed to pass reCAPTCHA')

    response は JSON で帰ってきますが、簡単に使うだけなら ‘success’ の値だけを取得すれば OK です。response の JSON は次のような内容を含んでいます(https://developers.google.com/recaptcha/docs/verify)。

    {
      "success": true|false,
      "challenge_ts": timestamp,  // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
      "hostname": string,         // the hostname of the site where the reCAPTCHA was solved
      "error-codes": [...]        // optional
    }

    これだけなので、bot 対策に悩まされているなら試してみる価値はあります。

  • Bottle: Python の Web フレームワークを使う

    経緯

    普段は閉鎖的なシステム開発が多いため、基本的には標準ライブラリで全て自分で実装するというスタイルを採ってきました。全て自分の実装であるが故にバグや挙動の把握がしやすいという利点はありますが、それ故にバグの温床になっているという事実もあります。何より実装と検証に時間がかかるため、生産性が非常に悪くなります。最近になって生産性向上のためにフレームワークにも手を出そうと思い立ちました。

    Python のフレームワークといえば Django が有名どころで、実際に使っていた時期もありました。しかし、高機能であるが故に自分にとっては複雑すぎて長続きしませんでした。今ではもう綺麗さっぱり忘れてしまっています。

    10 年近く経っているので事情も変ってきているだろうと調べていると、Bottle という Web フレームワークを見つけました。Flask というのも人気があるようですが、1 ファイルでの実装に魅力を感じて Bottle を選びました。今回はその Bottle について簡単に記します。

    簡単な説明

    Bottle は bottle.py の 1 ファイルで構成される、軽量で高速な Web フレームワークです。テンプレートエンジンも搭載しており、小規模な開発では扱いやすいフレームワークです。

    インストール

    公式のインストール方法には最新版の bottle.py を取得するだけのこんな方法が書かれていて、1 ファイルのコンパクトさがよくわかります。

    $ wget https://bottlepy.org/bottle.py

    pip が使える環境であれば

    $ pip install bottle

    apt が使える環境であれば

    $ sudo apt install python-bottle

    とすればよいでしょう。パスや設置のことを考えるとパッケージマネージャでインストールした方が良い思いますが、最新版を使わなければいけない事情があれば直接ダウンロードしましょう。

    サンプル

    Web サーバを建てて、アクセスすると “Hello world!” を返すだけの簡単なコードです。同様のコードが公式サイトにも載っています。

    # -*- coding: utf-8 -*-
    
    from bottle import route, run
    
    @route('/')
    def hello():
        return '<p>Hello world!</p>'
    
    run(host = '0.0.0.0', port = 8080)

    これを例えば hello.py と保存して、次のように実行します。

    $ python sample.py
    Bottle v0.12.13 server starting up (using WSGIRefServer())...
    Listening on http://0.0.0.0:8080/
    Hit Ctrl-C to quit.

    別の PC でも良いので Web ブラウザから http://[HOST_ADDRESS]:8080/ にアクセスしてみると、”Hello world!” が表示されると思います。終了するには書かれている通り、 Ctrl + C で Python 自体を終了させます。

    Hello world!
    Hello world!

    実行方法からわかるように、ただの Python スクリプトを実行しているだけなので、ソース冒頭に Python 実行ファイルへのパス(#!/usr/local/bin/python 等)を書いておいて実行権限を付与しておけば、直接実行することも可能です。

    #!/usr/local/bin/python
    ...(略)...
    $ chmod +x hello.py
    $ ./hello.py
    Bottle v0.12.13 server starting up (using WSGIRefServer())...
    Listening on http://0.0.0.0:8080/
    Hit Ctrl-C to quit.

    さきほどの hello.py は次のように記述しても全く同じ動作をします。これはオブジェクト指向な書き方で、どちらの書き方を選ぶかは好みです。名前の衝突を避けるために使われることもあります。

    # -*- coding: utf-8 -*-
    
    from bottle import Bottle, run
    
    app = Bottle()
    
    @app.route('/')
    def hello():
        return '<p>Hello world!</p>'
    
    run(app, host = '0.0.0.0', port = 8080)

    ちなみに @ がついているのは Python のデコレータ(decorator)と呼ばれるもので、関数のラッパーのようなものです。関数定義の前に置くことでその関数をデコレータで包みます。ここでは詳しくは述べません。

    ルーティング(request routing)

    リクエストに応じて処理を変更したいとき、CGI であれば目的に応じたファイルで分けます。list を表示するのに list.py, contact フォームのために contact.py など。Bottle を使えば 1 ファイルで複数のリクエストに対する処理を書くことができます。

    次のサンプルでは、http://[HOST_ADDRESS]:8080/ または http://[HOST_ADDRESS]:8080/ にアクセスすると “Home” が表示され、http://[HOST_ADDRESS]:8080/hello にアクセスすると “Hello!” と表示されます。

    # -*- coding: utf-8 -*-
    
    from bottle import route, run
    
    @route('/')
    @route('/index')
    def index():
        return '<p>Home</p>'
    
    @route('/hello')
    def hello():
        return '<p>Hello!</p>'
    
    run(host = '0.0.0.0', port = 8080)

    これでページ分けが行えますね。

    ここまでは予め用意した内容を出力しているだけですが、実際の Web アプリケーションではリクエストに応じて内容を変化させることがほとんどだと思います。クライアントからサーバへ情報を送るにはいくつか方法があります。例えば GET, POST, Cookie, … 等ですが、ルーティングの続きとして GET リクエストによるルーティングを試してみます。

    次のサンプルは  http://[HOST_ADDRESS]:8080/hello/[YOUR_NAME] にアクセスすると、”Hello, [YOUR_NAME]!” と表示されます。

    # -*- coding: utf-8 -*-
    
    from bottle import template, route, run
    
    @route('/hello/<str>')
    def hello(str):
        return template('<p>Hello, {{name}}!</p>', name = str)
    
    run(host = '0.0.0.0', port = 8080)
    Hello, John!
    Hello, John!

    @route(‘/hello/<str>’) の <str> がワイルドカードとして扱われ、引数のように値を使うことができます。これによって URI 自体が意味を成すため、リンクを張ったときに内容を把握しやくなります。

    ここで [YOUR_NAME] を指定しなかったらどうなるでしょうか?試しにアクセスしてみると Error: 404 Not Found と返ってきます。

    404 Not Found
    404 Not Found

    ここで次のようにデフォルト引数を与えてあげると、/hello/ でアクセスするとデフォルト引数が使われて “Hello, Guest!” と表示されます。

    # -*- coding: utf-8 -*-
    
    from bottle import template, route, run
    
    @route('/hello/')
    @route('/hello/<str>')
    def hello(str = 'Guest'):
        return template('<p>Hello, {{name}}!</p>', name = str)
    
    run(host = '0.0.0.0', port = 8080)
    Hello, Guest!
    Hello, Guest!

    ここで注意する必要があるのは、”/hello/” と “/hello”  をきちんと区別するということです。このサンプルで http://[HOST_ADDRESS]:8080/hello にアクセスしても 404 Not Found となってしまいます。普段から意識していれば問題ありませんが、ディレクトリであっても末尾の ‘/’ を省略する癖がある人は気を付けた方が良いでしょう。

    @route() で使うワイルドカードは、値をフィルターで限定することができます。次の例では int は整数に限定され、float は小数を受け付け、path は ‘/’ を含むパス文字列、re は ‘:’ に続く正規表現に一致する文字列を受け付けます。

    @route('/item/<id:int>')
    @route('/release/<ver:float>')
    @route('/static/<path:path>')
    @route('/view/<name:re:[a-z]+>')

    GET と POST

    Bottle が用意しているデコレーターは @route() の他にも @get(), @post(), @put(), @delete(), @patch(), @error() があります。HTML でよく使われる GET と POST に絞って使ってみることにします。

    次のサンプルは簡易的なログイン機能を設けたものです。

    # -*- coding: utf-8 -*-
    
    from bottle import get, post, request, run
    
    @get('/login')
    def login_form():
        return '''<form action="/login" method="post">
            <p><label>ID:</label> <input type="text" name="id" />
            <label>Password:</label> <input type="password" name="password" />
            <input type="submit" value="Login" /></p>
            </form>'''
    
    @post('/login')
    def login():
        id = request.forms.get('id')
        pw = request.forms.get('password')
        
        if id == 'foo' and pw == 'bar':
            return '<p>You have successfully logged in.</p>'
        else:
            return '<p>Login failed.</p>'
    
    run(host = '0.0.0.0', port = 8080)

    ここで注目していただきたいのは、同じ /login にアクセスしているのに GET と POST では異なる処理が行われているということです。デコレータをうまく使うことによって、簡潔な URI を実現できることがおわかりいただけると思います。

    静的ファイルの扱い

    Bottle アプリケーションでは、画像や CSS, JavaScript といった静的なファイルを扱う場合にも自分でルーティングする必要があります。公式のチュートリアルを参考に、/static/ 以下へのアクセスは /home/user/static/ 以下のファイルを返すルーティングを実装したのが次になります。

    # -*- coding: utf-8 -*-
    
    from bottle import static_file, route, run
    
    @route('/static/<filepath:path>')
    def static(filepath):
        return static_file(filepath, '/home/user/static')
    
    run(host = '0.0.0.0', port = 8080)

    static_file() の引数は static_file(filename, root, …) となっていて、root を基準とした filename の内容を返す実装になっています。実装を見てみると root は関数内部で絶対パスへ変換されている(root = os.path.join(os.path.abspath(root), ”))ので相対パスも指定できますが、実行時のディレクトリ(current directory, working directory)に依存するので注意する必要があります。

    static_file() の引数に download = True を与えると、そのファイルを Content-Disposition: attachment として出力するので、ダウンロード扱いにできます。このときのファイル名は元ファイルのファイル名となります。download = ‘filename.ext’ とすればダウンロード時のファイル名を指定できます。

    エラーページのルーティング(404 Not Found 等)

    @error(404) のように、ステータスコードを含んだデコレータを関数の前に置くだけです。

    # -*- coding: utf-8 -*-
    
    from bottle import error, run
    
    @error(404)
    def error_404(e):
        return '<h1>404 Not Found</h1><p>{}</p>'.format(e)
    
    run(host = '0.0.0.0', port = 8080)
    errorデコレータ
    errorデコレータ

    これでエラー時に任意のページを表示させることができます。

    逆にエラーページに飛ばしたい場合は、import abort した上で abort(401, ‘Authorization required’) のように呼び出します。

    # -*- coding: utf-8 -*-
    
    from bottle import abort, route, run
    
    @route('/admin')
    def admin():
        abort(401, 'Authorization required.')
    
    run(host = '0.0.0.0', port = 8080)

     

    abort
    abort

    クライアントからのデータを処理する

    Bottle で扱えるクライアントからのデータには、以下のようなものがあります。それぞれ簡単な例を挙げます。

    request.headers

    HTTP ヘッダを辞書形式で取得できます。下記は headers 内の全ての値を出力します。

    # -*- coding: utf-8 -*-
    
    from bottle import route, run, request
    
    @route('/req/headers')
    def req_headers():
        ret = '<dl>'
        for k in request.headers:
            ret += '<dt>{}</dt><dd>{}</dd>'.format(k, request.headers.get(k))
        ret += '</dl>'
        return ret
    
    run(host = '0.0.0.0', port = 8080)
    request.headers
    request.headers

    余談ですが、Edge のUser-Agentってこんなに色々な名前が入っているんですね。KHTML から拡張されてきた歴史は知っていましたが、Chrome と Safari だけでなく AppleWebKit の名前まで入っていて、どれが実体なのかぱっと見わけがわかりません。ついでなのでちょっと調べてみると、同じこと思っている方は他にもいるみたいです。

    ということでモバイル版はもっとひどいようで、Android は “EdgA” であり、iOSは “EdgiOS” らしいです。Edge ですらなくなってます。

    request.cookies

    全ての Cookie を辞書で返します。Cookie の名前がわかっているなら request.get_cookie(‘cookie_name’) で直接取得できます。

    # -*- coding: utf-8 -*-
    
    from bottle import route, run, request, response
    
    @route('/req/set-cookie')
    def set_cookie():
        response.set_cookie('foo', 'bar')
        response.set_cookie('key', 'value')
        return '<p>Set some cookies.</p>'
    
    @route('/req/cookies')
    def req_cookies():
        ret = '<dl>'
        for k in request.cookies:
            ret += '<dt>{}</dt><dd>{}</dd>'.format(k, request.cookies.getunicode(k))
            ret += '</dl>'
        return ret
    
    run(host = '0.0.0.0', port = 8080)
    request.set_cookie
    request.set_cookie
    request.cookies
    request.cookies

    request.query

    QUERY_STRING と呼ばれる、URL の ? 以下の部分を扱います。request.query_string は ? 以下を一つの文字列として返します。request.query は key=value 形式の辞書を返します。

    # -*- coding: utf-8 -*-
    
    from bottle import route, post, run, request, response
    
    @route('/req/query')
    def req_query():
        ret = '<p>{}</p>'.format(request.query_string)
        ret += '<dl>'
        for k in request.query:
            ret += '<dt>{}</dt><dd>{}</dd>'.format(k, request.query.getunicode(k))
            ret += '</dl>'
        return ret
    
    run(host = '0.0.0.0', port = 8080)
    request.query
    request.query

    公式チュートリアルでは id=foo&page=5 のような QUERY_STRING が与えられた場合に、次のように値を取得するサンプルも掲載されています。

    id = request.query.id
    page = request.query.page

    request.forms

    一般的な method=”POST” な form の値は、request.forms で取得します。これも name=value 形式の辞書になっています。name が予めわかっていれば request.forms.get(‘name’) で取得できます。

    # -*- coding: utf-8 -*-
    
    from bottle import route, post, run, request
    
    @route('/req/forms')
    def post_form():
        ret = '''<form action="" method="post">
        <p>ID: <input name="id" type="text" /> Password: <input name="pw" type="password" /> <input type="submit" value="送信" /></p>
        </form>'''
        return ret
    
    @post('/req/forms')
    def req_forms():
        ret = '<dl>'
        for k in request.forms:
            ret += '<dt>{}</dt><dd>{}</dd>'.format(k, request.forms.getunicode(k))
            ret += '</dl>'
        return ret
    
    run(host = '0.0.0.0', port = 8080)
    request.forms
    request.forms

    request.files

    ファイルの受信も簡単に行えます。request.files は input の name を key とし、送られたファイルを持つ辞書になっています。name がわかっていれば直接 get() でファイルの内容を取得できます。

    # -*- coding: utf-8 -*-
    
    from bottle import route, post, run, request
    
    @route('/req/files')
    def file_form():
        ret = '''<form action="" method="post" enctype="multipart/form-data">
        <p>File1: <input name="file1" type="file" /> File2: <input name="file2" type="file" /> <input type="submit" value="Send" /></p>
        </form>'''
        return ret
    
    @post('/req/files')
    def file_form():
        ret = '<dl>'
        for k in request.files:
            f = request.files.get(k)
            ret += '<dt>{}</dt><dd>{}({})</dd>'.format(k, f.filename, f.content_type)
            f.save('/home/user/files')
        ret += '</dl>'
        return ret
    
    run(host = '0.0.0.0', port = 8080)
    request.files 選択前
    request.files 選択前
    request.files 送信後
    request.files 送信後

    get() で取得できるのは class FileUpload のオブジェクトです。この FileUpload について簡単に書きます。以下、file は class FileUpload のオブジェクトとします。

    file.raw_filename で元のファイル名を取得できますが、”may contain unsafe characters” とリファレンスにあるように、ユニコードの特殊文字の扱いを考えなければセキュリティ上の問題になる可能性があります。代りに file.filename で取得できるファイル名は安全なものに処理されているので、そのまま保存に使っても問題ありません。ただし、”ASCII letters, digits, dashes, underscores and dots” の範囲内でしか処理されないため、日本語等は全て省略されてしまいます。元のファイル名は何らかの別の形で残すように処理した方が良いでしょう。

    file.save(destination = ‘/path/to/dir/’) とすると、ディレクトリ destination に file.filename という名前で保存されます。事前に destination に書込権限があることを確認してください。

    送信側の form に enctype=”multipart/form-data” を入れ忘れないように注意しましょう。今まで CGI でファイルを扱ったことがなかったのでこれに気付かず、しばらくハマりました。

     テンプレート(SimpleTemplate Engine)

    テンプレートエンジンについて簡単に説明しておくと、予め雛型となるファイルを用意しておいて、変える必要がある部分のみを書き換えてくれるというものです。

    例えば HTML であれば、DOCTYPE から始まるヘッダに相当する部分はどのページでもさほど違いはありません。title 要素の値が違うくらいでしょうか。これらを毎回出力するのは非効率的ですし、1 つの変更を適用させるためにすべての出力を書き換える必要ができます。CSS が普及するまで HTML に直接デザインを書き入れてたのと同じくらい非効率です。

    Bottle には SimpleTemplate Engine と呼ばれるテンプレートエンジンが用意されています。その名の通りとてもシンプルなエンジンですが、テンプレート内に Python コードを埋め込むことができるため、簡素ながらも柔軟性があります。PHP と似たようなもの、といったら経験者にはわかりやすいでしょうか。

    最も簡単な例として、次のようなコードを用意しました。

    # -*- coding: utf-8 -*-
    
    from bottle import template, route, run
    
    @route('/<dir>/<name>')
    def hello(dir, name):
        return template('<p>Dir: {{DIR}}<br />Name: {{NAME}}</p>', DIR = dir, NAME = name)
    
    run(host = '0.0.0.0', port = 8080)
    template
    template

    {{ と }} で囲まれた部分が置き換え対象となり、第 2 引数以降で与えられる値に置き換えられます。第 2 引数を辞書にすることも可能で、上の例であれば {‘DIR’ : dir, ‘NAME’ : name} を第 2 引数にします。

    インライン式(Inline expressions)

    {{ と }} で囲まれた式を変数のように扱いましたが、Python コードそのものを書くこともできます。次は bool 値が True であれば “TRUE” と、False であれば “FALSE” と表示するコード埋め込んだものです。

    # -*- coding: utf-8 -*-
    
    from bottle import template, route, run
    
    @route('/flag')
    def home():
        return template('<p>{{"TRUE" if flag else "FALSE"}}</p>', flag = True)
    
    run(host = '0.0.0.0', port = 8080)
    template: inline expression
    template: inline expression

    変数の値を埋め込むとき、XSS 攻撃防止のために HTML の特別文字は全てエスケープされます。

    埋め込みコード(Embedded Python code)

    ‘%’ から始まる行は Python コードとして処理されます。’%’ の前後に空白があっても構いません。また、Python の構文であるインデントも必要ありません。インデントで表していたブロックの範囲は、”% end”と明示するまで続きます。

    次のコードは、埋め込みコードを使って 0 から 9 の数字をリストアイテムとして出力します。

    # -*- coding: utf-8 -*-
    
    from bottle import template, route, run
    
    @route('/loop')
    def loop():
        t = '''<ul>
        % for count in range(10):
            <li>{{count}}</li>
        % end
        </ul>'''
        return template(t)
    
    run(host = '0.0.0.0', port = 8080)
    template: 埋め込みコード
    template: 埋め込みコード

    テンプレートをファイルから読み込む

    ここまでは、把握しやすいようにソースコードの中に直接 HTML を書いてテンプレートを記述していましたが、実際は別ファイルとして用意しないとテンプレートの意味がなくなってしまいます。

    例として、ページタイトルと本文だけを埋め込めるようにしたテンプレートを作って表示してみます。

    Bottle を実行しているディレクトリの下に views ディレクトリを作成し、次のソースを html.tpl として保存します。

    <!DOCTYPE html>
    <head>
    <title>{{TITLE}}</title>
    </head>
    <body>
    {{BODY}}
    </body>
    </html>

    そして次の Python コードを実行します。

    # -*- coding: utf-8 -*-
    
    from bottle import template, route, run
    
    @route('/')
    def home():
        return template('html.tpl', TITLE = 'Bottle Sample', BODY = '<p>Testing template engine.</p>')
    
    run(host = '0.0.0.0', port = 8080)
    template 別ファイル
    template 別ファイル

    おや、HTML のタグがそのまま表示されてしまいましたね。先程述べたように、XSS 攻撃を防ぐために HTML の特別文字は全てエスケープされます。エスケープを行いたくないときは、{{ と }} の中の変数名に ‘!’ を付けます。

    <!DOCTYPE html>
    <head>
    <title>{{TITLE}}</title>
    </head>
    <body>
    {{!BODY}}
    </body>
    </html>
    template HTML エスケープ無効化
    template HTML エスケープ無効化

    エスケープを無効にする際には、XSS に十分に注意してください。できるだけ HTML はテンプレート側に記述してしまい、値だけを流し込むのが理想だと思います。

    @view デコレータ

    先のコードでは template() で直接テンプレートを呼び出していましたが、これをデコレータで指定することもできます。

    # -*- coding: utf-8 -*-
    
    from bottle import template, view, route, run
    
    @route('/')
    @view('html.tpl')
    def home():
        return dict(TITLE = 'Bottle Sample', BODY = '<p>Testing template engine.</p>')
    
    run(host = '0.0.0.0', port = 8080)
  • Python のバージョン管理、pyenv のインストールと使い方

    今まで Python 2.xで開発を行っていましたが、Python 3.x に移行する必要があって 2.x と 3.x が混在する環境をどう構築するか悩んでいました。pyenv や virtualenv, pyvenv という便利なものがあるようですが、それぞれ役割が違うようです。

    今回はバージョン管理だけを行いたかったので、pyenv に絞った内容です。

    簡単な説明

    pyenv  が主に行うのはユーザー環境下での Python のバージョン管理です。ユーザー環境下であってシステム全体ではないところが重要で、システムを利用する他のユーザに影響を与えません。

    以下は提供サイト(https://github.com/pyenv/pyenv)にある説明です。

    pyenv does…

    • Let you change the global Python version on a per-user basis.
    • Provide support for per-project Python versions.
    • Allow you to override the Python version with an environment variable.
    • Search commands from multiple versions of Python at a time. This may be helpful to test across Python versions with tox.

    特定のディレクトリで使う Python のバージョン(per-project Python versions)、それ以外のディレクトリで使う Python のバージョン(global Python version) を簡単に切り替えられるのが特徴です。仕組としてはシェルの PATH の探索順を利用しているようです。

    インストール

    環境は Debian (Raspbian) 8.0 を想定しています。

    インストールといってもシェルスクリプトなので、git からコピーして環境変数を設定するだけです。

    # pyenv installのとき必要になるパッケージのインストール
    $ sudo apt update
    $ sudo apt install build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils
    
    # gitのインストール(gitが入っていたら不要)
    $ sudo apt install git
    
    # pyenvのインストール
    $ git clone https://github.com/yyuu/pyenv.git ~/.pyenv
    
    # 環境変数の設定
    $ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
    $ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
    $ echo 'eval "$(pyenv init -)"' >> ~/.bashrc
    $ source ~/.bashrc
    
    # 確認
    $ pyenv -v
    pyenv 1.2.4-2-gdad0fc7

    簡単な使い方

    まず最初にインストールされているバージョンを確認します。

    $ pyenv versions
    * system (set by /home/pi/.pyenv/version)
    $ python -V
    Python 2.7.9

    何もインストールしてないので、システムに標準で入っている Python のみ状態です。この状態から pyenv 環境に使いたいバージョンの Python を追加していきます。

    インストールできる Python のバージョンは “pyenv install -l”  または “pyenv install –list”で確認できます。

    $ pyenv install -l
    Available versions:
     2.1.3
    ...
    ...(省略)...
    ...
     stackless-3.5.4

    この中から、例えば Python 3.6.5 をインストールするなら次のようにするだけです。ビルドを伴うのでそれなりに時間がかかります。

     $ pyenv install 3.6.5
    Downloading Python-3.6.5.tar.xz...
    -> https://www.python.org/ftp/python/3.6.5/Python-3.6.5.tar.xz
    Installing Python-3.6.5...
    Installed Python-3.6.5 to /home/pi/.pyenv/versions/3.6.5
    
    $ pyenv versions
    * system (set by /home/pi/.python-version)
     3.6.5

    インストールが完了したらバージョンを切り替えてみます。現在のディレクトリにだけ効力を持たせるには local を、全てのディレクトリで効力を持たせるには global を指定します。

    $ pyenv local 3.6.5
    $ python -V
    Python 3.6.5

    local の挙動

    local は指定したディレクトリ以下(子ディレクトリも含む)で効果を持ちます。そのディレクトリだけでなく、子ディレクトリにも影響が及ぶことに注意します。

    $ python -V
    Python 2.7.9
    $ pyenv local 3.6.5
    $ python -V
    Python 3.6.5
    $ cd ..
    $ python -V
    Python 2.7.9
    $ cd ./user/dir/
    $ python -V
    Python 3.6.5

    global の挙動

    global はそのユーザがログインしているとき、全てのディレクトリで効果を持ちます。どのディレクトリに移動しても、global で指定されたバージョンが実行されます。このとき、local でバージョンが指定されているディレクトリは local で指定したものが優先されます。

    $ python -V
    Python 2.7.9
    $ pyenv local 3.6.5
    $ python -V
    Python 3.6.5
    $ cd ..
    $ python -V
    Python 3.6.5
    $ cd ./user/dir/
    $ python -V
    Python 3.6.5

    Python の仮想環境

    Python のバージョン管理を行う pyenv の説明は以上です。2.x と 3.x の共存や切替はこれで簡単に行えますが、環境が複数になると次はプロジェクト毎にモジュールの管理を行う必要が出てきます。

    そこで冒頭に挙げた virtualenv や venv といったものが登場します。これらはまた別の記事にまとめます。

  • ムームードメインからさくらインターネットへの.comドメイン移管

    ムームードメインからさくらインターネットへの.comドメイン移管

    GMO のムームードメインから、さくらインターネットへ.comドメインの移管を行ったときの記録です。

    ムームードメインで「認証コード(AuthCode)」を取得する

    ムームードメインのコントロールパネルにログイン後、左メニューの「ドメイン管理」 – 「ドメイン操作」 – 「ドメイン一覧」と辿ります。

    ムームードメイン 認証コード

    移管するドメイン名を開きます。ページ中ほどに「認証コード(AuthCode)」があるので控えておきます。

    ムームードメイン 認証コード

    さくらインターネットで転入を申し込む

    • 【gTLDドメイン】転入マニュアル – さくらのサポート情報

    さくらインターネットの「お申し込み」から「ドメイン管理」 – 「gTLDドメインの移管(転入)」 – 「オンラインサインアップ」を開きます。

    さくらインターネット 転入

    ログイン後、移管するドメイン(*.com)を入力します。約款に同意したら「次へ」進みます。

    さくらインターネット 転入

    先ほど控えた認証コード(AuthCode)を入力し、Whois に登録された登録者名を入力します。

    さくらインターネット 転入

    支払情報を確認し、必要事項を埋めます。今回は既にクレジットカード決済にしてあるため、カードのセキュリティコードを入力するのみでした。

    「最終のご確認」で内容を確認して、申込完了です。

    さくらインターネット 転入

     JPRS から承認メールが届くので承認する

    「[JPRS] レジストラトランスファー承認手続きのご案内」という表題のメールが届くので、メール内に書かれた URL を開いて「承認」すれば後は待つだけです。

  • jQuery の autocomplete で任意のタイミングで候補を探索

    <input type="text" name="title" id="title">

    とあったとして

    $("#title").autocomplete("search");

    で好きなタイミングで候補を表示できる。

  • HTML の文字参照(数値文字参照、文字実体参照)

    もう 20 年以上も使っているのに憶えられないのでメモ。

    数値文字参照

    10 進数表記
    $#1234;
    16 進数表記
    $#xABCD;

    文字実体参照

    &amp; &lt; &gt; といった名前での参照。