Dubbo之telnet實現

weixin_34402408發表於2018-01-05

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一級的說明

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指令的功能以及詳細方法列印。

相關文章