§JSON と HTTP
Play は、JSON ライブラリと組み合わせて HTTP API を使用することによって、コンテンツタイプが JSON の HTTP リクエストとレスポンスをサポートしています。
コントローラ、アクション、ルーティングの詳細については、HTTP プログラミング を参照してください。
エンティティのリストを GET したり、新しいエンティティを作成する POST を受け入れる、簡単な RESTful Web サービスを設計することにより、必要な概念を説明します。このサービスでは、すべてのデータに JSON のコンテンツタイプを使用します。
このサービスで使用するモデルは次のとおりです。
case class Location(lat: Double, long: Double)
case class Place(name: String, location: Location)
object Place {
var list: List[Place] = {
List(
Place(
"Sandleford",
Location(51.377797, -1.318965)
),
Place(
"Watership Down",
Location(51.235685, -1.309197)
)
)
}
def save(place: Place) = {
list = list ::: List(place)
}
}
§JSON によるエンティティのリストの提供
必要なインポートをコントローラに追加することから始めます。
import play.api.mvc._
import play.api.libs.json._
import play.api.libs.functional.syntax._
object Application extends Controller {
}
Action
を記述する前に、モデルを JsValue
表現に変換する仕組みが必要です。これには、暗黙の Writes[Place]
を定義することによって対応します。
implicit val locationWrites: Writes[Location] = (
(JsPath \ "lat").write[Double] and
(JsPath \ "long").write[Double]
)(unlift(Location.unapply))
implicit val placeWrites: Writes[Place] = (
(JsPath \ "name").write[String] and
(JsPath \ "location").write[Location]
)(unlift(Place.unapply))
次に Action
を記述します。
def listPlaces = Action {
val json = Json.toJson(Place.list)
Ok(json)
}
Action
は Place
オブジェクトのリストを取得し、暗黙の Writes[Place]
による Json.toJson
を使って JsValue
に変換し、これを結果のボディとして返します。Play は結果を JSON として認識し、適切な Content-Type
ヘッダとレスポンス用のボディ値を設定します。
最後のステップとして、conf/routes
に Action
用のルートを追加します。
GET /places controllers.Application.listPlaces
ブラウザや HTTP ツールを使ってリクエストを行うことでアクションをテストできます。この例では unix コマンドラインツール cURL を使用しています。
curl --include http://localhost:9000/places
処理結果は以下のようになります。
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 141
[{"name":"Sandleford","location":{"lat":51.377797,"long":-1.318965}},{"name":"Watership Down","location":{"lat":51.235685,"long":-1.309197}}]
§JSON による新しいエンティティインスタンスの作成
この Action
では、JsValue
をモデルに変換するために暗黙の Reads[Place]
を定義する必要があります。
implicit val locationReads: Reads[Location] = (
(JsPath \ "lat").read[Double] and
(JsPath \ "long").read[Double]
)(Location.apply _)
implicit val placeReads: Reads[Place] = (
(JsPath \ "name").read[String] and
(JsPath \ "location").read[Location]
)(Place.apply _)
次に Action
を定義します。
def savePlace = Action(BodyParsers.parse.json) { request =>
val placeResult = request.body.validate[Place]
placeResult.fold(
errors => {
BadRequest(Json.obj("status" ->"KO", "message" -> JsError.toFlatJson(errors)))
},
place => {
Place.save(place)
Ok(Json.obj("status" ->"OK", "message" -> ("Place '"+place.name+"' saved.") ))
}
)
}
この Action
は、リストの場合より複雑です。 注意すべき事項を挙げます。
- この
Action
は、Content-Type
ヘッダがtext-json
やapplication/json
となっているリクエストと、作成するエンティティの JSON 表現を含むボディを期待します。 - リクエストを解析したり、
request.body
をJsValue
として提供する、JSON に特化したBodyParser
を使用します。 - 暗黙の
Reads[Place]
に依存する変換のためにvalidate
メソッドを使いました。 - バリデーションの結果を処理するために、エラーと成功のフローを持つ
fold
を使用しました。このパターンは、フォームの送信 でも使用されているので、よく知られているかもしれません。 Action
は JSON のレスポンスも送信します。
最後に conf/routes
にルートバインディングを追加します。
POST /places controllers.Application.savePlace
成功とエラーのフローを検証するため、このアクションを有効なリクエストと無効なリクエストでテストします。
有効なデータによるアクションのテスト:
curl --include
--request POST
--header "Content-type: application/json"
--data '{"name":"Nuthanger Farm","location":{"lat" : 51.244031,"long" : -1.263224}}'
http://localhost:9000/places
処理結果:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 57
{"status":"OK","message":"Place 'Nuthanger Farm' saved."}
“name” フィールドが存在しない、無効なデータによるアクションのテスト:
curl --include
--request POST
--header "Content-type: application/json"
--data '{"location":{"lat" : 51.244031,"long" : -1.263224}}'
http://localhost:9000/places
処理結果:
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 79
{"status":"KO","message":{"obj.name":[{"msg":"error.path.missing","args":[]}]}}
“lat” のデータ型が正しくない、無効なデータによるアクションのテスト:
curl --include
--request POST
--header "Content-type: application/json"
--data '{"name":"Nuthanger Farm","location":{"lat" : "xxx","long" : -1.263224}}'
http://localhost:9000/places
処理結果:
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 92
{"status":"KO","message":{"obj.location.lat":[{"msg":"error.expected.jsnumber","args":[]}]}}
§まとめ
Play は JSON を用いた REST をサポートするように設計されており、これらのサービスの開発作業はすんなりと進むことでしょう。作業の大部分は、モデルの Reads
と Writes
を記述することになります。詳細については次の章で説明します。
このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。