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/20 15:14:32 UTC

[shardingsphere-elasticjob] branch restful-api updated: Add new module elasticjob-restful (#1384)

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 42a63e3  Add new module elasticjob-restful (#1384)
42a63e3 is described below

commit 42a63e302a1360e05dc5c15024cd1df74d0b1d04
Author: 吴伟杰 <ro...@me.com>
AuthorDate: Thu Aug 20 23:14:19 2020 +0800

    Add new module elasticjob-restful (#1384)
---
 elasticjob-infra/elasticjob-restful/README.md      |  52 +++++++
 elasticjob-infra/elasticjob-restful/pom.xml        |  59 ++++++++
 .../shardingsphere/elasticjob/restful/Http.java    |  48 +++++++
 .../elasticjob/restful/NettyRestfulService.java    |  83 +++++++++++
 .../restful/NettyRestfulServiceConfiguration.java  |  68 +++++++++
 .../elasticjob/restful/RestfulController.java      |  24 ++++
 .../elasticjob/restful/RestfulService.java         |  34 +++++
 .../elasticjob/restful/annotation/ContextPath.java |  37 +++++
 .../elasticjob/restful/annotation/Mapping.java     |  46 ++++++
 .../elasticjob/restful/annotation/Param.java       |  52 +++++++
 .../elasticjob/restful/annotation/ParamSource.java |  50 +++++++
 .../elasticjob/restful/annotation/RequestBody.java |  31 ++++
 .../elasticjob/restful/annotation/Returning.java   |  47 ++++++
 .../deserializer/RequestBodyDeserializer.java      |  41 ++++++
 .../RequestBodyDeserializerFactory.java            |  50 +++++++
 .../impl/JsonRequestBodyDeserializer.java          |  42 ++++++
 .../impl/TextPlainRequestBodyDeserializer.java     |  46 ++++++
 .../restful/handler/ExceptionHandleResult.java     |  32 +++++
 .../restful/handler/ExceptionHandler.java          |  35 +++++
 .../elasticjob/restful/handler/HandleContext.java  |  41 ++++++
 .../elasticjob/restful/handler/Handler.java        | 108 ++++++++++++++
 .../restful/handler/HandlerMappingRegistry.java    |  64 +++++++++
 .../restful/handler/HandlerNotFoundException.java  |  30 ++++
 .../restful/handler/HandlerParameter.java          |  38 +++++
 .../mapping/AmbiguousPathPatternException.java     |  28 ++++
 .../restful/mapping/DefaultMappingContext.java     |  43 ++++++
 .../elasticjob/restful/mapping/MappingContext.java |  40 ++++++
 .../elasticjob/restful/mapping/PathMatcher.java    |  59 ++++++++
 .../restful/mapping/RegexPathMatcher.java          |  99 +++++++++++++
 .../restful/mapping/RegexUrlPatternMap.java        | 105 ++++++++++++++
 .../elasticjob/restful/mapping/UrlPatternMap.java  |  43 ++++++
 .../restful/pipeline/ExceptionHandling.java        | 102 +++++++++++++
 .../restful/pipeline/HandleMethodExecutor.java     |  76 ++++++++++
 .../restful/pipeline/HandlerParameterDecoder.java  | 157 +++++++++++++++++++++
 .../restful/pipeline/HttpRequestDispatcher.java    |  81 +++++++++++
 .../pipeline/RestfulServiceChannelInitializer.java |  57 ++++++++
 .../restful/serializer/ResponseBodySerializer.java |  39 +++++
 .../serializer/ResponseBodySerializerFactory.java  |  50 +++++++
 .../impl/JsonResponseBodySerializer.java           |  42 ++++++
 ...ob.restful.deserializer.RequestBodyDeserializer |  19 +++
 ...icjob.restful.serializer.ResponseBodySerializer |  18 +++
 .../elasticjob/restful/RegexPathMatcherTest.java   |  70 +++++++++
 .../elasticjob/restful/RegexUrlPatternMapTest.java |  68 +++++++++
 .../restful/controller/JobController.java          |  90 ++++++++++++
 .../CustomIllegalStateExceptionHandler.java        |  33 +++++
 .../elasticjob/restful/pipeline/HttpClient.java    |  80 +++++++++++
 .../pipeline/HttpRequestDispatcherTest.java        |  38 +++++
 .../restful/pipeline/NettyRestfulServiceTest.java  | 122 ++++++++++++++++
 .../elasticjob/restful/pojo/JobPojo.java           |  37 +++++
 .../elasticjob/restful/pojo/ResultDto.java         |  30 ++++
 elasticjob-infra/pom.xml                           |   1 +
 51 files changed, 2785 insertions(+)

diff --git a/elasticjob-infra/elasticjob-restful/README.md b/elasticjob-infra/elasticjob-restful/README.md
new file mode 100644
index 0000000..c203953
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/README.md
@@ -0,0 +1,52 @@
+# Restful Service
+
+## Usage
+
+### Create a RestfulController
+```java
+@ContextPath("/job")
+public class JobController implements RestfulController {
+    
+    @Mapping(method = Http.POST, pattern = "/{group}/{jobName}")
+    public JobPojo createJob(@Param(name = "group", source = ParamSource.PATH) final String group,
+                             @Param(name = "jobName", source = ParamSource.PATH) final String jobName,
+                             @Param(name = "cron", source = ParamSource.QUERY) final String cron,
+                             @RequestBody String description) {
+        JobPojo jobPojo = new JobPojo();
+        jobPojo.setName(jobName);
+        jobPojo.setCron(cron);
+        jobPojo.setGroup(group);
+        jobPojo.setDescription(description);
+        return jobPojo;
+    }
+
+    @Mapping(method = Http.GET, pattern = "/code/204")
+    @Returning(code = 204)
+    public Object return204() {
+        return null;
+    }
+}
+```
+
+### (Optional) Create ExceptionHandler
+```java
+public class CustomIllegalStateExceptionHandler implements ExceptionHandler<IllegalStateException> {
+    @Override
+    public ExceptionHandleResult handleException(final IllegalStateException ex) {
+        return ExceptionHandleResult.builder()
+                .statusCode(403)
+                .contentType(Http.DEFAULT_CONTENT_TYPE)
+                .result(ResultDto.builder().code(1).data(ex.getLocalizedMessage()).build())
+                .build();
+    }
+}
+```
+
+### Configure Restful Service and Start Up
+```java
+NettyRestfulServiceConfiguration configuration = new NettyRestfulServiceConfiguration(8080);
+configuration.addControllerInstance(new JobController());
+configuration.addExceptionHandler(IllegalStateException.class, new CustomIllegalStateExceptionHandler());
+RestfulService restfulService = new NettyRestfulService(configuration);
+restfulService.startup();
+```
diff --git a/elasticjob-infra/elasticjob-restful/pom.xml b/elasticjob-infra/elasticjob-restful/pom.xml
new file mode 100644
index 0000000..fad0128
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/pom.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>elasticjob-infra</artifactId>
+        <groupId>org.apache.shardingsphere.elasticjob</groupId>
+        <version>3.0.0-beta-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>elasticjob-restful</artifactId>
+    <name>${project.artifactId}</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.shardingsphere.elasticjob</groupId>
+            <artifactId>elasticjob-infra-common</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-codec-http</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-jdk14</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/Http.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/Http.java
new file mode 100644
index 0000000..1d7fc47
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/Http.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * Constants for HTTP.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class Http {
+    
+    public static final String GET = "GET";
+    
+    public static final String POST = "POST";
+    
+    public static final String HEAD = "HEAD";
+    
+    public static final String PUT = "PUT";
+    
+    public static final String PATCH = "PATCH";
+    
+    public static final String OPTIONS = "OPTIONS";
+    
+    public static final String DELETE = "DELETE";
+    
+    public static final String TRACE = "TRACE";
+    
+    public static final String CONNECT = "CONNECT";
+    
+    public static final String DEFAULT_CONTENT_TYPE = "application/json; charset=utf-8";
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/NettyRestfulService.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/NettyRestfulService.java
new file mode 100644
index 0000000..6ea0362
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/NettyRestfulService.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import com.google.common.base.Strings;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.util.NettyRuntime;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.shardingsphere.elasticjob.restful.pipeline.RestfulServiceChannelInitializer;
+
+/**
+ * Implemented {@link RestfulService} via Netty.
+ */
+@Slf4j
+public final class NettyRestfulService implements RestfulService {
+    
+    private static final int DEFAULT_WORKER_GROUP_THREADS = 1 + 2 * NettyRuntime.availableProcessors();
+    
+    private final NettyRestfulServiceConfiguration configuration;
+    
+    private ServerBootstrap serverBootstrap;
+    
+    private EventLoopGroup bossEventLoopGroup;
+    
+    private EventLoopGroup workerEventLoopGroup;
+    
+    public NettyRestfulService(final NettyRestfulServiceConfiguration configuration) {
+        this.configuration = configuration;
+    }
+    
+    private void initServerBootstrap() {
+        bossEventLoopGroup = new NioEventLoopGroup();
+        workerEventLoopGroup = new NioEventLoopGroup(DEFAULT_WORKER_GROUP_THREADS);
+        
+        serverBootstrap = new ServerBootstrap()
+                .group(bossEventLoopGroup, workerEventLoopGroup)
+                .channel(NioServerSocketChannel.class)
+                .childHandler(new RestfulServiceChannelInitializer(configuration));
+    }
+    
+    @Override
+    public void startup() {
+        initServerBootstrap();
+        ChannelFuture channelFuture;
+        if (!Strings.isNullOrEmpty(configuration.getHost())) {
+            channelFuture = serverBootstrap.bind(configuration.getHost(), configuration.getPort());
+        } else {
+            channelFuture = serverBootstrap.bind(configuration.getPort());
+        }
+        channelFuture.addListener(future -> {
+            if (future.isSuccess()) {
+                log.info("Restful Service started on port {}.", configuration.getPort());
+            } else {
+                log.error("Failed to start Restful Service.", future.cause());
+            }
+        });
+    }
+    
+    @Override
+    public void shutdown() {
+        bossEventLoopGroup.shutdownGracefully();
+        workerEventLoopGroup.shutdownGracefully();
+    }
+}
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
new file mode 100644
index 0000000..d40bfcc
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/NettyRestfulServiceConfiguration.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+import com.google.common.base.Preconditions;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import org.apache.shardingsphere.elasticjob.restful.handler.ExceptionHandler;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Configuration for {@link NettyRestfulService}.
+ */
+@Getter
+@RequiredArgsConstructor
+public final class NettyRestfulServiceConfiguration {
+    
+    private final int port;
+    
+    @Setter
+    private String host;
+    
+    private final List<RestfulController> controllerInstances = new ArrayList<>();
+    
+    private final Map<Class<? extends Throwable>, ExceptionHandler<? extends Throwable>> exceptionHandlers = new HashMap<>();
+    
+    /**
+     * Add instances of RestfulController.
+     *
+     * @param instances instances of RestfulController
+     */
+    public void addControllerInstance(final RestfulController... instances) {
+        controllerInstances.addAll(Arrays.asList(instances));
+    }
+    
+    /**
+     * Add an instance of ExceptionHandler for specific exception.
+     *
+     * @param exceptionType    The type of exception to handle
+     * @param exceptionHandler Instance of ExceptionHandler
+     * @param <E>              The type of exception to handle
+     */
+    public <E extends Throwable> void addExceptionHandler(final Class<E> exceptionType, final ExceptionHandler<E> exceptionHandler) {
+        Preconditions.checkState(!exceptionHandlers.containsKey(exceptionType), "ExceptionHandler for %s has already existed.", exceptionType.getName());
+        exceptionHandlers.put(exceptionType, exceptionHandler);
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/RestfulController.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/RestfulController.java
new file mode 100644
index 0000000..a12688f
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/RestfulController.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+/**
+ * Mark a class as RestfulController.
+ */
+public interface RestfulController {
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/RestfulService.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/RestfulService.java
new file mode 100644
index 0000000..2abb8bf
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/RestfulService.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * A facade of restful service. Invoke startup() method to start listen a port to provide Restful API.
+ */
+public interface RestfulService {
+    
+    /**
+     * Start Restful Service.
+     */
+    void startup();
+    
+    /**
+     * Shutdown Restful Service.
+     */
+    void shutdown();
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/ContextPath.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/ContextPath.java
new file mode 100644
index 0000000..8972f72
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/ContextPath.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ContextPath {
+    
+    /**
+     * Context path.
+     * Starts with '/' and no '/' at the end.
+     * Such as <code>/api/app</code>.
+     *
+     * @return Context path
+     */
+    String value();
+}
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
new file mode 100644
index 0000000..ee23a16
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/Mapping.java
@@ -0,0 +1,46 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 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();
+    
+    /**
+     * Path pattern of this handler.
+     * Such as <code>/app/{jobName}/enable</code>.
+     *
+     * @return Path pattern
+     */
+    String path() default "";
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/Param.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/Param.java
new file mode 100644
index 0000000..3e50641
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/Param.java
@@ -0,0 +1,52 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specify name and source of the parameter.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface Param {
+    
+    /**
+     * Parameter name.
+     *
+     * @return Parameter name
+     */
+    String name();
+    
+    /**
+     * Source of parameter.
+     *
+     * @return Source of parameter
+     */
+    ParamSource source();
+    
+    /**
+     * If the parameter is required.
+     *
+     * @return Requirement
+     */
+    boolean required() default true;
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/ParamSource.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/ParamSource.java
new file mode 100644
index 0000000..0eff5f0
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/ParamSource.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.annotation;
+
+/**
+ * Sources of parameter.
+ *
+ * @see org.apache.shardingsphere.elasticjob.restful.annotation.Param
+ */
+public enum ParamSource {
+    /**
+     * Request path.
+     */
+    PATH,
+    
+    /**
+     * Query parameters.
+     */
+    QUERY,
+    
+    /**
+     * HTTP headers.
+     */
+    HEADER,
+    
+    /**
+     * HTTP request body.
+     */
+    BODY,
+    
+    /**
+     * Unknown source.
+     */
+    UNKNOWN,
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/RequestBody.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/RequestBody.java
new file mode 100644
index 0000000..20b41f5
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/RequestBody.java
@@ -0,0 +1,31 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotate the parameter which is from HTTP request body.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface RequestBody {
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/Returning.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/Returning.java
new file mode 100644
index 0000000..c628184
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/annotation/Returning.java
@@ -0,0 +1,47 @@
+/*
+ * 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.annotation;
+
+import org.apache.shardingsphere.elasticjob.restful.Http;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotate on handler method to declare HTTP status code and content type of HTTP response.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Returning {
+    
+    /**
+     * HTTP status code to return after handling.
+     *
+     * @return Http status code
+     */
+    int code() default 200;
+    
+    /**
+     * HTTP content type of response.
+     *
+     * @return HTTP content type
+     */
+    String contentType() default Http.DEFAULT_CONTENT_TYPE;
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/deserializer/RequestBodyDeserializer.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/deserializer/RequestBodyDeserializer.java
new file mode 100644
index 0000000..9028045
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/deserializer/RequestBodyDeserializer.java
@@ -0,0 +1,41 @@
+/*
+ * 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.deserializer;
+
+/**
+ * Deserializer for deserializing request body with specific MIME type.
+ */
+public interface RequestBodyDeserializer {
+    
+    /**
+     * Specify which type would be deserialized by this deserializer.
+     *
+     * @return MIME type
+     */
+    String mimeType();
+    
+    /**
+     * Deserialize request body to an object.
+     *
+     * @param targetType       Target type
+     * @param requestBodyBytes Request body bytes
+     * @param <T>              Target type
+     * @return Deserialized object
+     */
+    <T> T deserialize(Class<T> targetType, byte[] requestBodyBytes);
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/deserializer/RequestBodyDeserializerFactory.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/deserializer/RequestBodyDeserializerFactory.java
new file mode 100644
index 0000000..7dc6ada
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/deserializer/RequestBodyDeserializerFactory.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.deserializer;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Request body deserializer factory.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class RequestBodyDeserializerFactory {
+    
+    private static final Map<String, RequestBodyDeserializer> REQUEST_BODY_DESERIALIZERS = new ConcurrentHashMap<>();
+    
+    static {
+        for (RequestBodyDeserializer deserializer : ServiceLoader.load(RequestBodyDeserializer.class)) {
+            REQUEST_BODY_DESERIALIZERS.put(deserializer.mimeType(), deserializer);
+        }
+    }
+    
+    /**
+     * Get deserializer for specific HTTP content type.
+     *
+     * @param contentType HTTP content type
+     * @return Deserializer
+     */
+    public static RequestBodyDeserializer getRequestBodyDeserializer(final String contentType) {
+        return REQUEST_BODY_DESERIALIZERS.get(contentType);
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/deserializer/impl/JsonRequestBodyDeserializer.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/deserializer/impl/JsonRequestBodyDeserializer.java
new file mode 100644
index 0000000..e742a6f
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/deserializer/impl/JsonRequestBodyDeserializer.java
@@ -0,0 +1,42 @@
+/*
+ * 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.deserializer.impl;
+
+import com.google.gson.Gson;
+import io.netty.handler.codec.http.HttpHeaderValues;
+import org.apache.shardingsphere.elasticjob.restful.deserializer.RequestBodyDeserializer;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Deserializer for <code>application/json</code>.
+ */
+public final class JsonRequestBodyDeserializer implements RequestBodyDeserializer {
+    
+    private final Gson gson = new Gson();
+    
+    @Override
+    public String mimeType() {
+        return HttpHeaderValues.APPLICATION_JSON.toString();
+    }
+    
+    @Override
+    public <T> T deserialize(final Class<T> targetType, final byte[] requestBodyBytes) {
+        return gson.fromJson(new String(requestBodyBytes, StandardCharsets.UTF_8), targetType);
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/deserializer/impl/TextPlainRequestBodyDeserializer.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/deserializer/impl/TextPlainRequestBodyDeserializer.java
new file mode 100644
index 0000000..3268f0c
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/deserializer/impl/TextPlainRequestBodyDeserializer.java
@@ -0,0 +1,46 @@
+/*
+ * 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.deserializer.impl;
+
+import io.netty.handler.codec.http.HttpHeaderValues;
+import org.apache.shardingsphere.elasticjob.restful.deserializer.RequestBodyDeserializer;
+
+import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
+
+/**
+ * Deserializer for <code>text/plain</code>.
+ */
+public final class TextPlainRequestBodyDeserializer implements RequestBodyDeserializer {
+    
+    @Override
+    public String mimeType() {
+        return HttpHeaderValues.TEXT_PLAIN.toString();
+    }
+    
+    @Override
+    public <T> T deserialize(final Class<T> targetType, final byte[] requestBodyBytes) {
+        if (byte[].class.equals(targetType)) {
+            return (T) requestBodyBytes;
+        }
+        if (String.class.isAssignableFrom(targetType)) {
+            return (T) new String(requestBodyBytes, StandardCharsets.UTF_8);
+        }
+        throw new UnsupportedOperationException(MessageFormat.format("Cannot deserialize [{0}] into [{1}]", mimeType(), targetType.getName()));
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/ExceptionHandleResult.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/ExceptionHandleResult.java
new file mode 100644
index 0000000..722d3a8
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/ExceptionHandleResult.java
@@ -0,0 +1,32 @@
+/*
+ * 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.handler;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Builder
+@Getter
+public final class ExceptionHandleResult {
+
+    private final Object result;
+    
+    private final int statusCode;
+    
+    private final String contentType;
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/ExceptionHandler.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/ExceptionHandler.java
new file mode 100644
index 0000000..3603046
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/ExceptionHandler.java
@@ -0,0 +1,35 @@
+/*
+ * 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.handler;
+
+/**
+ * If an exception was thrown, {@link org.apache.shardingsphere.elasticjob.restful.pipeline.ExceptionHandling}
+ * will search a proper handler to handle it.
+ *
+ * @param <E> Type of Exception
+ */
+public interface ExceptionHandler<E extends Throwable> {
+    
+    /**
+     * Handler for specific Exception.
+     *
+     * @param ex Exception
+     * @return Handle result
+     */
+    ExceptionHandleResult handleException(E ex);
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/HandleContext.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/HandleContext.java
new file mode 100644
index 0000000..d2adee5
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/HandleContext.java
@@ -0,0 +1,41 @@
+/*
+ * 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.handler;
+
+import io.netty.handler.codec.http.FullHttpRequest;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import org.apache.shardingsphere.elasticjob.restful.mapping.MappingContext;
+
+/**
+ * HandleContext will hold a instance of HTTP request, {@link MappingContext} and arguments for handle method invoking.
+ *
+ * @param <T> Type of MappingContext
+ */
+@RequiredArgsConstructor
+@Getter
+@Setter
+public final class HandleContext<T> {
+    
+    private final FullHttpRequest httpRequest;
+    
+    private final MappingContext<T> mappingContext;
+    
+    private Object[] args = new Object[0];
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/Handler.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/Handler.java
new file mode 100644
index 0000000..790e596
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/Handler.java
@@ -0,0 +1,108 @@
+/*
+ * 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.handler;
+
+import lombok.Getter;
+import org.apache.shardingsphere.elasticjob.restful.Http;
+import org.apache.shardingsphere.elasticjob.restful.annotation.ParamSource;
+import org.apache.shardingsphere.elasticjob.restful.annotation.Param;
+import org.apache.shardingsphere.elasticjob.restful.annotation.RequestBody;
+import org.apache.shardingsphere.elasticjob.restful.annotation.Returning;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Handle holds a handle method and an instance for method invoking.
+ * Describes parameters requirements of handle method.
+ */
+public final class Handler {
+    
+    private final Object instance;
+    
+    private final Method handleMethod;
+    
+    @Getter
+    private final List<HandlerParameter> handlerParameters;
+    
+    /**
+     * HTTP status code to return.
+     */
+    @Getter
+    private final int httpStatusCode;
+    
+    /**
+     * Content type to producing.
+     */
+    @Getter
+    private final String producing;
+    
+    public Handler(final Object instance, final Method handleMethod) {
+        this.instance = instance;
+        this.handleMethod = handleMethod;
+        this.handlerParameters = parseHandleMethodParameter();
+        this.httpStatusCode = parseReturning();
+        this.producing = parseProducing();
+    }
+    
+    /**
+     * Execute handle method with required arguments.
+     *
+     * @param args Required arguments
+     * @return Method invoke result
+     * @throws InvocationTargetException Wraps exception thrown by invoked method
+     * @throws IllegalAccessException    Handle method is not accessible
+     */
+    public Object execute(final Object... args) throws InvocationTargetException, IllegalAccessException {
+        return handleMethod.invoke(instance, args);
+    }
+    
+    private List<HandlerParameter> parseHandleMethodParameter() {
+        List<HandlerParameter> params = new LinkedList<>();
+        Parameter[] parameters = handleMethod.getParameters();
+        for (int i = 0; i < parameters.length; i++) {
+            Parameter parameter = parameters[i];
+            Param annotation = parameter.getAnnotation(Param.class);
+            HandlerParameter handlerParameter;
+            if (null != annotation) {
+                handlerParameter = new HandlerParameter(i, parameter.getType(), annotation.source(), annotation.name());
+            } else if (null != parameter.getAnnotation(RequestBody.class)) {
+                handlerParameter = new HandlerParameter(i, parameter.getType(), ParamSource.BODY, parameter.getName());
+            } else {
+                handlerParameter = new HandlerParameter(i, parameter.getType(), ParamSource.UNKNOWN, parameter.getName());
+            }
+            params.add(handlerParameter);
+        }
+        return Collections.unmodifiableList(params);
+    }
+    
+    private int parseReturning() {
+        Returning returning = handleMethod.getAnnotation(Returning.class);
+        return Optional.ofNullable(returning).map(Returning::code).orElse(200);
+    }
+    
+    private String parseProducing() {
+        Returning returning = handleMethod.getAnnotation(Returning.class);
+        return Optional.ofNullable(returning).map(Returning::contentType).orElse(Http.DEFAULT_CONTENT_TYPE);
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/HandlerMappingRegistry.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/HandlerMappingRegistry.java
new file mode 100644
index 0000000..33b001e
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/HandlerMappingRegistry.java
@@ -0,0 +1,64 @@
+/*
+ * 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.handler;
+
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpRequest;
+import org.apache.shardingsphere.elasticjob.restful.mapping.MappingContext;
+import org.apache.shardingsphere.elasticjob.restful.mapping.RegexUrlPatternMap;
+import org.apache.shardingsphere.elasticjob.restful.mapping.UrlPatternMap;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * HandlerMappingRegistry stores mappings of handlers.
+ * Search a proper {@link MappingContext} by HTTP method and request URI.
+ */
+public final class HandlerMappingRegistry {
+    
+    private final Map<HttpMethod, UrlPatternMap<Handler>> mappings = new HashMap<>();
+    
+    /**
+     * Get a MappingContext with Handler for the request.
+     *
+     * @param httpRequest Http request
+     * @return A MappingContext if matched. Return null if mismatched.
+     */
+    public MappingContext<Handler> getMappingContext(final HttpRequest httpRequest) {
+        UrlPatternMap<Handler> urlPatternMap = mappings.get(httpRequest.method());
+        String uriWithoutQuery = httpRequest.uri().split("\\?")[0];
+        return Optional
+                .ofNullable(urlPatternMap.match(uriWithoutQuery))
+                .orElse(null);
+    }
+    
+    /**
+     * Add a Handler for a path pattern.
+     *
+     * @param method      HTTP method
+     * @param pathPattern Path pattern
+     * @param handler     Handler
+     */
+    public void addMapping(final HttpMethod method, final String pathPattern, final Handler handler) {
+        mappings.computeIfAbsent(method, httpMethod -> new RegexUrlPatternMap<>());
+        UrlPatternMap<Handler> urlPatternMap = mappings.get(method);
+        urlPatternMap.put(pathPattern, handler);
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/HandlerNotFoundException.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/HandlerNotFoundException.java
new file mode 100644
index 0000000..f8f45a9
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/HandlerNotFoundException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.handler;
+
+import java.text.MessageFormat;
+
+public final class HandlerNotFoundException extends RuntimeException {
+    
+    private final String path;
+    
+    public HandlerNotFoundException(final String path) {
+        super(MessageFormat.format("No handler found for [{0}].", path));
+        this.path = path;
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/HandlerParameter.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/HandlerParameter.java
new file mode 100644
index 0000000..8a08ecc
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/handler/HandlerParameter.java
@@ -0,0 +1,38 @@
+/*
+ * 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.handler;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.apache.shardingsphere.elasticjob.restful.annotation.ParamSource;
+
+/**
+ * Describe parameters of a handle method.
+ */
+@RequiredArgsConstructor
+@Getter
+public final class HandlerParameter {
+    
+    private final int index;
+    
+    private final Class<?> type;
+    
+    private final ParamSource paramSource;
+    
+    private final String name;
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/AmbiguousPathPatternException.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/AmbiguousPathPatternException.java
new file mode 100644
index 0000000..0567184
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/AmbiguousPathPatternException.java
@@ -0,0 +1,28 @@
+/*
+ * 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.mapping;
+
+/**
+ * A path matched more than one path patterns and failed to determine which pattern is more proper.
+ */
+public final class AmbiguousPathPatternException extends RuntimeException {
+    
+    public AmbiguousPathPatternException(final String message) {
+        super(message);
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/DefaultMappingContext.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/DefaultMappingContext.java
new file mode 100644
index 0000000..cac664e
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/DefaultMappingContext.java
@@ -0,0 +1,43 @@
+/*
+ * 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.mapping;
+
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Default mapping context.
+ *
+ * @param <T> Type of payload
+ */
+@RequiredArgsConstructor
+public final class DefaultMappingContext<T> implements MappingContext<T> {
+    
+    private final String pattern;
+    
+    private final T payload;
+    
+    @Override
+    public String pattern() {
+        return pattern;
+    }
+    
+    @Override
+    public T payload() {
+        return payload;
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/MappingContext.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/MappingContext.java
new file mode 100644
index 0000000..4b4e2b5
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/MappingContext.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.mapping;
+
+/**
+ * MappingContext will hold a path pattern and a payload.
+ *
+ * @param <T> Payload type
+ */
+public interface MappingContext<T> {
+    
+    /**
+     * The path pattern of this MappingContext.
+     *
+     * @return Path pattern
+     */
+    String pattern();
+    
+    /**
+     * Payload of this MappingContext.
+     *
+     * @return Payload
+     */
+    T payload();
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/PathMatcher.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/PathMatcher.java
new file mode 100644
index 0000000..7588d27
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/PathMatcher.java
@@ -0,0 +1,59 @@
+/*
+ * 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.mapping;
+
+import java.util.Map;
+
+/**
+ * PathMatcher is a supporting tool for HTTP request dispatching.
+ * <p>
+ * Used by {@link UrlPatternMap}, {@link org.apache.shardingsphere.elasticjob.restful.pipeline.HandlerParameterDecoder}
+ * for template variables extracting, path pattern validating, pattern matching.
+ * </p>
+ *
+ * @see RegexPathMatcher
+ */
+public interface PathMatcher {
+    
+    /**
+     * Capture actual values of placeholder.
+     * The format of Path pattern likes <code>/app/{jobName}/{status}</code>.
+     *
+     * @param pathPattern Path pattern contains templates
+     * @param path        Actual path
+     * @return Map from template name to actual value
+     */
+    Map<String, String> captureVariables(String pathPattern, String path);
+    
+    /**
+     * Check if the path pattern matches the given path.
+     *
+     * @param pathPattern Path pattern
+     * @param path        The path to check
+     * @return true if matched, or else false
+     */
+    boolean matches(String pathPattern, String path);
+    
+    /**
+     * Check if the given string is a valid path pattern.
+     *
+     * @param pathPattern Path pattern to check
+     * @return true if valid, or else false
+     */
+    boolean isValidPathPattern(String pathPattern);
+}
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
new file mode 100644
index 0000000..55e29a2
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/RegexPathMatcher.java
@@ -0,0 +1,99 @@
+/*
+ * 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.mapping;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Implemented {@link PathMatcher} by regular expression.
+ */
+public final class RegexPathMatcher implements PathMatcher {
+    
+    private static final String PATH_SEPARATOR = "/";
+    
+    private static final Pattern PATH_PATTERN = Pattern.compile("^(?:/|(/[^/{}?]+|/\\{[^/{}?]+})+)$");
+    
+    private static final Pattern TEMPLATE_PATTERN = Pattern.compile("\\{(?<template>[^/]+)}");
+    
+    private static final String TEMPLATE_REGEX = "(?<${template}>[^/]+)";
+    
+    private final Map<String, Pattern> patternCache = new ConcurrentHashMap<>();
+    
+    @Override
+    public Map<String, String> captureVariables(final String pathPattern, final String path) {
+        Pattern compiled = getCompiledPattern(pathPattern);
+        String pathWithoutQuery = trimUriQuery(path);
+        Matcher matcher = compiled.matcher(pathWithoutQuery);
+        if (!matcher.matches() || 0 == matcher.groupCount()) {
+            return Collections.emptyMap();
+        }
+        Map<String, String> variables = new LinkedHashMap<>();
+        for (String variableName : extractTemplateNames(pathPattern)) {
+            variables.put(variableName, matcher.group(variableName));
+        }
+        return Collections.unmodifiableMap(variables);
+    }
+    
+    @Override
+    public boolean matches(final String pathPattern, final String path) {
+        return getCompiledPattern(pathPattern).matcher(trimUriQuery(path)).matches();
+    }
+    
+    @Override
+    public boolean isValidPathPattern(final String pathPattern) {
+        return PATH_PATTERN.matcher(pathPattern).matches();
+    }
+    
+    private Pattern getCompiledPattern(final String pathPattern) {
+        String regexPattern = convertToRegexPattern(pathPattern);
+        patternCache.computeIfAbsent(regexPattern, Pattern::compile);
+        return patternCache.get(regexPattern);
+    }
+    
+    private String convertToRegexPattern(final String pathPattern) {
+        return TEMPLATE_PATTERN.matcher(pathPattern).replaceAll(TEMPLATE_REGEX);
+    }
+    
+    private List<String> extractTemplateNames(final String pathPattern) {
+        String[] pathFragments = pathPattern.split(PATH_SEPARATOR);
+        List<String> names = new ArrayList<>();
+        for (String fragment : pathFragments) {
+            int start = fragment.indexOf('{');
+            int end = fragment.lastIndexOf('}');
+            if (-1 != start && -1 != end) {
+                names.add(fragment.substring(start + 1, end));
+            }
+        }
+        return names;
+    }
+    
+    private String trimUriQuery(final String uri) {
+        int index = uri.indexOf('?');
+        if (-1 != index) {
+            return uri.substring(0, index);
+        }
+        return uri;
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/RegexUrlPatternMap.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/RegexUrlPatternMap.java
new file mode 100644
index 0000000..2b25777
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/RegexUrlPatternMap.java
@@ -0,0 +1,105 @@
+/*
+ * 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.mapping;
+
+import com.google.common.base.Preconditions;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * Implemented {@link UrlPatternMap} by regular expression.
+ *
+ * @param <V> Type of payload
+ */
+public final class RegexUrlPatternMap<V> implements UrlPatternMap<V> {
+    
+    private static final String PATH_SEPARATOR = "/";
+    
+    private static final Pattern TEMPLATE_PATTERN = Pattern.compile("(?<=/)\\{(?<template>[^/]+)}");
+    
+    private final Map<String, MappingContext<V>> map = new LinkedHashMap<>();
+    
+    private final PathMatcher pathMatcher = new RegexPathMatcher();
+    
+    @Override
+    public void put(final String pathPattern, final V value) {
+        Objects.requireNonNull(pathPattern, "Path pattern must be not null.");
+        Preconditions.checkArgument(pathMatcher.isValidPathPattern(pathPattern), "Path pattern [%s] invalid.", pathPattern);
+        String unified = unifyPattern(pathPattern);
+        MappingContext<V> mappingContext = new DefaultMappingContext<>(pathPattern, value);
+        if (map.containsKey(unified)) {
+            throw new IllegalArgumentException(String.format("Duplicate pattern [%s]", unified));
+        }
+        map.put(unified, mappingContext);
+    }
+    
+    @Override
+    public MappingContext<V> match(final String path) {
+        List<MappingContext<V>> hits = new ArrayList<>();
+        for (Map.Entry<String, MappingContext<V>> entry : map.entrySet()) {
+            final String pattern = entry.getKey();
+            if (pattern.equals(path)) {
+                return entry.getValue();
+            }
+            if (pathMatcher.matches(pattern, path)) {
+                hits.add(entry.getValue());
+            }
+        }
+        if (hits.isEmpty()) {
+            return null;
+        }
+        if (1 < hits.size()) {
+            hits.sort(new MappingComparator().reversed());
+        }
+        return hits.get(0);
+    }
+    
+    private String unifyPattern(final String pattern) {
+        return TEMPLATE_PATTERN.matcher(pattern).replaceAll("[^/]+");
+    }
+    
+    static class MappingComparator implements Comparator<MappingContext<?>> {
+        
+        @Override
+        public int compare(final MappingContext<?> o1, final MappingContext<?> o2) {
+            String[] s1 = o1.pattern().split(PATH_SEPARATOR);
+            String[] s2 = o2.pattern().split(PATH_SEPARATOR);
+            int len = Math.min(s1.length, s2.length);
+            for (int i = 0; i < len; i++) {
+                if (isTemplate(s1[i]) && !isTemplate(s2[i])) {
+                    return -1;
+                }
+                if (!isTemplate(s1[i]) && isTemplate(s2[i])) {
+                    return 1;
+                }
+            }
+            throw new AmbiguousPathPatternException(MessageFormat.format("Ambiguous path pattern: [{0}], [{1}].", o1.pattern(), o2.pattern()));
+        }
+        
+        private static boolean isTemplate(final String fragment) {
+            return fragment.startsWith("{") && fragment.endsWith("}");
+        }
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/UrlPatternMap.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/UrlPatternMap.java
new file mode 100644
index 0000000..cd96c46
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/mapping/UrlPatternMap.java
@@ -0,0 +1,43 @@
+/*
+ * 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.mapping;
+
+/**
+ * UrlPatternMap is used for path pattern storage and path matching.
+ * {@link MappingContext} is an object holding path pattern and payload.
+ *
+ * @param <V> Type of payload
+ */
+public interface UrlPatternMap<V> {
+    
+    /**
+     * Add a path pattern and value to UrlPatternMap.
+     *
+     * @param pathPattern Path pattern
+     * @param value       Payload of the path pattern
+     */
+    void put(String pathPattern, V value);
+    
+    /**
+     * Find a proper MappingContext for current path.
+     *
+     * @param path A path to match.
+     * @return A MappingContext if the path matched a pattern. Return null if mismatched.
+     */
+    MappingContext<V> match(String path);
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/ExceptionHandling.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/ExceptionHandling.java
new file mode 100644
index 0000000..1603b81
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/ExceptionHandling.java
@@ -0,0 +1,102 @@
+/*
+ * 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.Unpooled;
+import io.netty.channel.ChannelHandler.Sharable;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpUtil;
+import io.netty.handler.codec.http.HttpVersion;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.shardingsphere.elasticjob.restful.handler.ExceptionHandleResult;
+import org.apache.shardingsphere.elasticjob.restful.handler.ExceptionHandler;
+import org.apache.shardingsphere.elasticjob.restful.Http;
+import org.apache.shardingsphere.elasticjob.restful.serializer.ResponseBodySerializer;
+import org.apache.shardingsphere.elasticjob.restful.serializer.ResponseBodySerializerFactory;
+
+import java.util.Map;
+
+/**
+ * Catch exceptions and look for a ExceptionHandler.
+ */
+@Sharable
+public final class ExceptionHandling extends ChannelInboundHandlerAdapter {
+    
+    private static final DefaultExceptionHandler DEFAULT_EXCEPTION_HANDLER = new DefaultExceptionHandler();
+    
+    private final Map<Class<? extends Throwable>, ExceptionHandler<? extends Throwable>> exceptionHandlers;
+    
+    public ExceptionHandling(final Map<Class<? extends Throwable>, ExceptionHandler<? extends Throwable>> exceptionHandlers) {
+        this.exceptionHandlers = exceptionHandlers;
+    }
+    
+    @Override
+    public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception {
+        ExceptionHandler<Throwable> exceptionHandler = searchExceptionHandler(cause);
+        ExceptionHandleResult handleResult = exceptionHandler.handleException(cause);
+        String mimeType = HttpUtil.getMimeType(handleResult.getContentType()).toString();
+        ResponseBodySerializer serializer = ResponseBodySerializerFactory.getResponseBodySerializer(mimeType);
+        byte[] body = serializer.serialize(handleResult.getResult());
+        FullHttpResponse response = createHttpResponse(handleResult.getStatusCode(), handleResult.getContentType(), body);
+        ctx.writeAndFlush(response);
+    }
+    
+    private FullHttpResponse createHttpResponse(final int statusCode, final String contentType, final byte[] body) {
+        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(statusCode), Unpooled.copiedBuffer(body));
+        response.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType);
+        HttpUtil.setContentLength(response, body.length);
+        return response;
+    }
+    
+    private <T extends Throwable> ExceptionHandler<T> searchExceptionHandler(final Throwable cause) {
+        Class<? extends Throwable> exceptionType = cause.getClass();
+        ExceptionHandler<? extends Throwable> exceptionHandler = exceptionHandlers.get(exceptionType);
+        if (null == exceptionHandler) {
+            for (Map.Entry<Class<? extends Throwable>, ExceptionHandler<? extends Throwable>> entry : exceptionHandlers.entrySet()) {
+                Class<? extends Throwable> clazz = entry.getKey();
+                ExceptionHandler<? extends Throwable> handler = entry.getValue();
+                if (clazz.isAssignableFrom(exceptionType)) {
+                    exceptionHandler = handler;
+                    break;
+                }
+            }
+        }
+        if (null == exceptionHandler) {
+            exceptionHandler = DEFAULT_EXCEPTION_HANDLER;
+        }
+        return (ExceptionHandler<T>) exceptionHandler;
+    }
+    
+    @Slf4j
+    private static class DefaultExceptionHandler implements ExceptionHandler<Throwable> {
+        
+        @Override
+        public ExceptionHandleResult handleException(final Throwable ex) {
+            return ExceptionHandleResult.builder()
+                    .statusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code())
+                    .contentType(Http.DEFAULT_CONTENT_TYPE)
+                    .result(ex)
+                    .build();
+        }
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HandleMethodExecutor.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HandleMethodExecutor.java
new file mode 100644
index 0000000..2dcfa92
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HandleMethodExecutor.java
@@ -0,0 +1,76 @@
+/*
+ * 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.Unpooled;
+import io.netty.channel.ChannelHandler.Sharable;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpUtil;
+import io.netty.handler.codec.http.HttpVersion;
+import org.apache.shardingsphere.elasticjob.restful.handler.HandleContext;
+import org.apache.shardingsphere.elasticjob.restful.handler.Handler;
+import org.apache.shardingsphere.elasticjob.restful.serializer.ResponseBodySerializer;
+import org.apache.shardingsphere.elasticjob.restful.serializer.ResponseBodySerializerFactory;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * The handler which actually executes handle method and creates HTTP response for responding.
+ * If an exception occurred when executing handle method, this handler would pass it to Handler named {@link ExceptionHandling}.
+ */
+@Sharable
+public final class HandleMethodExecutor extends ChannelInboundHandlerAdapter {
+    
+    @Override
+    public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
+        HandleContext<Handler> handleContext = (HandleContext<Handler>) msg;
+        Handler handler = handleContext.getMappingContext().payload();
+        Object[] args = handleContext.getArgs();
+        
+        Object handleResult = handler.execute(args);
+        
+        String mimeType = HttpUtil.getMimeType(handler.getProducing()).toString();
+        ResponseBodySerializer serializer = ResponseBodySerializerFactory.getResponseBodySerializer(mimeType);
+        byte[] bodyBytes = serializer.serialize(handleResult);
+        FullHttpResponse response = createHttpResponse(handler.getProducing(), bodyBytes, handler.getHttpStatusCode());
+        ctx.writeAndFlush(response);
+    }
+    
+    private FullHttpResponse createHttpResponse(final String producingContentType, final byte[] bodyBytes, final int statusCode) {
+        HttpResponseStatus httpResponseStatus = HttpResponseStatus.valueOf(statusCode);
+        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, httpResponseStatus, Unpooled.wrappedBuffer(bodyBytes));
+        response.headers().set(HttpHeaderNames.CONTENT_TYPE, producingContentType);
+        HttpUtil.setContentLength(response, bodyBytes.length);
+        HttpUtil.setKeepAlive(response, true);
+        return response;
+    }
+    
+    @Override
+    public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception {
+        if (cause instanceof InvocationTargetException) {
+            ctx.fireExceptionCaught(cause.getCause());
+        } else {
+            ctx.fireExceptionCaught(cause);
+        }
+    }
+}
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
new file mode 100644
index 0000000..736f198
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HandlerParameterDecoder.java
@@ -0,0 +1,157 @@
+/*
+ * 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 com.google.common.base.Preconditions;
+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;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.shardingsphere.elasticjob.restful.handler.HandleContext;
+import org.apache.shardingsphere.elasticjob.restful.handler.Handler;
+import org.apache.shardingsphere.elasticjob.restful.handler.HandlerParameter;
+import org.apache.shardingsphere.elasticjob.restful.Http;
+import org.apache.shardingsphere.elasticjob.restful.mapping.MappingContext;
+import org.apache.shardingsphere.elasticjob.restful.mapping.PathMatcher;
+import org.apache.shardingsphere.elasticjob.restful.mapping.RegexPathMatcher;
+import org.apache.shardingsphere.elasticjob.restful.deserializer.RequestBodyDeserializer;
+import org.apache.shardingsphere.elasticjob.restful.deserializer.RequestBodyDeserializerFactory;
+
+import java.text.MessageFormat;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * This handler is used for preparing parameters before executing handle method.
+ * It prepares arguments declared by {@link org.apache.shardingsphere.elasticjob.restful.annotation.Param}
+ * and {@link org.apache.shardingsphere.elasticjob.restful.annotation.RequestBody}, and deserializes arguments to declared type.
+ */
+@Slf4j
+@Sharable
+public final class HandlerParameterDecoder extends ChannelInboundHandlerAdapter {
+    
+    private final PathMatcher pathMatcher = new RegexPathMatcher();
+    
+    @Override
+    public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
+        HandleContext<Handler> handleContext = (HandleContext<Handler>) msg;
+        FullHttpRequest httpRequest = handleContext.getHttpRequest();
+        MappingContext<Handler> mappingContext = handleContext.getMappingContext();
+        Object[] arguments = prepareArguments(httpRequest, mappingContext);
+        handleContext.setArgs(arguments);
+        ctx.fireChannelRead(handleContext);
+    }
+    
+    private Object[] prepareArguments(final FullHttpRequest httpRequest, final MappingContext<Handler> mappingContext) {
+        Handler handler = mappingContext.payload();
+        List<HandlerParameter> handlerParameters = handler.getHandlerParameters();
+        Map<String, List<String>> queryParameters = parseQuery(httpRequest.uri());
+        Map<String, String> templateVariables = pathMatcher.captureVariables(mappingContext.pattern(), httpRequest.uri());
+        Object[] args = new Object[handlerParameters.size()];
+        boolean requestBodyAlreadyParsed = false;
+        for (int i = 0; i < handlerParameters.size(); i++) {
+            HandlerParameter handlerParameter = handlerParameters.get(i);
+            Object parsedValue = null;
+            switch (handlerParameter.getParamSource()) {
+                case PATH:
+                    parsedValue = deserializeBuiltInType(handlerParameter.getType(), templateVariables.get(handlerParameter.getName()));
+                    break;
+                case QUERY:
+                    List<String> queryValues = queryParameters.get(handlerParameter.getName());
+                    parsedValue = deserializeQueryParameter(handlerParameter.getType(), queryValues);
+                    break;
+                case HEADER:
+                    String headerValue = httpRequest.headers().get(handlerParameter.getName());
+                    parsedValue = deserializeBuiltInType(handlerParameter.getType(), headerValue);
+                    break;
+                case BODY:
+                    Preconditions.checkState(!requestBodyAlreadyParsed, "@RequestBody duplicated on handle method.");
+                    byte[] bytes = ByteBufUtil.getBytes(httpRequest.content());
+                    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));
+                    }
+                    parsedValue = deserializer.deserialize(handlerParameter.getType(), bytes);
+                    requestBodyAlreadyParsed = true;
+                    break;
+                case UNKNOWN:
+                    log.warn("Unknown source argument [{}] on index [{}].", handlerParameter.getName(), handlerParameter.getIndex());
+                    break;
+                default:
+            }
+            args[i] = parsedValue;
+        }
+        return args;
+    }
+    
+    private Map<String, List<String>> parseQuery(final String uri) {
+        QueryStringDecoder queryStringDecoder = new QueryStringDecoder(uri);
+        return queryStringDecoder.parameters();
+    }
+    
+    private Object deserializeQueryParameter(final Class<?> targetType, final List<String> queryValues) {
+        if (null == queryValues || queryValues.isEmpty()) {
+            return null;
+        }
+        if (1 == queryValues.size()) {
+            return deserializeBuiltInType(targetType, queryValues.get(0));
+        }
+        throw new UnsupportedOperationException("Multi value query doesn't support yet.");
+    }
+    
+    private Object deserializeBuiltInType(final Class<?> targetType, final String value) {
+        Preconditions.checkArgument(!value.isEmpty(), "Cannot deserialize empty value.");
+        if (String.class.equals(targetType)) {
+            return value;
+        }
+        if (Boolean.class.equals(targetType) || boolean.class.equals(targetType)) {
+            return Boolean.parseBoolean(value);
+        }
+        if (Character.class.equals(targetType) || char.class.equals(targetType)) {
+            Preconditions.checkArgument(1 >= value.length(), MessageFormat.format("Cannot set value [{0}] into a char.", value));
+            return value.charAt(0);
+        }
+        if (Byte.class.equals(targetType) || byte.class.equals(targetType)) {
+            return Byte.parseByte(value);
+        }
+        if (Short.class.equals(targetType) || short.class.equals(targetType)) {
+            return Short.parseShort(value);
+        }
+        if (Integer.class.equals(targetType) || int.class.equals(targetType)) {
+            return Integer.parseInt(value);
+        }
+        if (Long.class.equals(targetType) || long.class.equals(targetType)) {
+            return Long.parseLong(value);
+        }
+        if (Float.class.equals(targetType) || float.class.equals(targetType)) {
+            return Float.parseFloat(value);
+        }
+        if (Double.class.equals(targetType) || double.class.equals(targetType)) {
+            return Double.parseDouble(value);
+        }
+        throw new IllegalArgumentException(MessageFormat.format("Cannot deserialize path variable [{0}] into [{1}]", value, targetType.getName()));
+    }
+}
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
new file mode 100644
index 0000000..0fa26f1
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HttpRequestDispatcher.java
@@ -0,0 +1,81 @@
+/*
+ * 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.channel.ChannelHandler.Sharable;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpMethod;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.shardingsphere.elasticjob.restful.handler.HandleContext;
+import org.apache.shardingsphere.elasticjob.restful.handler.Handler;
+import org.apache.shardingsphere.elasticjob.restful.handler.HandlerMappingRegistry;
+import org.apache.shardingsphere.elasticjob.restful.handler.HandlerNotFoundException;
+import org.apache.shardingsphere.elasticjob.restful.mapping.MappingContext;
+import org.apache.shardingsphere.elasticjob.restful.RestfulController;
+import org.apache.shardingsphere.elasticjob.restful.annotation.ContextPath;
+import org.apache.shardingsphere.elasticjob.restful.annotation.Mapping;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * If a HTTP request reached, HttpRequestDispatcher would lookup a proper Handler for the request.
+ * Assemble a {@link HandleContext} with HTTP request and {@link MappingContext}, then pass it to the next in-bound handler.
+ */
+@Sharable
+@Slf4j
+public final class HttpRequestDispatcher extends ChannelInboundHandlerAdapter {
+    
+    private final HandlerMappingRegistry mappingRegistry = new HandlerMappingRegistry();
+    
+    public HttpRequestDispatcher(final List<RestfulController> restfulControllers) {
+        initMappingRegistry(restfulControllers);
+    }
+    
+    @Override
+    public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
+        log.debug("{}", msg);
+        FullHttpRequest request = (FullHttpRequest) msg;
+        MappingContext<Handler> mappingContext = mappingRegistry.getMappingContext(request);
+        if (null == mappingContext) {
+            throw new HandlerNotFoundException(request.uri());
+        }
+        HandleContext<Handler> handleContext = new HandleContext<>(request, mappingContext);
+        ctx.fireChannelRead(handleContext);
+    }
+    
+    private void initMappingRegistry(final List<RestfulController> restfulControllers) {
+        for (RestfulController restfulController : restfulControllers) {
+            Class<? extends RestfulController> controllerClass = restfulController.getClass();
+            String contextPath = Optional.ofNullable(controllerClass.getAnnotation(ContextPath.class)).map(ContextPath::value).orElse("");
+            for (Method method : controllerClass.getMethods()) {
+                Mapping mapping = method.getAnnotation(Mapping.class);
+                if (null == mapping) {
+                    continue;
+                }
+                HttpMethod httpMethod = HttpMethod.valueOf(mapping.method());
+                String pattern = mapping.path();
+                String fullPathPattern = contextPath + pattern;
+                mappingRegistry.addMapping(httpMethod, fullPathPattern, new Handler(restfulController, method));
+            }
+        }
+    }
+}
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
new file mode 100644
index 0000000..bd261a9
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/pipeline/RestfulServiceChannelInitializer.java
@@ -0,0 +1,57 @@
+/*
+ * 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.channel.Channel;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpServerCodec;
+import org.apache.shardingsphere.elasticjob.restful.NettyRestfulServiceConfiguration;
+
+/**
+ * Initialize channel pipeline.
+ */
+public final class RestfulServiceChannelInitializer extends ChannelInitializer<Channel> {
+    
+    private final HttpRequestDispatcher httpRequestDispatcher;
+    
+    private final HandlerParameterDecoder handlerParameterDecoder;
+    
+    private final HandleMethodExecutor handleMethodExecutor;
+    
+    private final ExceptionHandling exceptionHandling;
+    
+    public RestfulServiceChannelInitializer(final NettyRestfulServiceConfiguration configuration) {
+        httpRequestDispatcher = new HttpRequestDispatcher(configuration.getControllerInstances());
+        handlerParameterDecoder = new HandlerParameterDecoder();
+        handleMethodExecutor = new HandleMethodExecutor();
+        exceptionHandling = new ExceptionHandling(configuration.getExceptionHandlers());
+    }
+    
+    @Override
+    protected void initChannel(final Channel channel) throws Exception {
+        ChannelPipeline pipeline = channel.pipeline();
+        pipeline.addLast("codec", new HttpServerCodec());
+        pipeline.addLast("aggregator", new HttpObjectAggregator(1024 * 1024));
+        pipeline.addLast("dispatcher", httpRequestDispatcher);
+        pipeline.addLast("handlerParameterDecoder", handlerParameterDecoder);
+        pipeline.addLast("handleMethodExecutor", handleMethodExecutor);
+        pipeline.addLast("exceptionHandling", exceptionHandling);
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/serializer/ResponseBodySerializer.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/serializer/ResponseBodySerializer.java
new file mode 100644
index 0000000..6c5c22d
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/serializer/ResponseBodySerializer.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * Serializer for serializing response body with specific MIME type.
+ */
+public interface ResponseBodySerializer {
+    
+    /**
+     * Specify which type would be serialized by this serializer.
+     *
+     * @return MIME type
+     */
+    String mimeType();
+    
+    /**
+     * Serialize object to bytes.
+     *
+     * @param responseBody Object to be serialized
+     * @return bytes
+     */
+    byte[] serialize(Object responseBody);
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/serializer/ResponseBodySerializerFactory.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/serializer/ResponseBodySerializerFactory.java
new file mode 100644
index 0000000..118dfb3
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/serializer/ResponseBodySerializerFactory.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.serializer;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Response body serializer factory.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class ResponseBodySerializerFactory {
+    
+    private static final Map<String, ResponseBodySerializer> RESPONSE_BODY_SERIALIZERS = new ConcurrentHashMap<>();
+    
+    static {
+        for (ResponseBodySerializer serializer : ServiceLoader.load(ResponseBodySerializer.class)) {
+            RESPONSE_BODY_SERIALIZERS.put(serializer.mimeType(), serializer);
+        }
+    }
+    
+    /**
+     * Get serializer for specific HTTP content type.
+     *
+     * @param contentType HTTP content type
+     * @return Serializer
+     */
+    public static ResponseBodySerializer getResponseBodySerializer(final String contentType) {
+        return RESPONSE_BODY_SERIALIZERS.get(contentType);
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/serializer/impl/JsonResponseBodySerializer.java b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/serializer/impl/JsonResponseBodySerializer.java
new file mode 100644
index 0000000..5552fcb
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/java/org/apache/shardingsphere/elasticjob/restful/serializer/impl/JsonResponseBodySerializer.java
@@ -0,0 +1,42 @@
+/*
+ * 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.impl;
+
+import com.google.gson.Gson;
+import io.netty.handler.codec.http.HttpHeaderValues;
+import org.apache.shardingsphere.elasticjob.restful.serializer.ResponseBodySerializer;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Serializer for <code>application/json</code>.
+ */
+public final class JsonResponseBodySerializer implements ResponseBodySerializer {
+    
+    private final Gson gson = new Gson();
+    
+    @Override
+    public String mimeType() {
+        return HttpHeaderValues.APPLICATION_JSON.toString();
+    }
+    
+    @Override
+    public byte[] serialize(final Object responseBody) {
+        return gson.toJson(responseBody).getBytes(StandardCharsets.UTF_8);
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/main/resources/META-INF/services/org.apache.shardingsphere.elasticjob.restful.deserializer.RequestBodyDeserializer b/elasticjob-infra/elasticjob-restful/src/main/resources/META-INF/services/org.apache.shardingsphere.elasticjob.restful.deserializer.RequestBodyDeserializer
new file mode 100644
index 0000000..b0a4b73
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/resources/META-INF/services/org.apache.shardingsphere.elasticjob.restful.deserializer.RequestBodyDeserializer
@@ -0,0 +1,19 @@
+#
+# 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.deserializer.impl.JsonRequestBodyDeserializer
+org.apache.shardingsphere.elasticjob.restful.deserializer.impl.TextPlainRequestBodyDeserializer
diff --git a/elasticjob-infra/elasticjob-restful/src/main/resources/META-INF/services/org.apache.shardingsphere.elasticjob.restful.serializer.ResponseBodySerializer b/elasticjob-infra/elasticjob-restful/src/main/resources/META-INF/services/org.apache.shardingsphere.elasticjob.restful.serializer.ResponseBodySerializer
new file mode 100644
index 0000000..5b02313
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/main/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.impl.JsonResponseBodySerializer
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
new file mode 100644
index 0000000..68b0777
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/RegexPathMatcherTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+import org.apache.shardingsphere.elasticjob.restful.mapping.PathMatcher;
+import org.apache.shardingsphere.elasticjob.restful.mapping.RegexPathMatcher;
+import org.junit.Test;
+
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public class RegexPathMatcherTest {
+    
+    @Test
+    public void assertCaptureTemplate() {
+        PathMatcher pathMatcher = new RegexPathMatcher();
+        Map<String, String> variables = pathMatcher.captureVariables("/app/{jobName}/disable/{until}/done", "/app/myJob/disable/20201231/done?name=some_name&value=some_value");
+        assertFalse(variables.isEmpty());
+        assertThat(variables.size(), is(2));
+        assertThat(variables.get("jobName"), is("myJob"));
+        assertThat(variables.get("until"), is("20201231"));
+        assertNull(variables.get("app"));
+    }
+    
+    @Test
+    public void assertCapturePatternWithoutTemplate() {
+        PathMatcher pathMatcher = new RegexPathMatcher();
+        Map<String, String> variables = pathMatcher.captureVariables("/app", "/app");
+        assertTrue(variables.isEmpty());
+    }
+    
+    @Test
+    public void assertPathMatch() {
+        PathMatcher pathMatcher = new RegexPathMatcher();
+        assertTrue(pathMatcher.matches("/app/{jobName}", "/app/myJob"));
+    }
+    
+    @Test
+    public void assertValidatePathPattern() {
+        PathMatcher pathMatcher = new RegexPathMatcher();
+        assertTrue(pathMatcher.isValidPathPattern("/"));
+        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(""));
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/RegexUrlPatternMapTest.java b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/RegexUrlPatternMapTest.java
new file mode 100644
index 0000000..c1fb645
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/RegexUrlPatternMapTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+import org.apache.shardingsphere.elasticjob.restful.mapping.MappingContext;
+import org.apache.shardingsphere.elasticjob.restful.mapping.RegexUrlPatternMap;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+
+public class RegexUrlPatternMapTest {
+    
+    @Test
+    public void assertRegexUrlPatternMap() {
+        RegexUrlPatternMap<Integer> urlPatternMap = new RegexUrlPatternMap<>();
+        urlPatternMap.put("/app/{jobName}", 1);
+        urlPatternMap.put("/app/list", 2);
+        urlPatternMap.put("/app/{jobName}/disable", 3);
+        urlPatternMap.put("/app/{jobName}/enable", 4);
+        MappingContext<Integer> mappingContext = urlPatternMap.match("/app/myJob");
+        assertNotNull(mappingContext);
+        assertThat(mappingContext.pattern(), is("/app/{jobName}"));
+        assertThat(mappingContext.payload(), is(1));
+        mappingContext = urlPatternMap.match("/app/list");
+        assertNotNull(mappingContext);
+        assertThat(mappingContext.pattern(), is("/app/list"));
+        assertThat(mappingContext.payload(), is(2));
+        mappingContext = urlPatternMap.match("/job/list");
+        assertNull(mappingContext);
+    }
+    
+    @Test
+    public void assertAmbiguous() {
+        RegexUrlPatternMap<Integer> urlPatternMap = new RegexUrlPatternMap<>();
+        urlPatternMap.put("/foo/{bar}/{fooName}/status", 10);
+        urlPatternMap.put("/foo/{bar}/operate/{metrics}", 11);
+        MappingContext<Integer> mappingContext = urlPatternMap.match("/foo/barValue/operate/status");
+        assertNotNull(mappingContext);
+        assertThat(mappingContext.pattern(), is("/foo/{bar}/operate/{metrics}"));
+        assertThat(mappingContext.payload(), is(11));
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void assertDuplicate() {
+        RegexUrlPatternMap<Integer> urlPatternMap = new RegexUrlPatternMap<>();
+        urlPatternMap.put("/app/{jobName}/enable", 0);
+        urlPatternMap.put("/app/{jobName}", 1);
+        urlPatternMap.put("/app/{appName}", 2);
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/controller/JobController.java b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/controller/JobController.java
new file mode 100644
index 0000000..aef76ec
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/controller/JobController.java
@@ -0,0 +1,90 @@
+/*
+ * 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 lombok.extern.slf4j.Slf4j;
+import org.apache.shardingsphere.elasticjob.restful.Http;
+import org.apache.shardingsphere.elasticjob.restful.annotation.ParamSource;
+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.Param;
+import org.apache.shardingsphere.elasticjob.restful.annotation.RequestBody;
+import org.apache.shardingsphere.elasticjob.restful.annotation.Returning;
+import org.apache.shardingsphere.elasticjob.restful.pojo.JobPojo;
+
+@Slf4j
+@ContextPath("/job")
+public class JobController implements RestfulController {
+    
+    /**
+     * Pretend to create a job.
+     *
+     * @param group       Group
+     * @param jobName     Job name
+     * @param cron        Job cron
+     * @param description Job description
+     * @return Result
+     */
+    @Mapping(method = Http.POST, path = "/{group}/{jobName}")
+    public JobPojo createJob(@Param(name = "group", source = ParamSource.PATH) final String group,
+                             @Param(name = "jobName", source = ParamSource.PATH) final String jobName,
+                             @Param(name = "cron", source = ParamSource.QUERY) final String cron,
+                             @RequestBody final String description) {
+        JobPojo jobPojo = new JobPojo();
+        jobPojo.setName(jobName);
+        jobPojo.setCron(cron);
+        jobPojo.setGroup(group);
+        jobPojo.setDescription(description);
+        return jobPojo;
+    }
+    
+    /**
+     * Throw an IllegalStateException.
+     *
+     * @param message Exception message
+     * @return None
+     */
+    @Mapping(method = Http.GET, path = "/throw/IllegalState")
+    public Object throwIllegalStateException(@Param(name = "Exception-Message", source = ParamSource.HEADER) final String message) {
+        throw new IllegalStateException(message);
+    }
+    
+    /**
+     * Throw an IllegalArgumentException.
+     *
+     * @param message Exception message
+     * @return None
+     */
+    @Mapping(method = Http.GET, path = "/throw/IllegalArgument")
+    public Object throwIllegalArgumentException(@Param(name = "Exception-Message", source = ParamSource.HEADER) final String message) {
+        throw new IllegalArgumentException(message);
+    }
+    
+    /**
+     * Return 204.
+     *
+     * @param noop Useless
+     * @return None
+     */
+    @Mapping(method = Http.GET, path = "/code/204")
+    @Returning(code = 204)
+    public Object return204(final String noop) {
+        return null;
+    }
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/handler/CustomIllegalStateExceptionHandler.java b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/handler/CustomIllegalStateExceptionHandler.java
new file mode 100644
index 0000000..95c5d28
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/handler/CustomIllegalStateExceptionHandler.java
@@ -0,0 +1,33 @@
+/*
+ * 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.handler;
+
+import org.apache.shardingsphere.elasticjob.restful.Http;
+import org.apache.shardingsphere.elasticjob.restful.pojo.ResultDto;
+
+public class CustomIllegalStateExceptionHandler implements ExceptionHandler<IllegalStateException> {
+    
+    @Override
+    public ExceptionHandleResult handleException(final IllegalStateException ex) {
+        return ExceptionHandleResult.builder()
+                .statusCode(403)
+                .contentType(Http.DEFAULT_CONTENT_TYPE)
+                .result(ResultDto.builder().code(1).data(ex.getLocalizedMessage()).build())
+                .build();
+    }
+}
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
new file mode 100644
index 0000000..410c1b3
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HttpClient.java
@@ -0,0 +1,80 @@
+/*
+ * 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.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.HttpClientCodec;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.SneakyThrows;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class HttpClient {
+    
+    /**
+     * Send a HTTP request and invoke consumer when server response.
+     *
+     * @param host           Server host
+     * @param port           Server port
+     * @param request        HTTP request
+     * @param consumer       HTTP response consumer
+     * @param timeoutSeconds Wait for consume
+     */
+    @SneakyThrows
+    public static void request(final String host, final int port, final FullHttpRequest request, final Consumer<FullHttpResponse> consumer, final Long timeoutSeconds) {
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
+        Channel channel = new Bootstrap()
+                .group(eventLoopGroup)
+                .channel(NioSocketChannel.class)
+                .remoteAddress(host, port)
+                .handler(new ChannelInitializer<Channel>() {
+                    @Override
+                    protected void initChannel(final Channel ch) throws Exception {
+                        ch.pipeline()
+                                .addLast(new HttpClientCodec())
+                                .addLast(new HttpObjectAggregator(1024 * 1024))
+                                .addLast(new SimpleChannelInboundHandler<FullHttpResponse>() {
+                                    @Override
+                                    protected void channelRead0(final ChannelHandlerContext ctx, final FullHttpResponse httpResponse) throws Exception {
+                                        consumer.accept(httpResponse);
+                                        countDownLatch.countDown();
+                                    }
+                                });
+                    }
+                }).connect()
+                .sync().channel();
+        channel.writeAndFlush(request);
+        countDownLatch.await(timeoutSeconds, TimeUnit.SECONDS);
+        channel.close().sync();
+    }
+}
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
new file mode 100644
index 0000000..e55fcf9
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/HttpRequestDispatcherTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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 com.google.common.collect.Lists;
+import io.netty.channel.embedded.EmbeddedChannel;
+import io.netty.handler.codec.http.DefaultFullHttpRequest;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpVersion;
+import org.apache.shardingsphere.elasticjob.restful.handler.HandlerNotFoundException;
+import org.apache.shardingsphere.elasticjob.restful.controller.JobController;
+import org.junit.Test;
+
+public class HttpRequestDispatcherTest {
+    
+    @Test(expected = HandlerNotFoundException.class)
+    public void assertDispatcherHandlerNotFound() {
+        EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDispatcher(Lists.newArrayList(new JobController())));
+        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
new file mode 100644
index 0000000..87ca121
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pipeline/NettyRestfulServiceTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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 com.google.gson.Gson;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.netty.handler.codec.http.DefaultFullHttpRequest;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpUtil;
+import io.netty.handler.codec.http.HttpVersion;
+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.JobController;
+import org.apache.shardingsphere.elasticjob.restful.handler.CustomIllegalStateExceptionHandler;
+import org.apache.shardingsphere.elasticjob.restful.pojo.JobPojo;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+public class NettyRestfulServiceTest {
+    
+    private static final String HOST = "localhost";
+    
+    private static final int PORT = 18080;
+    
+    private static RestfulService restfulService;
+    
+    @BeforeClass
+    public static void init() {
+        NettyRestfulServiceConfiguration configuration = new NettyRestfulServiceConfiguration(PORT);
+        configuration.setHost(HOST);
+        configuration.addControllerInstance(new JobController());
+        configuration.addExceptionHandler(IllegalStateException.class, new CustomIllegalStateExceptionHandler());
+        restfulService = new NettyRestfulService(configuration);
+        restfulService.startup();
+    }
+    
+    @SneakyThrows
+    @Test(timeout = 10000L)
+    public void assertRequestWithParameters() {
+        String cron = "0 * * * * ?";
+        String uri = String.format("/job/myGroup/myJob?cron=%s", URLEncoder.encode(cron, "UTF-8"));
+        String description = "Descriptions about this job.";
+        byte[] body = description.getBytes(StandardCharsets.UTF_8);
+        DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uri, Unpooled.wrappedBuffer(body));
+        request.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN);
+        HttpUtil.setContentLength(request, body.length);
+        HttpClient.request(HOST, PORT, request, httpResponse -> {
+            assertEquals(200, httpResponse.status().code());
+            byte[] bytes = ByteBufUtil.getBytes(httpResponse.content());
+            String json = new String(bytes, StandardCharsets.UTF_8);
+            Gson gson = new Gson();
+            JobPojo jobPojo = gson.fromJson(json, JobPojo.class);
+            assertThat(jobPojo.getCron(), is(cron));
+            assertThat(jobPojo.getGroup(), is("myGroup"));
+            assertThat(jobPojo.getName(), is("myJob"));
+            assertThat(jobPojo.getDescription(), is(description));
+        }, 10000L);
+    }
+    
+    @Test(timeout = 10000L)
+    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);
+    }
+    
+    @Test(timeout = 10000L)
+    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);
+    }
+    
+    @Test(timeout = 10000L)
+    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);
+    }
+    
+    @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/pojo/JobPojo.java b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pojo/JobPojo.java
new file mode 100644
index 0000000..bef033f
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pojo/JobPojo.java
@@ -0,0 +1,37 @@
+/*
+ * 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.pojo;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * A simple POJO for test.
+ */
+@Getter
+@Setter
+public class JobPojo {
+    
+    private String group;
+    
+    private String name;
+    
+    private String cron;
+    
+    private String description;
+}
diff --git a/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pojo/ResultDto.java b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pojo/ResultDto.java
new file mode 100644
index 0000000..6f870c9
--- /dev/null
+++ b/elasticjob-infra/elasticjob-restful/src/test/java/org/apache/shardingsphere/elasticjob/restful/pojo/ResultDto.java
@@ -0,0 +1,30 @@
+/*
+ * 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.pojo;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Builder
+@Getter
+public class ResultDto<T> {
+    
+    private final int code;
+    
+    private final T data;
+}
diff --git a/elasticjob-infra/pom.xml b/elasticjob-infra/pom.xml
index a9eb5ca..587b797 100644
--- a/elasticjob-infra/pom.xml
+++ b/elasticjob-infra/pom.xml
@@ -31,5 +31,6 @@
         <module>elasticjob-infra-common</module>
         <module>elasticjob-registry-center</module>
         <module>elasticjob-tracing</module>
+        <module>elasticjob-restful</module>
     </modules>
 </project>