title: Dubbo之telnet實現 tags:
- dubbo
- telnet
- netty categories: dubbo date: 2017-07-25 18:18:53
我們可以通過telnet來訪問道對應dubbo服務的資訊
比如
我們可以利用一些指令來訪問。
我們知道,預設情況下,dubbo使用netty做transport。
那麼dubbo是如何區分開正常業務請求和telnet請求呢?
首先來看一下netty的服務。
NettyServer在開啟是會註冊一些downStream和upStream的event
public class NettyServer extends AbstractServer implements Server {
private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);
private Map<String, Channel> channels; // <ip:port, channel>
private ServerBootstrap bootstrap;
private org.jboss.netty.channel.Channel channel;
public NettyServer(URL url, ChannelHandler handler) throws RemotingException{
super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
}
@Override
protected void doOpen() throws Throwable {
NettyHelper.setNettyLoggerFactory();
ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
bootstrap = new ServerBootstrap(channelFactory);
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
channels = nettyHandler.getChannels();
// https://issues.jboss.org/browse/NETTY-365
// https://issues.jboss.org/browse/NETTY-379
// final Timer timer = new HashedWheelTimer(new NamedThreadFactory("NettyIdleTimer", true));
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() {
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec() ,getUrl(), NettyServer.this);
ChannelPipeline pipeline = Channels.pipeline();
/*int idleTimeout = getIdleTimeout();
if (idleTimeout > 10000) {
pipeline.addLast("timer", new IdleStateHandler(timer, idleTimeout / 1000, 0, 0));
}*/
pipeline.addLast("decoder", adapter.getDecoder());
pipeline.addLast("encoder", adapter.getEncoder());
pipeline.addLast("handler", nettyHandler);
return pipeline;
}
});
// bind
channel = bootstrap.bind(getBindAddress());
}
複製程式碼
其中decoder和encoder對應加解碼,這邊也對應了之前呼叫異常時無法通過attachment傳遞資訊Dubbo自定義異常message過長解決
那麼這邊的nettyHandler最終通過層層包裝委託的機制其實到了真正執行的應該是
HeaderExchangeHandler
複製程式碼
public void received(Channel channel, Object message) throws RemotingException {
channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis());
ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
try {
if (message instanceof Request) {
// handle request.
Request request = (Request) message;
if (request.isEvent()) {
handlerEvent(channel, request);
} else {
if (request.isTwoWay()) {
Response response = handleRequest(exchangeChannel, request);
channel.send(response);
} else {
handler.received(exchangeChannel, request.getData());
}
}
} else if (message instanceof Response) {
handleResponse(channel, (Response) message);
} else if (message instanceof String) {
if (isClientSide(channel)) {
Exception e = new Exception("Dubbo client can not supported string message: " + message + " in channel: " + channel + ", url: " + channel.getUrl());
logger.error(e.getMessage(), e);
} else {
String echo = handler.telnet(channel, (String) message);
if (echo != null && echo.length() > 0) {
channel.send(echo);
}
}
} else {
handler.received(exchangeChannel, message);
}
} finally {
HeaderExchangeChannel.removeChannelIfDisconnected(channel);
}
}
複製程式碼
其中根據請求message的型別進行了區分,如果是request則進行正常的業務呼叫,如果是String則進行telnet的回覆。
這邊的handler型別是ExchangeHandlerAdapter及其對應的子類。
可以確認呼叫telnet時繼續根據spi的方法來查詢對應的實現
public String telnet(Channel channel, String message) throws RemotingException {
String prompt = channel.getUrl().getParameterAndDecoded(Constants.PROMPT_KEY, Constants.DEFAULT_PROMPT);
boolean noprompt = message.contains("--no-prompt");
message = message.replace("--no-prompt", "");
StringBuilder buf = new StringBuilder();
message = message.trim();
String command;
if (message.length() > 0) {
int i = message.indexOf(' ');
if (i > 0) {
command = message.substring(0, i).trim();
message = message.substring(i + 1).trim();
} else {
command = message;
message = "";
}
} else {
command = "";
}
if (command.length() > 0) {
if (extensionLoader.hasExtension(command)) {
try {
String result = extensionLoader.getExtension(command).telnet(channel, message);
if (result == null) {
return null;
}
buf.append(result);
} catch (Throwable t) {
buf.append(t.getMessage());
}
} else {
buf.append("Unsupported command: ");
buf.append(command);
}
}
if (buf.length() > 0) {
buf.append("\r\n");
}
if (prompt != null && prompt.length() > 0 && ! noprompt) {
buf.append(prompt);
}
return buf.toString();
}
複製程式碼
可想而知spi在dubbo服務中完全是非常大規模的使用,可以在專案中借鑑,這也是一種很典型的控制反轉 詳細檢視filter一級的說明
回到TelnetHandler的spi檔案
clear=com.alibaba.dubbo.remoting.telnet.support.command.ClearTelnetHandler
exit=com.alibaba.dubbo.remoting.telnet.support.command.ExitTelnetHandler
help=com.alibaba.dubbo.remoting.telnet.support.command.HelpTelnetHandler
status=com.alibaba.dubbo.remoting.telnet.support.command.StatusTelnetHandler
log=com.alibaba.dubbo.remoting.telnet.support.command.LogTelnetHandler
ls=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.ListTelnetHandler
ps=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.PortTelnetHandler
cd=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.ChangeTelnetHandler
pwd=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.CurrentTelnetHandler
invoke=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.InvokeTelnetHandler
trace=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.TraceTelnetHandler
count=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.CountTelnetHandler
複製程式碼
很明顯這些就是telnet所提供的具體的指令。
這邊以Help為例
@Activate
@Help(parameter = "[command]", summary = "Show help.", detail = "Show help.")
public class HelpTelnetHandler implements TelnetHandler {
private final ExtensionLoader<TelnetHandler> extensionLoader = ExtensionLoader.getExtensionLoader(TelnetHandler.class);
public String telnet(Channel channel, String message) {
if (message.length() > 0) {
if (! extensionLoader.hasExtension(message)) {
return "No such command " + message;
}
TelnetHandler handler = extensionLoader.getExtension(message);
Help help = handler.getClass().getAnnotation(Help.class);
StringBuilder buf = new StringBuilder();
buf.append("Command:\r\n ");
buf.append(message + " " + help.parameter().replace("\r\n", " ").replace("\n", " "));
buf.append("\r\nSummary:\r\n ");
buf.append(help.summary().replace("\r\n", " ").replace("\n", " "));
buf.append("\r\nDetail:\r\n ");
buf.append(help.detail().replace("\r\n", " \r\n").replace("\n", " \n"));
return buf.toString();
} else {
List<List<String>> table = new ArrayList<List<String>>();
List<TelnetHandler> handlers = extensionLoader.getActivateExtension(channel.getUrl(), "telnet");
if (handlers != null && handlers.size() > 0) {
for (TelnetHandler handler : handlers) {
Help help = handler.getClass().getAnnotation(Help.class);
List<String> row = new ArrayList<String>();
String parameter = " " + extensionLoader.getExtensionName(handler) + " " + (help != null ? help.parameter().replace("\r\n", " ").replace("\n", " ") : "");
row.add(parameter.length() > 50 ? parameter.substring(0, 50) + "..." : parameter);
String summary = help != null ? help.summary().replace("\r\n", " ").replace("\n", " ") : "";
row.add(summary.length() > 50 ? summary.substring(0, 50) + "..." : summary);
table.add(row);
}
}
return "Please input \"help [command]\" show detail.\r\n" + TelnetUtils.toList(table);
}
}
}
複製程式碼
根據Help的註解,將各個telnet指令的功能以及詳細方法列印。