ueizhou opened a new issue, #12907: URL: https://github.com/apache/dubbo/issues/12907
Dubbo 3.2.5 使用 Rest 协议时,如果路径名相同,仅 HttpMethond 不同,当客户端发出请求时,服务器会响应如下错误: `service require request method is : PATCH, but current request method is: POST` 查了一下代码,问题出在 `org.apache.dubbo.metadata.rest.PathMatcher` 类的 `hashCode()` 方法实现错误: ``` @Override public int hashCode() { return Objects.hash(version, group, port); } ``` 这里遗漏了很多属性,在这个问题里,httpMethod 属性没有放到 hashCode() 方法里: ``` public class PathMatcher { private static final String SEPARATOR = "/"; private String path; private String version;// service version private String group;// service group private Integer port;// service port private String[] pathSplits; private boolean hasPathVariable; private String contextPath; private String httpMethod; // for provider http method compare,http 405 private boolean needCompareHttpMethod = true; // compare method directly (for get Invoker by method) private boolean needCompareServiceMethod = false; // service method private Method method; ``` 错误跟踪: 1. 报错点:ServiceInvokeRestFilter 类: ``` // method disallowed if (!restMethodMetadata.getRequest().methodAllowed(request.getMethod())) { nettyHttpResponse.sendError(405, "service require request method is : " + restMethodMetadata.getRequest().getMethod() + ", but current request method is: " + request.getMethod() ); return; } ``` 2. 部署REST服务时,使用了 HashMap 来保存服务信息,key 使用了 PathMatcher,PathMatcher 的 hashCode()方法只有 version,group,port 三个参数,只要这三个相同,就会互相覆盖。 ``` ServiceDeployer.java public void deploy(ServiceRestMetadata serviceRestMetadata, Invoker invoker) { Map<PathMatcher, RestMethodMetadata> pathToServiceMapContainPathVariable = serviceRestMetadata.getPathContainPathVariableToServiceMap(); pathAndInvokerMapper.addPathAndInvoker(pathToServiceMapContainPathVariable, invoker); Map<PathMatcher, RestMethodMetadata> pathToServiceMapUnContainPathVariable = serviceRestMetadata.getPathUnContainPathVariableToServiceMap(); pathAndInvokerMapper.**addPathAndInvoker**(pathToServiceMapUnContainPathVariable, invoker); } PathAndInvokerMapper.java public void addPathAndInvoker(Map<PathMatcher, RestMethodMetadata> metadataMap, Invoker invoker) { metadataMap.entrySet().stream().forEach(entry -> { PathMatcher pathMatcher = entry.getKey(); if (pathMatcher.hasPathVariable()) { **addPathMatcherToPathMap**(pathMatcher, pathToServiceMapContainPathVariable, InvokerAndRestMethodMetadataPair.pair(invoker, entry.getValue())); } else { addPathMatcherToPathMap(pathMatcher, pathToServiceMapNoPathVariable, InvokerAndRestMethodMetadataPair.pair(invoker, entry.getValue())); } }); } PathAndInvokerMapper.java public void addPathMatcherToPathMap(PathMatcher pathMatcher, Map<PathMatcher, InvokerAndRestMethodMetadataPair> pathMatcherPairMap, InvokerAndRestMethodMetadataPair invokerRestMethodMetadataPair) { if (**pathMatcherPairMap.containsKey(pathMatcher)**) { // cover the old service metadata when current interface is old interface & current method desc equals old`s method desc,else ,throw double check exception InvokerAndRestMethodMetadataPair beforeMetadata = pathMatcherPairMap.get(pathMatcher); // true when reExport if (!invokerRestMethodMetadataPair.compareServiceMethod(beforeMetadata)){ throw new DoublePathCheckException( "dubbo rest double path check error, current path is: " + pathMatcher + " ,and service method is: " + invokerRestMethodMetadataPair.getRestMethodMetadata().getReflectMethod() + "before service method is: " + beforeMetadata.getRestMethodMetadata().getReflectMethod() ); } } **pathMatcherPairMap.put(pathMatcher, invokerRestMethodMetadataPair);** logger.info("dubbo rest deploy pathMatcher:" + pathMatcher + ", and service method is :" + invokerRestMethodMetadataPair.getRestMethodMetadata().getReflectMethod()); } ``` Map.containsKey()只是用 hashCode() 来找 bucket 位置,判断时是调用的equals()方法。最终会执行`pathMatcherPairMap.put(pathMatcher, invokerRestMethodMetadataPair);`导致错误发生。 如下是 equals() 方法,判断了httpMethod。 ``` PathMather.java @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PathMatcher that = (PathMatcher) o; return serviceMethodEqual(that, this) || pathMatch(that); } private boolean pathMatch(PathMatcher that) { return (!that.needCompareServiceMethod && !needCompareServiceMethod) // no need service method compare && pathEqual(that) // path compare && Objects.equals(version, that.version) // service version compare && **httpMethodMatch(that) // http method compare** && Objects.equals(group, that.group) && Objects.equals(port, that.port); } ``` -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: notifications-unsubscr...@dubbo.apache.org.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@dubbo.apache.org For additional commands, e-mail: notifications-h...@dubbo.apache.org