Repository: caicloud/nirvana
Pull request #388 (2020-11-20 08:02:16)

[release-0.3] feat(*): support RPC style
This is an automated cherry-pick of #382

/assign iawia002

caicloud-bot: @caicloud-bot: Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our [release note process](https://git.k8s.io/community/contributors/guide/release-notes.md) to remove it.

<details>

Instructions for interacting with me using PR comments are available [here](https://github.com/caicloud/engineering/blob/master/guidelines/caicloud_bot.md).  If you have questions or suggestions related to my behavior, please file an issue against the [caicloud/tools](https://github.com/caicloud/tools/issues/new?title=Prow%20issue:) repository.
</details>
caicloud-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: 
To fully approve this pull request, please assign additional approvers.
We suggest the following additional approver: **ddysher**

Assign the PR to them by writing `/assign @ddysher` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://github.com/caicloud/engineering/blob/master/guidelines/caicloud_bot.md#bot-commands).

The pull request process is described [here](https://github.com/caicloud/engineering/blob/master/guidelines/caicloud_bot.md#typical-workflow)

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/caicloud/nirvana/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["ddysher"]} -->
iawia002: /release-note-none
/lgtm
caicloud-bot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/caicloud/nirvana/pull/388#issuecomment-731017727" title="LGTM">iawia002</a>*

The full list of commands accepted by this bot can be found [here](https://github.com/caicloud/engineering/blob/master/guidelines/caicloud_bot.md#bot-commands).

The pull request process is described [here](https://github.com/caicloud/engineering/blob/master/guidelines/caicloud_bot.md#typical-workflow)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/caicloud/nirvana/blob/master/OWNERS)~~ [iawia002]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/config/config.go b/config/config.go
index b3e5983..444ee33 100644
--- a/config/config.go
+++ b/config/config.go
@@ -24,9 +24,9 @@ import (
 	"time"
 
 	"github.com/caicloud/nirvana"
-	"github.com/caicloud/nirvana/definition"
 	"github.com/caicloud/nirvana/errors"
 	"github.com/caicloud/nirvana/utils/printer"
+
 	"github.com/spf13/cast"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
@@ -110,7 +110,7 @@ type NirvanaCommand interface {
 	// `desc` describes the field.
 	Add(pointer interface{}, key string, shortFlag string, desc string) NirvanaCommand
 	// Execute runs nirvana server.
-	Execute(descriptors ...definition.Descriptor) error
+	Execute(descriptors ...interface{}) error
 	// ExecuteWithConfig runs nirvana server from a custom config.
 	ExecuteWithConfig(cfg *nirvana.Config) error
 	// Command returns a command for command.
@@ -263,7 +263,7 @@ func (s *command) Add(pointer interface{}, key string, shortFlag string, desc st
 }
 
 // Execute runs nirvana server.
-func (s *command) Execute(descriptors ...definition.Descriptor) error {
+func (s *command) Execute(descriptors ...interface{}) error {
 	cfg := nirvana.NewDefaultConfig()
 	if len(descriptors) > 0 {
 		cfg.Configure(nirvana.Descriptor(descriptors...))
diff --git a/definition/definition.go b/definition/definition.go
index 4ade241..4369a83 100644
--- a/definition/definition.go
+++ b/definition/definition.go
@@ -224,14 +224,14 @@ type Definition struct {
 	Tags []string
 	// ErrorProduces is used to generate data for error. If this field is empty,
 	// it means that this field equals to Produces.
-	// In some cases, succeessful data and error data should be generated in
+	// In some cases, successful data and error data should be generated in
 	// different ways.
 	ErrorProduces []string
 	// Function is a function handler. It must be func type.
 	Function interface{}
 	// Parameters describes function parameters.
 	Parameters []Parameter
-	// Results describes function retrun values.
+	// Results describes function return values.
 	Results []Result
 	// Summary is a one-line brief description of this definition.
 	Summary string
@@ -240,31 +240,3 @@ type Definition struct {
 	// Examples contains many examples for the API handler.
 	Examples []Example
 }
-
-// Descriptor describes a descriptor for API definitions.
-type Descriptor struct {
-	// Path is the url path. It will inherit parent's path.
-	//
-	// If parent path is "/api/v1", current is "/some",
-	// It means current definitions handles "/api/v1/some".
-	Path string
-	// Consumes indicates content types that current definitions
-	// and child definitions can consume.
-	// It will override parent descriptor's consumes.
-	Consumes []string
-	// Produces indicates content types that current definitions
-	// and child definitions can produce.
-	// It will override parent descriptor's produces.
-	Produces []string
-	// Tags indicates tags of current definitions and child definitions.
-	// It will override parent descriptor's tags.
-	Tags []string
-	// Middlewares contains path middlewares.
-	Middlewares []Middleware
-	// Definitions contains definitions for current path.
-	Definitions []Definition
-	// Children is used to place sub-descriptors.
-	Children []Descriptor
-	// Description describes the usage of the path.
-	Description string
-}
diff --git a/definition/restful.go b/definition/restful.go
new file mode 100644
index 0000000..4796809
--- /dev/null
+++ b/definition/restful.go
@@ -0,0 +1,45 @@
+/*
+Copyright 2020 Caicloud Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package definition
+
+// Descriptor describes a descriptor for API definitions.
+type Descriptor struct {
+	// Path is the url path. It will inherit parent's path.
+	//
+	// If parent path is "/api/v1", current is "/some",
+	// It means current definitions handles "/api/v1/some".
+	Path string
+	// Consumes indicates content types that current definitions
+	// and child definitions can consume.
+	// It will override parent descriptor's consumes.
+	Consumes []string
+	// Produces indicates content types that current definitions
+	// and child definitions can produce.
+	// It will override parent descriptor's produces.
+	Produces []string
+	// Tags indicates tags of current definitions and child definitions.
+	// It will override parent descriptor's tags.
+	Tags []string
+	// Middlewares contains path middlewares.
+	Middlewares []Middleware
+	// Definitions contains definitions for current path.
+	Definitions []Definition
+	// Children is used to place sub-descriptors.
+	Children []Descriptor
+	// Description describes the usage of the path.
+	Description string
+}
diff --git a/definition/rpc.go b/definition/rpc.go
new file mode 100644
index 0000000..7ad307f
--- /dev/null
+++ b/definition/rpc.go
@@ -0,0 +1,76 @@
+/*
+Copyright 2020 Caicloud Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package definition
+
+// RPCDescriptor describes a descriptor for API definition in RPC style.
+type RPCDescriptor struct {
+	// Path describes url path prefix for all RPCActions, default: "/".
+	Path string
+	// Description describes the usage of the path.
+	Description string
+	// Middlewares contains path middlewares.
+	Middlewares []Middleware
+	// Tags indicates tags of current definitions and child definitions.
+	// It will override parent descriptor's tags.
+	Tags []string
+	// Consumes indicates content types that current definitions
+	// and child definitions can consume.
+	// It will override parent descriptor's consumes.
+	Consumes []string
+	// Produces indicates content types that current definitions
+	// and child definitions can produce.
+	// It will override parent descriptor's produces.
+	Produces []string
+	// Actions contain actions in this descriptor. These actions will inherit the Middlewares, Tags, Consumes, Produces
+	// of the descriptor if values in the action are not specified.
+	Actions []RPCAction
+}
+
+// RPCAction defines an API handler in RPC style.
+type RPCAction struct {
+	// Version defines the version this API belongs to.
+	// Need to use time format, eg: 2020-10-10
+	Version string
+	// Name defines the Action name.
+	Name string
+	// Consumes indicates how many content types the handler can consume.
+	// It will override parent descriptor's consumes.
+	Consumes []string
+	// Produces indicates how many content types the handler can produce.
+	// It will override parent descriptor's produces.
+	Produces []string
+	// Tags indicates tags of the API handler.
+	// It will override parent descriptor's tags.
+	Tags []string
+	// ErrorProduces is used to generate data for error. If this field is empty,
+	// it means that this field equals to Produces.
+	// In some cases, succeessful data and error data should be generated in
+	// different ways.
+	ErrorProduces []string
+	// Function is a function handler. It must be func type.
+	Function interface{}
+	// Parameters describes function parameters.
+	Parameters []Parameter
+	// Results describes function retrun values.
+	Results []Result
+	// Summary is a one-line brief description of this definition.
+	Summary string
+	// Description describes the API handler.
+	Description string
+	// Examples contains many examples for the API handler.
+	Examples []Example
+}
diff --git a/examples/getting-started/any-method/main.go b/examples/getting-started/any-method/main.go
index 7e67d51..d4ed7af 100644
--- a/examples/getting-started/any-method/main.go
+++ b/examples/getting-started/any-method/main.go
@@ -41,8 +41,8 @@ func main() {
 		log.Fatal(err)
 	}
 
-	descriptors := []definition.Descriptor{
-		{
+	descriptors := []interface{}{
+		definition.Descriptor{
 			Path:        "/",
 			Description: "hello API",
 			Definitions: []definition.Definition{
@@ -57,7 +57,7 @@ func main() {
 				},
 			},
 		},
-		{
+		definition.Descriptor{
 			Path:        "/proxy",
 			Description: "proxy API",
 			Definitions: []definition.Definition{
diff --git a/examples/getting-started/file/main.go b/examples/getting-started/file/main.go
index 1ed6f91..e12a8d3 100644
--- a/examples/getting-started/file/main.go
+++ b/examples/getting-started/file/main.go
@@ -38,8 +38,8 @@ func Upload(ctx context.Context, file multipart.File) (string, error) {
 }
 
 func main() {
-	descriptors := []definition.Descriptor{
-		{
+	descriptors := []interface{}{
+		definition.Descriptor{
 			Path:        "/required",
 			Description: "Upload API",
 			Definitions: []definition.Definition{
@@ -58,7 +58,7 @@ func main() {
 				},
 			},
 		},
-		{
+		definition.Descriptor{
 			Path:        "/optional",
 			Description: "Upload API",
 			Definitions: []definition.Definition{
diff --git a/examples/getting-started/handler/main.go b/examples/getting-started/handler/main.go
index 0c9cc6c..4719dda 100644
--- a/examples/getting-started/handler/main.go
+++ b/examples/getting-started/handler/main.go
@@ -42,8 +42,8 @@ func main() {
 		log.Fatal(err)
 	}
 
-	descriptors := []definition.Descriptor{
-		{
+	descriptors := []interface{}{
+		definition.Descriptor{
 			Path:        "/",
 			Description: "hello API",
 			Definitions: []definition.Definition{
@@ -58,7 +58,7 @@ func main() {
 				},
 			},
 		},
-		{
+		definition.Descriptor{
 			Path:        "/proxy",
 			Description: "proxy API",
 			Middlewares: []definition.Middleware{
diff --git a/examples/getting-started/rpc-style/main.go b/examples/getting-started/rpc-style/main.go
new file mode 100644
index 0000000..a4585f4
--- /dev/null
+++ b/examples/getting-started/rpc-style/main.go
@@ -0,0 +1,103 @@
+/*
+Copyright 2018 Caicloud Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+	"context"
+
+	"github.com/caicloud/nirvana"
+	"github.com/caicloud/nirvana/config"
+	"github.com/caicloud/nirvana/definition"
+	"github.com/caicloud/nirvana/log"
+	"github.com/caicloud/nirvana/middlewares/reqlog"
+	"github.com/caicloud/nirvana/service/builder"
+)
+
+var echo = definition.RPCDescriptor{
+	Description: "Echo API",
+	Middlewares: []definition.Middleware{reqlog.Default()},
+	Consumes:    []string{definition.MIMEAll},
+	Produces:    []string{definition.MIMEJSON},
+	Actions: []definition.RPCAction{
+		{
+			Name:    "GetEcho",
+			Version: "2020-10-10",
+			Function: func(ctx context.Context, msg string) (string, error) {
+				return msg, nil
+			},
+			Parameters: []definition.Parameter{
+				{
+					Source:      definition.Query,
+					Name:        "msg",
+					Description: "Corresponding to the second parameter",
+				},
+			},
+			Results: definition.DataErrorResults(""),
+		},
+		{
+			Name:    "CreateEcho",
+			Version: "2020-10-10",
+			Function: func(ctx context.Context, msg string) (string, error) {
+				return msg, nil
+			},
+			Parameters: []definition.Parameter{
+				{
+					Source:      definition.Query,
+					Name:        "msg",
+					Description: "Corresponding to the second parameter",
+				},
+			},
+			Results: definition.DataErrorResults(""),
+		},
+	},
+}
+
+var echo2 = definition.RPCDescriptor{
+	Path:        "/aaa",
+	Description: "Echo API",
+	Actions: []definition.RPCAction{
+		{
+			Name:    "DeleteEcho",
+			Version: "2020-10-10",
+			Function: func(ctx context.Context, msg string) (string, error) {
+				return msg, nil
+			},
+			Consumes: []string{definition.MIMEAll},
+			Produces: []string{definition.MIMEJSON},
+			Parameters: []definition.Parameter{
+				{
+					Source:      definition.Query,
+					Name:        "msg",
+					Description: "Corresponding to the second parameter",
+				},
+			},
+			Results: definition.DataErrorResults(""),
+		},
+	},
+}
+
+func main() {
+	cmd := config.NewDefaultNirvanaCommand()
+	conf := nirvana.NewDefaultConfig()
+	conf.Configure(
+		nirvana.APIStyle(builder.APIStyleRPC),
+		nirvana.Descriptor(echo, echo2),
+	)
+	if err := cmd.ExecuteWithConfig(conf); err != nil {
+		log.Fatal(err)
+	}
+}
diff --git a/nirvana.go b/nirvana.go
index 42e24e3..9f1d90e 100644
--- a/nirvana.go
+++ b/nirvana.go
@@ -23,10 +23,10 @@ import (
 	"sync"
 	"sync/atomic"
 
-	"github.com/caicloud/nirvana/definition"
 	"github.com/caicloud/nirvana/errors"
 	"github.com/caicloud/nirvana/log"
 	"github.com/caicloud/nirvana/service"
+	builderutil "github.com/caicloud/nirvana/service/builder"
 
 	// This blank import will make it in the dependencies of projects using Nirvana
 	// for API docs generation.
@@ -52,6 +52,8 @@ type Server interface {
 
 // Config describes configuration of server.
 type Config struct {
+	// apiStyle makes nirvana serve as REST or RPC style, default is REST.
+	apiStyle string
 	// tls cert file
 	certFile string
 	// tls ket file
@@ -63,7 +65,7 @@ type Config struct {
 	// logger is used to output info inside framework.
 	logger log.Logger
 	// descriptors contains all APIs.
-	descriptors []definition.Descriptor
+	descriptors []interface{}
 	// filters is http filters.
 	filters []service.Filter
 	// modifiers is definition modifiers
@@ -175,6 +177,7 @@ func NewDefaultConfig() *Config {
 // modifiers for specific scenario, please use NewDefaultConfig().
 func NewConfig() *Config {
 	return &Config{
+		apiStyle:  builderutil.APIStyleREST,
 		port:      8080,
 		logger:    &log.SilentLogger{},
 		configSet: make(map[string]interface{}),
@@ -214,7 +217,7 @@ func (s *server) Builder() (builder service.Builder, cleaner func() error, err e
 	if s.builder != nil {
 		return s.builder, s.cleaner, nil
 	}
-	builder = service.NewBuilder()
+	builder = builderutil.New(s.config.apiStyle)
 	builder.SetLogger(s.config.logger)
 	builder.AddFilter(s.config.filters...)
 	builder.SetModifier(s.config.modifiers.Combine())
@@ -328,6 +331,14 @@ func RegisterConfigInstaller(ci ConfigInstaller) {
 	installers[ci.Name()] = ci
 }
 
+// APIStyle returns a configurer to set api style into config.
+func APIStyle(apiStyle string) Configurer {
+	return func(c *Config) error {
+		c.apiStyle = apiStyle
+		return nil
+	}
+}
+
 // IP returns a configurer to set ip into config.
 func IP(ip string) Configurer {
 	return func(c *Config) error {
@@ -366,7 +377,7 @@ func Logger(logger log.Logger) Configurer {
 }
 
 // Descriptor returns a configurer to add descriptors into config.
-func Descriptor(descriptors ...definition.Descriptor) Configurer {
+func Descriptor(descriptors ...interface{}) Configurer {
 	return func(c *Config) error {
 		c.descriptors = append(c.descriptors, descriptors...)
 		return nil
diff --git a/plugins/apidocs/apidocs.go b/plugins/apidocs/apidocs.go
index a438e96..a6a6c51 100644
--- a/plugins/apidocs/apidocs.go
+++ b/plugins/apidocs/apidocs.go
@@ -59,7 +59,7 @@ func (i *apiDocsInstaller) Install(builder service.Builder, cfg *nirvana.Config)
 			files[filename] = data
 		}
 
-		apiDescriptor := make([]definition.Descriptor, 0, len(files)+1)
+		apiDescriptor := make([]interface{}, 0, len(files)+1)
 		versions := make([]string, 0, len(files))
 		for v, data := range files {
 			versions = append(versions, v)
diff --git a/rest/rest.go b/rest/rest.go
index 9be1dc0..bc1e114 100644
--- a/rest/rest.go
+++ b/rest/rest.go
@@ -34,7 +34,7 @@ import (
 
 	"github.com/caicloud/nirvana/definition"
 	"github.com/caicloud/nirvana/errors"
-	"github.com/caicloud/nirvana/service/router"
+	"github.com/caicloud/nirvana/service/rest/router"
 )
 
 type path struct {
diff --git a/service/builder/builder.go b/service/builder/builder.go
new file mode 100644
index 0000000..5be4b0d
--- /dev/null
+++ b/service/builder/builder.go
@@ -0,0 +1,40 @@
+/*
+Copyright 2020 Caicloud Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package builder
+
+import (
+	"github.com/caicloud/nirvana/service"
+	"github.com/caicloud/nirvana/service/rest"
+	"github.com/caicloud/nirvana/service/rpc"
+)
+
+const (
+	// APIStyleREST represents the RESTful API style.
+	APIStyleREST = "rest"
+	// APIStyleRPC represents the RPC API style.
+	APIStyleRPC = "rpc"
+)
+
+// New creates a service builder based on the given API style.
+func New(apiStyle string) service.Builder {
+	switch apiStyle {
+	case APIStyleRPC:
+		return rpc.NewBuilder()
+	default:
+		return rest.NewBuilder()
+	}
+}
diff --git a/service/context.go b/service/context.go
index 1565d94..d6df489 100644
--- a/service/context.go
+++ b/service/context.go
@@ -29,21 +29,22 @@ import (
 
 var (
 	// contextKeyUnderlyingHTTPContext is a key for context.
-	// It's unique and point to httpCtx.
+	// It's unique and point to HTTPCtx.
 	contextKeyUnderlyingHTTPContext interface{} = new(byte)
 )
 
-// httpCtx contains a http.Request and a http.ResponseWriter for a request.
+// HTTPCtx contains a http.Request and a http.ResponseWriter for a request.
 // It goes through the life cycle of a request.
-type httpCtx struct {
+type HTTPCtx struct {
 	context.Context
 	container container
 	response  response
 	path      string
 }
 
-func newHTTPContext(resp http.ResponseWriter, request *http.Request) *httpCtx {
-	ctx := &httpCtx{}
+// NewHTTPContext generates the http context from ResponseWriter and Request.
+func NewHTTPContext(resp http.ResponseWriter, request *http.Request) *HTTPCtx {
+	ctx := &HTTPCtx{}
 	ctx.Context = request.Context()
 	ctx.container.request = request
 	ctx.container.params = make([]param, 0, 5)
@@ -52,7 +53,7 @@ func newHTTPContext(resp http.ResponseWriter, request *http.Request) *httpCtx {
 }
 
 // Value returns itself when key is contextKeyUnderlyingHTTPContext.
-func (c *httpCtx) Value(key interface{}) interface{} {
+func (c *HTTPCtx) Value(key interface{}) interface{} {
 	if key == contextKeyUnderlyingHTTPContext {
 		return c
 	}
@@ -61,6 +62,10 @@ func (c *httpCtx) Value(key interface{}) interface{} {
 
 // ValueContainer contains values from a request.
 type ValueContainer interface {
+	// Set sets path parameter key-value pairs.
+	Set(key, value string)
+	// Get gets path value.
+	Get(key string) (string, bool)
 	// Path returns path value by key.
 	Path(key string) (string, bool)
 	// Query returns value from query string.
@@ -185,7 +190,7 @@ type response struct {
 	respBody       []byte
 }
 
-// For http.HTTPResponseWriter and HTTPResponseInfo
+// Header For http.HTTPResponseWriter and HTTPResponseInfo
 func (c *response) Header() http.Header {
 	return c.writer.Header()
 }
@@ -267,7 +272,7 @@ type HTTPContext interface {
 	ResponseWriter() ResponseWriter
 	ValueContainer() ValueContainer
 	RoutePath() string
-	setRoutePath(path string)
+	SetRoutePath(path string)
 }
 
 // HTTPContextFrom get http context from context.
@@ -276,33 +281,33 @@ func HTTPContextFrom(ctx context.Context) HTTPContext {
 	if value == nil {
 		return nil
 	}
-	if c, ok := value.(*httpCtx); ok {
+	if c, ok := value.(*HTTPCtx); ok {
 		return c
 	}
 	return nil
 }
 
 // Request gets http.Request.
-func (c *httpCtx) Request() *http.Request {
+func (c *HTTPCtx) Request() *http.Request {
 	return c.container.request
 }
 
 // ResponseWriter gets ResponseWriter.
-func (c *httpCtx) ResponseWriter() ResponseWriter {
+func (c *HTTPCtx) ResponseWriter() ResponseWriter {
 	return &c.response
 }
 
 // ValueContainer gets ValueContainer.
-func (c *httpCtx) ValueContainer() ValueContainer {
+func (c *HTTPCtx) ValueContainer() ValueContainer {
 	return &c.container
 }
 
 // RoutePath is the abstract path which matches request URL.
-func (c *httpCtx) RoutePath() string {
+func (c *HTTPCtx) RoutePath() string {
 	return c.path
 }
 
-// setRoutePath sets the abstract path which matches request URL.
-func (c *httpCtx) setRoutePath(path string) {
+// SetRoutePath sets the abstract path which matches request URL.
+func (c *HTTPCtx) SetRoutePath(path string) {
 	c.path = path
 }
diff --git a/service/executor/errors.go b/service/executor/errors.go
new file mode 100644
index 0000000..f506d13
--- /dev/null
+++ b/service/executor/errors.go
@@ -0,0 +1,71 @@
+/*
+Copyright 2020 Caicloud Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package executor
+
+import (
+	"github.com/caicloud/nirvana/errors"
+)
+
+var (
+	// DefinitionNoMethod represents no http method error.
+	DefinitionNoMethod = errors.InternalServerError.Build("Nirvana:Service:DefinitionNoMethod", "no http method in [${method}]${path}")
+	// DefinitionNoConsumes represents no content type to consume.
+	DefinitionNoConsumes = errors.InternalServerError.Build("Nirvana:Service:DefinitionNoConsumes", "no content type to consume in [${method}]${path}")
+	// DefinitionNoProduces represents no content type to produce.
+	DefinitionNoProduces = errors.InternalServerError.Build("Nirvana:Service:DefinitionNoProduces", "no content type to produce in [${method}]${path}")
+	// DefinitionNoErrorProduces represents no content type to produce error.
+	DefinitionNoErrorProduces = errors.InternalServerError.Build("Nirvana:Service:DefinitionNoErrorProduces", "no content type to produce error in [${method}]${path}")
+	// DefinitionNoFunction represents no function error.
+	DefinitionNoFunction = errors.InternalServerError.Build("Nirvana:Service:DefinitionNoFunction", "no function in [${method}]${path}")
+	// DefinitionInvalidFunctionType represents invalid function type.
+	DefinitionInvalidFunctionType = errors.InternalServerError.Build("Nirvana:Service:DefinitionInvalidFunctionType", "${type} is not function in [${method}]${path}")
+	// DefinitionNoConsumer represents no consumer error.
+	DefinitionNoConsumer = errors.InternalServerError.Build("Nirvana:Service:DefinitionNoConsumer", "no consumer for content type ${type} in [${method}]${path}")
+	// DefinitionNoProducer represents no producer error.
+	DefinitionNoProducer = errors.InternalServerError.Build("Nirvana:Service:DefinitionNoProducer", "no producer for content type ${type} in [${method}]${path}")
+	// DefinitionConflict represents conflict error.
+	DefinitionConflict = errors.InternalServerError.Build("Nirvana:Service:DefinitionConflict", "consumer-producer pair ${key}:${value} conflicts in [http.${method}]${path}")
+	// DefinitionUnmatchedParameters represents parameters unmatch.
+	DefinitionUnmatchedParameters = errors.InternalServerError.Build(
+		"Nirvana:Service:DefinitionUnmatchedParameters",
+		"function ${function} has ${count} parameters but want ${desired} in ${path}, you can define it with descriptor->definition[]->parameters[]",
+	)
+	// DefinitionUnmatchedResults represents results unmatch.
+	DefinitionUnmatchedResults = errors.InternalServerError.Build(
+		"Nirvana:Service:DefinitionUnmatchedResults",
+		"function ${function} has ${count} results but want ${desired} in ${path}, you can define it with descriptor->definition[]->results[]",
+	)
+	// NoDestinationHandler represents no DestinationHandler error.
+	NoDestinationHandler = errors.InternalServerError.Build(
+		"Nirvana:Service:NoDestinationHandler",
+		"no destination handler for destination ${destination}, you can define it with descriptor->definition[]->results[]->destination",
+	)
+	// InvalidParameter represents invalid parameter error.
+	InvalidParameter = errors.InternalServerError.Build("Nirvana:Service:InvalidParameter", "can't validate ${order} parameter of function ${name}: ${err}")
+	// InvalidResult represents invalid parameter error.
+	InvalidResult = errors.InternalServerError.Build("Nirvana:Service:InvalidResult", "can't validate ${order} result of function ${name}: ${err}")
+	// InvalidOperatorsForParameter represents invalid operators error.
+	InvalidOperatorsForParameter = errors.InternalServerError.Build("Nirvana:Service:InvalidOperatorsForParameter", "can't validate operators for ${order} parameter of function ${name}: ${err}")
+	// InvalidOperatorsForResult represents invalid operators error.
+	InvalidOperatorsForResult = errors.InternalServerError.Build("Nirvana:Service:InvalidOperatorsForResult", "can't validate operators for ${order} result of function ${name}: ${err}")
+)
+
+var (
+	requiredField          = errors.InternalServerError.Build("Nirvana:Service:RequiredField", "required field ${field} in ${source} but got empty")
+	invalidOperatorInType  = errors.InternalServerError.Build("Nirvana:Service:invalidOperatorInType", "the type ${type} is not compatible to the in type of the ${index} operator")
+	invalidOperatorOutType = errors.InternalServerError.Build("Nirvana:Service:invalidOperatorOutType", "the out type of the ${index} operator is not compatible to the type ${type}")
+)
diff --git a/service/executor.go b/service/executor/executor.go
similarity index 60%
rename from service/executor.go
rename to service/executor/executor.go
index ea4d2aa..f84ba8f 100644
--- a/service/executor.go
+++ b/service/executor/executor.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2017 Caicloud Authors
+Copyright 2020 Caicloud Authors
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package service
+package executor
 
 import (
 	"context"
@@ -27,54 +27,51 @@ import (
 	"sort"
 
 	"github.com/caicloud/nirvana/definition"
-	"github.com/caicloud/nirvana/log"
-	"github.com/caicloud/nirvana/service/router"
+	"github.com/caicloud/nirvana/service"
 )
 
-type inspector struct {
-	path      string
-	logger    log.Logger
-	executors map[string][]*executor
-}
+// Executor executes with a context.
+type Executor interface {
+	MiddlewareExecutor
 
-func newInspector(path string, logger log.Logger) *inspector {
-	return &inspector{
-		path:      path,
-		logger:    logger,
-		executors: map[string][]*executor{},
-	}
+	ContentTypeMap() map[string][]string
+	Acceptable(string) bool
+	Producible([]string) bool
 }
 
-func (i *inspector) addDefinition(d definition.Definition) error {
+// DefinitionToExecutor generates a Executor for the Definition.
+func DefinitionToExecutor(urlPath string, d definition.Definition, customCode int) (Executor, error) {
 	var method string
 	if d.Method == definition.Any {
 		method = string(definition.Any)
 	} else {
-		method = HTTPMethodFor(d.Method)
+		method = service.HTTPMethodFor(d.Method)
 	}
 	if method == "" {
-		return definitionNoMethod.Error(d.Method, i.path)
+		return nil, DefinitionNoMethod.Error(d.Method, urlPath)
 	}
 	if len(d.Consumes) <= 0 {
-		return definitionNoConsumes.Error(d.Method, i.path)
+		return nil, DefinitionNoConsumes.Error(d.Method, urlPath)
 	}
 	if len(d.Produces) <= 0 {
-		return definitionNoProduces.Error(d.Method, i.path)
+		return nil, DefinitionNoProduces.Error(d.Method, urlPath)
 	}
 	if len(d.ErrorProduces) <= 0 {
-		return definitionNoErrorProduces.Error(d.Method, i.path)
+		return nil, DefinitionNoErrorProduces.Error(d.Method, urlPath)
 	}
 	if d.Function == nil {
-		return definitionNoFunction.Error(d.Method, i.path)
+		return nil, DefinitionNoFunction.Error(d.Method, urlPath)
 	}
 	value := reflect.ValueOf(d.Function)
 	if value.Kind() != reflect.Func {
-		return definitionInvalidFunctionType.Error(value.Type(), d.Method, i.path)
+		return nil, DefinitionInvalidFunctionType.Error(value.Type(), d.Method, urlPath)
+	}
+	if customCode == 0 {
+		customCode = service.HTTPCodeFor(d.Method)
 	}
 	c := &executor{
-		logger:   i.logger,
 		method:   method,
-		code:     HTTPCodeFor(d.Method),
+		code:     customCode,
 		function: value,
 	}
 	consumeAll := false
@@ -84,16 +81,16 @@ func (i *inspector) addDefinition(d definition.Definition) error {
 			consumeAll = true
 			continue
 		}
-		if consumer := ConsumerFor(ct); consumer != nil {
+		if consumer := service.ConsumerFor(ct); consumer != nil {
 			c.consumers = append(c.consumers, consumer)
 			consumes[consumer.ContentType()] = true
 		} else {
-			return definitionNoConsumer.Error(ct, d.Method, i.path)
+			return nil, DefinitionNoConsumer.Error(ct, d.Method, urlPath)
 		}
 	}
 	if consumeAll {
 		// Add remaining consumers to executor.
-		for _, consumer := range AllConsumers() {
+		for _, consumer := range service.AllConsumers() {
 			if !consumes[consumer.ContentType()] {
 				c.consumers = append(c.consumers, consumer)
 			}
@@ -106,16 +103,16 @@ func (i *inspector) addDefinition(d definition.Definition) error {
 			produceAll = true
 			continue
 		}
-		if producer := ProducerFor(ct); producer != nil {
+		if producer := service.ProducerFor(ct); producer != nil {
 			c.producers = append(c.producers, producer)
 			produces[producer.ContentType()] = true
 		} else {
-			return definitionNoProducer.Error(ct, d.Method, i.path)
+			return nil, DefinitionNoProducer.Error(ct, d.Method, urlPath)
 		}
 	}
 	if produceAll {
 		// Add remaining producers to executor.
-		for _, producer := range AllProducers() {
+		for _, producer := range service.AllProducers() {
 			if !produces[producer.ContentType()] {
 				c.producers = append(c.producers, producer)
 			}
@@ -128,16 +125,16 @@ func (i *inspector) addDefinition(d definition.Definition) error {
 			errorProduceAll = true
 			continue
 		}
-		if producer := ProducerFor(ct); producer != nil {
+		if producer := service.ProducerFor(ct); producer != nil {
 			c.errorProducers = append(c.errorProducers, producer)
 			errorProduces[producer.ContentType()] = true
 		} else {
-			return definitionNoProducer.Error(ct, d.Method, i.path)
+			return nil, DefinitionNoProducer.Error(ct, d.Method, urlPath)
 		}
 	}
 	if errorProduceAll {
 		// Add remaining producers to executor.
-		for _, producer := range AllProducers() {
+		for _, producer := range service.AllProducers() {
 			if !errorProduces[producer.ContentType()] {
 				c.errorProducers = append(c.errorProducers, producer)
 			}
@@ -151,57 +148,28 @@ func (i *inspector) addDefinition(d definition.Definition) error {
 	// 2. Anonymous function: api.glob..func1(create.go#30)
 	//    Anonymous function names are generated by go. Don't explore their meaning.
 	funcName := fmt.Sprintf("%s(%s#%d)", path.Base(f.Name()), path.Base(file), line)
-	ps, err := i.generateParameters(funcName, value.Type(), d.Parameters)
+	ps, err := generateParameters(urlPath, funcName, value.Type(), d.Parameters)
 	if err != nil {
-		return err
+		return nil, err
 	}
 	c.parameters = ps
-	rs, err := i.generateResults(funcName, value.Type(), d.Results)
+	rs, err := generateResults(urlPath, funcName, value.Type(), d.Results)
 	if err != nil {
-		return err
+		return nil, err
 	}
 	c.results = rs
-	if err := i.conflictCheck(c); err != nil {
-		return err
-	}
-	i.executors[method] = append(i.executors[method], c)
-	return nil
+	return c, nil
 }
 
-func (i *inspector) conflictCheck(c *executor) error {
-	cs := i.executors[c.method]
-	if len(cs) <= 0 {
-		return nil
-	}
-	ctMap := map[string]bool{}
-	for _, extant := range cs {
-		result := extant.ctMap()
-		for k, vs := range result {
-			for _, v := range vs {
-				ctMap[k+":"+v] = true
-			}
-		}
-	}
-	cMap := c.ctMap()
-	for k, vs := range cMap {
-		for _, v := range vs {
-			if ctMap[k+":"+v] {
-				return definitionConflict.Error(k, v, c.method, i.path)
-			}
-		}
-	}
-	return nil
-}
-
-func (i *inspector) generateParameters(funcName string, typ reflect.Type, ps []definition.Parameter) ([]parameter, error) {
+func generateParameters(path, funcName string, typ reflect.Type, ps []definition.Parameter) ([]parameter, error) {
 	if typ.NumIn() != len(ps) {
-		return nil, definitionUnmatchedParameters.Error(funcName, typ.NumIn(), len(ps), i.path)
+		return nil, DefinitionUnmatchedParameters.Error(funcName, typ.NumIn(), len(ps), path)
 	}
 	parameters := make([]parameter, 0, len(ps))
 	for index, p := range ps {
-		generator := ParameterGeneratorFor(p.Source)
+		generator := service.ParameterGeneratorFor(p.Source)
 		if generator == nil {
-			return nil, noParameterGenerator.Error(p.Source)
+			return nil, service.NoParameterGenerator.Error(p.Source)
 		}
 
 		param := parameter{
@@ -218,13 +186,11 @@ func (i *inspector) generateParameters(funcName string, typ reflect.Type, ps []d
 		}
 		if err := generator.Validate(param.name, param.defaultValue, param.targetType); err != nil {
 			// Order from 0 is odd. So index+1.
-			i.logger.Errorf("Can't validate %s parameter of function %s: %s", order(index+1), funcName, err.Error())
-			return nil, err
+			return nil, InvalidParameter.Error(order(index+1), funcName, err.Error())
 		}
 		if len(param.operators) > 0 {
-			if err := i.validateOperators(param.targetType, typ.In(index), param.operators); err != nil {
-				i.logger.Errorf("Can't validate operators for %s parameter of function %s: %s", order(index+1), funcName, err.Error())
-				return nil, err
+			if err := validateOperators(param.targetType, typ.In(index), param.operators); err != nil {
+				return nil, InvalidOperatorsForParameter.Error(order(index+1), funcName, err.Error())
 			}
 		}
 		parameters = append(parameters, param)
@@ -232,15 +198,15 @@ func (i *inspector) generateParameters(funcName string, typ reflect.Type, ps []d
 	return parameters, nil
 }
 
-func (i *inspector) generateResults(funcName string, typ reflect.Type, rs []definition.Result) ([]result, error) {
+func generateResults(path, funcName string, typ reflect.Type, rs []definition.Result) ([]result, error) {
 	if typ.NumOut() != len(rs) {
-		return nil, definitionUnmatchedResults.Error(funcName, typ.NumOut(), len(rs), i.path)
+		return nil, DefinitionUnmatchedResults.Error(funcName, typ.NumOut(), len(rs), path)
 	}
 	results := make([]result, 0, len(rs))
 	for index, r := range rs {
-		handler := DestinationHandlerFor(r.Destination)
+		handler := service.DestinationHandlerFor(r.Destination)
 		if handler == nil {
-			return nil, noDestinationHandler.Error(r.Destination)
+			return nil, NoDestinationHandler.Error(r.Destination)
 		}
 		result := result{
 			index:     index,
@@ -250,22 +216,19 @@ func (i *inspector) generateResults(funcName string, typ reflect.Type, rs []defi
 		outType := typ.Out(index)
 		if len(result.operators) > 0 {
 			LastOperatorOutType := result.operators[len(result.operators)-1].Out()
-			if err := i.validateOperators(outType, LastOperatorOutType, result.operators); err != nil {
-				i.logger.Errorf("Can't validate operators for %s result of function %s: %s", order(index+1), funcName, err.Error())
-				return nil, err
+			if err := validateOperators(outType, LastOperatorOutType, result.operators); err != nil {
+				return nil, InvalidOperatorsForResult.Error(order(index+1), funcName, err.Error())
 			}
 			outType = LastOperatorOutType
 		}
 		if err := handler.Validate(outType); err != nil {
 			// Order from 0 is odd. So index+1.
-			i.logger.Errorf("Can't validate %s result of function %s: %s", order(index+1), funcName, err.Error())
-			return nil, err
+			return nil, InvalidResult.Error(order(index+1), funcName, err.Error())
 		}
 		results = append(results, result)
 	}
 	sort.Sort(resultsSorter(results))
 	return results, nil
-
 }
 
 // validateOperators checks if the chain is valid:
@@ -273,7 +236,7 @@ func (i *inspector) generateResults(funcName string, typ reflect.Type, rs []defi
 //   operators[0].Out() -> operators[1].In()
 //   ...
 //   operators[N].Out() -> out
-func (i *inspector) validateOperators(in, out reflect.Type, operators []definition.Operator) error {
+func validateOperators(in, out reflect.Type, operators []definition.Operator) error {
 	if len(operators) <= 0 {
 		return nil
 	}
@@ -294,90 +257,12 @@ func (i *inspector) validateOperators(in, out reflect.Type, operators []definiti
 	return nil
 }
 
-type resultsSorter []result
-
-// Len is the number of elements in the collection.
-func (s resultsSorter) Len() int {
-	return len(s)
-}
-
-// Less reports whether the element with
-// index i should sort before the element with index j.
-func (s resultsSorter) Less(i, j int) bool {
-	return s[i].handler.Priority() < s[j].handler.Priority()
-}
-
-// Swap swaps the elements with indexes i and j.
-func (s resultsSorter) Swap(i, j int) {
-	s[i], s[j] = s[j], s[i]
-}
-
-// Inspect finds a valid executor to execute target context.
-func (i *inspector) Inspect(ctx context.Context) (router.Executor, error) {
-	httpCtx := HTTPContextFrom(ctx)
-	req := httpCtx.Request()
-	if req == nil {
-		return nil, noContext.Error()
-	}
-	executors := make([]*executor, 0)
-	if cs, ok := i.executors[req.Method]; ok && len(cs) > 0 {
-		executors = append(executors, cs...)
-	}
-	if cs, ok := i.executors[string(definition.Any)]; ok && len(cs) > 0 {
-		executors = append(executors, cs...)
-	}
-	if len(executors) <= 0 {
-		return nil, noExecutorForMethod.Error()
-	}
-	ct, err := ContentType(req)
-	if err != nil {
-		return nil, err
-	}
-	accepted := 0
-	for i, c := range executors {
-		if c.acceptable(ct) {
-			if accepted != i {
-				executors[accepted] = c
-			}
-			accepted++
-		}
-	}
-	if accepted <= 0 {
-		return nil, noExecutorForContentType.Error()
-	}
-	ats, err := AcceptTypes(req)
-	if err != nil {
-		return nil, err
-	}
-	executors = executors[:accepted]
-	var target *executor
-	for _, c := range executors {
-		if c.producible(ats) {
-			target = c
-			break
-		}
-	}
-	if target == nil {
-		for _, at := range ats {
-			if at == definition.MIMEAll {
-				target = executors[0]
-			}
-		}
-	}
-	if target == nil {
-		return nil, noExecutorToProduce.Error()
-	}
-	httpCtx.setRoutePath(i.path)
-	return target, nil
-}
-
 type executor struct {
-	logger         log.Logger
 	method         string
 	code           int
-	consumers      []Consumer
-	producers      []Producer
-	errorProducers []Producer
+	consumers      []service.Consumer
+	producers      []service.Producer
+	errorProducers []service.Producer
 	parameters     []parameter
 	results        []result
 	function       reflect.Value
@@ -387,38 +272,36 @@ type parameter struct {
 	name         string
 	targetType   reflect.Type
 	defaultValue interface{}
-	generator    ParameterGenerator
+	generator    service.ParameterGenerator
 	operators    []definition.Operator
 	optional     bool
 }
 
 type result struct {
 	index     int
-	handler   DestinationHandler
+	handler   service.DestinationHandler
 	operators []definition.Operator
 }
 
-func (e *executor) ctMap() map[string][]string {
-	result := map[string][]string{}
-	for _, c := range e.consumers {
-		for _, p := range e.producers {
-			ct := c.ContentType()
-			result[ct] = append(result[ct], p.ContentType())
-		}
-	}
-	return result
+type resultsSorter []result
+
+// Len is the number of elements in the collection.
+func (s resultsSorter) Len() int {
+	return len(s)
 }
 
-func (e *executor) acceptable(ct string) bool {
-	for _, c := range e.consumers {
-		if c.ContentType() == ct {
-			return true
-		}
-	}
-	return false
+// Less reports whether the element with
+// index i should sort before the element with index j.
+func (s resultsSorter) Less(i, j int) bool {
+	return s[i].handler.Priority() < s[j].handler.Priority()
 }
 
-func (e *executor) check(producers []Producer, ats []string) bool {
+// Swap swaps the elements with indexes i and j.
+func (s resultsSorter) Swap(i, j int) {
+	s[i], s[j] = s[j], s[i]
+}
+
+func (e *executor) check(producers []service.Producer, ats []string) bool {
 	for _, at := range ats {
 		for _, c := range producers {
 			if c.ContentType() == at {
@@ -429,21 +312,41 @@ func (e *executor) check(producers []Producer, ats []string) bool {
 	return false
 }
 
-func (e *executor) producible(ats []string) bool {
+func (e *executor) Acceptable(ct string) bool {
+	for _, c := range e.consumers {
+		if c.ContentType() == ct {
+			return true
+		}
+	}
+	return false
+}
+
+func (e *executor) Producible(ats []string) bool {
 	return e.check(e.producers, ats) && e.check(e.errorProducers, ats)
 }
 
+func (e *executor) ContentTypeMap() map[string][]string {
+	result := map[string][]string{}
+	for _, c := range e.consumers {
+		for _, p := range e.producers {
+			ct := c.ContentType()
+			result[ct] = append(result[ct], p.ContentType())
+		}
+	}
+	return result
+}
+
 // Execute executes with context.
 func (e *executor) Execute(ctx context.Context) (err error) {
-	c := HTTPContextFrom(ctx)
+	c := service.HTTPContextFrom(ctx)
 	if c == nil {
-		return noContext.Error()
+		return service.NoContext.Error()
 	}
 	paramValues := make([]reflect.Value, 0, len(e.parameters))
 	for _, p := range e.parameters {
 		result, err := p.generator.Generate(ctx, c.ValueContainer(), e.consumers, p.name, p.targetType)
 		if err != nil {
-			return writeError(ctx, e.errorProducers, err)
+			return service.WriteError(ctx, e.errorProducers, err)
 		}
 		if result == nil {
 			if p.defaultValue != nil {
@@ -455,12 +358,12 @@ func (e *executor) Execute(ctx context.Context) (err error) {
 		for _, operator := range p.operators {
 			result, err = operator.Operate(ctx, p.name, result)
 			if err != nil {
-				return writeError(ctx, e.errorProducers, err)
+				return service.WriteError(ctx, e.errorProducers, err)
 			}
 		}
 
 		if result == nil && !p.optional {
-			return writeError(ctx, e.errorProducers, requiredField.Error(p.name, p.generator.Source()))
+			return service.WriteError(ctx, e.errorProducers, requiredField.Error(p.name, p.generator.Source()))
 		}
 
 		if closer, ok := result.(io.Closer); ok {
diff --git a/service/router/executor.go b/service/executor/middleware.go
similarity index 73%
rename from service/router/executor.go
rename to service/executor/middleware.go
index 10dc140..780372d 100644
--- a/service/router/executor.go
+++ b/service/executor/middleware.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2017 Caicloud Authors
+Copyright 2020 Caicloud Authors
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -14,22 +14,32 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package router
+package executor
 
-import "context"
+import (
+	"context"
+
+	"github.com/caicloud/nirvana/definition"
+)
+
+// MiddlewareExecutor executes with a context.
+type MiddlewareExecutor interface {
+	// Execute executes with context.
+	Execute(context.Context) error
+}
 
 // middlewareExecutor is a combination of middlewares and executor.
 type middlewareExecutor struct {
 	// Middlewares contains all middlewares for the executor.
-	middlewares []Middleware
+	middlewares []definition.Middleware
 	// Index is used to record the count of executed middleware.
 	index int
 	// Executor executes after middlewares.
-	executor Executor
+	executor MiddlewareExecutor
 }
 
-// newMiddlewareExecutor creates a new executor with middlewares.
-func newMiddlewareExecutor(ms []Middleware, e Executor) Executor {
+// NewMiddlewareExecutor creates a new executor with middlewares.
+func NewMiddlewareExecutor(ms []definition.Middleware, e MiddlewareExecutor) MiddlewareExecutor {
 	return &middlewareExecutor{ms, 0, e}
 }
 
diff --git a/service/handler.go b/service/handler.go
index 59f17d9..ad26502 100644
--- a/service/handler.go
+++ b/service/handler.go
@@ -155,10 +155,11 @@ func (h *ErrorDestinationHandler) Handle(ctx context.Context, producers []Produc
 	if value == nil {
 		return true, nil
 	}
-	return false, writeError(ctx, producers, value)
+	return false, WriteError(ctx, producers, value)
 }
 
-func writeError(ctx context.Context, producers []Producer, err interface{}) error {
+// WriteError writes error data to context.
+func WriteError(ctx context.Context, producers []Producer, err interface{}) error {
 	httpCtx := HTTPContextFrom(ctx)
 	ats, e := AcceptTypes(httpCtx.Request())
 	if e != nil {
diff --git a/service/builder.go b/service/rest/builder.go
similarity index 67%
rename from service/builder.go
rename to service/rest/builder.go
index 4200962..783c939 100644
--- a/service/builder.go
+++ b/service/rest/builder.go
@@ -14,42 +14,20 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package service
+package rest
 
 import (
-	"context"
+	"fmt"
 	"net/http"
+	"reflect"
 	"strings"
 
 	"github.com/caicloud/nirvana/definition"
 	"github.com/caicloud/nirvana/log"
-	"github.com/caicloud/nirvana/service/router"
+	"github.com/caicloud/nirvana/service"
+	"github.com/caicloud/nirvana/service/rest/router"
 )
 
-// Builder builds service.
-type Builder interface {
-	// Logger returns logger of builder.
-	Logger() log.Logger
-	// SetLogger sets logger to server.
-	SetLogger(logger log.Logger)
-	// Modifier returns modifier of builder.
-	Modifier() DefinitionModifier
-	// SetModifier sets definition modifier.
-	SetModifier(m DefinitionModifier)
-	// Filters returns all request filters.
-	Filters() []Filter
-	// AddFilter add filters to filter requests.
-	AddFilter(filters ...Filter)
-	// AddDescriptor adds descriptors to router.
-	AddDescriptor(descriptors ...definition.Descriptor) error
-	// Middlewares returns all router middlewares.
-	Middlewares() map[string][]definition.Middleware
-	// Definitions returns all definitions. If a modifier exists, it will be executed.
-	Definitions() map[string][]definition.Definition
-	// Build builds a service to handle request.
-	Build() (Service, error)
-}
-
 type binding struct {
 	middlewares []definition.Middleware
 	definitions []definition.Definition
@@ -57,13 +35,13 @@ type binding struct {
 
 type builder struct {
 	bindings map[string]*binding
-	modifier DefinitionModifier
-	filters  []Filter
+	modifier service.DefinitionModifier
+	filters  []service.Filter
 	logger   log.Logger
 }
 
 // NewBuilder creates a service builder.
-func NewBuilder() Builder {
+func NewBuilder() service.Builder {
 	return &builder{
 		bindings: make(map[string]*binding),
 		logger:   &log.SilentLogger{},
@@ -71,14 +49,14 @@ func NewBuilder() Builder {
 }
 
 // Filters returns all request filters.
-func (b *builder) Filters() []Filter {
-	result := make([]Filter, len(b.filters))
+func (b *builder) Filters() []service.Filter {
+	result := make([]service.Filter, len(b.filters))
 	copy(result, b.filters)
 	return result
 }
 
 // AddFilter add filters to filter requests.
-func (b *builder) AddFilter(filters ...Filter) {
+func (b *builder) AddFilter(filters ...service.Filter) {
 	b.filters = append(b.filters, filters...)
 }
 
@@ -97,18 +75,22 @@ func (b *builder) SetLogger(logger log.Logger) {
 }
 
 // Modifier returns modifier of builder.
-func (b *builder) Modifier() DefinitionModifier {
+func (b *builder) Modifier() service.DefinitionModifier {
 	return b.modifier
 }
 
 // SetModifier sets definition modifier.
-func (b *builder) SetModifier(m DefinitionModifier) {
+func (b *builder) SetModifier(m service.DefinitionModifier) {
 	b.modifier = m
 }
 
 // AddDescriptor adds descriptors to router.
-func (b *builder) AddDescriptor(descriptors ...definition.Descriptor) error {
-	for _, descriptor := range descriptors {
+func (b *builder) AddDescriptor(descriptors ...interface{}) error {
+	for _, obj := range descriptors {
+		descriptor, ok := obj.(definition.Descriptor)
+		if !ok {
+			return fmt.Errorf("%s is not a definition.Descriptor", reflect.TypeOf(obj).String())
+		}
 		b.addDescriptor("", nil, nil, nil, descriptor)
 	}
 	return nil
@@ -196,19 +178,6 @@ func (b *builder) copyDefinition(d *definition.Definition, consumes []string, pr
 	return newOne
 }
 
-// Middlewares returns all router middlewares.
-func (b *builder) Middlewares() map[string][]definition.Middleware {
-	result := make(map[string][]definition.Middleware)
-	for path, bd := range b.bindings {
-		if len(bd.middlewares) > 0 {
-			middlewares := make([]definition.Middleware, len(bd.middlewares))
-			copy(middlewares, bd.middlewares)
-			result[path] = middlewares
-		}
-	}
-	return result
-}
-
 // Definitions returns all definitions. If a modifier exists, it will be executed.
 // All results are copied from original definitions. Modifications can not affect
 // original data.
@@ -231,7 +200,7 @@ func (b *builder) Definitions() map[string][]definition.Definition {
 }
 
 // Build builds a service to handle request.
-func (b *builder) Build() (Service, error) {
+func (b *builder) Build() (service.Service, error) {
 	if len(b.bindings) <= 0 {
 		return nil, noRouter.Error()
 	}
@@ -250,7 +219,7 @@ func (b *builder) Build() (Service, error) {
 			if len(path) > 1 && strings.HasSuffix(path, "/") {
 				b.logger.Warningf("If RedirectTrailingSlash filter is enabled, following %d definition(s) would not be executed", len(bd.definitions))
 			}
-			inspector := newInspector(path, b.logger)
+			inspector := newInspector(path)
 			for _, d := range bd.definitions {
 				b.logger.V(log.LevelDebug).Infof("  Method: %s Consumes: %v Produces: %v",
 					d.Method, d.Consumes, d.Produces)
@@ -265,10 +234,7 @@ func (b *builder) Build() (Service, error) {
 			leaf.SetInspector(inspector)
 		}
 		for _, m := range bd.middlewares {
-			m := m
-			leaf.AddMiddleware(func(ctx context.Context, chain router.RoutingChain) error {
-				return m(ctx, chain)
-			})
+			leaf.AddMiddleware(m)
 		}
 		if root == nil {
 			root = top
@@ -276,65 +242,44 @@ func (b *builder) Build() (Service, error) {
 			return nil, err
 		}
 	}
-	s := &service{
+	s := &server{
 		root:      root,
 		filters:   b.filters,
 		logger:    b.logger,
-		producers: AllProducers(),
+		producers: service.AllProducers(),
 	}
 	return s, nil
 }
 
-// Service handles HTTP requests.
-//
-// Workflow:
-//            Service.ServeHTTP()
-//          ----------------------
-//          ↓                    ↑
-// |-----Filters------|          ↑
-//          ↓                    ↑
-// |---Router Match---|          ↑
-//          ↓                    ↑
-// |-------------Middlewares------------|
-//          ↓                    ↑
-// |-------------Executor---------------|
-//          ↓                    ↑
-// |-ParameterGenerators-|-DestinationHandlers-|
-//          ↓                    ↑
-// |------------User Function-----------|
-type Service interface {
-	http.Handler
-}
-
-type service struct {
+type server struct {
 	root      router.Router
-	filters   []Filter
+	filters   []service.Filter
 	logger    log.Logger
-	producers []Producer
+	producers []service.Producer
 }
 
-func (s *service) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+func (s *server) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
 	for _, f := range s.filters {
 		if !f(resp, req) {
 			return
 		}
 	}
-	ctx := newHTTPContext(resp, req)
+	ctx := service.NewHTTPContext(resp, req)
 
-	executor, err := s.root.Match(ctx, &ctx.container, req.URL.EscapedPath())
+	executor, err := s.root.Match(ctx, ctx.ValueContainer(), req.URL.EscapedPath())
 	if err != nil {
-		if err := writeError(ctx, s.producers, err); err != nil {
+		if err := service.WriteError(ctx, s.producers, err); err != nil {
 			s.logger.Error(err)
 		}
 		return
 	}
 	err = executor.Execute(ctx)
-	if err == nil && ctx.response.HeaderWritable() {
-		err = invalidService.Error()
+	if err == nil && ctx.ResponseWriter().HeaderWritable() {
+		err = service.InvalidService.Error()
 	}
 	if err != nil {
-		if ctx.response.HeaderWritable() {
-			if err := writeError(ctx, s.producers, err); err != nil {
+		if ctx.ResponseWriter().HeaderWritable() {
+			if err := service.WriteError(ctx, s.producers, err); err != nil {
 				s.logger.Error(err)
 			}
 		} else {
diff --git a/service/rest/errors.go b/service/rest/errors.go
new file mode 100644
index 0000000..6bce1f7
--- /dev/null
+++ b/service/rest/errors.go
@@ -0,0 +1,28 @@
+/*
+Copyright 2020 Caicloud Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package rest
+
+import (
+	"github.com/caicloud/nirvana/errors"
+)
+
+var (
+	noExecutorForMethod      = errors.MethodNotAllowed.Build("Nirvana:Service:NoExecutorForMethod", "method not allowed")
+	noExecutorForContentType = errors.UnsupportedMediaType.Build("Nirvana:Service:NoExecutorForContentType", "unsupported media type")
+	noExecutorToProduce      = errors.NotAcceptable.Build("Nirvana:Service:NoExecutorToProduce", "not acceptable")
+	noRouter                 = errors.InternalServerError.Build("Nirvana:Service:NoRouter", "no router to build service")
+)
diff --git a/service/rest/inspector.go b/service/rest/inspector.go
new file mode 100644
index 0000000..9d9bfeb
--- /dev/null
+++ b/service/rest/inspector.go
@@ -0,0 +1,142 @@
+/*
+Copyright 2017 Caicloud Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package rest
+
+import (
+	"context"
+
+	"github.com/caicloud/nirvana/definition"
+	"github.com/caicloud/nirvana/service"
+	"github.com/caicloud/nirvana/service/executor"
+)
+
+type inspector struct {
+	path      string
+	executors map[string][]executor.Executor
+}
+
+func newInspector(path string) *inspector {
+	return &inspector{
+		path:      path,
+		executors: make(map[string][]executor.Executor),
+	}
+}
+
+func (i *inspector) addDefinition(d definition.Definition) error {
+	var method string
+	if d.Method == definition.Any {
+		method = string(definition.Any)
+	} else {
+		method = service.HTTPMethodFor(d.Method)
+	}
+	if method == "" {
+		return executor.DefinitionNoMethod.Error(d.Method, i.path)
+	}
+	c, err := executor.DefinitionToExecutor(i.path, d, 0)
+	if err != nil {
+		return err
+	}
+	if err := i.conflictCheck(c, method); err != nil {
+		return err
+	}
+	i.executors[method] = append(i.executors[method], c)
+	return nil
+}
+
+func (i *inspector) conflictCheck(c executor.Executor, method string) error {
+	cs := i.executors[method]
+	if len(cs) <= 0 {
+		return nil
+	}
+	ctMap := map[string]bool{}
+	for _, extant := range cs {
+		result := extant.ContentTypeMap()
+		for k, vs := range result {
+			for _, v := range vs {
+				ctMap[k+":"+v] = true
+			}
+		}
+	}
+	cMap := c.ContentTypeMap()
+	for k, vs := range cMap {
+		for _, v := range vs {
+			if ctMap[k+":"+v] {
+				return executor.DefinitionConflict.Error(k, v, method, i.path)
+			}
+		}
+	}
+	return nil
+}
+
+// Inspect finds a valid executor to execute target context.
+func (i *inspector) Inspect(ctx context.Context) (executor.MiddlewareExecutor, error) {
+	httpCtx := service.HTTPContextFrom(ctx)
+	req := httpCtx.Request()
+	if req == nil {
+		return nil, service.NoContext.Error()
+	}
+	executors := make([]executor.Executor, 0)
+	if cs, ok := i.executors[req.Method]; ok && len(cs) > 0 {
+		executors = append(executors, cs...)
+	}
+	if cs, ok := i.executors[string(definition.Any)]; ok && len(cs) > 0 {
+		executors = append(executors, cs...)
+	}
+	if len(executors) <= 0 {
+		return nil, noExecutorForMethod.Error()
+	}
+	ct, err := service.ContentType(req)
+	if err != nil {
+		return nil, err
+	}
+	accepted := 0
+	for i, c := range executors {
+		if c.Acceptable(ct) {
+			if accepted != i {
+				executors[accepted] = c
+			}
+			accepted++
+		}
+	}
+	if accepted <= 0 {
+		return nil, noExecutorForContentType.Error()
+	}
+	ats, err := service.AcceptTypes(req)
+	if err != nil {
+		return nil, err
+	}
+	executors = executors[:accepted]
+	var target executor.Executor
+	for _, c := range executors {
+		if c.Producible(ats) {
+			target = c
+			break
+		}
+	}
+	if target == nil {
+		for _, at := range ats {
+			if at == definition.MIMEAll {
+				target = executors[0]
+			}
+		}
+	}
+	if target == nil {
+		return nil, noExecutorToProduce.Error()
+	}
+	httpCtx.SetRoutePath(i.path)
+	return target, nil
+}
diff --git a/service/router/basic.go b/service/rest/router/basic.go
similarity index 78%
rename from service/router/basic.go
rename to service/rest/router/basic.go
index 2341191..ceb1d5c 100644
--- a/service/router/basic.go
+++ b/service/rest/router/basic.go
@@ -20,24 +20,27 @@ import (
 	"context"
 	"reflect"
 	"sort"
+
+	"github.com/caicloud/nirvana/definition"
+	"github.com/caicloud/nirvana/service/executor"
 )
 
 // handler contains middlewares and executor.
 type handler struct {
-	middlewares []Middleware
+	middlewares []definition.Middleware
 	inspector   Inspector
 }
 
 // AddMiddleware adds middleware to the router node.
 // If the router matches a path, all middlewares in the router
 // will be executed by the returned executor.
-func (h *handler) AddMiddleware(ms ...Middleware) {
+func (h *handler) AddMiddleware(ms ...definition.Middleware) {
 	h.middlewares = append(h.middlewares, ms...)
 }
 
 // Middlewares returns all middlewares of the router.
 // Don't modify the returned values.
-func (h *handler) Middlewares() []Middleware {
+func (h *handler) Middlewares() []definition.Middleware {
 	return h.middlewares
 }
 
@@ -57,7 +60,7 @@ func (h *handler) Merge(o *handler) error {
 	h.AddMiddleware(o.middlewares...)
 	if h.inspector != nil {
 		if o.inspector != nil {
-			return ConflictInspectors.Error()
+			return conflictInspectors.Error()
 		}
 	} else {
 		h.inspector = o.inspector
@@ -66,20 +69,20 @@ func (h *handler) Merge(o *handler) error {
 }
 
 // pack packs middlewares with the executor.
-func (h *handler) pack(e Executor) (Executor, error) {
+func (h *handler) pack(e executor.MiddlewareExecutor) (executor.MiddlewareExecutor, error) {
 	if e == nil {
-		return nil, NoExecutor.Error()
+		return nil, noExecutor.Error()
 	}
 	if len(h.middlewares) <= 0 {
 		return e, nil
 	}
-	return newMiddlewareExecutor(h.middlewares, e), nil
+	return executor.NewMiddlewareExecutor(h.middlewares, e), nil
 }
 
 // unionExecutor packs middlewares and own executor.
-func (h *handler) unionExecutor(ctx context.Context) (Executor, error) {
+func (h *handler) unionExecutor(ctx context.Context) (executor.MiddlewareExecutor, error) {
 	if h.inspector == nil {
-		return nil, NoInspector.Error()
+		return nil, noInspector.Error()
 	}
 	e, err := h.inspector.Inspect(ctx)
 	if err != nil {
@@ -132,31 +135,31 @@ func (p *children) findStringRouter(char byte) Router {
 // The container can save key-value pair from the path.
 // If the router is the leaf node to match the path, it will return
 // the first executor which Inspect() returns true.
-func (p *children) Match(ctx context.Context, c Container, path string) (Executor, error) {
+func (p *children) Match(ctx context.Context, c Container, path string) (executor.MiddlewareExecutor, error) {
 	if len(path) <= 0 {
-		return nil, RouterNotFound.Error()
+		return nil, routerNotFound.Error()
 	}
 
 	// Two routers may match same path:
 	//   /path/{id} without inspector
 	//   /path/{name} with inspector
 	// When match `/path/some`, the first router won't match it and
-	// returns NoInspector. The the second router can match the path.
+	// returns noInspector. The the second router can match the path.
 	// If the second router can't generate an executor, an error is
 	// returned by inspector. In this case, resultError should be the
 	// assigned with the error from second router.
 	// If there are multiple routers match a path, the error is from
 	// the last matched router.
-	resultError := RouterNotFound.Error()
+	resultError := routerNotFound.Error()
 
 	// Match string routers
 	if len(p.stringRouters) > 0 {
 		if router := p.findStringRouter(path[0]); router != nil {
-			if executor, err := router.Match(ctx, c, path); err == nil {
-				return executor, nil
-			} else if !RouterNotFound.Derived(err) &&
-				!NoInspector.Derived(err) &&
-				!NoExecutor.Derived(err) {
+			if e, err := router.Match(ctx, c, path); err == nil {
+				return e, nil
+			} else if !routerNotFound.Derived(err) &&
+				!noInspector.Derived(err) &&
+				!noExecutor.Derived(err) {
 				resultError = err
 			}
 		}
@@ -164,22 +167,22 @@ func (p *children) Match(ctx context.Context, c Container, path string) (Executo
 
 	// Match regexp routers
 	for _, regexp := range p.regexpRouters {
-		if executor, err := regexp.Match(ctx, c, path); err == nil {
-			return executor, nil
-		} else if !RouterNotFound.Derived(err) &&
-			!NoInspector.Derived(err) &&
-			!NoExecutor.Derived(err) {
+		if e, err := regexp.Match(ctx, c, path); err == nil {
+			return e, nil
+		} else if !routerNotFound.Derived(err) &&
+			!noInspector.Derived(err) &&
+			!noExecutor.Derived(err) {
 			resultError = err
 		}
 	}
 
 	// Match path router
 	if p.pathRouter != nil {
-		if executor, err := p.pathRouter.Match(ctx, c, path); err == nil {
-			return executor, nil
-		} else if !RouterNotFound.Derived(err) &&
-			!NoInspector.Derived(err) &&
-			!NoExecutor.Derived(err) {
+		if e, err := p.pathRouter.Match(ctx, c, path); err == nil {
+			return e, nil
+		} else if !routerNotFound.Derived(err) &&
+			!noInspector.Derived(err) &&
+			!noExecutor.Derived(err) {
 			resultError = err
 		}
 	}
@@ -192,11 +195,11 @@ func (p *children) addRouter(router Router) error {
 	case String:
 		target := router.Target()
 		if len(target) <= 0 {
-			return EmptyRouterTarget.Error(router.Kind())
+			return emptyRouterTarget.Error(router.Kind())
 		}
 		r, ok := router.(*stringNode)
 		if !ok {
-			return UnknownRouterType.Error(router.Kind(), reflect.TypeOf(router).String())
+			return unknownRouterType.Error(router.Kind(), reflect.TypeOf(router).String())
 		}
 		c := target[0]
 		sr := p.findStringRouter(c)
@@ -243,7 +246,7 @@ func (p *children) addRouter(router Router) error {
 			p.pathRouter = router
 		}
 	default:
-		return UnknownRouterType.Error(router.Kind(), reflect.TypeOf(router).String())
+		return unknownRouterType.Error(router.Kind(), reflect.TypeOf(router).String())
 	}
 	return nil
 }
diff --git a/service/rest/router/errors.go b/service/rest/router/errors.go
new file mode 100644
index 0000000..4648c11
--- /dev/null
+++ b/service/rest/router/errors.go
@@ -0,0 +1,59 @@
+/*
+Copyright 2017 Caicloud Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package router
+
+import "github.com/caicloud/nirvana/errors"
+
+// Router `Match` method only can return errors from these error factory:
+// routerNotFound, noInspector, noExecutor.
+
+var (
+	// routerNotFound means there no router matches path.
+	routerNotFound = errors.NotFound.Build("Nirvana:Router:routerNotFound", "can't find router")
+	// noInspector means there is no inspector in router.
+	noInspector = errors.NotFound.Build("Nirvana:Router:noInspector", "no inspector to generate executor")
+	// noExecutor means can't pack middlewares for nil executor.
+	noExecutor = errors.NotFound.Build("Nirvana:Router:noExecutor", "no executor to pack middlewares")
+	// conflictInspectors can build errors for failure of merging routers.
+	// If attempts to merge two router and they all have inspector, an error
+	// should be returned. A merged router can't have two inspectors.
+	conflictInspectors = errors.Conflict.Build("Nirvana:Router:conflictInspectors", "can't merge two routers that all have inspector")
+	// emptyRouterTarget means a router node has an invalid empty target.
+	emptyRouterTarget = errors.UnprocessableEntity.Build("Nirvana:Router:emptyRouterTarget", "router ${kind} has no target")
+	// unknownRouterType means a node type is unprocessable.
+	unknownRouterType = errors.UnprocessableEntity.Build("Nirvana:Router:unknownRouterType", "router ${kind} has unknown type ${type}")
+	// unmatchedRouterKey means a router's key is not matched with another.
+	unmatchedRouterKey = errors.UnprocessableEntity.Build("Nirvana:Router:unmatchedRouterKey", "router key ${keyA} is not matched with ${keyB}")
+	// unmatchedRouterRegexp means a router's regexp is not matched with another.
+	unmatchedRouterRegexp = errors.UnprocessableEntity.Build("Nirvana:Router:unmatchedRouterRegexp", "router regexp ${regexpA} is not matched with ${regexpA}")
+	// noCommonPrefix means two routers have no common prefix.
+	noCommonPrefix = errors.UnprocessableEntity.Build("Nirvana:Router:noCommonPrefix", "there is no common prefix for the two routers")
+	// invalidPath means router path is invalid.
+	invalidPath = errors.UnprocessableEntity.Build("Nirvana:Router:invalidPath", "invalid path")
+	// invalidParentRouter means router node has no method to add child routers.
+	invalidParentRouter = errors.UnprocessableEntity.Build("Nirvana:Router:invalidParentRouter", "router ${type} has no method to add children")
+	// unmatchedSegmentKeys means segment has unmatched keys.
+	unmatchedSegmentKeys = errors.UnprocessableEntity.Build("Nirvana:Router:unmatchedSegmentKeys", "segment ${value} has unmatched keys")
+	// unknownSegment means can't recognize segment.
+	unknownSegment = errors.UnprocessableEntity.Build("Nirvana:Router:unknownSegment", "unknown segment ${value}")
+	// unmatchedPathBrace means path has unmatched brace.
+	unmatchedPathBrace = errors.UnprocessableEntity.Build("Nirvana:Router:unmatchedPathBrace", "unmatched braces")
+	// invalidPathKey means path key must be the last element.
+	invalidPathKey = errors.UnprocessableEntity.Build("Nirvana:Router:invalidPathKey", "key ${key} should be last element in the path")
+	// invalidRegexp means regexp is not notmative.
+	invalidRegexp = errors.UnprocessableEntity.Build("Nirvana:Router:invalidRegexp", "regexp ${regexp} does not have normative format")
+)
diff --git a/service/router/path.go b/service/rest/router/path.go
similarity index 88%
rename from service/router/path.go
rename to service/rest/router/path.go
index d8911d4..5c07f0a 100644
--- a/service/router/path.go
+++ b/service/rest/router/path.go
@@ -19,6 +19,8 @@ package router
 import (
 	"context"
 	"reflect"
+
+	"github.com/caicloud/nirvana/service/executor"
 )
 
 // pathNode matches all rest path.
@@ -43,7 +45,7 @@ func (n *pathNode) Kind() RouteKind {
 // The container can save key-value pair from the path.
 // If the router is the leaf node to match the path, it will return
 // the first executor which Inspect() returns true.
-func (n *pathNode) Match(ctx context.Context, c Container, path string) (Executor, error) {
+func (n *pathNode) Match(ctx context.Context, c Container, path string) (executor.MiddlewareExecutor, error) {
 	c.Set(n.key, path)
 	return n.handler.unionExecutor(ctx)
 }
@@ -53,10 +55,10 @@ func (n *pathNode) Match(ctx context.Context, c Container, path string) (Executo
 func (n *pathNode) Merge(r Router) (Router, error) {
 	node, ok := r.(*pathNode)
 	if !ok {
-		return nil, UnknownRouterType.Error(r.Kind(), reflect.TypeOf(r).String())
+		return nil, unknownRouterType.Error(r.Kind(), reflect.TypeOf(r).String())
 	}
 	if n.key != node.key {
-		return nil, UnmatchedRouterKey.Error(n.key, node.key)
+		return nil, unmatchedRouterKey.Error(n.key, node.key)
 	}
 	if err := n.handler.Merge(&node.handler); err != nil {
 		return nil, err
diff --git a/service/router/regexp.go b/service/rest/router/regexp.go
similarity index 83%
rename from service/router/regexp.go
rename to service/rest/router/regexp.go
index ef0eab2..0a5d39f 100644
--- a/service/router/regexp.go
+++ b/service/rest/router/regexp.go
@@ -21,6 +21,8 @@ import (
 	"reflect"
 	"regexp"
 	"strings"
+
+	"github.com/caicloud/nirvana/service/executor"
 )
 
 // index contains the key and it's index of the submatches.
@@ -58,7 +60,7 @@ func (n *regexpNode) Kind() RouteKind {
 // The container can save key-value pair from the path.
 // If the router is the leaf node to match the path, it will return
 // the first executor which Inspect() returns true.
-func (n *regexpNode) Match(ctx context.Context, c Container, path string) (Executor, error) {
+func (n *regexpNode) Match(ctx context.Context, c Container, path string) (executor.MiddlewareExecutor, error) {
 	// Match self
 	index := strings.IndexByte(path, '/')
 	if index < 0 {
@@ -67,18 +69,18 @@ func (n *regexpNode) Match(ctx context.Context, c Container, path string) (Execu
 	segment := path[:index]
 	result := n.regexp.FindStringSubmatch(segment)
 	if result == nil {
-		return nil, RouterNotFound.Error()
+		return nil, routerNotFound.Error()
 	}
 	// Match progeny
-	var executor Executor
+	var e executor.MiddlewareExecutor
 	var err error
 	if index < len(path) {
-		executor, err = n.children.Match(ctx, c, path[index:])
+		e, err = n.children.Match(ctx, c, path[index:])
 		if err == nil {
-			executor, err = n.handler.pack(executor)
+			e, err = n.handler.pack(e)
 		}
 	} else {
-		executor, err = n.unionExecutor(ctx)
+		e, err = n.unionExecutor(ctx)
 	}
 
 	if err != nil {
@@ -90,7 +92,7 @@ func (n *regexpNode) Match(ctx context.Context, c Container, path string) (Execu
 	for _, i := range n.indices {
 		c.Set(i.Key, result[i.Pos])
 	}
-	return executor, nil
+	return e, nil
 }
 
 // Merge merges r to the current router. The type of r should be same
@@ -98,10 +100,10 @@ func (n *regexpNode) Match(ctx context.Context, c Container, path string) (Execu
 func (n *regexpNode) Merge(r Router) (Router, error) {
 	node, ok := r.(*regexpNode)
 	if !ok {
-		return nil, UnknownRouterType.Error(r.Kind(), reflect.TypeOf(r).String())
+		return nil, unknownRouterType.Error(r.Kind(), reflect.TypeOf(r).String())
 	}
 	if n.exp != node.exp {
-		return nil, UnmatchedRouterRegexp.Error(n.exp, node.exp)
+		return nil, unmatchedRouterRegexp.Error(n.exp, node.exp)
 	}
 	if err := n.handler.Merge(&node.handler); err != nil {
 		return nil, err
@@ -135,25 +137,25 @@ func (n *fullMatchRegexpNode) Kind() RouteKind {
 // The container can save key-value pair from the path.
 // If the router is the leaf node to match the path, it will return
 // the first executor which Inspect() returns true.
-func (n *fullMatchRegexpNode) Match(ctx context.Context, c Container, path string) (Executor, error) {
+func (n *fullMatchRegexpNode) Match(ctx context.Context, c Container, path string) (executor.MiddlewareExecutor, error) {
 	index := strings.IndexByte(path, '/')
-	var executor Executor
+	var e executor.MiddlewareExecutor
 	var err error
 	if index > 0 {
-		executor, err = n.children.Match(ctx, c, path[index:])
+		e, err = n.children.Match(ctx, c, path[index:])
 		if err == nil {
-			executor, err = n.handler.pack(executor)
+			e, err = n.handler.pack(e)
 		}
 	} else {
 		index = len(path)
-		executor, err = n.unionExecutor(ctx)
+		e, err = n.unionExecutor(ctx)
 	}
 	if err != nil {
 		// Unmatched
 		return nil, err
 	}
 	c.Set(n.key, path[:index])
-	return executor, nil
+	return e, nil
 }
 
 // Merge merges r to the current router. The type of r should be same
@@ -161,10 +163,10 @@ func (n *fullMatchRegexpNode) Match(ctx context.Context, c Container, path strin
 func (n *fullMatchRegexpNode) Merge(r Router) (Router, error) {
 	node, ok := r.(*fullMatchRegexpNode)
 	if !ok {
-		return nil, UnknownRouterType.Error(r.Kind(), reflect.TypeOf(r).String())
+		return nil, unknownRouterType.Error(r.Kind(), reflect.TypeOf(r).String())
 	}
 	if n.key != node.key {
-		return nil, UnmatchedRouterKey.Error(n.key, node.key)
+		return nil, unmatchedRouterKey.Error(n.key, node.key)
 	}
 	if err := n.handler.Merge(&node.handler); err != nil {
 		return nil, err
diff --git a/service/router/router.go b/service/rest/router/router.go
similarity index 88%
rename from service/router/router.go
rename to service/rest/router/router.go
index 9bbec73..a393e8a 100644
--- a/service/router/router.go
+++ b/service/rest/router/router.go
@@ -22,29 +22,16 @@ import (
 	"reflect"
 	"regexp"
 	"strings"
-)
-
-// RoutingChain contains the call chain of middlewares and executor.
-type RoutingChain interface {
-	// Continue continues to execute the next middleware or executor.
-	Continue(context.Context) error
-}
 
-// Middleware describes the form of middlewares. If you want to
-// carry on, call RoutingChain.Continue() and pass the context.
-type Middleware func(context.Context, RoutingChain) error
+	"github.com/caicloud/nirvana/definition"
+	"github.com/caicloud/nirvana/service/executor"
+)
 
 // Inspector can select an executor to execute.
 type Inspector interface {
 	// Inspect finds a valid executor to execute target context.
 	// It returns an error if it can't find a valid executor.
-	Inspect(context.Context) (Executor, error)
-}
-
-// Executor executs with a context.
-type Executor interface {
-	// Execute executes with context.
-	Execute(context.Context) error
+	Inspect(context.Context) (executor.MiddlewareExecutor, error)
 }
 
 // RouteKind is kind of routers.
@@ -80,14 +67,14 @@ type Router interface {
 	// The container can save key-value pair from the path.
 	// If the router is the leaf node to match the path, it will return
 	// the first executor which Inspect() returns true.
-	Match(ctx context.Context, c Container, path string) (Executor, error)
+	Match(ctx context.Context, c Container, path string) (executor.MiddlewareExecutor, error)
 	// AddMiddleware adds middleware to the router node.
 	// If the router matches a path, all middlewares in the router
 	// will be executed by the returned executor.
-	AddMiddleware(ms ...Middleware)
+	AddMiddleware(ms ...definition.Middleware)
 	// Middlewares returns all middlewares of the router.
 	// Don't modify the returned values.
-	Middlewares() []Middleware
+	Middlewares() []definition.Middleware
 	// SetInspector sets inspector to the router node.
 	SetInspector(inspector Inspector)
 	// Inspector gets inspector from the router node.
@@ -124,7 +111,7 @@ func Parse(path string) (Router, Router, error) {
 		return nil, nil, err
 	}
 	if len(paths) <= 0 {
-		return nil, nil, InvalidPath.Error()
+		return nil, nil, invalidPath.Error()
 	}
 	segments, err := reorganize(paths)
 	if err != nil {
@@ -152,7 +139,7 @@ func Parse(path string) (Router, Router, error) {
 					return nil, nil, err
 				}
 			} else {
-				return nil, nil, InvalidParentRouter.Error(reflect.TypeOf(parent).String())
+				return nil, nil, invalidParentRouter.Error(reflect.TypeOf(parent).String())
 			}
 		}
 		parent = router
@@ -179,7 +166,7 @@ func segmentToRouter(seg *segment) (Router, error) {
 		}
 		r, err := regexp.Compile("^" + seg.match + "$")
 		if err != nil {
-			return nil, InvalidRegexp.Error(seg.match)
+			return nil, invalidRegexp.Error(seg.match)
 		}
 		node.regexp = r
 		names := r.SubexpNames()
@@ -191,7 +178,7 @@ func segmentToRouter(seg *segment) (Router, error) {
 			}
 		}
 		if j != len(seg.keys) {
-			return nil, UnmatchedSegmentKeys.Error(seg)
+			return nil, unmatchedSegmentKeys.Error(seg)
 		}
 		return node, nil
 
@@ -200,7 +187,7 @@ func segmentToRouter(seg *segment) (Router, error) {
 			key: seg.keys[0],
 		}, nil
 	}
-	return nil, UnknownSegment.Error(seg)
+	return nil, unknownSegment.Error(seg)
 }
 
 // Split splits string segments and regexp segments.
@@ -232,7 +219,7 @@ func Split(path string) ([]string, error) {
 		}
 	}
 	if braceCounter > 0 {
-		return nil, UnmatchedPathBrace.Error()
+		return nil, unmatchedPathBrace.Error()
 	}
 	if lastElementPos < len(path) {
 		result = append(result, path[lastElementPos:])
@@ -285,7 +272,7 @@ func reorganize(paths []string) ([]*segment, error) {
 			}
 			if seg.Tail() {
 				if i != len(paths)-1 {
-					return nil, InvalidPathKey.Error(seg.key)
+					return nil, invalidPathKey.Error(seg.key)
 				}
 				if target != nil {
 					segments = append(segments, target)
@@ -318,7 +305,7 @@ type expSegment struct {
 // parseExpSegment parses a regexp segment to ExpSegment.
 func parseExpSegment(exp string) (*expSegment, error) {
 	if !strings.HasPrefix(exp, "{") || !strings.HasSuffix(exp, "}") {
-		return nil, InvalidRegexp.Error(exp)
+		return nil, invalidRegexp.Error(exp)
 	}
 	exp = exp[1 : len(exp)-1]
 	pos := strings.Index(exp, ":")
diff --git a/service/router/string.go b/service/rest/router/string.go
similarity index 83%
rename from service/router/string.go
rename to service/rest/router/string.go
index 9354d5e..a7aa988 100644
--- a/service/router/string.go
+++ b/service/rest/router/string.go
@@ -20,6 +20,8 @@ import (
 	"context"
 	"reflect"
 	"strings"
+
+	"github.com/caicloud/nirvana/service/executor"
 )
 
 // stringNode describes a string router node.
@@ -45,18 +47,18 @@ func (n *stringNode) Kind() RouteKind {
 // The container can save key-value pair from the path.
 // If the router is the leaf node to match the path, it will return
 // the first executor which Inspect() returns true.
-func (n *stringNode) Match(ctx context.Context, c Container, path string) (Executor, error) {
+func (n *stringNode) Match(ctx context.Context, c Container, path string) (executor.MiddlewareExecutor, error) {
 	if n.prefix != "" && !strings.HasPrefix(path, n.prefix) {
 		// No match
-		return nil, RouterNotFound.Error()
+		return nil, routerNotFound.Error()
 	}
 	if len(n.prefix) < len(path) {
 		// Match prefix
-		executor, err := n.children.Match(ctx, c, path[len(n.prefix):])
+		e, err := n.children.Match(ctx, c, path[len(n.prefix):])
 		if err != nil {
 			return nil, err
 		}
-		return n.handler.pack(executor)
+		return n.handler.pack(e)
 	}
 	// Match self
 	return n.handler.unionExecutor(ctx)
@@ -67,7 +69,7 @@ func (n *stringNode) Match(ctx context.Context, c Container, path string) (Execu
 func (n *stringNode) Merge(r Router) (Router, error) {
 	node, ok := r.(*stringNode)
 	if !ok {
-		return nil, UnknownRouterType.Error(r.Kind(), reflect.TypeOf(r).String())
+		return nil, unknownRouterType.Error(r.Kind(), reflect.TypeOf(r).String())
 	}
 	commonPrefix := 0
 	for commonPrefix < len(n.prefix) && commonPrefix < len(node.prefix) {
@@ -77,7 +79,7 @@ func (n *stringNode) Merge(r Router) (Router, error) {
 		commonPrefix++
 	}
 	if commonPrefix <= 0 {
-		return nil, NoCommonPrefix.Error()
+		return nil, noCommonPrefix.Error()
 	}
 	switch {
 	case commonPrefix == len(n.prefix) && commonPrefix == len(node.prefix):
@@ -93,20 +95,20 @@ func (n *stringNode) Merge(r Router) (Router, error) {
 			return nil, err
 		}
 	case commonPrefix == len(node.prefix):
-		copy := *n
-		copy.prefix = copy.prefix[commonPrefix:]
+		copyN := *n
+		copyN.prefix = copyN.prefix[commonPrefix:]
 		*n = *node
-		if err := n.addRouter(&copy); err != nil {
+		if err := n.addRouter(&copyN); err != nil {
 			return nil, err
 		}
 	default:
-		copy := *n
-		copy.prefix = copy.prefix[commonPrefix:]
+		copyN := *n
+		copyN.prefix = copyN.prefix[commonPrefix:]
 		node.prefix = node.prefix[commonPrefix:]
 		n.handler = handler{}
 		n.children = children{}
 		n.prefix = n.prefix[:commonPrefix]
-		if err := n.addRouter(&copy); err != nil {
+		if err := n.addRouter(&copyN); err != nil {
 			return nil, err
 		}
 		if err := n.addRouter(node); err != nil {
diff --git a/service/router/utils.go b/service/router/utils.go
deleted file mode 100644
index 41f9596..0000000
--- a/service/router/utils.go
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
-Copyright 2017 Caicloud Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package router
-
-import "github.com/caicloud/nirvana/errors"
-
-// Router `Match` method only can return errors from these error factory:
-// RouterNotFound, NoInspector, NoExecutor.
-
-// RouterNotFound means there no router matches path.
-var RouterNotFound = errors.NotFound.Build("Nirvana:Router:RouterNotFound",
-	"can't find router")
-
-// NoInspector means there is no inspector in router.
-var NoInspector = errors.NotFound.Build("Nirvana:Router:NoInspector",
-	"no inspector to generate executor")
-
-// NoExecutor means can't pack middlewares for nil executor.
-var NoExecutor = errors.NotFound.Build("Nirvana:Router:NoExecutor",
-	"no executor to pack middlewares")
-
-// ConflictInspectors can build errors for failure of merging routers.
-// If attempts to merge two router and they all have inspector, an error
-// should be returned. A merged router can't have two inspectors.
-var ConflictInspectors = errors.Conflict.Build("Nirvana:Router:ConflictInspectors",
-	"can't merge two routers that all have inspector")
-
-// EmptyRouterTarget means a router node has an invalid empty target.
-var EmptyRouterTarget = errors.UnprocessableEntity.Build("Nirvana:Router:EmptyRouterTarget",
-	"router ${kind} has no target")
-
-// UnknownRouterType means a node type is unprocessable.
-var UnknownRouterType = errors.UnprocessableEntity.Build("Nirvana:Router:UnknownRouterType",
-	"router ${kind} has unknown type ${type}")
-
-// UnmatchedRouterKey means a router's key is not matched with another.
-var UnmatchedRouterKey = errors.UnprocessableEntity.Build("Nirvana:Router:UnmatchedRouterKey",
-	"router key ${keyA} is not matched with ${keyB}")
-
-// UnmatchedRouterRegexp means a router's regexp is not matched with another.
-var UnmatchedRouterRegexp = errors.UnprocessableEntity.Build("Nirvana:Router:UnmatchedRouterRegexp",
-	"router regexp ${regexpA} is not matched with ${regexpA}")
-
-// NoCommonPrefix means two routers have no common prefix.
-var NoCommonPrefix = errors.UnprocessableEntity.Build("Nirvana:Router:NoCommonPrefix",
-	"there is no common prefix for the two routers")
-
-// InvalidPath means router path is invalid.
-var InvalidPath = errors.UnprocessableEntity.Build("Nirvana:Router:InvalidPath",
-	"invalid path")
-
-// InvalidParentRouter means router node has no method to add child routers.
-var InvalidParentRouter = errors.UnprocessableEntity.Build("Nirvana:Router:InvalidParentRouter",
-	"router ${type} has no method to add children")
-
-// UnmatchedSegmentKeys means segment has unmatched keys.
-var UnmatchedSegmentKeys = errors.UnprocessableEntity.Build("Nirvana:Router:UnmatchedSegmentKeys",
-	"segment ${value} has unmatched keys")
-
-// UnknownSegment means can't recognize segment.
-var UnknownSegment = errors.UnprocessableEntity.Build("Nirvana:Router:UnknownSegment",
-	"unknown segment ${value}")
-
-// UnmatchedPathBrace means path has unmatched brace.
-var UnmatchedPathBrace = errors.UnprocessableEntity.Build("Nirvana:Router:UnmatchedPathBrace",
-	"unmatched braces")
-
-// InvalidPathKey means path key must be the last element.
-var InvalidPathKey = errors.UnprocessableEntity.Build("Nirvana:Router:InvalidPathKey",
-	"key ${key} should be last element in the path")
-
-// InvalidRegexp means regexp is not notmative.
-var InvalidRegexp = errors.UnprocessableEntity.Build("Nirvana:Router:InvalidRegexp",
-	"regexp ${regexp} does not have normative format")
diff --git a/service/rpc/builder.go b/service/rpc/builder.go
new file mode 100644
index 0000000..9df2d4a
--- /dev/null
+++ b/service/rpc/builder.go
@@ -0,0 +1,227 @@
+/*
+Copyright 2020 Caicloud Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package rpc
+
+import (
+	"fmt"
+	"net/http"
+	"reflect"
+
+	"github.com/caicloud/nirvana/definition"
+	"github.com/caicloud/nirvana/log"
+	"github.com/caicloud/nirvana/service"
+	"github.com/caicloud/nirvana/service/executor"
+)
+
+type binding struct {
+	middlewares []definition.Middleware
+	definition  definition.Definition
+	executor    executor.MiddlewareExecutor
+}
+
+type builder struct {
+	// bindings contains all RPC action definitions, the key is a unique id (path + version + name),
+	// it is currently formatted as an API URL path, eg: /?Version=2020-10-10&Action=Echo, which is useful for both
+	// printing logs and generating API documents/client
+	bindings map[string]*binding
+	modifier service.DefinitionModifier
+	filters  []service.Filter
+	logger   log.Logger
+}
+
+// NewBuilder creates a service builder.
+func NewBuilder() service.Builder {
+	return &builder{
+		bindings: make(map[string]*binding),
+		logger:   &log.SilentLogger{},
+	}
+}
+
+// Filters returns all request filters.
+func (b *builder) Filters() []service.Filter {
+	result := make([]service.Filter, len(b.filters))
+	copy(result, b.filters)
+	return result
+}
+
+// AddFilter add filters to filter requests.
+func (b *builder) AddFilter(filters ...service.Filter) {
+	b.filters = append(b.filters, filters...)
+}
+
+// Logger returns logger of builder.
+func (b *builder) Logger() log.Logger {
+	return b.logger
+}
+
+// SetLogger sets logger to builder.
+func (b *builder) SetLogger(logger log.Logger) {
+	if logger != nil {
+		b.logger = logger
+	} else {
+		b.logger = &log.SilentLogger{}
+	}
+}
+
+// Modifier returns modifier of builder.
+func (b *builder) Modifier() service.DefinitionModifier {
+	return b.modifier
+}
+
+// SetModifier sets definition modifier.
+func (b *builder) SetModifier(m service.DefinitionModifier) {
+	b.modifier = m
+}
+
+func genRPCPath(path, version, action string) string {
+	return fmt.Sprintf("%s?Version=%s&Action=%s", path, version, action)
+}
+
+// AddDescriptor adds descriptors to router.
+func (b *builder) AddDescriptor(descriptors ...interface{}) error {
+	for _, obj := range descriptors {
+		descriptor, ok := obj.(definition.RPCDescriptor)
+		if !ok {
+			return fmt.Errorf("%s is not a definition.RPCDescriptor", reflect.TypeOf(obj).String())
+		}
+		path := descriptor.Path
+		if path == "" {
+			path = "/"
+		}
+		for _, action := range descriptor.Actions {
+			b.bindings[genRPCPath(path, action.Version, action.Name)] = &binding{
+				middlewares: descriptor.Middlewares,
+				definition:  b.genDefinition(action, descriptor.Consumes, descriptor.Produces, descriptor.Tags),
+			}
+		}
+	}
+	return nil
+}
+
+func (b *builder) genDefinition(action definition.RPCAction, consumes []string, produces []string, tags []string) definition.Definition {
+	if len(action.Consumes) > 0 {
+		consumes = action.Consumes
+	}
+	if len(action.Produces) > 0 {
+		produces = action.Produces
+	}
+	if len(action.Tags) > 0 {
+		tags = action.Tags
+	}
+	errorProduces := produces
+	if len(action.ErrorProduces) > 0 {
+		errorProduces = action.ErrorProduces
+	}
+
+	return definition.Definition{
+		Method:        definition.Create,
+		Consumes:      consumes,
+		Produces:      produces,
+		Tags:          tags,
+		ErrorProduces: errorProduces,
+		Function:      action.Function,
+		Parameters:    action.Parameters,
+		Results:       action.Results,
+		Summary:       action.Summary,
+		Description:   action.Description,
+		Examples:      action.Examples,
+	}
+}
+
+// Definitions returns all definitions. If a modifier exists, it will be executed.
+// All results are copied from original definitions. Modifications can not affect
+// original data.
+func (b *builder) Definitions() map[string][]definition.Definition {
+	result := make(map[string][]definition.Definition)
+	for path, bd := range b.bindings {
+		d := bd.definition
+		if b.modifier != nil {
+			b.modifier(&d)
+		}
+		result[path] = []definition.Definition{d}
+	}
+	return result
+}
+
+// Build builds a service to handle request.
+func (b *builder) Build() (service.Service, error) {
+	if len(b.bindings) <= 0 {
+		return nil, fmt.Errorf("no router")
+	}
+
+	var err error
+	for path, bd := range b.bindings {
+		b.logger.V(log.LevelDebug).Infof("Path: %s, Consumes: %v, Produces: %v", path, bd.definition.Consumes, bd.definition.Produces)
+		if b.modifier != nil {
+			b.modifier(&bd.definition)
+		}
+		bd.executor, err = executor.DefinitionToExecutor(path, bd.definition, http.StatusOK)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	s := &server{
+		executors: b.bindings,
+		filters:   b.filters,
+		logger:    b.logger,
+		producers: service.AllProducers(),
+	}
+	return s, nil
+}
+
+type server struct {
+	executors map[string]*binding
+	filters   []service.Filter
+	logger    log.Logger
+	producers []service.Producer
+}
+
+func (s *server) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+	for _, f := range s.filters {
+		if !f(resp, req) {
+			return
+		}
+	}
+	ctx := service.NewHTTPContext(resp, req)
+
+	action := req.URL.Query().Get("Action")
+	version := req.URL.Query().Get("Version")
+	path := genRPCPath(req.URL.Path, version, action)
+	e, ok := s.executors[path]
+	if !ok {
+		if err := service.WriteError(ctx, s.producers, noExecutorForAction.Error(path)); err != nil {
+			s.logger.Error(err)
+		}
+		return
+	}
+
+	ctx.SetRoutePath(path)
+	err := executor.NewMiddlewareExecutor(e.middlewares, e.executor).Execute(ctx)
+	if err == nil && ctx.ResponseWriter().HeaderWritable() {
+		err = service.InvalidService.Error()
+	}
+	if err != nil {
+		if ctx.ResponseWriter().HeaderWritable() {
+			if err := service.WriteError(ctx, s.producers, err); err != nil {
+				s.logger.Error(err)
+			}
+		} else {
+			s.logger.Error(err)
+		}
+	}
+}
diff --git a/service/rpc/errors.go b/service/rpc/errors.go
new file mode 100644
index 0000000..62c6f45
--- /dev/null
+++ b/service/rpc/errors.go
@@ -0,0 +1,23 @@
+/*
+Copyright 2020 Caicloud Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package rpc
+
+import (
+	"github.com/caicloud/nirvana/errors"
+)
+
+var noExecutorForAction = errors.MethodNotAllowed.Build("Nirvana:Service:NoExecutorForAction", "no executor for action ${path}")
diff --git a/service/source.go b/service/source.go
index d6f2976..f4d9d50 100644
--- a/service/source.go
+++ b/service/source.go
@@ -409,7 +409,7 @@ func (g *AutoParameterGenerator) Validate(name string, defaultValue interface{},
 		}
 		generator := ParameterGeneratorFor(source)
 		if generator == nil {
-			return noParameterGenerator.Error(source)
+			return NoParameterGenerator.Error(source)
 		}
 
 		var value interface{}
@@ -459,7 +459,7 @@ func (g *AutoParameterGenerator) generate(ctx context.Context, vc ValueContainer
 		}
 		generator := ParameterGeneratorFor(source)
 		if generator == nil {
-			return noParameterGenerator.Error(source)
+			return NoParameterGenerator.Error(source)
 		}
 		ins, err := generator.Generate(ctx, vc, consumers, name, field.Type)
 		if err != nil {
diff --git a/service/types.go b/service/types.go
new file mode 100644
index 0000000..98b3238
--- /dev/null
+++ b/service/types.go
@@ -0,0 +1,67 @@
+/*
+Copyright 2017 Caicloud Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package service
+
+import (
+	"net/http"
+
+	"github.com/caicloud/nirvana/definition"
+	"github.com/caicloud/nirvana/log"
+)
+
+// Builder builds service.
+type Builder interface {
+	// Logger returns logger of builder.
+	Logger() log.Logger
+	// SetLogger sets logger to server.
+	SetLogger(logger log.Logger)
+	// Modifier returns modifier of builder.
+	Modifier() DefinitionModifier
+	// SetModifier sets definition modifier.
+	SetModifier(m DefinitionModifier)
+	// Filters returns all request filters.
+	Filters() []Filter
+	// AddFilter add filters to filter requests.
+	AddFilter(filters ...Filter)
+	// AddDescriptor adds descriptors to router.
+	AddDescriptor(descriptors ...interface{}) error
+	// Definitions returns all definitions. If a modifier exists, it will be executed.
+	Definitions() map[string][]definition.Definition
+	// Build builds a service to handle request.
+	Build() (Service, error)
+}
+
+// Service handles HTTP requests.
+//
+// Workflow:
+//            Service.ServeHTTP()
+//          ----------------------
+//          ↓                    ↑
+// |-----Filters------|          ↑
+//          ↓                    ↑
+// |-Router/RPC Action Match-|   ↑
+//          ↓                    ↑
+// |-------------Middlewares------------|
+//          ↓                    ↑
+// |-------------Executor---------------|
+//          ↓                    ↑
+// |-ParameterGenerators-|-DestinationHandlers-|
+//          ↓                    ↑
+// |------------User Function-----------|
+type Service interface {
+	http.Handler
+}
diff --git a/service/utils.go b/service/utils.go
index d40d69a..1d770ba 100644
--- a/service/utils.go
+++ b/service/utils.go
@@ -95,68 +95,33 @@ func MetaForContentType(ctype string) map[string]string {
 }
 
 // Internal error factories:
-var noExecutorForMethod = errors.MethodNotAllowed.Build("Nirvana:Service:NoExecutorForMethod", "method not allowed")
-var noExecutorForContentType = errors.UnsupportedMediaType.Build("Nirvana:Service:NoExecutorForContentType", "unsupported media type")
-var noExecutorToProduce = errors.NotAcceptable.Build("Nirvana:Service:NoExecutorToProduce", "not acceptable")
-var invalidContentType = errors.BadRequest.Build("Nirvana:Service:InvalidContentType", "invalid content type ${type}")
-var invalidConversion = errors.BadRequest.Build("Nirvana:Service:InvalidConversion", "can't convert ${data} to ${type}")
-
-var noRouter = errors.InternalServerError.Build("Nirvana:Service:NoRouter", "no router to build service")
-var invalidService = errors.InternalServerError.Build("Nirvana:Service:NoResponse", "no response")
-var invalidConsumer = errors.InternalServerError.Build("Nirvana:Service:InvalidConsumer", "${type} is invalid for consumer")
-var invalidProducer = errors.InternalServerError.Build("Nirvana:Service:InvalidProducer", "${type} is invalid for producer")
-var noConnectionHijacker = errors.InternalServerError.Build("Nirvana:Service:NoConnectionHijacker",
-	"underlying http.ResponseWriter does not implement http.Hijacker")
-var definitionNoMethod = errors.InternalServerError.Build("Nirvana:Service:DefinitionNoMethod", "no http method in [${method}]${path}")
-var definitionNoConsumes = errors.InternalServerError.Build("Nirvana:Service:DefinitionNoConsumes", "no content type to consume in [${method}]${path}")
-var definitionNoProduces = errors.InternalServerError.Build("Nirvana:Service:DefinitionNoProduces", "no content type to produce in [${method}]${path}")
-var definitionNoErrorProduces = errors.InternalServerError.Build("Nirvana:Service:DefinitionNoErrorProduces",
-	"no content type to produce error in [${method}]${path}")
-var definitionNoFunction = errors.InternalServerError.Build("Nirvana:Service:DefinitionNoFunction", "no function in [${method}]${path}")
-var definitionInvalidFunctionType = errors.InternalServerError.Build("Nirvana:Service:DefinitionInvalidFunctionType",
-	"${type} is not function in [${method}]${path}")
-
-var definitionNoConsumer = errors.InternalServerError.Build("Nirvana:Service:DefinitionNoConsumer",
-	"no consumer for content type ${type} in [${method}]${path}")
-
-var definitionNoProducer = errors.InternalServerError.Build("Nirvana:Service:DefinitionNoProducer",
-	"no producer for content type ${type} in [${method}]${path}")
-
-var definitionConflict = errors.InternalServerError.Build("Nirvana:Service:DefinitionConflict",
-	"consumer-producer pair ${key}:${value} conflicts in [http.${method}]${path}")
-
-var definitionUnmatchedParameters = errors.InternalServerError.Build("Nirvana:Service:DefinitionUnmatchedParameters",
-	"function ${function} has ${count} parameters but want ${desired} in ${path}, "+
-		"you can define it with descriptor->definition[]->parameters[]")
-
-var definitionUnmatchedResults = errors.InternalServerError.Build("Nirvana:Service:DefinitionUnmatchedResults",
-	"function ${function} has ${count} results but want ${desired} in ${path}, "+
-		"you can define it with descriptor->definition[]->results[]")
-
-var noDestinationHandler = errors.InternalServerError.Build("Nirvana:Service:NoDestinationHandler", "no destination handler for destination ${destination}, "+
-	"you can define it with descriptor->definition[]->results[]->destination")
-
-var noContext = errors.InternalServerError.Build("Nirvana:Service:NoContext", "can't find http context, "+
-	"you should define `ctx context.Context` as the first parameter of your handler function")
-
-var requiredField = errors.InternalServerError.Build("Nirvana:Service:RequiredField", "required field ${field} in ${source} but got empty")
-var invalidMetaType = errors.InternalServerError.Build("Nirvana:Service:InvalidMetaType", "can't recognize meta for type ${type}")
-var noProducerToWrite = errors.NotAcceptable.Build("Nirvana:Service:NoProducerToWrite", "can't find producer for accept types ${types}")
-var invalidMethod = errors.InternalServerError.Build("Nirvana:Service:InvalidMethod", "http method ${method} is invalid")
-var invalidStatusCode = errors.InternalServerError.Build("Nirvana:Service:InvalidStatusCode", "http status code must be in [100,599]")
-var unassignableType = errors.InternalServerError.Build("Nirvana:Service:UnassignableType", "type ${typeA} can't assign to ${typeB}")
-var noConverter = errors.InternalServerError.Build("Nirvana:Service:UnassignableType", "no converter for type ${type}")
-var invalidBodyType = errors.InternalServerError.Build("Nirvana:Service:InvalidBodyType", "${type} is not a valid type for body")
-var noPrefab = errors.InternalServerError.Build("Nirvana:Service:NoPrefab", "no prefab named ${name}")
-var invalidAutoParameter = errors.InternalServerError.Build("Nirvana:Service:InvalidAutoParameter", "${type} is not a struct or a pointer to struct")
-var noParameterGenerator = errors.InternalServerError.Build("Nirvana:Service:NoParameterGenerator", "no parameter generator for source ${source}")
-var invalidFieldTag = errors.InternalServerError.Build("Nirvana:Service:InvalidFieldTag", "filed tag ${tag} is invalid")
-var noName = errors.InternalServerError.Build("Nirvana:Service:NoName", "${source} must have a name")
-var invalidTypeForConsumer = errors.InternalServerError.Build("Nirvana:Service:InvalidTypeForConsumer",
-	"consumer ${content} can't consume data for type ${type}")
-var invalidTypeForProducer = errors.InternalServerError.Build("Nirvana:Service:InvalidTypeForProducer",
-	"producer ${content} can't produce data for type ${type}")
-var invalidOperatorInType = errors.InternalServerError.Build("Nirvana:Service:InvalidOperatorInType",
-	"the type ${type} is not compatible to the in type of the ${index} operator")
-var invalidOperatorOutType = errors.InternalServerError.Build("Nirvana:Service:InvalidOperatorOutType",
-	"the out type of the ${index} operator is not compatible to the type ${type}")
+
+var (
+	// InvalidService represents no response error.
+	InvalidService = errors.InternalServerError.Build("Nirvana:Service:NoResponse", "no response")
+	// NoContext means can't find http context.
+	NoContext = errors.InternalServerError.Build("Nirvana:Service:NoContext", "can't find http context, you should define `ctx context.Context` as the first parameter of your handler function")
+	// NoParameterGenerator represents no parameter generator error.
+	NoParameterGenerator = errors.InternalServerError.Build("Nirvana:Service:NoParameterGenerator", "no parameter generator for source ${source}")
+)
+
+var (
+	invalidContentType     = errors.BadRequest.Build("Nirvana:Service:InvalidContentType", "invalid content type ${type}")
+	invalidConversion      = errors.BadRequest.Build("Nirvana:Service:InvalidConversion", "can't convert ${data} to ${type}")
+	invalidConsumer        = errors.InternalServerError.Build("Nirvana:Service:invalidConsumer", "${type} is invalid for consumer")
+	invalidProducer        = errors.InternalServerError.Build("Nirvana:Service:invalidProducer", "${type} is invalid for producer")
+	noConnectionHijacker   = errors.InternalServerError.Build("Nirvana:Service:noConnectionHijacker", "underlying http.ResponseWriter does not implement http.Hijacker")
+	invalidMetaType        = errors.InternalServerError.Build("Nirvana:Service:invalidMetaType", "can't recognize meta for type ${type}")
+	noProducerToWrite      = errors.NotAcceptable.Build("Nirvana:Service:noProducerToWrite", "can't find producer for accept types ${types}")
+	invalidMethod          = errors.InternalServerError.Build("Nirvana:Service:invalidMethod", "http method ${method} is invalid")
+	invalidStatusCode      = errors.InternalServerError.Build("Nirvana:Service:invalidStatusCode", "http status code must be in [100,599]")
+	invalidBodyType        = errors.InternalServerError.Build("Nirvana:Service:invalidBodyType", "${type} is not a valid type for body")
+	noPrefab               = errors.InternalServerError.Build("Nirvana:Service:noPrefab", "no prefab named ${name}")
+	invalidAutoParameter   = errors.InternalServerError.Build("Nirvana:Service:invalidAutoParameter", "${type} is not a struct or a pointer to struct")
+	invalidFieldTag        = errors.InternalServerError.Build("Nirvana:Service:invalidFieldTag", "filed tag ${tag} is invalid")
+	noName                 = errors.InternalServerError.Build("Nirvana:Service:noName", "${source} must have a name")
+	invalidTypeForConsumer = errors.InternalServerError.Build("Nirvana:Service:invalidTypeForConsumer", "consumer ${content} can't consume data for type ${type}")
+	invalidTypeForProducer = errors.InternalServerError.Build("Nirvana:Service:invalidTypeForProducer", "producer ${content} can't produce data for type ${type}")
+	unassignableType       = errors.InternalServerError.Build("Nirvana:Service:unassignableType", "type ${typeA} can't assign to ${typeB}")
+	noConverter            = errors.InternalServerError.Build("Nirvana:Service:unassignableType", "no converter for type ${type}")
+)
diff --git a/utils/api/api.go b/utils/api/api.go
index 9392f71..394c802 100644
--- a/utils/api/api.go
+++ b/utils/api/api.go
@@ -20,8 +20,8 @@ import (
 	"fmt"
 	"reflect"
 
-	"github.com/caicloud/nirvana/definition"
 	"github.com/caicloud/nirvana/service"
+	builderutil "github.com/caicloud/nirvana/service/builder"
 	"github.com/caicloud/nirvana/utils/project"
 )
 
@@ -106,7 +106,7 @@ func (d *Definitions) fillTypes(types map[TypeName]*Type, name TypeName) {
 // Container contains informations to generate APIs.
 type Container struct {
 	modifiers     service.DefinitionModifiers
-	descriptors   []definition.Descriptor
+	descriptors   []interface{}
 	typeContainer *TypeContainer
 	analyzer      *Analyzer
 }
@@ -129,19 +129,19 @@ func (ac *Container) AddModifier(modifiers ...service.DefinitionModifier) {
 }
 
 // AddDescriptor add descriptors to container.
-func (ac *Container) AddDescriptor(descriptors ...definition.Descriptor) {
+func (ac *Container) AddDescriptor(descriptors ...interface{}) {
 	ac.descriptors = append(ac.descriptors, descriptors...)
 }
 
 // Generate generates API definitions.
-func (ac *Container) Generate() (*Definitions, error) {
-	builder := service.NewBuilder()
+func (ac *Container) Generate(apiStyle string) (*Definitions, error) {
+	builder := builderutil.New(apiStyle)
 	builder.SetModifier(ac.modifiers.Combine())
 	if err := builder.AddDescriptor(ac.descriptors...); err != nil {
 		return nil, err
 	}
 	definitions := builder.Definitions()
-	result, err := NewPathDefinitions(ac.typeContainer, definitions)
+	result, err := NewPathDefinitions(ac.typeContainer, definitions, apiStyle)
 	if err != nil {
 		return nil, err
 	}
diff --git a/utils/api/definitions.go b/utils/api/definitions.go
index 52459a3..c49dd2a 100644
--- a/utils/api/definitions.go
+++ b/utils/api/definitions.go
@@ -22,6 +22,7 @@ import (
 
 	"github.com/caicloud/nirvana/definition"
 	"github.com/caicloud/nirvana/service"
+	"github.com/caicloud/nirvana/service/builder"
 )
 
 // Parameter describes a function parameter.
@@ -97,11 +98,16 @@ type Definition struct {
 }
 
 // NewDefinition creates openapi.Definition from definition.Definition.
-func NewDefinition(tc *TypeContainer, d *definition.Definition) (*Definition, error) {
+func NewDefinition(tc *TypeContainer, d *definition.Definition, apiStyle string) (*Definition, error) {
+	code := service.HTTPCodeFor(d.Method)
+	if apiStyle == builder.APIStyleRPC {
+		code = http.StatusOK
+	}
+
 	cd := &Definition{
 		Method:        d.Method,
 		HTTPMethod:    service.HTTPMethodFor(d.Method),
-		HTTPCode:      service.HTTPCodeFor(d.Method),
+		HTTPCode:      code,
 		Summary:       d.Summary,
 		Description:   d.Description,
 		Tags:          d.Tags,
@@ -164,10 +170,10 @@ func NewDefinition(tc *TypeContainer, d *definition.Definition) (*Definition, er
 }
 
 // NewDefinitions creates a list of definitions.
-func NewDefinitions(tc *TypeContainer, definitions []definition.Definition) ([]Definition, error) {
+func NewDefinitions(tc *TypeContainer, definitions []definition.Definition, apiStyle string) ([]Definition, error) {
 	result := make([]Definition, len(definitions))
 	for i, d := range definitions {
-		cd, err := NewDefinition(tc, &d)
+		cd, err := NewDefinition(tc, &d, apiStyle)
 		if err != nil {
 			return nil, err
 		}
@@ -177,10 +183,10 @@ func NewDefinitions(tc *TypeContainer, definitions []definition.Definition) ([]D
 }
 
 // NewPathDefinitions creates a list of definitions with path.
-func NewPathDefinitions(tc *TypeContainer, definitions map[string][]definition.Definition) (map[string][]Definition, error) {
+func NewPathDefinitions(tc *TypeContainer, definitions map[string][]definition.Definition, apiStyle string) (map[string][]Definition, error) {
 	result := make(map[string][]Definition)
 	for path, defs := range definitions {
-		cds, err := NewDefinitions(tc, defs)
+		cds, err := NewDefinitions(tc, defs, apiStyle)
 		if err != nil {
 			return nil, err
 		}
diff --git a/utils/builder/builder.go b/utils/builder/builder.go
index 71827a1..04371fd 100644
--- a/utils/builder/builder.go
+++ b/utils/builder/builder.go
@@ -30,6 +30,7 @@ import (
 	"strconv"
 	"text/template"
 
+	builderutil "github.com/caicloud/nirvana/service/builder"
 	"github.com/caicloud/nirvana/utils/api"
 )
 
@@ -59,6 +60,8 @@ func (b *APIBuilder) Build() (*api.Definitions, error) {
 	descriptors := make([]function, 0)
 	modifiers := make([]function, 0)
 
+	apiStyle := builderutil.APIStyleREST
+
 	for _, pkg := range analyzer.Paths() {
 		groups := analyzer.PackageComments(pkg)
 		for _, group := range groups {
@@ -82,6 +85,10 @@ func (b *APIBuilder) Build() (*api.Definitions, error) {
 						}
 						modifiers = append(modifiers, *f)
 					}
+					style := tag.Get("style")
+					if style != "" {
+						apiStyle = style
+					}
 				}
 			}
 		}
@@ -89,7 +96,7 @@ func (b *APIBuilder) Build() (*api.Definitions, error) {
 	if len(descriptors) <= 0 {
 		return nil, fmt.Errorf("can't find descriptors from %v", b.paths)
 	}
-	return b.runMain(descriptors, modifiers, b.root, b.paths)
+	return b.runMain(descriptors, modifiers, b.root, b.paths, apiStyle)
 }
 
 type function struct {
@@ -134,7 +141,7 @@ func getFunction(analyzer *api.Analyzer, pkg, name string) (*function, error) {
 	return f, nil
 }
 
-func (b *APIBuilder) runMain(descriptors, modifiers []function, root string, paths []string) (*api.Definitions, error) {
+func (b *APIBuilder) runMain(descriptors, modifiers []function, root string, paths []string, apiStyle string) (*api.Definitions, error) {
 	tempDir, err := ioutil.TempDir(root, "nirvana-generated")
 	if err != nil {
 		return nil, err
@@ -144,7 +151,7 @@ func (b *APIBuilder) runMain(descriptors, modifiers []function, root string, pat
 		err := os.RemoveAll(tempDir)
 		_ = err
 	}()
-	data, err := b.file(descriptors, modifiers, root, paths)
+	data, err := b.file(descriptors, modifiers, root, paths, apiStyle)
 	if err != nil {
 		return nil, err
 	}
@@ -166,7 +173,7 @@ func (b *APIBuilder) runMain(descriptors, modifiers []function, root string, pat
 	return definitions, nil
 }
 
-func (b *APIBuilder) file(descriptors, modifiers []function, root string, paths []string) ([]byte, error) {
+func (b *APIBuilder) file(descriptors, modifiers []function, root string, paths []string, apiStyle string) ([]byte, error) {
 	const tpl = `
 package main
 
@@ -196,7 +203,7 @@ func main() {
 	{{ range $i,$d := .descriptors }}
 	container.AddDescriptor(d{{ $i }}.{{ $d.Name }}(){{ if $d.Array }}...{{ end }})
 	{{ end }}
-	definitions, err := container.Generate()
+	definitions, err := container.Generate({{ .apiStyle }})
 	if definitions == nil {
 		log.Fatal(err)
 	}
@@ -217,6 +224,7 @@ func main() {
 		"descriptors": descriptors,
 		"root":        strconv.Quote(root),
 		"paths":       paths,
+		"apiStyle":    strconv.Quote(apiStyle),
 	}); err != nil {
 		return nil, err
 	}


Test patch:
diff --git a/examples/api-basic/api/v2/app_test.go b/examples/api-basic/api/v2/app_test.go
index 531dfcf..1ac8046 100644
--- a/examples/api-basic/api/v2/app_test.go
+++ b/examples/api-basic/api/v2/app_test.go
@@ -22,12 +22,12 @@ import (
 	"testing"
 
 	"github.com/caicloud/nirvana/examples/api-basic/application"
-
+	"github.com/caicloud/nirvana/service/builder"
 	"github.com/caicloud/nirvana/utils/unittest"
 )
 
 func TestCreate(t *testing.T) {
-	s, err := unittest.NewTestService(Descriptor())
+	s, err := unittest.NewTestService(builder.APIStyleREST, Descriptor())
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/middlewares/reqlog/reqlog_test.go b/middlewares/reqlog/reqlog_test.go
index 21d28ab..d60def8 100644
--- a/middlewares/reqlog/reqlog_test.go
+++ b/middlewares/reqlog/reqlog_test.go
@@ -32,6 +32,7 @@ import (
 	"github.com/caicloud/nirvana/definition"
 	"github.com/caicloud/nirvana/log"
 	"github.com/caicloud/nirvana/service"
+	"github.com/caicloud/nirvana/service/rest"
 )
 
 const tenantHeader = "X-Tenant"
@@ -64,7 +65,7 @@ func mustURL(rawURL string) *url.URL {
 }
 
 func buildService(middleware definition.Middleware) (service.Service, error) {
-	builder := service.NewBuilder()
+	builder := rest.NewBuilder()
 	builder.SetModifier(service.FirstContextParameter())
 	if err := builder.AddDescriptor(
 		definition.Descriptor{
diff --git a/service/builder_test.go b/service/rest/builder_test.go
similarity index 95%
rename from service/builder_test.go
rename to service/rest/builder_test.go
index 804e797..e221841 100644
--- a/service/builder_test.go
+++ b/service/rest/builder_test.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package service
+package rest
 
 import (
 	"bytes"
@@ -28,6 +28,7 @@ import (
 
 	"github.com/caicloud/nirvana/definition"
 	"github.com/caicloud/nirvana/errors"
+	"github.com/caicloud/nirvana/service"
 )
 
 type responseWriter struct {
@@ -102,7 +103,7 @@ type Application struct {
 }
 
 func Handle(ctx context.Context, userAgent string, target1 int, target2 bool, app *Application) (*Application, error) {
-	path := HTTPContextFrom(ctx).RoutePath()
+	path := service.HTTPContextFrom(ctx).RoutePath()
 	if path != "/api/v1/{target1}/{target2}" {
 		return nil, fmt.Errorf("http abstract path is not correct: %s", path)
 	}
@@ -130,8 +131,8 @@ func TestServer(t *testing.T) {
 		ContentLength: int64(len(data)),
 	}
 	builder := NewBuilder()
-	builder.SetModifier(FirstContextParameter())
-	builder.AddFilter(RedirectTrailingSlash(), FillLeadingSlash(), ParseRequestForm())
+	builder.SetModifier(service.FirstContextParameter())
+	builder.AddFilter(service.RedirectTrailingSlash(), service.FillLeadingSlash(), service.ParseRequestForm())
 	err := builder.AddDescriptor(desc)
 	if err != nil {
 		t.Fatal(err)
@@ -225,7 +226,7 @@ func TestChildrenPath(t *testing.T) {
 		},
 	}
 	builder := NewBuilder()
-	builder.SetModifier(FirstContextParameter())
+	builder.SetModifier(service.FirstContextParameter())
 	err := builder.AddDescriptor(childrenDesc)
 	if err != nil {
 		t.Fatal(err)
@@ -292,7 +293,7 @@ func TestHomePath(t *testing.T) {
 		},
 	}
 	builder := NewBuilder()
-	builder.SetModifier(FirstContextParameter())
+	builder.SetModifier(service.FirstContextParameter())
 	err := builder.AddDescriptor(homeDesc)
 	if err != nil {
 		t.Fatal(err)
@@ -387,7 +388,7 @@ func TestDefaultParams(t *testing.T) {
 		},
 	}
 	builder := NewBuilder()
-	builder.SetModifier(FirstContextParameter())
+	builder.SetModifier(service.FirstContextParameter())
 	err := builder.AddDescriptor(defaultParamsDesc)
 	if err != nil {
 		t.Fatal(err)
@@ -502,8 +503,8 @@ func BenchmarkServer(b *testing.B) {
 		ContentLength: int64(len(data)),
 	}
 	builder := NewBuilder()
-	builder.SetModifier(FirstContextParameter())
-	builder.AddFilter(RedirectTrailingSlash(), FillLeadingSlash(), ParseRequestForm())
+	builder.SetModifier(service.FirstContextParameter())
+	builder.AddFilter(service.RedirectTrailingSlash(), service.FillLeadingSlash(), service.ParseRequestForm())
 	err := builder.AddDescriptor(desc)
 	if err != nil {
 		b.Fatal(err)
diff --git a/service/executor_test.go b/service/rest/inspector_test.go
similarity index 89%
rename from service/executor_test.go
rename to service/rest/inspector_test.go
index 63ef1c1..309d305 100644
--- a/service/executor_test.go
+++ b/service/rest/inspector_test.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package service
+package rest
 
 import (
 	"context"
@@ -22,7 +22,8 @@ import (
 
 	"github.com/caicloud/nirvana/definition"
 	"github.com/caicloud/nirvana/errors"
-	"github.com/caicloud/nirvana/log"
+	"github.com/caicloud/nirvana/service"
+	"github.com/caicloud/nirvana/service/executor"
 )
 
 type definitionMap struct {
@@ -31,26 +32,26 @@ type definitionMap struct {
 }
 
 func TestAddDefinition(t *testing.T) {
-	inspector := newInspector("/test", &log.SilentLogger{})
+	inspector := newInspector("/test")
 	units := []definitionMap{
 		{
 			definition.Definition{
 				Method: definition.Method(""),
 			},
-			definitionNoMethod,
+			executor.DefinitionNoMethod,
 		},
 		{
 			definition.Definition{
 				Method: definition.Get,
 			},
-			definitionNoConsumes,
+			executor.DefinitionNoConsumes,
 		},
 		{
 			definition.Definition{
 				Method:   definition.Get,
 				Consumes: []string{definition.MIMENone},
 			},
-			definitionNoProduces,
+			executor.DefinitionNoProduces,
 		},
 		{
 			definition.Definition{
@@ -58,7 +59,7 @@ func TestAddDefinition(t *testing.T) {
 				Consumes: []string{definition.MIMENone},
 				Produces: []string{definition.MIMEJSON},
 			},
-			definitionNoErrorProduces,
+			executor.DefinitionNoErrorProduces,
 		},
 
 		{
@@ -68,7 +69,7 @@ func TestAddDefinition(t *testing.T) {
 				Produces:      []string{definition.MIMEJSON},
 				ErrorProduces: []string{definition.MIMEJSON},
 			},
-			definitionNoFunction,
+			executor.DefinitionNoFunction,
 		},
 		{
 			definition.Definition{
@@ -78,7 +79,7 @@ func TestAddDefinition(t *testing.T) {
 				ErrorProduces: []string{definition.MIMEJSON},
 				Function:      1,
 			},
-			definitionInvalidFunctionType,
+			executor.DefinitionInvalidFunctionType,
 		},
 		{
 			definition.Definition{
@@ -89,7 +90,7 @@ func TestAddDefinition(t *testing.T) {
 				Function: func() {
 				},
 			},
-			definitionNoConsumer,
+			executor.DefinitionNoConsumer,
 		},
 		{
 			definition.Definition{
@@ -100,7 +101,7 @@ func TestAddDefinition(t *testing.T) {
 				Function: func() {
 				},
 			},
-			definitionNoProducer,
+			executor.DefinitionNoProducer,
 		},
 		{
 			definition.Definition{
@@ -111,7 +112,7 @@ func TestAddDefinition(t *testing.T) {
 				Function: func() {
 				},
 			},
-			definitionNoProducer,
+			executor.DefinitionNoProducer,
 		},
 		{
 			definition.Definition{
@@ -122,7 +123,7 @@ func TestAddDefinition(t *testing.T) {
 				Function: func(ctx context.Context) {
 				},
 			},
-			definitionUnmatchedParameters,
+			executor.DefinitionUnmatchedParameters,
 		},
 		{
 			definition.Definition{
@@ -138,7 +139,7 @@ func TestAddDefinition(t *testing.T) {
 				Function: func(a int) {
 				},
 			},
-			noParameterGenerator,
+			service.NoParameterGenerator,
 		},
 		{
 			definition.Definition{
@@ -156,7 +157,7 @@ func TestAddDefinition(t *testing.T) {
 				Function: func(a int) {
 				},
 			},
-			unassignableType,
+			executor.InvalidParameter,
 		},
 		{
 			definition.Definition{
@@ -173,7 +174,7 @@ func TestAddDefinition(t *testing.T) {
 				Function: func(a []*int) {
 				},
 			},
-			noConverter,
+			executor.InvalidParameter,
 		},
 		{
 			definition.Definition{
@@ -195,7 +196,7 @@ func TestAddDefinition(t *testing.T) {
 				Function: func(a []int) {
 				},
 			},
-			invalidOperatorOutType,
+			executor.InvalidOperatorsForParameter,
 		},
 		{
 			definition.Definition{
@@ -220,7 +221,7 @@ func TestAddDefinition(t *testing.T) {
 				Function: func(a []int) {
 				},
 			},
-			invalidOperatorInType,
+			executor.InvalidOperatorsForParameter,
 		},
 		{
 			definition.Definition{
@@ -232,7 +233,7 @@ func TestAddDefinition(t *testing.T) {
 					return 0
 				},
 			},
-			definitionUnmatchedResults,
+			executor.DefinitionUnmatchedResults,
 		},
 		{
 			definition.Definition{
@@ -249,7 +250,7 @@ func TestAddDefinition(t *testing.T) {
 					return 0
 				},
 			},
-			noDestinationHandler,
+			executor.NoDestinationHandler,
 		},
 		{
 			definition.Definition{
@@ -271,7 +272,7 @@ func TestAddDefinition(t *testing.T) {
 					return 0
 				},
 			},
-			invalidOperatorInType,
+			executor.InvalidOperatorsForResult,
 		},
 		{
 			definition.Definition{
@@ -293,7 +294,7 @@ func TestAddDefinition(t *testing.T) {
 				Function: func() {
 				},
 			},
-			definitionConflict,
+			executor.DefinitionConflict,
 		},
 	}
 
diff --git a/service/router/router_test.go b/service/rest/router/router_test.go
similarity index 97%
rename from service/router/router_test.go
rename to service/rest/router/router_test.go
index eaaf565..b3074f2 100644
--- a/service/router/router_test.go
+++ b/service/rest/router/router_test.go
@@ -22,7 +22,9 @@ import (
 	"reflect"
 	"testing"
 
+	"github.com/caicloud/nirvana/definition"
 	"github.com/caicloud/nirvana/errors"
+	"github.com/caicloud/nirvana/service/executor"
 )
 
 var (
@@ -80,12 +82,12 @@ func TestReorganize(t *testing.T) {
 		{
 			[]string{"/segments/", "{segment:[a-z]{1,2}}", ".log", "{temp", "sss/paths/", "{path:*}"},
 			nil,
-			InvalidRegexp,
+			invalidRegexp,
 		},
 		{
 			[]string{"/segments/", "{segment:[a-z]{1,2}}", ".log", "{temp}", "{path:*}", "sss/paths/"},
 			nil,
-			InvalidPathKey,
+			invalidPathKey,
 		},
 		{
 			[]string{"/segments/", "{segment:[a-z]{1,2}}", ".log", "{temp}", "{path:*}"},
@@ -144,22 +146,22 @@ func TestParse(t *testing.T) {
 		{
 			path: "/segments/{{cmd}/{segment:[a-z]{1,2}}.log{temp}sss/paths/{path:*}",
 			tab:  nil,
-			err:  UnmatchedPathBrace,
+			err:  unmatchedPathBrace,
 		},
 		{
 			path: "",
 			tab:  nil,
-			err:  InvalidPath,
+			err:  invalidPath,
 		},
 		{
 			path: "/segments/{cmd}/{segment:[a-z]{1,2}}.log{temp}sss/paths/{path:*}/why",
 			tab:  nil,
-			err:  InvalidPathKey,
+			err:  invalidPathKey,
 		},
 		{
 			path: "/segments/{cmd}/{segment:[a-z]{1,2}}.log{temp{why}}sss/paths/{path:*}",
 			tab:  nil,
-			err:  InvalidRegexp,
+			err:  invalidRegexp,
 		},
 	}
 
@@ -276,7 +278,7 @@ func TestPathNodeMerge(t *testing.T) {
 			root = router
 		} else {
 			if _, err := root.Merge(router); err != nil {
-				if !UnmatchedRouterKey.Derived(err) {
+				if !unmatchedRouterKey.Derived(err) {
 					t.Fatal(err)
 				}
 			} else {
@@ -303,7 +305,7 @@ func TestCommonPrefixMergeError(t *testing.T) {
 			root = router
 		} else {
 			_, err = root.Merge(router)
-			errorCompare(t, err, NoCommonPrefix)
+			errorCompare(t, err, noCommonPrefix)
 		}
 	}
 }
@@ -325,7 +327,7 @@ func TestRegexpMergeError(t *testing.T) {
 			root = router
 		} else {
 			_, err = root.Merge(router)
-			errorCompare(t, err, UnmatchedRouterKey)
+			errorCompare(t, err, unmatchedRouterKey)
 		}
 	}
 }
@@ -393,7 +395,7 @@ func testMatch(t *testing.T, router Router, right []TestData, wrong []TestData)
 
 func TestMiddleWare(t *testing.T) {
 	rds := []TestRouterData{
-		{"/api/v2", []*TestExecutor{{"GET", 202}, {"DELETE", 203}}, []Middleware{func(ctx context.Context, c RoutingChain) error {
+		{"/api/v2", []*TestExecutor{{"GET", 202}, {"DELETE", 203}}, []definition.Middleware{func(ctx context.Context, c definition.Chain) error {
 			resultptr := ctx.Value(contextKey("Result")).(*int)
 			*resultptr++
 			c.Continue(context.WithValue(ctx, contextKey("Result"), resultptr))
@@ -619,7 +621,7 @@ type TestData struct {
 type TestRouterData struct {
 	Path       string
 	Executor   []*TestExecutor
-	Middleware []Middleware
+	Middleware []definition.Middleware
 }
 
 type TestValueContainer struct {
@@ -647,7 +649,7 @@ type TestExecutor struct {
 	Result int
 }
 
-func (te *TestExecutor) Inspect(c context.Context) (Executor, error) {
+func (te *TestExecutor) Inspect(c context.Context) (executor.MiddlewareExecutor, error) {
 	ins := c.Value(contextKey("Type"))
 	if typ, ok := ins.(string); ok && typ == te.Type {
 		return te, nil
@@ -667,7 +669,7 @@ func (te *TestExecutor) Execute(c context.Context) error {
 
 type TestInspector []*TestExecutor
 
-func (ti TestInspector) Inspect(c context.Context) (Executor, error) {
+func (ti TestInspector) Inspect(c context.Context) (executor.MiddlewareExecutor, error) {
 	for _, e := range ti {
 		target, err := e.Inspect(c)
 		if err != errUnmatched {
diff --git a/service/source_test.go b/service/source_test.go
index 11a31b8..a245e15 100644
--- a/service/source_test.go
+++ b/service/source_test.go
@@ -30,13 +30,19 @@ type vc struct{}
 
 const testKey = "test"
 
-func (v *vc) Path(key string) (string, bool) {
+func (v *vc) Set(key, value string) {}
+
+func (v *vc) Get(key string) (string, bool) {
 	if key == testKey {
 		return "path", true
 	}
 	return "", false
 }
 
+func (v *vc) Path(key string) (string, bool) {
+	return v.Get(key)
+}
+
 func (v *vc) Query(key string) ([]string, bool) {
 	if key == testKey {
 		return []string{"query"}, true
diff --git a/utils/unittest/unittest.go b/utils/unittest/unittest.go
index 262639f..bba6ffe 100644
--- a/utils/unittest/unittest.go
+++ b/utils/unittest/unittest.go
@@ -24,8 +24,8 @@ import (
 	"io"
 	"net/http"
 
-	"github.com/caicloud/nirvana/definition"
 	"github.com/caicloud/nirvana/service"
+	builderutil "github.com/caicloud/nirvana/service/builder"
 )
 
 // ResponseWriter mocks http.ResponseWriter and records response for testing
@@ -71,8 +71,8 @@ func (r *responseWriter) Bytes() []byte {
 }
 
 // NewTestService creates a service.Service for testing.
-func NewTestService(desc ...definition.Descriptor) (service.Service, error) {
-	builder := service.NewBuilder()
+func NewTestService(apiStyle string, desc ...interface{}) (service.Service, error) {
+	builder := builderutil.New(apiStyle)
 	builder.SetModifier(service.FirstContextParameter())
 	builder.AddFilter(service.RedirectTrailingSlash(), service.FillLeadingSlash(), service.ParseRequestForm())
 	if err := builder.AddDescriptor(desc...); err != nil {
@@ -83,9 +83,9 @@ func NewTestService(desc ...definition.Descriptor) (service.Service, error) {
 
 // NewTestServiceWithConfig creates a service.Service for testing with user specified modifier and
 // filters. If modifier or filters is nil, default option will be used.
-func NewTestServiceWithConfig(desc []definition.Descriptor, modifier service.DefinitionModifier,
+func NewTestServiceWithConfig(apiStyle string, desc []interface{}, modifier service.DefinitionModifier,
 	filters []service.Filter) (service.Service, error) {
-	builder := service.NewBuilder()
+	builder := builderutil.New(apiStyle)
 	if modifier == nil {
 		modifier = service.FirstContextParameter()
 	}