979 lines
23 KiB
Go
979 lines
23 KiB
Go
/*
|
|
* Copyright 2022 CloudWeGo 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 generator
|
|
|
|
var (
|
|
routerTplName = "router.go"
|
|
middlewareTplName = "middleware.go"
|
|
middlewareSingleTplName = "middleware_single.go"
|
|
handlerTplName = "handler.go"
|
|
handlerSingleTplName = "handler_single.go"
|
|
modelTplName = "model.go"
|
|
registerTplName = "register.go"
|
|
clientTplName = "client.go" // generate a default client for server
|
|
hertzClientTplName = "hertz_client.go" // underlying client for client command
|
|
idlClientName = "idl_client.go" // client of service for quick call
|
|
|
|
insertPointNew = "//INSERT_POINT: DO NOT DELETE THIS LINE!"
|
|
insertPointPatternNew = `//INSERT_POINT\: DO NOT DELETE THIS LINE\!`
|
|
)
|
|
|
|
var templateNameSet = map[string]string{
|
|
routerTplName: routerTplName,
|
|
middlewareTplName: middlewareTplName,
|
|
middlewareSingleTplName: middlewareSingleTplName,
|
|
handlerTplName: handlerTplName,
|
|
handlerSingleTplName: handlerSingleTplName,
|
|
modelTplName: modelTplName,
|
|
registerTplName: registerTplName,
|
|
clientTplName: clientTplName,
|
|
hertzClientTplName: hertzClientTplName,
|
|
idlClientName: idlClientName,
|
|
}
|
|
|
|
func IsDefaultPackageTpl(name string) bool {
|
|
if _, exist := templateNameSet[name]; exist {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
var defaultPkgConfig = TemplateConfig{
|
|
Layouts: []Template{
|
|
{
|
|
Path: defaultHandlerDir + sp + handlerTplName,
|
|
Delims: [2]string{"{{", "}}"},
|
|
Body: `// Code generated by hertz generator.
|
|
|
|
package {{.PackageName}}
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/cloudwego/hertz/pkg/app"
|
|
"github.com/cloudwego/hertz/pkg/protocol/consts"
|
|
|
|
{{- range $k, $v := .Imports}}
|
|
{{$k}} "{{$v.Package}}"
|
|
{{- end}}
|
|
)
|
|
|
|
{{range $_, $MethodInfo := .Methods}}
|
|
{{$MethodInfo.Comment}}
|
|
func {{$MethodInfo.Name}}(ctx context.Context, c *app.RequestContext) {
|
|
var err error
|
|
{{if ne $MethodInfo.RequestTypeName "" -}}
|
|
var req {{$MethodInfo.RequestTypeName}}
|
|
err = c.BindAndValidate(&req)
|
|
if err != nil {
|
|
c.String(consts.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
{{end}}
|
|
resp := new({{$MethodInfo.ReturnTypeName}})
|
|
|
|
c.{{.Serializer}}(consts.StatusOK, resp)
|
|
}
|
|
{{end}}
|
|
`,
|
|
},
|
|
{
|
|
Path: defaultRouterDir + sp + routerTplName,
|
|
Delims: [2]string{"{{", "}}"},
|
|
Body: `// Code generated by hertz generator. DO NOT EDIT.
|
|
|
|
package {{$.PackageName}}
|
|
|
|
import (
|
|
"github.com/cloudwego/hertz/pkg/app/server"
|
|
|
|
{{- range $k, $v := .HandlerPackages}}
|
|
{{$k}} "{{$v}}"
|
|
{{- end}}
|
|
)
|
|
|
|
/*
|
|
This file will register all the routes of the services in the master idl.
|
|
And it will update automatically when you use the "update" command for the idl.
|
|
So don't modify the contents of the file, or your code will be deleted when it is updated.
|
|
*/
|
|
|
|
{{define "g"}}
|
|
{{- if eq .Path "/"}}r
|
|
{{- else}}{{.GroupName}}{{end}}
|
|
{{- end}}
|
|
|
|
{{define "G"}}
|
|
{{- if ne .Handler ""}}
|
|
{{- .GroupName}}.{{.HttpMethod}}("{{.Path}}", append({{.HandlerMiddleware}}Mw(), {{.Handler}})...)
|
|
{{- end}}
|
|
{{- if ne (len .Children) 0}}
|
|
{{.MiddleWare}} := {{template "g" .}}.Group("{{.Path}}", {{.GroupMiddleware}}Mw()...)
|
|
{{- end}}
|
|
{{- range $_, $router := .Children}}
|
|
{{- if ne .Handler ""}}
|
|
{{template "G" $router}}
|
|
{{- else}}
|
|
{ {{template "G" $router}}
|
|
}
|
|
{{- end}}
|
|
{{- end}}
|
|
{{- end}}
|
|
|
|
// Register register routes based on the IDL 'api.${HTTP Method}' annotation.
|
|
func Register{{$.IdlName}}(r *server.Hertz) {
|
|
{{template "G" .Router}}
|
|
}
|
|
|
|
`,
|
|
},
|
|
{
|
|
Path: defaultRouterDir + sp + registerTplName,
|
|
Body: `// Code generated by hertz generator. DO NOT EDIT.
|
|
|
|
package {{.PackageName}}
|
|
|
|
import (
|
|
"github.com/cloudwego/hertz/pkg/app/server"
|
|
{{$.DepPkgAlias}} "{{$.DepPkg}}"
|
|
)
|
|
|
|
// GeneratedRegister registers routers generated by IDL.
|
|
func GeneratedRegister(r *server.Hertz){
|
|
` + insertPointNew + `
|
|
{{$.DepPkgAlias}}.{{$.RegisterName}}(r)
|
|
}
|
|
`,
|
|
},
|
|
// Model tpl is imported by model generator. Here only decides model directory.
|
|
{
|
|
Path: defaultModelDir + sp + modelTplName,
|
|
Body: ``,
|
|
},
|
|
{
|
|
Path: defaultRouterDir + sp + middlewareTplName,
|
|
Delims: [2]string{"{{", "}}"},
|
|
Body: `// Code generated by hertz generator.
|
|
|
|
package {{$.PackageName}}
|
|
|
|
import (
|
|
"github.com/cloudwego/hertz/pkg/app"
|
|
)
|
|
|
|
{{define "M"}}
|
|
{{- if ne .Children.Len 0}}
|
|
func {{.GroupMiddleware}}Mw() []app.HandlerFunc {
|
|
// your code...
|
|
return nil
|
|
}
|
|
{{end}}
|
|
{{- if ne .Handler ""}}
|
|
func {{.HandlerMiddleware}}Mw() []app.HandlerFunc {
|
|
// your code...
|
|
return nil
|
|
}
|
|
{{end}}
|
|
{{range $_, $router := $.Children}}{{template "M" $router}}{{end}}
|
|
{{- end}}
|
|
|
|
{{template "M" .Router}}
|
|
|
|
`,
|
|
},
|
|
{
|
|
Path: defaultClientDir + sp + clientTplName,
|
|
Delims: [2]string{"{{", "}}"},
|
|
Body: `// Code generated by hertz generator.
|
|
|
|
package {{$.PackageName}}
|
|
|
|
import (
|
|
"github.com/cloudwego/hertz/pkg/app/client"
|
|
"github.com/cloudwego/hertz/pkg/common/config"
|
|
)
|
|
|
|
type {{.ServiceName}}Client struct {
|
|
client * client.Client
|
|
}
|
|
|
|
func New{{.ServiceName}}Client(opt ...config.ClientOption) (*{{.ServiceName}}Client, error) {
|
|
c, err := client.NewClient(opt...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &{{.ServiceName}}Client{
|
|
client: c,
|
|
}, nil
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
Path: defaultHandlerDir + sp + handlerSingleTplName,
|
|
Delims: [2]string{"{{", "}}"},
|
|
Body: `
|
|
{{.Comment}}
|
|
func {{.Name}}(ctx context.Context, c *app.RequestContext) {
|
|
var err error
|
|
{{if ne .RequestTypeName "" -}}
|
|
var req {{.RequestTypeName}}
|
|
err = c.BindAndValidate(&req)
|
|
if err != nil {
|
|
c.String(consts.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
{{end}}
|
|
resp := new({{.ReturnTypeName}})
|
|
|
|
c.{{.Serializer}}(consts.StatusOK, resp)
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
Path: defaultRouterDir + sp + middlewareSingleTplName,
|
|
Delims: [2]string{"{{", "}}"},
|
|
Body: `
|
|
func {{.MiddleWare}}Mw() []app.HandlerFunc {
|
|
// your code...
|
|
return nil
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
Path: defaultRouterDir + sp + hertzClientTplName,
|
|
Delims: [2]string{"{{", "}}"},
|
|
Body: hertzClientTpl,
|
|
},
|
|
{
|
|
Path: defaultRouterDir + sp + idlClientName,
|
|
Delims: [2]string{"{{", "}}"},
|
|
Body: idlClientTpl,
|
|
},
|
|
},
|
|
}
|
|
|
|
var hertzClientTpl = `// Code generated by hz.
|
|
|
|
package {{.PackageName}}
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
|
|
hertz_client "github.com/cloudwego/hertz/pkg/app/client"
|
|
"github.com/cloudwego/hertz/pkg/common/config"
|
|
"github.com/cloudwego/hertz/pkg/common/errors"
|
|
"github.com/cloudwego/hertz/pkg/protocol"
|
|
"github.com/cloudwego/hertz/pkg/protocol/client"
|
|
)
|
|
|
|
type use interface {
|
|
Use(mws ...hertz_client.Middleware)
|
|
}
|
|
|
|
// Definition of global data and types.
|
|
type ResponseResultDecider func(statusCode int, rawResponse *protocol.Response) (isError bool)
|
|
|
|
type (
|
|
bindRequestBodyFunc func(c *cli, r *request) (contentType string, body io.Reader, err error)
|
|
beforeRequestFunc func(*cli, *request) error
|
|
afterResponseFunc func(*cli, *response) error
|
|
)
|
|
|
|
var (
|
|
hdrContentTypeKey = http.CanonicalHeaderKey("Content-Type")
|
|
hdrContentEncodingKey = http.CanonicalHeaderKey("Content-Encoding")
|
|
|
|
plainTextType = "text/plain; charset=utf-8"
|
|
jsonContentType = "application/json; charset=utf-8"
|
|
formContentType = "multipart/form-data"
|
|
|
|
jsonCheck = regexp.MustCompile(` + "`(?i:(application|text)/(json|.*\\+json|json\\-.*)(; |$))`)\n" +
|
|
`xmlCheck = regexp.MustCompile(` + "`(?i:(application|text)/(xml|.*\\+xml)(; |$))`)\n" +
|
|
`
|
|
)
|
|
|
|
// Configuration of client
|
|
type Option struct {
|
|
f func(*Options)
|
|
}
|
|
|
|
type Options struct {
|
|
hostUrl string
|
|
enumAsInt bool
|
|
doer client.Doer
|
|
header http.Header
|
|
requestBodyBind bindRequestBodyFunc
|
|
responseResultDecider ResponseResultDecider
|
|
middlewares []hertz_client.Middleware
|
|
clientOption []config.ClientOption
|
|
}
|
|
|
|
func getOptions(ops ...Option) *Options {
|
|
opts := &Options{}
|
|
for _, do := range ops {
|
|
do.f(opts)
|
|
}
|
|
return opts
|
|
}
|
|
|
|
// WithHertzClientOption is used to pass configuration for the hertz client
|
|
func WithHertzClientOption(opt ...config.ClientOption) Option {
|
|
return Option{func(op *Options) {
|
|
op.clientOption = append(op.clientOption, opt...)
|
|
}}
|
|
}
|
|
|
|
// WithHertzClientMiddleware is used to register the middleware for the hertz client
|
|
func WithHertzClientMiddleware(mws ...hertz_client.Middleware) Option {
|
|
return Option{func(op *Options) {
|
|
op.middlewares = append(op.middlewares, mws...)
|
|
}}
|
|
}
|
|
|
|
// WithHertzClient is used to register a custom hertz client
|
|
func WithHertzClient(client client.Doer) Option {
|
|
return Option{func(op *Options) {
|
|
op.doer = client
|
|
}}
|
|
}
|
|
|
|
// WithHeader is used to add the default header, which is carried by every request
|
|
func WithHeader(header http.Header) Option {
|
|
return Option{func(op *Options) {
|
|
op.header = header
|
|
}}
|
|
}
|
|
|
|
// WithResponseResultDecider configure custom deserialization of http response to response struct
|
|
func WithResponseResultDecider(decider ResponseResultDecider) Option {
|
|
return Option{func(op *Options) {
|
|
op.responseResultDecider = decider
|
|
}}
|
|
}
|
|
|
|
// WithQueryEnumAsInt is used to set enum as int for query parameters
|
|
func WithQueryEnumAsInt(enable bool) Option {
|
|
return Option{func(op *Options) {
|
|
op.enumAsInt = enable
|
|
}}
|
|
}
|
|
|
|
func withHostUrl(HostUrl string) Option {
|
|
return Option{func(op *Options) {
|
|
op.hostUrl = HostUrl
|
|
}}
|
|
}
|
|
|
|
// underlying client
|
|
type cli struct {
|
|
hostUrl string
|
|
enumAsInt bool
|
|
doer client.Doer
|
|
header http.Header
|
|
bindRequestBody bindRequestBodyFunc
|
|
responseResultDecider ResponseResultDecider
|
|
|
|
beforeRequest []beforeRequestFunc
|
|
afterResponse []afterResponseFunc
|
|
}
|
|
|
|
func (c *cli) Use(mws ...hertz_client.Middleware) error {
|
|
u, ok := c.doer.(use)
|
|
if !ok {
|
|
return errors.NewPublic("doer does not support middleware, choose the right doer.")
|
|
}
|
|
u.Use(mws...)
|
|
return nil
|
|
}
|
|
|
|
func newClient(opts *Options) (*cli, error) {
|
|
if opts.requestBodyBind == nil {
|
|
opts.requestBodyBind = defaultRequestBodyBind
|
|
}
|
|
if opts.responseResultDecider == nil {
|
|
opts.responseResultDecider = defaultResponseResultDecider
|
|
}
|
|
if opts.doer == nil {
|
|
cli, err := hertz_client.NewClient(opts.clientOption...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opts.doer = cli
|
|
}
|
|
|
|
c := &cli{
|
|
hostUrl: opts.hostUrl,
|
|
enumAsInt: opts.enumAsInt,
|
|
doer: opts.doer,
|
|
header: opts.header,
|
|
bindRequestBody: opts.requestBodyBind,
|
|
responseResultDecider: opts.responseResultDecider,
|
|
beforeRequest: []beforeRequestFunc{
|
|
parseRequestURL,
|
|
parseRequestHeader,
|
|
createHTTPRequest,
|
|
},
|
|
afterResponse: []afterResponseFunc{
|
|
parseResponseBody,
|
|
},
|
|
}
|
|
|
|
if len(opts.middlewares) != 0 {
|
|
if err := c.Use(opts.middlewares...); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
func (c *cli) execute(req *request) (*response, error) {
|
|
var err error
|
|
for _, f := range c.beforeRequest {
|
|
if err = f(c, req); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if hostHeader := req.header.Get("Host"); hostHeader != "" {
|
|
req.rawRequest.Header.SetHost(hostHeader)
|
|
}
|
|
|
|
resp := protocol.Response{}
|
|
|
|
err = c.doer.Do(req.ctx, req.rawRequest, &resp)
|
|
|
|
response := &response{
|
|
request: req,
|
|
rawResponse: &resp,
|
|
}
|
|
|
|
if err != nil {
|
|
return response, err
|
|
}
|
|
|
|
body, err := resp.BodyE()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if strings.EqualFold(resp.Header.Get(hdrContentEncodingKey), "gzip") && resp.Header.ContentLength() != 0 {
|
|
body, err = resp.BodyGunzip()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
response.bodyByte = body
|
|
|
|
response.size = int64(len(response.bodyByte))
|
|
|
|
// Apply Response middleware
|
|
for _, f := range c.afterResponse {
|
|
if err = f(c, response); err != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
return response, err
|
|
}
|
|
|
|
// r get request
|
|
func (c *cli) r() *request {
|
|
return &request{
|
|
queryParam: url.Values{},
|
|
header: http.Header{},
|
|
pathParam: map[string]string{},
|
|
formParam: map[string]string{},
|
|
fileParam: map[string]string{},
|
|
client: c,
|
|
}
|
|
}
|
|
|
|
type response struct {
|
|
request *request
|
|
rawResponse *protocol.Response
|
|
|
|
bodyByte []byte
|
|
size int64
|
|
}
|
|
|
|
// statusCode method returns the HTTP status code for the executed request.
|
|
func (r *response) statusCode() int {
|
|
if r.rawResponse == nil {
|
|
return 0
|
|
}
|
|
|
|
return r.rawResponse.StatusCode()
|
|
}
|
|
|
|
// body method returns HTTP response as []byte array for the executed request.
|
|
func (r *response) body() []byte {
|
|
if r.rawResponse == nil {
|
|
return []byte{}
|
|
}
|
|
return r.bodyByte
|
|
}
|
|
|
|
// Header method returns the response headers
|
|
func (r *response) header() http.Header {
|
|
if r.rawResponse == nil {
|
|
return http.Header{}
|
|
}
|
|
h := http.Header{}
|
|
r.rawResponse.Header.VisitAll(func(key, value []byte) {
|
|
h.Add(string(key), string(value))
|
|
})
|
|
|
|
return h
|
|
}
|
|
|
|
type request struct {
|
|
client *cli
|
|
url string
|
|
method string
|
|
queryParam url.Values
|
|
header http.Header
|
|
pathParam map[string]string
|
|
formParam map[string]string
|
|
fileParam map[string]string
|
|
bodyParam interface{}
|
|
rawRequest *protocol.Request
|
|
ctx context.Context
|
|
requestOptions []config.RequestOption
|
|
result interface{}
|
|
Error interface{}
|
|
}
|
|
|
|
func (r *request) setContext(ctx context.Context) *request {
|
|
r.ctx = ctx
|
|
return r
|
|
}
|
|
|
|
func (r *request) context() context.Context {
|
|
return r.ctx
|
|
}
|
|
|
|
func (r *request) setHeader(header, value string) *request {
|
|
r.header.Set(header, value)
|
|
return r
|
|
}
|
|
|
|
func (r *request) addHeader(header, value string) *request {
|
|
r.header.Add(header, value)
|
|
return r
|
|
}
|
|
|
|
func (r *request) addHeaders(params map[string]string) *request {
|
|
for k, v := range params {
|
|
r.addHeader(k, v)
|
|
}
|
|
return r
|
|
}
|
|
|
|
|
|
func (r *request) setQueryParam(param string, value interface{}) *request {
|
|
v := reflect.ValueOf(value)
|
|
switch v.Kind() {
|
|
case reflect.Slice, reflect.Array:
|
|
for index := 0; index < v.Len(); index++ {
|
|
r.queryParam.Add(param, fmt.Sprint(v.Index(index).Interface()))
|
|
}
|
|
case reflect.Int32, reflect.Int64:
|
|
if r.client.enumAsInt {
|
|
r.queryParam.Add(param, fmt.Sprintf("%d", v.Interface()))
|
|
} else {
|
|
r.queryParam.Add(param, fmt.Sprint(v))
|
|
}
|
|
default:
|
|
r.queryParam.Set(param, fmt.Sprint(v))
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (r *request) setResult(res interface{}) *request {
|
|
r.result = res
|
|
return r
|
|
}
|
|
|
|
func (r *request) setError(err interface{}) *request {
|
|
r.Error = err
|
|
return r
|
|
}
|
|
|
|
func (r *request) setHeaders(headers map[string]string) *request {
|
|
for h, v := range headers {
|
|
r.setHeader(h, v)
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
func (r *request) setQueryParams(params map[string]interface{}) *request {
|
|
for p, v := range params {
|
|
r.setQueryParam(p, v)
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
func (r *request) setPathParams(params map[string]string) *request {
|
|
for p, v := range params {
|
|
r.pathParam[p] = v
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (r *request) setFormParams(params map[string]string) *request {
|
|
for p, v := range params {
|
|
r.formParam[p] = v
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (r *request) setFormFileParams(params map[string]string) *request {
|
|
for p, v := range params {
|
|
r.fileParam[p] = v
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (r *request) setBodyParam(body interface{}) *request {
|
|
r.bodyParam = body
|
|
return r
|
|
}
|
|
|
|
func (r *request) setRequestOption(option ...config.RequestOption) *request {
|
|
r.requestOptions = append(r.requestOptions, option...)
|
|
return r
|
|
}
|
|
|
|
func (r *request) execute(method, url string) (*response, error) {
|
|
r.method = method
|
|
r.url = url
|
|
return r.client.execute(r)
|
|
}
|
|
|
|
func parseRequestURL(c *cli, r *request) error {
|
|
if len(r.pathParam) > 0 {
|
|
for p, v := range r.pathParam {
|
|
r.url = strings.Replace(r.url, ":"+p, url.PathEscape(v), -1)
|
|
}
|
|
}
|
|
|
|
// Parsing request URL
|
|
reqURL, err := url.Parse(r.url)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If request.URL is relative path then added c.HostURL into
|
|
// the request URL otherwise request.URL will be used as-is
|
|
if !reqURL.IsAbs() {
|
|
r.url = reqURL.String()
|
|
if len(r.url) > 0 && r.url[0] != '/' {
|
|
r.url = "/" + r.url
|
|
}
|
|
|
|
reqURL, err = url.Parse(c.hostUrl + r.url)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Adding Query Param
|
|
query := make(url.Values)
|
|
|
|
for k, v := range r.queryParam {
|
|
// remove query param from client level by key
|
|
// since overrides happens for that key in the request
|
|
query.Del(k)
|
|
for _, iv := range v {
|
|
query.Add(k, iv)
|
|
}
|
|
}
|
|
|
|
if len(query) > 0 {
|
|
if isStringEmpty(reqURL.RawQuery) {
|
|
reqURL.RawQuery = query.Encode()
|
|
} else {
|
|
reqURL.RawQuery = reqURL.RawQuery + "&" + query.Encode()
|
|
}
|
|
}
|
|
|
|
r.url = reqURL.String()
|
|
|
|
return nil
|
|
}
|
|
|
|
func isStringEmpty(str string) bool {
|
|
return len(strings.TrimSpace(str)) == 0
|
|
}
|
|
|
|
func parseRequestHeader(c *cli, r *request) error {
|
|
hdr := make(http.Header)
|
|
if c.header != nil {
|
|
for k := range c.header {
|
|
hdr[k] = append(hdr[k], c.header[k]...)
|
|
}
|
|
}
|
|
|
|
for k := range r.header {
|
|
hdr.Del(k)
|
|
hdr[k] = append(hdr[k], r.header[k]...)
|
|
}
|
|
|
|
if len(r.formParam) != 0 || len(r.fileParam) != 0 {
|
|
hdr.Add(hdrContentTypeKey, formContentType)
|
|
}
|
|
|
|
r.header = hdr
|
|
return nil
|
|
}
|
|
|
|
// detectContentType method is used to figure out "request.Body" content type for request header
|
|
func detectContentType(body interface{}) string {
|
|
contentType := plainTextType
|
|
kind := reflect.Indirect(reflect.ValueOf(body)).Kind()
|
|
switch kind {
|
|
case reflect.Struct, reflect.Map:
|
|
contentType = jsonContentType
|
|
case reflect.String:
|
|
contentType = plainTextType
|
|
default:
|
|
if b, ok := body.([]byte); ok {
|
|
contentType = http.DetectContentType(b)
|
|
} else if kind == reflect.Slice {
|
|
contentType = jsonContentType
|
|
}
|
|
}
|
|
|
|
return contentType
|
|
}
|
|
|
|
func defaultRequestBodyBind(c *cli, r *request) (contentType string, body io.Reader, err error) {
|
|
if !isPayloadSupported(r.method) {
|
|
return
|
|
}
|
|
var bodyBytes []byte
|
|
contentType = r.header.Get(hdrContentTypeKey)
|
|
if isStringEmpty(contentType) {
|
|
contentType = detectContentType(r.bodyParam)
|
|
r.header.Set(hdrContentTypeKey, contentType)
|
|
}
|
|
kind := reflect.Indirect(reflect.ValueOf(r.bodyParam)).Kind()
|
|
if isJSONType(contentType) &&
|
|
(kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) {
|
|
bodyBytes, err = json.Marshal(r.bodyParam)
|
|
} else if isXMLType(contentType) && (kind == reflect.Struct) {
|
|
bodyBytes, err = xml.Marshal(r.bodyParam)
|
|
}
|
|
if err != nil {
|
|
return
|
|
}
|
|
return contentType, strings.NewReader(string(bodyBytes)), nil
|
|
}
|
|
|
|
func isPayloadSupported(m string) bool {
|
|
return !(m == http.MethodHead || m == http.MethodOptions || m == http.MethodGet || m == http.MethodDelete)
|
|
}
|
|
|
|
func createHTTPRequest(c *cli, r *request) (err error) {
|
|
contentType, body, err := c.bindRequestBody(c, r)
|
|
if !isStringEmpty(contentType) {
|
|
r.header.Set(hdrContentTypeKey, contentType)
|
|
}
|
|
if err == nil {
|
|
r.rawRequest = protocol.NewRequest(r.method, r.url, body)
|
|
if contentType == formContentType && isPayloadSupported(r.method) {
|
|
if r.rawRequest.IsBodyStream() {
|
|
r.rawRequest.ResetBody()
|
|
}
|
|
r.rawRequest.SetMultipartFormData(r.formParam)
|
|
r.rawRequest.SetFiles(r.fileParam)
|
|
}
|
|
for key, values := range r.header {
|
|
for _, val := range values {
|
|
r.rawRequest.Header.Add(key, val)
|
|
}
|
|
}
|
|
r.rawRequest.SetOptions(r.requestOptions...)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func silently(_ ...interface{}) {}
|
|
|
|
// defaultResponseResultDecider method returns true if HTTP status code >= 400 otherwise false.
|
|
func defaultResponseResultDecider(statusCode int, rawResponse *protocol.Response) bool {
|
|
return statusCode > 399
|
|
}
|
|
|
|
// IsJSONType method is to check JSON content type or not
|
|
func isJSONType(ct string) bool {
|
|
return jsonCheck.MatchString(ct)
|
|
}
|
|
|
|
// IsXMLType method is to check XML content type or not
|
|
func isXMLType(ct string) bool {
|
|
return xmlCheck.MatchString(ct)
|
|
}
|
|
|
|
func parseResponseBody(c *cli, res *response) (err error) {
|
|
if res.statusCode() == http.StatusNoContent {
|
|
return
|
|
}
|
|
// Handles only JSON or XML content type
|
|
ct := res.header().Get(hdrContentTypeKey)
|
|
|
|
isError := c.responseResultDecider(res.statusCode(), res.rawResponse)
|
|
if isError {
|
|
if res.request.Error != nil {
|
|
if isJSONType(ct) || isXMLType(ct) {
|
|
err = unmarshalContent(ct, res.bodyByte, res.request.Error)
|
|
}
|
|
} else {
|
|
jsonByte, jsonErr := json.Marshal(map[string]interface{}{
|
|
"status_code": res.rawResponse.StatusCode(),
|
|
"body": string(res.bodyByte),
|
|
})
|
|
if jsonErr != nil {
|
|
return jsonErr
|
|
}
|
|
err = fmt.Errorf(string(jsonByte))
|
|
}
|
|
} else if res.request.result != nil {
|
|
if isJSONType(ct) || isXMLType(ct) {
|
|
err = unmarshalContent(ct, res.bodyByte, res.request.result)
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// unmarshalContent content into object from JSON or XML
|
|
func unmarshalContent(ct string, b []byte, d interface{}) (err error) {
|
|
if isJSONType(ct) {
|
|
err = json.Unmarshal(b, d)
|
|
} else if isXMLType(ct) {
|
|
err = xml.Unmarshal(b, d)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
`
|
|
|
|
var idlClientTpl = `// Code generated by hertz generator.
|
|
|
|
package {{.PackageName}}
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/cloudwego/hertz/pkg/common/config"
|
|
"github.com/cloudwego/hertz/pkg/protocol"
|
|
{{- range $k, $v := .Imports}}
|
|
{{$k}} "{{$v.Package}}"
|
|
{{- end}}
|
|
)
|
|
|
|
// unused protection
|
|
var (
|
|
_ = fmt.Formatter(nil)
|
|
)
|
|
|
|
type Client interface {
|
|
{{range $_, $MethodInfo := .ClientMethods}}
|
|
{{$MethodInfo.Name}}(context context.Context, req *{{$MethodInfo.RequestTypeName}}, reqOpt ...config.RequestOption) (resp *{{$MethodInfo.ReturnTypeName}}, rawResponse *protocol.Response, err error)
|
|
{{end}}
|
|
}
|
|
|
|
type {{.ServiceName}}Client struct {
|
|
client *cli
|
|
}
|
|
|
|
func New{{.ServiceName}}Client(hostUrl string, ops ...Option) (Client, error) {
|
|
opts := getOptions(append(ops, withHostUrl(hostUrl))...)
|
|
cli, err := newClient(opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &{{.ServiceName}}Client{
|
|
client: cli,
|
|
}, nil
|
|
}
|
|
|
|
{{range $_, $MethodInfo := .ClientMethods}}
|
|
func (s *{{$.ServiceName}}Client) {{$MethodInfo.Name}}(context context.Context, req *{{$MethodInfo.RequestTypeName}}, reqOpt ...config.RequestOption) (resp *{{$MethodInfo.ReturnTypeName}}, rawResponse *protocol.Response, err error) {
|
|
httpResp := &{{$MethodInfo.ReturnTypeName}}{}
|
|
ret, err := s.client.r().
|
|
setContext(context).
|
|
setQueryParams(map[string]interface{}{
|
|
{{$MethodInfo.QueryParamsCode}}
|
|
}).
|
|
setPathParams(map[string]string{
|
|
{{$MethodInfo.PathParamsCode}}
|
|
}).
|
|
addHeaders(map[string]string{
|
|
{{$MethodInfo.HeaderParamsCode}}
|
|
}).
|
|
setFormParams(map[string]string{
|
|
{{$MethodInfo.FormValueCode}}
|
|
}).
|
|
setFormFileParams(map[string]string{
|
|
{{$MethodInfo.FormFileCode}}
|
|
}).
|
|
{{$MethodInfo.BodyParamsCode}}
|
|
setRequestOption(reqOpt...).
|
|
setResult(httpResp).
|
|
execute("{{if EqualFold $MethodInfo.HTTPMethod "Any"}}POST{{else}}{{ $MethodInfo.HTTPMethod }}{{end}}", "{{$MethodInfo.Path}}")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
resp = httpResp
|
|
rawResponse = ret.rawResponse
|
|
return resp, rawResponse, nil
|
|
}
|
|
{{end}}
|
|
|
|
var defaultClient, _ = New{{.ServiceName}}Client("{{.BaseDomain}}")
|
|
|
|
func ConfigDefaultClient(ops ...Option) (err error) {
|
|
defaultClient, err = New{{.ServiceName}}Client("{{.BaseDomain}}", ops...)
|
|
return
|
|
}
|
|
|
|
{{range $_, $MethodInfo := .ClientMethods}}
|
|
func {{$MethodInfo.Name}}(context context.Context, req *{{$MethodInfo.RequestTypeName}}, reqOpt ...config.RequestOption) (resp *{{$MethodInfo.ReturnTypeName}}, rawResponse *protocol.Response, err error) {
|
|
return defaultClient.{{$MethodInfo.Name}}(context, req, reqOpt...)
|
|
}
|
|
{{end}}
|
|
`
|