Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

为什么高版本的dubbo对telnet支持不友好了呢? #3105

Closed
jianhao84 opened this issue Dec 31, 2018 · 30 comments · Fixed by #3210
Closed

为什么高版本的dubbo对telnet支持不友好了呢? #3105

jianhao84 opened this issue Dec 31, 2018 · 30 comments · Fixed by #3210

Comments

@jianhao84
Copy link

经常我们测试dubbo服务是通过telnet测试,最近我在自己写个工具,过程中发现了个问题:
在dubbo接口接收的参数是对象时候,我传json格式参数,测试了3个版本:
版本2.5.3:可以成功测试
版本2.5.6和版本2.6.4:需要在json中增加class属性,值为接收对象的全路径才能测试成功.

所以想问下,能不能恢复成2.5.3?不然每次调用接口还需要把类全路径放到json里,很坑爹啊

@zonghaishang
Copy link
Member

@jianhao84 上传个demo, 我瞅瞅

@jianhao84
Copy link
Author

https://gitlab.com/jianhao84/dubborun
我是在做这个dubbo客户端工具的时候遇到的,你可以通过telnet命令试试

@jianhao84
Copy link
Author

@zonghaishang
比如有接口com.jianhao84.service.TestService.test(Person person),Person对象有name和age属性:
情况1:dubbo2.5.3版本,传入以下json字符串就行
{"name":"苍井空","age":18}

情况2:dubbo2.x.x(其他高版本,我也没所有版本都测试,反正就这两种参数方式自己试)需要在json中指定class路径.假设Person类的完整包路径为:com.jianhao84.bean.Person,就需要传入以下json才行
{"name":"苍井空","age":18,"class":"com.jianhao84.bean.Person"}
为什么高版本的dubbo不能和低版本(比如2.5.3)一样对telnet支持友好点呢?

@LiZhenNet
Copy link
Contributor

@zonghaishang
比如有接口com.jianhao84.service.TestService.test(Person person),Person对象有name和age属性:
情况1:dubbo2.5.3版本,传入以下json字符串就行
{"name":"苍井空","age":18}

情况2:dubbo2.x.x(其他高版本,我也没所有版本都测试,反正就这两种参数方式自己试)需要在json中指定class路径.假设Person类的完整包路径为:com.jianhao84.bean.Person,就需要传入以下json才行
{"name":"苍井空","age":18,"class":"com.jianhao84.bean.Person"}
为什么高版本的dubbo不能和低版本(比如2.5.3)一样对telnet支持友好点呢?

是这样的,因为Dubbo 把参数转换成FastJson 的JsonObject,JsonObject继承自Map 所以会去取 class 字段进行类型判断。
我看了一下文档,这部分确实没有说明,而且2.5.x 的分支同样有这个问题,这个周末我会尝试修复一下

@jianhao84
Copy link
Author

@LiZhenNet
是会修复所有有问题的版本吗?
那麻烦啦

@zonghaishang
Copy link
Member

@LiZhenNet 按照他的说法为啥2.5.3没有问题

@zonghaishang
Copy link
Member

2.5.3 解码方式:
list = (List) JSON.parse("[" + args + "]", List.class);

2.7.0解码方式:
list = JSON.parseArray("[" + args + "]", Object.class);

@zonghaishang
Copy link
Member

@chickenlj

看下这个问题,当初提的Pr: 209aecf

@gudegg
Copy link
Contributor

gudegg commented Jan 4, 2019

这个问题是这个PR引入 27917f2
2.5.3

    private static Method findMethod(Exporter<?> exporter, String method, List<Object> args) {
        Invoker<?> invoker = exporter.getInvoker();
        Method[] methods = invoker.getInterface().getMethods();
        Method invokeMethod = null;
        for (Method m : methods) {
            if (m.getName().equals(method) && m.getParameterTypes().length == args.size()) {
                if (invokeMethod != null) { // 重载
                    if (isMatch(invokeMethod.getParameterTypes(), args)) {
                        invokeMethod = m;
                        break;
                    }
                } else {
                    invokeMethod = m;
                }
                invoker = exporter.getInvoker();
            }
        }
        return invokeMethod;
    }

之后版本:

   private static Method findMethod(Exporter<?> exporter, String method, List<Object> args) {
        Invoker<?> invoker = exporter.getInvoker();
        Method[] methods = invoker.getInterface().getMethods();
        for (Method m : methods) {
            if (m.getName().equals(method) && isMatch(m.getParameterTypes(), args)) {
                return m;
            }
        }
        return null;
    }

新版本导致都会执行isMatch,这个方法里面有个逻辑,(ps:telnet传入json格式经过fastjson反序列化后是JsonObject,都会执行下面代码)

else if (arg instanceof Map) {
                String name = (String) ((Map<?, ?>) arg).get("class");
                Class<?> cls = arg.getClass();
                if (name != null && name.length() > 0) {
                    cls = ReflectUtils.forName(name);
                }
                if (!type.isAssignableFrom(cls)) {
                    return false;
                }
            }

当用户没有传class时,clz就是JsonObject.class 导致直接返回false,最终在不传class时,会匹配不到方法,telnet直接会返回找不到方法;之前老代码在调用重载方法时,用户需要传入class进行参数类型判断,无重载方法时无需传入class;PR这样改正之后,导致所有telnet都必须传入class

@LiZhenNet
Copy link
Contributor

LiZhenNet commented Jan 5, 2019

@zonghaishang ,@gudegg ,感谢提醒,
我再之前一个没有合并的 pull request(#3038) 上尝试修复了这个问题,请帮我review一下.

@zonghaishang
Copy link
Member

我已经评论了,看下变更:c833771

@beiwei30
Copy link
Member

beiwei30 commented Jan 7, 2019

@LiZhenNet 我也留了言,你看下,检验阶段做太多的反序列化不是太好,我建议没有 class 的时候简单让他通过就好

@xcorpio
Copy link

xcorpio commented Jan 7, 2019

请问如何在代码中调用 telnet 提供的那些功能呢 @zonghaishang ,必须使用 telnet 协议吗

@LiZhenNet
Copy link
Contributor

@LiZhenNet 我也留了言,你看下,检验阶段做太多的反序列化不是太好,我建议没有 class 的时候简单让他通过就好

我回复你了,我感觉这样是有问题的。

@LiZhenNet
Copy link
Contributor

请问如何在代码中调用 telnet 提供的那些功能呢 @zonghaishang ,必须使用 telnet 协议吗

能具体一点吗?

@xcorpio
Copy link

xcorpio commented Jan 7, 2019

@LiZhenNet 我在做一个测试 Dubbo 接口的工具,在不知道 Dubbo 服务定义的情况下,通过泛化引用来测试Dubbo的接口,再写泛化调用时需要填写实际实现接口的 class 全路径引用, 方法名, 参数列表的全路径引用 ,http://dubbo.apache.org/zh-cn/docs/user/demos/generic-reference.html

我这个工具希望在不知道具体接口定义的情况下(无jar包)提供出 泛化引用需要的那几个参数,其中通过 ZK 可以获取 Address, Interface, Methods 但是不能获取参数列表引用(这个可以通过 Telnet 的 ls 命令获取),所以想知道在已经获得 ReferenceConfigGenericService 的情况下能不能发送个什么指令(类似Telnet 里的 ls命令 )可以获得 参数列表,而不需要再通过 Telnet 协议

@LiZhenNet
Copy link
Contributor

@LiZhenNet 我在做一个测试 Dubbo 接口的工具,在不知道 Dubbo 服务定义的情况下,通过泛化引用来测试Dubbo的接口,再写泛化调用时需要填写实际实现接口的 class 全路径引用, 方法名, 参数列表的全路径引用 ,http://dubbo.apache.org/zh-cn/docs/user/demos/generic-reference.html

我这个工具希望在不知道具体接口定义的情况下(无jar包)提供出 泛化引用需要的那几个参数,其中通过 ZK 可以获取 Address, Interface, Methods 但是不能获取参数列表引用(这个可以通过 Telnet 的 ls 命令获取),所以想知道在已经获得 ReferenceConfigGenericService 的情况下能不能发送个什么指令(类似Telnet 里的 ls命令 )可以获得 参数列表,而不需要再通过 Telnet 协议

Telnet 里的 ls命令 也不会返回参数的列表,你已经获取到了ReferenceConfig 你就应该知道调用的Interface ,也就能知道需要的参数。

@gudegg
Copy link
Contributor

gudegg commented Jan 7, 2019

@LiZhenNet 我在做一个测试 Dubbo 接口的工具,在不知道 Dubbo 服务定义的情况下,通过泛化引用来测试Dubbo的接口,再写泛化调用时需要填写实际实现接口的 class 全路径引用, 方法名, 参数列表的全路径引用 ,http://dubbo.apache.org/zh-cn/docs/user/demos/generic-reference.html
我这个工具希望在不知道具体接口定义的情况下(无jar包)提供出 泛化引用需要的那几个参数,其中通过 ZK 可以获取 Address, Interface, Methods 但是不能获取参数列表引用(这个可以通过 Telnet 的 ls 命令获取),所以想知道在已经获得 ReferenceConfigGenericService 的情况下能不能发送个什么指令(类似Telnet 里的 ls命令 )可以获得 参数列表,而不需要再通过 Telnet 协议

Telnet 里的 ls命令 也不会返回参数的列表,你已经获取到了ReferenceConfig 你就应该知道调用的Interface ,也就能知道需要的参数。

ls -l XxxService 可以获取到参数

@xcorpio
Copy link

xcorpio commented Jan 7, 2019

Telnet 里的 ls命令 也不会返回参数的列表,你已经获取到了ReferenceConfig 你就应该知道调用的Interface ,也就能知道需要的参数。

额,不是参数列表,应该是参数的引用类型列表。我测试dubbo接口理论上我是应该知道需要什么参数的,应为做的是一个通用的工具,希望做到可以有智能提示,下拉框选择之类的方便功能可以直接选择参数列表(UI 工具)

dubbo>ls -l asura.dubbo.service.EchoService
java.lang.String echoString(java.lang.String)
dubbo>

上面 java.lang.String 就是参数类型,这块也可能是自定义的 Model 类型,我需要的是这个类型信息
泛化引用调用时需要:
如: com.xxx.Person

Object result = genericService.$invoke("findPerson", new String[]
{"com.xxx.Person"}, new Object[]{person}); 
...

@gudegg
Copy link
Contributor

gudegg commented Jan 7, 2019

Telnet 里的 ls命令 也不会返回参数的列表,你已经获取到了ReferenceConfig 你就应该知道调用的Interface ,也就能知道需要的参数。

额,不是参数列表,应该是参数的引用类型列表。我测试dubbo接口理论上我是应该知道需要什么参数的,应为做的是一个通用的工具,希望做到可以有智能提示,下拉框选择之类的方便功能可以直接选择参数列表(UI 工具)

dubbo>ls -l asura.dubbo.service.EchoService
java.lang.String echoString(java.lang.String)
dubbo>

上面 java.lang.String 就是参数类型,这块也可能是自定义的 Model 类型,我需要的是这个类型信息
泛化引用调用时需要:
如: com.xxx.Person

Object result = genericService.$invoke("findPerson", new String[]
{"com.xxx.Person"}, new Object[]{person}); 
...

目前消费端是获取不到参数类型,都是提供端的校验的

@LiZhenNet
Copy link
Contributor

@xcorpio 你获取到了 ReferenceConfig 就能获取到接口,通过反射就能拿到想要的参数类型, 建议你单独开一个 issue 来讨论这个问题,我担心这个 issue 会因为本来的问题解决后关掉。

@gudegg
Copy link
Contributor

gudegg commented Jan 7, 2019

@xcorpio 你获取到了 ReferenceConfig 就能获取到接口,通过反射就能拿到想要的参数类型, 建议你单独开一个 issue 来讨论这个问题,我担心这个 issue 会因为本来的问题解决后关掉。

能说下哪个方法吗,确实没看到能获取提供接口类型的

@xcorpio
Copy link

xcorpio commented Jan 7, 2019

@LiZhenNet 移到这了, #3161 , 应该不能通过反射拿到

@zonghaishang
Copy link
Member

zonghaishang commented Jan 7, 2019

@xcorpio telnet调用,代码可以开一个tcp长连接,按照dubbo提供的调用命令,直接写socket数据和读取就可以了

如果你熟悉dubbo框架代码,请阅读:org.apache.dubbo.rpc.protocol.dubbo.telnet.InvokeTelnetHandler

如果不熟悉,请使用sudo tcpdump -i any -A dst port 20880 抓包看返回数据

@xcorpio
Copy link

xcorpio commented Jan 7, 2019

@xcorpio telnet调用,代码可以开一个tcp长连接,按照dubbo提供的调用命令,直接写socket数据和读取就可以了

如果你熟悉dubbo框架代码,请阅读:org.apache.dubbo.rpc.protocol.dubbo.telnet.InvokeTelnetHandler

如果不熟悉,请使用sudo tcpdump -i any -A dst port 20880 抓包看返回数据

OK多谢。@zonghaishang ,我想能不能扩展 泛化调用 ,写成的内置服务。我觉得通过 泛化调用 来调用简单些,如果要这么做,我需要付出什么代价吗,能写扩展实现吗?再创建个长连接,觉得有些麻烦🤔

@zonghaishang
Copy link
Member

@xcorpio
如果用java来实现测试客户端,直接使用dubbo的泛化调用,不需要依赖jar包
如果用c#来实现,你自己要建立tcp长连接调用telnet命令执行

如果使用java的泛化调用,你不需要自己写长连接,泛化封装好了,不需要扩展实现

@beiwei30
Copy link
Member

beiwei30 commented Jan 8, 2019

@LiZhenNet 我也留了言,你看下,检验阶段做太多的反序列化不是太好,我建议没有 class 的时候简单让他通过就好

我回复你了,我感觉这样是有问题的。

PR 上没看到你的留言。我的意思是,如果是 JSONObject 的话,不需要反序列话做检查,直接让它过,最终调用的时候如果不对,肯定出错的。如果对的话,按照你的逻辑,会做两次反序列化,太重了。

@jianhao84
Copy link
Author

Telnet 里的 ls命令 也不会返回参数的列表,你已经获取到了ReferenceConfig 你就应该知道调用的Interface ,也就能知道需要的参数。

额,不是参数列表,应该是参数的引用类型列表。我测试dubbo接口理论上我是应该知道需要什么参数的,应为做的是一个通用的工具,希望做到可以有智能提示,下拉框选择之类的方便功能可以直接选择参数列表(UI 工具)

dubbo>ls -l asura.dubbo.service.EchoService
java.lang.String echoString(java.lang.String)
dubbo>

上面 java.lang.String 就是参数类型,这块也可能是自定义的 Model 类型,我需要的是这个类型信息
泛化引用调用时需要:
如: com.xxx.Person

Object result = genericService.$invoke("findPerson", new String[]
{"com.xxx.Person"}, new Object[]{person}); 
...

开始为了避免自动化测试烦我,用java做了一个dubbo客户端工具,界面太不友好导致别人不愿意用,所以才用winform。。。
满足一般测试开发使用了,参数可以看接口文档:https://gitlab.com/jianhao84/dubborun

@gudegg
Copy link
Contributor

gudegg commented Jan 8, 2019

@LiZhenNet 我也留了言,你看下,检验阶段做太多的反序列化不是太好,我建议没有 class 的时候简单让他通过就好

我回复你了,我感觉这样是有问题的。

PR 上没看到你的留言。我的意思是,如果是 JSONObject 的话,不需要反序列话做检查,直接让它过,最终调用的时候如果不对,肯定出错的。如果对的话,按照你的逻辑,会做两次反序列化,太重了。

  private static Method findMethod(Exporter<?> exporter, String method, List<Object> args) {
        Invoker<?> invoker = exporter.getInvoker();
        Method[] methods = invoker.getInterface().getMethods();
        Method invokeMethod = null;
        for (Method m : methods) {
            if (m.getName().equals(method) && m.getParameterTypes().length == args.size()) {
                if (invokeMethod != null) { // 重载
                    if (isMatch(invokeMethod.getParameterTypes(), args)) {
                        invokeMethod = m;
                        break;
                    }
                } else {
                    invokeMethod = m;
                }
                invoker = exporter.getInvoker();
            }
        }
        return invokeMethod;
    }

可否就用2.5.3的代码逻辑,这样非重载方法都不用调isMatch判断;只有重载方法才要做判断,只是在没有class时会存在调错方法的可能

@gMan1990
Copy link

入参,很长,怎么办?好像输入到一定长度就输入不了了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants