1. HTTP接口的意義
二進制接口使用的是java/hessian序列化協議,不能很好的與其他語言通信,雖然hessian也是一種跨語言的通用協議,但很多語言沒有很好的實現該協議的產品。
所以為了能夠與其他語言進行服務通信,我們實現了http + json的協議實現,利用json原生的跨語言的特性。
2. 原理簡圖
描述:通過Netty暴露http服務端口,接收到http請求,通過HttpDecoder將其解析為HttpRequest,通過JSONDecoder提取service請求信息,生成Request請求對象,從而adapt到binary協議實現,交由request processor處理,返回Response結果,最終通過JSONEncoder編碼為JSON格式數據,再通過HttpEncoder生成HttpResponse返回給Http接口調用方。
3. 協議格式
假定:有服務暴露接口ExampleService,服務名稱:http://service.huifu.com/ExampleService/exampleService_1.0.0,定義了服務方法public String sayHello(String message),則HTTP訪問協議描述如下:
請求協議:
Name | Value | Description | |
---|---|---|---|
URL | http://{machine-ip}:{http-port}/rpc.json | HTTP服務的訪問地址,注意http端口號為pegasus端口號+1000,即:pegasus配置為8888,http端口就是9888 | |
Method | POST | 訪問方式,推薦使用POST方式 | |
Encoding | UTF-8 | HTTP請求編碼 | |
Params | _service | http://service.huifu.com/ExampleService/exampleService_1.0.0 |
訪問的服務名稱 |
_method | sayHello |
訪問的服務接口方法名稱 | |
_param | {"message" : "kitty"} | 傳遞到接口方法的參數值,使用json格式,形如:{"param1" : "value1", "param2" : {"f1" : 1, "f2" : "vvv"}},其中param1,param2為方法的參數名稱,param1為string類型,param2為javabean類型,f1和f2為該javabean的屬性名。 如:調用方法為sayHello(String message),則對應json為{"message":"kitty"}。如果調用方法為:sayHello(RequestDTO userDTO),RequestDTO中有一個屬性為name,則對應的json為{"userDTO":{"name":"kitty"}}。 |
響應協議:
Name | Value | Description |
---|---|---|
格式 | {"type" : "xxx", "result" : "xxx", "error" : "xxx"} | HTTP服務接口的響應內容為JSON格式 |
type | "service" | "service-exception" | "exception" | "service":業務處理正常返回 |
result | type = "service"時,服務方法的返回值,為JSON格式 | 若方法返回值為簡單類型: |
error | type = "service-exception" | "exception"時,返回的錯誤信息,為JSON格式 |
形如{"type" : "com.huifu.xx.BizException", "message" : "order_no is not supported"} |
-
關於服務方法參數名
默認情況下,pegasus通過asm讀取服務實現類的方法簽名信息,獲取方法的參數名,但因重構或其他原因,該方法的參數名有可能被修改,從而導致原有的http接口使用方無法正常調用;
所以推薦在有可能提供http訪問的服務方法上通過@Param注解定義參數名,這樣就可以防止參數名被修改的情況,如void sayHello(@Param("message") String message),此時該方法就可以安全地重構為void sayHello(@Param("message") String msg),而不影響原有調用方;注意:在使用的過程中發現,通過javassist獲取參數名,如果使用javac編譯器編譯的class文件(maven默認使用),可能會出現無法讀取參數名或讀取錯誤; 我們遇到的幾個問題case改用eclipse的ecj編譯器后都可以fix,但我們無法去測試覆蓋所有的代碼case,因此若方法涉及到http調用時,請使用@Param的方式顯式提供參數名;btw: maven使用ecj作為編譯器的詳細配置如下(1.0.2中已經使用asm替換javassist,該問題已FIXED) - 關於服務傳輸對象(DTO)的定義
由於使用json格式傳遞方法參數值,就牽扯到json ==> javabean的轉換過程,而這個過程要求能明確地知道所有java對象及其屬性的類型,所以在創建服務協議DTO對象時,如果這個對象牽扯到http方式的調用,那么就需要明確定義其類型,比如集合類型或數組,必須有明確的元素類型信息,所以Map, List, Set, Object[]都是無法轉換的,必須是Map<String, String>, List<Order>, Set<Integer>, Order[]等有明確的元素類型聲明的集合類和數組,即混合類型的集合http方式不支持;
由於json對象無法表示對象引用,所以在定義DTO時,應避免引用的方式,比如parent下定義了list<child>,child中又定義一個parent引用剛才的parent對象,這種方式不允許。
由於json對象的key一定是string類型,所以對於DTO中的Map,只能是Map<String, xx>, Map<Integer, xx>, Map<BigDecimal, xx>, Map<Boolean, xx>,Map<Date, xx>這樣的Map類型,而Map<JavaBean, xx>這種類型則無法通過http接口調用,因為json無法表示這樣的key。json轉map:{"k1", "v1"} => map {"k1", "v1"}。
對於有Map<JavaBean, JavaBean>這種類型的dto,如果又需要通過http方式訪問,那么請重構該map類型為List方式,比如List<MapBean>, MapBean中有JavaBean的key和JavaBean類型的value屬性,即通過List<Entry>的方式實現Map,此時可以通過json表示,即[{"key1" : {xxx}, "value1" : {xxx}}, {"key2" : {xxx}, "value2" : {xxx}}]。 - 關於客戶端調用
可以按照正常的http方式訪問服務接口,由於目前各語言還沒有自己封裝的客戶端,所以集群方面只能通過前端架設軟負載的方式實現,后期考慮各語言封裝自己的客戶端,內部維護到各服務提供者的http長連接,並定期地通過服務注冊中心的http接口更新服務提供者列表信息,搭配定期心跳探測移除不可用的連接,以便最終摒棄軟負載。
通過http方式訪問服務接口時,需要在客戶端設置http請求的超時時間,以便在超過設定的超時時間后,客戶端會立即拋出超時異常,並中斷這次請求,否則會一直等待服務端返回,一旦服務端業務邏輯處理僵死,將無法返回http響應,並一直維持該http連接,造成資源無法釋放;