dubbo組成原理-http服務消費端如何呼叫
dubbo協議已經用的很多了,這裡來稍微介紹一下http協議,官方對http協議的說明簡直少的讓人髮指。哈哈
百度大部分都只是講了http服務端的配置
那就先從服務端的配置說起
dubbo需要的jar包這裡就不說明了,網上找些maven的pom就可以
web.xml配置servlet,注意url-pattern 是需要攔截哪些請求
接著配置dubbo配置檔案<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: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包下的一個類,以下是介紹
-
RMI:使用JRMP協議(基於TCP/IP),不允許穿透防火牆,使用JAVA系列化方式,使用於任何JAVA應用之間相互呼叫。
-
Hessian:使用HTTP協議,允許穿透防火牆,使用自己的系列化方式,支援JAVA、C++、.Net等跨語言使用。
-
Burlap: 與Hessian相同,只是Hessian使用二進位制傳輸,而Burlap使用XML格式傳輸(兩個產品均屬於caucho公司的開源產品)。
-
Spring HTTP Invoker: 使用HTTP協議,允許穿透防火牆,使用JAVA系列化方式,但僅限於Spring應用之間使用,即呼叫者與被呼叫者都必須是使用Spring框架的應用。
網上有一些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