28.扩展支持HTTP协议

关于dubbo对http协议的支持,要分几个维度说:

  1. 仅仅传输协议使用http,报文的组织方式没有要求,那么dubbo这个版本(2.5.3)原生提供的http协议就能满足。适当配置即可。此处报文指http请求体与响应体的报文。http协议本身规定了其报文规范,诸如method、url、content-type,header等等,但是其请求体与响应体中内容你可以随意放置。
  2. 传输协议使用http,报文组织希望使用json/xml,那么可以使用当当的dubbox,dubbo在2.6.0之后合入了。使用入门文档。这种形式还分三种:
    1. 非dubbo的消费端调用dubbo的REST服务(non-dubbo –> dubbo)
    2. dubbo消费端调用dubbo的REST服务 (dubbo –> dubbo)
    3. dubbo的消费端调用非dubbo的REST服务 (dubbo –> non-dubbo)
  3. 其他的一些玩法:基于spring mvc的玩法 基于RestExpress的玩法 基于Servlet的玩法

dubbo原生支持的http协议

case配置

在META-INF目录下放置名为com.alibaba.dubbo.rpc.Protocol的文件,里面写上

http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol

配置文件中声明协议使用http

<dubbo:protocol name="http" port="10880"/>

pom中增加spring-web 3.x和jetty等依赖,如果用spring-web4.x的话会有个别类不能发现。

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-web</artifactId>
	<version>3.2.18.RELEASE</version>
</dependency>
<dependency>
	<groupId>javax.servlet</groupId>
	<artifactId>javax.servlet-api</artifactId>
	<version>3.0.1</version>
	<scope>provided</scope>
</dependency>
	
<dependency>
	<groupId>org.mortbay.jetty</groupId>
	<artifactId>jetty</artifactId>
	<version>6.1.26</version>
</dependency>

简单分析其实现

默认的http协议支持,仅仅是传输走http协议的方式,报文的组织方式是用的spring-web中的java序列化的方式。

实现主要依赖spring-web 3.x版本加jetty实现。

用wireshark抓到的请求报文如下:

POST /com.code260.ss.dubbo.demov.facade.service.HelloService HTTP/1.1
Content-Type: application/x-java-serialized-object
Accept-Encoding: gzip
User-Agent: Java/1.8.0_121
Host: 172.17.0.224:10880
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 343

....sr.5org.springframework.remoting.support.RemoteInvocation_l...
.
...[.	argumentst..[Ljava/lang/Object;L.
attributest..Ljava/util/Map;L.
methodNamet..Ljava/lang/String;[..parameterTypest..[Ljava/lang/Class;xpur..[Ljava.lang.Object;..X..s)l...xp....t..Simonpt..sayHellour..[Ljava.lang.Class;......Z....xp....vr..java.lang.String...8z;.B...xpHTTP/1.1 200 OK
Content-Type: application/x-java-serialized-object
Transfer-Encoding: chunked
Server: Jetty(6.1.26)

....sr.;org.springframework.remoting.support.RemoteInvocationResult.....IJm...L.	exceptiont..Ljava/lang/Throwable;L..valuet..Ljava/lang/Object;xppp

provider端解码请求的堆栈:

HttpInvokerServiceExporter(RemoteInvocationSerializingExporter).doReadRemoteInvocation(ObjectInputStream) line: 142	
HttpInvokerServiceExporter.readRemoteInvocation(HttpServletRequest, InputStream) line: 121	
HttpInvokerServiceExporter.readRemoteInvocation(HttpServletRequest) line: 100	
HttpInvokerServiceExporter.handleRequest(HttpServletRequest, HttpServletResponse) line: 77	
HttpProtocol$InternalHandler.handle(HttpServletRequest, HttpServletResponse) line: 81	
DispatcherServlet.service(HttpServletRequest, HttpServletResponse) line: 64	
DispatcherServlet(HttpServlet).service(ServletRequest, ServletResponse) line: 770	
ServletHolder.handle(ServletRequest, ServletResponse) line: 511	
ServletHandler.handle(String, HttpServletRequest, HttpServletResponse, int) line: 401	
Server(HandlerWrapper).handle(String, HttpServletRequest, HttpServletResponse, int) line: 152	
Server.handle(HttpConnection) line: 322	
HttpConnection.handleRequest() line: 542	
HttpConnection$RequestHandler.content(Buffer) line: 945	
HttpParser.parseNext() line: 756	
HttpParser.parseAvailable() line: 218	
HttpConnection.handle() line: 404	
SelectChannelConnector$ConnectorEndPoint(SelectChannelEndPoint).run() line: 410	
QueuedThreadPool$PoolThread.run() line: 582

解码的代码由spring-web完成,代码如下:

	protected RemoteInvocation doReadRemoteInvocation(ObjectInputStream ois)
			throws IOException, ClassNotFoundException {

141		Object obj = ois.readObject();
142		if (!(obj instanceof RemoteInvocation)) {
143			throw new RemoteException("Deserialized object needs to be assignable to type [" +
					RemoteInvocation.class.getName() + "]: " + obj);
		}
		return (RemoteInvocation) obj;
	}

141行从流中读出来的这个obj(反序列化出来)就是RemoteInvocation。这个RemoteInvocation中有方法名、参数类型、参数值等,调试其看到其信息如下:

[Simon]
sayHello
class java.lang.String

141行的ois是org.springframework.remoting.rmi.CodebaseAwareObjectInputStream。

dubbo合入dubbox之后http rest的支持

case配置

dubbo2.5.3在此处用不了了,我们换成2.6.8。

pom依赖

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.code260.ss</groupId>
	<artifactId>dubbox-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>pom</packaging>

	<dependencies>
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-client</artifactId>
			<version>2.12.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-framework</artifactId>
			<version>2.12.0</version>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>dubbo</artifactId>
			<version>2.6.8</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>3.2.18.RELEASE</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>3.2.18.RELEASE</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>3.2.18.RELEASE</version>
		</dependency>

		<dependency>
			<groupId>com.101tec</groupId>
			<artifactId>zkclient</artifactId>
			<version>0.10</version>
		</dependency>

		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.0.1</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>org.mortbay.jetty</groupId>
			<artifactId>jetty</artifactId>
			<version>6.1.26</version>
		</dependency>

		<dependency>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>resteasy-jaxrs</artifactId>
			<version>3.0.7.Final</version>
		</dependency>
		<dependency>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>resteasy-client</artifactId>
			<version>3.0.7.Final</version>
		</dependency>
		<dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<version>1.0.0.GA</version>
		</dependency>

		<!-- 如果要使用json序列化 -->
		<dependency>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>resteasy-jackson-provider</artifactId>
			<version>3.0.7.Final</version>
		</dependency>

		<!-- 如果要使用xml序列化 -->
		<dependency>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>resteasy-jaxb-provider</artifactId>
			<version>3.0.7.Final</version>
		</dependency>

		<!-- 如果要使用netty server -->
		<dependency>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>resteasy-netty</artifactId>
			<version>3.0.7.Final</version>
		</dependency>

		<!-- 如果要使用Sun HTTP server -->
		<dependency>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>resteasy-jdk-http</artifactId>
			<version>3.0.7.Final</version>
		</dependency>

		<!-- 如果要使用tomcat server -->
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-core</artifactId>
			<version>8.0.11</version>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-logging-juli</artifactId>
			<version>8.0.11</version>
		</dependency>

		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
			<version>4.5.3</version>
		</dependency>
	</dependencies>

	<build>
		<pluginManagement>
			<plugins>
				<plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-compiler-plugin</artifactId>
					<version>3.1</version>
					<configuration>
						<source>1.8</source>
						<target>1.8</target>
					</configuration>
				</plugin>
			</plugins>
		</pluginManagement>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-resources-plugin</artifactId>
				<version>3.0.2</version>
				<configuration>
					<delimiters>
						<delimiter>@</delimiter>
					</delimiters>
					<useDefaultDelimiters>false</useDefaultDelimiters>
					<encoding>UTF-8</encoding>
				</configuration>
			</plugin>
		</plugins>
		<resources>
			<resource>
				<directory>src/main/resources</directory>
				<filtering>true</filtering>
			</resource>
			<resource>
				<directory>src/main/java</directory>
				<includes>
					<include>**/*.xml</include>
				</includes>
			</resource>
		</resources>
	</build>

	<modules>
		<module>dubbox-demo-v-facade</module>
		<module>dubbox-demo-v-server</module>
		<module>dubbox-demo-v-client</module>
	</modules>
</project>

接口配置

@Path("hello")
public interface HelloService
{
	@POST
    @Path("sayHello")
    @Consumes({MediaType.APPLICATION_JSON})
    public void sayHello(HelloReq req);

}

协议配置

<dubbo:protocol name="rest" port="8090"/>

简单分析其实现

wireshark抓到的报文

POST /hello/sayHello HTTP/1.1
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 16
Host: 172.17.0.224:8090
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.5.3 (Java/1.8.0_121)

{"name":"simon"}HTTP/1.1 204 No Content
Server: Jetty(6.1.26)

入口协议:

rest=com.alibaba.dubbo.rpc.protocol.rest.RestProtocol

handler: com.alibaba.dubbo.rpc.protocol.rest.DubboHttpServer$RestHandler

实现了dubbo的com.alibaba.dubbo.remoting.http.HttpHandler

接口约定行为如下:

void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;

到handler的调用堆栈

com.alibaba.dubbo.rpc.protocol.rest.DubboHttpServer$RestHandler.handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) line: 88	
com.alibaba.dubbo.remoting.http.servlet.DispatcherServlet.service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) line: 61	
com.alibaba.dubbo.remoting.http.servlet.DispatcherServlet(javax.servlet.http.HttpServlet).service(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 770	
org.mortbay.jetty.servlet.ServletHolder.handle(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 511	
org.mortbay.jetty.servlet.ServletHandler.handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) line: 401	
org.mortbay.jetty.servlet.SessionHandler.handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) line: 182	
org.mortbay.jetty.servlet.Context(org.mortbay.jetty.handler.ContextHandler).handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) line: 766	
org.mortbay.jetty.Server(org.mortbay.jetty.handler.HandlerWrapper).handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) line: 152	
org.mortbay.jetty.Server.handle(org.mortbay.jetty.HttpConnection) line: 326	
org.mortbay.jetty.HttpConnection.handleRequest() line: 542	
org.mortbay.jetty.HttpConnection$RequestHandler.content(org.mortbay.io.Buffer) line: 945	
org.mortbay.jetty.HttpParser.parseNext() line: 756	
org.mortbay.jetty.HttpParser.parseAvailable() line: 218	
org.mortbay.jetty.HttpConnection.handle() line: 404	
org.mortbay.jetty.nio.SelectChannelConnector$ConnectorEndPoint(org.mortbay.io.nio.SelectChannelEndPoint).run() line: 410	
org.mortbay.thread.QueuedThreadPool$PoolThread.run() line: 582

com.alibaba.dubbo.rpc.protocol.rest.DubboHttpServer$RestHandler 中的HttpServletDispatcher
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher@6e62b305

调用到业务方法的堆栈:

com.code260.ss.dubbo.demov.server.service.impl.HelloServiceImpl.sayHello(com.code260.ss.dubbo.demov.facade.bean.HelloReq) line: 37	
com.alibaba.dubbo.common.bytecode.Wrapper1.invokeMethod(java.lang.Object, java.lang.String, java.lang.Class[], java.lang.Object[]) line: not available	
com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(T, java.lang.String, java.lang.Class<?>[], java.lang.Object[]) line: 47	
com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1(com.alibaba.dubbo.rpc.proxy.AbstractProxyInvoker<T>).invoke(com.alibaba.dubbo.rpc.Invocation) line: 76	
com.alibaba.dubbo.config.invoker.DelegateProviderMetaDataInvoker<T>.invoke(com.alibaba.dubbo.rpc.Invocation) line: 52	
com.alibaba.dubbo.registry.integration.RegistryProtocol$InvokerDelegete<T>(com.alibaba.dubbo.rpc.protocol.InvokerWrapper<T>).invoke(com.alibaba.dubbo.rpc.Invocation) line: 56	
com.alibaba.dubbo.rpc.filter.ExceptionFilter.invoke(com.alibaba.dubbo.rpc.Invoker<?>, com.alibaba.dubbo.rpc.Invocation) line: 62	
com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(com.alibaba.dubbo.rpc.Invocation) line: 72	
// ......
com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(com.alibaba.dubbo.rpc.Invocation) line: 72	
com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) line: 52	
com.alibaba.dubbo.common.bytecode.proxy0.sayHello(com.code260.ss.dubbo.demov.facade.bean.HelloReq) line: not available	
sun.reflect.NativeMethodAccessorImpl.invoke0(java.lang.reflect.Method, java.lang.Object, java.lang.Object[]) line: not available [native method]	
sun.reflect.NativeMethodAccessorImpl.invoke(java.lang.Object, java.lang.Object[]) line: 62	
sun.reflect.DelegatingMethodAccessorImpl.invoke(java.lang.Object, java.lang.Object[]) line: 43	
java.lang.reflect.Method.invoke(java.lang.Object, java.lang.Object...) line: 498	
org.jboss.resteasy.core.MethodInjectorImpl.invoke(org.jboss.resteasy.spi.HttpRequest, org.jboss.resteasy.spi.HttpResponse, java.lang.Object) line: 137	
org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(org.jboss.resteasy.spi.HttpRequest, org.jboss.resteasy.spi.HttpResponse, java.lang.Object) line: 288	
org.jboss.resteasy.core.ResourceMethodInvoker.invoke(org.jboss.resteasy.spi.HttpRequest, org.jboss.resteasy.spi.HttpResponse, java.lang.Object) line: 242	
org.jboss.resteasy.core.ResourceMethodInvoker.invoke(org.jboss.resteasy.spi.HttpRequest, org.jboss.resteasy.spi.HttpResponse) line: 229	
org.jboss.resteasy.core.SynchronousDispatcher.invoke(org.jboss.resteasy.spi.HttpRequest, org.jboss.resteasy.spi.HttpResponse, org.jboss.resteasy.core.ResourceInvoker) line: 356	
org.jboss.resteasy.core.SynchronousDispatcher.invoke(org.jboss.resteasy.spi.HttpRequest, org.jboss.resteasy.spi.HttpResponse) line: 179	
org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, boolean) line: 220	
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) line: 56	
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) line: 51	
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher(javax.servlet.http.HttpServlet).service(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 770	
com.alibaba.dubbo.rpc.protocol.rest.DubboHttpServer$RestHandler.handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) line: 89

总体上看,默认情况下 http server由jetty实现。rest部分由resteasy实现。当然,也可以配置成其他方式。