本章通过Lift的RestHelper
特点来讨论REST Web服务的配方。有关介绍,请查看Lift Wiki页面和Simply Lift的第5章。
本章的示例代码位于https://github.com/LiftCookbook/cookbook_rest。
DRY网址
解
用于prefix
您的RestHelper
:
package
code.rest
import
net.liftweb.http.rest.RestHelper
import
net.liftweb.http.LiftRules
object
IssuesService
extends
RestHelper
{
def
init
()
:
Unit
=
{
LiftRules
.
statelessDispatch
.
append
(
IssuesService
)
}
serve
(
"issues"
/
"by-state"
prefix
{
case
"open"
::
Nil
XmlGet
_
=>
<
p
>
None
open
</
p
>
case
"closed"
::
Nil
XmlGet
_
=>
<
p
>
None
closed
</
p
>
case
"closed"
::
Nil
XmlDelete
_
=>
<
p
>
All
deleted
</
p
>
})
}
该服务响应/ issues / by-state / open和/ issue / by-state / closed的 URL,我们将常见部分作为一个prefix
。
将其连接到Boot.scala中:
import
code.rest.IssuesService
IssuesService
.
init
()
我们可以用cURL测试服务:
$ curl -H'Content-Type:application / xml' http:// localhost:8080 / issues / by-state / open<?xml version="1.0" encoding="UTF-8"?>
<p>
$ curl -X DELETE -H'Content-Type:application / xml' http:// localhost:8080 / issues / by-state / closedNone
open
</p><?xml version="1.0" encoding="UTF-8"?>
<p>
All
deleted
</p>
讨论
您可以拥有多个serve
块RestHelper
,这有助于您提供REST服务结构。
在这个例子中,我们已经任意决定返回XML,并使用XmlGet
和使用XML请求进行匹配XmlDelete
。XML请求的测试需要text / xml或application / xml的内容类型,或以.xml结尾的路径请求。这就是为什么cURL请求包含带-H
标志的头文件。如果我们没有列出,请求将不符合我们的任何模式,结果将是404响应。
也可以看看
“返回JSON”提供了接受和返回JSON的示例。
缺少文件后缀
解
访问req.path.suffix
恢复后缀。
例如,当处理/download/123.png时,您可以重建 123.png:
package
code.rest
import
net.liftweb.http.rest.RestHelper
import
net.liftweb.http.LiftRules
import
xml.Text
object
Reunite
extends
RestHelper
{
private
def
reunite
(
name
:
String
,
suffix
:
String
)
=
if
(
suffix
.
isEmpty
)
name
else
name
+
"."
+
suffix
serve
{
case
"download"
::
file
::
Nil
Get
req
=>
Text
(
"You requested "
+
reunite
(
file
,
req
.
path
.
suffix
))
}
def
init
()
:
Unit
=
{
LiftRules
.
statelessDispatch
.
append
(
Reunite
)
}
}
我们在下载时进行匹配,而不是file
直接使用该值,我们先通过该reunite
函数传递它以附加后缀(如果有的话)。
使用像cURL这样的命令请求此URL会按预期显示文件名:
$ curl http://127.0.0.1:8080/download/123.png<?xml version="1.0" encoding="UTF-8"?>
You requested
123.png
讨论
当Lift解析请求时,它将请求分成组成部分(例如,将路径转换为a List[String]
)。这包括一些后缀的分离。当您想要根据后缀更改行为时,这对于模式匹配是有利的,但在这种特殊情况下是一个障碍。
只有这些定义的后缀才能LiftRules.explicitlyParsedSuffixes
从文件名中拆分。这包括许多常见的文件后缀(如.png,.atom,.json),还有一些您可能不太熟悉,比如.com。
请注意,如果后缀不在explicitlyParsedSuffixes
,则后缀将为空字符串,name
(在上一个示例中)将是后缀仍然附加的文件名。
根据您的需要,您可以添加一个保护条件来检查文件后缀:
case
"download"
::
file
::
Nil
Get
req
if
req
.
path
.
suffix
==
"png"
=>
Text
(
"You requested PNG file called "
+
file
)
或者不是简单地附加后缀,您可以借此机会进行一些计算,并决定要发回的内容。例如,如果客户端支持WebP图像格式,您可能更愿意发送:
package
code.rest
import
net.liftweb.http.rest.RestHelper
import
net.liftweb.http.LiftRules
import
xml.Text
object
Reunite
extends
RestHelper
{
def
init
()
:
Unit
=
{
LiftRules
.
statelessDispatch
.
append
(
Reunite
)
}
serve
{
case
"negotiate"
::
file
::
Nil
Get
req
=>
val
toSend
=
if
(
req
.
header
(
"Accept"
).
exists
(
_
==
"image/webp"
))
file
+
".webp"
else
file
+
".png"
Text
(
"You requested "
+
file
+
", would send "
+
toSend
)
}
}
调用此服务将Accept
在确定要发送的资源之前检查HTTP 头文件:
$ curl http:// localhost:8080 / negotiate / 123<?xml version="1.0" encoding="UTF-8"?>
你要求123,会发123.png $ curl http:// localhost:8080 / negotiate / 123 -H“Accept:image / webp”<?xml version="1.0" encoding="UTF-8"?>
你要求123,会发123.webp
也可以看看
“电子邮件地址丢失.com”显示如何从中删除项目explicitlyParsedSuffixes
。
源代码HttpHelpers.scala
包含explicitlyParsedSuffixes
列表,这是从URL提升分析的后缀的默认列表。
从电子邮件地址丢失.com
当向REST服务提交电子邮件地址时,在您的REST服务可以处理请求之前,将删除结束于.com的域。
解
修改LiftRules.explicitlyParsedSuffixes
,以便Lift不会更改以.com结尾的URL 。
在Boot.scala中:
import
net.liftweb.util.Helpers
LiftRules
.
explicitlyParsedSuffixes
=
Helpers
.
knownSuffixes
-
"com"
讨论
默认情况下,Lift将从URL中删除文件后缀,以便轻松匹配后缀。需要匹配所有以.xml或.pdf结尾的请求。但是,.com也注册为这些后缀之一,但如果您的URL以电子邮件地址结尾,那么这样做是不方便的。
请注意,这不会影响网址中的电子邮件地址。例如,考虑以下REST服务:
package
code.rest
import
net.liftweb.http.rest.RestHelper
import
net.liftweb.http.LiftRules
import
xml.Text
object
Suffix
extends
RestHelper
{
def
init
()
:
Unit
=
{
LiftRules
.
statelessDispatch
.
append
(
Suffix
)
}
serve
{
case
"email"
::
e
::
"send"
::
Nil
Get
req
=>
Text
(
"In middle: "
+
e
)
case
"email"
::
e
::
Nil
Get
req
=>
Text
(
"At end: "
+
e
)
}
}
init
在Boot.scala中调用此服务方法后,我们可以提出请求并观察问题:
$ curl http:// localhost:8080/email/you@example.com/send<?xml version="1.0" encoding="UTF-8"?>
在中间:you@example.com $ curl http:// localhost:8080/email/you@example.com<?xml version="1.0" encoding="UTF-8"?>
结束:你@的例子
该.COM被当作一个文件后缀,这就是为什么从后缀的列表中删除的解决方案将解决此问题。
请注意,由于其他顶级域名(例如.uk,.nl,.gov)不在explicitlyParsedSuffixes
,所以这些电子邮件地址保持不变。
也可以看看
“遗失文件后缀”更详细地描述了后缀处理。
无法匹配文件后缀
解
确保您匹配的后缀包含在 LiftRules.explicitlyParsedSuffixes
。
举个例子,或许你想在/ reports / URL 匹配任何以.csv结尾的东西:
case
Req
(
"reports"
::
name
::
Nil
,
"csv"
,
GetRequest
)
=>
Text
(
"Here's your CSV report for "
+
name
)
您期待/reports/foo.csv生成“以下是您的foo的CSV报告”,但是您将获得404。
要解决此问题,"csv"
请将其包含为Lift知道从URL分割的文件后缀。在Boot.scala中,调用:
LiftRules
.
explicitlyParsedSuffixes
+=
"csv"
该模式现在将匹配。
讨论
不添加"csv"
到explicitlyParsedSuffixes
,示例URL会匹配:
case
Req
(
"reports"
::
name
::
Nil
,
""
,
GetRequest
)
=>
Text
(
"Here's your CSV report for "
+
name
)
在这里我们匹配no后缀(""
)。在这种情况下,name
将被设置为"foo.csv"
。这是因为Lift仅将文件后缀从URL的尾部分离出来才能注册的文件后缀explicitlyParsedSuffixes
。因为"csv"
不是默认的注册后缀之一,"foo.csv"
不会被拆分。这就是为什么"csv"
在Req
模式匹配的后缀位置不符合请求,但在该位置的空字符串将会。
也可以看看
“遗失文件后缀”更详细地解释了Lift中的后缀删除。
在REST服务中接受二进制数据
解
访问您的REST助手中的请求正文:
package
code.rest
import
net.liftweb.http.rest.RestHelper
import
net.liftweb.http.LiftRules
object
Upload
extends
RestHelper
{
def
init
()
:
Unit
=
{
LiftRules
.
statelessDispatch
.
append
(
Upload
)
}
serve
{
case
"upload"
::
Nil
Post
req
=>
for
{
bodyBytes
<-
req
.
body
}
yield
<
info
>
Received
{
bodyBytes
.
length
}
bytes
</
info
>
}
}
在Boot.scala中将其连接到您的应用程序中:
import
code.rest.Upload
Upload
.
init
()
使用像cURL这样的工具测试此服务:
$ curl -X POST --data-binary“@ dog.jpg”-H'Content-Type:image / jpg' http://127.0.0.1:8080/upload<?xml version="1.0" encoding="UTF-8"?>
<info>
收到1365418字节</info>
讨论
在这个例子中,二进制数据是通过req.body
它访问的,它返回a Box[Array[Byte]]
。我们把它变成一个Box[Elem]
送回客户端。RestHelper
反过来,这意味着XmlResponse
Lift处理。
请注意,Web容器(如Jetty和Tomcat)可能会限制上传的大小。你会认识到这种情况,例如错误java.lang.IllegalStateException: Form too large705784>200000
。检查容器的文档以更改这些限制。
要限制您接受的图像类型,您可以为匹配添加防护条件,但您可能会发现通过将逻辑移动到unapply
对象上的方法来获得更多可读的代码。例如,要限制上传到只是一个JPEG你可以说:
serve
{
case
"jpg"
::
Nil
Post
JPeg
(
req
)
=>
for
{
bodyBytes
<-
req
.
body
}
yield
<
info
>
Jpeg
Received
{
bodyBytes
.
length
}
bytes
</
info
>
}
object
JPeg
{
def
unapply
(
req
:
Req
)
:
Option
[
Req
]
=
req
.
contentType
.
filter
(
_
==
"image/jpg"
).
map
(
_
=>
req
)
}
我们定义了一个调用的提取器JPeg
,Some[Req]
如果上传的内容类型是image / jpg,则返回; 否则,结果将是None
。这在REST模式匹配中用于JPeg(req)
。请注意,unapply
需要返回Option[Req]
,因为这是预期的Post
提取器。
投放视频
问题
您要使用HTML5 video
标签来投放视频。
解
使用a RestHelper
返回StreamingResponse
一个视频块。
一个简单的例子:
import
net.liftweb.http.StreamingResponse
import
net.liftweb.http.rest.RestHelper
object
Streamer
extends
RestHelper
{
serve
{
case
req
@Req
((
"video"
::
id
::
Nil
),
_
,
_
)
=>
val
fis
=
new
FileInputStream
(
new
File
(
id
))
val
size
=
file
.
length
-
1
val
content_type
=
"Content-Type"
->
"video/mp4"
// replace with appropriate format
val
start
=
0L
val
end
=
size
val
headers
=
(
"Connection"
->
"close"
)
::
(
"Transfer-Encoding"
->
"chunked"
)
::
content_type
::
(
"Content-Range"
->
(
"bytes "
+
start
.
toString
+
"-"
+
end
.
toString
+
"/"
+
file
.
length
.
toString
))
::
Nil
()
=>
StreamingResponse
(
data
=
fis
,
onEnd
=
fis
.
close
,
size
,
headers
,
cookies
=
Nil
,
code
=
206
)
}
}
讨论
上述示例不允许客户端在文件中跳过。该Range
头必须以促进这一解析。它指定客户端希望接收的文件的哪一部分。请求标头包含req@Req(urlParams, _, _)
在上一个示例中的模式中。开始和结束可以这样提取:
val
(
start
,
end
)
=
req
.
header
(
"Range"
)
match
{
case
Full
(
r
)
=>
{
(
parseNumber
(
r
.
substring
(
r
.
indexOf
(
"bytes="
)
+
6
)),
{
if
(
r
.
endsWith
(
"-"
))
file
.
length
-
1
else
parseNumber
(
r
.
substring
(
r
.
indexOf
(
"-"
)
+
1
))
}
)
}
case
_
=>
(
0L
,
file
.
length
-
1
)
}
接下来,响应必须跳过客户端希望开始的视频中的位置。这是通过执行以下操作完成的:
val
fis
:
FileInputStream
=
...
// Shown in the first example
fis
.
skip
(
start
)
也可以看看
这个食谱基于Streaming Response wiki文章。
ObeseRabbit是展示此功能的小型应用程序。
“流内容”描述StreamingResponse
和类似类型的响应,如OutputStreamResponse
和InMemoryResponse
。
返回JSON
解
package
code.rest
import
net.liftweb.http.rest.RestHelper
import
net.liftweb.http.LiftRules
import
net.liftweb.json.JsonAST._
import
net.liftweb.json.JsonDSL._
object
QuotationsAPI
extends
RestHelper
{
def
init
()
:
Unit
=
{
LiftRules
.
statelessDispatch
.
append
(
QuotationsAPI
)
}
serve
{
case
"quotation"
::
Nil
JsonGet
req
=>
(
"text"
->
"A beach house isn't just real estate. It's a state of mind."
)
~
(
"by"
->
"Douglas Adams"
)
:
JValue
}
}
将其连接到Boot.scala中:
import
code.rest.QuotationsAPI
QuotationsAPI
.
init
()
运行此示例生成:
$
curl
-H
'Content-type:
text/json'
http://
127.0
.
0.1
:
8080
/quotation
{
"text"
:
"A beach house isn't just real estate. It's a state of mind."
,
"by"
:
"Douglas Adams"
}
讨论
该类型归属在JSON表达式(的端部: JValue
)告诉表达预计类型的编译器 JValue
。这是允许DSL应用的必需条件。如果您正在调用定义为返回的函数,则不需要JValue
。
JSON DSL允许您创建嵌套的结构,列表和您期望的其他一切JSON。
除了DSL之外,您可以使用以下Extraction.decompose
方法从案例类创建JSON :
import
net.liftweb.json.Extraction
import
net.liftweb.json.DefaultFormats
case
class
Quote
(
text
:
String
,
by
:
String
)
val
quote
=
Quote
(
"A beach house isn't just real estate. It's a state of mind."
,
"Douglas Adams"
)
implicit
val
formats
=
DefaultFormats
val
json
:
JValue
=
Extraction
decompose
quote
这也会产生一个JValue
,打印时会是:
{
"text"
:
"
A
beach
house
isn
'
t
just
real
estate.
It
'
s
a
state
of
mind.
"
,
"by"
:
"
Douglas
Adams
"
}
也可以看看
用于lift-json项目的README文件是使用JSON DSL的一个很好的例子。
Google Sitemap
解
创建Google站点地图结构,并将其绑定到Lift中的任何模板。然后将其作为XML内容提供。
首先得有个sitemap.html在你的web应用程序包含有效XML的标记地图文件夹:
<?xml version="1.0" encoding="utf-8" ?>
<urlset
xmlns=
"http://www.sitemaps.org/schemas/sitemap/0.9"
>
<url
data-lift=
"SitemapContent.base"
>
<loc></loc>
<changefreq>
每日</changefreq>
<priority>
1.0</priority>
<lastmod></lastmod>
</url>
<url
data-lift=
"SitemapContent.list"
>
<loc></loc>
<lastmod></lastmod>
</url>
</urlset>
制作一个代码段来填补所需的差距:
package
code.snippet
import
org.joda.time.DateTime
import
net.liftweb.util.CssSel
import
net.liftweb.http.S
import
net.liftweb.util.Helpers._
class
SitemapContent
{
case
class
Post
(
url
:
String
,
date
:
DateTime
)
lazy
val
entries
=
Post
(
"/welcome"
,
new
DateTime
)
::
Post
(
"/about"
,
new
DateTime
)
::
Nil
val
siteLastUdated
=
new
DateTime
def
base
:
CssSel
=
"loc *"
#>
"http://%s/"
.
format
(
S
.
hostName
)
&
"lastmod *"
#>
siteLastUpdated
.
toString
(
"yyyy-MM-dd'T'HH:mm:ss.SSSZZ"
)
def
list
:
CssSel
=
"url *"
#>
entries
.
map
(
post
=>
"loc *"
#>
"http://%s%s"
.
format
(
S
.
hostName
,
post
.
url
)
&
"lastmod *"
#>
post
.
date
.
toString
(
"yyyy-MM-dd'T'HH:mm:ss.SSSZZ"
))
}
在/ sitemap的REST服务中应用模板和代码段:
package
code.rest
import
net.liftweb.http._
import
net.liftweb.http.rest.RestHelper
object
Sitemap
extends
RestHelper
{
serve
{
case
"sitemap"
::
Nil
Get
req
=>
XmlResponse
(
S
.
render
(<
lift
:
embed
what
=
"sitemap"
/>,
req
.
request
).
head
)
}
}
将其连接到Boot.scala中的应用程序,并强制升级以XML格式处理/站点地图:
LiftRules
.
statelessDispatch
.
append
(
code
.
rest
.
Sitemap
)
LiftRules
.
htmlProperties
.
default
.
set
({
request
:
Req
=>
request
.
path
.
partPath
match
{
case
"sitemap"
::
Nil
=>
OldHtmlProperties
(
request
.
userAgent
)
case
_
=>
Html5Properties
(
request
.
userAgent
)
}
})
使用像cURL这样的工具测试此服务:
$ curl http://127.0.0.1:8080/sitemap<?xml version="1.0" encoding="UTF-8"?>
<urlset
xmlns=
"http://www.sitemaps.org/schemas/sitemap/0.9"
>
<url>
<loc>
http://127.0.0.1/</loc>
<changefreq>
daily</changefreq>
<priority>
1.0</priority>
<lastmod>
2013-02-10T19:16:12.433 + 00:00</lastmod>
</url>
<url>
<loc>
http://127.0.0.1/welcome</loc>
<lastmod>
2013-02-10T19:16:12.434 + 00:00</lastmod>
</url><url>
<loc>
http://127.0 .0.1 / about</loc>
<lastmod>
2013-02-10T19:16:12.434 + 00:00</lastmod>
</url>
</urlset>
讨论
您可能会想知道为什么我们在使用常规的HTML模板和代码段时我们已经使用了REST。原因是我们需要XML而不是HTML输出。我们使用相同的机制,但是调用它并将其包装在一个XmlResponse
。
使用Lift的常规代码段机制来呈现此XML是很方便的。但是,尽管我们正在使用XML,但Lift将使用默认的HTML5解析器来解析sitemap.html。解析器的行为遵循HTML5规范,这与您从XML解析器可能期望的行为不同。例如,HTML解析器移动无效位置中的识别HTML标签。为了避免这些情况,我们修改了Boot.scala以使用/ sitemap的XML解析器。
该S.render
方法需要a NodeSeq
和a HTTPRequest
。我们首先通过运行sitemap.html片段来提供; 第二个只是当前的请求。 XmlResponse
需要一个Node
而不是一个NodeSeq
,这就是为什么我们称之为head
响应中只有一个节点,这是我们需要的。
请注意,Google Sitemaps需要日期为ISO 8601格式。内置的java.text.SimpleDateFormat
Java 7之前不支持此格式。如果使用Java 6,则需要org.joda.time.DateTime
像本例中那样使用。
也可以看看
您可以在Google的网站管理员工具网站上找到Sitemap通讯录的说明。
从本机iOS应用程序调用REST服务
解
使用NSURLConnection
,以确保您设置content-type
到application/json
。
例如,假设我们要调用这个服务:
package
code.rest
import
net.liftweb.http.rest.RestHelper
import
net.liftweb.json.JsonDSL._
import
net.liftweb.json.JsonAST._
object
Shouty
extends
RestHelper
{
def
greet
(
name
:
String
)
:
JValue
=
"greeting"
->
(
"HELLO "
+
name
.
toUpperCase
)
serve
{
case
"shout"
::
Nil
JsonPost
json
->
request
=>
for
{
JString
(
name
)
<-
(
json
\\
"name"
).
toOpt
}
yield
greet
(
name
)
}
}
该服务期望具有参数的JSON文章name
,并将其作为JSON对象返回问候语。为了演示来往服务的数据,我们可以在Boot.scala中包含该服务:
LiftRules
.
statelessDispatch
.
append
(
Shouty
)
从命令行调用它:
$
curl
-d
'
{
"name"
:
"World"
}
'
-X
POST
-H
'Content-type:
application/json'
http://
127.0
.
0.1
:
8080
/shout
{
"greeting"
:
"HELLO WORLD"
}
我们可以使用NSURLConnection
以下方式实现POST请求:
static
NSString
*
url
=
@"http://localhost:8080/shout"
;
-
(
void
)
postAction
{
// JSON data:
NSDictionary
*
dic
=
@
{
@"name"
:
@"World"
};
NSData
*
jsonData
=
[
NSJSONSerialization
dataWithJSONObject
:
dic
options
:
0
error
:
nil
];
NSMutableURLRequest
*
request
=
[
NSMutableURLRequest
requestWithURL
:
[
NSURL
URLWithString
:
url
]
cachePolicy:
NSURLRequestUseProtocolCachePolicy
timeoutInterval
:
60.0
];
// Construct HTTP request:
[
request
setHTTPMethod
:
@"POST"
];
[
request
setValue
:
@"application/json"
forHTTPHeaderField
:
@"Content-Type"
];
[
request
setValue
:
[
NSString
stringWithFormat
:
@"%d"
,
[
jsonData
length
]]
forHTTPHeaderField:
@"Content-Length"
];
[
request
setHTTPBody
:
jsonData
];
// Send the request:
NSURLConnection
*
con
=
[[
NSURLConnection
alloc
]
initWithRequest:
request
delegate
:
self
];
}
-
(
void
)
connection:
(
NSURLConnection
*
)
connection
didReceiveResponse:
(
NSURLResponse
*
)
response
{
// Start off with new, empty, response data
self
.
receivedJSONData
=
[
NSMutableData
data
];
}
-
(
void
)
connection:
(
NSURLConnection
*
)
connection
didReceiveData:
(
NSData
*
)
data
{
// append incoming data
[
self
.
receivedJSONData
appendData
:
data
];
}
-
(
void
)
connection:
(
NSURLConnection
*
)
connection
didFailWithError:
(
NSError
*
)
error
{
NSLog
(
@"Error occurred "
);
}
-
(
void
)
connectionDidFinishLoading:
(
NSURLConnection
*
)
connection
{
NSError
*
e
=
nil
;
NSDictionary
*
JSON
=
[
NSJSONSerialization
JSONObjectWithData
:
self
.
receivedJSONData
options:
NSJSONReadingMutableContainers
error
:
&
e
];
NSLog
(
@"Return result: %@"
,
[
JSON
objectForKey
:
@"greeting"
]);
}
很明显,在这个例子中,我们使用了硬编码的值和URL,但这将有望成为您的应用程序的起点。
讨论
有很多方法可以从iOS中执行HTTP POST,并且可能会令人困惑,以确定正确的方法,特别是没有外部库的帮助。上一个示例使用iOS本机API。
另一种方法是使用AFNetworking。这是iOS开发的流行外部库,可以应对许多场景,使用起来很简单:
#import "AFHTTPClient.h"
#import "AFNetworking.h"
#import "JSONKit.h"
static
NSString
*
url
=
@"http://localhost:8080/shout"
;
-
(
void
)
postAction
{
// JSON data:
NSDictionary
*
dic
=
@
{
@"name"
:
@"World"
};
NSData
*
jsonData
=
[
NSJSONSerialization
dataWithJSONObject
:
dic
options
:
0
error
:
nil
];
// Construct HTTP request:
NSMutableURLRequest
*
request
=
[
NSMutableURLRequest
requestWithURL
:
[
NSURL
URLWithString
:
url
]
cachePolicy:
NSURLRequestUseProtocolCachePolicy
timeoutInterval
:
60.0
];
[
request
setHTTPMethod
:
@"POST"
];
[
request
setValue
:
@"application/json"
forHTTPHeaderField
:
@"Content-Type"
];
[
request
setValue
:
[
NSString
stringWithFormat
:
@"%d"
,
[
jsonData
length
]]
forHTTPHeaderField:
@"Content-Length"
];
[
request
setHTTPBody
:
jsonData
];
// Send the request:
AFJSONRequestOperation
*
operation
=
[[
AFJSONRequestOperation
alloc
]
initWithRequest
:
request
];
[
operation
setCompletionBlockWithSuccess
:^
(
AFHTTPRequestOperation
*
operation
,
id
responseObject
)
{
NSString
*
response
=
[
operation
responseString
];
// Use JSONKit to deserialize the response into NSDictionary
NSDictionary
*
deserializedJSON
=
[
response
objectFromJSONString
];
[
deserializedJSON
count
];
// The response object can be a NSDicionary or a NSArray:
if
([
deserializedJSON
count
]
>
0
)
{
NSLog
(
@"Return value: %@"
,[
deserializedJSON
objectForKey
:
@"greeting"
]);
}
else
{
NSArray
*
deserializedJSONArray
=
[
response
objectFromJSONString
];
NSLog
(
@"Return array value: %@"
,
deserializedJSONArray
);
}
}
failure
:^
(
AFHTTPRequestOperation
*
operation
,
NSError
*
error
)
{
NSLog
(
@"Error: %@"
,
error
);
}];
[
operation
start
];
}
该NSURLConnection
方法更为通用,为您提供自己的解决方案,例如使内容类型更具体化。但是,AFNetworking
很受欢迎,你可能更喜欢这条路线。
也可以看看
您可以在Simply Lift中找到“完整的REST示例”,这是您对Lift的呼叫的良好测试平台。