周末看到社区的协议迁移开始被提交了pr,还没merge,打算拜读一下
看到HttpRemoteInvocation被更改了,这里是要把json-rpc
协议转换为http
协议
为什么要转换呢?先来看看两种协议的不同
HTTP 请求本身也可以看做是 RPC 的一种具体形式。HTTP 请求也一样是可以从本地发一个信号到服务器,服务器上执行某个函数,然后返回一些信息给客户端。
经常用的一些数据通过HTTP协议来传输,thrift,grpc,xml-rpc,json-rpc都是通过HTTP传输的。也就说json-rpc是依赖http协议传输的,所以新建一个公共的代理协议,凡是用http协议传输的数据格式都设置这个协议,是比较好的方案。
根据dubbo的类机构继承图,是沿用SPI机制实现的AbstractProxyProtocol
这里比较重要的是protocolBindingRefer
方法。,构建一个DubboInvoker对象,根据dubbo的动态代理不断找到调用方的目标对象,添加到invoke当中,但这里没有指定要传输的格式,所以在dubbo序列化的时候还是会走默认的hessian协议,需要对协议中传入的格式进行校验
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.protocol;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.remoting.Constants;
import org.apache.dubbo.rpc.Exporter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.ProxyFactory;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcException;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
/**
* AbstractProxyProtocol
*/
public abstract class AbstractProxyProtocol extends AbstractProtocol {
private final List> rpcExceptions = new CopyOnWriteArrayList>();
private ProxyFactory proxyFactory;
public AbstractProxyProtocol() {
}
public AbstractProxyProtocol(Class>... exceptions) {
for (Class> exception : exceptions) {
addRpcException(exception);
}
}
public void addRpcException(Class> exception) {
this.rpcExceptions.add(exception);
}
public ProxyFactory getProxyFactory() {
return proxyFactory;
}
public void setProxyFactory(ProxyFactory proxyFactory) {
this.proxyFactory = proxyFactory;
}
@Override
@SuppressWarnings("unchecked")
public Exporter export(final Invoker invoker) throws RpcException {
final String uri = serviceKey(invoker.getUrl());
Exporter exporter = (Exporter) exporterMap.get(uri);
if (exporter != null) {
// When modifying the configuration through override, you need to re-expose the newly modified service.
if (Objects.equals(exporter.getInvoker().getUrl(), invoker.getUrl())) {
return exporter;
}
}
final Runnable runnable = doExport(proxyFactory.getProxy(invoker, true), invoker.getInterface(), invoker.getUrl());
exporter = new AbstractExporter(invoker) {
@Override
public void unexport() {
super.unexport();
exporterMap.remove(uri);
if (runnable != null) {
try {
runnable.run();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
};
exporterMap.put(uri, exporter);
return exporter;
}
@Override
protected Invoker protocolBindingRefer(final Class type, final URL url) throws RpcException {
final Invoker target = proxyFactory.getInvoker(doRefer(type, url), type, url);
Invoker invoker = new AbstractInvoker(type, url) {
@Override
protected Result doInvoke(Invocation invocation) throws Throwable {
try {
Result result = target.invoke(invocation);
// FIXME result is an AsyncRpcResult instance.
Throwable e = result.getException();
if (e != null) {
for (Class> rpcException : rpcExceptions) {
if (rpcException.isAssignableFrom(e.getClass())) {
throw getRpcException(type, url, invocation, e);
}
}
}
return result;
} catch (RpcException e) {
if (e.getCode() == RpcException.UNKNOWN_EXCEPTION) {
e.setCode(getErrorCode(e.getCause()));
}
throw e;
} catch (Throwable e) {
throw getRpcException(type, url, invocation, e);
}
}
};
invokers.add(invoker);
return invoker;
}
protected RpcException getRpcException(Class> type, URL url, Invocation invocation, Throwable e) {
RpcException re = new RpcException("Failed to invoke remote service: " + type + ", method: "
+ invocation.getMethodName() + ", cause: " + e.getMessage(), e);
re.setCode(getErrorCode(e));
return re;
}
protected String getAddr(URL url) {
String bindIp = url.getParameter(Constants.BIND_IP_KEY, url.getHost());
if (url.getParameter(ANYHOST_KEY, false)) {
bindIp = ANYHOST_VALUE;
}
return NetUtils.getIpByHost(bindIp) + ":" + url.getParameter(Constants.BIND_PORT_KEY, url.getPort());
}
protected int getErrorCode(Throwable e) {
return RpcException.UNKNOWN_EXCEPTION;
}
protected abstract Runnable doExport(T impl, Class type, URL url) throws RpcException;
protected abstract T doRefer(Class type, URL url) throws RpcException;
}
基本思路:将参数中的service(传输格式传入)设置到dubbo的代理,拿到代理之后添加到invoke的责任链当中,返回一个dubbo的代理对象给调用方
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.protocol.httpinvoker;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.common.Version;
import org.apache.dubbo.remoting.Constants;
import org.apache.dubbo.remoting.http.HttpBinder;
import org.apache.dubbo.remoting.http.HttpHandler;
import org.apache.dubbo.remoting.http.HttpServer;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.protocol.AbstractProxyProtocol;
import org.apache.dubbo.rpc.service.GenericService;
import org.apache.dubbo.rpc.support.ProtocolUtils;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.remoting.httpinvoker.HttpComponentsHttpInvokerRequestExecutor;
import org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean;
import org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter;
import org.springframework.remoting.httpinvoker.SimpleHttpInvokerRequestExecutor;
import org.springframework.remoting.support.RemoteInvocation;
import org.springframework.remoting.support.RemoteInvocationFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_TIMEOUT;
import static org.apache.dubbo.common.constants.CommonConstants.RELEASE_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
import static org.apache.dubbo.remoting.Constants.DUBBO_VERSION_KEY;
import static org.apache.dubbo.rpc.Constants.GENERIC_KEY;
/**
* HttpInvokerProtocol
*/
public class HttpInvokerProtocol extends AbstractProxyProtocol {
public static final int DEFAULT_PORT = 80;
private final Map serverMap = new ConcurrentHashMap();
private final Map skeletonMap = new ConcurrentHashMap();
private HttpBinder httpBinder;
public HttpInvokerProtocol() {
super(RemoteAccessException.class);
}
public void setHttpBinder(HttpBinder httpBinder) {
this.httpBinder = httpBinder;
}
@Override
public int getDefaultPort() {
return DEFAULT_PORT;
}
@Override
protected Runnable doExport(final T impl, Class 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 String path = url.getAbsolutePath();
skeletonMap.put(path, createExporter(impl, type));
final String genericPath = path + "/" + GENERIC_KEY;
skeletonMap.put(genericPath, createExporter(impl, GenericService.class));
return new Runnable() {
@Override
public void run() {
skeletonMap.remove(path);
skeletonMap.remove(genericPath);
}
};
}
private HttpInvokerServiceExporter createExporter(T impl, Class> type) {
final HttpInvokerServiceExporter httpServiceExporter = new HttpInvokerServiceExporter();
httpServiceExporter.setServiceInterface(type);
httpServiceExporter.setService(impl);
try {
httpServiceExporter.afterPropertiesSet();
} catch (Exception e) {
throw new RpcException(e.getMessage(), e);
}
return httpServiceExporter;
}
@Override
@SuppressWarnings("unchecked")
protected T doRefer(final Class serviceType, final URL url) throws RpcException {
final String generic = url.getParameter(GENERIC_KEY);
final boolean isGeneric = ProtocolUtils.isGeneric(generic) || serviceType.equals(GenericService.class);
final HttpInvokerProxyFactoryBean httpProxyFactoryBean = new HttpInvokerProxyFactoryBean();
httpProxyFactoryBean.setRemoteInvocationFactory(new RemoteInvocationFactory() {
@Override
public RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {
RemoteInvocation invocation;
/*
package was renamed to 'org.apache.dubbo' in v2.7.0, so only provider versions after v2.7.0 can
recognize org.apache.xxx.HttpRemoteInvocation'.
*/
if (Version.isRelease270OrHigher(url.getParameter(RELEASE_KEY))) {
invocation = new HttpRemoteInvocation(methodInvocation);
} else {
/*
The customized 'com.alibaba.dubbo.rpc.protocol.http.HttpRemoteInvocation' was firstly introduced
in v2.6.3. The main purpose is to support transformation of attachments in HttpInvokerProtocol, see
https://github.com/apache/dubbo/pull/1827. To guarantee interoperability with lower
versions, we need to check if the provider is v2.6.3 or higher before sending customized
HttpRemoteInvocation.
*/
if (Version.isRelease263OrHigher(url.getParameter(DUBBO_VERSION_KEY))) {
invocation = new com.alibaba.dubbo.rpc.protocol.http.HttpRemoteInvocation(methodInvocation);
} else {
invocation = new RemoteInvocation(methodInvocation);
}
}
if (isGeneric) {
invocation.addAttribute(GENERIC_KEY, generic);
}
return invocation;
}
});
String key = url.toIdentityString();
if (isGeneric) {
key = key + "/" + GENERIC_KEY;
}
httpProxyFactoryBean.setServiceUrl(key);
httpProxyFactoryBean.setServiceInterface(serviceType);
String client = url.getParameter(Constants.CLIENT_KEY);
if (StringUtils.isEmpty(client) || "simple".equals(client)) {
SimpleHttpInvokerRequestExecutor httpInvokerRequestExecutor = new SimpleHttpInvokerRequestExecutor() {
@Override
protected void prepareConnection(HttpURLConnection con,
int contentLength) throws IOException {
super.prepareConnection(con, contentLength);
con.setReadTimeout(url.getParameter(TIMEOUT_KEY, 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(TIMEOUT_KEY, DEFAULT_TIMEOUT));
httpInvokerRequestExecutor.setConnectTimeout(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();
return (T) httpProxyFactoryBean.getObject();
}
@Override
protected int getErrorCode(Throwable e) {
if (e instanceof RemoteAccessException) {
e = e.getCause();
}
if (e != null) {
Class> cls = e.getClass();
if (SocketTimeoutException.class.equals(cls)) {
return RpcException.TIMEOUT_EXCEPTION;
} else if (IOException.class.isAssignableFrom(cls)) {
return RpcException.NETWORK_EXCEPTION;
} else if (ClassNotFoundException.class.isAssignableFrom(cls)) {
return RpcException.SERIALIZATION_EXCEPTION;
}
}
return super.getErrorCode(e);
}
private class InternalHandler implements HttpHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
String uri = request.getRequestURI();
HttpInvokerServiceExporter skeleton = skeletonMap.get(uri);
if (!"POST".equalsIgnoreCase(request.getMethod())) {
response.setStatus(500);
} else {
RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort());
try {
skeleton.handleRequest(request, response);
} catch (Throwable e) {
throw new ServletException(e);
}
}
}
}
}