WebX實踐指南_WebXRPC(四)

fibojie發表於2015-12-17

WebX RPC顧名思義,是基於webx框架之上支援非同步請求的一種擴充套件,是輕量級的RPC框架。
參考:http://docs.alibaba-inc.com/display/opentech/Web-RPC

WebX RPC說明

1.1、意義

1. 對webx框架的擴充套件,使webx能夠更好的支援ajax請求
2. 統一各個站點對webx ajax的使用規範

1.2、不解決的問題

1. 不提供類似DWR框架的client端js輸出
2. 不是一個REST的實現(**?**)

1.3、應用範圍 & 特性

  • 應用範圍:
    1. 需要使用ajax技術的webx專案
    2. 已經用過ajax在webx當中的專案
  • 特性:
    1. 支援annotation的資料繫結和驗證
    2. 統一的錯誤處理流程
    3. 支援json、xml、jsonp的輸出格式
    4. 可擴充套件MimeResult自定義輸出格式
    5. 可擴充套件RPCResultGenerator來完成對生成結果的自定義包裝
    6. 提供全域性的攔截器和方法級別的攔截器
    7. 框架內建的XSS和CSRF安全過濾器 [前端如何實現]

1.4、快速開始

  • 新增Maven庫依賴pom.xml 片段:
<dependency>
     <groupId>com.alibaba.platform.shared</groupId>
     <artifactId>webx3.extension.rpc</artifactId>
     <version>0.2.6</version>
</dependency>
<!--fasttext配置 begin -->
<dependency>
    <groupId>com.alibaba.platform.shared</groupId>
    <artifactId>fasttext.all</artifactId>
    <version>1.3</version>
</dependency>
  • 配置Web-RPC的基礎服務
    • pipeline.xml中增加對RPC的攔截支援,pipeline.xml片段
<when>
    <pl-conditions:target-extension-condition extension="json,jsonp,xml,xhtml" />
    <valve class="com.alibaba.citrus.extension.rpc.integration.RPCServiceHandlerValve" />
</when>
  • 在webx-component.xml中配置以下服務,Spring配置檔案片段
<!-- ===================================================  -->
<!-- WebX3 RPC 服務配置 (開始)-->
<!-- ===================================================  -->
<!-- RPC 資料繫結服務 -->
<beans:bean id=`rpcDatabindService` class=`com.alibaba.citrus.extension.rpc.databind.RPCDatabindServiceImpl`>
    <beans:property name="registry">
        <beans:bean id=`databinderRegistry` class=`com.alibaba.nonda.databind.impl.DatabinderRegistryImpl`>
            <beans:property name="binderFactories">
                <beans:list>
                    <beans:bean class="com.alibaba.citrus.extension.rpc.databind.RPCDatabinderFactory"/>
                </beans:list>
            </beans:property>
        </beans:bean>
    </beans:property></beans:bean>

<!-- RPC 資料驗證服務 -->
<beans:bean id=`rpcValidateService` class="com.alibaba.nonda.integration.webx3.validation.ValidateService4WebX3" />

<!-- RPC URL與元件的對映服務 -->
<beans:bean id=`rpcUrlComponentMapping` class="com.alibaba.citrus.extension.rpc.impl.URLComponentMappingImpl">
    <!-- 標識RPC服務的名稱空間(通過不同的namespace來模板化不同的業務) -->
    <beans:property name="namespace" value="${component}" />
    <!--  是否啟用URL的馱峰轉換,預設為true -->
    <beans:property name="useCamelCaseURL" value="false" /></beans:bean>

<!-- RPC 處理請求的核心服務 --><beans:bean id=`rpcServiceHandler` class="com.alibaba.citrus.extension.rpc.impl.RPCServiceHandlerImpl">
    <!-- 示例:覆蓋預設的 resultGenerator -->
    <beans:property name="resultGenerator">
        <beans:bean class="com.alibaba.sample.showcase.web.rpc.common.generator.MyCustomResultGenerator" />
    </beans:property>
    <!-- 示例:新增全域性自定義的Interceptor -->
    <beans:property name="interceptors">
        <beans:list>
            <beans:bean class="com.alibaba.sample.showcase.web.interceptors.SecurityInterceptor" />
        </beans:list>
    </beans:property>
    <!-- 示例:配置全域性的屬性過濾器 -->
    <beans:property name="propertyFilters">
        <beans:list>
            <!-- Trim2Null過濾器  -->
            <beans:bean class="com.alibaba.citrus.extension.rpc.response.impl.Trim2NullPropertyFilter" />
        </beans:list>
    </beans:property>

    <!-- xssType配置選擇項有兩種: 1.delete會直接刪除xss標籤 2.escape會將xss標籤做轉義輸出  系統預設delete-->
    <beans:property name="xssType" value="escape" />
</beans:bean>

• 在webx-應用模組.xml中增加對RPC型別元件的掃瞄支援
webx-應用模組.xml中的module-loader片段

<!-- 裝載模組。 -->
<services:module-loader>
    <ml-factories:class-modules>
        <ml-factories:search-packages type="$1" packages="xxx.yyy.module.*" />
    </ml-factories:class-modules>
    <ml-adapters:adapter class="com.alibaba.citrus.extension.rpc.integration.RPCModuleAdapterFactory" />
</services:module-loader>

1.5、編寫一個HelloRPCService類:

在module目錄下新增新的package:rpc,編寫對應的服務類。對映的實現可以參看上面配置檔案的 rpcUrlComponentMapping部分.
例如對於:HelloRPCService.java 類

    @WebResource("hello")
    public class HelloRPCService { 
      // some fields
      // getter & setter 
      @ResourceMapping("/loadUser")
      public  String loadUser() {
        return  user;
     } 

訪問的URL為:
http://localhost:8081/hello/loadUser.json (輸出json格式)
http://localhost:8081/hello/loadUser.xml (輸出xml格式)
引數傳遞:
public Object groupQuery(@RequestParam(name=”id”) Integer id, HttpSession session) {
}
RPC返回的引數型別,一般採用JSON的方式傳回,如何返回複雜的JSON資料結構,例如:
{“data”:[
{},
{},
{}
],
“totalCount”:50,
“pageSize”:10,
“currentPage”:1
}
針對這樣的資料結構,產生結果時,採用Map

1.6、高階應用

1.6.1、定義自己的result generator

result generator程式碼示例:
public class ErrorMessageResultGenerator implements RPCResultGenerator {
private static final Logger logger = LoggerFactory.getLogger( ErrorMessageResultGenerator.class );
/**
* code

* 500: RPC服務異常

* 404: 沒有匹配的RPC服務

* rpc_system_exception: RPC框架異常

* rpc_invalid_arg: 資料繫結異常

* null: 資料驗證異常
*/
private static final Map

1.6.2、RPC Wraper對所有的RPC請求進行統一的包裝,RPC Wraper

web-app.xml //針對應用的xml配置

示例程式的RPC Wraper:
package com.tmall.mastermind.web.rpcwraper;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import lombok.extern.slf4j.Slf4j;

import org.aspectj.lang.ProceedingJoinPoint;

import phoenix.exception.DSLServiceException;

import com.alibaba.common.lang.StringUtil;
import com.tmall.mastermind.app.auth.service.api.AccessControlSwitcherService;
import com.tmall.mastermind.exception.MastermindException;
import com.tmall.mastermind.exception.MastermindExceptionType;
import com.tmall.mastermind.web.helper.BUCUserInfoHelper;

@Slf4j
public class RpcResultWraper {

@Resource
AccessControlSwitcherService accessControlSwitcherService;

public Object doWrap(ProceedingJoinPoint pjp) throws MastermindException {
    Map<String, Object> result = new HashMap<String, Object>();
    try {
        rpcSwitcher(pjp);
        return pjp.proceed();
    } catch (MastermindException me) {
        result.put("result", "false");
        result.put("errorMsg", me.getErrorMsg());
        return result;
    } catch (DSLServiceException e) {
        result.put("result", "false");
        result.put("errorMsg", "系統繁忙~" + MastermindExceptionType.TIME_OUT.getMsg());
        log.error("erro on :" + pjp.getClass(), e);
        result.put("debugMsg", e.getMessage());
        return result;
    } catch (Throwable e) {
        result.put("result", "false");
        result.put("errorMsg", "系統開小差了~" + MastermindExceptionType.EXECUTION_FAILED);
        log.error("erro on :" + pjp.getClass(), e);
        result.put("debugMsg", e.getMessage());
        return result;
    }
}

/**
 * rpc訪問控制開關
 * 
 * @param pjp
 * @throws MastermindException
 */
private void rpcSwitcher(ProceedingJoinPoint pjp) throws MastermindException {
    if (!accessControlSwitcherService.isSwitcherEnabled()) return;

    String url = getRequestUrl(pjp.getTarget());
    if (StringUtil.isEmpty(url)) {
        return;
    }
    log.info("rpcSwitcher: get request url:" + url);
    String empId = BUCUserInfoHelper.getEmpId(getRequest(pjp.getTarget()));
    if (StringUtil.isEmpty(empId)) {
        log.error("rpcSwitcher error!! get empty empId from request");
        throw new MastermindException(MastermindExceptionType.EXECUTION_FAILED, "無法獲取使用者資訊");
    }

    accessControlSwitcherService.checkWhiteList(url, empId);
}

private String getRequestUrl(Object target) {
    HttpServletRequest request = getRequest(target);
    if (null == request) {
        return "";
    }

    return new String(((HttpServletRequest) request).getRequestURL());
}

private HttpServletRequest getRequest(Object t) {
    Field field = null;
    try {
        field = t.getClass().getDeclaredField("request");
    } catch (Exception e) {
        log.error("rpcSwitcher error!! RPC-Object does not have request object", e);
        return null;
    }
    if (null == field) {
        return null;
    }

    HttpServletRequest request = null;
    boolean save = field.isAccessible();
    field.setAccessible(true);
    try {
        request = (HttpServletRequest) field.get(t);
    } catch (Exception e) {
        log.error("rpcSwitcher error!! could not get request from field", e);
    }
    field.setAccessible(save);
    return request;
}

}

1.6.3、RPC timeout

1.7、WebX RPC原理

Pipeline任務的分發處理,RPC Service Handler, 處理的關鍵點,結果集合的重新生成,包括JSON、XML格式的結果集合。同時可以自己定義結果的生成集合。
RPC和Web服務區分 WebX的不同於Rest服務是一種RPC實現這種區分在於分佈系統中的兩種實現,一種是Web Service,一種是RPC實現。Web服務遵循SOAP協議。
RPC客戶機呼叫程式傳送一個有程式引數的呼叫資訊到服務程式,然後等待應答資訊。在伺服器端,程式保持睡眠狀態直到呼叫資訊的到達為止。當一個呼叫資訊到達,伺服器獲得程式引數,計算結果,傳送答覆資訊,然後等待下一個呼叫資訊,最後,客戶端呼叫程式接收答覆資訊,獲得程式結果,然後呼叫執行繼續進行。
Web Service本身其實是在實現應用程式間的通訊。我們有兩種應用程式通訊的方法:RPC遠端過程呼叫和訊息傳遞。使用RPC的時候,客戶端的概念是呼叫伺服器上的遠端過程,通常方式為例項化一個遠端物件並呼叫其方法和屬性。RPC系統試圖達到一種位置上的透明性:伺服器暴露出遠端物件的介面,而客戶端就好像在本地使用的這些物件的介面一樣,這樣就隱藏了底層的資訊,客戶端也就根本不需要知道物件是在哪臺機器上。
Web Service是構造分散式、模組化應用程式和麵向服務應用整合的最新技術和發展趨勢。Web服務是基於Web容器的,使用Servlet來實現。
RPC OVER HTTP Microsoft RPC-over-HTTP 部署(RPC over HTTP)允許RPC客戶端安全和有效地通過Internet 連線到RPC 伺服器程式並執行遠端過程呼叫。這是在一個名稱為RPC-over-HTTP 代理,或簡稱為RPC 代理的中介軟體的幫助下完成的。
RPC代理執行在IIS計算機上。它接受來自Internet 的RPC 請求,在這些請求上執行認證,檢驗和訪問檢查,如果請求通過所有的測試,RPC 代理將請求轉發給執行真正處理的RPC 伺服器。通過RPC over HTTP,RPC客戶端不和伺服器直接通訊,它們使用RPC 代理作為中介軟體。
WebX的實現也是類似的,是一種RPC over HTTP的實現。


相關文章