技术开发 频道

精通 Grails: RESTful Grails

  内容协商与 Accept 报头

  创建一个返回数据的 HTML 和 XML 表示的单独闭包是很简单的,但如果想创建一个既可以返回 HTML 又可以返回 XML 表示的闭包的话,该怎么办呢。这也是可以实现的,这要多亏在 HTTP 请求中包含有 Accept 报头。这个简单的元数据告诉服务器:“嗨,您对这个 URI 中的资源可能有不只一个资源表示 — 我更喜欢这个。”

  cURL 是一个方便的开源命令行 HTTP 工具(参见 参考资料)。在命令行输入 curl http://localhost:9090/trip/airport/list ,以此来模拟请求机场列表的浏览器请求。您应该会看到 HTML 响应展现在您的荧屏上。

  现在,对请求做两处小小的变动。这回,代替 GET 发出一个 HEAD 请求。HEAD 是一个标准 HTTP 方法,它仅仅返回响应的元数据,而不返回正文(您现在正在进行的调试的类型包含在 HTTP 规范中)。另外,将 cURL 放置于 verbose 模式,这样您就也能够看到请求元数据了,如清单 3 所示:

  清单 3. 使用 cURL 来调试 HTTP

$ curl --request HEAD --verbose http://localhost:9090/trip/airport/list
* About to connect() to localhost port 9090 (#0)
*   Trying ::1... connected
* Connected to localhost (::1) port 9090 (#0)
> HEAD /trip/airport/list HTTP/1.1
> User-Agent: curl/7.16.3 (powerpc-apple-darwin9.0)
        libcurl
/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
> Host: localhost:9090
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Language: en-US
< Content-Type: text/html; charset=utf-8
< Content-Length: 0
< Server: Jetty(6.1.4)
<
* Connection #0 to host localhost left intact
* Closing connection #0

  注意请求中的 Accept 报头。客户机要是提交 */* 的话,就意味着:“返回什么样的格式都无所谓。我将接受任何内容。”

  cURL 允许您使用这个值来覆盖 --header 参数。输入 curl --request HEAD --verbose --header Accept:text/xml http://localhost:9090/trip/airport/list,并验证 Accept 报头正在请求 text/xml。这就是资源的 MIME 类型了。

  那么,Grails 是如何响应服务器端的 Accept 报头的呢?再向 AirportController 添加一个闭包,如清单 4 所示:

  清单 4. debugAccept 操作

def debugAccept = {
  def clientRequest
= request.getHeader("accept")
  def serverResponse
= request.format
  render
"Client: ${clientRequest}\nServer: ${serverResponse}\n"    
}

  清单 4 中的第一行从请求中检索出了 Accept 报头。第二行展示了 Grails 如何转换请求和它将要发回的响应。

  现在,使用 cURL 来做相同的搜索,如清单 5 所示:

  清单 5. 调试 cURL 中的 Accept 报头

$ curl  http://localhost:9090/trip/airport/debugAccept
Client:
*/*
Server: all

$ curl  
--header Accept:text/xml http://localhost:9090/trip/airport/debugAccept
Client: text
/xml
Server: xml

  all 和 xml 值是哪来的呢?看一下 grails-app/conf/Config.groovy。在文件顶部,您应该看到了一个散列映射,它对所有的键都使用了简单名称(像 all 和 xml 这样的名称),而且所有的值都使用了与之对应的 MIME 类型。清单 6 展示了 grails.mime.types 散列映射:

  清单 6. Config.groovy 中的 grails.mime.types 散列

grails.mime.types = [ html: ['text/html','application/xhtml+xml'],
                      xml: ['text/xml', 'application/xml'],
                      text: 'text-plain',
                      js: 'text/javascript',
                      rss: 'application/rss+xml',
                      atom: 'application/atom+xml',
                      css: 'text/css',
                      csv: 'text/csv',
                      all: '*/*',
                      json: ['application/json','text/json'],
                      form: 'application/x-www-form-urlencoded',
                      multipartForm: 'multipart/form-data'
                    ]

  那么,现在您应该对内容协商有了更多的了解了,您可以将 withFormat 块添加到 list 操作,以此来依据请求中的 Accept 报头返回合适的数据类型,如清单 7 所示:

  清单 7. 在一个操作中使用 withFormat 块

def list = {
  
if(!params.max) params.max = 10
  def list
= Airport.list(params)
  withFormat{
    html{
      return [airportList:list]
    }
    xml{
      render list
as XML
    }
  }
}

  每一个块的最后一行一定会是一个 render、return 或者 redirect — 与普通操作没什么不同。如果 Accept 报头变成 “all”(*/*)的话,则会使用块中的第一个条目。

  改变 cURL 中的 Accept 报头是不错,但是通过改变 URI 您还可以作一些测试工作。http://localhost:8080/trip/airport/list.xml 和 http://localhost:8080/trip/airport/list?format=xml 都可以用来显式地覆盖 Accept 报头。随便试一下 cURL 和各种 URI 值,确保 withFormat 块能发挥预期作用。

  如果想让这个行为成为 Grails 中的标准的话,不要忘记您可以输入 grails install-templates,并在 /src/templates 中编辑文件。

  所有的基本构建块就位之后,最后一步就是将 GETful 接口转化成一个真正的 RESTful 接口。

0
相关文章