Repository: caicloud/nirvana
Pull request #387 (2020-11-20 07:39:33)

chore(*): update GitHub Actions config
<!--  Thanks for sending a pull request! See below for tips! -->

**What this PR does / why we need it**:

update #384

**Which issue(s) this PR is related to** *(optional, link to 3rd issue(s))*:

Fixes #

Reference to #
<!-- 填在 Fixes，PR 合并就会关 issue。填在 Reference to 会关联 issue，不会联动关闭。-->

**Special notes for your reviewer**:

/cc @xpofei 

<!-- Please answer the following questions during the code freeze, and delete this line.
**Code freeze questions**

1. What causes this PR to not be merged before code freeze?
2. Why this PR is absolutely necessary for this version? Paste a screenshot of smoke testing docs if you could.
3. What's the effects after merging it?
4. Is there anyway we can skip this to not affect the overall process?
-->

**Release note**:
<!--  Write your release note:
1. Enter your extended release note in the below block. If the PR requires additional action from users switching to the new release, include the string "action required".
2. If no release note is required, just write "NONE".
-->

```release-note
NONE
```

<!--  Thanks for sending a pull request! Here are some tips:

1. https://github.com/caicloud/engineering/blob/master/guidelines/review_conventions.md      <-- what is the review process looks like
2. https://github.com/caicloud/engineering/blob/master/guidelines/git_commit_conventions.md  <-- how to structure your git commit
3. https://github.com/caicloud/engineering/blob/master/guidelines/caicloud_bot.md            <-- how to work with caicloud bot

Other tips:

If this is your first contribution, read our Getting Started guide https://github.com/caicloud/engineering/blob/master/guidelines/README.md
-->


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: /cherrypick release-0.3
caicloud-bot: @iawia002: once the present PR merges, I will cherry-pick it on top of release-0.3 in a new PR and assign it to you.

<details>

In response to [this](https://github.com/caicloud/nirvana/pull/387#issuecomment-730967228):

>/cherrypick release-0.3


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

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 24c13a8..796a4a1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,15 +1,13 @@
 name: ci
-
 on: [pull_request]
-
 jobs:
   ci:
     runs-on: ${{ matrix.os }}
     timeout-minutes: 10
     strategy:
       matrix:
-        go: ["1.13"]
-        os: [ubuntu-latest, macOS-latest]
+        go: ["1.13", "1.14", "1.15"]
+        os: [ubuntu-latest]
     name: Go ${{ matrix.go }} in ${{ matrix.os }}
     steps:
       - uses: actions/checkout@v2
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()
 	}