Apache CXF實現Web Service(5)—— GZIP使用
參考來源:
- CXF WebService整合Spring
- Apache CXF實現Web Service(1)——不借助重量級Web容器和Spring實現一個純的JAX-WS web service
- Apache CXF實現Web Service(4)——Tomcat容器和Spring實現JAX-RS(RESTful) web service
首先參照
Apache CXF實現Web Service(4) 創建一個WTP項目,並參照(1) 新建一個測試的Web Service:HelloWorld.java和其實現HelloWorldImpl.java
HelloWorld.java
package com.cnblog.richaaaard.cxftest.spring.ws.helloworld.services;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import org.apache.cxf.annotations.GZIP;
//@GZIP(threshold=128)
@GZIP
@WebService
public interface HelloWorld {
@WebMethod
@WebResult String sayHi(@WebParam String text);
}
HelloWorldImpl.java
package com.cnblog.richaaaard.cxftest.spring.ws.helloworld.services;
import javax.jws.WebService;
@WebService(endpointInterface="com.cnblog.richaaaard.cxftest.spring.ws.helloworld.services.HelloWorld",serviceName="helloService")
public class HelloWorldImpl implements HelloWorld {
public String sayHi(String name) {
String msg = "Hello " + name + "!";
return msg;
}
}
在Spring的配置中需要注意的是,我們需要引入jarws的schema
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd
完整的配置文件如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"
default-lazy-init="true">
<import resource="classpath:META-INF/cxf/cxf.xml"/>
<import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
<bean id="helloService" class="com.cnblog.richaaaard.cxftest.spring.ws.helloworld.services.HelloWorldImpl">
</bean>
<jaxws:endpoint implementor="#helloService" address="/HelloService"/>
</beans>
web.xml文件保持不變
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"
default-lazy-init="true">
<import resource="classpath:META-INF/cxf/cxf.xml"/>
<import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
<bean id="helloService" class="com.cnblog.richaaaard.cxftest.spring.ws.helloworld.services.HelloWorldImpl">
</bean>
<jaxws:endpoint implementor="#helloService" address="/HelloService"/>
</beans>
項目結構如圖
下面需要測試幾個問題
- @GZIP如何工作的?
- @GZIP有兩個屬性 force 和 threshold 怎么用?
- @GZIP加在接口上是否可行?(我們用Spring實例化bean是用的HelloWorldImpl)
測試
-
@GZIP如何工作的?
第一步
在Eclipse中Run As... -> Run on Server,然后在瀏覽器中驗證是否發布成功:
我們Tomcat本地運行的端口是8080。
第二步
運行TCPMon,新建監聽端口8081,目標端口8080:
第三步
新建客戶端測試代碼,並將address設置成"http://localhost:8081/cxf/services/HelloService"
package com.cnblog.richaaaard.cxftest.spring.ws.helloworld.client;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.transport.common.gzip.GZIPInInterceptor;
import org.apache.cxf.transport.common.gzip.GZIPOutInterceptor;
import com.cnblog.richaaaard.cxftest.spring.ws.helloworld.services.HelloWorld;
public class Client {
public static void main(String[] args) {
JaxWsProxyFactoryBean client = new JaxWsProxyFactoryBean();
// factory.getInInterceptors().add(new LoggingInInterceptor());
// factory.getOutInterceptors().add(new LoggingOutInterceptor());
// factory.getInInterceptors().add(new GZIPInInterceptor());
// factory.getOutInterceptors().add(new GZIPOutInterceptor());
client.setServiceClass(HelloWorld.class);
client.setAddress("http://localhost:8081/cxf/services/HelloService");
HelloWorld helloworld = (HelloWorld) client.create();
System.out.println(helloworld.sayHi("Richard"));
System.exit(0);
}
}
第一個測試,運行Client.java
Run As... -> Java Application
在TCPMon(關於如何使用TCPMon請查看http://www.cnblogs.com/richaaaard/p/5019438.html)中查看結果
發現並沒有像預料中的那樣發生GZIP壓縮
懷疑出現問題
- 服務器不支持GZIP?
- CXF有BUG?
- 使用方式有問題?
打開@GZIP Annotation的源碼查看
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Inherited
public @interface GZIP {
int threshold() default -1;
boolean force() default false;
}
這個標注有兩個屬性:threshold 與 force
然后查看Apache CXF 關於@GZIP的文檔 (CXF Features http://cxf.apache.org/docs/featureslist.html) 與 (CXF Annotations http://cxf.apache.org/docs/annotations.html)
細心的同學會發現“CXF Features文檔中關於GZIPFeature的說明中仍然出現了FastInfoset”這個錯誤
- threshold - the threshold under which messages are not gzipped
- force - force GZIP compression instead of negotiating via the Accept-Encoding header
GZIP is a negotiated enhancement. An initial request from a client will not be gzipped, but an Accept header will be added and if the server supports it, the response will be gzipped and any subsequent requests will be.
上面一段話的意思是:第一次請求不會發生GZIP,但是如果服務器支持,會加如到Accept頭上,返回的消息會發生GZIP然后,后面發生的請求也會有GZIP。
關於threshold的定義:可以發現我們測試中的請求(request)長度(Content-Length)是232,返回(response)長度是259。
這里猜想
@GZIP應該有一個自己的默認threshold,如果修改默認實現,也就能發生GZIP了
我們將threshold修改成256介於232和259之間
@GZIP(threshold=256)
//@GZIP
@WebService
public interface HelloWorld {
@WebMethod
@WebResult String sayHi(@WebParam String text);
}
重啟服務器,運行程序
請求沒有發生GZIP,而響應端有GZIP
再次運行Client(不重啟服務器)
我們發現,請求(request)並沒有像Apache官方文檔那樣說的,也會有GZIP。
這是為什么呢?是不是和我們的客戶端有關?
將代碼增加一次請求試試
發現連續的兩次請求仍然沒有發生GZIP。再仔細查看關於GZIP的解釋,"...如果服務器支持..."
package com.cnblog.richaaaard.cxftest.spring.ws.helloworld.client;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.transport.common.gzip.GZIPInInterceptor;
import org.apache.cxf.transport.common.gzip.GZIPOutInterceptor;
import com.cnblog.richaaaard.cxftest.spring.ws.helloworld.services.HelloWorld;
public class Client {
public static void main(String[] args) {
JaxWsProxyFactoryBean client = new JaxWsProxyFactoryBean();
// factory.getInInterceptors().add(new LoggingInInterceptor());
// factory.getOutInterceptors().add(new LoggingOutInterceptor());
// factory.getInInterceptors().add(new GZIPInInterceptor());
// factory.getOutInterceptors().add(new GZIPOutInterceptor());
client.setServiceClass(HelloWorld.class);
client.setAddress("http://localhost:8081/cxf/services/HelloService");
HelloWorld helloworld = (HelloWorld) client.create();
System.out.println(helloworld.sayHi("Richard"));
System.out.println(helloworld.sayHi("Kobe Bryant"));
System.exit(0);
}
}
這里猜想
是不是我們服務器的設置問題,不支持GZIP呢?
修改Tomcat的Connector配置,增加
compressionMinSize="256"
compression="on"
noCompressionUserAgents="gozilla, traviata"
compressableMimeType="text/html,text/xml"
然后重啟服務器,再運行客戶端
發現請求(request)仍然沒有發生GZIP
如何才能使請求也提交GZIP格式呢?
我們暫且放下這個問題,先將Tomcat配置文件關於compression的配置還原
來看看force如何工作的
在HelloWorld.java的頭上修改@GZIP增加force屬性,threshold仍然為256
@GZIP(force=true, threshold=256)
@WebService
public interface HelloWorld {
@WebMethod
@WebResult String sayHi(@WebParam String text);
}
請求仍然沒有被壓縮

好像漏了什么東西
之前我們反復測試,期望第二次請求(request)可以根據服務端返回的Accept-Encoding header 自行進行GZIP壓縮,我們測試代碼當時設置的threhold是256,而請求的Content-Length=232。我們將threshold調整到128,去掉force屬性,重啟服務器再試一下(這時的Tomcat沒有配置compression相關屬性)。
- 當我們單次運行的時候(每次客戶端運行結束,進程退出)
先后運行兩次獨立的請求,請求(request)沒有發生GZIP,這是因為客戶端是不同進程的緣故
- 當我加入一行代碼,在統一進程中連續兩次請求服務器時,我們會發現第二次請求會自行GZIP壓縮,而此時Tomcat上沒有對compression進行特別配置
由此可見
-
Tomcat是內置支持GZIP的服務器
-
Tomcat上的compression是服務器自己獨立的壓縮機制,與Apache CXF無關,但是服務器級別的配置會影響我們使用的CXF Web Service
那么問題來了
Tomcat服務器配置的壓縮機制是怎么工作的呢?
*擴展
StackOverflow上關於GZIPInInterceptor和GZIPOutInterceptor的回答是否正確?
通過上面的所有測試就能得出結論,這個Interceptor並不對服務端響應消息的GZIP起任何作用,讀者可以自行測試