REST
REpresentational State Transfer;代表性状态传输、具象状态传输
REST定义了应该如何正确地使用Web标准,例如HTTP和URI。REST并非标准,而是一种开发 Web 应用的架构风格,可以将其理解为一种设计模式。
REST关键原则
1、为所有“事物”定义ID
含义:
在Web中,代表ID的统一概念是:URI。URI构成了一个全局命名空间,使用URI标识你的关键资源意味着它们获得了一个唯一、全局的ID。
使用URI标识所有值得标识的事物,特别是应用中提供的所有“高级”资源,无论这些资源代表单一数据项、数据项集合、虚拟亦或实际的对象还是计算结果等。
好处:
使用唯一、全局统一的命名规则的好处,既适用于浏览器中的Web应用,也适用于机对机(machine-to-machine,m2m)通信。
2、将所有事物链接在一起
含义:
任何可能的情况下,使用链接指引可以被标识的事物(资源)。
正式描述:“超媒体被当作应用状态引擎(Hypermedia as the engine of application state)”,有时简写为HATEOAS。这个描述的核心是超媒体概念,换句话说:是链接的思想
好处:
超媒体原则还有一个更重要的方面——应用“状态”。简而言之,实际上服务器端为客户端提供一组链接,使客户端能通过链接将应用从一个状态改变为另一个状态。目前,只需要记住:链接是构成动态应用的非常有效的方式.
- <order self="http://example.com/customers/1234">
- <amount>23</amount>
- <product ref="http://example.com/products/4554">
- <customer ref="http://example.com/customers/1234">
- </customer>
- </product>
- </order>
3、使用标准方法
含义:
浏览器知道如何去处理URI的原因在于所有的资源都支持同样的接口,一套同样的方法集合。标准方法集合包含GET、POST\PUT、DELETE、HEAD和OPTIONS
为使客户端程序能与你的资源相互协作,资源应该正确地实现默认的应用协议(HTTP),也就是使用标准的GET、PUT、POST和DELETE方法。
好处:
它使你的应用成为Web的一部分——应用程序为Web变成Internet上最成功的应用所做的贡献,与它添加到Web中的资源数量成比例。采用RESTful方式,一个应用可能会向Web中添加数以百万计的客户URI
统一接口也使得所有理解HTTP应用协议的组件能与你的应用交互。通用客户程序(generic client)就是从中受益的组件的例子,例如curl、wget、代理、缓存、HTTP服务器、网关还有Google、Yahoo!、MSN等等。
4、资源多重表述?
含义:
客户程序如何知道该怎样处理检索到的数据,比如作为GET或者POST请求的结果?原因是,HTTP采取的方式是允许数据处理和操作调用之间关系分离的。
针对不同的需求提供资源多重表述
好处:
如果你为你的资源提供HTML和XML两种表述方式,那这些资源不仅可以被你的应用所用,还可以被任意标准Web浏览器所用
5、无状态通信
含义:
REST要求状态要么被放入资源状态中,要么保存在客户端上。或者换句话说,服务器端不能保持除了单次请求之外的,任何与其通信的客户端的通信状态。
这样做的最直接的理由就是可伸缩性—— 如果服务器需要保持客户端状态,那么大量的客户端交互会严重影响服务器的内存可用空间
好处:
无状态约束使服务器的变化对客户端是不可见的,因为在两次连续的请求中,客户端并不依赖于同一台服务器。
JAX-RS
Java API forRESTful WebServices旨在定义一个统一的规范,使得 Java 程序员可以使用一套固定的接口来开发 REST 应用,避免了依赖于第三方框架。是一个Java编程语言的应用程序接口,支持按照表象化状态转变 (REST)架构风格创建Web服务Web服务。
与传统的 servlet 模型相比,JAX-RS 提供了一种可行的、更为简便、移植性更好的方式来在 Java 内实现 RESTful 服务。使用注释让您能够轻松提供 Java 资源的路径位置并将 Java 方法绑定到 HTTP 请求方法。一种可移植的数据绑定架构提供了一些本机的 Java 类型支持并允许进行序列化/反序列化处理的完全定制。javax.ws.rs.core.Application 子类的扩展以及 web.xml 内的相应清单表明了用最少的部署描述符配置就能进行轻松部署。
JAX-RS 的具体实现由第三方提供,例如 Sun 的参考实现 Jersey、Apache 的 CXF 以及 JBoss 的 RESTEasy。
JAX-RS标注
JAX-RS提供了一些标注将一个资源类,一个POJO类,封装为Web资源。标注包括:
@Path,标注资源类或方法的相对路径
@GET,@PUT,@POST,@DELETE,标注方法是用的HTTP请求的类型,分别对应 4 种 HTTP 方法,用于对资源进行创建、检索、更新和删除的操作。
- 若要创建资源,应该使用 POST 方法;
- 若要检索某个资源,应该使用 GET 方法;
- 若要更改资源状态或对其进行更新,应该使用 PUT 方法;
- 若要删除某个资源,应该使用 DELETE 方法。
@Produces,标注返回的MIME媒体类型
@Consumes,标注可接受请求的MIME媒体类型
@PathParam,@QueryParam,@HeaderParam,@CookieParam,@MatrixParam,@FormParam ,分别标注方法的参数来自于HTTP请求的不同位置,- @PathParam来自于URL的路径,
- @QueryParam来自于URL的查询参数,
- @HeaderParam来自于HTTP请求的头信息,
- @CookieParam来自于HTTP请求的Cookie。
Resource类和Resource方法
Web 资源作为一个 Resource 类来实现,对资源的请求由 Resource 方法来处理。
Resource 类或 Resource 方法被打上了 Path 标注,Path 标注的值是一个相对的 URI 路径,用于对资源进行定位,路径中可以包含任意的正则表达式以匹配资源。和大多数 JAX-RS 标注一样,Path 标注是可继承的,子类或实现类可以继承超类或接口中的 Path 标注。
Resource 类是 POJO,使用 JAX-RS 标注来实现相应的 Web 资源。Resource 类分为根 Resource 类和子 Resource 类,区别在于子 Resource 类没有打在类上的 @Path 标注。
Resource 类的实例方法打上了@Path 标注,则为 Resource 方法或子 Resource 定位器,子 Resource 定位器上没有任何 @GET、@POST、@PUT、@DELETE 或者自定义的 @HttpMethod
- @Path("/")
- public class BookkeepingService {
- ......
- @Path("/person/") //资源方法;若无@POST,则为子资源定位器
- @POST
- @Consumes("application/json")
- public Response createPerson(Person person) { //JSON 格式的请求体被自动映射为实体参数person
- ......
- }
- @Path("/person/")
- @PUT
- @Consumes("application/json")
- public Response updatePerson(Person person) {
- ......
- }
- @Path("/person/{id:\\d+}/") //正则表达式
- @DELETE
- public Response deletePerson(@PathParam("id")
- int id) {
- ......
- }
- @Path("/person/{id:\\d+}/")
- @GET
- @Produces("application/json")
- public Person readPerson(@PathParam("id")
- int id) {
- ......
- }
- @Path("/persons/")
- @GET
- @Produces("application/json")
- public Person[] readAllPersons() { //数组类型的返回值被自动映射为 JSON 格式的响应体——?
- ......
- }
- @Path("/person/{name}/")
- @GET
- @Produces("application/json")
- public Person readPersonByName(@PathParam("name")
- String name) {
- ......
- }
注意:
- Subresources Locators是指一个指定了@Path annotation,但未指定HttpMethod的annotation
- 最好是在一个interface中定义这个标注,然后实现这个interface
Resource方法参数类型、返回类型
Resource 方法合法的 参数类型 包括:- 原生类型 ——在客户端如何发送原生类型?可否发送JSON类?
- 构造函数接收单个字符串参数,或者包含拥有一个static的valueOf(String)方法
- List<T>,Set<T>,SortedSet<T>(T 为以上的 2 种类型)
- 用于映射请求体的实体参数
Resource 方法合法的返回值类型包括:
- void:状态码 204 和空响应体
- Response:Response 的 status 属性指定了状态码,entity 属性映射为响应体
- return Response.status(Status.OK).entity(JsonUtils.toString(result)).build();
- GenericEntity:GenericEntity 的 entity 属性映射为响应体,entity 属性为空则状态码为 204,非空则状态码为 200
- 其它类型:返回的对象实例映射为响应体,实例为空则状态码为 204,非空则状态码为 200
对于错误处理,Resource 方法可以抛出非受控异常 WebApplicationException 或者返回包含了适当的错误码集合的 Response 对象。
内容协商与数据绑定
Web 资源可以有不同的表现形式,服务端与客户端之间需要一种称为内容协商(Content Negotiation)的机制:
作为服务端,Resource 方法的@Produces 标注用于指定响应体的数据格式(MIME 类型),@Consumes 标注用于指定请求体的数据格式;
作为客户端,Accept 请求头用于选择响应体的数据格式,Content-Type 请求头用于标识请求体的数据格式。
——Produces=Accept?Consumes=Content-Type?
服务端:@Produces,@Consumes
- @GET
- @Path(value="/{emailAddress:.+@.+\\.[a-z]+}")
- @Produces(value={"text/xml", "application/json"})
- public ContactInfo getByEmailAddress(@PathParam(value="emailAddress")
- String emailAddress) {
- ...
- }
- @POST
- @Consumes(value={"text/xml", "application/json"})
- public void addContactInfo(ContactInfo contactInfo) {
- ...
- }
客户端AngularJS:Accept,Content-Type
例如AngularJS发送请求:
- $http({method: 'GET', url: '/someUrl'}).
- success(function(data, status, headers, config) {
- // this callback will be called asynchronously
- // when the response is available
- }).
- error(function(data, status, headers, config) {
- // called asynchronously if an error occurs
- // or server returns response with an error status.
- });
AngularJS Setting HTTP Headers
默认的HTTP头:
The $http service will automatically add certain HTTP headers to all requests. These defaultscan be fully configured by accessing the$httpProvider.defaults.headers
configurationobject, which currently contains this default configuration:
$httpProvider.defaults.headers.common
(headers that are common for all requests):Accept: application/json, text/plain, * / *
X-Requested-With: XMLHttpRequest
$httpProvider.defaults.headers.post
: (header defaults for POST requests)Content-Type: application/json
$httpProvider.defaults.headers.put
(header defaults for PUT requests)Content-Type: application/json
如何修改HTTP头:
To add or overwrite these defaults, simply add or remove a property from these configuration objects. To add headers for an HTTP method other than POST or PUT, simply add a new objectwith the lowercased HTTP method name as the key, e.g.$httpProvider.defaults.headers.get['My-Header']='value'
.
Additionally, the defaults can be set at runtime via the $http.defaults
object in the same fashion.
e.g. 修改$httpProvider.defaults.headers
(prevent angular.js $http object from sending X-Requested-With header)
- angular.module('myModule', [])
- .config(['$httpProvider', function($httpProvider) {
- delete $httpProvider.defaults.headers.common["X-Requested-With"]
- }])
e.g. 使用$http.defaults
- $http({
- method: 'POST',
- url: url,
- headers: {'Content-Type': 'application/x-www-form-urlencoded'}, //headers参数可以设置Accept、Content-Type
- }).success(function () {});
Requests与Responses的序列化和反序列化
服务端:实现MessageBodyWriter、MessageBodyReader
JAX-RS 依赖于 MessageBodyReader 和 MessageBodyWriter 的实现来自动完成返回值到响应体的序列化以及请求体到实体参数的反序列化工作,其中,XML 格式的请求/响应数据与 Java 对象的自动绑定依赖于 JAXB 的实现。
用户可以使用 Provider 标注来注册使用自定义的 MessageBodyProvider,如 清单 6 所示,GsonProvider 类使用了 Google Gson 作为 JSON 格式的 MessageBodyProvider 的实现。
- @Provider
- @Produces("application/json")
- @Consumes("application/json")
- public class GsonProvider implements MessageBodyWriter<Object>,
- MessageBodyReader<Object> {
- private final Gson gson;
- public GsonProvider() {
- gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().setDateFormat(
- "yyyy-MM-dd").create();
- }
- public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations,
- MediaType mediaType) {
- return true;
- }
- //MessageBodyReader.readFrom()
- public Object readFrom(Class<Object> type, Type genericType,
- Annotation[] annotations, MediaType mediaType,
- MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
- throws IOException, WebApplicationException {
- return gson.fromJson(new InputStreamReader(entityStream, "UTF-8"), type);
- }
- public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations,
- MediaType mediaType) {
- return true;
- }
- public long getSize(Object obj, Class<?> type, Type genericType,
- Annotation[] annotations, MediaType mediaType) {
- return -1;
- }
- //MessageBodyWriter.writeTo()
- public void writeTo(Object obj, Class<?> type, Type genericType,
- Annotation[] annotations, MediaType mediaType,
- MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
- throws IOException, WebApplicationException {
- entityStream.write(gson.toJson(obj, type).getBytes("UTF-8"));
- }
- }
配置完成后,如何让它生效呢?——可以通过扩展 javax.ws.rs.core.Application 类实现
- public class ContactInfoApplicaiton extends Application {
- public Set<Class<?>> getClasses() {
- Set<Class<?>> classes = new HashSetSet<Class<?>>();
- classes.add(ContactsResource.class);
- classes.add(ContactInfoWriter.class);
- classes.add(ContactInfoReader.class);
- }
- public SetSet<Object<?>> getSingletons() {
- // nothing to do, no singletons
- }
- }
- <web-app id="WebApp_ID" version="2.5">
- <servlet>
- <servlet-name>ContactInfoServlet</servlet-name>
- <servlet-class>com.sample.RESTSystemServlet</servlet-class>
- <init-param>
- <param-name>javax.ws.rs.Application</param-name>
- <param-value>
- com.ibm.jaxrs.sample.organization.ContactInfoApplication
- </param-value>
- </init-param>
- </servlet>
- <servlet-mapping>
- <servlet-name>ContactInfoServlet</servlet-name>
- <url-pattern>/*</url-pattern>
- </servlet-mapping>
- </web-app>
客户端:$httpProvider.defaults.transformRequest
/Response
AngularJS Transforming Requests and Responses
Both requests and responses can be transformed using transform functions. By default, Angularapplies these transformations:
Request transformations:
- If the
data
property of the request configuration object contains an object,serialize it intoJSON format.
Response transformations:
- If XSRF prefix is detected, strip it (see Security Considerations section below).
- If JSON response is detected, deserialize it using a JSON parser.
To globally augment or override the default transforms, modify the$httpProvider.defaults.transformRequest
and$httpProvider.defaults.transformResponse
properties. These properties are by default an array of transform functions, which allows you topush
or unshift
a new transformation function into the transformation chain. You can also decide to completely override any default transformations by assigning yourtransformation functions to these properties directly without the array wrapper.
Similarly, to locally override the request/response transforms, augment thetransformRequest
and/ortransformResponse
properties of the configuration object passed into$http
.
- $http({
- method: 'POST',
- url: url,
- headers: {'Content-Type': 'application/x-www-form-urlencoded'},
- transformRequest: function(obj) {
- var str = [];
- for(var p in obj)
- str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
- return str.join("&");
- },
- data: xsrf
- }).success(function () {});
e.g. Global配置:$httpProvider.defaults.transformRequest
- var module = angular.module('myApp');
- module.config(function ($httpProvider) {
- $httpProvider.defaults.transformRequest = function(data){
- if (data === undefined) {
- return data;
- }
- return $.param(data);
- }
- });
RESTEasy
RESTEasy是JBoss提供的JAX-RS 的具体实现。、
web.xml:
- <?xml version="1.0" encoding="UTF-8"?>
- <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" >
- <context-param>
- <param-name>resteasy.providers</param-name>
- <param-value>
- org.jboss.resteasy.plugins.providers.DefaultTextPlain,
- org.jboss.resteasy.plugins.providers.ByteArrayProvider,
- org.jboss.resteasy.plugins.providers.InputStreamProvider,
- org.jboss.resteasy.plugins.providers.ByteArrayProvider,
- org.jboss.resteasy.plugins.providers.StringTextStar,
- org.jboss.resteasy.plugins.providers.FormUrlEncodedProvider
- </param-value>
- </context-param>
- <context-param>
- <param-name>resteasy.scan</param-name>
- <param-value>true</param-value>
- </context-param>
- <context-param>
- <param-name>resteasy.servlet.mapping.prefix</param-name>
- <param-value>/web</param-value>
- </context-param>
- <listener>
- <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
- </listener>
- <servlet>
- <servlet-name>Resteasy</servlet-name>
- <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>Resteasy</servlet-name>
- <url-pattern>/web/*</url-pattern>
- </servlet-mapping>
- </web-app>
参考资料
http://liugang594.iteye.com/category/218423
http://www.infoq.com/cn/articles/rest-introduction
http://www.ibm.com/developerworks/cn/web/wa-jaxrs/ 用 Java 技术创建 RESTful Web 服务
http://www.ibm.com/developerworks/cn/java/j-lo-jaxrs/ 使用 JAX-RS 简化 REST 应用开发
http://www.ibm.com/developerworks/cn/webservices/ws-restful/ 基于 REST 的 Web 服务:基础