在进入代码部分之前,还是贴出<<RESTful Web Service>>里对压缩的说明,毕竟这是本非常不错的书,而且书上说的比较清晰明了。
象XML文档这种文本格式的表示可以又很大的压缩率。HTTP客户端可以请求一个压缩版的表示,并为用户进行
透明的解压。其工作原理是这样的:客户端在发送HTTP请求时,在Accept-Ecoding包头里指出客户端支持那些
压缩算法。Accept-Encoding抱头又两个标准的值:compress和gzip
GET /resource.html HTTP/1.1
Host: www.example.com
Accept-Encoding: gzip, compress
若服务器支持Accept-Ecoding请求报头里指出的压缩算法,那么它将先对表示进行压缩,然后再发给客户端。
Content-type响应报头的值不会因为服务器是否压缩而改变。但是服务器会再响应里附上一个
Content-Encoding报头,这样客户端就知道服务器返回的表示有没有压缩:
200 OK
Content-Type : text/html
Content-Encoding: gzip
客户端再收到服务器的响应后,将先用Content-Encoding响应报头里指出的算法来解压缩数据,然后按照
Content-Type响应报头指出的媒体类型来处理解压缩后的数据。通过采用压缩技术,只需增加少许复杂性就
可以节省大量带宽。
下面我们结合两种情况看看如何实现压缩(使用Restlet作为服务器和客户端):
1. 客户端向服务器发送GET请求,服务器返回一个压缩的表示给客户端
首先客户端发送请求,并指出客户端支持那些压缩算法:
Reference reference = new Reference("http://localhost:8080/restlet/resources/users/1");
Request get = new Request(Method.GET, reference);
List<Preference<Encoding>> encodings = new ArrayList<Preference<Encoding>>();
Preference<Encoding> pref = new Preference<Encoding>(Encoding.GZIP, 1.0f);
encodings.add(pref);
pref = new Preference<Encoding>(Encoding.COMPRESS, 0.5f);
encodings.add(pref);
get.getClientInfo().setAcceptedEncodings(encodings);
Client client = new Client(Protocol.HTTP);
Response response = client.handle(get);
/users/{userId}对应的资源是UserResource,那么服务器端接收到请求后,会做什么处理呢?服务器会首先判断客户端是否可以接受压缩的表示,并且支持那些算法,然后根据支持压缩算法的优先级别来决定使用那种算法来压缩表示。这里,为了测试方便,假设服务器端只支持GZIP算法,代码如下:
@Override
public void handleGet() {
boolean isGZIP = false;
List<Preference<Encoding>> encodings = getRequest().getClientInfo().getAcceptedEncodings();
if (encodings != null) {
for (Preference<Encoding> pre : encodings) {
if (pre.getMetadata().equals(Encoding.GZIP)) {
isGZIP = true;
break;
}
}
}
Representation representation = null;
if(isGZIP){
representation = new EncodeRepresentation(Encoding.GZIP,
new StringRepresentation(getUserXml(),
MediaType.TEXT_PLAIN));
}else{
representation = new StringRepresentation(getUserXml(user), MediaType.TEXT_PLAIN);
}
getResponse().setStatus(Status.SUCCESS_OK);
getResponse().setEntity(representation);
}
同样为了测试方便,我硬编码了一个压缩前的表示内容:
private String getUserXml(){
StringBuffer userXml = new StringBuffer();
userXml.append("<user id=\"1\">");
userXml.append("<name>ajax</name>");
userXml.append("<phone>13888888888</phone>");
userXml.append("<email>ajax@iteye.com</email>");
userXml.append("<address>Shanghai</address>");
userXml.append("</user>");
return userXml.toString();
}
至此,服务器端处理完成,那么客户端接收到服务器端返回的响应后,需要解压缩,当然了,需要首先判断服务器端使用的是那种压缩算法,看代码:
List<Encoding> _encodings = response.getEntity().getEncodings();
boolean isGZIP = false;
if (_encodings != null) {
for (Encoding encoding : _encodings) {
if (encoding.equals(Encoding.GZIP)) {
isGZIP = true;
break;
}
}
}
Representation rep = response.getEntity();
if(isGZIP){
rep = new DecodeRepresentation(response.getEntity());
}
String testText = rep.getText();
过程比较简单,不再多做解释。
2.客户端发送POST请求到服务器端,并且附送一个压缩的表示。
这种情况一般是客户端在请求时附送的表示非常耗费服务器资源,占用带宽,例如某些XML表示。为了测试,这里没有同服务器端反复协商的过程,直接发送使用GZIP压缩算法压缩过的表示。
Reference reference = new Reference("http://localhost:8080/restlet/resources/users/1");
Client client = new Client(Protocol.HTTP);
Representation representation = new EncodeRepresentation(Encoding.GZIP,
new StringRepresentation(getUserXml(), MediaType.TEXT_PLAIN));
Response response = client.post(reference, representation);
assertTrue(response.getStatus().isSuccess());
看看服务器端怎么处理:
@Override
public void acceptRepresentation(Representation entity) throws ResourceException {
try {
String text = getRequest().getEntity().getText();
System.out.println(text);
} catch (IOException e1) {
e1.printStackTrace();
}
}
咦,貌似不对,既然客户端压缩了表示,为什么服务器端没有解压缩? 是的,Restlet已经自动的做了解压缩,Restlet目前支持的压缩算法有:看DecodeRepresentation中的一段源代码:
public static List<Encoding> getSupportedEncodings() {
return Arrays.<Encoding> asList(Encoding.GZIP, Encoding.DEFLATE,
Encoding.ZIP, Encoding.IDENTITY);
}
那假设服务器端和客户端有自己约定的压缩算法,而这个算法又不在Restlet支持之列,怎么处理这种情况?可以在Application的实例中禁止进行解压缩:
getDecoderService().setEnabled(false);
这样,就可以使用约定的压缩算法进行相应的压缩与解压缩了。