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: 吴伟杰 <[email protected]>
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