端っこプログラマーの手帳

主にプログラムに関する手記です

JsRender for文内で親要素にアクセス

JsRender にて for文内で親要素へのアクセスが分からず調べてみた。

問題になった状況

ファイル構成

├ jquery-1.9.1.js 
├ jsrender.min.js(JsRender v1.0.0-beta)
└ index.html

index.html のコード

<!DOCTYPE html>
<html>
    <head>
        <title></title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
        <script type="text/javascript" src="./jquery-1.9.1.js"></script>
        <script type="text/javascript" src="./jsrender.min.js"></script>
    </head>
    <body>
        <div class="container">
            <div>映画視聴記録</div>
            <script id="template" type="text/x-jsrender">
                {{for movies}}
                <tr>
                   <td>{{>title}}</td>
                   <td>
                       {{for ~statuslist}}
                       <p><input type="radio" name="status{{>no}}" value="{{>status_cd}}">{{>status_name}}</p>
                       {{/for}}
                   </td>
                </tr>
                {{/for}}
            </script>
            <table border='1'>
                <thead>
                <tr>
                <th>作品名</th>
                <th>ステイタス</th>
                </tr>
                </thead>
                <tbody id="movies"></tbody>
            </table>
        </div>
        <script type="text/javascript">
           
            var output = $('#template').render(
               {
                   movies: [
                       {no: 1, title: 'いかレスラー', status_cd:'not'},
                       {no: 2, title: 'コアラ課長',   status_cd:'not'},
                       {no: 3, title: 'かにゴールキーパー', status_cd:'done'},
                   ],
               },
               {
                   statuslist:[
                       {status_cd: 'not', status_name: '未鑑賞'},
                       {status_cd: 'done', status_name: '鑑賞済み'},
                   ]
               }
            );
            $('#movies').append(output);
        </script>
    </body>
</html>

次の2点の理由から、 {{for ~statuslist}}〜{{/for}} の中で、親要素の movies にアクセスする必要があります。*1

・デフォルト値の取得(movies.status_cd)
・一行毎に input の name属性値を変更(movies.no)*2

最終的なイメージはこんな感じ。

f:id:kzhishu:20170603220754p:plain

親要素にアクセスする

ドキュメントに書いてありました。

JsRender/JsViews

「Accessing parent data」とドンピシャの内容です。 「There are several ways to get to parent data:」とあり、方法はいくつかあります。

グローバル領域にセット(Create a contextual parameter)

for文で、~[key] に値をセットすることで、ループ内からアクセスできる。

<script id="template" type="text/x-jsrender">
    {{for movies}}
    <tr>
       <td>{{>title}}</td>
       <td>
           {{for ~statuslist ~no=no ~status_cd=status_cd}}
           <p><input type="radio" name="status{{>~no}}" value="{{>status_cd}}"{{if ~status_cd == status_cd}} checked{{/if}}>{{>status_name}}</p>
           {{/for}}
       </td>
    </tr>
    {{/for}}
</script>

親要素を1つまるごとセット(itemVar)

親要素の for文で、itemVar に ~[key] をセットすることで、配下の要素でアクセスできるようになる。まるごと指定できるため、参照する値が多い時は便利。

<script id="template" type="text/x-jsrender">
    {{for movies itemVar="~movie"}}
    <tr>
       <td>{{>title}}</td>
       <td>
           {{for ~statuslist}}
           <p><input type="radio" name="status{{>~movie.no}}" value="{{>status_cd}}"{{if ~movie.status_cd == status_cd}} checked{{/if}}>{{>status_name}}</p>
           {{/for}}
       </td>
    </tr>
    {{/for}}
</script>

親要素のプロパティにアクセス(view.parent property)

for文でのセットは不要で、直接プロパティにアクセス。書き方は冗長だが一番シンプル。*3

<script id="template" type="text/x-jsrender">
    {{for movies}}
    <tr>
       <td>{{>title}}</td>
       <td>
           {{for ~statuslist}}
           <p><input type="radio" name="status{{>#parent.parent.data.no}}" value="{{>status_cd}}"{{if #parent.parent.data.status_cd == status_cd}} checked{{/if}}>{{>status_name}}</p>
           {{/for}}
       </td>
    </tr>
    {{/for}}
</script>

*1:正確には、movies のループ中の1要素です。

*2:すべて同じラジオボタンのグループとして扱われてしまうため。no は書いてあるがアクセスはできていないです。

*3:ちなみに「Use the view.get(type) method」という方法を使うと、#parent.get("item").data でアクセスできる。

Python3でMySQLを操作する

Pythonを書き始めて結構経つのですが、最近初めてDBを使いたくなり、はて?どうすればよいのやら…と調べたあれこれを書き残しておきます。PythonでDBを操るには、標準モジュールでは事足りず、各DB向けの追加モジュールが必要です。

実行環境

Python 3.5.2
Mysql 5.7.17

PyMySQLのインストー

MySQL-python」を使おうと思ったのですが、自分の環境ではエラーとなりうまくインストールできませんでした。 Python3なら「PyMySQL」がサクッと接続できていいよ との情報を目にしてこちらにしてみました。

$ sudo pip3 install PyMySQL

$ python3
> import pymysql.cursors

エラーがでないので無事インストール完了です。

テーブル・データ

testというDBに作成しました。

CREATE TABLE `post` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `content` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `post` (`id`, `content`) VALUES
(1, 'おはようございます。いい天気ですね!!'),
(2, '太郎さん、新聞が届いていますよ。'),
(3, '天気がいいのは良いですが、ちと暑いですね'),
(4, '今日もお仕事ご苦労さまです');

Pythonコード

GitHubのサンプルコードを参考に、PythonMySQLを操作するコードを書いていきます。

PyMySQL/PyMySQL: Pure Python MySQL Client

#! /usr/bin/python3
# -*- coding: utf-8 -*-
 
import pymysql.cursors
 
connection = pymysql.connect(host='localhost',
                             user='root',
                             password='******',
                             db='test',
                             charset='utf8',
                             # Selectの結果をdictionary形式で受け取る
                             cursorclass=pymysql.cursors.DictCursor)
try:
    with connection.cursor() as cursor:
        sql = "SELECT * FROM post"
        cursor.execute(sql)
 
        dbdata = cursor.fetchall()
        for rows in dbdata:
            print(rows)
 
finally:
    connection.close()

実行結果

{'content': 'おはようございます。いい天気ですね!!', 'id': 1}
{'content': '太郎さん、新聞が届いていますよ。', 'id': 2}
{'content': '天気がいいのは良いですが、ちと暑いですね', 'id': 3}
{'content': '今日もお仕事ご苦労さまです', 'id': 4}

気になった点の調査

実行したSQL文を知りたい

意図しない動作となったとき早期解決できるように実行したSQL文が見たいです。上のテストコードように単純なら良いのですが、動的に値を変えるとなると頭の中だけでは追いづらいです。
先ほどのGitHubのページから、executeの実処理である、cursors.py の executeメソッドを追ってみました。すると、self.executed に実行したSQL文を保持していました。このプロパティにアクセスすれば実行SQL文が取得できそうです。
先ほどのコードのSQL文にWHERE句を追加して、idを置換するように変更します。実行後に self.
executed を出力するとid置換後のSQL文が取得できました。

    with connection.cursor() as cursor:
        sql = "SELECT * FROM post WHERE id = %s"
        cursor.execute(sql, (1))
        print(cursor._executed)

        dbdata = cursor.fetchall()
        for rows in dbdata:
            print(rows)
SELECT * FROM post WHERE id = 1
{'content': 'おはようございます。いい天気ですね!!', 'id': 1}

特殊文字エスケープがされるか

文字置換するときに、SQLインジェクション対策がなされるかを調べました。

    with connection.cursor() as cursor:
        sql = "SELECT * FROM post WHERE id = %s"
        cursor.execute(sql, ("';DELETE FROM post;'"))
        print(cursor._executed)

        dbdata = cursor.fetchall()
        for rows in dbdata:
            print(rows)
SELECT * FROM post WHERE id = '\';DELETE FROM post;\''

適切にエスケープされました。
DELETE文は文字列として処理され、\‘;DELETE FROM post;\’ という id は存在しないので何も取得されません。

置換文字を名前ベースで指定

GitHubのサンプルコードでは、置換文字列をタプルで渡しています。 このままだと、置換する件数が後から増えたとき前から順番を合わせなければならず大変です。 名前ベースでアクセスをしたいもの。
こちらも、cursors.py を追っていくと mogrifyメソッドで 渡した値を % 演算子で置換していました。 最終的には、次の形で実行しています。タプル部分が、executeの第2引数です。

'SELECT * FROM post WHERE id = %s' % (1)

Pythonの文法的に次のように変えても問題ないはずです。

'SELECT * FROM post WHERE id = %(id)s' % {'id':1}

SQL文とexecuteの引数を変えてみます。

    with connection.cursor() as cursor:
        sql = "SELECT * FROM post WHERE id = %(id)s"
        cursor.execute(sql, {'id':1})
        print(cursor._executed)

        dbdata = cursor.fetchall()
        for rows in dbdata:
            print(rows)
SELECT * FROM post WHERE id = 1
{'content': 'おはようございます。いい天気ですね!!', 'id': 1}

問題ないですね!!!
ふぅ、実際にコードを追ってみるって大切ですね。

使いやすくしてみる

今までの内容をまとめて、もう少し使いやすくするため、クラス化してみました。

Python3 で、MySQL にアクセスする

Webページでシンタックスハイライト

「この関数以前使ったことあるけど、どんな点に注意すれば良いんだっけ?」

使ったことは覚えているのに詳細は忘れてしまった。プログラムを書いているとよく起こります。 こんなとき自分の過去のコードをすぐに振り返れるようにWeb上で検索できるシステムを作ってみました。

Web上でコードをそのまま表示しても味気なかったため、テキストエディタのようにシンタックスハイライトで見やすくしてみました。 今回は、Web上でシンタックスハイライトを導入する方法を紹介します。

目次

実現したいこと

もう少し具体的に実現したいことを整理しました。

・htmlとPHPが対象となること
・行数を表示したい

検討したJSライブラリ

シンタックスハイライトを調べてみるとJavaScriptのライブラリが多数出てきました。 その中で3つをピックアップして試してみます。

highlight.js

ライブラリは公式サイトから入手できます。

highlight.js
│  highlight.pack.js … メイン処理
│
└─styles            … カラーリング用CSSファイル
       agate.css
       androidstudio.css
       arduino-light.css
       …
ライブラリ読み込み・初期化

カラーリングは、CSSを読み込むことによって指定します。 豊富に用意されているため、公式サイトで表示を確認*1し、好みのものを選択しましょう。 今回は、atom-one-dark を読み込んでみます。あとはJSライブラリを読み込んで初期化します。

<head>
<link rel="stylesheet" href="styles/atom-one-dark.css">
<script type="text/javascript" src="highlight.pack.js"></script>
<script type="text/javascript">
   hljs.initHighlightingOnLoad();
</script>
</head>
ハイライトするコードの設置

codeタグのclass属性で、言語を指定します。*2 コード部分の先頭の改行は、ハイライト後も反映されてしまうのでない方がいいでしょう。 <(小なり)と>(大なり)は、htmlとして解釈されないようにエスケープが必要です。*3

html

<body>
<pre>
<code class="html"><!-- ハイライトしたいコード start--><?php 
$code = <<<EOL
<h4>プルダウン</h4>
<select id="select" name="select">
    <option value="0">丹下段平</option>
    <option value="1" selected>矢吹丈</option>
    <option value="2">マンモス西</option>
    <option value="3">力石徹</option>
    <option value="4">カーロス・リベラ</option>
</select>
EOL;
echo htmlspecialchars($code, ENT_QUOTES, 'UTF-8');
?><!-- ハイライトしたいコード end-->
</code>
</pre>
</body>

php

<body>
<pre>
<code class="php"><!-- コード部分 start-->&lt;?php 
define('ARCHIVE_KEEP_NUM', 5);
$archive_dir = $argv[1];
$list_archive = array();

foreach(glob($archive_dir.'*.tar.gz') as $archivename){
    $created = filemtime($archivename);
    $list_archive[$created] = $archivename;
}<!-- コード部分 end-->
</code>
</pre>
</body>
表示キャプチャ

html
f:id:kzhishu:20170408023814p:plain

php
f:id:kzhishu:20170408023835p:plain

シンプルで悪くないのですが「行数を出せない」「PHPのカラーリングが弱い」という2点で採用は見送りました。

prism.js

ファイルダウンロード

公式サイトから、カラーリング(Themes)・言語(Languages)・プラグイン(Plugins)を指定して 次の2ファイルをダウンロードします。 カラーリング変更やプラグイン追加をするには再度ダウンロードする必要があるようです。「これは使いたいかも」と思ったものは入れておきます。

prism.css
prism.js
① カラーリング(Themes)

公式サイトTOPの Examplesで表示を確認できます。 [DOWNLOAD] 下の[THEME:]を切り替えて試してみましょう。 柔らかな感じで見やすい Coy に決めました。

② 言語(Languages)

知っている言語は全て選択しました。

プラグイン(Plugins)

色々とあるのですが、次の3つを選択しました。

Line Numbers(行番号表示)
Copy to Clipboard (クリップボードにコピー)
Show Language (言語名の表示)
ライブラリ読み込み

単純に読み込むだけで問題ありません。

<head>
    <link rel="stylesheet" href="prism.css">
    <script type="text/javascript" src="prism.js"></script>
</head>
ハイライトしたいコードの設置

<pre><code></code></pre> でハイライトしたいコードを囲う点は、highlight.js と同じです。 言語の指定は、codeタグに、language-[言語コード] のクラス名を付与します。*4 [言語コード]は、TOPページ Supported languages のところに載っています。 preタグの line-numbersクラスは、「Line Numberプラグイン」の機能で行数の表示 codeタグの data-languageクラスは、「Show Languageプラグイン」の言語名タグが表示されます。

html

<body>
<pre class="line-numbers">
<code class="language-html" data-language="html"><!--ハイライトしたいコード start--><?php 
$code = <<<EOL
<h4>プルダウン</h4>
<select id="select" name="select">
    <option value="0">丹下段平</option>
    <option value="1" selected>矢吹丈</option>
    <option value="2">マンモス西</option>
    <option value="3">力石徹</option>
    <option value="4">カーロス・リベラ</option>
</select>
EOL;
echo htmlspecialchars($code, ENT_QUOTES, 'UTF-8');
?><!--ハイライトしたいコード end--></code>
</pre>
</body>

php

<body>
<pre class="line-numbers">
<code class="language-php" data-language="php">&lt;?php 
define('ARCHIVE_KEEP_NUM', 5);
$archive_dir = $argv[1];
$list_archive = array();

foreach(glob($archive_dir.'*.tar.gz') as $archivename){
    $created = filemtime($archivename);
    $list_archive[$created] = $archivename;
}</code>
</pre>
</body>
表示キャプチャ

html
f:id:kzhishu:20170408023859p:plain

php
f:id:kzhishu:20170408023914p:plain

行番号も表示でき、色もきれいですが、次に紹介するJSライブラリの方が用途に合っていたためこちらも採用は見送りました。

google-code-prettify

オートローダー版

オートローダーを読み込み、preタグ内にコードを設置すれば簡単にハイライトを導入できます。

<!DOCTYPE html>
<html>
    <head>
       <title></title>
       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
       <script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
   </head>
    <body>
        <pre class="prettyprint linenums"><?php 
$code = <<<EOL
<h4>プルダウン</h4>
<select id="select" name="select">
    <option value="0">丹下段平</option>
    <option value="1" selected>矢吹丈</option>
    <option value="2">マンモス西</option>
    <option value="3">力石徹</option>
    <option value="4">カーロス・リベラ</option>
</select>
EOL;
echo htmlspecialchars($code, ENT_QUOTES, 'UTF-8');
?>
    </pre>
    </body>
</html>

表示結果
f:id:kzhishu:20170411000148p:plain

… 5行ずつでしか行番号が出ないため、GitHubからコードを入手しカスタマイズしてみます。

GitHub - google/code-prettify: Automatically exported from code.google.com/p/google-code-prettify

ダウンロード

prettify.css と prettify.js を使います。 lang-XXX.js は、デフォルトで非対応の言語のときに読み込む必要がありますが、 今回ハイライトしたい言語には対応しているので使いません。 styles 配下は、カラーリングのテーマを変えることができるようです。 こちらもデフォルトが気にいったので使いません。

code-prettify-master
├─distrib
├─docs
├─examples
├─js-modules
├─loader
│  └─skins
├─src
│      lang-XXX.js    ○ 言語拡張スクリプト
│      prettify.css   ★ メインのCSSファイル
│      prettify.js    ★ メインのJSファイル
│      run_prettify.js
│
├─styles             ○ カラーリング別テーマ
│      desert.css
│      doxy.css
│      sons-of-obsidian.css
│      sunburst.css
├─tasks
│  └─lib
├─tests
└─tools
    └─closure-compiler
1行ずつ行番号を表示

prettify.css 55行目あたりで、5番目・10番目以外のリスト先頭部分を消しており、 以下の通りコメントアウトすることで行番号が表示されるようになります。

/* Specify class=linenums on a pre to get line numbering */
ol.linenums { margin-top: 0; margin-bottom: 0 } /* IE indents via margin-left */
/*li.L0,
li.L1,
li.L2,
li.L3,
li.L5,
li.L6,
li.L7,
li.L8 { list-style-type: none }*/
ハイライトするコードの設置

head内で、ダウンロード・カスタマイズした prettify.css を読み込みます

<head>
   <title></title>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
   <link rel="stylesheet" href="./prettify.css">
</head>

bodyの閉じタグの前で、prettify.js を読み込み、prettyPrint()でカラーリングを実行します。

<script type="text/javascript" src="./prettify.js"></script>
<script type="text/javascript">
   prettyPrint();
</script>

例のごとく、htmlエスケープしたコードをpreタグで囲います。 prettyprintクラスを付与するとカラーリング対象となります。 また、linenumsクラスで行数を表示します。

html

<pre class="prettyprint linenums"><?php 
$code = <<<EOL
<h4>プルダウン</h4>
<select id="select" name="select">
    <option value="0">丹下段平</option>
    <option value="1" selected>矢吹丈</option>
    <option value="2">マンモス西</option>
    <option value="3">力石徹</option>
    <option value="4">カーロス・リベラ</option>
</select>
EOL;
echo htmlspecialchars($code, ENT_QUOTES, 'UTF-8');
?>
</pre>

php

<pre class="prettyprint linenums">&lt;?php 
define('ARCHIVE_KEEP_NUM', 5);
$archive_dir = $argv[1];
$list_archive = array();

foreach(glob($archive_dir.'*.tar.gz') as $archivename){
    $created = filemtime($archivename);
    $list_archive[$created] = $archivename;
}
</pre>
表示キャプチャ

html
f:id:kzhishu:20170412002642p:plain

php
f:id:kzhishu:20170412002657p:plain

表示は、シャープで見やすいです。言語も自動で判別してくれるてGoodです。 これを採用することに決めました。

*1:style:[カラーリング名] の下線部をクリックするとカラーリングが切り替わります。ダウンロードしたファイルの style/[カラーリング名].css が該当のCSSとなります

*2:言語を指定しないと自動判別されるようです。htmlとphpに関しては各言語クラスを指定したときと同じ表示になりました

*3:同じページでhtmlエスケープを実行したり、html特殊文字でハードコーディングしてますが、実装時にはサーバーサイドでエスケープをかけるべきでしょう

*4:highlight.js と違いクラスを指定しなかったらハイライトされないです

【PHP】VirtualHostにリライト設定を書いたときの SCRIPT_NAME の値がおかしい

PHPのサーバ環境変数に、SCRIPT_NAME という値があります。
「現在の実行ファイルのパス」が入り、例えば、以下のようなリライト設定で、 RewriteRuleに引っかかるリクエストでは、「/index.php」が入ります。

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]

しかし設定の記述場所によっては、「/index.php」が入らないケースがあるようです。
リライトの設定場所って...なんか毎回ハマっている気がします。

リライト設定を記述できる場所は2箇所。

  • Directoryディレクティブ(=.htaccess
  • VirtualHost内

Directoryディレクティブまたは、.htaccess の場合は、どちらに書いても「/index.php」になります。 (以下は、Directoryディレクティブの場合。)
しかし、VirtualHost内では、なんと「/index.php」とはなりません。

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/html
    DirectoryIndex index.php index.html

    <Directory "/var/www/html">
        <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteRule ^(.*)$ index.php [QSA,L] 
        </IfModule>
    </Directory>
</VirtualHost>

VirtualHost内に書いた場合

httpd.conf の設定
<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/html
    DirectoryIndex index.php index.html

    <IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ /index.php [QSA,L] 
    </IfModule>
</VirtualHost>
ファイル構成

ドキュメントルート直下に、index.php がおいてある(/var/www/html/index.php

リクエストと結果

上記内容で、http://example.com/hoge/hige/ にアクセスします。
すると、SCRIPT_NAME の値は「/hoge/hige/」なります。 これは、REQUEST_URI と同じ値で、「/index.php」を期待しますがそうはならないのです。

何が違うのか?

VirtualHostに書いた場合とDirectoryディレクティブに書いた場合とで動作はどのように違うのでしょうか。リライトログを追ってみました。

■ VirtualHost リライトログ(※メッセージ部分のみ抽出)

[example.com/sid#7f1ac4223418][rid#7f1ac4433670/initial] init rewrite engine with requested uri /hoge/hige/
[example.com/sid#7f1ac4223418][rid#7f1ac4433670/initial] applying pattern '^(.*)$' to uri '/hoge/hige/'
[example.com/sid#7f1ac4223418][rid#7f1ac4433670/initial] RewriteCond: input='/hoge/hige/' pattern='!-f' => matched
[example.com/sid#7f1ac4223418][rid#7f1ac4433670/initial] rewrite '/hoge/hige/' -> '/index.php'
[example.com/sid#7f1ac4223418][rid#7f1ac4433670/initial] local path result: /index.php
[example.com/sid#7f1ac4223418][rid#7f1ac4433670/initial] prefixed with document_root to /var/www/html/index.php
[example.com/sid#7f1ac4223418][rid#7f1ac4433670/initial] go-ahead with /var/www/html/index.php [OK]

■ Directoryディレクティブ リライトログ(※メッセージ部分のみ抽出)

[example.com/sid#7fa563ae9418][rid#7fa563cf9670/initial] [perdir /var/www/html/] add path info postfix: /var/www/html/hoge -> /var/www/html/hoge/hige/
[example.com/sid#7fa563ae9418][rid#7fa563cf9670/initial] [perdir /var/www/html/] strip per-dir prefix: /var/www/html/hoge/hige/ -> hoge/hige/
[example.com/sid#7fa563ae9418][rid#7fa563cf9670/initial] [perdir /var/www/html/] applying pattern '^(.*)$' to uri 'hoge/hige/'
[example.com/sid#7fa563ae9418][rid#7fa563cf9670/initial] [perdir /var/www/html/] RewriteCond: input='/var/www/html/hoge' pattern='!-f' => matched
[example.com/sid#7fa563ae9418][rid#7fa563cf9670/initial] [perdir /var/www/html/] rewrite 'hoge/hige/' -> 'index.php'
[example.com/sid#7fa563ae9418][rid#7fa563cf9670/initial] [perdir /var/www/html/] add per-dir prefix: index.php -> /var/www/html/index.php
[example.com/sid#7fa563ae9418][rid#7fa563cf9670/initial] [perdir /var/www/html/] strip document_root prefix: /var/www/html/index.php -> /index.php
[example.com/sid#7fa563ae9418][rid#7fa563cf9670/initial] [perdir /var/www/html/] internal redirect with /index.php [INTERNAL REDIRECT]
[example.com/sid#7fa563ae9418][rid#7fa563ce44f0/initial/redir#1] [perdir /var/www/html/] strip per-dir prefix: /var/www/html/index.php -> index.php
[example.com/sid#7fa563ae9418][rid#7fa563ce44f0/initial/redir#1] [perdir /var/www/html/] applying pattern '^(.*)$' to uri 'index.php'
[example.com/sid#7fa563ae9418][rid#7fa563ce44f0/initial/redir#1] [perdir /var/www/html/] RewriteCond: input='/var/www/html/index.php' pattern='!-f' => not-matched
[example.com/sid#7fa563ae9418][rid#7fa563ce44f0/initial/redir#1] [perdir /var/www/html/] pass through /var/www/html/index.php

詳しく見ると、Directoryディレクティブ では、内部リダイレクトが発生して、2回リライト設定を通っていることが分かります。 リダイレクト前と後では、リクエストパスが変わります。

1回目 … /hoge/hige/
2回目 … /index.php

一方で、VirtualHostに書いた場合は、リクエストは繰り返されることはありません。1回のみです。

1回目 … /hoge/hige/ ★

どうやら、★で記したところの情報が、PHPの$_SERVER['SCRIPT_NAME']に入るようです。(これは上の結果からの推測ですが...)
この値は、Apache上では、REQUEST_FILENAME という変数になるため、SCRIPT_NAMEは、 内部リダイレクトした時も含めた最終リクエスト時の REQUEST_FILENAME が入る といったところではないでしょうか。

ただ、内部リダイレクトあるなしの挙動の違いだけで、これ以上詳しいことは分かりませんでした。 Directoryディレクトティブで内部リダイレクトなしにできないのかとか 逆に、VirtualHostで内部リダイレクトできないのか等が気になります。

リライト設定はDirectoryディレクティブへ

VirtualHost設定に書いた場合は、罠が多いように思います。
.htaccess と記述を合わせることのできるメリットもありますし、リライト設定はDirectoryディレクティブへ書くのが好ましいと思いました。

【Apache】mod_rewrite で「403 Forbidden」

以前のエントリーで、mod_rewrite の設定は書く場所(.htaccess or httpd.conf)によって、環境変数に入ってくる値やリライト先のパスが異なるが、Directoryディレクティブ内に設定を書くことで、.htaccess と同じ記述にできることが分かった。

kzhishu.hatenablog.jp

最近、.htaccess が許可されていない環境で、リライト設定をする機会があり「待ってました!!」と、httpd.conf のDirectoryディレクティブに設定を書いた。

<VirtualHost XXX.XXX.XXX.XXX:80>
  ServerName example.com
  DocumentRoot /var/www/html

  <Directory /var/www/html>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ index.php [QSA,L]
  </Directory>

</VirtualHost>

これがなんとうまくいかず「403 forbidden」
エラーログには以下の内容が...

Options FollowSymLinks and SymLinksIfOwnerMatch are both off, so the RewriteRule directive is also forbidden due to its similar ability to circumvent directory restrictions : /var/www/html/

なぜだろう(-_-)...
エラー内容を調べる。

Apacheのドキュメントに「you need to set "RewriteEngine On" and "Options FollowSymLinks" must be enabled」とあり、どうやら FollowSymLinks が有効でないため、RewriteRule の機能が動いていなかったようだ。

httpd.conf の全体を確認してみると、/var/www で FollowSymLinks を無効にしている箇所があり、今回は問題となっている VirtualHost は、/var/www/html のため、この設定の影響下のため同じく無効となる。これが原因だ。

<Directory /var/www>
Options -FollowSymLinks
</Directory>

以下のように書けば、エラーにはならない。ドキュメントルートは /var/www 配下だけれども、VirtualHost直下に書けば影響は及ばないようだ。

<VirtualHost XXX.XXX.XXX.XXX:80>
  ServerName example.com
  DocumentRoot /var/www/html

  RewriteEngine On
  RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f
  RewriteRule ^(.*)$ index.php [QSA,L]

</VirtualHost>

「Directory /」で設定で無効になるのかも確認。(VirtualHost直下は、/ とイコールかと思った...)これでも問題なく動く。VirtualHost は、Directoryの影響外のようだ。

<Directory />
  Options -FollowSymLinks
</Directory>

まとめると、リライト設定を書くときは、「Options」で検索をして FollowSymLinks が有効かを確認して Directoryディレクティブで問題ないのか、VirtualHost に書く必要があるのかを検討する必要がありそうだ。
できれば、.htaccess と記述を合わすためにDirectoryディレクティブに書きたい。
ちなみに「Options」が見つからない場合は、有効になっている。
設定されていなければ「Options All」になるためだ。

Options ディレクティブ
https://httpd.apache.org/docs/2.4/ja/mod/core.html#options

【JsRender】配列に無い要素を、for文の中で参照する方法

JsRender(javascriptのテンプレートエンジン)のfor文の中で、ループの各要素には無い値を参照したいことがある。 各要素に参照したい値を一つ一つ設置するという力技もあるが、すべて同じ値のためモヤモヤ感が半端ない。もっとスマートな方法は無いものか。
結論から言うと、renderメソッドの第2引数に設定してあげることでfor文の中でも参照できた。ドキュメントを見ると第2引数はヘルパー関数やメタデータを設定するとあるので、想定されている使い方ではないかもしれないが、意図通り動作しているので問題無いでしょう。

JsRender API documentation
http://www.jsviews.com/#tmplrender

具体例

ジャンルは全作品で共通のため、第2引数に設定する。
設定した値は、~[keyname]、つまり~genre で参照できる。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
        <script type="text/javascript" src="./jquery-1.9.1.js"></script>
        <script type="text/javascript" src="./jsrender.min.js"></script>
    </head>
    <body>
    <script id="template" type="text/x-jsrender">
        <ul>
        {{for books}}
        <li>{{:author}}{{>title}}({{:~genre}})</li>
        {{/for}}
        <ul>
    </script>
    <ul id='booklist'></ul>
    <script type="text/javascript">
        var output = $('#template').render({
        books: [
            {title: '剣客商売',  author: '池波正太郎'},
            {title: '坂の上の雲',author: '司馬遼太郎'},
            {title: '蝉しぐれ',  author: '藤沢周平'}
        ]
        },
        {genre: '歴史・時代小説'}
        );
        $('#booklist').html(output);
    </script>
    </body>
</html>

※出力結果はこんな感じ
f:id:kzhishu:20160920222619j:plain

【ラズパイ】LEDをたくさん点灯させたい!! IOエキスパンダ(MCP23017)によるGPIOポート拡張

「LEDをたくさん点灯させたいけど、ラズパイのGPIOポートでは足りない..さてどうしよう?」

方法を調べてみると、IOエキスパンダ(MCP23017)というデバイスを使えば増設できるということ。 I2Cを使ってラズパイからデータを送ると、16本あるIOエキスパンダのポートをうまいこと制御できる。 さらに、ラズパイ1台に対して最大で8個まで複数接続OK (IOエキスパンダのICアドレスは電流を加えることにより変更可能。それが8通りある) つまり最大、16 x 8 で 128ポート!! これは結構な数です!! 値段も、1個 120円と安価なのがうれしい限り。早速試してみた。

f:id:kzhishu:20160718101351j:plain:w600

ラズパイ
RaspberryPi2(ModelB)

I2Cデバイス
MCP23017
http://akizukidenshi.com/catalog/g/gI-09486/

ラズパイ側の準備

I2C有効化

#「9 Advanced Options」でI2Cを有効化する
$ raspi-config  

# backlist i2c-bcm2708 が記述してあればコメントアウトする
$ cat /etc/modprobe.d/raspi-blacklist.conf

# i2c-dev が記述されていること確認(なければ追記する)
$ cat /etc/modules

I2C用のコマンドラインツール

$ sudo apt-get install i2c-tools

smbusモジュール(PythonからI2Cを使用するため)

python3対応版があったのでそれを使用

$ sudo apt-get install python3-smbus
$ python3
>> import smbus

ちゃんとimportできること確認

配線

まずは、ラズパイに対してMCP2301が1つの場合の配線。こんな感じになりました。

f:id:kzhishu:20160718102741j:plain

配線に関してはこちらのサイトを参考にしました。 raspi.tv

I2Cの仕組みについてはここら辺で理解。

I2Cバスを使ってみよう - やまねこのマイコン実験室

実処理は、smbusライブラリに任せるので詳細はなんとなく(←情けないが...)で SCL(Serial Clock)のクロック信号でタイミングを取り、SDA(Serial Data)でデータを通信。 そのときに、SDAでスレーブ側のアドレスとデータを送るということを把握しておきましょうか。

コマンドラインから出力制御

接続デバイスの確認

$ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

無事配線が済み、スレーブ側のICアドレスが、0x20であることがわかる。
コマンドラインでデータを送ってみる。以下の形式で実行。

$ sudo i2cset -y 1 [ICアドレス] [レジスタアドレス] [データ]

ICアドレス

上でみた、スレーブ側のICアドレス。今回は、0x20 となる

レジスタアドレス

バイスのどの機能を使うのかを指定。GPIOポートの初期化や出力など。 レジスタアドレスを指定することによりその機能を使用できる。

ここでデータシートで、各ポートとレジスタアドレスについて確認しておきます。

f:id:kzhishu:20160718105324p:plain

f:id:kzhishu:20160718105335p:plain

(引用元:http://akizukidenshi.com/download/ds/microchip/mcp23017_mcp23s17.pdf

これを踏まえて整理するとこんな感じになります。

使用するレジスタ 説明
IODIRA GPA0(21) 〜 GPA7(28)の出入力設定をする(1:入力、0:出力) IO0 ~ IO7 と GPA0 ~ GPA7 が対応する
OLATA GPA0(21) 〜 GPA7(28)の出力制御(出力オン/オフ)を行う OL0 ~ OL7 と GPA0 ~ GPA7 が対応する

データ

例えば、GPA7, GPA5, GPA3 を出力して、LED点灯を行いたいときは GPA0(21) 〜 GPA7(28) の出力の設定(1:出力する、0:出力しない)をビットで渡してあげれば良いので 0b10101000 となる(GPA7が先頭となる点に注意!!)最終的には、16進数の 0xA8 で渡す。

上記内容をもとにコマンドラインを実行する

使用GPIOピンの初期化

$ sudo i2cset -y 1 0x20 0x00 0x00

LED点灯(GPA7, GPA5, GPA3 を オン)

$ sudo i2cset -y 1 0x20 0x14 0xA8

LED消灯(GPA7, GPA5, GPA3 を オフ)

$ sudo i2cset -y 1 0x20 0x14 0x00

Pythonから制御

シンプルに、指定のピンを5秒点灯したあと消灯するプログラム
基本的には、コマンドでやったことを bus.write_byte_data 関数を使いPythonから行っているのみ。

# -*- coding: utf-8 -*-

import smbus
import time

CHANNEL   = 1      # i2c割り当てチャンネル 1 or 0
ICADDR    = 0x20   # スレーブ側ICアドレス
REG_IODIR = 0x00   # 入出力設定レジスタ
REG_OLAT  = 0x14   # 出力レジスタ

bus = smbus.SMBus(CHANNEL)

# ピンの入出力設定
bus.write_byte_data(ICADDR, REG_IODIR, 0x00)

# GPA3, GPA5, GPA7 出力オン
bus.write_byte_data(ICADDR, REG_OLAT, 0xA8)
time.sleep(5)
# GPA3, GPA5, GPA7 出力オフ
bus.write_byte_data(ICADDR, REG_OLAT, 0x00)

複数接続!!

f:id:kzhishu:20160718110559j:plain

$ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 21 -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

スレーブ側のアドレスがきちんと2つになっているのでそれぞれで指定してあげる Pythonのプログラムはこんな感じ

# -*- coding: utf-8 -*-

import smbus
import time

CHANNEL    = 1      # i2c割り当てチャンネル 1 or 0
ICADDR1    = 0x20   # スレーブ側ICアドレス1
ICADDR2    = 0x21   # スレーブ側ICアドレス2
REG_IODIR  = 0x00   # 入出力設定レジスタ
REG_OLAT   = 0x14   # 出力レジスタ

bus = smbus.SMBus(CHANNEL)

# ピンの入出力設定
bus.write_byte_data(ICADDR1, REG_IODIR, 0x00)
bus.write_byte_data(ICADDR2, REG_IODIR, 0x00)

# GPA3, GPA5, GPA7 出力オン
bus.write_byte_data(ICADDR1, REG_OLAT, 0xA8)
bus.write_byte_data(ICADDR2, REG_OLAT, 0xA8)
time.sleep(5)
# GPA3, GPA5, GPA7 出力オフ
bus.write_byte_data(ICADDR1, REG_OLAT, 0x00)
bus.write_byte_data(ICADDR2, REG_OLAT, 0x00)

これで複数接続ができた。 データシートを読むのはきつかったが実現できました。 さて、たくさんLEDを点灯してみますか♪