JAX-RS入门 十二: 可伸缩的JAX-RS应用
2013-12-16 22:05
302 查看
一、HTTP Caching
HTTP1.0中定义了Expires来指定cache在浏览器的生命时长。例如:
Html代码
HTTP/1.1 200 OK
Content-Type: application/xml
Expires: Tue, 15 May 2011 16:00 GMT
<Customer id="1">...</Customers>
缓存的数据直到2011年5月15有效。
在JAX-RS中使用javax.ws.rs.core.Response对象来设置有效期:
Java代码
@Path("{id}")
@GET
@Produces("application/xml")
public Response getCustomer(@PathParam("id") int id) {
Customer cust = findCustomer(id);
ResponseBuilder builder = Response.ok(cust, "application/xml");
Date date = Calendar.getInstance().set(2010, 5, 15, 16, 0);
builder.expires(date);
return builder.build();
}
HTTP1.1中重新设计了缓存语法。通过使用Cache-Control来对cache进行控制,其中的值以逗号分隔。它包括:
private
用于指定当且仅有Client才能缓存这个数据
public
指定在 请求/响应 链中的任何环节都可以缓存数据
no-cache
这个指令用于指示数据不应该缓存或者除非数据被重新验证过,否则不能被用于再次请求
no-store
通过缓存数据都被缓存了硬盘中,以使用得下次可以;这个指令表示不要将缓存存在本地。
no-transform
有时缓存会被自动进行转换以节省内存或者带宽,例如压缩图像。no-transform用于指定不允许进行数据转换
max-age
max-age用于指示缓存的有效期;如果max-age和Expires同时指定,则max-age有效
s-maxage
用于指定缓存在一个共享的,中间节点的最大生命期。
例如:
Java代码
HTTP/1.1 200 OK
Content-Type: application/xml
Cache-Control: private, no-store, max-age=300
<customers>...</customers>
与Cache-Control对应的,JAX-RS提供了javax.ws.rs.core.CacheControl类来表示Cache-Control头:
Java代码
public class CacheControl {
public CacheControl() {...}
public static CacheControl valueOf(String value)
throws IllegalArgumentException {...}
public boolean isMustRevalidate() {...}
public void setMustRevalidate(boolean mustRevalidate) {...}
public boolean isProxyRevalidate() {...}
public void setProxyRevalidate(boolean proxyRevalidate) {...}
public int getMaxAge() {...}
public void setMaxAge(int maxAge) {...}
public int getSMaxAge() {...}
public void setSMaxAge(int sMaxAge) {...}
public List<String> getNoCacheFields() {...}
public void setNoCache(boolean noCache) {...}
public boolean isNoCache() {...}
public boolean isPrivate() {...}
public List<String> getPrivateFields() {...}
public void setPrivate(boolean _private) {...}
public boolean isNoTransform() {...}
public void setNoTransform(boolean noTransform) {...}
public boolean isNoStore() {...}
public void setNoStore(boolean noStore) {...}
public Map<String, String> getCacheExtension() {...}
}
在ResponseBuilder类中调用callControl()设置返回的Cache-Control:
Java代码
@Path("{id}")
@GET
@Produces("application/xml")
public Response getCustomer(@PathParam("id") int id) {
Customer cust = findCustomer(id);
CacheControl cc = new CacheControl();
cc.setMaxAge(300);
cc.setPrivate(true);
cc.setNoStore(true);
ResponseBuilder builder = Response.ok(cust, "application/xml");
builder.cacheControl(cc);
return builder.build();
}
二、重验证和条件GET
另一个和cache有关的就是当cache变得陈旧的时候,缓存端能够询问服务端缓存的数据是否仍然有效,这叫做revalidation。为了执行revalidation,客户端需要从服务器端取得一些缓存数据的额外的信息,服务端在最初的响应里会发回一个Last-Modified与/或ETag头。
* Last-Modified
Last-Modified头表示服务端发回数据的一个时间戳。例如:
Java代码
HTTP/1.1 200 OK
Content-Type: application/xml
Cache-Control: max-age=1000
Last-Modified: Tue, 15 May 2009 09:56 EST
<customer id = "1">..</customer>
以上,最初的响应的有效期是1000秒,并且时间戳是2009,5,15,9点56。如果客户端支持重新验证,它会存储这个时候戳到缓存数据中。1000稍以后,客户端可以选择重新验证缓存。要重新验证,它需要发送一个条件(conditional)GET请求,并且传递一个If-Modified-Since头信息,If-Modified-Since的值即是Last-Modified的值。例如:
Java代码
GET /customers/123 HTTP/1.1
If-Modified-Since: Tue, 15 May 2009 09:56 EST
然后服务器会判断数据是否有变化,如果有,则返回一个200, 'OK'的响应,并发回新的响应体;如果没有改变,则返回304, 'Not Modified',和一个空的响应体。这两种情况下都需要发送新的Cache-Control和Last-Modified头。
* ETag
ETag是用来表示数据版本的、假设唯一的某个标识。它的值是任一一个以引用括起的字符串,通常是MD5 哈希值。 例如:
Html代码
HTTP/1.1 200 OK
Content-Type: application/xml
Cache-Control: max-age=1000
ETag: "3141271342554322343200"
<customer id="123">...</customer>
类似于Last-Modified头,如果客户端缓存了这个响应体,则也应该缓存ETag的值。1000秒以后,客户端需要执行一个重验证请求,其中包含一个If-None-Match的请求头:
Html代码
GET /customers/123 HTTP/1.1
If-None-Match: "3141271342554322343200"
当服务器端收到这个GET请求时,它就会试图比较当前resource的ETag值和传入的If-None-Match的值,如果不成功,则返回200, 'OK'和一个新的响应体;否则返回304和'Not Modified’和一个空的响应体。
最后关于ETag有两种类型:strong和weak:
一个strong的ETag:资源的任何变化都会引起ETag的变化
一个weak的ETag:只有资源显著的变化才变化
例如,一个weak的ETag:
Java代码
HTTP/1.1 200 OK
Content-Type: application/xml
Cache-Control: max-age=1000
ETag: W/"3141271342554322343200"
<customer id="123">...</customer>
对应于ETag的JAX-RS类是javax.ws.rs.core.EntityTag:
Java代码
public class EntityTag {
public EntityTag(String value) {...}
public EntityTag(String value, boolean weak) {...}
public static EntityTag valueOf(String value)
throws IllegalArgumentException {...}
public boolean isWeak() {...}
public String getValue() {...}
}
* JAX-RS和条件GET
为了计算上面的验证条件,JAX-RS提供了一个帮助类javax.ws.rs.core.Request:
Java代码
public interface Request {
...
ResponseBuilder evaluatePreconditions(EntityTag eTag);
ResponseBuilder evaluatePreconditions(Date lastModified);
ResponseBuilder evaluatePreconditions(Date lastModified, EntityTag eTag);
}
重载的evaludatePreconditions()方法需要一个javax.ws.rs.core.EntityTag和/或java.util.Date对象,表达ETag和最后修改时间(Last-Modified)。这些值是最新的、当前的,然后把它们同传入的请求参数If-Not-Modified和If-None-Match对象。如果不匹配则返回200,'OK'和响应体,或者返回304,'Not Modified'和空响应体:
Java代码
@Path("/customers")
public class CustomerResource {
@Path("{id}")
@GET
@Produces("application/xml")
public Response getCustomer(@PathParam("id") int id,
@Context Request request) {
Customer cust = findCustomer(id);
EntityTag tag = new EntityTag(
Integer.toString(cust.hashCode()));
CacheControl cc = new CacheControl();
cc.setMaxAge(1000);
ResponseBuilder builder = request.evaluatePreconditions(tag);
if (builder != null) {
builder.cacheControl(cc);
return builder.build();
}
// Preconditions not met!
builder = Response.ok(cust, "application/xml");
builder.cacheControl(cc);
builder.tag(tag);
return builder.build();
}
}
三、并发(Concurrency)
当需要更新Server上的某个数据时,通常就需要面对并发的问题了,因为很有可能有多个人同时在更新同一个信息。
HTTP规范通过使用有条件的PUT或POST请求来解决这个问题,有点类似于有条件GET请求。首先客户端取得数据:
Html代码
HTTP/1.1 200 OK
Content-Type: application/xml
Cache-Control: max-age=1000
ETag: "3141271342554322343200"
Last-Modified: Tue, 15 May 2009 09:56 EST
<customer id="123">...</customer>
为了带条件的更新,我们需要用到ETag或Last-Modified头,这些信息用于告诉服务端我们需要更新的哪个版本。当我们做PUT或POST请求时,这些信息会随同If-Match或If-Unmodified-Since一同发送出去,其中If-Match信息的值是ETag;If-Unmodified-Since的值是Last-Modified。因此上面GET对应的更新请求可能是:
Html代码
PUT /customers/123 HTTP/1.1
If-Match: "3141271342554322343200"
If-Unmodified-Since: Tue, 15 May 2009 09:56 EST
Content-Type: application/xml
<customer id="123">...</customer>
可以发送If-Match或If-Unmodified-Since中的任何一个。当Server收到这个请求时,就会去检查当前的ETag是否匹配If-Match或当前的时间戳是否匹配If-Unmodified-Since头。如果这些条件都不满足,则返回412, 'Precondition Failed'响应,用于告诉客户端当前数据已经被修改过,请重试;如果条件满足,则执行更新,并返回成功的结果。
* JAX-RS和条件更新
和读取数据一样,还是要用到Request.evaluatePreconditions()方法,例如:
Java代码
@Path("/customers")
public class CustomerResource {
@Path("{id}")
@PUT
@Consumes("application/xml")
public Response updateCustomer(@PathParam("id") int id,
@Context Request request) {
Customer cust = findCustomer(id);
EntityTag tag = new EntityTag(
Integer.toString(cust.hashCode()));
Date timestamp = ...; // get the timestampe
ResponseBuilder builder =
request.evaluatePreconditions(timestamp, tag);
if (builder != null) {
// Preconditions not met!
return builder.build();
}
//... perform the update ...
builder = Response.noContent();
return builder.build();
}
}
HTTP1.0中定义了Expires来指定cache在浏览器的生命时长。例如:
Html代码
HTTP/1.1 200 OK
Content-Type: application/xml
Expires: Tue, 15 May 2011 16:00 GMT
<Customer id="1">...</Customers>
缓存的数据直到2011年5月15有效。
在JAX-RS中使用javax.ws.rs.core.Response对象来设置有效期:
Java代码
@Path("{id}")
@GET
@Produces("application/xml")
public Response getCustomer(@PathParam("id") int id) {
Customer cust = findCustomer(id);
ResponseBuilder builder = Response.ok(cust, "application/xml");
Date date = Calendar.getInstance().set(2010, 5, 15, 16, 0);
builder.expires(date);
return builder.build();
}
HTTP1.1中重新设计了缓存语法。通过使用Cache-Control来对cache进行控制,其中的值以逗号分隔。它包括:
private
用于指定当且仅有Client才能缓存这个数据
public
指定在 请求/响应 链中的任何环节都可以缓存数据
no-cache
这个指令用于指示数据不应该缓存或者除非数据被重新验证过,否则不能被用于再次请求
no-store
通过缓存数据都被缓存了硬盘中,以使用得下次可以;这个指令表示不要将缓存存在本地。
no-transform
有时缓存会被自动进行转换以节省内存或者带宽,例如压缩图像。no-transform用于指定不允许进行数据转换
max-age
max-age用于指示缓存的有效期;如果max-age和Expires同时指定,则max-age有效
s-maxage
用于指定缓存在一个共享的,中间节点的最大生命期。
例如:
Java代码
HTTP/1.1 200 OK
Content-Type: application/xml
Cache-Control: private, no-store, max-age=300
<customers>...</customers>
与Cache-Control对应的,JAX-RS提供了javax.ws.rs.core.CacheControl类来表示Cache-Control头:
Java代码
public class CacheControl {
public CacheControl() {...}
public static CacheControl valueOf(String value)
throws IllegalArgumentException {...}
public boolean isMustRevalidate() {...}
public void setMustRevalidate(boolean mustRevalidate) {...}
public boolean isProxyRevalidate() {...}
public void setProxyRevalidate(boolean proxyRevalidate) {...}
public int getMaxAge() {...}
public void setMaxAge(int maxAge) {...}
public int getSMaxAge() {...}
public void setSMaxAge(int sMaxAge) {...}
public List<String> getNoCacheFields() {...}
public void setNoCache(boolean noCache) {...}
public boolean isNoCache() {...}
public boolean isPrivate() {...}
public List<String> getPrivateFields() {...}
public void setPrivate(boolean _private) {...}
public boolean isNoTransform() {...}
public void setNoTransform(boolean noTransform) {...}
public boolean isNoStore() {...}
public void setNoStore(boolean noStore) {...}
public Map<String, String> getCacheExtension() {...}
}
在ResponseBuilder类中调用callControl()设置返回的Cache-Control:
Java代码
@Path("{id}")
@GET
@Produces("application/xml")
public Response getCustomer(@PathParam("id") int id) {
Customer cust = findCustomer(id);
CacheControl cc = new CacheControl();
cc.setMaxAge(300);
cc.setPrivate(true);
cc.setNoStore(true);
ResponseBuilder builder = Response.ok(cust, "application/xml");
builder.cacheControl(cc);
return builder.build();
}
二、重验证和条件GET
另一个和cache有关的就是当cache变得陈旧的时候,缓存端能够询问服务端缓存的数据是否仍然有效,这叫做revalidation。为了执行revalidation,客户端需要从服务器端取得一些缓存数据的额外的信息,服务端在最初的响应里会发回一个Last-Modified与/或ETag头。
* Last-Modified
Last-Modified头表示服务端发回数据的一个时间戳。例如:
Java代码
HTTP/1.1 200 OK
Content-Type: application/xml
Cache-Control: max-age=1000
Last-Modified: Tue, 15 May 2009 09:56 EST
<customer id = "1">..</customer>
以上,最初的响应的有效期是1000秒,并且时间戳是2009,5,15,9点56。如果客户端支持重新验证,它会存储这个时候戳到缓存数据中。1000稍以后,客户端可以选择重新验证缓存。要重新验证,它需要发送一个条件(conditional)GET请求,并且传递一个If-Modified-Since头信息,If-Modified-Since的值即是Last-Modified的值。例如:
Java代码
GET /customers/123 HTTP/1.1
If-Modified-Since: Tue, 15 May 2009 09:56 EST
然后服务器会判断数据是否有变化,如果有,则返回一个200, 'OK'的响应,并发回新的响应体;如果没有改变,则返回304, 'Not Modified',和一个空的响应体。这两种情况下都需要发送新的Cache-Control和Last-Modified头。
* ETag
ETag是用来表示数据版本的、假设唯一的某个标识。它的值是任一一个以引用括起的字符串,通常是MD5 哈希值。 例如:
Html代码
HTTP/1.1 200 OK
Content-Type: application/xml
Cache-Control: max-age=1000
ETag: "3141271342554322343200"
<customer id="123">...</customer>
类似于Last-Modified头,如果客户端缓存了这个响应体,则也应该缓存ETag的值。1000秒以后,客户端需要执行一个重验证请求,其中包含一个If-None-Match的请求头:
Html代码
GET /customers/123 HTTP/1.1
If-None-Match: "3141271342554322343200"
当服务器端收到这个GET请求时,它就会试图比较当前resource的ETag值和传入的If-None-Match的值,如果不成功,则返回200, 'OK'和一个新的响应体;否则返回304和'Not Modified’和一个空的响应体。
最后关于ETag有两种类型:strong和weak:
一个strong的ETag:资源的任何变化都会引起ETag的变化
一个weak的ETag:只有资源显著的变化才变化
例如,一个weak的ETag:
Java代码
HTTP/1.1 200 OK
Content-Type: application/xml
Cache-Control: max-age=1000
ETag: W/"3141271342554322343200"
<customer id="123">...</customer>
对应于ETag的JAX-RS类是javax.ws.rs.core.EntityTag:
Java代码
public class EntityTag {
public EntityTag(String value) {...}
public EntityTag(String value, boolean weak) {...}
public static EntityTag valueOf(String value)
throws IllegalArgumentException {...}
public boolean isWeak() {...}
public String getValue() {...}
}
* JAX-RS和条件GET
为了计算上面的验证条件,JAX-RS提供了一个帮助类javax.ws.rs.core.Request:
Java代码
public interface Request {
...
ResponseBuilder evaluatePreconditions(EntityTag eTag);
ResponseBuilder evaluatePreconditions(Date lastModified);
ResponseBuilder evaluatePreconditions(Date lastModified, EntityTag eTag);
}
重载的evaludatePreconditions()方法需要一个javax.ws.rs.core.EntityTag和/或java.util.Date对象,表达ETag和最后修改时间(Last-Modified)。这些值是最新的、当前的,然后把它们同传入的请求参数If-Not-Modified和If-None-Match对象。如果不匹配则返回200,'OK'和响应体,或者返回304,'Not Modified'和空响应体:
Java代码
@Path("/customers")
public class CustomerResource {
@Path("{id}")
@GET
@Produces("application/xml")
public Response getCustomer(@PathParam("id") int id,
@Context Request request) {
Customer cust = findCustomer(id);
EntityTag tag = new EntityTag(
Integer.toString(cust.hashCode()));
CacheControl cc = new CacheControl();
cc.setMaxAge(1000);
ResponseBuilder builder = request.evaluatePreconditions(tag);
if (builder != null) {
builder.cacheControl(cc);
return builder.build();
}
// Preconditions not met!
builder = Response.ok(cust, "application/xml");
builder.cacheControl(cc);
builder.tag(tag);
return builder.build();
}
}
三、并发(Concurrency)
当需要更新Server上的某个数据时,通常就需要面对并发的问题了,因为很有可能有多个人同时在更新同一个信息。
HTTP规范通过使用有条件的PUT或POST请求来解决这个问题,有点类似于有条件GET请求。首先客户端取得数据:
Html代码
HTTP/1.1 200 OK
Content-Type: application/xml
Cache-Control: max-age=1000
ETag: "3141271342554322343200"
Last-Modified: Tue, 15 May 2009 09:56 EST
<customer id="123">...</customer>
为了带条件的更新,我们需要用到ETag或Last-Modified头,这些信息用于告诉服务端我们需要更新的哪个版本。当我们做PUT或POST请求时,这些信息会随同If-Match或If-Unmodified-Since一同发送出去,其中If-Match信息的值是ETag;If-Unmodified-Since的值是Last-Modified。因此上面GET对应的更新请求可能是:
Html代码
PUT /customers/123 HTTP/1.1
If-Match: "3141271342554322343200"
If-Unmodified-Since: Tue, 15 May 2009 09:56 EST
Content-Type: application/xml
<customer id="123">...</customer>
可以发送If-Match或If-Unmodified-Since中的任何一个。当Server收到这个请求时,就会去检查当前的ETag是否匹配If-Match或当前的时间戳是否匹配If-Unmodified-Since头。如果这些条件都不满足,则返回412, 'Precondition Failed'响应,用于告诉客户端当前数据已经被修改过,请重试;如果条件满足,则执行更新,并返回成功的结果。
* JAX-RS和条件更新
和读取数据一样,还是要用到Request.evaluatePreconditions()方法,例如:
Java代码
@Path("/customers")
public class CustomerResource {
@Path("{id}")
@PUT
@Consumes("application/xml")
public Response updateCustomer(@PathParam("id") int id,
@Context Request request) {
Customer cust = findCustomer(id);
EntityTag tag = new EntityTag(
Integer.toString(cust.hashCode()));
Date timestamp = ...; // get the timestampe
ResponseBuilder builder =
request.evaluatePreconditions(timestamp, tag);
if (builder != null) {
// Preconditions not met!
return builder.build();
}
//... perform the update ...
builder = Response.noContent();
return builder.build();
}
}
相关文章推荐
- JAX-RS入门 十二: 可伸缩的JAX-RS应用
- JAX-RS入门 十二: 可伸缩的JAX-RS应用
- JAX-RS入门 十二: 可伸缩的JAX-RS应用
- JAX-RS入门 九: 内容约定(2)
- JAX-RS入门 二 :运行
- JAX-RS入门 二 :运行
- JAX-RS入门 十一:HATEOAS
- JAX-RS入门 二 :运行
- JAX-RS入门 十一:HATEOAS
- JAX-RS入门,学习笔记
- JAX-RS入门 三 :细节
- JAX-RS入门 三 :细节
- JAX-RS之Jersey入门
- JAX-RS入门 一 :基础
- JAX-RS入门 四: 注入
- JAX-RS入门
- JAX-RS入门 四: 注入
- JAX-RS入门 一 :基础
- JAX-RS开发(一):基本概念入门和工具准备
- JAX-RS入门 四: 注入