Redmine
angular
CORS

Redmine APIでPOST/PUTリクエストメソッドを送信できない時の(強引な)対処方法

Redmineが好き過ぎてRedmineを題材にしたファンタジー小説書いてる8amjpです。

Redmine API

さて、Redmineは素晴らしいツールなんですが、画面がちょっと無機質で、初心者には取っ付きにくい……という印象があります。
そこで私は、もう少し取っ付きやすい画面を提供するため、Angularを使ってRedmine API経由でチケットの情報にアクセスするWebクライアントアプリを作ろうと考えました。
このRedmine API、とっても便利なんですよ。AngularのHTTPモジュールからGETメソッドでアクセスすれば、チケットの情報をJSONやXMLの形式で自由自在に取得できます。
さらには、POSTメソッドやPUTメソッドを使えば、チケットの作成や更新も簡単に……

……できないんですよ。
Redmine APIに、Chrome等のWebブラウザからPOST/PUTメソッドでリクエストを送信すると、必ずエラーになります。
えー。これじゃ読み取り専用じゃないですか。なんで?

なぜエラーになるのか

勉強してみましたよ。もう。

えーと、Redmine APIにアクセスする時など、別ドメインへのリクエストは、セキュリティ上の理由で厳しいルールが課せられます。
そのルールを規定したのがCross-Origin Resource Sharing、略してCORSと呼ばれるものです。
で、「シンプルではない」リクエスト(PUT等)の送信時は、安全性を確認するため、事前にOPTIONSメソッドでリクエストを送信し、正常な応答があれば続けてPUTメソッドを送信します。
これをプリフライトリクエストといって…………

と、頭の痛くなるような(でもセキュリティ上とても大事な)ルールに縛られながら、リクエストを送るわけなんですが。ここで大問題が発生です。

まず、ChromeやFirefoxといった主要なWebブラウザは、必ずプリフライトリクエストを送信します。仕様です。簡単にはオフにできません。
で、Redmine APIでは、このプリフライトリクエストに応答する術がありません。必ず404エラーを返します。
結果、POSTもPUTもできません。

どうせえっちゅうんだよーー!!
インターネットに公開されたサーバーでならわかるけどよー! イントラの内部でくらい自由にやらせてくれよーー!!
畜生めー!! ちくしょうめーーー!!!

解決方法

というわけで、Apacheにこの怒りをぶつけてやりましょう。
/apps/redmine/conf/ディレクトリにあるhttpd-app.confの末尾に、下記を追記します。

Header always set Access-Control-Allow-Origin "*" 
Header always set Access-Control-Allow-Methods "OPTIONS, PUT" 
Header always set Access-Control-Max-Age "60" 
Header always set Access-Control-Allow-Headers "Content-Type, X-Redmine-API-Key" 
RewriteEngine On 
RewriteCond %{REQUEST_METHOD} OPTIONS 
RewriteRule ^(.*)$ $1 [R=200,L] 

これでPOSTもPUTも送信し放題。いやー、嬉しくて何度POST/PUTしたことやら。

詳しいことはさっきのMDNのページに書いてありますが、一応さらりと解説しておきますね。

1行目は、どのドメインからのアクセスを許可するかを指定します。
ワイルドカードを指定すればすべてのアクセスを受け付けますが、セキュリティの事を考えるとちゃんと指定すべきです。わかっちゃいるけど。
2行目は、どのメソッドを許可するかを指定します。GET/HEAD/POSTメソッドは既に許可されてるっぽいので、OPTIONSとPUTメソッドを追加で指定します。
3行目は、プリフライトリクエストの有効期間です。とりあえず60秒にしました。深い根拠はありません。
4行目は、どのリクエストヘッダを許可するかを指定します。Redmine APIでは「Content-Type」と「X-Redmine-API-Key」ヘッダは必須なので、それを指定しています。
5-7行目は、OPTIONSメソッドのリクエストがあったらオウム返しにステータスコード200を返します。ゆるゆる門番。

これで、長らく頭を悩ませていた問題Issueが、力技とは言えやっと解決Resolvedになりました。良かった良かった。
ま、解決できたのは、本家Redmineサイトのフォーラム内のこの記事のおかげなんですけどね。ありがとうございます。

おわりに

「わざわざこんな事しなくても、こうすれば簡単にPOST/PUTできるよー」などという情報を御存知でしたらぜひ教えてください。