Drupal8でREST APIを使ってみる

(※2016/7/14 追記)
こちらでご紹介しているRESTful web servicesモジュールのDrupal7バージョンは、**極めて危険な脆弱性**が報告されています。
まだセキュリティアップデートが行われていない場合は直ちにアップデートを行ってください。
■RESTWS - Highly critical - Remote code execution - SA-CONTRIB-2016-040
https://www.drupal.org/node/2765567



# はじめに
こちらは、[Drupal Advent Calendar 2015](http://qiita.com/advent-calendar/2015/drupal)の19日目の投稿です。

Drupal8の特長の一つとして、**標準でRestfulなWEBサービスを構築できる**というものがありますが、日本語でまとまった情報がなかなか見つからないので自分で調べながら使ってみました。
試行錯誤しながら書いていますので、間違いやより良い方法などがありましたら、コメントででもご指摘いただければ感謝です。

# Drupal8におけるREST APIの構成
REST(RESTful)の詳細についてはここでは割愛させて頂き、**URLをリソースに見立てて、HTTPメソッドとパラメータでデータを操作するWebサービスの提供形態の一つ**位の漠然とした定義としておきます。
以下、REST APIを使ったリソースへのアクセス、認証、フォーマットの仕様についてまとめました。

## HTTPメソッド/CRUDの対応表
よくあるHTTPメソッドとCRUD処理の対応表をDrupal8のREST API用にまとめてみました。

留意点としては以下の2点。
１．リソース一覧の取得はViewsの**REST export display**で構築します。
　強力なViewsの出力機能をそのまま使うことができるのは非常に魅力的です。
　（もちろん個別のリソースの取得にも使用できます）
２．リソースの更新は**PUT**ではなく**PATCH**で行います。
　必要なところだけを更新してあとはDrupalに任せるということでしょうが、PaaSやレンタルサーバによっては、PATCHメソッドが使用できるか注意が必要でしょう。


| HTTPメソッド | 概要             | CRUD      |パス|HTTPステータスコード(成功時)|
|:------------|:-----------------|:------------|:--------|:-------------|
| GET         | リソース一覧の取得(ViewsのREST export) | READ|(任意のパス)|200 OK|
| GET         | リソースの取得     | READ |/node/{nid} または URLエイリアス |200 OK|
| POST        | リソースの新規登録 | CREATE |/entity/node|201 Created|
| (PUT)       | リソースの更新(使用不可？) | UPDATE |-|-|
| PATCH       | リソースの一部更新 | UPDATE |/node/{nid} または URLエイリアス|204 No Content|
| DELETE      | リソースの削除     | DELETE |/node/{nid} または URLエイリアス|204 No Content|

## 認証プロバイダーとCSRFトークン
###認証プロバイダー
標準でCookieとベーシック認証、モジュールの追加でOAuthを利用できます。
■Cookieを使う
従来のセッションを使った認証を利用します。
/user/login にPOSTリクエストを投げることでログイン状態にします。
リクエストのたびに認証を行った（セッションIDが登録されている）クッキーを送信する必要があります。

■ベーシック認証を使う
DrupalのユーザID/パスワードでベーシック認証を行うことが出来ます。
リクエストにAuthorizationヘッダーを付加して送信します。

■OAuthを使う
この記事では取扱いませんが、[Simple OAuthモジュール(OAuth 2.0)](https://www.drupal.org/project/simple_oauth)、[OAuthモジュール(OAuth 1.0)](https://www.drupal.org/project/oauth)を導入すると利用できるようです。
この辺りもご参照ください。

- [Simple OAuth: token bearer authentication for Drupal 8 | Drupal.org](https://www.drupal.org/node/2632478)
- [OAuth authentication (Drupal 8) | Drupal.org](https://www.drupal.org/node/2110825)

###CSRFトークン
リソースの登録・更新・削除を行う際にリクエストに**X-CSRF-Tokenヘッダー**にCSRFトークンを付与する必要があります。
（ベーシック認証を使用する場合は不要）
**/rest/session/token** にリクエストを送信することで取得できます。

## 対応フォーマット形式
HTTPメソッドごとに以下の種類のフォーマットを利用できます。

- json ( Content-Type: application/json )
- hal_json ( Content-Type: application/hal+json )
- xml ( Content-Type: text/xml )

出入力フォーマットは、GETでは \_format パラメータ、POST/PATCHではHTTPヘッダーのContent-Typeで指定します。
(ex. /node/1?_format=json )

GETでは、もともとAcceptヘッダーでフォーマットを指定する仕様から、途中で変更されたようです。
(参照) [Accept header based routing got replaced by a query parameter](https://www.drupal.org/node/2501221)

# 準備
ここからはDrupal8インストールが完了してからの具体的な手順を追っていきます。
##1.モジュールのインストール
GUIでリソースの各種設定ができる、[REST UIモジュール](https://www.drupal.org/project/restui)をインストールし、ウェブサービスの各モジュールを有効化します。

**ホーム > 管理 > 拡張**
![WS000000.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/3548af6b-ac4c-5290-e266-227c75c4e293.jpeg)

（HALとベーシック認証は使わない場合は無効で問題ないと思います）

##2.REST UIモジュールのREST resourcesを設定


この記事では最初から有効になっている**コンテンツ**のみを使用します。

**ホーム > 管理 > 環境設定 > ウェブサービス > REST**
![WS000010.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/081dd190-e2b8-43d5-53a2-ae49fe18d09e.jpeg)


- 入出力フォーマットはjson, hal+jsonを使用
- 認証プロバイダーはCookie,ベーシック認証を使用 

コンテンツのほかにも、ユーザー、ファイル、タクソノミータームなど様々なリソースが用意されていますので、有効化するだけでいろいろ使えそうです。





##3.役割（Role)とユーザの作成
編集者(editor)ロールを追加し、そのロールを付与したユーザを作成します。

**ホーム > 管理 > ユーザー > 役割 > 役割の追加**
**ホーム > 管理 > ユーザー > ユーザーの追加**
![WS000004.jpg](https://qiita-image-store.s3.amazonaws.com/0/84385/132f9b66-481d-93b4-9330-a20e38065064.jpeg)

##4.権限の設定
この記事では、コンテンツタイプは記事（Article）のみを使用します。

**ホーム > 管理 > ユーザー > 権限**
■Nodeの権限設定
![WS000005.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/663b0504-df69-f041-0a4e-36b282eb5125.jpeg)

- 編集者は記事の投稿と自分の記事を編集・削除ができる

■RESTful Web Serviceの権限設定
![WS000004.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/e776081e-a369-5db3-7759-cef1c1cda6ea.jpeg)

- GETは誰でも可、POST,PATCH,DELETEは編集者、管理者のみ

# 実際にAPIを叩いてみる
ここではHTTPクライアントとして、Chrome拡張の DHC - REST/HTTP API Client を使って検証していきます。

まずは最初の記事(article)をDrupalから普通に登録します。
URLエイリアスを「 /my-1st-article 」としておきます。

**ホーム > コンテンツの追加 > 記事の作成**
![article1.png](https://qiita-image-store.s3.amazonaws.com/0/84385/2ea8b7a2-7d34-c0c2-11dd-4a70dd82f1f5.png)

## 記事の取得(GET)
先ほど登録した記事はノードIDが1なので、パスは「/node/1」となり、出力するフォーマットをHALにする場合は「\_format=hal\_json」のパラメータを追加します。（jsonなら\_format=jsonです）

**/node/1?\_format=hal\_json**
![WS000011.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/3040d5c0-bed8-db37-9ad1-1fcc4f5a20dc.jpeg)

また、URLエイリアスを使用することもできます。

**/my-1st-article?\_format=json**
![WS000013.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/fe1d4704-ebd0-75a0-36d0-6acd10ee98aa.jpeg)


## 認証(cookie)およびCSRFトークンの取得
次に、記事の登録・更新・削除を行っていきますが、ここではCookieを使った場合の手順で進めます。
（HTTPクライアントがCookieを管理してくれるので、スクリーンショットでは省略）

1. ログイン認証(POST送信）
2. CookieでCSRFトークンの取得(GET送信)
3. Cookie+CSRFトークンで POST,PATCH,DELETE送信

■LOGIN認証
「/user/login」にPOSTリクエストを送信します。
送信内容は通常のログインフォームから入力したものと大体同じです。
送信時のHTTPステータスコードに少し注意が必要です。

**/user/login**
![WS000015_2.jpg](https://qiita-image-store.s3.amazonaws.com/0/84385/bb1c1c66-004f-8606-d8ba-bf22e1308f88.jpeg)

- Content-Type: application/x-www-form-urlencoded
- BODY: name={user id}&pass={password)&form_id=user_login_form


(HTTPステータスコード)

- 認証成功: 303 See Other
- ログイン済み: 302 Found
- 認証失敗: 200 OK

■CSRFトークン
「/rest/session/token」にGETリクエストを送信すると、text/plainな文字列を取得できます。（JSONではありません）

**/rest/session/token**
![WS000025.jpg](https://qiita-image-store.s3.amazonaws.com/0/84385/1d2310ec-cdfe-d60b-a170-7085d52a0969.jpeg)



## 記事の登録(POST)
それでは、ログイン後に取得したCSRFトークンを使って記事を登録します。
今回はタイトルと内容をhal+json形式で登録します。

**/entity/node**
![WS000020.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/dc4b0479-a0ea-8500-87cd-611b5536ce7d.jpeg)

- Content-Type: application/application/hal+json
- X-CSRF-Token: 取得したCSRFトークン文字列
- BODY: {"title":[{"value":"My 2nd article"}],"body":[{"value":"Hello REST API!"}],"type":[{"target_id":"article"}],"_links":{"type":{"href":"http:\/\/d8dev.local\/rest\/type\/node\/article"}}}

記事が登録されているかサイトを確認します。
![WS000021.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/f1948f76-fdc8-e0fa-6456-8091ed423c46.jpeg)


## 記事の更新(PATCH)
次に、先ほど登録した記事のタイトルのみを更新しましょう。
先ほど登録した記事のノードIDは2ですので、「/node/2」にPATCHでリクエストを送信します。

**/node/2**
![WS000022.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/b66f00bd-7532-46a1-fc4f-8aabac1f674e.jpeg)


- Content-Type: application/application/hal+json
- X-CSRF-Token: 取得したCSRFトークン文字列
- BODY: {"title":[{"value":"REST APIで更新!"}],"type":[{"target_id":"article"}],"_links":{"type":{"href":"http:\/\/d8dev.local\/rest\/type\/node\/article"}}}

記事が更新されたことを確認します。
![WS000023.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/67e75095-0aea-87d8-bb17-1757c7ea5aa3.jpeg)



## 記事の削除(DELETE)
つぎに登録した記事を削除します。

**/node/2**
![WS000026.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/13eb33a5-6e2c-7561-7637-f604a4a3939f.jpeg)


- X-CSRF-Token: 取得したCSRFトークン文字列

記事が削除されたことを確認します。
![WS000024.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/ac4e62de-d55a-f76f-e523-97b451d6552d.jpeg)


## ベーシック認証を使った記事の登録
ベーシック認証でも記事が登録できるかも試してみましょう。
Authorizationヘッダーに認証情報を付与しますが、使用する開発言語やライブラリなどには大抵便利な仕組みがありますので、そちらを利用してください。

**/entity/node**
![WS000029.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/8ca5d6c4-b805-745b-50e6-2c6dcfe8c4f1.jpeg)

- Content-Type: application/application/hal+json
- Authorization: Basic ～
- BODY: {"title":[{"value":"My 3rd article"}],"body":[{"value":"ベーシック認証で登録!"}],"type":[{"target_id":"article"}],"_links":{"type":{"href":"http:\/\/d8dev.local\/rest\/type\/node\/article"}}}


無事登録できました。
![WS000028.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/dcda1b63-7cdc-9372-a621-a2c9a8cd7828.jpeg)

# 記事一覧の取得（Views REST export）
リソースの一覧を提供する機能として、ViewsのREST出力機能を使います。
（RESTful Web Services/Serializationモジュールが有効であることが前提です）
使い方はすごく簡単で、ビューの基本設定をしたあとに、REST出力を提供するにチェック、RESTのパス(エンドポイント）を設定するだけです。

**ホーム > 管理 > サイト構築 > ビュー > ビューを追加**
![WS000032.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/1608ea18-1a81-529b-cc2f-6c20036e2275.jpeg)

各種出力フォーマットの指定や必要なフィールドのみ出力することもできます。
そのほかにも強力なViewsの各種機能が使えるので、アイデア次第で何でもできそうですね！
![WS000033.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/31ce94f8-2e49-b523-33f0-4ae1fdb100fd.jpeg)


最後に、HTTPクライアントでアクセスできるか確認します。
![WS000034.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/d7cdf222-bb27-8b03-9966-5da5082e3a78.jpeg)


# (時間切れ^^;)郵便番号検索APIを作ってみる
REST exportを使って、WEBサービスでは定番の郵便番号検索APIを作るまでの過程をまとめようとしましたが、あえなく時間切れ（^^;
たいしたことはしてませんが、ニーズがありそうなら別の機会に。
（完成形はこんな感じです）
![WS000009.JPG](https://qiita-image-store.s3.amazonaws.com/0/84385/0a58864f-64d6-92e2-ddbc-22de39a8c1c4.jpeg)

# おわりに
いかがだったでしょうか。
実際使ってみると、REST APIが単なるオプション機能としてではなく、設計思想の段階からDrupalコアに組み込まれているんだなぁということが実感できました。
今後、主要なモジュールがDrupal8に対応されてくると、さらに選択肢の幅が広がってきそうなので楽しみですね！
皆さんもぜひ試してみてください。

# 参考リンク
- [REST: exposing data as RESTful web services | Drupal.org](https://www.drupal.org/documentation/modules/rest)
- [An Introduction to RESTful Web Services in Drupal 8 | Drupalize.Me](https://drupalize.me/blog/201401/introduction-restful-web-services-drupal-8)