@peccul is peccu

(love peccu '(emacs lisp cat outdoor bicycle mac linux coffee))

CakePHPでREST風APIを作っていて、Content-Typeが変えられなかった

REST風APICakePHP(2.x系)で作ってて、IE9で画像をアップロードした結果のレスポンスがapplication/jsonだと、JavaScriptが受け取れずにダウンロードしようとする。

これを回避した話。

経緯

  • IE9APIに対して非同期に画像をアップロードしたい。

  • FormDataが使えない環境では、iframeにformのDOM構成を作って、ファイルをPOSTする方法がとって対応している模様。

  • IE9ではapplication/jsonが定義されていないのか解釈できず、APIのレスポンスをファイルとしてダウンロードしようとする。

  • IE9の場合はapplication/jsonではなく、text/plainで返せばJavaScriptで処理できる

状況

ここに従って、CakePHPでREST風APIを作っていました。

REST — CakePHP Cookbook 2.x ドキュメント

app/Config/routes.phpでこんな風に指定し、

Router::mapResources('recipes');
Router::parseExtensions('json');

あと、Controller/RecipesController.phpRequestHandlerコンポーネントを指定。

class RecipesController extends AppController {

    public $components = array('RequestHandler');
 ...

この状態でrecipes.jsonにアクセスするとレスポンスのContent-Typeがapplication/jsonとなります。 parseExtensions()RequestHandlerがリクエストの形からフォーマットをJSONと判断します。 レスポンスのビューまでを一括で担当し、JSONとしてレスポンスを返してくれます。

このためJSONでやりとりしながらContent-Typeのみtext/plainにするのはそのままではできませんでした。

解決策

最終的には、拡張子txtでやりとりし、レスポンスの中身はJSONにすることで対応しました。

  • *.txtでのアクセスを許可する
Router::mapResources('recipes');
Router::parseExtensions('json', 'txt');
  • Lib/Cake/Views/Component/JsonViews.phpをapp/Views/TxtViews.phpにコピー

  • クラス名とタイプを変更

...
class TxtViews extends Views{
...
$controller->response->type('txt');
...

これで、呼ぶ方も.txtで呼ばないといけなくなりましたが、なんとかなった状態です。

以下試して反映されなかったもの

JsonViewsが上書きしているので、Content-Typeを変更する手段はことごとくうまくいきませんでした。

header('Content-Type: text/plain; charset=utf8;')
$this->RequestHandler->type('txt');
...