PCがあれば何でもできる!

へっぽこアラサープログラマーが、覚えたての知識を得意げにお届けします

【CakePHP2.x】data URI Schemeの画像をダウンロードさせる

data URI Schemeはバイナリを文字列でやり取りできるので、色々と便利ですよね!

今回、DBに保存しているdata URIの画像を、ダウンロードできるようにして欲しいという依頼があり、対応してみました。

aタグのdownload属性は、IE11やSafariでは使えない

HTML5で追加されたdownload属性。

<a href="data:image/jpeg;base64,/..." download="file_name.jpg">ダウンロード</a>

こんな感じで、download属性にファイル名を書けば、そのままダウンロードできる仕組み。

すごく簡単で喜んでいたのですが、なんとIE11やSafariでは使えないことが発覚。そんな〜 orz

CakePHP側で対応する

通常サーバー側でファイルをダウンロードさせたい場合、レスポンスヘッダーをいじらなければいけないのですが、CakePHPだとこのように書けます。

<?php
App::uses('AppController', 'Controller');
class SampleController extends AppController {
  
  /* 〜 省略 〜 */

  public function download_image() {
    $dataUriImage = 'data:image/jpeg;base64,/...';

    // data URIのヘッダー部とMIMEの画像種別を取得
    //   - $match[0]: data:image/jpeg;base64
    //   - $match[1]: jpeg
    if (!preg_match('{^data:image/(.+);base64,}i', $dataUriImage, $match)){
      throw new NotFoundException('画像が見つかりませんでした');
    }

    // data URIのデータ部を取得し、base64からデコード
    $image = preg_replace('{^'.$match[0].'}', '', $dataUriImage);
    $image = base64_decode($image);

    // 本文に画像データを出力
    $this->response->body($image);

    // コンテンツタイプ指定(未指定だとSafariでDL時にhtmlの拡張子がついてしまう)
    $this->response->type('image/'.$match[1]);

    // ダウンロードファイル名を設定(拡張子はMIMEから取得)
    // $this->response->download('画像名.' . $match[1]); // これだと日本語がIEで化ける
    // $this->response->download(rawurlencode('画像名.' . $match[1])); // これだと今度はFireFox, Safariでデコードされずに化ける
    header('Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode('画像名.') . $match[1]); // これで全ブラウザOK

    return $this->response;
  }
}

response->bodyとdownloadを設定すれば良いだけです。簡単ですね!

ファイル名の拡張子はとりあえずMIMEの後方から取っているので、通常のjpeg, gif, pngあたりなら大丈夫ですが、きちんとやるなら変換表を作った方が良いかと思います。

もし、data URIではなく、ローカルに保存されているファイルであれば、response->fileを設定すればいいみたいです。
リクエストとレスポンスオブジェクト — CakePHP Cookbook 2.x ドキュメント

2016/03/18 追記

  • SafariでDL時にhtmlの拡張子がついてしまうため、$this->response->typeでコンテンツタイプを指定するようにしました。
  • ブラウザによって画像名に日本語が混じっていると文字化けするため、ファイル名に文字コードの指定と、URLエンコードを噛ませるようにしました。

Webpack+babel-loaderでビルド時にエラー(ERROR in Cannot find module './node_modules/babel-loader/index.js')

Webpack+babel-loaderでES6のソースをビルドしていたらエラーが発生しました。

エラー内容は

ERROR in Cannot find module './node_modules/babel-loader/index.js'

というもの。

エラーの詳細情報や、解決策はこちらに記載されていました。

Cannot find module './node_modules/babel-loader/index.js' · Issue #1260 · webpack/webpack · GitHub

要は、Webpackの設定ファイル(webpack.config.js)のresolve.rootに相対パスを指定すると発生するエラーのようです。

__dirname等を使い、絶対パスを指定すると解決します。

resolve: {
  root: __dirname,
  modulesDirectories: ['node_modules', 'src/js'],
  extensions: ['', '.js'],
}

Wordpress+httpsで、にほんブログ村の新着記事反映に対応する

にほんブログ村での衝撃

最近、Wordpressで新しいサイトを始めたので、試しに老舗ランキングサイトの「にほんブログ村」に登録してみました。

ですが、記事を書いても書いても、にほんブログ村が検知してくれず、全然新着記事としてあがってこない。

設定を見ても間違っていないし、これはおかしいな…と運営に質問してみたのですが、そこで衝撃の事実。

どうやらwordpress.comのドメイン以外、httpsには対応していないらしい…orz

登録している人、みんな素のまま認証してるの!?

いい方法がないか試してみる

にほんブログ村の新着記事の取得方法は至ってシンプル。

  1. RSSのURLをにほんブログ村に登録。

  2. 新着記事投稿後、にほんブログ村にpingを飛ばす。

  3. pingを受けたにほんブログ村が、登録されているRSSから記事を取得。

ということのようです。

そこで、もしかしてRSSがhttpで読めるようになればOKなのかな?と。

実際に試してみたところ、それであっさり新着記事が反映されました。

う〜ん、でもRSS読むのにhttpとhttpsで何が違うんだろうか。非対応な理由が謎すぎる ^^;

RSSだけhttpにした方法

通常、Wordpressは www.example.com/feed/がRSSのURLになるようですが、https対応している場合、httpでアクセスしてもWordpressのsiteurlの設定よって自動でhttpsにリダイレクトしてしまいます。

そこで苦肉の策。

f:id:m_doi2:20160213012413p:plain

こんな感じで、no_sslというディレクトリを作り、feed.phpという新規ファイルを作ります。

feed.phpの内容はこのような感じです。

<?php
$url = 'https://'.$_SERVER['SERVER_NAME'].'/feed/';
$ch = curl_init();
$ch_options = array(
  CURLOPT_URL => $url,
  CURLOPT_HEADER => false,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_TIMEOUT => 60,
  CURLOPT_CUSTOMREQUEST => 'GET',
  // リダイレクトを辿る
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_AUTOREFERER => true,
);
curl_setopt_array($ch, $ch_options);
$response = curl_exec($ch);
curl_close($ch);

header('Content-Type: application/rss+xml');
echo $response;
?>

見ての通り、https://www.example.com/feed/の内容をそのまま表示させているだけです。

デフォルトだとContent-Typeがtext/htmlになってしまうため、rss+xmlで上書きします。

リダイレクトを辿る設定は、念のためのおまじない程度に書いてます。

これで直接的にWordpressと関わらないため、siteurlによるリダイレクトは働きません。

後は、にほんブログ村にhttp://www.example.com/no_ssl/feed.phpをRSSとして登録し、pingを送れば完了です!

※ apache、nginx、.htaccess等、別の所で、http→httpsのリダイレクトを噛ませている場合は、no_sslのディレクトリだけ除外してください。

CentOS6でLet's Encryptを使おうとするとpython2.7が入らずに詰まる

無料でSSL証明書が発行できると話題のLet's Encrypt。
先日、CentOS6のサーバに試しに入れてみようとしたところ、python2.7が入らずに詰まってしまいました。

最初に

OSはCentOS 6.7で、gcc, git, nginx以外ほとんど入っていない状態。

導入の参考にしたのはこちらのサイト。
nginxの設定など細かく書いてくださっているので、非常に参考になります。

光の速さのWEBサーバー(nginx)をlet's encryptでSSL化及びHTTP/2化。ついでにセキュリティ評価をA+にする。 - Qiita

現象

Gitでclone後、letsencrypt-autoを叩くと、必要なものが自動でインストールされるわけなのですが、virtualenvがなくて止まります。

$ git clone https://github.com/letsencrypt/letsencrypt
$ cd letsencrypt/
$ ./letsencrypt-auto --help
Bootstrapping dependencies for RedHat-based OSes...
yum is /usr/bin/yum
Loaded plugins: fastestmirror
Setting up Install Process
Loading mirror speeds from cached hostfile
 * base: www.ftp.ne.jp
 * extras: www.ftp.ne.jp
 * updates: www.ftp.ne.jp
Package python-2.6.6-64.el6.x86_64 already installed and latest version
No package python-virtualenv available.
No package python-pip available.

〜 省略 〜

Creating virtual environment...
./letsencrypt-auto: line 165: virtualenv: command not found

上記の、

Package python-2.6.6-64.el6.x86_64 already installed and latest version
No package python-virtualenv available.
No package python-pip available.

が根本的な原因ですね。

どうやらletsencrypt-autoはpython2.7でないと動かないようですが、CentOS標準のリポジトリではpython2.6までしか落とせず、関連するパッケージについても同様ということのようです。pythonにはあまり詳しくないので、パッケージの詳細については割愛します。

色々調べて、結局このスレッドにたどり着きました。こちらの最後のコメントが解決策になります。
CentOS 6.7 + Python 2.7 errors on letsencrypt-auto run · Issue #1106 · letsencrypt/letsencrypt · GitHub

まとめると、このような感じです。

# SCLをインストール
$ sudo yum install centos-release-SCL

# python2.7 と scl utilsをインストール
$ sudo yum update
$ sudo yum install scl-utils python27 python27-scldevel

# python2.7をbash上で有効化
$ scl enable python27 bash

yum updateはご自身の判断で行ってください。ここまでやってあげると、問題なく動くようになります。

$ ./letsencrypt-auto --help
〜 省略(インストール後、ヘルプが表示される) 〜

SCLはRedHatが提供しているもので、従来のリポジトリにはない新し目のパッケージを、従来のリポジトリと競合しない形でインストールおよび利用できる仕組みのようです。(主に言語系のパッケージ、Ruby, PHPなどなど)

まず、最初のcentos-release-SCLのインストールでyumのリポジトリが追加され、上記で言うとpython27がyumでインストールできるようになる。

そして、scl-utilsをインストールすることで、sclコマンドで簡単にバージョンを切り替えることができるようになる、ということみたいです。

めちゃくちゃ便利ですね!

【CakePHP2.x】ModelのcounterCacheは、負荷が高い上に、複数キーで結合するテーブルに適用できない

counterCacheは、複数キーで結合するテーブルに適用できない

CakePHPのcounterCacheの便利さに甘えてカウントを保存しようとしたところ、うまくいかない事案が発生。

今回の対象のModelは複数キーでの紐付けが必要でした。

イメージとしてはこのような感じです。

<?php
App::uses('AppModel', 'Model');

class Sample1Model extends Model {

  public $belongsTo = array(
    'Sample2' => array(
      'foreignKey' => false,
      'conditions' => array(
        'Sample1.key1 = Sample2.key1',
        'Sample1.key2 = Sample2.key2',
      ),
      'counterCache' => 'sample1_count',
      'counterScope' => array(
        'Sample1.scope1' => true,
        'Sample1.scope2' => true,
      ),
    )
  );
}

よくある、foreignKey = falseconditionsを使って結合したパターンです。

ですがこれ、saveやdelete時にWarningが発生して、カウント処理も動きません。

Warning (2): array_key_exists(): The first argument should be either a string or an integer [CORE/Cake/Model/Model.php, line 2162]
Warning (2): array_key_exists() [function.array-key-exists]: The first argument should be either a string or an integer [CORE/Cake/Model/Model.php, line 2162]

lib/Cake/Model/Model.phpupdateCounterCacheメソッドの処理を追うとわかりますが、結論としてはforeignKeyを元に更新を行うため、文字列でないfalseで怒られる上に、更新するレコードを認識できないという理由でした。

一応、foreignKeyを空白文字列にすれば、Warningは回避できますが、カウントできないという結果は同じです。

counterCacheは、負荷が高い

勝手な思い込みで、counterCacheはModelの更新内容に合わせて、countの増減(プラス1 or マイナス1)をしてくれるものだと思っていました。

しかし、上記の件でソースを追ったところ、毎回全レコードからカウントクエリで取りなおすという、言ってみればシンプルなコードになっていました。

レコード数が少ない場合はズレもなくていいのですが、数百万、億といったレコード数だったり、合わせて更新の頻度が多い場合は到底耐えられるものではありません。

RDBの場合は素直にトリガを利用するのが安心かもしれません。

【Wordpress】Facebook公式プラグインで埋め込んだものを日本語表示にする方法

Wordpressに、Facebookの記事やボタンを埋め込む際は、Facebookの公式プラグインがおすすめです。

導入も簡単。プラグインを有効化した後、ビジュアルの記事編集画面に追加されるボタンを押して、流れに沿って入力していくだけです!

f:id:m_doi2:20151127175826p:plain

ちなみに、このプラグインを使わずに、直接埋め込みコードを貼り付けるという方法もありますが、大量に埋め込む場合はあまりオススメしません。
理由は、Facebookの埋め込みコードの仕様が変わった場合、埋め込んだ全てのコードの書き換えが必要になるためです。 このプラグインなら、記事に記載したショートコードが、記事表示時に自動で埋め込みコードに置き換わるという仕組みなので、Facebookの仕様が変わってもプラグイン側が対応するだけで済むというメリットが有ります。

ただし表示が英語になってしまう

さて、本題ですが実はこのプラグイン、初期では埋め込み後の表示が英語にしか対応していません。

こんな感じ。

f:id:m_doi2:20151127204714p:plain

Likeとか中々おしゃれな感じですが、日本ではあまり馴染みがないですよね。

そこで、表示の日本語化を試みます。

続きを読む

Duet Displayをアップデートしたら、解像度が合わなくなってチラチラするようになった話

iPadがPCのサブディスプレイになる、Duet Displayを皆さんはお使いですか?

Duet Display

Duet Display

  • Rahul Dewan
  • 仕事効率化
  • ¥1,900

よくあるWi-Fi接続ではなくUSB接続なので、遅延も全く感じず中々便利ですよ!

さて、先日のiPad Proのリリースによって、このDuet Displayもバージョンアップしたわけなんですが、アプリを更新したところ変な症状が起きるようになり、まともに使えなくなっていました。

その解決方法がようやくわかったのでご紹介します。

続きを読む