1. 程式人生 > >dubbo組成原理-http服務消費端如何呼叫

dubbo組成原理-http服務消費端如何呼叫

dubbo協議已經用的很多了,這裡來稍微介紹一下http協議,官方對http協議的說明簡直少的讓人髮指。哈哈

百度大部分都只是講了http服務端的配置

那就先從服務端的配置說起

dubbo需要的jar包這裡就不說明了,網上找些maven的pom就可以

web.xml配置servlet,注意url-pattern 是需要攔截哪些請求

<servlet>
	    <servlet-name>dubbo</servlet-name>
	    <servlet-class>com.alibaba.dubbo.remoting.http.servlet.DispatcherServlet</servlet-class>
	    <load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
	    <servlet-name>dubbo</servlet-name>
	    <url-pattern>/api/*</url-pattern>
	</servlet-mapping>
接著配置dubbo配置檔案
<dubbo:application name="imp" ></dubbo:application>
	<!-- <dubbo:protocol name="hessian" contextpath="imp/api" port="${dubbo.hessian.port}" server="servlet" threadpool="cached" threads="5000" register="false"></dubbo:protocol> -->
	<dubbo:protocol name="dubbo" port="30883" />
	<dubbo:protocol name="http" port="8080" server="servlet" contextpath="imp/api/httpService"/> 
	<dubbo:provider group="${dubbo.group}" />
	<dubbo:consumer check="false"  group="${dubbo.group}"/>
	
	<!-- 使用zookeeper註冊中心暴露發現服務地址 -->
	<dubbo:registry protocol="zookeeper" address="${dubbo.zookeeper.address}"  check="false" file="/home/epay/.dubbo/dubbo-registry-imp.cache" ></dubbo:registry>

因為dubbo支援多協議,所以這裡配置了dubbo與http二種

http配置需要注意contextpath這個引數,引用一下官方說明

  • 協議的上下文路徑<dubbo:protocol contextpath="foo" />必須與servlet應用的上下文路徑相同
什麼看不懂?跑起來打個斷點看看

用java模擬一個psot的http請求http://localhost:8080/imp/api/httpService/com.hellowin.imp.common.service.IDeviceService

服務端接收到http請求,因為/api/。所以會被DispatcherServlet攔截執行service

public class DispatcherServlet extends HttpServlet {

	private static final long serialVersionUID = 5766349180380479888L;
	
	private static DispatcherServlet INSTANCE;

    private static final Map<Integer, HttpHandler> handlers = new ConcurrentHashMap<Integer, HttpHandler>();

    public static void addHttpHandler(int port, HttpHandler processor) {
        handlers.put(port, processor);
    }

    public static void removeHttpHandler(int port) {
        handlers.remove(port);
    }
    
    public static DispatcherServlet getInstance() {
    	return INSTANCE;
    }
    
    public DispatcherServlet() {
    	DispatcherServlet.INSTANCE = this;
    }

    protected void service(HttpServletRequest request, HttpServletResponse response) 
    		throws ServletException, IOException {
        HttpHandler handler = handlers.get(request.getLocalPort());
        if( handler == null ) {// service not found.
            response.sendError(HttpServletResponse.SC_NOT_FOUND, "Service not found.");
        } else {
            handler.handle(request, response);
        }
    }

}

進入handler.handle(request, response);


handle有hession、http、webservice 這裡我們只看http的

點選進入HttpProtocol 這個類

 private class InternalHandler implements HttpHandler {
        
        public void handle(HttpServletRequest request, HttpServletResponse response)
                throws IOException, ServletException {
            String uri = request.getRequestURI();
            HttpInvokerServiceExporter skeleton = skeletonMap.get(uri);
            if (! request.getMethod().equalsIgnoreCase("POST")) {
                response.setStatus(500);
            } else {
                RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort());
                try {
                    skeleton.handleRequest(request, response);
                } catch (Throwable e) {
                    throw new ServletException(e);
                }
            }
        }
        
    }

同志們看到了嗎,skeletonMap.get(uri);。是不是很像springmvc中根據url定位handlermapping


所以http暴露服務的路徑是contextpath +暴露介面類名稱

最開始我以為可以模擬http請求呼叫dubbo介面,因為網上始終找不到demo,都是各種複製黏貼的內容,所以打算通過原始碼來研究一下是否可以通過模擬http請求來實現呼叫。

當跟進到HttpProtocol 中看到HttpInvokerServiceExporter 感覺不妙

HttpInvokerServiceExporter 是spring-web.jar包下的一個類,以下是介紹

Spring HTTP Invoker一種JAVA遠端方法呼叫框架實現,原理與JDK的RMI基本一致,所以我們先跟其它JAVA遠端方法呼叫實現做下簡單比較。

  • RMI:使用JRMP協議(基於TCP/IP),不允許穿透防火牆,使用JAVA系列化方式,使用於任何JAVA應用之間相互呼叫。

  • Hessian:使用HTTP協議,允許穿透防火牆,使用自己的系列化方式,支援JAVA、C++、.Net等跨語言使用。

  • Burlap: 與Hessian相同,只是Hessian使用二進位制傳輸,而Burlap使用XML格式傳輸(兩個產品均屬於caucho公司的開源產品)。

  • Spring HTTP Invoker: 使用HTTP協議,允許穿透防火牆,使用JAVA系列化方式,但僅限於Spring應用之間使用,即呼叫者與被呼叫者都必須是使用Spring框架的應用。

Spring一定希望大家儘量使用它的產品,但實際專案中我們還是要根據需求來決定選擇哪個框架,下面我們來看看Spring HTTP Invoker的使用。

既然通過是HTTP請求呼叫,那麼客戶端肯定需要一個代理用於幫忙傳送HTTP請求,幫忙做物件系列化和反系列化等,Spring框架中的HttpInvokerServiceExporter類處理這些雜事;而伺服器端需要一個HTTP請求處理器,幫忙處理HTTP請求已經物件系列化和反系列化工作,Spring框架中的HttpInvokerServiceExporter類就是幹這活的,對於Sun JRE 6 的HTTP Server,Spring還提供了SimpleHttpInvokerServiceExporter類供選擇。


網上有一些SimpleHttpInvokerServiceExporter的服務端和客戶端配置說明,這裡就不說明了

之前介紹dubbo說過,服務方就是doExport,消費方就是doRefer。繼續看HttpProtocol 中這二個方法

protected <T> Runnable doExport(final T impl, Class<T> type, URL url) throws RpcException {
        String addr = url.getIp() + ":" + url.getPort();
        HttpServer server = serverMap.get(addr);
        if (server == null) {
            server = httpBinder.bind(url, new InternalHandler());
            serverMap.put(addr, server);
        }
        final HttpInvokerServiceExporter httpServiceExporter = new HttpInvokerServiceExporter();
        httpServiceExporter.setServiceInterface(type);
        httpServiceExporter.setService(impl);
        try {
            httpServiceExporter.afterPropertiesSet();
        } catch (Exception e) {
            throw new RpcException(e.getMessage(), e);
        }
        final String path = url.getAbsolutePath();
        skeletonMap.put(path, httpServiceExporter);
        return new Runnable() {
            public void run() {
                skeletonMap.remove(path);
            }
        };
    }

httpServiceExporter設定的二個引數

Service實現類,一般引用其它bean

ServiceInterface服務型別

這樣就達到了暴露服務

 protected <T> T doRefer(final Class<T> serviceType, final URL url) throws RpcException {
        final HttpInvokerProxyFactoryBean httpProxyFactoryBean = new HttpInvokerProxyFactoryBean();
        httpProxyFactoryBean.setServiceUrl(url.toIdentityString());
        httpProxyFactoryBean.setServiceInterface(serviceType);
        String client = url.getParameter(Constants.CLIENT_KEY);
        if (client == null || client.length() == 0 || "simple".equals(client)) {
        	SimpleHttpInvokerRequestExecutor httpInvokerRequestExecutor = new SimpleHttpInvokerRequestExecutor() {
				protected void prepareConnection(HttpURLConnection con,
						int contentLength) throws IOException {
					super.prepareConnection(con, contentLength);
					con.setReadTimeout(url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT));
					con.setConnectTimeout(url.getParameter(Constants.CONNECT_TIMEOUT_KEY, Constants.DEFAULT_CONNECT_TIMEOUT));
				}
        	};
        	httpProxyFactoryBean.setHttpInvokerRequestExecutor(httpInvokerRequestExecutor);
        } else if ("commons".equals(client)) {
        	CommonsHttpInvokerRequestExecutor httpInvokerRequestExecutor = new CommonsHttpInvokerRequestExecutor();
        	httpInvokerRequestExecutor.setReadTimeout(url.getParameter(Constants.CONNECT_TIMEOUT_KEY, Constants.DEFAULT_CONNECT_TIMEOUT));
        	httpProxyFactoryBean.setHttpInvokerRequestExecutor(httpInvokerRequestExecutor);
        } else if (client != null && client.length() > 0) {
        	throw new IllegalStateException("Unsupported http protocol client " + client + ", only supported: simple, commons");
        }
        httpProxyFactoryBean.afterPropertiesSet();
        return (T) httpProxyFactoryBean.getObject();
    }
ServiceUrl路徑

ServiceInterface服務型別

這樣,消費方在呼叫服務FactoryBean的getObject()的時候,獲取到的代理物件就是httpProxyFactoryBean的物件

所以最後得出結論,dubbo的http協議,只是最後遠端呼叫走的http,消費方通過服務呼叫還是需要類似dubbo協議的呼叫方式。如果需要傳統http訪問方式則需要看dubbox

不過有興趣的可以看看噹噹的dubbox

http://dangdangdotcom.github.io/dubbox/rest.html

在Dubbo中開發REST風格的遠端呼叫