-
Notifications
You must be signed in to change notification settings - Fork 2
HTTP Transport
HTTP transport is RESTful API transport of Courier.
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
Operatorin the Route- The name of the Operator type should be unique in whole service project, which will be the
OperationIDof 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
- use
- The name of the Operator type should be unique in whole service project, which will be the
- Each
Operatorin the Route could implementinterface { Path() string }, and concatenated pattern path will be the final pattern path- example: Route contains
OperatorAwith path/a,OperatorBwithout path,OperatorCwith path/c, we will get the final path/a/c
- example: Route contains
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
nameto define the parameter name, if tagnameis missing, use struct field name instead.- if the parameter is optional, we could mark
,omitemptyin tagname.
- if the parameter is optional, we could mark
- use tag
into define parameter position, it could be one ofquerypathcookieheaderbody- for
cookie, if it is expires, value will be ignored.
- for
- use tag
mimecould 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.TextUnmarshalerwill beapplication/json- but if tag
inis notbody, type slice or array will be based on its elem type, and be exploded to multi values likea=1&a=b
- but if tag
-
content type of others type will be
text/plain
-
content type of type struct/slice/array/map which not implements
- content type's encoding/decoding preset in
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
,omitemptyis not marked, when field value is empty, the validate will throw agithub.com/go-courier/validator/errors.MissingRequiredFieldError- pointer/slice/map value is
nil - string value is
"" - float and integer value is
0
- pointer/slice/map value is
- If
,omitemptyis marked, but when default value be marked by tagdefault="",-
nilpointer/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.
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() stringwill defined the context key, otherwise, context key will bereflect.TypeOf(operator).String()
- interface
- last Operator in route, the result will be transform to body contents.
- if Operator is not last in route. the result will be store in context for next Operator.
-
we could define status code of response by implementing interface
StatusCode() int.- if missing
- when result is
nil, status code will be204as defaults. - when request method is
GET, status code will be200as defaults. - when request method is
POST, status code will be201as defaults.
- when result is
- if missing
-
we could define content type of response by implementing interface
ContentType() string- results implements
io.Readerwill be copy to response without transformer- otherwise, content type will match transformer of
github.com/go-courier/httptransport/transformers
- otherwise, content type will match transformer of
- if missing
-
content type of type struct/slice/array/map which not implements
encoding.TextMarshalerwill beapplication/json - others which not
nilwill betext/plain
-
content type of type struct/slice/array/map which not implements
- results implements
Courier provide cli to help scanning code base or code generating
go get -u github.com/go-courier/cmd/courier
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.
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
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
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