github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/cmd/internal/svc/codegen/httpclientproxy.go (about)

     1  package codegen
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"github.com/unionj-cloud/go-doudou/cmd/internal/astutils"
     7  	v3helper "github.com/unionj-cloud/go-doudou/cmd/internal/openapi/v3"
     8  	"github.com/unionj-cloud/go-doudou/toolkit/copier"
     9  	"github.com/unionj-cloud/go-doudou/toolkit/zlogger"
    10  	"github.com/unionj-cloud/go-doudou/version"
    11  	"go/ast"
    12  	"go/parser"
    13  	"go/token"
    14  	"io/ioutil"
    15  	"os"
    16  	"path/filepath"
    17  	"strings"
    18  	"text/template"
    19  )
    20  
    21  var cpimportTmpl = `
    22  	"context"
    23  	"github.com/pkg/errors"
    24  	"github.com/prometheus/client_golang/prometheus"
    25  	"github.com/rs/zerolog"
    26  	"github.com/unionj-cloud/go-doudou/toolkit/zlogger"
    27  	"github.com/slok/goresilience"
    28  	"github.com/go-resty/resty/v2"
    29  	"github.com/slok/goresilience/circuitbreaker"
    30  	rerrors "github.com/slok/goresilience/errors"
    31  	"github.com/slok/goresilience/metrics"
    32  	"github.com/slok/goresilience/retry"
    33  	"github.com/slok/goresilience/timeout"
    34  	v3 "github.com/unionj-cloud/go-doudou/toolkit/openapi/v3"
    35  	"os"
    36  	"time"
    37  	"{{.VoPackage}}"
    38  `
    39  
    40  var appendTmpl = `
    41  {{- range $m := .Meta.Methods }}
    42  	func (receiver *{{$.SvcName}}ClientProxy) {{$m.Name}}(ctx context.Context, _headers map[string]string, {{- range $i, $p := $m.Params}}
    43  	{{- if ne $p.Type "context.Context" }}
    44  	{{- $p.Name}} {{$p.Type}},
    45  	{{- end }}
    46      {{- end }}) (_resp *resty.Response, {{- range $i, $r := $m.Results}}
    47                       {{- if $i}},{{end}}
    48                       {{- $r.Name}} {{$r.Type}}
    49                       {{- end }}) {
    50  		if _err := receiver.runner.Run(ctx, func(ctx context.Context) error {
    51  			_resp, {{ range $i, $r := $m.Results }}{{- if $i}},{{- end}}{{- $r.Name }}{{- end }} = receiver.client.{{$m.Name}}(
    52  				ctx,
    53  				_headers,
    54  				{{- range $p := $m.Params }}
    55  				{{- if ne $p.Type "context.Context" }}
    56  				{{- if isVarargs $p.Type }}
    57  				{{ $p.Name }}...,
    58  				{{- else }}
    59  				{{ $p.Name }},
    60  				{{- end }}
    61  				{{- end }}
    62  				{{- end }}
    63  			)
    64  			{{- range $r := $m.Results }}
    65  				{{- if eq $r.Type "error" }}
    66  					if {{ $r.Name }} != nil {
    67  						return errors.Wrap({{ $r.Name }}, "call {{$m.Name}} fail")
    68  					}
    69  				{{- end }}
    70  			{{- end }}
    71  			return nil
    72  		}); _err != nil {
    73  			// you can implement your fallback logic here
    74  			if errors.Is(_err, rerrors.ErrCircuitOpen) {
    75  				receiver.logger.Error().Err(_err).Msg("")
    76  			}
    77  			{{- range $r := $m.Results }}
    78  				{{- if eq $r.Type "error" }}
    79  					{{ $r.Name }} = errors.Wrap(_err, "call {{$m.Name}} fail")
    80  				{{- end }}
    81  			{{- end }}
    82  		}
    83  		return
    84  	}
    85  {{- end }}
    86  `
    87  
    88  var baseTmpl = `/**
    89  * Generated by go-doudou {{.Version}}.
    90  * You can edit it as your need.
    91  */
    92  package client
    93  
    94  import ()
    95  
    96  type {{.SvcName}}ClientProxy struct {
    97  	client *{{.SvcName}}Client
    98  	logger zerolog.Logger
    99  	runner goresilience.Runner
   100  }
   101  
   102  ` + appendTmpl + `
   103  
   104  type ProxyOption func(*{{.SvcName}}ClientProxy)
   105  
   106  func WithRunner(runner goresilience.Runner) ProxyOption {
   107  	return func(proxy *{{.SvcName}}ClientProxy) {
   108  		proxy.runner = runner
   109  	}
   110  }
   111  
   112  func WithLogger(logger zerolog.Logger) ProxyOption {
   113  	return func(proxy *{{.SvcName}}ClientProxy) {
   114  		proxy.logger = logger
   115  	}
   116  }
   117  
   118  func New{{.SvcName}}ClientProxy(client *{{.SvcName}}Client, opts ...ProxyOption) *{{.SvcName}}ClientProxy {
   119  	cp := &{{.SvcName}}ClientProxy{
   120  		client: client,
   121  		logger: zlogger.Logger,
   122  	}
   123  
   124  	for _, opt := range opts {
   125  		opt(cp)
   126  	}
   127  
   128  	if cp.runner == nil {
   129  		var mid []goresilience.Middleware
   130  		mid = append(mid, metrics.NewMiddleware("{{.ServicePackage}}_client", metrics.NewPrometheusRecorder(prometheus.DefaultRegisterer)))
   131  		mid = append(mid, circuitbreaker.NewMiddleware(circuitbreaker.Config{
   132  			ErrorPercentThresholdToOpen:        50,
   133  			MinimumRequestToOpen:               6,
   134  			SuccessfulRequiredOnHalfOpen:       1,
   135  			WaitDurationInOpenState:            5 * time.Second,
   136  			MetricsSlidingWindowBucketQuantity: 10,
   137  			MetricsBucketDuration:              1 * time.Second,
   138  		}),
   139  			timeout.NewMiddleware(timeout.Config{
   140  				Timeout: 3 * time.Minute,
   141  			}),
   142  			retry.NewMiddleware(retry.Config{
   143  				Times: 3,
   144  			}))
   145  
   146  		cp.runner = goresilience.RunnerChain(mid...)
   147  	}
   148  
   149  	return cp
   150  }
   151  `
   152  
   153  func unimplementedSvcMethods(meta *astutils.InterfaceMeta, clientfile string) {
   154  	fset := token.NewFileSet()
   155  	root, err := parser.ParseFile(fset, clientfile, nil, parser.ParseComments)
   156  	if err != nil {
   157  		panic(err)
   158  	}
   159  	sc := astutils.NewStructCollector(astutils.ExprString)
   160  	ast.Walk(sc, root)
   161  	if handlers, exists := sc.Methods[meta.Name+"ClientProxy"]; exists {
   162  		var notimplemented []astutils.MethodMeta
   163  		for _, item := range meta.Methods {
   164  			for _, handler := range handlers {
   165  				if item.Name == handler.Name {
   166  					goto L
   167  				}
   168  			}
   169  			notimplemented = append(notimplemented, item)
   170  
   171  		L:
   172  		}
   173  
   174  		meta.Methods = notimplemented
   175  	}
   176  }
   177  
   178  // GenGoClientProxy wraps client with resiliency features
   179  func GenGoClientProxy(dir string, ic astutils.InterfaceCollector) {
   180  	var (
   181  		err             error
   182  		clientfile      string
   183  		f               *os.File
   184  		tpl             *template.Template
   185  		buf             bytes.Buffer
   186  		clientDir       string
   187  		fi              os.FileInfo
   188  		modfile         string
   189  		modName         string
   190  		firstLine       string
   191  		modf            *os.File
   192  		meta            astutils.InterfaceMeta
   193  		clientProxyTmpl string
   194  		importBuf       bytes.Buffer
   195  	)
   196  	clientDir = filepath.Join(dir, "client")
   197  	if err = os.MkdirAll(clientDir, os.ModePerm); err != nil {
   198  		panic(err)
   199  	}
   200  
   201  	clientfile = filepath.Join(clientDir, "clientproxy.go")
   202  	fi, err = os.Stat(clientfile)
   203  	if err != nil && !os.IsNotExist(err) {
   204  		panic(err)
   205  	}
   206  	err = copier.DeepCopy(ic.Interfaces[0], &meta)
   207  	if err != nil {
   208  		panic(err)
   209  	}
   210  	if fi != nil {
   211  		zlogger.Warn().Msg("New content will be append to clientproxy.go file")
   212  		if f, err = os.OpenFile(clientfile, os.O_APPEND, os.ModePerm); err != nil {
   213  			panic(err)
   214  		}
   215  		defer f.Close()
   216  		clientProxyTmpl = appendTmpl
   217  
   218  		unimplementedSvcMethods(&meta, clientfile)
   219  	} else {
   220  		if f, err = os.Create(clientfile); err != nil {
   221  			panic(err)
   222  		}
   223  		defer f.Close()
   224  		clientProxyTmpl = baseTmpl
   225  	}
   226  
   227  	modfile = filepath.Join(dir, "go.mod")
   228  	if modf, err = os.Open(modfile); err != nil {
   229  		panic(err)
   230  	}
   231  	reader := bufio.NewReader(modf)
   232  	firstLine, _ = reader.ReadString('\n')
   233  	modName = strings.TrimSpace(strings.TrimPrefix(firstLine, "module"))
   234  
   235  	funcMap := make(map[string]interface{})
   236  	funcMap["isVarargs"] = v3helper.IsVarargs
   237  	if tpl, err = template.New("clientproxy.go.tmpl").Funcs(funcMap).Parse(clientProxyTmpl); err != nil {
   238  		panic(err)
   239  	}
   240  	if err = tpl.Execute(&buf, struct {
   241  		VoPackage      string
   242  		Meta           astutils.InterfaceMeta
   243  		ServicePackage string
   244  		ServiceAlias   string
   245  		SvcName        string
   246  		Version        string
   247  	}{
   248  		VoPackage:      modName + "/vo",
   249  		Meta:           meta,
   250  		ServicePackage: modName,
   251  		ServiceAlias:   ic.Package.Name,
   252  		SvcName:        ic.Interfaces[0].Name,
   253  		Version:        version.Release,
   254  	}); err != nil {
   255  		panic(err)
   256  	}
   257  
   258  	original, err := ioutil.ReadAll(f)
   259  	if err != nil {
   260  		panic(err)
   261  	}
   262  
   263  	original = append(original, buf.Bytes()...)
   264  	if tpl, err = template.New("cpimportimpl.go.tmpl").Parse(cpimportTmpl); err != nil {
   265  		panic(err)
   266  	}
   267  	if err = tpl.Execute(&importBuf, struct {
   268  		ConfigPackage string
   269  		VoPackage     string
   270  	}{
   271  		VoPackage:     modName + "/vo",
   272  		ConfigPackage: modName + "/config",
   273  	}); err != nil {
   274  		panic(err)
   275  	}
   276  	original = astutils.AppendImportStatements(original, importBuf.Bytes())
   277  	astutils.FixImport(original, clientfile)
   278  }