【Python】コンソールでのログ監視をFTP経由で行いたい
レンタルサーバーなどで、FTP接続しかできない開発者泣かせの環境があったりします。
こんな環境ではログファイルをみるのも大変々々。
「SSHを使えればなぁ」と夢想しつつ、FTPクライアントからファイルをダウンロードして開いて末尾を確認という非生産的な行為を繰り返すうちに
「誰かの陰謀ではないか?」と世の中疑り出す始末。。。
こんなときこそプログラムでしょ♪
こんなときこそ、手動で行っている作業をプログラムで自動化すべきです。
開発スイッチが入りました。
手順を整理するとこんな感じ!!
(Linxコマンドでいう tail -f [file] のようなことをやりたいのです)
1. FTP接続(指定した環境にPythonからFTP接続) 2. ログファイルのDWと出力(ログファイルをダウンロードして、末尾の何行かをコンソールに出力) 3. 更新有無の判定(ログファイルが追記されているか確認) 4. 更新分の出力(ログファイルが追記されていれば、再びダウンロードして追記分を出力) (以後3,4を繰り返す)
1. FTP接続
# -*- coding: utf-8 -*- import ftplib import time host = '' # 接続先のホスト名 user = '' # 接続先のユーザ名 password = '' # 接続先のパスワード ftp = ftplib.FTP(host, user, password)
pythonのftplibライブラリを使用します。
21.13. ftplib — FTPプロトコルクライアント — Python 3.5.1 ドキュメント
ホスト、ユーザ名、パスワードを引数に渡してインスタンスを生成します。
以後は、このFTPクラスを通して接続先とファイルの状態のチェックやダウンロードなどのやり取りを行います。
FTP接続先に用事があったらこいつに問い合わせる感じでしょうか...
一度FTPに接続したら、Pythonの関数(os.path.exists)などでアクセスできるのかと思いましたが、Pythonから直接FTP先にアクセスは無理なのでそんなことはできないです。
2. ログファイルのDWと出力
logpath = '/var/www/log/' logfile = 'test.log' # ダウンロードデータの受け皿 content = bytearray() # ダウンロードしたバイトデータを引数に呼ばれる def download(data): for b in data: content.append(b) # 該当ディレクトリに移動してダウンロード ftp.cwd(logpath) ftp.retrbinary("RETR %s" % logfile, download) logdata = content.decode('utf-8')
目的のログファイルをダウンロードします。
まずは、ftp.cwd() で目的のパスへ移動します。(Linxでいうと cd ですね)
パスの指定は、FTPのルートディレクトリからの絶対パスでの指定となります。
当然といえば当然なのですが、サーバのルートディレクトリとは異なる環境もあります。(FTPクライアントで接続するとリモートサイトのパスが表示されるのでそれを参考にするとよいです。)
ftp.retrbinary() が対象ファイルのダウンロード処理で、第1引数はFTPコマンドでファイルを指定。
FTPコマンドをPythonから使えるようにラップしたものが、fitlib と思うのですが、ここでは素のFTPコマンドを指定します。
(つまり、ダウンロード以外のFTPコマンドも指定できるということでもあります。マニュアルをみて理解できない点でも、FTPコマンドを調べてみると納得いくことが多々ありました。)
FTPコマンド一覧
FTPコマンドの一覧 - Wikipedia
第2引数は、コールバック関数を指定。(ここでは、download関数を指定)
引数の dataにダウンロードした内容が、バイナリで格納されます。
ログファイルのサイズが大きいときは、download() が何回かに分けて呼ばれます。
その影響で、バイナリ → UTF-8へのデコード処理を、download() 関数内で行うのでは、上手くいきませんでした。
(数バイトの固まりで意味を成す、マルチバイトのデータが途中でちょん切れてしまってうまくできないようです)
そこで「分かれてきたデータをバイナリのまま結合して、最後にデコードする」という手段をとりました。
logdata = logdata.strip().split("\n") # 末尾の行数を覚えておく index = len(logdata) for oneRow in logdata[-3:]: print(oneRow ) # ダウンロードファイルの受け皿として後でも使うので初期化 content = bytearray()
ダウンロードしたデータを配列に変換して末尾の3行をコンソールに出力します。
この配列の要素数が、ファイルの行数になります。追加分の出力のとき必要なため、末尾の行数を覚えておきます。
前後の改行は、strip() メソッドで無視します。
3. 更新有無の判定
# ファイルサイズを取得 # 失敗する場合は、FTP接続先がバイナリモードでない可能性がある。ftp.sendcmd("TYPE I") でバイナリモードしてから実行すると解決するかも size = ftp.size(logfile)
ログファイルが追記されているかの判定は、ファイルサイズで行います。
ftp.size() でファイルのサイズ取得を行います。
4. 更新分の出力
前回の値と比較して、大きい場合のみ再度、2. と同じようにダウンロードして
追加分(前回の末尾の行数から今回の末尾まで)を出力します。
このままだと無限ループになるため、ctrl+c で終了できるようにしました。
クラッシュしないように、time.sleepでウェイトをいれています。
# ウェイトを置かないと処理が止まる time.sleep(2.0) try: while True: old_size = size size = ftp.size(logfile) # 前回のサイズと比較 if old_size < size: ftp.retrbinary("RETR %s" % logfile, download) logdata = content.decode('utf-8') logdata = logdata.strip().split("\n") # 前回との差分を出力 for oneRow in logdata[index:]: print(oneRow) index = len(logdata) time.sleep(2.0) except KeyboardInterrupt: ftp.close() except: ftp.close()
おわり
こんな感じでFTP経由でログファイル監視が実現できました。
整理改良したものをGitHubに置いておきます。
Python FTP接続でログファイルを監視する · GitHub
次の野望はこいつをGUIのアプリケーション化することです。 PyQtがなかなか素晴らしいそうなのでやってみたいです。 久しぶりの投稿でした。
Ubuntu15.10 に Brother製プリンタ(DCP-J940N)を設定
最近新しく買ったノートPCにUbuntuをインストールしてみました。 以前もサブで使っていて愛着も沸いてきたので、 これからはメインで使っていきたいなぁと考えていて そうなると印刷設定はしておかんとなと思いたち、やってみました。 「なんか難しそう...」と今まで敬遠していましたが、思ったよりもすんなりとできました。 (GUIだけでは完結できなかったですが....)
OSとプリンタ
PC:Ubuntu15.10(64bit版) プリンタ:DCP-J940N(Brother製) ※プリンタとPCは無線でつなぎます
CUPS のインストール確認
CUPS というLinuxの印刷システムを使います。
仕組みについては、うーんなんとなく分かったような...
さて、下記コマンドでインストールされているかを確認します。
$ apt-cache policy cups cups: インストールされているバージョン: 2.1.0-4ubuntu3 候補: 2.1.0-4ubuntu3 バージョンテーブル: *** 2.1.0-4ubuntu3 0 500 http://mirror01.idc.hinet.net/ubuntu/ wily/main amd64 Packages 100 /var/lib/dpkg/status
うん。インストール済みでした。
ちなみに apt-cache policy 〜 を使うことで、インストール済みのバージョンのみではなく
インストールできるパッケージのバージョンを知ることができて非常に便利です。
ドライバのインストール
64bitで32bitアプリを動作させるライブラリ
ありがたいことに、DCP-J940N には Linux用のドライバが提供されています。 ただ、ドライバをダウンロードするとわかるのですが、32bit版しかなく このままでは、64bit動作しないです。(多分..)
なので、64bitで32bitアプリを動作させるライブラリというのを 導入する必要があるようです。コマンドは以下
$ sudo apt-get install lib32stdc++6
さぁ、いよいよドライバのインストールです。
http://support.brother.co.jp/j/b/downloadlist.aspx?source_c=&c=jp&lang=ja&prod=dcpj940n&os=128
LPR プリンタードライバー (deb package) CUPSwrapper プリンタードライバー (deb package)
上の2つをそれぞれダウンロードしてきて
Ubuntuソフトウェアセンターで開いてインストールします。
途中「パッケージの品質が悪いです..」と警告ダイアログが出ますが
無視してインストールします。
(gdebi などでコマンドからインストールしないとだめかなと思いましたが
ソフトウェアセンターで大丈夫でした)
CUPS のWeb管理ページからプリンタを追加する
ドライバもインストールされ準備が整ったので あとはPCのプリンタ設定を追加していくだけです。 Ubuntuの設定からでは上手くいかず、 CUPSの管理画面から設定する必要がありました。 以下のページから設定できます。
http://localhost:631/admin
[Add Printer]をクリック
→ ユーザー名とパスワードは、UbuntuのユーザでOK次のページで Discovered Network Printers: Brother DCP-J940N (Brother DCP-J940N) を選択 [Continue]で次へ
→ DCP-J940N が複数表示されるが、違いがよく分からなかった。 とりあえず上の方を選択した。Name や Description を入力するページ デフォルトでプリンタ名が入力されているため とくに何も変更せず、[Continue]で次へ
Modelなどを選択するページになるが 特に何も変更しなかった
デフォルトオプションの設定のページになるが ここもなにも設定しなかった
...とほとんど、次へ次へでOKでした。
テスト印刷をする
以上で設定は完了しており、システム設定 > プリンタ をみると 先ほど追加した設定が増えているはずです。 そのプリンタのプロパティからテスト印刷をしてみました。
ちゃんと印刷できてます。やったね。
httpd.conf と .htaccess でリライト設定が異なる
.htaccess リライト設定をそのままApache設定ファイル(VirtualHostディレクティブ)に書いても動かずハマったのでメモ。
.htaccessに設定
RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php [QSA,L]
RewriteCond でリクエストファイルが存在するか判定します。
判定 | 実行される処理 |
---|---|
存在する | RewriteRuleを通らず直接リクエストファイルを表示 |
存在しない | RewriteRuleを通り index.php にリライトする |
よく見るリライト設定です。 説明の便宜上 .htaccess は example.jp のドキュメントルート直下に置いてあることにします。 ドキュメントルートは /var/www/html で /var/www/html/.htaccess にファイルがあるということになります。
VirtualHost ディレクティブに設定
同じように内容を VirtualHostディレクティブに設定します。 上と同じ動きになることを期待しますが、 「400 Bad Request」とエラーになってしまいます。
httpd.conf
<VirtualHost *:80> ServerName example.jp DocumentRoot /var/www/html RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php [QSA,L] </VirtualHost>
何が異なるのか?
リライトログを取って詳細の動きを追ってみました。 .htaccess と VirtualHostディレクティブ で異なる点は2点
REQUEST_FILENAME に入る値が違う
http://example.jp/hoge にアクセスすると
.htaccess → /var/www/html/hoge
VirtualHost → /hoge
VirtualHostの場合はドキュメントルート部分が抜けます。 正確に動かすには、 %{DOCUMENT_ROOT} 部分を追加する必要があります。
RewriteRule のリライト先に / が必要かどうか
.htaccess → RewriteRule のリライト先 index.php は .htaccess の置いてある階層の index.php と判定される。
VirtualHost → RewriteRule のリライト先 index.php は、ドキュメントルート直下にならずにエラーとなる
VirtualHost の場合は、/index.php と絶対指定に指定する必要があります。
上の2点を直すとこんな感じになります。 どうやら、VirtualHost 下に書くとドキュメントルートが基点にはならないようです。
httpd.conf
<VirtualHost *:80> ServerName example.jp DocumentRoot /var/www/html RewriteEngine On RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ /index.php [QSA,L] </VirtualHost>
ええぃ憶えるのめんどくさいぞぉ
こんなのすぐ忘れます。いつもApacheの設定をいじっている訳でもないですし。 曖昧な記憶から「あれっなんだたっけ」と思いだすオーバーヘッドも大きいです。
実は、Directoryディレクティブを使えば、.htaccess と同じ設定で書けます
こんな感じです。
httpd.conf
<VirtualHost *:80> ServerName example.jp DocumentRoot /var/www/html <Directory /var/www/html> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php [QSA,L] </Directory> </VirtualHost>
Apacheのドキュメントにも、上の設定での、.htaccess と Directoryディレクティブは等価とあります。
ところで、ディレクティブの書かれた .htaccess を /www/htdocs/example に置くことと、同じディレクティブを 主サーバ設定の Directory セクション >
に書くことは 完全に等価です
Apache HTTP Server Tutorial: .htaccess files - Apache HTTP Server Version 2.4
Apache設定ファイルのリライト設定は、Directory [ドキュメントルート]に書く
と覚えておけば良いのではないでしょうか。これでコードを修正する手間などなくなります。
「Raspberry Pi + レンタルサーバー」で円滑に退社時間を伝える~おきがる伝言鳥~
Raspberry Piとレンタルサーバーで、自宅に会社を出た時間を伝える仕組みを作りました。
プログラムは「作って楽しい、使って便利」となれば最高ですが、割と前者の自己満足で終わることが多かったりします。 しかし、今回は後者の便利な点が少し実現できたかなと思っているのでまとめてみます。
我が家のお話。会社から帰るとき自宅に「今から帰るよ~」と電話してます。なぜ電話かって、私の親は携帯を持っていないためメールという手段は持ち合わせいません。電話ってかける方はいいですけど、出る方は大変です。常に電話のあるお茶の間にいる訳じゃないですから。トイレにいっていたり、テレビドラマを見ていて盛り上がっているところかもしれません。
そこで、電話を使わずに「今から帰るよ」と伝える手段を考えてみました。思いついたのは、
スマホでWebページのボタンを押すと、自宅ラズベリーパイの7セグLEDに退社時間が点灯
という装置。名付けて「おきがる伝言鳥~msgbird~」
実は伝言じゃない気もするのですが、初めは退社時間だけでなく、文字でメッセージ通知もする予定でそのときの名残です。でなんで鳥なんだというと、そのままでは味気ないため、鳥のぬいぐるみにLEDをひっかけようと思ったからです。
構成図
説明
- 退社時にスマホでサーバーにアクセスし退社ボタンを押す
- サーバーのJSONファイルに退社時間が書き込まれる
- ラズパイは、サーバー上のJSONファイルを監視(1分おきにリクエスト)
- 更新があったら退社時間を7セグLEDを点灯。圧電ブザーも鳴らす。
JSONファイルの監視は1分毎なので、点灯するまでブランクがある設計です。ただ、今回の目的は退社時間を伝えることなので、1分後でも問題ないと判断しました。あと、レンタルサーバーを経由するため、自宅のラズパイは外からアクセスできる必要はないです。(外からアクセスは自分の今の技術では難しそうなため断念しました。) 退社ボタン画面は、さくらのレンタルサーバー スタンダードに置きました。 自宅のラズパイは、RaspberryPi2です。言語は、Python3系を使いました。
後で思いついたのですが「node.js + websocket」を使えば、即反映とサーバーへの無駄なリクエストを無くすことができそうです。今後改良をしてみたいと思います。
3週間程使っていますが、安定して動作しています。
コードはGitHubにおいてます。
おきがる伝言鳥 GitHub
GitHub - megatk/msgbird: 退社・メッセージ通知 「おきがる伝言鳥」のリポジトリ
client/ 自宅ラズパイのPG
doc/ 回路図
server/ サーバーに置くPG
README.md
PHP monologをカスタマイズしてみた
PHPでプログラムを書くときにログをファイルに出力したいこともあると思います。
ゼロベースで仕組みを作るのはしんどいので、monologというライブラリを試してみました。しかし、出力できる情報が足りなかったため思い切ってカスタマイズしてみました。主に、GitHubのmonologマニュアルを参考にしています。
Composerの導入
$ curl -s -S https://getcomposer.org/installer | php $ mv composer.phar composer $ composer --version Composer version 1.0-dev (5ccaad92c19ac673435dbb2858ae20d14f34950d) …
まずはComposer本体(パッケージ管理ツール)をインストール。
後々楽なので、パスの通っているディレクトリで実行しました。
また、composer でコマンド実行したいため、リネームして.pharの部分を消します。
$ composer --version とコマンドを打ってみて確認。無事インストールできました。
プロジェクトにセットアップ
適当な場所にプロジェクトのディレクトリを作成し、composer.json というファイルを作成。このファイルに必要なライブラリをJSON形式で指定して、インストールします。
$ mkdir cstest $ cd cstest $ vim composer.json { "require": { "monolog/monolog": "1.17.*" } } $ composer install
バージョンは、https://packagist.org/ からmonolog を探して、最新の1.17系を指定しました。(1.17.* で1.17系の最新版になるようです)
※ composer.json の内容を書き換えたときは、$ composer update で更新します。
monologを試してみる
ドキュメントを参考にしてmonologを試してみます。StreamHandler でログをファイルに出力し、LineFormatter でフォーマットを指定しています。
<?php require_once('vendor/autoload.php'); use Monolog\Logger; use Monolog\Handler\StreamHandler; use Monolog\Formatter\LineFormatter; $logging_path = 'log/error.log'; $log = new Logger('test'); // フォーマット $output = "[%datetime%] %level_name%: %message% %context% %extra%\n"; // LineFormatterで出力フォーマット指定 $formatter = new LineFormatter($output); // StreamHandlerでファイルにログを出力 $stream = new StreamHandler($logging_path, Logger::DEBUG); $stream->setFormatter($formatter); $log->pushHandler($stream); // ログ出力 ex: [2015-10-03 18:35:29] INFO: debug message [] [] $log->addInfo('debug message!!');
monolog での不満点
さて、これで無事ファイルにログが出力されるのですが、「めでたしめでたし」という訳にはいきません。全く物足りないです。次のような情報もほしいです。
・配列やオブジェクトの内容の出力
・データ型の表示
・実行ファイルと行番号
・実行開始から経過時間
Processor 自由にフォーマットを作る
いろいろ調べてみたところ、Processor という付加情報を表示する仕組みがあるところに目を付けました。ユーザが自由に作成した関数を登録でき、その関数内で、$record['extra']という項目に値をセットすれば、フォーマットの %extra% の箇所にその値が出力されるようです。
<?php $logger->pushProcessor(function ($record) { $record['extra']['dummy'] = 'Hello world!'; return $record; });
そして、$record の値をvar_dumpしてみた結果です。
array(7) { ["message"]=> string(13) "debug message" ["context"]=> array(0) { } ["level"]=> int(200) ["level_name"]=> string(4) "INFO" ["channel"]=> string(4) "test" ["datetime"]=> object(DateTime)#7 (3) { ["date"]=> string(26) "2015-10-03 19:03:59.766603" ["timezone_type"]=> int(3) ["timezone"]=> string(10) "Asia/Tokyo" } ["extra"]=> array(0) { } }
最初の階層のキー名がすべてフォーマットのキー名と合致しているのがわかります。つまり、$record にキーを追加すればフォーマットを自由に増やすことができそうと予測できます。実際にやってみたところこれは当たっていました。
context を使い Processorに情報を渡す
あとは、Processorに登録した関数内に、ログに出力する情報を渡せればよいだけです。 ログ出力メソッドの第2引数を配列で指定すると、$record['context'] にその配列が入ります。
<?php $context = array( 'file' => 'ファイル名', 'line' => '行数' ); $log->addInfo('debug message!!', $context);
やりました。これで解決です。以下のようにログ出力用のラッパー関数を作ることで、様々な値がログに出力出来そうです。
(若干乱暴な気もしますが....)
<?php require_once('vendor/autoload.php'); use Monolog\Logger; use Monolog\Handler\StreamHandler; use Monolog\Formatter\LineFormatter; $logging_path = 'log/error.log'; $log = new Logger('test'); // ★フォーマットに、file と line を追加 $output = "[%datetime%] %level_name% %message% %file% %line%\n"; $formatter = new LineFormatter($output); $stream = new StreamHandler($logging_path, Logger::DEBUG); $stream->setFormatter($formatter); $log->pushHandler($stream); $log->pushProcessor(function ($record) { $record['file'] = $record['context']['file']; $record['line'] = $record['context']['line']; return $record; }); function debug($message, $depth=''){ global $log; // 呼び出し元ファイルと行数 $backtrace = debug_backtrace(); // 指定の深さが存在しない場合は呼び出し元に $key = isset($backtrace[$depth]) ? $depth : 0; $file = $backtrace[$key]['file']; $line = $backtrace[$key]['line']; $context = array('file' => $file, 'line' => $line); // エラーレベルは一旦固定 $log->addInfo($message, $context); } // ログ出力 debug('debug message!!');
他の情報も追加してみる
他の情報も追加してみます。例のごとくクラス化してみました。
・ 配列やオブジェクトの内容の出力とデータ型の表示
受け取った値を var_dump を使い、ob_start()等を使い変数に格納させます。
var_dumpの値の改行が、ログ出力時有効にならないため焦りました。これは、以下の記事で解決できました。
http://hack.aipo.com/archives/12506/
・実行開始から経過時間
ミリ秒単位で出力します。開始はインスタンス生成のタイミングで、終了はデバックメソッド呼び出しです。
ログってただでさえ目がチカチカしてしまうので、綺麗に出るというのは結構大切なことだと思います。今後の開発で大いに役に立てたいと思います。
PHPでWunderlist API を叩いてみる
こんにちは~。kzhishuです。
早速なのですが、自分はタスク管理に、Wunderlistを使っています。
タスク登録を一部自動化してみたかったので、APIを調べました。
ドキュメントが簡潔に書かれているため、「英語無理~」という自分のような人間でも比較的簡単にできました。
TwitterとかでAPIに触れたことがあるという方はドキュメントをみてパッパッと料理できてしまうと思います。
ですが、日本語の情報が少ないので、まとめてみます。
なお、Wunderlistのアカウントは既に持っていることを前提に話を進めています。
Wunderlist ドキュメント
APIキーの取得
まずはAPIキーを取得のため、Wunderlistへのアプリーケーション登録が必要です。 「アプリーケーション登録」というと大げさに聞こえますが、アプリケーション名とコールバックURLを登録すればOKです。
Wunderlist開発者用ページ の MY APP > Create New App から以下の情報を入力しました。Name は適当に、App Url と Auth Callback URL を実行環境と合わせれば問題ないと思います。ちなみに、この2項目は、後から変更可能です。
Name wnuderlist util Description 未入力 App Icon 未入力 App Url http://localhost Auth Callback URL http://localhost/wunderlist
SAVEボタンを押した後、「Client Id」「Client Secret」が表示されます。
この2つがAPIでアクセスする際に必要になります。これをメモっておきましょう。
アクセストークンの取得
APIを使用するために、アクセストークンを取得します。 ドキュメントでのリクエスト手順をPHPで記述してみます。 流れは、
1、初回リクエスト(GET)
2、リダイレクト(code値の取得)
3、code値とAPIキー情報をPOSTでリクエスト
4、3のレスポンスでアクセストークンが返ってくる
といった具合。
<?php // 1 設定の定義 $client_id = '/* Client Id */'; $client_secret = '/* Client Secret */'; $redirect_url = '/* Auth Callback URL */'; $state = '/* STATE */'; if(!isset($_GET['code'])){ // 2 初回のアクセス header("Location: https://www.wunderlist.com/oauth/authorize?redirect_uri={$redirect_url}&client_id={$client_id}&state={$state}"); exit(); } else{ // 3 リダイレクト後 アクセストークンの取得 $code = $_GET['code']; $rdstate = $_GET['state']; if($rdstate != $state){ exit('stateの内容が違います'); } $params = array( 'client_id' => $client_id, 'client_secret' => $client_secret, 'code' => $code ); $token_url = 'https://www.wunderlist.com/oauth/access_token'; $context = array( "http" => array( "method" => "POST", "content" => http_build_query($params) ) ); $response = file_get_contents($token_url, false, stream_context_create($context)); $json = json_decode($response); $access_token = $json->access_token; /* 以下タスク取得などの処理が続きます */ } ?>
1 設定の定義
先ほど取得した「Client Id」「Client Secret」と登録した「Auth Callback URL」を変数で定義します。
state についてはCSRF対策のために必要なようです。
リダイレクト時の実行プログラムが正しいかどうかを保障するためと理解しました。「リクエスト前のstate」と「リダイレクト後のstate」が同一であるか調べることでその判定ができそうです。予測しづらい文字列を書いておけばよいかと思います。
2 初回のアクセス
client_id, redirect_url, stateをパラメータに https://www.wunderlist.com/oauth/authorize へGETリクエストしています。
初回の実行はここで終了なので、exit(); しています。
成功するとredirect_url に「code」と「state」の2つパラメータと共にリクエストが帰ってきます。このとき、redirect_url と登録した「Auth Callback URL」が異なる場合は失敗します。リダイレクトかどうかは、GETパラメータ「code」の有無で判断しました。
3 リダイレクト後 アクセストークンの取得
リダイレクトの「state」が正しいか検証した後、「code」の値を取得します。
その値を、client_id, client_secret と共に今度はPOSTで https://www.wunderlist.com/oauth/access_token にリクエストします。
処理には file_get_contents() を使いました。(file_get_contents() ってヘッダーつけたり、POSTでリクエストできるんですね)
レスポンスはJSON形式で返って来るためそれを取得します。これでアクセストークンを取得できました。
APIを叩く(リスト取得とタスク登録)
これでやっと準備が整いました。ここからリストを取得したりタスクを登録したりというAPIを叩いていくのですが、リクエスト時に2つ決まりがあります。
・リクエストヘッダーにアクセストークンとクライアントIDをつける(X-Access-Token、X-Client-ID) ・パラメータはJSON形式で送る (またリクエストヘッダーにも Content-Type: application/json; をつける)
以上を元に取得してみます。プログラムは、上の /* 以下タスク取得などの処理が続きます */
の部分に書いていきます。まずはリストの取得。
$json に自分のWunderlist のリストがしっかりと含まれ無事成功しました。
<?php $header = array( "X-Client-ID: {$client_id}", "X-Access-Token: {$access_token}", "Content-Type: application/json; charset=utf-8", ); $list_url = 'https://a.wunderlist.com/api/v1/lists'; $context = array( "http" => array( "method" => "GET", "header" => implode("\r\n", $header) ) ); $response = file_get_contents($list_url, false, stream_context_create($context)); $json = json_decode($response); ?>
リストが取得できれば、list_id がわかりますので、そのidを指定することで、タスクの登録ができます。一つ注意点として、パラメータを送る際は Content-Length: [文字数] をリクエストヘッダーに含める必要があるようです。これで無事にタスクを登録することができました。
<?php $data = json_encode($params); $header = array( "X-Client-ID: {$client_id}", "X-Access-Token: {$access_token}", "Content-Type: application/json; charset=utf-8", "Content-Length: ".strlen($data) ); $task_url = 'https://a.wunderlist.com/api/v1/tasks'; $params = array( 'list_id' => '/* list_id */', 'title' => 'テスト タスク' ); $context = array( "http" => array( "method" => "POST", "header" => implode("\r\n", $header), "content" => $data ) ); $response = file_get_contents($task_url, false, stream_context_create($context)); $json = json_decode($response); ?>
クラス化してみる
記述したコードを上から眺めていると、同じような処理が重複しており、頭がクラクラしてきますので、クラス化してリスト名を指定してタスクが登録できるようなサンプルコードを書いてみました。インスタンス生成後、URLとリクエストメソッドとパラメータを指定すればAPIを叩けるようになっています。
詳細は以下のGitHubにコードを載せておきました。これでだいぶスッキリです。
Wunderlist API · GitHub