基于 HTTP 表单的远程调用协议,采用 Spring 的 HttpInvoker 实现 1
特性
- 连接个数:多连接
- 连接方式:短连接
- 传输协议:HTTP
- 传输方式:同步传输
- 序列化:表单序列化
- 适用范围:传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看,可用表单或URL传入参数,暂不支持传文件。
- 适用场景:需同时给应用程序和浏览器 JS 使用的服务。
约束
- 参数及返回值需符合 Bean 规范
配置
配置协议:
<dubbo:protocol name="http" port="8080" />
配置 Jetty Server (默认):
<dubbo:protocol ... server="jetty" />
配置 Servlet Bridge Server (推荐使用):
<dubbo:protocol ... server="servlet" />
配置 DispatcherServlet:
<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>/*</url-pattern>
</servlet-mapping>
注意,如果使用 servlet 派发请求:
- 协议的端口
<dubbo:protocol port="8080" />
必须与 servlet 容器的端口相同,
- 协议的上下文路径
<dubbo:protocol contextpath="foo" />
必须与 servlet 应用的上下文路径相同。
接下来我们看看在HttpProtocol中dubbo具体做了什么逻辑处理。
HttpProtocol同样也是提供了两个接口doExport和doRefer
(1)doExport中简单来说是暴露服务,但是httpProtocol其真正是通过容器(内置容器tomcat和jetty或者外部容器)来暴露服务,其只需要将其配置初始化到容器即可。
protected <T> Runnable doExport(final T impl, Class<T> type, URL url) throws RpcException {
//获取暴露服务的地址
String addr = getAddr(url);
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);
}
};
}
(2)doRefer中简单来说就是组建远程调用的IP、端口、上下文和路径信息创建代理类,通过http协议来完成远程调用,在远程调用时消费者会根据url将要调用的接口、方法及参数信息发送给服务提供者,服务提供者根据接口和方法进行反射获取实现类,根据参数运行获取结果,在将结果返回给服务消费者。
@SuppressWarnings("unchecked")
protected <T> T doRefer(final Class<T> serviceType, final URL url) throws RpcException {
final HttpInvokerProxyFactoryBean httpProxyFactoryBean = new HttpInvokerProxyFactoryBean();
//创建服务的url信息
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)) {
HttpComponentsHttpInvokerRequestExecutor httpInvokerRequestExecutor = new HttpComponentsHttpInvokerRequestExecutor();
httpInvokerRequestExecutor.setReadTimeout(url.getParameter(Constants.CONNECT_TIMEOUT_KEY, Constants.DEFAULT_CONNECT_TIMEOUT));
httpProxyFactoryBean.setHttpInvokerRequestExecutor(httpInvokerRequestExecutor);
} else {
throw new IllegalStateException("Unsupported http protocol client " + client + ", only supported: simple, commons");
}
httpProxyFactoryBean.afterPropertiesSet();
//创建http连接的代理类
return (T) httpProxyFactoryBean.getObject();
}