From b252e30ce4b9e61a2d058811b50168af0bba6484 Mon Sep 17 00:00:00 2001 From: Andrey Pangin Date: Wed, 26 Dec 2018 15:51:34 +0300 Subject: [PATCH] HTTP @RequestMethod annotation --- src/one/nio/http/HttpServer.java | 56 ++++++++++++------------ src/one/nio/http/PathMapper.java | 61 +++++++++++++++++++++++++++ src/one/nio/http/RequestMethod.java | 28 ++++++++++++ test/one/nio/http/HttpMethodTest.java | 8 +++- 4 files changed, 123 insertions(+), 30 deletions(-) create mode 100644 src/one/nio/http/PathMapper.java create mode 100644 src/one/nio/http/RequestMethod.java diff --git a/src/one/nio/http/HttpServer.java b/src/one/nio/http/HttpServer.java index 7b4a856..2fc322e 100755 --- a/src/one/nio/http/HttpServer.java +++ b/src/one/nio/http/HttpServer.java @@ -28,19 +28,19 @@ import java.util.Map; public class HttpServer extends Server { - private final Map defaultHandlers = new HashMap<>(); - private final Map> handlersByAlias = new HashMap<>(); - private final Map> handlersByHost = new HashMap<>(); + private final PathMapper defaultMapper = new PathMapper(); + private final Map mappersByAlias = new HashMap<>(); + private final Map mappersByHost = new HashMap<>(); public HttpServer(HttpServerConfig config, Object... routers) throws IOException { super(config); if (config.virtualHosts != null) { for (Map.Entry virtualHost : config.virtualHosts.entrySet()) { - Map handlers = new HashMap<>(); - handlersByAlias.put(virtualHost.getKey(), handlers); + PathMapper mapper = new PathMapper(); + mappersByAlias.put(virtualHost.getKey(), mapper); for (String host : virtualHost.getValue()) { - handlersByHost.put(host.toLowerCase(), handlers); + mappersByHost.put(host.toLowerCase(), mapper); } } } @@ -59,7 +59,7 @@ public HttpSession createSession(Socket socket) throws RejectedSessionException public void handleRequest(Request request, HttpSession session) throws IOException { RequestHandler handler = findHandlerByHost(request); if (handler == null) { - handler = defaultHandlers.get(request.getPath()); + handler = defaultMapper.find(request.getPath(), request.getMethod()); } if (handler != null) { @@ -74,24 +74,6 @@ public void handleDefault(Request request, HttpSession session) throws IOExcepti session.sendResponse(response); } - private RequestHandler findHandlerByHost(Request request) { - if (handlersByHost.isEmpty()) { - return null; - } - - String host = request.getHost(); - if (host == null) { - return null; - } - - Map handlers = handlersByHost.get(host.toLowerCase()); - if (handlers == null) { - return null; - } - - return handlers.get(request.getPath()); - } - public void addRequestHandlers(Object router) { ArrayList supers = new ArrayList<>(4); for (Class cls = router.getClass(); cls != Object.class; cls = cls.getSuperclass()) { @@ -111,6 +93,9 @@ public void addRequestHandlers(Object router) { continue; } + RequestMethod requestMethod = m.getAnnotation(RequestMethod.class); + int[] methods = requestMethod == null ? null : requestMethod.value(); + RequestHandler requestHandler = generator.generateFor(m, router); for (String path : annotation.value()) { if (!path.startsWith("/")) { @@ -118,12 +103,12 @@ public void addRequestHandlers(Object router) { } if (aliases == null || aliases.length == 0) { - defaultHandlers.put(path, requestHandler); + defaultMapper.add(path, methods, requestHandler); } else { for (String alias : aliases) { - Map handlers = handlersByAlias.get(alias); - if (handlers != null) { - handlers.put(path, requestHandler); + PathMapper mapper = mappersByAlias.get(alias); + if (mapper != null) { + mapper.add(path, methods, requestHandler); } } } @@ -131,4 +116,17 @@ public void addRequestHandlers(Object router) { } } } + + protected RequestHandler findHandlerByHost(Request request) { + if (!mappersByHost.isEmpty()) { + String host = request.getHost(); + if (host != null) { + PathMapper mapper = mappersByHost.get(host.toLowerCase()); + if (mapper != null) { + return mapper.find(request.getPath(), request.getMethod()); + } + } + } + return null; + } } diff --git a/src/one/nio/http/PathMapper.java b/src/one/nio/http/PathMapper.java new file mode 100644 index 0000000..94d76cf --- /dev/null +++ b/src/one/nio/http/PathMapper.java @@ -0,0 +1,61 @@ +/* + * Copyright 2018 Odnoklassniki Ltd, Mail.Ru Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package one.nio.http; + +import java.util.Arrays; +import java.util.HashMap; + +/** + * Finds a RequestHandler by the given @Path and HTTP method. + * Uses an embedded HashMap for performance reasons. + */ +public class PathMapper extends HashMap { + + // Add a new mapping + public void add(String path, int[] methods, RequestHandler handler) { + RequestHandler[] handlersByMethod = super.computeIfAbsent(path, p -> new RequestHandler[1]); + if (methods == null) { + handlersByMethod[0] = handler; + } else { + for (int method : methods) { + if (method <= 0 || method >= Request.NUMBER_OF_METHODS) { + throw new IllegalArgumentException("Invalid RequestMethod " + method + " for path " + path); + } + if (method >= handlersByMethod.length) { + handlersByMethod = Arrays.copyOf(handlersByMethod, method + 1); + super.put(path, handlersByMethod); + } + handlersByMethod[method] = handler; + } + } + } + + // Return an existing handler for this HTTP request or null if not found + public RequestHandler find(String path, int method) { + RequestHandler[] handlersByMethod = super.get(path); + if (handlersByMethod == null) { + return null; + } + + // First, try to find a handler with @RequestMethod annotation + if (method > 0 && method < handlersByMethod.length && handlersByMethod[method] != null) { + return handlersByMethod[method]; + } + // Otherwise return the universal handler for all methods + return handlersByMethod[0]; + } +} diff --git a/src/one/nio/http/RequestMethod.java b/src/one/nio/http/RequestMethod.java new file mode 100644 index 0000000..62e3b45 --- /dev/null +++ b/src/one/nio/http/RequestMethod.java @@ -0,0 +1,28 @@ +/* + * Copyright 2018 Odnoklassniki Ltd, Mail.Ru Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package one.nio.http; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface RequestMethod { + int[] value(); // METHOD_GET, METHOD_POST etc. +} diff --git a/test/one/nio/http/HttpMethodTest.java b/test/one/nio/http/HttpMethodTest.java index 750533d..15d4a33 100644 --- a/test/one/nio/http/HttpMethodTest.java +++ b/test/one/nio/http/HttpMethodTest.java @@ -75,7 +75,7 @@ public void post() throws Exception { @Test public void put() throws Exception { assertEquals( - Integer.toString(Request.METHOD_PUT), + "echoMethodOnPut", client.put(ENDPOINT).getBodyUtf8()); } @@ -158,5 +158,11 @@ public static class TestServer extends HttpServer { public Response echoMethod(Request request) throws IOException { return Response.ok(Integer.toString(request.getMethod())); } + + @Path(ENDPOINT) + @RequestMethod(Request.METHOD_PUT) + public Response echoMethodOnPut(Request request) { + return Response.ok("echoMethodOnPut"); + } } }