You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by te...@apache.org on 2020/08/25 11:04:22 UTC
[shardingsphere-elasticjob] branch restful-api updated: Improve
trailing slash handling (#1408)
This is an automated email from the ASF dual-hosted git repository.
technoboy pushed a commit to branch restful-api
in repository https://gitbox.apache.org/repos/asf/shardingsphere-elasticjob.git
The following commit(s) were added to refs/heads/restful-api by this push:
new f306017 Improve trailing slash handling (#1408)
f306017 is described below
commit f3060171ba974604d18aafcadf414a1e5471c512
Author: 吴伟杰 <ro...@me.com>
AuthorDate: Tue Aug 25 19:04:10 2020 +0800
Improve trailing slash handling (#1408)
---
elasticjob-infra/elasticjob-restful/pom.xml | 13 ++-
.../restful/NettyRestfulServiceConfiguration.java | 6 ++
.../elasticjob/restful/annotation/Mapping.java | 2 +-
.../restful/mapping/RegexPathMatcher.java | 2 +-
.../restful/pipeline/HandlerParameterDecoder.java | 4 -
.../restful/pipeline/HttpRequestDispatcher.java | 30 +++++-
.../pipeline/RestfulServiceChannelInitializer.java | 2 +-
.../elasticjob/restful/RegexPathMatcherTest.java | 5 +
.../restful/controller/IndexController.java} | 35 +++---
.../controller/TrailingSlashTestController.java | 50 +++++++++
.../pipeline/HandlerParameterDecoderTest.java | 117 +++++++++++++++++++++
.../elasticjob/restful/pipeline/HttpClient.java | 8 +-
.../pipeline/HttpRequestDispatcherTest.java | 2 +-
.../restful/pipeline/NettyRestfulServiceTest.java | 41 ++++++--
...RestfulServiceTrailingSlashInsensitiveTest.java | 40 +++++++
...tyRestfulServiceTrailingSlashSensitiveTest.java | 85 +++++++++++++++
.../CustomTextPlainResponseBodySerializer.java | 44 ++++++++
...icjob.restful.serializer.ResponseBodySerializer | 18 ++++
18 files changed, 457 insertions(+), 47 deletions(-)
diff --git a/elasticjob-infra/elasticjob-restful/pom.xml b/elasticjob-infra/elasticjob-restful/pom.xml
index fad0128..87266a7 100644
--- a/elasticjob-infra/elasticjob-restful/pom.xml
+++ b/elasticjob-infra/elasticjob-restful/pom.xml
@@ -55,5 +55,16 @@
<scope>test</scope>
</dependency>
</dependencies>
-
+ <build>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ </resource>
+ </resources>
+ <testResources>
+ <testResource>
+ <directory>src/test/resources</directory>
+ </testResource>
+ </testResources>
+ </build>
</project>
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/NettyRestfulServiceConfiguration.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/NettyRestfulServiceConfiguration.java
index d40bfcc..bc35204 100644
--- a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/NettyRestfulServiceConfiguration.java
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/NettyRestfulServiceConfiguration.java
@@ -41,6 +41,12 @@ public final class NettyRestfulServiceConfiguration {
@Setter
private String host;
+ /**
+ * If trailing slash sensitive, <code>/foo/bar</code> is not equals to <code>/foo/bar/</code>.
+ */
+ @Setter
+ private boolean trailingSlashSensitive;
+
private final List<RestfulController> controllerInstances = new ArrayList<>();
private final Map<Class<? extends Throwable>, ExceptionHandler<? extends Throwable>> exceptionHandlers = new HashMap<>();
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/Mapping.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/Mapping.java
index ee23a16..0d0ac90 100644
--- a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/Mapping.java
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/Mapping.java
@@ -37,7 +37,7 @@ public @interface Mapping {
String method();
/**
- * Path pattern of this handler.
+ * Path pattern of this handler. Starts with '/'.
* Such as <code>/app/{jobName}/enable</code>.
*
* @return Path pattern
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/RegexPathMatcher.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/RegexPathMatcher.java
index 55e29a2..53c2e55 100644
--- a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/RegexPathMatcher.java
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/RegexPathMatcher.java
@@ -33,7 +33,7 @@ public final class RegexPathMatcher implements PathMatcher {
private static final String PATH_SEPARATOR = "/";
- private static final Pattern PATH_PATTERN = Pattern.compile("^(?:/|(/[^/{}?]+|/\\{[^/{}?]+})+)$");
+ private static final Pattern PATH_PATTERN = Pattern.compile("^/(([^/{}]+|\\{[^/{}]+})(/([^/{}]+|\\{[^/{}]+}))*/?)?$");
private static final Pattern TEMPLATE_PATTERN = Pattern.compile("\\{(?<template>[^/]+)}");
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HandlerParameterDecoder.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HandlerParameterDecoder.java
index 1fd80f7..0616141 100644
--- a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HandlerParameterDecoder.java
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HandlerParameterDecoder.java
@@ -22,7 +22,6 @@ import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
-import io.netty.handler.codec.UnsupportedMessageTypeException;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.QueryStringDecoder;
@@ -102,9 +101,6 @@ public final class HandlerParameterDecoder extends ChannelInboundHandlerAdapter
String mimeType = Optional.ofNullable(HttpUtil.getMimeType(httpRequest))
.orElseGet(() -> HttpUtil.getMimeType(Http.DEFAULT_CONTENT_TYPE)).toString();
RequestBodyDeserializer deserializer = RequestBodyDeserializerFactory.getRequestBodyDeserializer(mimeType);
- if (null == deserializer) {
- throw new UnsupportedMessageTypeException(MessageFormat.format("Unsupported MIME type [{0}]", mimeType));
- }
Object parsedBodyValue = deserializer.deserialize(targetType, bytes);
parsedValue = parsedBodyValue;
Preconditions.checkArgument(nullable || null != parsedBodyValue, "Missing request body");
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HttpRequestDispatcher.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HttpRequestDispatcher.java
index 0fa26f1..52a8e2e 100644
--- a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HttpRequestDispatcher.java
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HttpRequestDispatcher.java
@@ -44,9 +44,14 @@ import java.util.Optional;
@Slf4j
public final class HttpRequestDispatcher extends ChannelInboundHandlerAdapter {
+ private static final String TRAILING_SLASH = "/";
+
private final HandlerMappingRegistry mappingRegistry = new HandlerMappingRegistry();
- public HttpRequestDispatcher(final List<RestfulController> restfulControllers) {
+ private final boolean trailingSlashSensitive;
+
+ public HttpRequestDispatcher(final List<RestfulController> restfulControllers, final boolean trailingSlashSensitive) {
+ this.trailingSlashSensitive = trailingSlashSensitive;
initMappingRegistry(restfulControllers);
}
@@ -54,6 +59,9 @@ public final class HttpRequestDispatcher extends ChannelInboundHandlerAdapter {
public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
log.debug("{}", msg);
FullHttpRequest request = (FullHttpRequest) msg;
+ if (!trailingSlashSensitive) {
+ request.setUri(appendTrailingSlashIfAbsent(request.uri()));
+ }
MappingContext<Handler> mappingContext = mappingRegistry.getMappingContext(request);
if (null == mappingContext) {
throw new HandlerNotFoundException(request.uri());
@@ -72,10 +80,26 @@ public final class HttpRequestDispatcher extends ChannelInboundHandlerAdapter {
continue;
}
HttpMethod httpMethod = HttpMethod.valueOf(mapping.method());
- String pattern = mapping.path();
- String fullPathPattern = contextPath + pattern;
+ String path = mapping.path();
+ String fullPathPattern = resolveFullPath(contextPath, path);
+ if (!trailingSlashSensitive) {
+ fullPathPattern = appendTrailingSlashIfAbsent(fullPathPattern);
+ }
mappingRegistry.addMapping(httpMethod, fullPathPattern, new Handler(restfulController, method));
}
}
}
+
+ private String resolveFullPath(final String contextPath, final String pattern) {
+ return Optional.ofNullable(contextPath).orElse("") + pattern;
+ }
+
+ private String appendTrailingSlashIfAbsent(final String uri) {
+ String[] split = uri.split("\\?");
+ if (1 == split.length) {
+ return uri.endsWith(TRAILING_SLASH) ? uri : uri + TRAILING_SLASH;
+ }
+ String path = split[0];
+ return path.endsWith(TRAILING_SLASH) ? uri : path + TRAILING_SLASH + "?" + split[1];
+ }
}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/RestfulServiceChannelInitializer.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/RestfulServiceChannelInitializer.java
index bd261a9..831df2c 100644
--- a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/RestfulServiceChannelInitializer.java
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/RestfulServiceChannelInitializer.java
@@ -38,7 +38,7 @@ public final class RestfulServiceChannelInitializer extends ChannelInitializer<C
private final ExceptionHandling exceptionHandling;
public RestfulServiceChannelInitializer(final NettyRestfulServiceConfiguration configuration) {
- httpRequestDispatcher = new HttpRequestDispatcher(configuration.getControllerInstances());
+ httpRequestDispatcher = new HttpRequestDispatcher(configuration.getControllerInstances(), configuration.isTrailingSlashSensitive());
handlerParameterDecoder = new HandlerParameterDecoder();
handleMethodExecutor = new HandleMethodExecutor();
exceptionHandling = new ExceptionHandling(configuration.getExceptionHandlers());
diff --git a/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/RegexPathMatcherTest.java b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/RegexPathMatcherTest.java
index 68b0777..6c1286a 100644
--- a/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/RegexPathMatcherTest.java
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/RegexPathMatcherTest.java
@@ -59,12 +59,17 @@ public class RegexPathMatcherTest {
public void assertValidatePathPattern() {
PathMatcher pathMatcher = new RegexPathMatcher();
assertTrue(pathMatcher.isValidPathPattern("/"));
+ assertTrue(pathMatcher.isValidPathPattern("/app"));
assertTrue(pathMatcher.isValidPathPattern("/app/job"));
+ assertTrue(pathMatcher.isValidPathPattern("/app/job/"));
assertTrue(pathMatcher.isValidPathPattern("/app/{jobName}"));
assertTrue(pathMatcher.isValidPathPattern("/{appName}/{jobName}/status"));
assertFalse(pathMatcher.isValidPathPattern("/app/jobName}"));
assertFalse(pathMatcher.isValidPathPattern("/app/{jobName"));
assertFalse(pathMatcher.isValidPathPattern("/app/{job}Name"));
+ assertFalse(pathMatcher.isValidPathPattern("/app//jobName"));
+ assertFalse(pathMatcher.isValidPathPattern("//app/jobName"));
+ assertFalse(pathMatcher.isValidPathPattern("app/jobName"));
assertFalse(pathMatcher.isValidPathPattern(""));
}
}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/Mapping.java b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/controller/IndexController.java
similarity index 56%
copy from elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/Mapping.java
copy to elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/controller/IndexController.java
index ee23a16..3c9f3bc 100644
--- a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/Mapping.java
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/controller/IndexController.java
@@ -15,32 +15,23 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.elasticjob.restful.annotation;
+package org.apache.shardingsphere.elasticjob.restful.controller;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.shardingsphere.elasticjob.restful.Http;
+import org.apache.shardingsphere.elasticjob.restful.RestfulController;
+import org.apache.shardingsphere.elasticjob.restful.annotation.Mapping;
-/**
- * Declare what HTTP method and path is used to invoke the handler.
- */
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface Mapping {
-
- /**
- * Http method.
- *
- * @return Http method
- */
- String method();
+@Slf4j
+public class IndexController implements RestfulController {
/**
- * Path pattern of this handler.
- * Such as <code>/app/{jobName}/enable</code>.
+ * A mapping declare path implicit, meaning it mapped index.
*
- * @return Path pattern
+ * @return a string
*/
- String path() default "";
+ @Mapping(method = Http.GET)
+ public String index() {
+ return "hello, elastic-job";
+ }
}
diff --git a/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/controller/TrailingSlashTestController.java b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/controller/TrailingSlashTestController.java
new file mode 100644
index 0000000..c98d2b6
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/controller/TrailingSlashTestController.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shardingsphere.elasticjob.restful.controller;
+
+import org.apache.shardingsphere.elasticjob.restful.Http;
+import org.apache.shardingsphere.elasticjob.restful.RestfulController;
+import org.apache.shardingsphere.elasticjob.restful.annotation.ContextPath;
+import org.apache.shardingsphere.elasticjob.restful.annotation.Mapping;
+import org.apache.shardingsphere.elasticjob.restful.annotation.Returning;
+
+@ContextPath("/trailing")
+public final class TrailingSlashTestController implements RestfulController {
+
+ /**
+ * A mapping without trailing slash.
+ *
+ * @return a string
+ */
+ @Mapping(method = Http.GET, path = "/slash")
+ @Returning(contentType = "text/plain; charset=utf-8")
+ public String withoutTrailingSlash() {
+ return "without trailing slash";
+ }
+
+ /**
+ * A mapping with trailing slash.
+ *
+ * @return a string
+ */
+ @Mapping(method = Http.GET, path = "/slash/")
+ @Returning(contentType = "text/plain; charset=utf-8")
+ public String withTrailingSlash() {
+ return "with trailing slash";
+ }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HandlerParameterDecoderTest.java b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HandlerParameterDecoderTest.java
new file mode 100644
index 0000000..afeb004
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HandlerParameterDecoderTest.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shardingsphere.elasticjob.restful.pipeline;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.embedded.EmbeddedChannel;
+import io.netty.handler.codec.http.DefaultFullHttpRequest;
+import io.netty.handler.codec.http.DefaultHttpHeaders;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpVersion;
+import io.netty.handler.codec.http.QueryStringEncoder;
+import org.apache.shardingsphere.elasticjob.restful.Http;
+import org.apache.shardingsphere.elasticjob.restful.RestfulController;
+import org.apache.shardingsphere.elasticjob.restful.annotation.Mapping;
+import org.apache.shardingsphere.elasticjob.restful.annotation.Param;
+import org.apache.shardingsphere.elasticjob.restful.annotation.ParamSource;
+import org.apache.shardingsphere.elasticjob.restful.annotation.RequestBody;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class HandlerParameterDecoderTest {
+
+ private EmbeddedChannel channel;
+
+ @Before
+ public void setUp() {
+ HttpRequestDispatcher httpRequestDispatcher = new HttpRequestDispatcher(Collections.singletonList(new DecoderTestController()), false);
+ HandlerParameterDecoder handlerParameterDecoder = new HandlerParameterDecoder();
+ HandleMethodExecutor handleMethodExecutor = new HandleMethodExecutor();
+ channel = new EmbeddedChannel(httpRequestDispatcher, handlerParameterDecoder, handleMethodExecutor);
+ }
+
+ @Test
+ public void assertDecodeParameters() {
+ QueryStringEncoder queryStringEncoder = new QueryStringEncoder("/myApp/C");
+ queryStringEncoder.addParam("cron", "0 * * * * ?");
+ queryStringEncoder.addParam("integer", "30");
+ queryStringEncoder.addParam("bool", "true");
+ queryStringEncoder.addParam("long", "3000");
+ queryStringEncoder.addParam("double", "23.33");
+ String uri = queryStringEncoder.toString();
+ ByteBuf body = Unpooled.wrappedBuffer("BODY".getBytes());
+ HttpHeaders headers = new DefaultHttpHeaders();
+ headers.set("Message", "some_message");
+ FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri, body, headers, headers);
+ channel.writeInbound(httpRequest);
+ FullHttpResponse httpResponse = channel.readOutbound();
+ assertThat(httpResponse.status().code(), is(200));
+ assertThat(new String(ByteBufUtil.getBytes(httpResponse.content())), is("ok"));
+ }
+
+ public static class DecoderTestController implements RestfulController {
+
+ /**
+ * A handle method for decode testing.
+ *
+ * @param appName string from path
+ * @param ch character from path
+ * @param cron cron from query
+ * @param message message from header
+ * @param body from request body
+ * @param integer integer from query
+ * @param bool boolean from query
+ * @param longValue long from query
+ * @param doubleValue double from query
+ * @return OK
+ */
+ @Mapping(method = Http.GET, path = "/{appName}/{ch}")
+ public String handle(
+ final @Param(source = ParamSource.PATH, name = "appName") String appName,
+ final @Param(source = ParamSource.PATH, name = "ch") char ch,
+ final @Param(source = ParamSource.QUERY, name = "cron") String cron,
+ final @Param(source = ParamSource.HEADER, name = "Message") String message,
+ final @RequestBody String body,
+ final @Param(source = ParamSource.QUERY, name = "integer") int integer,
+ final @Param(source = ParamSource.QUERY, name = "bool") Boolean bool,
+ final @Param(source = ParamSource.QUERY, name = "long") Long longValue,
+ final @Param(source = ParamSource.QUERY, name = "double") double doubleValue
+ ) {
+ assertThat(appName, is("myApp"));
+ assertThat(ch, is('C'));
+ assertThat(cron, is("0 * * * * ?"));
+ assertThat(message, is("some_message"));
+ assertThat(body, is("BODY"));
+ assertThat(integer, is(30));
+ assertThat(bool, is(true));
+ assertThat(longValue, is(3000L));
+ assertThat(doubleValue, is(23.33));
+ return "ok";
+ }
+ }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HttpClient.java b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HttpClient.java
index 410c1b3..98fd65d 100644
--- a/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HttpClient.java
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HttpClient.java
@@ -48,6 +48,7 @@ public class HttpClient {
* @param request HTTP request
* @param consumer HTTP response consumer
* @param timeoutSeconds Wait for consume
+ * @throws InterruptedException interrupted
*/
@SneakyThrows
public static void request(final String host, final int port, final FullHttpRequest request, final Consumer<FullHttpResponse> consumer, final Long timeoutSeconds) {
@@ -66,8 +67,11 @@ public class HttpClient {
.addLast(new SimpleChannelInboundHandler<FullHttpResponse>() {
@Override
protected void channelRead0(final ChannelHandlerContext ctx, final FullHttpResponse httpResponse) throws Exception {
- consumer.accept(httpResponse);
- countDownLatch.countDown();
+ try {
+ consumer.accept(httpResponse);
+ } finally {
+ countDownLatch.countDown();
+ }
}
});
}
diff --git a/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HttpRequestDispatcherTest.java b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HttpRequestDispatcherTest.java
index e55fcf9..adfc21f 100644
--- a/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HttpRequestDispatcherTest.java
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HttpRequestDispatcherTest.java
@@ -31,7 +31,7 @@ public class HttpRequestDispatcherTest {
@Test(expected = HandlerNotFoundException.class)
public void assertDispatcherHandlerNotFound() {
- EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDispatcher(Lists.newArrayList(new JobController())));
+ EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDispatcher(Lists.newArrayList(new JobController()), false));
FullHttpRequest fullHttpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/hello/myJob/myCron");
channel.writeInbound(fullHttpRequest);
}
diff --git a/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/NettyRestfulServiceTest.java b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/NettyRestfulServiceTest.java
index 713f178..1e3927f 100644
--- a/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/NettyRestfulServiceTest.java
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/NettyRestfulServiceTest.java
@@ -30,6 +30,7 @@ import lombok.SneakyThrows;
import org.apache.shardingsphere.elasticjob.restful.NettyRestfulService;
import org.apache.shardingsphere.elasticjob.restful.NettyRestfulServiceConfiguration;
import org.apache.shardingsphere.elasticjob.restful.RestfulService;
+import org.apache.shardingsphere.elasticjob.restful.controller.IndexController;
import org.apache.shardingsphere.elasticjob.restful.controller.JobController;
import org.apache.shardingsphere.elasticjob.restful.handler.CustomIllegalStateExceptionHandler;
import org.apache.shardingsphere.elasticjob.restful.pojo.JobPojo;
@@ -46,6 +47,8 @@ import static org.junit.Assert.assertThat;
public class NettyRestfulServiceTest {
+ private static final long TESTCASE_TIMEOUT = 10000L;
+
private static final String HOST = "localhost";
private static final int PORT = 18080;
@@ -56,14 +59,14 @@ public class NettyRestfulServiceTest {
public static void init() {
NettyRestfulServiceConfiguration configuration = new NettyRestfulServiceConfiguration(PORT);
configuration.setHost(HOST);
- configuration.addControllerInstance(new JobController());
+ configuration.addControllerInstance(new JobController(), new IndexController());
configuration.addExceptionHandler(IllegalStateException.class, new CustomIllegalStateExceptionHandler());
restfulService = new NettyRestfulService(configuration);
restfulService.startup();
}
@SneakyThrows
- @Test(timeout = 10000L)
+ @Test(timeout = TESTCASE_TIMEOUT)
public void assertRequestWithParameters() {
String cron = "0 * * * * ?";
String uri = String.format("/job/myGroup/myJob?cron=%s", URLEncoder.encode(cron, "UTF-8"));
@@ -82,43 +85,59 @@ public class NettyRestfulServiceTest {
assertThat(jobPojo.getGroup(), is("myGroup"));
assertThat(jobPojo.getName(), is("myJob"));
assertThat(jobPojo.getDescription(), is(description));
- }, 10000L);
+ }, TESTCASE_TIMEOUT);
}
- @Test(timeout = 10000L)
+ @Test(timeout = TESTCASE_TIMEOUT)
public void assertCustomExceptionHandler() {
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/job/throw/IllegalState");
request.headers().set("Exception-Message", "An illegal state exception message.");
HttpClient.request(HOST, PORT, request, httpResponse -> {
// Handle by CustomExceptionHandler
assertThat(httpResponse.status().code(), is(403));
- }, 10000L);
+ }, TESTCASE_TIMEOUT);
}
- @Test(timeout = 10000L)
+ @Test(timeout = TESTCASE_TIMEOUT)
public void assertUsingDefaultExceptionHandler() {
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/job/throw/IllegalArgument");
request.headers().set("Exception-Message", "An illegal argument exception message.");
HttpClient.request(HOST, PORT, request, httpResponse -> {
// Handle by DefaultExceptionHandler
assertThat(httpResponse.status().code(), is(500));
- }, 10000L);
+ }, TESTCASE_TIMEOUT);
}
- @Test(timeout = 10000L)
+ @Test(timeout = TESTCASE_TIMEOUT)
public void assertReturnStatusCode() {
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/job/code/204");
HttpClient.request(HOST, PORT, request, httpResponse -> {
assertThat(httpResponse.status().code(), is(204));
- }, 10000L);
+ }, TESTCASE_TIMEOUT);
}
- @Test(timeout = 10000L)
+ @Test(timeout = TESTCASE_TIMEOUT)
public void assertHandlerNotFound() {
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/not/found");
HttpClient.request(HOST, PORT, request, httpResponse -> {
assertThat(httpResponse.status().code(), is(404));
- }, 10000L);
+ }, TESTCASE_TIMEOUT);
+ }
+
+ @Test(timeout = TESTCASE_TIMEOUT)
+ public void assertRequestIndexWithSlash() {
+ DefaultFullHttpRequest requestWithSlash = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
+ HttpClient.request(HOST, PORT, requestWithSlash, httpResponse -> {
+ assertThat(httpResponse.status().code(), is(200));
+ }, TESTCASE_TIMEOUT);
+ }
+
+ @Test(timeout = TESTCASE_TIMEOUT)
+ public void assertRequestIndexWithoutSlash() {
+ DefaultFullHttpRequest requestWithoutSlash = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "");
+ HttpClient.request(HOST, PORT, requestWithoutSlash, httpResponse -> {
+ assertThat(httpResponse.status().code(), is(200));
+ }, TESTCASE_TIMEOUT);
}
@AfterClass
diff --git a/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/NettyRestfulServiceTrailingSlashInsensitiveTest.java b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/NettyRestfulServiceTrailingSlashInsensitiveTest.java
new file mode 100644
index 0000000..8ba384c
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/NettyRestfulServiceTrailingSlashInsensitiveTest.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shardingsphere.elasticjob.restful.pipeline;
+
+import org.apache.shardingsphere.elasticjob.restful.NettyRestfulService;
+import org.apache.shardingsphere.elasticjob.restful.NettyRestfulServiceConfiguration;
+import org.apache.shardingsphere.elasticjob.restful.RestfulService;
+import org.apache.shardingsphere.elasticjob.restful.controller.TrailingSlashTestController;
+import org.junit.Test;
+
+public class NettyRestfulServiceTrailingSlashInsensitiveTest {
+
+ private static final String HOST = "localhost";
+
+ private static final int PORT = 18082;
+
+ @Test(expected = IllegalArgumentException.class)
+ public void assertPathDuplicateWhenTrailingSlashInsensitive() {
+ NettyRestfulServiceConfiguration configuration = new NettyRestfulServiceConfiguration(PORT);
+ configuration.setHost(HOST);
+ configuration.addControllerInstance(new TrailingSlashTestController());
+ RestfulService restfulService = new NettyRestfulService(configuration);
+ restfulService.startup();
+ }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/NettyRestfulServiceTrailingSlashSensitiveTest.java b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/NettyRestfulServiceTrailingSlashSensitiveTest.java
new file mode 100644
index 0000000..f175aa4
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/NettyRestfulServiceTrailingSlashSensitiveTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shardingsphere.elasticjob.restful.pipeline;
+
+import io.netty.buffer.ByteBufUtil;
+import io.netty.handler.codec.http.DefaultFullHttpRequest;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpVersion;
+import org.apache.shardingsphere.elasticjob.restful.NettyRestfulService;
+import org.apache.shardingsphere.elasticjob.restful.NettyRestfulServiceConfiguration;
+import org.apache.shardingsphere.elasticjob.restful.RestfulService;
+import org.apache.shardingsphere.elasticjob.restful.controller.TrailingSlashTestController;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class NettyRestfulServiceTrailingSlashSensitiveTest {
+
+ private static final long TESTCASE_TIMEOUT = 10000L;
+
+ private static final String HOST = "localhost";
+
+ private static final int PORT = 18081;
+
+ private static RestfulService restfulService;
+
+ @BeforeClass
+ public static void init() {
+ NettyRestfulServiceConfiguration configuration = new NettyRestfulServiceConfiguration(PORT);
+ configuration.setHost(HOST);
+ configuration.setTrailingSlashSensitive(true);
+ configuration.addControllerInstance(new TrailingSlashTestController());
+ restfulService = new NettyRestfulService(configuration);
+ restfulService.startup();
+ }
+
+ @Test(timeout = TESTCASE_TIMEOUT)
+ public void assertWithoutTrailingSlash() {
+ DefaultFullHttpRequest requestWithSlash = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/trailing/slash");
+ HttpClient.request(HOST, PORT, requestWithSlash, httpResponse -> {
+ assertThat(httpResponse.status().code(), is(200));
+ byte[] bytes = ByteBufUtil.getBytes(httpResponse.content());
+ String body = new String(bytes, StandardCharsets.UTF_8);
+ assertThat(body, is("without trailing slash"));
+ }, TESTCASE_TIMEOUT);
+ }
+
+ @Test(timeout = TESTCASE_TIMEOUT)
+ public void assertWithTrailingSlash() {
+ DefaultFullHttpRequest requestWithoutSlash = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/trailing/slash/");
+ HttpClient.request(HOST, PORT, requestWithoutSlash, httpResponse -> {
+ assertThat(httpResponse.status().code(), is(200));
+ byte[] bytes = ByteBufUtil.getBytes(httpResponse.content());
+ String body = new String(bytes, StandardCharsets.UTF_8);
+ assertThat(body, is("with trailing slash"));
+ }, TESTCASE_TIMEOUT);
+ }
+
+ @AfterClass
+ public static void tearDown() {
+ if (null != restfulService) {
+ restfulService.shutdown();
+ }
+ }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/serializer/CustomTextPlainResponseBodySerializer.java b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/serializer/CustomTextPlainResponseBodySerializer.java
new file mode 100644
index 0000000..032f923
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/serializer/CustomTextPlainResponseBodySerializer.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shardingsphere.elasticjob.restful.serializer;
+
+import io.netty.handler.codec.http.HttpHeaderValues;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Serializer for <code>text/plain</code>. Serialize String to bytes.
+ */
+public final class CustomTextPlainResponseBodySerializer implements ResponseBodySerializer {
+
+ @Override
+ public String mimeType() {
+ return HttpHeaderValues.TEXT_PLAIN.toString();
+ }
+
+ @Override
+ public byte[] serialize(final Object responseBody) {
+ if (responseBody instanceof String) {
+ return ((String) responseBody).getBytes(StandardCharsets.UTF_8);
+ }
+ if (responseBody instanceof byte[]) {
+ return (byte[]) responseBody;
+ }
+ throw new UnsupportedOperationException("Can not deserialize" + responseBody.getClass());
+ }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/test/resources/META-INF/services/org.apache.shardingsphere.elasticjob.restful.serializer.ResponseBodySerializer b/elasticjob-infra/elasticjob-restful/src/test/resources/META-INF/services/org.apache.shardingsphere.elasticjob.restful.serializer.ResponseBodySerializer
new file mode 100644
index 0000000..5d294f8
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/test/resources/META-INF/services/org.apache.shardingsphere.elasticjob.restful.serializer.ResponseBodySerializer
@@ -0,0 +1,18 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+org.apache.shardingsphere.elasticjob.restful.serializer.CustomTextPlainResponseBodySerializer