Python の append がバグった時のメモ
dictを使い回してappendするような時はコピーしないとバグる元になる。
どゆこと?
例えばこんな感じ
>>> _tmp = dict() >>> _list = list() >>> >>> _tmp['a'] = 'hoge' >>> _list.append(_dict) >>> >>> _tmp['b'] = 'huga' >>> _list.append(_dict) >>> >>> _list [{'a': 'hoge', 'b': 'huga'}, {'a': 'hoge', 'b': 'huga'}] >>>
この時 _list
は [{'a': 'hoge'}, {'a': 'hoge', 'b': 'huga'}]
となるはずでは……?
なんでなん?
listにおける append
は、要素のコピーではなく id
の追加だから
>>> id(_list[0]) 139632634542408 >>> id(_list[1]) 139632634542408 >>>
つまり、一時的な _tmp
を作って _list[]
へ転記するイメージではなく、単純に _tmp
というモノへのリンクを貼っているだけなイメージ。
じゃあどうすればええのん?
いじる前にコピることで id
が変わる
>>> import copy >>> >>> _tmp = dict() >>> _list = list() >>> >>> _tmp['a'] = 'hoge' >>> _list.append(_tmp) >>> >>> _tmp = copy.deepcopy(_tmp) >>> _tmp['b'] = 'huga' >>> _list.append(_tmp) >>> >>> _list [{'a': 'hoge'}, {'a': 'hoge', 'b': 'huga'}] >>> id(_list[0]) 139797555681536 >>> id(_list[1]) 139797556515248 >>>
というか……
使いまわしつつ途中で append
とか普通ありえないけど、ちゃんと使った後は初期化しようねっていう話。
>>> _tmp = dict() >>> _list = list() >>> >>> _tmp['a'] = 'hoge' >>> _list.append(_tmp) >>> >>> _tmp = dict(_tmp) >>> _tmp['b'] = 'huga' >>> _list.append(_tmp) >>> >>> _list [{'a': 'hoge'}, {'a': 'hoge', 'b': 'huga'}] >>>
Debianでrootだけlocaleが間違っていると思ったらとってもギルティだった話。
聞いてくれや、事の顛末を。
print('あいうえお')
とかやってみようとすると
root@sv1:~# python Python 3.5.3 (default, Sep 27 2018, 17:25:39) [GCC 6.3.0 20170516] on linux Type "help", "copyright", "credits" or "license" for more information. >>> print('あいうえお') File "<stdin>", line 0 ^ SyntaxError: 'ascii' codec can't decode byte 0xe3 in position 7: ordinal not in range(128) >>> root@sv1:~#
こんなことになる。
同じ構成にしているはずの別サーバーでは
root@sv2:~# python Python 3.5.3 (default, Sep 27 2018, 17:25:39) [GCC 6.3.0 20170516] on linux Type "help", "copyright", "credits" or "license" for more information. >>> print('あいうえお') あいうえお >>> root@sv2:~#
普通にうまく行く。
「なんでやねん!!!」と思って標準出力のエンコーディングを見て見ると
root@sv1:~# python Python 3.5.3 (default, Sep 27 2018, 17:25:39) [GCC 6.3.0 20170516] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> print(sys.stdout.encoding) ANSI_X3.4-1968 >>> root@sv1:~#
こりゃまたどうしてこんなことに…
もしやと思い、おもむろにlocale
と叩いてみると
root@sv1:~# locale LANG=C LANGUAGE= LC_CTYPE="C" LC_NUMERIC="C" LC_TIME="C" LC_COLLATE="C" LC_MONETARY="C" LC_MESSAGES="C" LC_PAPER="C" LC_NAME="C" LC_ADDRESS="C" LC_TELEPHONE="C" LC_MEASUREMENT="C" LC_IDENTIFICATION="C" LC_ALL= root@sv1:~#
∩(´;ヮ;`)∩ンヒィ~~~~~~~~~~~~~~~~~~~~
そういえばこのサーバー、インストールの時間違えてen_US.utf8
を選ぶつもりがC
を選んじゃったんだった…
でもその後直した気がするのでもう少し調べてみる。
root@sv1:~# cat /etc/default/locale LANG=en_US.UTF-8 root@sv1:~#
既に設定されている。
現に一般ユーザーでは問題ない
【ひみつだよ】$ locale LANG=en_US.UTF-8 LANGUAGE= LC_CTYPE="en_US.UTF-8" LC_NUMERIC="en_US.UTF-8" LC_TIME="en_US.UTF-8" LC_COLLATE="en_US.UTF-8" LC_MONETARY="en_US.UTF-8" LC_MESSAGES="en_US.UTF-8" LC_PAPER="en_US.UTF-8" LC_NAME="en_US.UTF-8" LC_ADDRESS="en_US.UTF-8" LC_TELEPHONE="en_US.UTF-8" LC_MEASUREMENT="en_US.UTF-8" LC_IDENTIFICATION="en_US.UTF-8" LC_ALL= 【ひみつだよ】$
…は?
たねあかし。
rootだけということは、きっとrootのbash profileあたりがおかしいんだろう…
root@sv1:~# cat ~/.profile # ~/.profile: executed by Bourne-compatible login shells. if [ "$BASH" ]; then if [ -f ~/.bashrc ]; then . ~/.bashrc fi fi mesg n || true # Installed by Debian Installer: # no localization for root because C # cannot be properly displayed at the Linux console LANG=C LANGUAGE=C root@sv1:~#
コラコラコラコラ~ッ!(`o´)
おまえなんでそんなとこに書いとんねん!!
ちなみに問題ないサーバーではこんなの無い。
root@sv2:~# cat ~/.profile # ~/.profile: executed by Bourne-compatible login shells. if [ "$BASH" ]; then if [ -f ~/.bashrc ]; then . ~/.bashrc fi fi mesg n || true root@sv2:~#
マジで何のために書いてあるんこれ…
と言うわけで直すよ。
root@sv1:~# vi ~/.profile root@sv1:~# cat ~/.profile # ~/.profile: executed by Bourne-compatible login shells. if [ "$BASH" ]; then if [ -f ~/.bashrc ]; then . ~/.bashrc fi fi mesg n || true root@sv1:~# logout 【ひみつだよ】@sv1:~$ su - Password: root@sv1:~# locale LANG=en_US.UTF-8 LANGUAGE= LC_CTYPE="en_US.UTF-8" LC_NUMERIC="en_US.UTF-8" LC_TIME="en_US.UTF-8" LC_COLLATE="en_US.UTF-8" LC_MONETARY="en_US.UTF-8" LC_MESSAGES="en_US.UTF-8" LC_PAPER="en_US.UTF-8" LC_NAME="en_US.UTF-8" LC_ADDRESS="en_US.UTF-8" LC_TELEPHONE="en_US.UTF-8" LC_MEASUREMENT="en_US.UTF-8" LC_IDENTIFICATION="en_US.UTF-8" LC_ALL= root@sv1:~# python Python 3.5.3 (default, Sep 27 2018, 17:25:39) [GCC 6.3.0 20170516] on linux Type "help", "copyright", "credits" or "license" for more information. >>> print('あいうえお') あいうえお >>> root@sv1:~#
直った。
結局あれ、なんのためやったん?
~/.profile
にはこんなことが書いてあった。
# Installed by Debian Installer: # no localization for root because C # cannot be properly displayed at the Linux console
翻訳すると、こう。
Debianインストーラによってインストールされます。
Cのため、root用のローカライゼーションはありません。
Linuxコンソールに正しく表示されない
要はシステムがC
になってるとコンソールがちゃんと表示されないからその対策ってこと…?
┐(´ー`)┌ さっぱりわかんないよ
Apacheのアクセスログでホスト名が出る時。
confで HostNameLookups
を on
にすると逆引きしてからログを記録するが、そうじゃなくてもホスト名で記録されるときがある。
わざわざ逆引きするとパフォーマンス悪くなりそうなのになんでだろーって思ってるとこんな記事を発見。
原因は .htaccess の「allow from」部分でした。
order deny,allow
deny from all
allow from {hostname}
とやっていると結局逆引きしているのでアクセスログにホスト名が載るようです。
つまり、htaccessにホスト名で記述すると逆引きしないと評価できないから、ログに逆引きされた結果で書かれるっていう事らしい。
なるほどなー
自分なりのpythonのロギング作法
「自分なり」?
自分なり。
いつもコーディングするときに「ロギングどないしてたんやったかな」ってなるから、メモ。
多分Python本来のお作法とは外れてるけど、んなもん知るかいな。さてはアンチだなオメー。
スタンス
- 基本的に画面には出力しないが、ロートロガーに流れてきた
WARNING
以上は出力 - 設定ファイル(yamlを使うことが多い)でログファイルを指定できるようにしておいて、そこには
INFO
以上を出力 - 実行時に
--debug
を付けたら、画面に全てのログが出るようにする。
コードはよ。
import logging # ログ用変数設定 logger = logging.getLogger(__name__) logformat = logging.Formatter('%(asctime)s %(module)s[%(lineno)d] [%(levelname)s]: %(message)s', '%Y-%m-%d %H:%M:%S') # ロギング設定 logger.setLevel(logging.DEBUG) logging.getLogger('').handlers.clear() disp_handler = logging.StreamHandler() disp_handler.setLevel(logging.WARNING) disp_handler.setFormatter(logformat) logging.getLogger('').addHandler(disp_handler) def main(): ~~~ここでごねごね~~~ nomal_handler = logging.FileHandler(filename=configs['system']['logfile']) nomal_handler.setLevel(logging.INFO) nomal_handler.setFormatter(logformat) logger.addHandler(nomal_handler) ~~~しょり~~~ if __name__ == '__main__': if '--debug' in sys.argv: disp_handler.setLevel(logging.DEBUG) try: main() except: logger.critical('********** Exception **********\n%s', traceback.format_exc()) sys.exit(1)
Apacheで特定IPをdenyしたりallowしたりするときのconf
職業柄だとは思うけど
あっ!!
今すぐこのIPを弾きたい!!!
今すぐ!!!!
みたいな時がたまにあるので、そんな時にどんなconfを書けばいいかすぐにわかるようにまとめておく。
絶対他の人の役には立たないと思うけど、僕の備忘録だからそれでよし。
特定IPからのリクエストを拒否
allowを明示的に書く
<FilesMatch "\.php"> <LIMIT POST GET> Order Allow,Deny Allow from all Deny from 8.8.8.8 </LIMIT> </FilesMatch>
denyだけでOK
<FilesMatch "\.php"> <LIMIT POST GET> Order Deny,Allow Deny from 8.8.8.8 </LIMIT> </FilesMatch>
特定IPからのリクエストを許可
denyを明示的に書く
<FilesMatch "\.php"> <LIMIT POST GET> Order Deny,Allow Deny from all Allow from 8.8.8.8 </LIMIT> </FilesMatch>
allowだけでOK
<FilesMatch "\.php"> <LIMIT POST GET> Order Allow,Deny Allow from 8.8.8.8 </LIMIT> </FilesMatch>
どゆことですのん?
感覚的にはOrder
が逆なんだよなぁ…
Order Allow,Deny
のとき
- 全てのIPが一旦拒否対象になる
- Allowで指定したIPが許可対象になる
- 例外としてDenyで設定したIPを拒否する
Order Deny,Allow
のとき
- 全てのIPが一旦許可対象になる
- Denyで指定したIPが拒否対象になる
- 例外としてAllowで設定したIPを拒否する
つまり…?
「Order ①,②
だと、①がメイン、ただし例外として②を適用する」という感じだろうか。
bindする関数(PHP編)
時々PHPでテキストファイルに変数をbindして使いたいって思うことがあるから、そんなときに便利な関数を作ってみた。
いわゆるテンプレートエンジンってやつかな。
$str = "bbbbb__test__c __aaa__ ccc"; echo bindTemplate($str, array('test'=>'うわーい')); echo bindTemplate($str, array('test'=>'ぽぷて', 'aaa'=>'ぴぴっく')); function bindTemplate($template, $param, $pattern='/__(.+?)__/') { return preg_replace_callback( $pattern, function ($matches) use($param){ $original = $matches[0]; $key = $matches[1]; if ( array_key_exists($key, $param) ){ return $param[$key]; }else{ return $original; } }, $template ); } //?>
↓実行結果はこうなる↓
bbbbbうわーいc __aaa__ cccbbbbbぽぷてc ぴぴっく ccc
余談だけど
これを試している環境でeAcceleratorが動いていて、そのせいで無名関数がまともに動かなかった。 まじくそっすわー凸(☎︎ω☎ )凸
Let’s Encrypt でワイルドカード証明書を自動更新させたらえらい目にあった話
Let’s Encryptのワイルドカード証明書がどうしても使ってみたかった(ついでに自動更新にも対応させたかった)ので、いろいろ頑張ってみた話。
*** おことわり *** 前のブログからほぼそのままコピペしてきてます。 この後なんか色々変えたような覚えがあるけど、記憶を探ってみないと何を変えたか覚えてないので、とりあえずこのまま記事にしました。 いつかちゃんと書き直そうと思います。いつか。
認証用ゾーンの準備
Let’s Encryptのワイルドカード証明書は、該当するドメインのTXTレコードを見に来る。
つまり、Certbotが勝手にTXTレコードを書き換えればよいわけだが、他のホスト名とかが書いてあるゾーンファイルをCertbotに直接いじらせてシリアルも変えさせるのは気持ち悪かったのでゾーンを分けた。
[root@sv1 ~]# cat /var/named/zone/_acme-challenge.yuicho.net $TTL 1M $ORIGIN _acme-challenge.yuicho.net. @ IN SOA sv1.yuicho.net. hostmaster.yuicho.net. ( 2018050304 ; serial 1D ; refresh 1H ; retry 4W ; expire 1M ) ; minimum IN NS sv1.yuicho.net. IN NS sv2.yuicho.net. @ IN TXT "TXT-Record-Test" [root@sv1 ~]# [root@sv1 ~]# tail -6 /etc/named.conf zone "_acme-challenge.yuicho.net" { type master; file "zone/_acme-challenge.yuicho.net"; allow-query { any; }; notify yes; }; [root@sv1 ~]# [root@sv2 ~]# tail -7 /etc/named.conf zone "_acme-challenge.yuicho.net" { type slave; file "slaves/_acme-challenge.yuicho.net"; allow-query { any; }; allow-transfer { none; }; masters { 133.18.169.101; }; }; [root@sv2 ~]# [root@sv1 ~]# host -t TXT _acme-challenge.yuicho.net sv1 Using domain server: Name: sv1 Address: 133.18.169.101#53 Aliases: _acme-challenge.yuicho.net descriptive text "TXT-Record-Test" [root@sv1 ~]#
余談だが、ゾーンファイルに権限がないとrndc reloadさせてくれなかった。
本来は
[root@sv1 ~]# rndc reload acme-challenge.yuicho.net rndc: 'reload' failed: permission denied [root@sv1 ~]#
となるはずなのだが、先頭に_が入っていると
[root@sv1 ~]# rndc reload acme-challenge.yuicho.net rndc: 'reload' failed: not found [root@sv1 ~]#
となった。
しかし、不思議なことに再度権限を戻して
[root@sv1 ~]# ls -la /var/named/zone/_acme-challenge.yuicho.net -rw-r-----. 1 root root 264 5月 4 01:28 /var/named/zone/_acme-challenge.yuicho.net [root@sv1 ~]#
rndc reload
しても通る。
[root@sv1 ~]# rndc reload _acme-challenge.yuicho.net zone reload queued [root@sv1 ~]#
なぜだ………(´・ω・`)
Certbotで使うフックの作成
CertbotでできるDNS-01認証(今もこの名前でいいのかな)は、基本インタラクティブな動作になる
でも今回は自動更新に対応させたかったので、それだと困る。
そのため、Certbotが認証の時に叩くフックスクリプトが必要になる。
そんなわけで、以下のスクリプトを作成。
[root@sv1 ~]# cat /usr/local/bin/dns-auth.sh #!/usr/bin/env bash ZONENAME="_acme-challenge.${CERTBOT_DOMAIN}" cp -av /var/named/zone/$ZONENAME{,.backup} sed -i -r -e "s/@\s+IN\s+TXT\s+\".+?\"/@ IN TXT \"${CERTBOT_VALIDATION}\"/g" -e "s/[0-9]{10}\s+;\s+serial/`date "+%s"` ; serial/g" /var/named/zone/$ZONENAME rndc reload $ZONENAME sleep 90; [root@sv1 ~]#
かなりお粗末スクリプトだけど動きゃいいんだよ!!!!!(`Д´)
ちなみに、sleep 90
を入れているのはとても苦肉の策。
当初は*.yuicho.net
のみで認証していたからこんなもの入れてなかったけど、それだとyuicho.net
でこの証明書が使えなくなってしまう。
SANsを使ってyuicho.net
と*.yuicho.net
の両方を兼ね備えた証明書にしなければいけないけど、そうすると2ドメイン分の認証が走ってしまうので、TTL 1Mでも間に合わなくなってしまう。
そこで仕方なく、TTL分以上(90秒)待ってLet’s Encrypt側のキャッシュが消えるようにしている。
(というか、認証サーバーがキャッシュ持つなよなー)
Certbotに証明書を発行させる
[root@sv1 ~]# certbot certonly -n -d yuicho.net -d *.yuicho.net --agree-tos --email register.letsencrypt@yuicho.net --manual-public-ip-logging-ok --manual --preferred-challenges dns-01 --manual-auth-hook /usr/local/bin/dns-auth.sh --server https://acme-v02.api.letsencrypt.org/directory --post-hook '/bin/systemctl reload httpd; /bin/systemctl reload postfix; /bin/systemctl reload dovecot;' Saving debug log to /var/log/letsencrypt/letsencrypt.log Plugins selected: Authenticator manual, Installer None Starting new HTTPS connection (1): acme-v02.api.letsencrypt.org Obtaining a new certificate Performing the following challenges: dns-01 challenge for yuicho.net dns-01 challenge for yuicho.net Output from dns-auth.sh: `/var/named/zone/_acme-challenge.yuicho.net' -> `/var/named/zone/_acme-challenge.yuicho.net.backup' zone reload queued Output from dns-auth.sh: `/var/named/zone/_acme-challenge.yuicho.net' -> `/var/named/zone/_acme-challenge.yuicho.net.backup' zone reload queued Waiting for verification... Cleaning up challenges Running post-hook command: /bin/systemctl reload httpd; /bin/systemctl reload postfix; /bin/systemctl reload dovecot; IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/yuicho.net/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/yuicho.net/privkey.pem Your cert will expire on 2018-08-01. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run "certbot renew" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le [root@sv1 ~]#
無事ワイルドカード証明書をげっちゅ!!!
これで、次からはcertbot renew
で良いはず。
まとめ
もっと簡単にCertbotでとれるようにしとけよなー!!!
っていうか、キャッシュ持つなよなー!!!