Documentation ¶
Overview ¶
Package api2 provides types and functions used to define interfaces of client-server API and facilitate creation of server and client for it.
How to use this package. Organize your code in services. Each service provides some domain specific functionality. It is a Go type whose methods correspond to exposed RPC's of the API. Each method has the following signature:
func(ctx, *Request) (*Response, error)
Let's define a service Foo with method Bar.
type Foo struct { ... } type BarRequest struct { // These fields are stored in JSON format in body. Name string `json:"name"` // These fields are GET parameters. UserID int `query:"user_id"` // These fields are headers. FileHash string `header:"file_hash"` // These fields are skipped. SkippedField int `json:"-"` } type BarResponse struct { // These fields are stored in JSON format in body. FileSize int `json:"file_size"` // These fields are headers. FileHash string `header:"file_hash"` // These fields are skipped. SkippedField int `json:"-"` } func (s *Foo) Bar(ctx context.Context, req *BarRequest) (*BarResponse, error) { ... }
A field must not have more than one of tags: json, query, header. Fields in query and header parts are encoded and decoded with fmt.Sprintf and fmt.Sscanf. Strings are not decoded with fmt.Sscanf, but passed as is. Types implementing encoding.TextMarshaler and encoding.TextUnmarshaler are encoded and decoded using it. If no field is no JSON field in the struct, then HTTP body is skipped.
Now let's write the function that generates the table of routes:
func GetRoutes(s *Foo) []api2.Route { return []api2.Route{ { Method: http.MethodPost, Path: "/v1/foo/bar", Handler: s.Bar, Transport: &api2.JsonTransport{}, }, } }
You can add multiple routes with the same path, but in this case their HTTP methods must be different so that they can be distinguished.
If Transport is not set, DefaultTransport is used which is defined as &api2.JsonTransport{}.
In the server you need a real instance of service Foo to pass to GetRoutes. Then just bind the routes to http.ServeMux and run the server:
// Server. foo := NewFoo(...) routes := GetRoutes(foo) api2.BindRoutes(http.DefaultServeMux, routes) log.Fatal(http.ListenAndServe(":8080", nil))
The server is running. It serves foo.Bar function on path /v1/foo/bar with HTTP method Post.
Now let's create the client:
// Client. routes := GetRoutes(nil) client := api2.NewClient(routes, "http://127.0.0.1:8080") barRes := &BarResponse{} err := client.Call(context.Background(), barRes, &BarRequest{ ... }) if err != nil { panic(err) } // Server's response is in variable barRes.
Note that you don't have to pass a real service object to GetRoutes on client side. You can pass nil, it is sufficient to pass all needed information about request and response types in the routes table, that is used by client to find a proper route.
You can make GetRoutes accepting an interface instead of a concrete Service type. In this case you can not get method handlers by s.Bar, because this code panics if s is nil interface. As a workaround api2 provides function Method(service pointer, methodName) which you can use:
type Service interface { Bar(ctx context.Context, req *BarRequest) (*BarResponse, error) } func GetRoutes(s Service) []api2.Route { return []api2.Route{ {Method: http.MethodPost, Path: "/v1/foo/bar", Handler: api2.Method(&s, "Bar"), Transport: &api2.JsonTransport{}}, } }
If you have function GetRoutes in package foo as above you can generate static client for it in file client.go located near the file in which GetRoutes is defined:
api2.GenerateClient(foo.GetRoutes)
Index ¶
- Variables
- func BindRoutes(mux *http.ServeMux, routes []Route, opts ...Option)
- func CustomParse(t reflect.Type) (typegen.IType, bool)
- func GenerateClient(getRoutes interface{})
- func GenerateClientCode(getRoutes interface{}) (code, clientFile string, err error)
- func GenerateTSClient(options *TsGenConfig)
- func Method(servicePtr interface{}, methodName string) interface{}
- func SerializeCustom(t reflect.Type) string
- type Client
- type Config
- type FnInfo
- type FuncInfoer
- type HttpError
- type JsonTransport
- func (h *JsonTransport) DecodeError(ctx context.Context, res *http.Response) error
- func (h *JsonTransport) DecodeRequest(ctx context.Context, r *http.Request, req interface{}) (context.Context, error)
- func (h *JsonTransport) DecodeResponse(ctx context.Context, res *http.Response, response interface{}) error
- func (h *JsonTransport) EncodeError(ctx context.Context, w http.ResponseWriter, err error) error
- func (h *JsonTransport) EncodeRequest(ctx context.Context, method, urlStr string, req interface{}) (*http.Request, error)
- func (h *JsonTransport) EncodeResponse(ctx context.Context, w http.ResponseWriter, res interface{}) error
- type Option
- type Route
- type Transport
- type TsGenConfig
Constants ¶
This section is empty.
Variables ¶
var DefaultTransport = &JsonTransport{}
Functions ¶
func BindRoutes ¶
BindRoutes adds handlers of routes to http.ServeMux.
func GenerateClient ¶
func GenerateClient(getRoutes interface{})
GenerateClient generates file client.go with static client near the file in which passed GetRoutes function is defined.
func GenerateClientCode ¶
GenerateClientCode accepts global function GetRoutes of a package and returns the code of static client and path to the file where the code should be saved (client.go in the same directory where GetRoutes and types of requests, responses and service are defined.
func GenerateTSClient ¶
func GenerateTSClient(options *TsGenConfig)
func SerializeCustom ¶
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is used on client-side to call remote methods provided by the API.
func NewClient ¶
NewClient creates new instance of client.
The list of routes must provide all routes that this client is aware of. Paths from the table of routes are appended to baseURL to generate final URL used by HTTP client. All pairs of (request type, response type) must be unique in the table of routes.
type Config ¶
type Config struct {
// contains filtered or unexported fields
}
func NewDefaultConfig ¶
func NewDefaultConfig() *Config
type FuncInfoer ¶
type FuncInfoer interface {
FuncInfo() (pkgFull, pkgName, structName, method string)
}
type JsonTransport ¶
type JsonTransport struct { RequestDecoder func(context.Context, *http.Request, interface{}) (context.Context, error) ResponseEncoder func(context.Context, http.ResponseWriter, interface{}) error ErrorEncoder func(context.Context, http.ResponseWriter, error) error RequestEncoder func(ctx context.Context, method, url string, req interface{}) (*http.Request, error) ResponseDecoder func(context.Context, *http.Response, interface{}) error ErrorDecoder func(context.Context, *http.Response) error }
JsonTransport implements interface Transport for JSON encoding of requests and responses.
It recognizes GET parameter "human". If it is set, JSON in the response is pretty formatted.
To redefine some methods, set corresponding fields in the struct:
&JsonTransport{RequestDecoder: func ...
func (*JsonTransport) DecodeError ¶
func (*JsonTransport) DecodeRequest ¶
func (*JsonTransport) DecodeResponse ¶
func (*JsonTransport) EncodeError ¶
func (h *JsonTransport) EncodeError(ctx context.Context, w http.ResponseWriter, err error) error
func (*JsonTransport) EncodeRequest ¶
func (*JsonTransport) EncodeResponse ¶
func (h *JsonTransport) EncodeResponse(ctx context.Context, w http.ResponseWriter, res interface{}) error
type Route ¶
type Route struct { // HTTP method. Method string // HTTP path. The same path can be used multiple times with different methods. Path string // Handler is a function with the following signature: // func(ctx, *Request) (*Response, error) // Request and Response are custom structures, unique to this route. Handler interface{} // The transport used in this route. If Transport is not set, DefaultTransport // is used. Transport Transport }
Route describes one endpoint in the API, associated with particular method of some service.
type Transport ¶
type Transport interface { // Called by server. DecodeRequest(ctx context.Context, r *http.Request, req interface{}) (context.Context, error) EncodeResponse(ctx context.Context, w http.ResponseWriter, res interface{}) error EncodeError(ctx context.Context, w http.ResponseWriter, err error) error // Called by client. EncodeRequest(ctx context.Context, method, url string, req interface{}) (*http.Request, error) DecodeResponse(ctx context.Context, httpRes *http.Response, res interface{}) error DecodeError(ctx context.Context, httpRes *http.Response) error }
Transport converts back and forth between HTTP and Request, Response types.