JSON with HTTP

本文介绍Play框架如何通过HTTP API和JSON库支持JSON类型的HTTP请求和响应。通过设计一个简单的RESTful Web服务,演示如何处理JSON数据,包括读取、验证和响应。

原文

Play通过HTTP API与JSON库共同支持内容类型为JSON的HTTP请求和应答。

关于 Controllers, Actions, 和routing的详细资料看HTTP Programming

我们将通过设计一个简单的RESTful Web服务来解释必要的概念,这个服务可以GET 到实体集合,并接收POSTs 创建新的实体。并且这个服务将对所有的数据使用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形式提供实体列表

我们将以给我们的Controller添加必须的导入开始:

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 对象列表,使用Json.toJson和我们的隐式Writes[Place]把他们转换成JsValue ,然后再把它作为结果Body返回。Play将会认出JSON结果并相应的为应答设置 Content-Type头和Body值。最后一步是把我们的Action 路由添加到conf/routes:

GET /places controllers.Application.listPlaces

我们可以使用浏览器或HTTP工具生成一个请求测试Action,这个例子使用了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 ,我们将需要定义一个隐式的 Reads[Place] 来把JsValue转换成我们的模型。

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 比我们的列表例子复杂些,一些事情需要注意:

  • 这个Action 要求有一个Content-Type头为text/jsonapplication/json ,body 包含需要创建的实体的JSON形式。
  • 它使用JSON指定的BodyParser ,它将 解析请求 并提供做为JsValuerequest.body
  • 我们使用依赖我们的隐式Reads[Place]的validate 方法进行转换。
  • 为了处理验证结果,我们使用有错误和成功流的fold。这个模式也类似于使用表单提交.
  • Action 也发送JSON应答

Body 解析器可以用Case类,隐式的Reads 或接受函数的方式被输入。因此我们可以省掉更多的工作,让Play自动把Json解析成Case类并在调用我们的Action之前进行[验证]。(https://www.playframework.com/documentation/2.5.x/ScalaJsonCombinators#Validation-with-Reads)

import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._

implicit val locationReads: Reads[Location] = (
(JsPath \ "lat").read[Double](min(-90.0) keepAnd max(90.0)) and
(JsPath \ "long").read[Double](min(-180.0) keepAnd max(180.0))
)(Location.apply _)

implicit val placeReads: Reads[Place] = (
(JsPath \ "name").read[String](minLength[String](2)) and
(JsPath \ "location").read[Location]
)(Place.apply _)

// This helper parses and validates JSON using the implicit `placeReads`
// above, returning errors if the parsed json fails validation.
def validateJson[A : Reads] = BodyParsers.parse.json.validate(
_.validate[A].asEither.left.map(e => BadRequest(JsError.toJson(e)))
)

// if we don't care about validation we could replace `validateJson[Place]`
// with `BodyParsers.parse.json[Place]` to get an unvalidated case class
// in `request.body` instead.
def savePlaceConcise = Action(validateJson[Place]) { request =>
// `request.body` contains a fully validated `Place` instance.
val place = request.body
Place.save(place)
Ok(Json.obj("status" ->"OK", "message" -> ("Place '"+place.name+"' saved.") ))
}

最后,我们将在conf/routes文件里添加一个路由绑定:

POST /places controllers.Application.savePlace

我们将使用有效的和无效的请求测试Action,以验证我们成功和错误流。 使用有效的数据测试Action:

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” 字段的无效数据测试Action:

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”的无效数据测试Action

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是被设计来支持REST使用JSON并且开发这些服务应该是简单的。大部分的工作是给你的模型写ReadsWrites,这将会在下一节中详细介绍。

转载于:https://my.oschina.net/u/587323/blog/967850

### 如何使用HTTP API传输JSON数据 当涉及到通过HTTP接口进行JSON数据传输时,通常会遵循一系列标准实践来确保数据能够被正确解析和处理。下面是一个详细的指南说明如何实现这一点。 #### 发送POST请求并携带JSON负载 为了向服务器发送JSON格式的数据,在构建HTTP POST请求时需设置`Content-Type`头为`application/json`,这表明客户端正在提交的是JSON编码的信息体[^1]。接着要创建一个有效的JSON对象作为请求的有效载荷(payload),该有效载荷应该按照目标API所定义的要求来组织其字段名与值。 ```python import requests import json url = "http://example.com/api/resource" headers = {'Content-type': 'application/json'} data = { "key": "value", "anotherKey": ["item1", "item2"] } response = requests.post(url, headers=headers, data=json.dumps(data)) print(response.status_code) print(response.text) ``` 这段Python代码片段展示了怎样利用`requests`库发起带有JSON主体的POST请求。这里先导入必要的模块,指定URL以及自定义头部信息;随后准备待发送的数据字典,并将其序列化为字符串形式以便于在网络上传输;最后执行实际的网络调用并将返回的状态码及内容打印出来供调试之用。 对于接收方而言,则需要相应地调整应用程序逻辑以接受这种类型的输入参数。一般情况下,Web框架都会提供内置的支持函数帮助开发者轻松获取经过解码后的原始消息实体[^4]。 #### 接收GET请求中的查询参数并通过JSON响应 除了上述提到的方法外,有时也会遇到这样的场景——即从浏览器或其他前端应用那里接收到包含特定键值对组合而成的查询串(GET request query string), 并期望回复一段描述性的JSON文档给对方。此时同样要注意到媒体类型(Media Type)的选择上应保持一致,也就是告诉消费者即将读取的内容确实是预期的应用程序级协议下的资源表述。 ```javascript // 假设这是Node.js环境下的Express路由处理器 app.get('/api/search', (req, res) => { const { q } = req.query; // 获取查询参数q let result; try { // 执行某些业务操作... result = JSON.stringify({ status: 'success', message: `You searched for "${q}"`, results: [] }); res.setHeader('Content-Type', 'application/json'); res.send(result); } catch(error){ console.error(`Error processing search request with term ${q}:`, error); result = JSON.stringify({ status: 'error', message: 'An unexpected error occurred while processing your request.' }); res.status(500).send(result); } }); ``` 此JavaScript例子中展示了一个简单的RESTful风格的服务端点设计模式,它允许外部访问者基于关键词(q parameter)检索信息的同时还提供了错误处理机制确保即使出现问题也能给出恰当反馈。值得注意的是每次成功或失败之后都要记得设定合适的MIME type并且把最终形成的JSON文本流回传回去。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值