Apache CXF實現Web Service(5)—— GZIP使用

Richaaaard發表於2015-12-04

Apache CXF實現Web Service(5)—— GZIP使用


參考來源:

首先參照

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>   

專案結構如圖

Apache CXF實現Web Service(5)—— GZIP使用

下面需要測試幾個問題

  • @GZIP如何工作的?
  • @GZIP有兩個屬性 force 和 threshold 怎麼用?
  • @GZIP加在介面上是否可行?(我們用Spring例項化bean是用的HelloWorldImpl)

測試

  • @GZIP如何工作的?

第一步

在Eclipse中Run As... -> Run on Server,然後在瀏覽器中驗證是否釋出成功:
Apache CXF實現Web Service(5)—— GZIP使用

我們Tomcat本地執行的埠是8080。

第二步

執行TCPMon,新建監聽埠8081,目標埠8080:
Apache CXF實現Web Service(5)—— GZIP使用

第三步

新建客戶端測試程式碼,並將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)中檢視結果
Apache CXF實現Web Service(5)—— GZIP使用

發現並沒有像預料中的那樣發生GZIP壓縮
Apache CXF實現Web Service(5)—— 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”這個錯誤

Apache CXF實現Web Service(5)—— GZIP使用
Apache CXF實現Web Service(5)—— GZIP使用

  • 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);  
}  

重啟伺服器,執行程式
Apache CXF實現Web Service(5)—— GZIP使用

請求沒有發生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"

然後重啟伺服器,再執行客戶端

Apache CXF實現Web Service(5)—— GZIP使用

發現請求(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);  
}  

請求仍然沒有被壓縮
[](http://images2015.cnblogs.com/blog/613455/201512/613455-20151204175354393-824266017.png

修改成128呢?因為請求的Content-Length是232

在次執行,發現請求成功壓縮了(這是請求壓縮的一種情景)
Apache CXF實現Web Service(5)—— GZIP使用

好像漏了什麼東西

之前我們反覆測試,期望第二次請求(request)可以根據服務端返回的Accept-Encoding header 自行進行GZIP壓縮,我們測試程式碼當時設定的threhold是256,而請求的Content-Length=232。我們將threshold調整到128,去掉force屬性,重啟伺服器再試一下(這時的Tomcat沒有配置compression相關屬性)。

  • 當我們單次執行的時候(每次客戶端執行結束,程式退出)

先後執行兩次獨立的請求,請求(request)沒有發生GZIP,這是因為客戶端是不同程式的緣故
Apache CXF實現Web Service(5)—— GZIP使用

  • 當我加入一行程式碼,在統一程式中連續兩次請求伺服器時,我們會發現第二次請求會自行GZIP壓縮,而此時Tomcat上沒有對compression進行特別配置

Apache CXF實現Web Service(5)—— GZIP使用

由此可見

  • Tomcat是內建支援GZIP的伺服器
  • Tomcat上的compression是伺服器自己獨立的壓縮機制,與Apache CXF無關,但是伺服器級別的配置會影響我們使用的CXF Web Service

那麼問題來了

Tomcat伺服器配置的壓縮機制是怎麼工作的呢?


*擴充套件

StackOverflow上關於GZIPInInterceptor和GZIPOutInterceptor的回答是否正確?

通過上面的所有測試就能得出結論,這個Interceptor並不對服務端響應訊息的GZIP起任何作用,讀者可以自行測試

相關文章