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

Reply via email to