Skip to content

HTTP Transport

Morlay edited this page Jul 26, 2018 · 1 revision

HTTP transport

HTTP transport is RESTful API transport of Courier.

Extensions of Operator

HTTP Method, Pattern Path and OperationID

For RESTful API, we need http method and pattern path to do routing distribution.

When we registered Operators to Router, we could get Routes from the root Router tree. We hope each Route have an unique identifier too.

For each Route,

  • Last Operator in the Route
    • The name of the Operator type should be unique in whole service project, which will be the OperationID of the Route
    • must implement interface { Method() string } will describe http method of the Route.
      • use github.com/go-courier/httptransport/httpx.Method* as embedded field to quick add it
  • Each Operator in the Route could implement interface { Path() string }, and concatenated pattern path will be the final pattern path
    • example: Route contains OperatorA with path /a, OperatorB without path, OperatorC with path /c, we will get the final path /a/c

Definitions of HTTP Request parameters and content

Exposed fields of Operator is the parameters of Operator for final process logic.

We will decode values from *http.Request and validate fields or set default values after decoding.

First, we have some rules to define where the field value from:

  • use tag name to define the parameter name, if tag name is missing, use struct field name instead.
    • if the parameter is optional, we could mark ,omitempty in tag name.
  • use tag in to define parameter position, it could be one of query path cookie header body
    • for cookie, if it is expires, value will be ignored.
  • use tag mime could be define content type of field
    • content type's encoding/decoding preset in github.com/go-courier/httptransport/transformers
    • if empty
      • content type of type struct/slice/array/map which not implements encoding.TextUnmarshaler will be application/json
        • but if tag in is not body, type slice or array will be based on its elem type, and be exploded to multi values like a=1&a=b
      • content type of others type will be text/plain

Example:

type ReqWithFormData struct {
    ID string `name:"id" in:"path"`
    Header string `name:"X-Header,omitempty" in:"header"`
    Query string `name:"query,omitempty" in:"query"`
    Token string `name:"token,omitempty" in:"cookie"`
    FormData struct {
        String string                `name:"string,omitempty"`
        Slice  []string              `name:"slice,omitempty"`
        Data   Data                  `name:"data,omitempty"`
        File   *multipart.FileHeader `name:"file"`
    } `in:"body" mime:"multipart"`
}

func (ReqWithFormData) Path() string  {
	return "/:id" // here should match name of parameter in path 
}

After parameter values are set, we will do validation if tag validate exists. Validation rules be preset by github.com/go-courier/validator. And we could use tag errMsg to overwrite err msg from validation error.

For each field,

  • If ,omitempty is not marked, when field value is empty, the validate will throw a github.com/go-courier/validator/errors.MissingRequiredFieldError
    • pointer/slice/map value is nil
    • string value is ""
    • float and integer value is 0
  • If ,omitempty is marked, but when default value be marked by tag default="",
    • nil pointer/slice/map will be created.
    • empty value will be set by the default value string

All errors in decoding or validate will be register to a httptransport.BadRequest. if Operator implemented interface { PostValidate(badRequest *BadRequest) }, we could modify the httptransport.BadRequest or add error after compare between parameters.

*httptransport.BadRequest contains slice of error fields. if the slice is not empty. processing will be disrupted with error, and encoding err and write to response.

Output(ctx context.Context) (interface{}, error)

When Operator is decoded success and validated pass. We will trigger Output method for business logic.

  • When return err, processing will be disrupted with error, and encoding err and write to response.
  • When return some result
    • if Operator is not last in route. the result will be store in context for next Operator.
      • interface ContextKey() string will defined the context key, otherwise, context key will be reflect.TypeOf(operator).String()
    • last Operator in route, the result will be transform to body contents.

Encoding result or error and write to response

  • we could define status code of response by implementing interface StatusCode() int.

    • if missing
      • when result is nil, status code will be 204 as defaults.
      • when request method is GET, status code will be 200 as defaults.
      • when request method is POST, status code will be 201 as defaults.
  • we could define content type of response by implementing interface ContentType() string

    • results implements io.Reader will be copy to response without transformer
      • otherwise, content type will match transformer of github.com/go-courier/httptransport/transformers
    • if missing
      • content type of type struct/slice/array/map which not implements encoding.TextMarshaler will be application/json
      • others which not nil will be text/plain

Command line interfaces

Courier provide cli to help scanning code base or code generating

Install

go get -u github.com/go-courier/cmd/courier

courer openapi

When we could run service by courier successfully, we could use

  • API documents automatically generate OpenAPI spec json file from codes.
    • generate client of target service by the generated OpenAPI spec json file.

courier gen client <ServiceName> --spec-url <url>

Generate client by openapi spec.

if openapi spec the which is be generated by courier openapi, extra features:

  • enumeration will be generated.
  • validate rule (github.com/go-courier/validator) will be tagged.
  • status errors of upstream will decorated in comments matched api call method

https://github.com/go-courier/httptransport/tree/master/__examples__/client_demo

courier gen enum <TypeName>

There is a way to declaring enumeration in go: [https://github.com/go-courier/enumeration]

//go:generate courier gen enum Protocol
type Protocol int // declare named type base on int or uint

// declare const as the declared type
// and with prefix upper-snake-cased type name
// link with UNKNOWN by one lodash _ as the zero value
// link with ENUM VALUE by two lodash __ as enum values
// comments after enum values will be the label of matched enum value
const (
	PROTOCOL_UNKNOWN Protocol = iota
	PROTOCOL__HTTP    // http
	PROTOCOL__HTTPS   // https
	PROTOCOL__TCP
)

By the subcommand, type Protocol will generate methods which implemented encoding.TextMarshall, encoding.TextUnmarshal, database/sql.Scaner and database/sql/driver.Valuer

courier gen error

There is a way to declare status error by auto-increased code [https://github.com/go-courier/statuserror]

code rule contains three part.

status code     serivce code    auto-increased id
500             001             001
//go:generate courier gen error StatusError
type StatusError int

func (StatusError) ServiceCode() int {
	return 999 * 1e3
}

const (
	// InternalServerError
	InternalServerError StatusError = http.StatusInternalServerError*1e6 + iota + 1
)

const (
	// @errTalk Unauthorized
	Unauthorized StatusError = http.StatusUnauthorized*1e6 + iota + 1
	
	// comment rule for generating
	// @errTalk <Summary>
)

By the subcommand, type StatusError will generate methods which implemented github.com/go-courier/statuserror.StatusError Then we could use the StatusError as an error

Tutorials