{"action":"created","comment":{"url":"https://api.github.com/repos/netty/netty/pulls/comments/32896306","id":32896306,"diff_hunk":"@@ -0,0 +1,257 @@\n+/*\n+ * Copyright 2014 The Netty Project\n+ *\n+ * The Netty Project licenses this file to you under the Apache License,\n+ * version 2.0 (the \"License\"); you may not use this file except in compliance\n+ * with the License. You may obtain a copy of the License at:\n+ *\n+ *   http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n+ * License for the specific language governing permissions and limitations\n+ * under the License.\n+ */\n+package io.netty.handler.codec.http.router;\n+\n+import io.netty.handler.codec.http.QueryStringDecoder;\n+\n+import java.io.UnsupportedEncodingException;\n+import java.net.URLEncoder;\n+import java.util.Collections;\n+import java.util.HashMap;\n+import java.util.HashSet;\n+import java.util.Map;\n+import java.util.Set;\n+\n+/**\n+ * Router that doesn't contain information about HTTP request methods and route\n+ * matching orders.\n+ */\n+class OrderlessRouter<T> {\n+    // A path can only point to one target\n+    private final Map<Path, T> routes = new HashMap<Path, T>();\n+\n+    // Reverse index to create reverse routes fast (a target can have multiple paths)\n+    private final Map<T, Set<Path>> reverseRoutes = new HashMap<T, Set<Path>>();\n+\n+    //--------------------------------------------------------------------------\n+\n+    public Map<Path, T> routes() {\n+        return routes;\n+    }\n+\n+    /**\n+     * This method does nothing if the path has already been added.\n+     * A path can only point to one target.\n+     */\n+    public OrderlessRouter<T> addRoute(String path, T target) {\n+        Path p = new Path(path);\n+        if (routes.containsKey(path)) {\n+            return this;\n+        }\n+\n+        routes.put(p, target);\n+        addReverseRoute(target, p);\n+        return this;\n+    }\n+\n+    private void addReverseRoute(T target, Path path) {\n+        Set<Path> paths = reverseRoutes.get(target);\n+        if (paths == null) {\n+            paths = new HashSet<Path>();\n+            paths.add(path);\n+            reverseRoutes.put(target, paths);\n+        } else {\n+            paths.add(path);\n+        }\n+    }\n+\n+    //--------------------------------------------------------------------------\n+\n+    /** Removes the route specified by the path. */\n+    public void removePath(String path) {\n+        Path p      = new Path(path);\n+        T    target = routes.remove(p);\n+        if (target == null) {\n+            return;\n+        }\n+\n+        Set<Path> paths = reverseRoutes.remove(target);\n+        paths.remove(p);\n+    }\n+\n+    /** Removes all routes leading to the target. */\n+    public void removeTarget(T target) {\n+        Set<Path> paths = reverseRoutes.remove(target);\n+        if (paths == null) {\n+            return;\n+        }\n+\n+        // A path can only point to one target.\n+        // A target can have multiple paths.\n+        // Remove all paths leading to this target.\n+        for (Path path : paths) {\n+            routes.remove(path);\n+        }\n+    }\n+\n+    //--------------------------------------------------------------------------\n+\n+    /** @return {@code null} if no match; note: {@code queryParams} is not set in {@link RouteResult} */\n+    public RouteResult<T> route(String path) {\n+        return route(Path.removeSlashesAtBothEnds(path).split(\"/\"));\n+    }\n+\n+    /** @return {@code null} if no match; note: {@code queryParams} is not set in {@link RouteResult} */\n+    public RouteResult<T> route(String[] requestPathTokens) {\n+        // Optimization note:\n+        // - Reuse tokens and pathParams in the loop\n+        // - decoder doesn't decode anything if decoder.parameters is not called\n+        Map<String, String> pathParams = new HashMap<String, String>();\n+        for (Map.Entry<Path, T> entry : routes.entrySet()) {\n+            Path path = entry.getKey();\n+            if (path.match(requestPathTokens, pathParams)) {\n+                T target = entry.getValue();\n+                return new RouteResult(target, pathParams, null);\n+            }\n+\n+            // Reset for the next loop\n+            pathParams.clear();\n+        }\n+\n+        return null;\n+    }\n+\n+    /** Checks if there's any matching route. */\n+    public boolean anyMatched(String[] requestPathTokens) {\n+        Map<String, String> pathParams = new HashMap<String, String>();\n+        for (Path path : routes.keySet()) {\n+            if (path.match(requestPathTokens, pathParams)) {\n+                return true;\n+            }\n+\n+            // Reset for the next loop\n+            pathParams.clear();\n+        }\n+\n+        return false;\n+    }\n+\n+    //--------------------------------------------------------------------------\n+\n+    /**\n+     * Given a target and params, this method tries to do the reverse routing\n+     * and returns the path.\n+     *\n+     * The params are put to placeholders in the path.\n+     * The params can be a map of {@code placeholder name -> value}\n+     * or ordered values. If a param doesn't have a placeholder, it will be put\n+     * to the query part of the path.\n+     *\n+     * @return {@code null} if there's no match\n+     */\n+    @SuppressWarnings(\"unchecked\")\n+    public String path(T target, Object... params) {\n+        if (params.length == 0) {\n+            return path(target, Collections.emptyMap());\n+        }\n+\n+        if (params.length == 1 && params[0] instanceof Map<?, ?>) {\n+            return pathMap(target, (Map<Object, Object>) params[0]);\n+        }\n+\n+        if (params.length % 2 == 1) {\n+            throw new IllegalArgumentException(\"Missing value for param: \" + params[params.length - 1]);\n+        }\n+\n+        Map<Object, Object> map = new HashMap<Object, Object>(params.length / 2);\n+        for (int i = 0; i < params.length; i += 2) {\n+            String key   = params[i].toString();\n+            String value = params[i + 1].toString();\n+            map.put(key, value);\n+        }\n+        return pathMap(target, map);\n+    }\n+\n+    /** @return {@code null} if there's no match */\n+    private String pathMap(T target, Map<Object, Object> params) {\n+        Set<Path> paths = reverseRoutes.get(target);\n+        if (paths == null) {\n+            return null;\n+        }\n+\n+        try {\n+            // The best one is the one with minimum number of params in the query\n+            String bestCandidate  = null;\n+            int    minQueryParams = Integer.MAX_VALUE;\n+\n+            boolean     matched  = true;\n+            Set<String> usedKeys = new HashSet<String>();\n+\n+            for (Path path : paths) {\n+                matched = true;\n+                usedKeys.clear();\n+\n+                StringBuilder b = new StringBuilder();","path":"codec-http/src/main/java/io/netty/handler/codec/http/router/OrderlessRouter.java","position":197,"original_position":197,"commit_id":"65281000bbcb76f4228b7affb9e6d955d21320ac","original_commit_id":"65281000bbcb76f4228b7affb9e6d955d21320ac","user":{"login":"normanmaurer","id":439362,"avatar_url":"https://avatars.githubusercontent.com/u/439362?v=3","gravatar_id":"","url":"https://api.github.com/users/normanmaurer","html_url":"https://github.com/normanmaurer","followers_url":"https://api.github.com/users/normanmaurer/followers","following_url":"https://api.github.com/users/normanmaurer/following{/other_user}","gists_url":"https://api.github.com/users/normanmaurer/gists{/gist_id}","starred_url":"https://api.github.com/users/normanmaurer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/normanmaurer/subscriptions","organizations_url":"https://api.github.com/users/normanmaurer/orgs","repos_url":"https://api.github.com/users/normanmaurer/repos","events_url":"https://api.github.com/users/normanmaurer/events{/privacy}","received_events_url":"https://api.github.com/users/normanmaurer/received_events","type":"User","site_admin":false},"body":"use proper initial capacity","created_at":"2015-06-21T18:59:42Z","updated_at":"2015-06-21T18:59:42Z","html_url":"https://github.com/netty/netty/pull/3901#discussion_r32896306","pull_request_url":"https://api.github.com/repos/netty/netty/pulls/3901","_links":{"self":{"href":"https://api.github.com/repos/netty/netty/pulls/comments/32896306"},"html":{"href":"https://github.com/netty/netty/pull/3901#discussion_r32896306"},"pull_request":{"href":"https://api.github.com/repos/netty/netty/pulls/3901"}}},"pull_request":{"url":"https://api.github.com/repos/netty/netty/pulls/3901","id":38071424,"html_url":"https://github.com/netty/netty/pull/3901","diff_url":"https://github.com/netty/netty/pull/3901.diff","patch_url":"https://github.com/netty/netty/pull/3901.patch","issue_url":"https://api.github.com/repos/netty/netty/issues/3901","number":3901,"state":"open","locked":false,"title":"Add HTTP router","user":{"login":"ngocdaothanh","id":10095,"avatar_url":"https://avatars.githubusercontent.com/u/10095?v=3","gravatar_id":"","url":"https://api.github.com/users/ngocdaothanh","html_url":"https://github.com/ngocdaothanh","followers_url":"https://api.github.com/users/ngocdaothanh/followers","following_url":"https://api.github.com/users/ngocdaothanh/following{/other_user}","gists_url":"https://api.github.com/users/ngocdaothanh/gists{/gist_id}","starred_url":"https://api.github.com/users/ngocdaothanh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ngocdaothanh/subscriptions","organizations_url":"https://api.github.com/users/ngocdaothanh/orgs","repos_url":"https://api.github.com/users/ngocdaothanh/repos","events_url":"https://api.github.com/users/ngocdaothanh/events{/privacy}","received_events_url":"https://api.github.com/users/ngocdaothanh/received_events","type":"User","site_admin":false},"body":"This PR adds a HTTP router that can route based on request HTTP method and URI. It supports reverse routing and getting all allowed methods, for use with OPTIONS and CORS.\r\n\r\nIn later version, I'll add support for regex and dots, like `/articles/:id<[0-9]+>.:format` (the APIs won't be changed).\r\n\r\nThis PR is for branch 4.0.\r\nI'll create a new PR for branch 4.1 if neccessary.\r\n\r\n-------------------------\r\n\r\nMotivation:\r\n\r\nSee #3023 and #3236\r\n\r\nModifications:\r\n\r\nAdd package io.netty.handler.codec.http.router which only exposes 2 classes, one for the router and one for the routing result. See Javadoc for more info.\r\n\r\nResult:\r\n\r\nNow Netty has a basic HTTP router feature.","created_at":"2015-06-19T02:11:26Z","updated_at":"2015-06-21T18:59:42Z","closed_at":null,"merged_at":null,"merge_commit_sha":"d86d5b93ea847d21585ea6ede0591d4144cc718d","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/netty/netty/pulls/3901/commits","review_comments_url":"https://api.github.com/repos/netty/netty/pulls/3901/comments","review_comment_url":"https://api.github.com/repos/netty/netty/pulls/comments{/number}","comments_url":"https://api.github.com/repos/netty/netty/issues/3901/comments","statuses_url":"https://api.github.com/repos/netty/netty/statuses/65281000bbcb76f4228b7affb9e6d955d21320ac","head":{"label":"ngocdaothanh:4.0-http-router","ref":"4.0-http-router","sha":"65281000bbcb76f4228b7affb9e6d955d21320ac","user":{"login":"ngocdaothanh","id":10095,"avatar_url":"https://avatars.githubusercontent.com/u/10095?v=3","gravatar_id":"","url":"https://api.github.com/users/ngocdaothanh","html_url":"https://github.com/ngocdaothanh","followers_url":"https://api.github.com/users/ngocdaothanh/followers","following_url":"https://api.github.com/users/ngocdaothanh/following{/other_user}","gists_url":"https://api.github.com/users/ngocdaothanh/gists{/gist_id}","starred_url":"https://api.github.com/users/ngocdaothanh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ngocdaothanh/subscriptions","organizations_url":"https://api.github.com/users/ngocdaothanh/orgs","repos_url":"https://api.github.com/users/ngocdaothanh/repos","events_url":"https://api.github.com/users/ngocdaothanh/events{/privacy}","received_events_url":"https://api.github.com/users/ngocdaothanh/received_events","type":"User","site_admin":false},"repo":{"id":2829961,"name":"netty","full_name":"ngocdaothanh/netty","owner":{"login":"ngocdaothanh","id":10095,"avatar_url":"https://avatars.githubusercontent.com/u/10095?v=3","gravatar_id":"","url":"https://api.github.com/users/ngocdaothanh","html_url":"https://github.com/ngocdaothanh","followers_url":"https://api.github.com/users/ngocdaothanh/followers","following_url":"https://api.github.com/users/ngocdaothanh/following{/other_user}","gists_url":"https://api.github.com/users/ngocdaothanh/gists{/gist_id}","starred_url":"https://api.github.com/users/ngocdaothanh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ngocdaothanh/subscriptions","organizations_url":"https://api.github.com/users/ngocdaothanh/orgs","repos_url":"https://api.github.com/users/ngocdaothanh/repos","events_url":"https://api.github.com/users/ngocdaothanh/events{/privacy}","received_events_url":"https://api.github.com/users/ngocdaothanh/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/ngocdaothanh/netty","description":"Netty project - an event-driven asynchronous network application framework","fork":true,"url":"https://api.github.com/repos/ngocdaothanh/netty","forks_url":"https://api.github.com/repos/ngocdaothanh/netty/forks","keys_url":"https://api.github.com/repos/ngocdaothanh/netty/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ngocdaothanh/netty/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ngocdaothanh/netty/teams","hooks_url":"https://api.github.com/repos/ngocdaothanh/netty/hooks","issue_events_url":"https://api.github.com/repos/ngocdaothanh/netty/issues/events{/number}","events_url":"https://api.github.com/repos/ngocdaothanh/netty/events","assignees_url":"https://api.github.com/repos/ngocdaothanh/netty/assignees{/user}","branches_url":"https://api.github.com/repos/ngocdaothanh/netty/branches{/branch}","tags_url":"https://api.github.com/repos/ngocdaothanh/netty/tags","blobs_url":"https://api.github.com/repos/ngocdaothanh/netty/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ngocdaothanh/netty/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ngocdaothanh/netty/git/refs{/sha}","trees_url":"https://api.github.com/repos/ngocdaothanh/netty/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ngocdaothanh/netty/statuses/{sha}","languages_url":"https://api.github.com/repos/ngocdaothanh/netty/languages","stargazers_url":"https://api.github.com/repos/ngocdaothanh/netty/stargazers","contributors_url":"https://api.github.com/repos/ngocdaothanh/netty/contributors","subscribers_url":"https://api.github.com/repos/ngocdaothanh/netty/subscribers","subscription_url":"https://api.github.com/repos/ngocdaothanh/netty/subscription","commits_url":"https://api.github.com/repos/ngocdaothanh/netty/commits{/sha}","git_commits_url":"https://api.github.com/repos/ngocdaothanh/netty/git/commits{/sha}","comments_url":"https://api.github.com/repos/ngocdaothanh/netty/comments{/number}","issue_comment_url":"https://api.github.com/repos/ngocdaothanh/netty/issues/comments{/number}","contents_url":"https://api.github.com/repos/ngocdaothanh/netty/contents/{+path}","compare_url":"https://api.github.com/repos/ngocdaothanh/netty/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ngocdaothanh/netty/merges","archive_url":"https://api.github.com/repos/ngocdaothanh/netty/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ngocdaothanh/netty/downloads","issues_url":"https://api.github.com/repos/ngocdaothanh/netty/issues{/number}","pulls_url":"https://api.github.com/repos/ngocdaothanh/netty/pulls{/number}","milestones_url":"https://api.github.com/repos/ngocdaothanh/netty/milestones{/number}","notifications_url":"https://api.github.com/repos/ngocdaothanh/netty/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ngocdaothanh/netty/labels{/name}","releases_url":"https://api.github.com/repos/ngocdaothanh/netty/releases{/id}","created_at":"2011-11-22T18:39:57Z","updated_at":"2015-02-05T00:35:34Z","pushed_at":"2015-06-19T02:23:25Z","git_url":"git://github.com/ngocdaothanh/netty.git","ssh_url":"git@github.com:ngocdaothanh/netty.git","clone_url":"https://github.com/ngocdaothanh/netty.git","svn_url":"https://github.com/ngocdaothanh/netty","homepage":"http://netty.io/","size":32335,"stargazers_count":1,"watchers_count":1,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"netty:4.0","ref":"4.0","sha":"1dfbab06429ac74e56eb161fc0c2456e184154f1","user":{"login":"netty","id":473791,"avatar_url":"https://avatars.githubusercontent.com/u/473791?v=3","gravatar_id":"","url":"https://api.github.com/users/netty","html_url":"https://github.com/netty","followers_url":"https://api.github.com/users/netty/followers","following_url":"https://api.github.com/users/netty/following{/other_user}","gists_url":"https://api.github.com/users/netty/gists{/gist_id}","starred_url":"https://api.github.com/users/netty/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/netty/subscriptions","organizations_url":"https://api.github.com/users/netty/orgs","repos_url":"https://api.github.com/users/netty/repos","events_url":"https://api.github.com/users/netty/events{/privacy}","received_events_url":"https://api.github.com/users/netty/received_events","type":"Organization","site_admin":false},"repo":{"id":1064563,"name":"netty","full_name":"netty/netty","owner":{"login":"netty","id":473791,"avatar_url":"https://avatars.githubusercontent.com/u/473791?v=3","gravatar_id":"","url":"https://api.github.com/users/netty","html_url":"https://github.com/netty","followers_url":"https://api.github.com/users/netty/followers","following_url":"https://api.github.com/users/netty/following{/other_user}","gists_url":"https://api.github.com/users/netty/gists{/gist_id}","starred_url":"https://api.github.com/users/netty/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/netty/subscriptions","organizations_url":"https://api.github.com/users/netty/orgs","repos_url":"https://api.github.com/users/netty/repos","events_url":"https://api.github.com/users/netty/events{/privacy}","received_events_url":"https://api.github.com/users/netty/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/netty/netty","description":"Netty project - an event-driven asynchronous network application framework","fork":false,"url":"https://api.github.com/repos/netty/netty","forks_url":"https://api.github.com/repos/netty/netty/forks","keys_url":"https://api.github.com/repos/netty/netty/keys{/key_id}","collaborators_url":"https://api.github.com/repos/netty/netty/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/netty/netty/teams","hooks_url":"https://api.github.com/repos/netty/netty/hooks","issue_events_url":"https://api.github.com/repos/netty/netty/issues/events{/number}","events_url":"https://api.github.com/repos/netty/netty/events","assignees_url":"https://api.github.com/repos/netty/netty/assignees{/user}","branches_url":"https://api.github.com/repos/netty/netty/branches{/branch}","tags_url":"https://api.github.com/repos/netty/netty/tags","blobs_url":"https://api.github.com/repos/netty/netty/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/netty/netty/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/netty/netty/git/refs{/sha}","trees_url":"https://api.github.com/repos/netty/netty/git/trees{/sha}","statuses_url":"https://api.github.com/repos/netty/netty/statuses/{sha}","languages_url":"https://api.github.com/repos/netty/netty/languages","stargazers_url":"https://api.github.com/repos/netty/netty/stargazers","contributors_url":"https://api.github.com/repos/netty/netty/contributors","subscribers_url":"https://api.github.com/repos/netty/netty/subscribers","subscription_url":"https://api.github.com/repos/netty/netty/subscription","commits_url":"https://api.github.com/repos/netty/netty/commits{/sha}","git_commits_url":"https://api.github.com/repos/netty/netty/git/commits{/sha}","comments_url":"https://api.github.com/repos/netty/netty/comments{/number}","issue_comment_url":"https://api.github.com/repos/netty/netty/issues/comments{/number}","contents_url":"https://api.github.com/repos/netty/netty/contents/{+path}","compare_url":"https://api.github.com/repos/netty/netty/compare/{base}...{head}","merges_url":"https://api.github.com/repos/netty/netty/merges","archive_url":"https://api.github.com/repos/netty/netty/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/netty/netty/downloads","issues_url":"https://api.github.com/repos/netty/netty/issues{/number}","pulls_url":"https://api.github.com/repos/netty/netty/pulls{/number}","milestones_url":"https://api.github.com/repos/netty/netty/milestones{/number}","notifications_url":"https://api.github.com/repos/netty/netty/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/netty/netty/labels{/name}","releases_url":"https://api.github.com/repos/netty/netty/releases{/id}","created_at":"2010-11-09T09:22:21Z","updated_at":"2015-06-21T07:31:10Z","pushed_at":"2015-06-21T17:58:14Z","git_url":"git://github.com/netty/netty.git","ssh_url":"git@github.com:netty/netty.git","clone_url":"https://github.com/netty/netty.git","svn_url":"https://github.com/netty/netty","homepage":"http://netty.io/","size":128299,"stargazers_count":4791,"watchers_count":4791,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2384,"mirror_url":null,"open_issues_count":281,"forks":2384,"open_issues":281,"watchers":4791,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/netty/netty/pulls/3901"},"html":{"href":"https://github.com/netty/netty/pull/3901"},"issue":{"href":"https://api.github.com/repos/netty/netty/issues/3901"},"comments":{"href":"https://api.github.com/repos/netty/netty/issues/3901/comments"},"review_comments":{"href":"https://api.github.com/repos/netty/netty/pulls/3901/comments"},"review_comment":{"href":"https://api.github.com/repos/netty/netty/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/netty/netty/pulls/3901/commits"},"statuses":{"href":"https://api.github.com/repos/netty/netty/statuses/65281000bbcb76f4228b7affb9e6d955d21320ac"}}}}