From c8a88914f925b8d3b6776aa72aa40d8fb92e8e1b Mon Sep 17 00:00:00 2001 From: zchh < zc123zhangchi@gmail.com> Date: Fri, 8 Jan 2021 22:38:35 +0800 Subject: [PATCH 01/10] error module design --- core/codes/codes.go | 11 +++++++++++ core/error/error.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 core/codes/codes.go create mode 100644 core/error/error.go diff --git a/core/codes/codes.go b/core/codes/codes.go new file mode 100644 index 00000000..9be5bb9c --- /dev/null +++ b/core/codes/codes.go @@ -0,0 +1,11 @@ +package codes + +type Code uint32 + +const ( + SessionSessionStartError Code = 5001001 +) + +var strToCode = map[string]Code{ + `"SESSION_MODULE_SESSION_START_ERROR"`: SessionSessionStartError, +} diff --git a/core/error/error.go b/core/error/error.go new file mode 100644 index 00000000..9e257313 --- /dev/null +++ b/core/error/error.go @@ -0,0 +1,44 @@ +package error + +import "fmt" +import "github.com/beego/beego/v2/core/codes" + +type Code int32 + +type Error struct { + Code Code + Msg string +} + +// New returns a Error representing c and msg. +func New(c Code, msg string) *Error { + return &Error{Code: c, Msg: msg} +} + +// Err returns an error representing c and msg. If c is OK, returns nil. +func Err(c codes.Code, msg string) error { + return New(c, msg) +} + +// Errorf returns Error(c, fmt.Sprintf(format, a...)). +func Errorf(c codes.Code, format string, a ...interface{}) error { + return Err(c, fmt.Sprintf(format, a...)) +} + +func (e *Error) Error() string { + return fmt.Sprintf("beego error: code = %s desc = %s", e.GetCode(), e.GetMessage()) +} + +func (x *Error) GetCode() Code { + if x != nil { + return x.Code + } + return 0 +} + +func (x *Error) GetMessage() string { + if x != nil { + return x.Msg + } + return "" +} \ No newline at end of file From 958f77ab901f9f6274ed2371fa78db8572a54a8f Mon Sep 17 00:00:00 2001 From: zchh < zc123zhangchi@gmail.com> Date: Fri, 8 Jan 2021 23:20:03 +0800 Subject: [PATCH 02/10] error module design bug fix --- core/error/error.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core/error/error.go b/core/error/error.go index 9e257313..f7b3b54d 100644 --- a/core/error/error.go +++ b/core/error/error.go @@ -1,17 +1,17 @@ package error -import "fmt" -import "github.com/beego/beego/v2/core/codes" - -type Code int32 +import ( + "fmt" + "github.com/beego/beego/v2/core/codes" +) type Error struct { - Code Code + Code codes.Code Msg string } // New returns a Error representing c and msg. -func New(c Code, msg string) *Error { +func New(c codes.Code, msg string) *Error { return &Error{Code: c, Msg: msg} } @@ -29,7 +29,7 @@ func (e *Error) Error() string { return fmt.Sprintf("beego error: code = %s desc = %s", e.GetCode(), e.GetMessage()) } -func (x *Error) GetCode() Code { +func (x *Error) GetCode() codes.Code { if x != nil { return x.Code } From 424817e9a170696094e3e52cb1ae931a427c410e Mon Sep 17 00:00:00 2001 From: zchh < zc123zhangchi@gmail.com> Date: Fri, 8 Jan 2021 23:39:46 +0800 Subject: [PATCH 03/10] fix bug --- core/error/error.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/error/error.go b/core/error/error.go index f7b3b54d..1f5506a5 100644 --- a/core/error/error.go +++ b/core/error/error.go @@ -3,6 +3,7 @@ package error import ( "fmt" "github.com/beego/beego/v2/core/codes" + "strconv" ) type Error struct { @@ -26,7 +27,8 @@ func Errorf(c codes.Code, format string, a ...interface{}) error { } func (e *Error) Error() string { - return fmt.Sprintf("beego error: code = %s desc = %s", e.GetCode(), e.GetMessage()) + codeSrt := strconv.FormatUint(uint64(e.GetCode()), 10) + return fmt.Sprintf("beego error: code = %s desc = %s", codeSrt, e.GetMessage()) } func (x *Error) GetCode() codes.Code { From d47a95df8d40218009962240cc8193e399935450 Mon Sep 17 00:00:00 2001 From: zchh < zc123zhangchi@gmail.com> Date: Sun, 17 Jan 2021 22:42:49 +0800 Subject: [PATCH 04/10] =?UTF-8?q?error=E6=A8=A1=E5=9D=97=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/codes/codes.go | 4 +- core/error/error.go | 18 +++-- core/error/error_test.go | 151 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+), 8 deletions(-) create mode 100644 core/error/error_test.go diff --git a/core/codes/codes.go b/core/codes/codes.go index 9be5bb9c..7938fea7 100644 --- a/core/codes/codes.go +++ b/core/codes/codes.go @@ -6,6 +6,6 @@ const ( SessionSessionStartError Code = 5001001 ) -var strToCode = map[string]Code{ - `"SESSION_MODULE_SESSION_START_ERROR"`: SessionSessionStartError, +var CodeToStr = map[Code]string{ + SessionSessionStartError : `"SESSION_MODULE_SESSION_START_ERROR"`, } diff --git a/core/error/error.go b/core/error/error.go index 1f5506a5..50cce244 100644 --- a/core/error/error.go +++ b/core/error/error.go @@ -6,6 +6,9 @@ import ( "strconv" ) +// The `Error`type defines custom error for Beego. It is used by every module +// in Beego. Each `Error` message contains three pieces of data: error code, +// error message. More docs http://beego.me/docs/module/error.md type Error struct { Code codes.Code Msg string @@ -26,21 +29,24 @@ func Errorf(c codes.Code, format string, a ...interface{}) error { return Err(c, fmt.Sprintf(format, a...)) } +// Error returns formatted message for user. func (e *Error) Error() string { codeSrt := strconv.FormatUint(uint64(e.GetCode()), 10) return fmt.Sprintf("beego error: code = %s desc = %s", codeSrt, e.GetMessage()) } -func (x *Error) GetCode() codes.Code { - if x != nil { - return x.Code +// GetCode returns Error's Code +func (e *Error) GetCode() codes.Code { + if e != nil { + return e.Code } return 0 } -func (x *Error) GetMessage() string { - if x != nil { - return x.Msg +// GetMessage returns Error's Msg. +func (e *Error) GetMessage() string { + if e != nil { + return e.Msg } return "" } \ No newline at end of file diff --git a/core/error/error_test.go b/core/error/error_test.go new file mode 100644 index 00000000..30b61164 --- /dev/null +++ b/core/error/error_test.go @@ -0,0 +1,151 @@ +package error + +import ( + "github.com/beego/beego/v2/core/codes" + "reflect" + "testing" +) + +func TestErr(t *testing.T) { + type args struct { + c codes.Code + msg string + } + tests := []struct { + name string + args args + wantErr bool + }{ + // TODO: Add test cases. + {name: "1", args: args{codes.SessionSessionStartError, codes.CodeToStr[codes.SessionSessionStartError]}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := Err(tt.args.c, tt.args.msg); (err != nil) != tt.wantErr { + t.Errorf("Err() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestError_Error(t *testing.T) { + type fields struct { + Code codes.Code + Msg string + } + tests := []struct { + name string + fields fields + want string + }{ + // TODO: Add test cases. + {name: "1", fields: fields{codes.SessionSessionStartError, codes.CodeToStr[codes.SessionSessionStartError]}, want: "beego error: code = 5001001 desc = \"SESSION_MODULE_SESSION_START_ERROR\""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := &Error{ + Code: tt.fields.Code, + Msg: tt.fields.Msg, + } + if got := e.Error(); got != tt.want { + t.Errorf("Error() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestError_GetCode(t *testing.T) { + type fields struct { + Code codes.Code + Msg string + } + tests := []struct { + name string + fields fields + want codes.Code + }{ + // TODO: Add test cases. + {name: "1", fields: fields{codes.SessionSessionStartError, codes.CodeToStr[codes.SessionSessionStartError]}, want: codes.SessionSessionStartError}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := &Error{ + Code: tt.fields.Code, + Msg: tt.fields.Msg, + } + if got := e.GetCode(); got != tt.want { + t.Errorf("GetCode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestError_GetMessage(t *testing.T) { + type fields struct { + Code codes.Code + Msg string + } + tests := []struct { + name string + fields fields + want string + }{ + // TODO: Add test cases. + {name: "1", fields: fields{codes.SessionSessionStartError, codes.CodeToStr[codes.SessionSessionStartError]}, want: codes.CodeToStr[codes.SessionSessionStartError]}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := &Error{ + Code: tt.fields.Code, + Msg: tt.fields.Msg, + } + if got := e.GetMessage(); got != tt.want { + t.Errorf("GetMessage() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestErrorf(t *testing.T) { + type args struct { + c codes.Code + format string + a []interface{} + } + tests := []struct { + name string + args args + wantErr bool + }{ + // TODO: Add test cases. + {name: "1", args: args{codes.SessionSessionStartError, "%s", []interface{}{codes.CodeToStr[codes.SessionSessionStartError]}}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := Errorf(tt.args.c, tt.args.format, tt.args.a...); (err != nil) != tt.wantErr { + t.Errorf("Errorf() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestNew(t *testing.T) { + type args struct { + c codes.Code + msg string + } + tests := []struct { + name string + args args + want *Error + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := New(tt.args.c, tt.args.msg); !reflect.DeepEqual(got, tt.want) { + t.Errorf("New() = %v, want %v", got, tt.want) + } + }) + } +} From 17d6795921c70b1e2707f5e7793a835d73800b47 Mon Sep 17 00:00:00 2001 From: zchh < zc123zhangchi@gmail.com> Date: Mon, 18 Jan 2021 19:56:07 +0800 Subject: [PATCH 05/10] =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/codes/codes.go | 5 ++++- core/error/error.go | 7 ++++--- core/error/error_test.go | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/core/codes/codes.go b/core/codes/codes.go index 7938fea7..77f59220 100644 --- a/core/codes/codes.go +++ b/core/codes/codes.go @@ -1,11 +1,14 @@ package codes +// A Code is an unsigned 32-bit error code as defined in the beego spec. type Code uint32 const ( + // SessionSessionStartError means func SessionStart error in session module. SessionSessionStartError Code = 5001001 ) +// CodeToStr is a map about Code and Code's message var CodeToStr = map[Code]string{ SessionSessionStartError : `"SESSION_MODULE_SESSION_START_ERROR"`, -} +} \ No newline at end of file diff --git a/core/error/error.go b/core/error/error.go index 50cce244..6caf9839 100644 --- a/core/error/error.go +++ b/core/error/error.go @@ -6,9 +6,10 @@ import ( "strconv" ) -// The `Error`type defines custom error for Beego. It is used by every module +// Error type defines custom error for Beego. It is used by every module // in Beego. Each `Error` message contains three pieces of data: error code, -// error message. More docs http://beego.me/docs/module/error.md +// error message. +// More docs http://beego.me/docs/module/error.md. type Error struct { Code codes.Code Msg string @@ -35,7 +36,7 @@ func (e *Error) Error() string { return fmt.Sprintf("beego error: code = %s desc = %s", codeSrt, e.GetMessage()) } -// GetCode returns Error's Code +// GetCode returns Error's Code. func (e *Error) GetCode() codes.Code { if e != nil { return e.Code diff --git a/core/error/error_test.go b/core/error/error_test.go index 30b61164..630b277c 100644 --- a/core/error/error_test.go +++ b/core/error/error_test.go @@ -118,7 +118,7 @@ func TestErrorf(t *testing.T) { wantErr bool }{ // TODO: Add test cases. - {name: "1", args: args{codes.SessionSessionStartError, "%s", []interface{}{codes.CodeToStr[codes.SessionSessionStartError]}}}, + {name: "1", args: args{codes.SessionSessionStartError, "%s", []interface{}{codes.CodeToStr[codes.SessionSessionStartError]}}, wantErr: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -140,6 +140,7 @@ func TestNew(t *testing.T) { want *Error }{ // TODO: Add test cases. + {name: "1", args: args{codes.SessionSessionStartError, codes.CodeToStr[codes.SessionSessionStartError]}, want: &Error{Code:codes.SessionSessionStartError, Msg:codes.CodeToStr[codes.SessionSessionStartError]}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From c8a325efe3abf5c598354cd69299954d1671eaa5 Mon Sep 17 00:00:00 2001 From: Ming Deng Date: Sat, 26 Dec 2020 21:11:04 +0800 Subject: [PATCH 06/10] Remove prometheus duration label --- CHANGELOG.md | 5 +++++ adapter/metric/prometheus.go | 4 ++-- adapter/metric/prometheus_test.go | 2 +- client/httplib/filter/prometheus/filter.go | 4 ++-- client/orm/filter/prometheus/filter.go | 10 +++++----- server/web/filter/prometheus/filter.go | 4 ++-- task/task.go | 3 +++ task/task_test.go | 2 ++ 8 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..9bf94fd1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# developing +- Remove `duration` from prometheus labels. [4391](https://github.com/beego/beego/pull/4391) +- Fix `unknown escape sequence` in generated code. [4385](https://github.com/beego/beego/pull/4385) +- Using fixed name `commentRouter.go` as generated file name. [4385](https://github.com/beego/beego/pull/4385) +- Fix 4383: ORM Adapter produces panic when using orm.RegisterModelWithPrefix. [4386](https://github.com/beego/beego/pull/4386) \ No newline at end of file diff --git a/adapter/metric/prometheus.go b/adapter/metric/prometheus.go index 7abd0e5a..6b276171 100644 --- a/adapter/metric/prometheus.go +++ b/adapter/metric/prometheus.go @@ -38,7 +38,7 @@ func PrometheusMiddleWare(next http.Handler) http.Handler { "appname": web.BConfig.AppName, }, Help: "The statics info for http request", - }, []string{"pattern", "method", "status", "duration"}) + }, []string{"pattern", "method", "status"}) prometheus.MustRegister(summaryVec) @@ -96,5 +96,5 @@ func report(dur time.Duration, writer http.ResponseWriter, q *http.Request, vec logs.Warn("we can not find the router info for this request, so request will be recorded as UNKNOWN: " + q.URL.String()) } ms := dur / time.Millisecond - vec.WithLabelValues(ptn, q.Method, strconv.Itoa(status), strconv.Itoa(int(ms))).Observe(float64(ms)) + vec.WithLabelValues(ptn, q.Method, strconv.Itoa(status)).Observe(float64(ms)) } diff --git a/adapter/metric/prometheus_test.go b/adapter/metric/prometheus_test.go index e87e06d6..53984845 100644 --- a/adapter/metric/prometheus_test.go +++ b/adapter/metric/prometheus_test.go @@ -35,7 +35,7 @@ func TestPrometheusMiddleWare(t *testing.T) { }, Method: "POST", } - vec := prometheus.NewSummaryVec(prometheus.SummaryOpts{}, []string{"pattern", "method", "status", "duration"}) + vec := prometheus.NewSummaryVec(prometheus.SummaryOpts{}, []string{"pattern", "method", "status"}) report(time.Second, writer, request, vec) middleware.ServeHTTP(writer, request) diff --git a/client/httplib/filter/prometheus/filter.go b/client/httplib/filter/prometheus/filter.go index a4de521b..3d5acf12 100644 --- a/client/httplib/filter/prometheus/filter.go +++ b/client/httplib/filter/prometheus/filter.go @@ -43,7 +43,7 @@ func (builder *FilterChainBuilder) FilterChain(next httplib.Filter) httplib.Filt "appname": builder.AppName, }, Help: "The statics info for remote http requests", - }, []string{"proto", "scheme", "method", "host", "path", "status", "duration", "isError"}) + }, []string{"proto", "scheme", "method", "host", "path", "status", "isError"}) return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { startTime := time.Now() @@ -73,5 +73,5 @@ func (builder *FilterChainBuilder) report(startTime time.Time, endTime time.Time dur := int(endTime.Sub(startTime) / time.Millisecond) builder.summaryVec.WithLabelValues(proto, scheme, method, host, path, - strconv.Itoa(status), strconv.Itoa(dur), strconv.FormatBool(err == nil)) + strconv.Itoa(status), strconv.FormatBool(err != nil)).Observe(float64(dur)) } diff --git a/client/orm/filter/prometheus/filter.go b/client/orm/filter/prometheus/filter.go index 1f30f770..db60876e 100644 --- a/client/orm/filter/prometheus/filter.go +++ b/client/orm/filter/prometheus/filter.go @@ -50,7 +50,7 @@ func (builder *FilterChainBuilder) FilterChain(next orm.Filter) orm.Filter { "appname": builder.AppName, }, Help: "The statics info for orm operation", - }, []string{"method", "name", "duration", "insideTx", "txName"}) + }, []string{"method", "name", "insideTx", "txName"}) return func(ctx context.Context, inv *orm.Invocation) []interface{} { startTime := time.Now() @@ -74,12 +74,12 @@ func (builder *FilterChainBuilder) report(ctx context.Context, inv *orm.Invocati builder.reportTxn(ctx, inv) return } - builder.summaryVec.WithLabelValues(inv.Method, inv.GetTableName(), strconv.Itoa(int(dur)), - strconv.FormatBool(inv.InsideTx), inv.TxName) + builder.summaryVec.WithLabelValues(inv.Method, inv.GetTableName(), + strconv.FormatBool(inv.InsideTx), inv.TxName).Observe(float64(dur)) } func (builder *FilterChainBuilder) reportTxn(ctx context.Context, inv *orm.Invocation) { dur := time.Now().Sub(inv.TxStartTime) / time.Millisecond - builder.summaryVec.WithLabelValues(inv.Method, inv.TxName, strconv.Itoa(int(dur)), - strconv.FormatBool(inv.InsideTx), inv.TxName) + builder.summaryVec.WithLabelValues(inv.Method, inv.TxName, + strconv.FormatBool(inv.InsideTx), inv.TxName).Observe(float64(dur)) } diff --git a/server/web/filter/prometheus/filter.go b/server/web/filter/prometheus/filter.go index 5a0db9a7..59a673ac 100644 --- a/server/web/filter/prometheus/filter.go +++ b/server/web/filter/prometheus/filter.go @@ -43,7 +43,7 @@ func (builder *FilterChainBuilder) FilterChain(next web.FilterFunc) web.FilterFu "appname": web.BConfig.AppName, }, Help: "The statics info for http request", - }, []string{"pattern", "method", "status", "duration"}) + }, []string{"pattern", "method", "status"}) prometheus.MustRegister(summaryVec) @@ -83,5 +83,5 @@ func report(dur time.Duration, ctx *context.Context, vec *prometheus.SummaryVec) status := ctx.Output.Status ptn := ctx.Input.GetData("RouterPattern").(string) ms := dur / time.Millisecond - vec.WithLabelValues(ptn, ctx.Input.Method(), strconv.Itoa(status), strconv.Itoa(int(ms))).Observe(float64(ms)) + vec.WithLabelValues(ptn, ctx.Input.Method(), strconv.Itoa(status)).Observe(float64(ms)) } diff --git a/task/task.go b/task/task.go index 00cbbfa7..2ea34f24 100644 --- a/task/task.go +++ b/task/task.go @@ -157,6 +157,9 @@ func (t *Task) GetSpec(context.Context) string { func (t *Task) GetStatus(context.Context) string { var str string for _, v := range t.Errlist { + if v == nil { + continue + } str += v.t.String() + ":" + v.errinfo + "
" } return str diff --git a/task/task_test.go b/task/task_test.go index 2cb807ce..5e117cbd 100644 --- a/task/task_test.go +++ b/task/task_test.go @@ -36,9 +36,11 @@ func TestParse(t *testing.T) { if err != nil { t.Fatal(err) } + assert.Equal(t, "0/30 * * * * *", tk.GetSpec(context.Background())) m.AddTask("taska", tk) m.StartTask() time.Sleep(3 * time.Second) + assert.True(t, len(tk.GetStatus(context.Background())) == 0) m.StopTask() } From 452d20a187193ce17ad33e4245adbf58e2fd07a1 Mon Sep 17 00:00:00 2001 From: Ming Deng Date: Tue, 19 Jan 2021 21:03:38 +0800 Subject: [PATCH 07/10] Fix 4444: panic when 404 not found; Using sync.Onyc to ensure register prometheus ovserver once --- CHANGELOG.md | 3 +- client/httplib/filter/prometheus/filter.go | 31 +++++++------ client/orm/filter/prometheus/filter.go | 32 ++++++++------ client/orm/filter/prometheus/filter_test.go | 2 +- go.mod | 6 +-- go.sum | 8 ++++ server/web/filter/prometheus/filter.go | 48 +++++++++++++++------ server/web/filter/prometheus/filter_test.go | 15 +++++++ 8 files changed, 101 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bf94fd1..2f345209 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,4 +2,5 @@ - Remove `duration` from prometheus labels. [4391](https://github.com/beego/beego/pull/4391) - Fix `unknown escape sequence` in generated code. [4385](https://github.com/beego/beego/pull/4385) - Using fixed name `commentRouter.go` as generated file name. [4385](https://github.com/beego/beego/pull/4385) -- Fix 4383: ORM Adapter produces panic when using orm.RegisterModelWithPrefix. [4386](https://github.com/beego/beego/pull/4386) \ No newline at end of file +- Fix 4383: ORM Adapter produces panic when using orm.RegisterModelWithPrefix. [4386](https://github.com/beego/beego/pull/4386) +- Fix 4444: panic when 404 not found. [4446](https://github.com/beego/beego/pull/4446) \ No newline at end of file diff --git a/client/httplib/filter/prometheus/filter.go b/client/httplib/filter/prometheus/filter.go index 3d5acf12..5761eb7e 100644 --- a/client/httplib/filter/prometheus/filter.go +++ b/client/httplib/filter/prometheus/filter.go @@ -18,6 +18,7 @@ import ( "context" "net/http" "strconv" + "sync" "time" "github.com/prometheus/client_golang/prometheus" @@ -26,24 +27,30 @@ import ( ) type FilterChainBuilder struct { - summaryVec prometheus.ObserverVec AppName string ServerName string RunMode string } +var summaryVec prometheus.ObserverVec +var initSummaryVec sync.Once + func (builder *FilterChainBuilder) FilterChain(next httplib.Filter) httplib.Filter { - builder.summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{ - Name: "beego", - Subsystem: "remote_http_request", - ConstLabels: map[string]string{ - "server": builder.ServerName, - "env": builder.RunMode, - "appname": builder.AppName, - }, - Help: "The statics info for remote http requests", - }, []string{"proto", "scheme", "method", "host", "path", "status", "isError"}) + initSummaryVec.Do(func() { + summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{ + Name: "beego", + Subsystem: "remote_http_request", + ConstLabels: map[string]string{ + "server": builder.ServerName, + "env": builder.RunMode, + "appname": builder.AppName, + }, + Help: "The statics info for remote http requests", + }, []string{"proto", "scheme", "method", "host", "path", "status", "isError"}) + + prometheus.MustRegister(summaryVec) + }) return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { startTime := time.Now() @@ -72,6 +79,6 @@ func (builder *FilterChainBuilder) report(startTime time.Time, endTime time.Time dur := int(endTime.Sub(startTime) / time.Millisecond) - builder.summaryVec.WithLabelValues(proto, scheme, method, host, path, + summaryVec.WithLabelValues(proto, scheme, method, host, path, strconv.Itoa(status), strconv.FormatBool(err != nil)).Observe(float64(dur)) } diff --git a/client/orm/filter/prometheus/filter.go b/client/orm/filter/prometheus/filter.go index db60876e..7563f51e 100644 --- a/client/orm/filter/prometheus/filter.go +++ b/client/orm/filter/prometheus/filter.go @@ -18,6 +18,7 @@ import ( "context" "strconv" "strings" + "sync" "time" "github.com/prometheus/client_golang/prometheus" @@ -33,24 +34,29 @@ import ( // if we want to records the metrics of QuerySetter // actually we only records metrics of invoking "QueryTable" and "QueryTableWithCtx" type FilterChainBuilder struct { - summaryVec prometheus.ObserverVec AppName string ServerName string RunMode string } +var summaryVec prometheus.ObserverVec +var initSummaryVec sync.Once + func (builder *FilterChainBuilder) FilterChain(next orm.Filter) orm.Filter { - builder.summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{ - Name: "beego", - Subsystem: "orm_operation", - ConstLabels: map[string]string{ - "server": builder.ServerName, - "env": builder.RunMode, - "appname": builder.AppName, - }, - Help: "The statics info for orm operation", - }, []string{"method", "name", "insideTx", "txName"}) + initSummaryVec.Do(func() { + summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{ + Name: "beego", + Subsystem: "orm_operation", + ConstLabels: map[string]string{ + "server": builder.ServerName, + "env": builder.RunMode, + "appname": builder.AppName, + }, + Help: "The statics info for orm operation", + }, []string{"method", "name", "insideTx", "txName"}) + prometheus.MustRegister(summaryVec) + }) return func(ctx context.Context, inv *orm.Invocation) []interface{} { startTime := time.Now() @@ -74,12 +80,12 @@ func (builder *FilterChainBuilder) report(ctx context.Context, inv *orm.Invocati builder.reportTxn(ctx, inv) return } - builder.summaryVec.WithLabelValues(inv.Method, inv.GetTableName(), + summaryVec.WithLabelValues(inv.Method, inv.GetTableName(), strconv.FormatBool(inv.InsideTx), inv.TxName).Observe(float64(dur)) } func (builder *FilterChainBuilder) reportTxn(ctx context.Context, inv *orm.Invocation) { dur := time.Now().Sub(inv.TxStartTime) / time.Millisecond - builder.summaryVec.WithLabelValues(inv.Method, inv.TxName, + summaryVec.WithLabelValues(inv.Method, inv.TxName, strconv.FormatBool(inv.InsideTx), inv.TxName).Observe(float64(dur)) } diff --git a/client/orm/filter/prometheus/filter_test.go b/client/orm/filter/prometheus/filter_test.go index 92316557..a25515a7 100644 --- a/client/orm/filter/prometheus/filter_test.go +++ b/client/orm/filter/prometheus/filter_test.go @@ -32,7 +32,7 @@ func TestFilterChainBuilder_FilterChain1(t *testing.T) { builder := &FilterChainBuilder{} filter := builder.FilterChain(next) - assert.NotNil(t, builder.summaryVec) + assert.NotNil(t, summaryVec) assert.NotNil(t, filter) inv := &orm.Invocation{} diff --git a/go.mod b/go.mod index 83516167..89baa406 100644 --- a/go.mod +++ b/go.mod @@ -12,9 +12,9 @@ require ( github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect - github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d - github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808 // indirect - github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a // indirect + github.com/couchbase/go-couchbase v0.0.0-20201216133707-c04035124b17 + github.com/couchbase/gomemcached v0.1.2-0.20201215185628-3bc3f73e68cb // indirect + github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67 // indirect github.com/elastic/go-elasticsearch/v6 v6.8.5 github.com/elazarl/go-bindata-assetfs v1.0.0 github.com/go-kit/kit v0.9.0 diff --git a/go.sum b/go.sum index 0f27f126..7025967c 100644 --- a/go.sum +++ b/go.sum @@ -40,10 +40,16 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbp github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d h1:OMrhQqj1QCyDT2sxHCDjE+k8aMdn2ngTCGG7g4wrdLo= github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U= +github.com/couchbase/go-couchbase v0.0.0-20201216133707-c04035124b17 h1:1ZELwRDUvpBpmgKSIUP6VMW1jIehzD0sCdWxRyejegw= +github.com/couchbase/go-couchbase v0.0.0-20201216133707-c04035124b17/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A= github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808 h1:8s2l8TVUwMXl6tZMe3+hPCRJ25nQXiA3d1x622JtOqc= github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c= +github.com/couchbase/gomemcached v0.1.2-0.20201215185628-3bc3f73e68cb h1:ZCFku0K/3Xvl7rXkGGM+ioT76Rxko8V9wDEWa0GFp14= +github.com/couchbase/gomemcached v0.1.2-0.20201215185628-3bc3f73e68cb/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9HjDvgW9MjCym5Vo= github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a h1:Y5XsLCEhtEI8qbD9RP3Qlv5FXdTDHxZM9UPUnMRgBp8= github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= +github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67 h1:NCqJ6fwen6YP0WlV/IyibaT0kPt3JEI1rA62V/UPKT4= +github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ= github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -257,6 +263,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrS golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -281,6 +288,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kYgpFIzOAfeAAN7m3qQ4= golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/server/web/filter/prometheus/filter.go b/server/web/filter/prometheus/filter.go index 59a673ac..520170e0 100644 --- a/server/web/filter/prometheus/filter.go +++ b/server/web/filter/prometheus/filter.go @@ -17,23 +17,49 @@ package prometheus import ( "strconv" "strings" + "sync" "time" "github.com/prometheus/client_golang/prometheus" "github.com/beego/beego/v2" + "github.com/beego/beego/v2/core/logs" "github.com/beego/beego/v2/server/web" "github.com/beego/beego/v2/server/web/context" ) +const unknownRouterPattern = "UnknownRouterPattern" + // FilterChainBuilder is an extension point, // when we want to support some configuration, // please use this structure type FilterChainBuilder struct { } +var summaryVec prometheus.ObserverVec +var initSummaryVec sync.Once + // FilterChain returns a FilterFunc. The filter will records some metrics func (builder *FilterChainBuilder) FilterChain(next web.FilterFunc) web.FilterFunc { + + initSummaryVec.Do(func() { + summaryVec = builder.buildVec() + err := prometheus.Register(summaryVec) + if _, ok := err.(*prometheus.AlreadyRegisteredError); err != nil && !ok { + logs.Error("web module register prometheus vector failed, %+v", err) + } + registerBuildInfo() + }) + + return func(ctx *context.Context) { + startTime := time.Now() + next(ctx) + endTime := time.Now() + go report(endTime.Sub(startTime), ctx, summaryVec) + } +} + +func (builder *FilterChainBuilder) buildVec() *prometheus.SummaryVec { summaryVec := prometheus.NewSummaryVec(prometheus.SummaryOpts{ Name: "beego", Subsystem: "http_request", @@ -44,17 +70,7 @@ func (builder *FilterChainBuilder) FilterChain(next web.FilterFunc) web.FilterFu }, Help: "The statics info for http request", }, []string{"pattern", "method", "status"}) - - prometheus.MustRegister(summaryVec) - - registerBuildInfo() - - return func(ctx *context.Context) { - startTime := time.Now() - next(ctx) - endTime := time.Now() - go report(endTime.Sub(startTime), ctx, summaryVec) - } + return summaryVec } func registerBuildInfo() { @@ -75,13 +91,17 @@ func registerBuildInfo() { }, }, []string{}) - prometheus.MustRegister(buildInfo) + _ = prometheus.Register(buildInfo) buildInfo.WithLabelValues().Set(1) } -func report(dur time.Duration, ctx *context.Context, vec *prometheus.SummaryVec) { +func report(dur time.Duration, ctx *context.Context, vec prometheus.ObserverVec) { status := ctx.Output.Status - ptn := ctx.Input.GetData("RouterPattern").(string) + ptnItf := ctx.Input.GetData("RouterPattern") + ptn := unknownRouterPattern + if ptnItf != nil { + ptn = ptnItf.(string) + } ms := dur / time.Millisecond vec.WithLabelValues(ptn, ctx.Input.Method(), strconv.Itoa(status)).Observe(float64(ms)) } diff --git a/server/web/filter/prometheus/filter_test.go b/server/web/filter/prometheus/filter_test.go index f00f20e7..8da63df9 100644 --- a/server/web/filter/prometheus/filter_test.go +++ b/server/web/filter/prometheus/filter_test.go @@ -18,6 +18,7 @@ import ( "net/http" "net/http/httptest" "testing" + "time" "github.com/stretchr/testify/assert" @@ -38,3 +39,17 @@ func TestFilterChain(t *testing.T) { filter(ctx) assert.True(t, ctx.Input.GetData("invocation").(bool)) } + +func TestFilterChainBuilder_report(t *testing.T) { + + ctx := context.NewContext() + r, _ := http.NewRequest("GET", "/prometheus/user", nil) + w := httptest.NewRecorder() + ctx.Reset(w, r) + fb := &FilterChainBuilder{} + // without router info + report(time.Second, ctx, fb.buildVec()) + + ctx.Input.SetData("RouterPattern", "my-route") + report(time.Second, ctx, fb.buildVec()) +} \ No newline at end of file From 53f01e50ce1b7871cbeecc6e51233170bc21d63f Mon Sep 17 00:00:00 2001 From: Ming Deng Date: Thu, 21 Jan 2021 23:27:38 +0800 Subject: [PATCH 08/10] Fix 4435: panic when controllers package not found --- CHANGELOG.md | 3 ++- server/web/admin.go | 5 ++++- server/web/hooks.go | 9 ++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f345209..570c98fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,4 +3,5 @@ - Fix `unknown escape sequence` in generated code. [4385](https://github.com/beego/beego/pull/4385) - Using fixed name `commentRouter.go` as generated file name. [4385](https://github.com/beego/beego/pull/4385) - Fix 4383: ORM Adapter produces panic when using orm.RegisterModelWithPrefix. [4386](https://github.com/beego/beego/pull/4386) -- Fix 4444: panic when 404 not found. [4446](https://github.com/beego/beego/pull/4446) \ No newline at end of file +- Fix 4444: panic when 404 not found. [4446](https://github.com/beego/beego/pull/4446) +- Fix 4435: fix panic when controller dir not found. \ No newline at end of file diff --git a/server/web/admin.go b/server/web/admin.go index 89c9ddb9..3f665b09 100644 --- a/server/web/admin.go +++ b/server/web/admin.go @@ -108,8 +108,11 @@ func registerAdmin() error { c := &adminController{ servers: make([]*HttpServer, 0, 2), } + + // copy config to avoid conflict + adminCfg := *BConfig beeAdminApp = &adminApp{ - HttpServer: NewHttpServerWithCfg(BConfig), + HttpServer: NewHttpServerWithCfg(&adminCfg), } // keep in mind that all data should be html escaped to avoid XSS attack beeAdminApp.Router("/", c, "get:AdminIndex") diff --git a/server/web/hooks.go b/server/web/hooks.go index 6a2d725d..438496a0 100644 --- a/server/web/hooks.go +++ b/server/web/hooks.go @@ -6,6 +6,8 @@ import ( "net/http" "path/filepath" + "github.com/coreos/etcd/pkg/fileutil" + "github.com/beego/beego/v2/core/logs" "github.com/beego/beego/v2/server/web/context" "github.com/beego/beego/v2/server/web/session" @@ -98,7 +100,12 @@ func registerGzip() error { func registerCommentRouter() error { if BConfig.RunMode == DEV { - if err := parserPkg(filepath.Join(WorkPath, BConfig.WebConfig.CommentRouterPath)); err != nil { + ctrlDir := filepath.Join(WorkPath, BConfig.WebConfig.CommentRouterPath) + if !fileutil.Exist(ctrlDir) { + logs.Warn("controller package not found, won't generate router: ", ctrlDir) + return nil + } + if err := parserPkg(ctrlDir); err != nil { return err } } From a91a5d01d1672401376ce9c60ac8f51ecbb9a35a Mon Sep 17 00:00:00 2001 From: Ming Deng Date: Fri, 22 Jan 2021 16:15:01 +0800 Subject: [PATCH 09/10] Move codes to error package --- ERROR_SPECIFICATION.md | 1 + client/httplib/error.go | 17 +++++++++++++++++ core/{codes => error}/codes.go | 4 ++-- core/error/error.go | 13 ++++++------- core/error/error_test.go | 27 +++++++++++++-------------- 5 files changed, 39 insertions(+), 23 deletions(-) create mode 100644 ERROR_SPECIFICATION.md create mode 100644 client/httplib/error.go rename core/{codes => error}/codes.go (78%) diff --git a/ERROR_SPECIFICATION.md b/ERROR_SPECIFICATION.md new file mode 100644 index 00000000..cf2f94b2 --- /dev/null +++ b/ERROR_SPECIFICATION.md @@ -0,0 +1 @@ +# Error Module diff --git a/client/httplib/error.go b/client/httplib/error.go new file mode 100644 index 00000000..974fa5bb --- /dev/null +++ b/client/httplib/error.go @@ -0,0 +1,17 @@ +// Copyright 2020 beego +// +// 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 httplib + + diff --git a/core/codes/codes.go b/core/error/codes.go similarity index 78% rename from core/codes/codes.go rename to core/error/codes.go index 77f59220..929714e8 100644 --- a/core/codes/codes.go +++ b/core/error/codes.go @@ -1,4 +1,4 @@ -package codes +package error // A Code is an unsigned 32-bit error code as defined in the beego spec. type Code uint32 @@ -10,5 +10,5 @@ const ( // CodeToStr is a map about Code and Code's message var CodeToStr = map[Code]string{ - SessionSessionStartError : `"SESSION_MODULE_SESSION_START_ERROR"`, + SessionSessionStartError: `"SESSION_MODULE_SESSION_START_ERROR"`, } \ No newline at end of file diff --git a/core/error/error.go b/core/error/error.go index 6caf9839..5844325b 100644 --- a/core/error/error.go +++ b/core/error/error.go @@ -2,7 +2,6 @@ package error import ( "fmt" - "github.com/beego/beego/v2/core/codes" "strconv" ) @@ -11,22 +10,22 @@ import ( // error message. // More docs http://beego.me/docs/module/error.md. type Error struct { - Code codes.Code - Msg string + Code Code + Msg string } // New returns a Error representing c and msg. -func New(c codes.Code, msg string) *Error { +func New(c Code, msg string) *Error { return &Error{Code: c, Msg: msg} } // Err returns an error representing c and msg. If c is OK, returns nil. -func Err(c codes.Code, msg string) error { +func Err(c Code, msg string) error { return New(c, msg) } // Errorf returns Error(c, fmt.Sprintf(format, a...)). -func Errorf(c codes.Code, format string, a ...interface{}) error { +func Errorf(c Code, format string, a ...interface{}) error { return Err(c, fmt.Sprintf(format, a...)) } @@ -37,7 +36,7 @@ func (e *Error) Error() string { } // GetCode returns Error's Code. -func (e *Error) GetCode() codes.Code { +func (e *Error) GetCode() Code { if e != nil { return e.Code } diff --git a/core/error/error_test.go b/core/error/error_test.go index 630b277c..85974623 100644 --- a/core/error/error_test.go +++ b/core/error/error_test.go @@ -1,14 +1,13 @@ package error import ( - "github.com/beego/beego/v2/core/codes" "reflect" "testing" ) func TestErr(t *testing.T) { type args struct { - c codes.Code + c Code msg string } tests := []struct { @@ -17,7 +16,7 @@ func TestErr(t *testing.T) { wantErr bool }{ // TODO: Add test cases. - {name: "1", args: args{codes.SessionSessionStartError, codes.CodeToStr[codes.SessionSessionStartError]}, wantErr: true}, + {name: "1", args: args{SessionSessionStartError, CodeToStr[SessionSessionStartError]}, wantErr: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -30,7 +29,7 @@ func TestErr(t *testing.T) { func TestError_Error(t *testing.T) { type fields struct { - Code codes.Code + Code Code Msg string } tests := []struct { @@ -39,7 +38,7 @@ func TestError_Error(t *testing.T) { want string }{ // TODO: Add test cases. - {name: "1", fields: fields{codes.SessionSessionStartError, codes.CodeToStr[codes.SessionSessionStartError]}, want: "beego error: code = 5001001 desc = \"SESSION_MODULE_SESSION_START_ERROR\""}, + {name: "1", fields: fields{SessionSessionStartError, CodeToStr[SessionSessionStartError]}, want: "beego error: code = 5001001 desc = \"SESSION_MODULE_SESSION_START_ERROR\""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -56,16 +55,16 @@ func TestError_Error(t *testing.T) { func TestError_GetCode(t *testing.T) { type fields struct { - Code codes.Code + Code Code Msg string } tests := []struct { name string fields fields - want codes.Code + want Code }{ // TODO: Add test cases. - {name: "1", fields: fields{codes.SessionSessionStartError, codes.CodeToStr[codes.SessionSessionStartError]}, want: codes.SessionSessionStartError}, + {name: "1", fields: fields{SessionSessionStartError, CodeToStr[SessionSessionStartError]}, want: SessionSessionStartError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -82,7 +81,7 @@ func TestError_GetCode(t *testing.T) { func TestError_GetMessage(t *testing.T) { type fields struct { - Code codes.Code + Code Code Msg string } tests := []struct { @@ -91,7 +90,7 @@ func TestError_GetMessage(t *testing.T) { want string }{ // TODO: Add test cases. - {name: "1", fields: fields{codes.SessionSessionStartError, codes.CodeToStr[codes.SessionSessionStartError]}, want: codes.CodeToStr[codes.SessionSessionStartError]}, + {name: "1", fields: fields{SessionSessionStartError, CodeToStr[SessionSessionStartError]}, want: CodeToStr[SessionSessionStartError]}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -108,7 +107,7 @@ func TestError_GetMessage(t *testing.T) { func TestErrorf(t *testing.T) { type args struct { - c codes.Code + c Code format string a []interface{} } @@ -118,7 +117,7 @@ func TestErrorf(t *testing.T) { wantErr bool }{ // TODO: Add test cases. - {name: "1", args: args{codes.SessionSessionStartError, "%s", []interface{}{codes.CodeToStr[codes.SessionSessionStartError]}}, wantErr: true}, + {name: "1", args: args{SessionSessionStartError, "%s", []interface{}{CodeToStr[SessionSessionStartError]}}, wantErr: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -131,7 +130,7 @@ func TestErrorf(t *testing.T) { func TestNew(t *testing.T) { type args struct { - c codes.Code + c Code msg string } tests := []struct { @@ -140,7 +139,7 @@ func TestNew(t *testing.T) { want *Error }{ // TODO: Add test cases. - {name: "1", args: args{codes.SessionSessionStartError, codes.CodeToStr[codes.SessionSessionStartError]}, want: &Error{Code:codes.SessionSessionStartError, Msg:codes.CodeToStr[codes.SessionSessionStartError]}}, + {name: "1", args: args{SessionSessionStartError, CodeToStr[SessionSessionStartError]}, want: &Error{Code: SessionSessionStartError, Msg: CodeToStr[SessionSessionStartError]}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 3c0dbe29146070ea8d0e588e5ea7de95fe30ce84 Mon Sep 17 00:00:00 2001 From: Ming Deng Date: Sat, 23 Jan 2021 21:11:06 +0800 Subject: [PATCH 10/10] Define error code for httplib --- CHANGELOG.md | 3 +- client/httplib/error_code.go | 131 +++++++++++++++ client/httplib/httplib.go | 218 +++++++++++++++---------- client/httplib/httplib_test.go | 33 ++++ client/httplib/{error.go => module.go} | 4 +- core/berror/codes.go | 91 +++++++++++ core/berror/error.go | 69 ++++++++ core/berror/error_test.go | 77 +++++++++ core/berror/pre_define_code.go | 52 ++++++ core/error/codes.go | 14 -- core/error/error.go | 52 ------ core/error/error_test.go | 151 ----------------- 12 files changed, 590 insertions(+), 305 deletions(-) create mode 100644 client/httplib/error_code.go rename client/httplib/{error.go => module.go} (91%) create mode 100644 core/berror/codes.go create mode 100644 core/berror/error.go create mode 100644 core/berror/error_test.go create mode 100644 core/berror/pre_define_code.go delete mode 100644 core/error/codes.go delete mode 100644 core/error/error.go delete mode 100644 core/error/error_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e2612b4..9235ec94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,4 +11,5 @@ - Support session Filter chain. [4404](https://github.com/beego/beego/pull/4404) - Feature issue #4402 finish router get example. [4416](https://github.com/beego/beego/pull/4416) - Implement context.Context support and deprecate `QueryM2MWithCtx` and `QueryTableWithCtx` [4424](https://github.com/beego/beego/pull/4424) -- Finish timeout option for tasks #4441 [4441](https://github.com/beego/beego/pull/4441) \ No newline at end of file +- Finish timeout option for tasks #4441 [4441](https://github.com/beego/beego/pull/4441) +- Error Module brief design & using httplib module to validate this design. [4453](https://github.com/beego/beego/pull/4453) \ No newline at end of file diff --git a/client/httplib/error_code.go b/client/httplib/error_code.go new file mode 100644 index 00000000..961e7e34 --- /dev/null +++ b/client/httplib/error_code.go @@ -0,0 +1,131 @@ +// Copyright 2020 beego +// +// 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 httplib + +import ( + "github.com/beego/beego/v2/core/berror" +) + +var InvalidUrl = berror.DefineCode(4001001, moduleName, "InvalidUrl", ` +You pass a invalid url to httplib module. Please check your url, be careful about special character. +`) + +var InvalidUrlProtocolVersion = berror.DefineCode(4001002, moduleName, "InvalidUrlProtocolVersion", ` +You pass a invalid protocol version. In practice, we use HTTP/1.0, HTTP/1.1, HTTP/1.2 +But something like HTTP/3.2 is valid for client, and the major version is 3, minor version is 2. +but you must confirm that server support those abnormal protocol version. +`) + +var UnsupportedBodyType = berror.DefineCode(4001003, moduleName, "UnsupportedBodyType", ` +You use a invalid data as request body. +For now, we only support type string and byte[]. +`) + +var InvalidXMLBody = berror.DefineCode(4001004, moduleName, "InvalidXMLBody", ` +You pass invalid data which could not be converted to XML documents. In general, if you pass structure, it works well. +Sometimes you got XML document and you want to make it as request body. So you call XMLBody. +If you do this, you got this code. Instead, you should call Header to set Content-type and call Body to set body data. +`) + +var InvalidYAMLBody = berror.DefineCode(4001005, moduleName, "InvalidYAMLBody", ` +You pass invalid data which could not be converted to YAML documents. In general, if you pass structure, it works well. +Sometimes you got YAML document and you want to make it as request body. So you call YAMLBody. +If you do this, you got this code. Instead, you should call Header to set Content-type and call Body to set body data. +`) + +var InvalidJSONBody = berror.DefineCode(4001006, moduleName, "InvalidJSONBody", ` +You pass invalid data which could not be converted to JSON documents. In general, if you pass structure, it works well. +Sometimes you got JSON document and you want to make it as request body. So you call JSONBody. +If you do this, you got this code. Instead, you should call Header to set Content-type and call Body to set body data. +`) + + +// start with 5 -------------------------------------------------------------------------- + +var CreateFormFileFailed = berror.DefineCode(5001001, moduleName, "CreateFormFileFailed", ` +In normal case than handling files with BeegoRequest, you should not see this error code. +Unexpected EOF, invalid characters, bad file descriptor may cause this error. +`) + +var ReadFileFailed = berror.DefineCode(5001002, moduleName, "ReadFileFailed", ` +There are several cases that cause this error: +1. file not found. Please check the file name; +2. file not found, but file name is correct. If you use relative file path, it's very possible for you to see this code. +make sure that this file is in correct directory which Beego looks for; +3. Beego don't have the privilege to read this file, please change file mode; +`) + +var CopyFileFailed = berror.DefineCode(5001003, moduleName, "CopyFileFailed", ` +When we try to read file content and then copy it to another writer, and failed. +1. Unexpected EOF; +2. Bad file descriptor; +3. Write conflict; + +Please check your file content, and confirm that file is not processed by other process (or by user manually). +`) + +var CloseFileFailed = berror.DefineCode(5001004, moduleName, "CloseFileFailed", ` +After handling files, Beego try to close file but failed. Usually it was caused by bad file descriptor. +`) + +var SendRequestFailed = berror.DefineCode(5001005, moduleName, "SendRequestRetryExhausted", ` +Beego send HTTP request, but it failed. +If you config retry times, it means that Beego had retried and failed. +When you got this error, there are vary kind of reason: +1. Network unstable and timeout. In this case, sometimes server has received the request. +2. Server error. Make sure that server works well. +3. The request is invalid, which means that you pass some invalid parameter. +`) + +var ReadGzipBodyFailed = berror.DefineCode(5001006, moduleName, "BuildGzipReaderFailed", ` +Beego parse gzip-encode body failed. Usually Beego got invalid response. +Please confirm that server returns gzip data. +`) + +var CreateFileIfNotExistFailed = berror.DefineCode(5001007, moduleName, "CreateFileIfNotExist", ` +Beego want to create file if not exist and failed. +In most cases, it means that Beego doesn't have the privilege to create this file. +Please change file mode to ensure that Beego is able to create files on specific directory. +Or you can run Beego with higher authority. +In some cases, you pass invalid filename. Make sure that the file name is valid on your system. +`) + +var UnmarshalJSONResponseToObjectFailed = berror.DefineCode(5001008, moduleName, + "UnmarshalResponseToObjectFailed", ` +Beego trying to unmarshal response's body to structure but failed. +Make sure that: +1. You pass valid structure pointer to the function; +2. The body is valid json document +`) + +var UnmarshalXMLResponseToObjectFailed = berror.DefineCode(5001009, moduleName, + "UnmarshalResponseToObjectFailed", ` +Beego trying to unmarshal response's body to structure but failed. +Make sure that: +1. You pass valid structure pointer to the function; +2. The body is valid XML document +`) + +var UnmarshalYAMLResponseToObjectFailed = berror.DefineCode(5001010, moduleName, + "UnmarshalResponseToObjectFailed", ` +Beego trying to unmarshal response's body to structure but failed. +Make sure that: +1. You pass valid structure pointer to the function; +2. The body is valid YAML document +`) + + + + diff --git a/client/httplib/httplib.go b/client/httplib/httplib.go index f89c6fa2..346de7af 100644 --- a/client/httplib/httplib.go +++ b/client/httplib/httplib.go @@ -40,7 +40,6 @@ import ( "encoding/xml" "io" "io/ioutil" - "log" "mime/multipart" "net" "net/http" @@ -52,8 +51,10 @@ import ( "time" "gopkg.in/yaml.v2" -) + "github.com/beego/beego/v2/core/berror" + "github.com/beego/beego/v2/core/logs" +) // it will be the last filter and execute request.Do var doRequestFilter = func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) { @@ -61,11 +62,14 @@ var doRequestFilter = func(ctx context.Context, req *BeegoHTTPRequest) (*http.Re } // NewBeegoRequest returns *BeegoHttpRequest with specific method +// TODO add error as return value +// I think if we don't return error +// users are hard to check whether we create Beego request successfully func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest { var resp http.Response u, err := url.Parse(rawurl) if err != nil { - log.Println("Httplib:", err) + logs.Error("%+v", berror.Wrapf(err, InvalidUrl, "invalid raw url: %s", rawurl)) } req := http.Request{ URL: u, @@ -110,8 +114,6 @@ func Head(url string) *BeegoHTTPRequest { return NewBeegoRequest(url, "HEAD") } - - // BeegoHTTPRequest provides more useful methods than http.Request for requesting a url. type BeegoHTTPRequest struct { url string @@ -211,7 +213,7 @@ func (b *BeegoHTTPRequest) SetHost(host string) *BeegoHTTPRequest { } // SetProtocolVersion sets the protocol version for incoming requests. -// Client requests always use HTTP/1.1. +// Client requests always use HTTP/1.1 func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest { if len(vers) == 0 { vers = "HTTP/1.1" @@ -222,8 +224,9 @@ func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest { b.req.Proto = vers b.req.ProtoMajor = major b.req.ProtoMinor = minor + return b } - + logs.Error("%+v", berror.Errorf(InvalidUrlProtocolVersion, "invalid protocol: %s", vers)) return b } @@ -291,6 +294,7 @@ func (b *BeegoHTTPRequest) PostFile(formname, filename string) *BeegoHTTPRequest // Body adds request raw body. // Supports string and []byte. +// TODO return error if data is invalid func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest { switch t := data.(type) { case string: @@ -307,6 +311,8 @@ func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest { return ioutil.NopCloser(bf), nil } b.req.ContentLength = int64(len(t)) + default: + logs.Error("%+v", berror.Errorf(UnsupportedBodyType, "unsupported body data type: %s", t)) } return b } @@ -316,7 +322,7 @@ func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) { if b.req.Body == nil && obj != nil { byts, err := xml.Marshal(obj) if err != nil { - return b, err + return b, berror.Wrap(err, InvalidXMLBody, "obj could not be converted to XML data") } b.req.Body = ioutil.NopCloser(bytes.NewReader(byts)) b.req.GetBody = func() (io.ReadCloser, error) { @@ -333,7 +339,7 @@ func (b *BeegoHTTPRequest) YAMLBody(obj interface{}) (*BeegoHTTPRequest, error) if b.req.Body == nil && obj != nil { byts, err := yaml.Marshal(obj) if err != nil { - return b, err + return b, berror.Wrap(err, InvalidYAMLBody, "obj could not be converted to YAML data") } b.req.Body = ioutil.NopCloser(bytes.NewReader(byts)) b.req.ContentLength = int64(len(byts)) @@ -347,7 +353,7 @@ func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) if b.req.Body == nil && obj != nil { byts, err := json.Marshal(obj) if err != nil { - return b, err + return b, berror.Wrap(err, InvalidJSONBody, "obj could not be converted to JSON body") } b.req.Body = ioutil.NopCloser(bytes.NewReader(byts)) b.req.ContentLength = int64(len(byts)) @@ -375,28 +381,15 @@ func (b *BeegoHTTPRequest) buildURL(paramBody string) { bodyWriter := multipart.NewWriter(pw) go func() { for formname, filename := range b.files { - fileWriter, err := bodyWriter.CreateFormFile(formname, filename) - if err != nil { - log.Println("Httplib:", err) - } - fh, err := os.Open(filename) - if err != nil { - log.Println("Httplib:", err) - } - // iocopy - _, err = io.Copy(fileWriter, fh) - fh.Close() - if err != nil { - log.Println("Httplib:", err) - } + b.handleFileToBody(bodyWriter, formname, filename) } for k, v := range b.params { for _, vv := range v { - bodyWriter.WriteField(k, vv) + _ = bodyWriter.WriteField(k, vv) } } - bodyWriter.Close() - pw.Close() + _ = bodyWriter.Close() + _ = pw.Close() }() b.Header("Content-Type", bodyWriter.FormDataContentType()) b.req.Body = ioutil.NopCloser(pr) @@ -412,6 +405,29 @@ func (b *BeegoHTTPRequest) buildURL(paramBody string) { } } +func (b *BeegoHTTPRequest) handleFileToBody(bodyWriter *multipart.Writer, formname string, filename string) { + fileWriter, err := bodyWriter.CreateFormFile(formname, filename) + const errFmt = "Httplib: %+v" + if err != nil { + logs.Error(errFmt, berror.Wrapf(err, CreateFormFileFailed, + "could not create form file, formname: %s, filename: %s", formname, filename)) + } + fh, err := os.Open(filename) + + if err != nil { + logs.Error(errFmt, berror.Wrapf(err, ReadFileFailed, "could not open this file %s", filename)) + } + // iocopy + _, err = io.Copy(fileWriter, fh) + if err != nil { + logs.Error(errFmt, berror.Wrapf(err, CopyFileFailed, "could not copy this file %s", filename)) + } + err = fh.Close() + if err != nil { + logs.Error(errFmt, berror.Wrapf(err, CloseFileFailed, "could not close this file %s", filename)) + } +} + func (b *BeegoHTTPRequest) getResponse() (*http.Response, error) { if b.resp.StatusCode != 0 { return b.resp, nil @@ -440,62 +456,20 @@ func (b *BeegoHTTPRequest) DoRequestWithCtx(ctx context.Context) (resp *http.Res return root(ctx, b) } -func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (resp *http.Response, err error) { - var paramBody string - if len(b.params) > 0 { - var buf bytes.Buffer - for k, v := range b.params { - for _, vv := range v { - buf.WriteString(url.QueryEscape(k)) - buf.WriteByte('=') - buf.WriteString(url.QueryEscape(vv)) - buf.WriteByte('&') - } - } - paramBody = buf.String() - paramBody = paramBody[0 : len(paramBody)-1] - } +func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (*http.Response, error) { + paramBody := b.buildParamBody() b.buildURL(paramBody) urlParsed, err := url.Parse(b.url) if err != nil { - return nil, err + return nil, berror.Wrapf(err, InvalidUrl, "parse url failed, the url is %s", b.url) } b.req.URL = urlParsed - trans := b.setting.Transport + trans := b.buildTrans() - if trans == nil { - // create default transport - trans = &http.Transport{ - TLSClientConfig: b.setting.TLSClientConfig, - Proxy: b.setting.Proxy, - Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout), - MaxIdleConnsPerHost: 100, - } - } else { - // if b.transport is *http.Transport then set the settings. - if t, ok := trans.(*http.Transport); ok { - if t.TLSClientConfig == nil { - t.TLSClientConfig = b.setting.TLSClientConfig - } - if t.Proxy == nil { - t.Proxy = b.setting.Proxy - } - if t.Dial == nil { - t.Dial = TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout) - } - } - } - - var jar http.CookieJar - if b.setting.EnableCookie { - if defaultCookieJar == nil { - createDefaultCookie() - } - jar = defaultCookieJar - } + jar := b.buildCookieJar() client := &http.Client{ Transport: trans, @@ -511,12 +485,16 @@ func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (resp *http.Response, } if b.setting.ShowDebug { - dump, err := httputil.DumpRequest(b.req, b.setting.DumpBody) - if err != nil { - log.Println(err.Error()) + dump, e := httputil.DumpRequest(b.req, b.setting.DumpBody) + if e != nil { + logs.Error("%+v", e) } b.dump = dump } + return b.sendRequest(client) +} + +func (b *BeegoHTTPRequest) sendRequest(client *http.Client) (resp *http.Response, err error) { // retries default value is 0, it will run once. // retries equal to -1, it will run forever until success // retries is setted, it will retries fixed times. @@ -524,11 +502,68 @@ func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (resp *http.Response, for i := 0; b.setting.Retries == -1 || i <= b.setting.Retries; i++ { resp, err = client.Do(b.req) if err == nil { - break + return } time.Sleep(b.setting.RetryDelay) } - return resp, err + return nil, berror.Wrap(err, SendRequestFailed, "sending request fail") +} + +func (b *BeegoHTTPRequest) buildCookieJar() http.CookieJar { + var jar http.CookieJar + if b.setting.EnableCookie { + if defaultCookieJar == nil { + createDefaultCookie() + } + jar = defaultCookieJar + } + return jar +} + +func (b *BeegoHTTPRequest) buildTrans() http.RoundTripper { + trans := b.setting.Transport + + if trans == nil { + // create default transport + trans = &http.Transport{ + TLSClientConfig: b.setting.TLSClientConfig, + Proxy: b.setting.Proxy, + DialContext: TimeoutDialerCtx(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout), + MaxIdleConnsPerHost: 100, + } + } else { + // if b.transport is *http.Transport then set the settings. + if t, ok := trans.(*http.Transport); ok { + if t.TLSClientConfig == nil { + t.TLSClientConfig = b.setting.TLSClientConfig + } + if t.Proxy == nil { + t.Proxy = b.setting.Proxy + } + if t.DialContext == nil { + t.DialContext = TimeoutDialerCtx(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout) + } + } + } + return trans +} + +func (b *BeegoHTTPRequest) buildParamBody() string { + var paramBody string + if len(b.params) > 0 { + var buf bytes.Buffer + for k, v := range b.params { + for _, vv := range v { + buf.WriteString(url.QueryEscape(k)) + buf.WriteByte('=') + buf.WriteString(url.QueryEscape(vv)) + buf.WriteByte('&') + } + } + paramBody = buf.String() + paramBody = paramBody[0 : len(paramBody)-1] + } + return paramBody } // String returns the body string in response. @@ -559,10 +594,10 @@ func (b *BeegoHTTPRequest) Bytes() ([]byte, error) { if b.setting.Gzip && resp.Header.Get("Content-Encoding") == "gzip" { reader, err := gzip.NewReader(resp.Body) if err != nil { - return nil, err + return nil, berror.Wrap(err, ReadGzipBodyFailed, "building gzip reader failed") } b.body, err = ioutil.ReadAll(reader) - return b.body, err + return b.body, berror.Wrap(err, ReadGzipBodyFailed, "reading gzip data failed") } b.body, err = ioutil.ReadAll(resp.Body) return b.body, err @@ -605,7 +640,7 @@ func pathExistAndMkdir(filename string) (err error) { return nil } } - return err + return berror.Wrapf(err, CreateFileIfNotExistFailed, "try to create(if not exist) failed: %s", filename) } // ToJSON returns the map that marshals from the body bytes as json in response. @@ -615,7 +650,8 @@ func (b *BeegoHTTPRequest) ToJSON(v interface{}) error { if err != nil { return err } - return json.Unmarshal(data, v) + return berror.Wrap(json.Unmarshal(data, v), + UnmarshalJSONResponseToObjectFailed, "unmarshal json body to object failed.") } // ToXML returns the map that marshals from the body bytes as xml in response . @@ -625,7 +661,8 @@ func (b *BeegoHTTPRequest) ToXML(v interface{}) error { if err != nil { return err } - return xml.Unmarshal(data, v) + return berror.Wrap(xml.Unmarshal(data, v), + UnmarshalXMLResponseToObjectFailed, "unmarshal xml body to object failed.") } // ToYAML returns the map that marshals from the body bytes as yaml in response . @@ -635,7 +672,8 @@ func (b *BeegoHTTPRequest) ToYAML(v interface{}) error { if err != nil { return err } - return yaml.Unmarshal(data, v) + return berror.Wrap(yaml.Unmarshal(data, v), + UnmarshalYAMLResponseToObjectFailed, "unmarshal yaml body to object failed.") } // Response executes request client gets response manually. @@ -644,8 +682,18 @@ func (b *BeegoHTTPRequest) Response() (*http.Response, error) { } // TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field. +// Deprecated +// we will move this at the end of 2021 +// please use TimeoutDialerCtx func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) { return func(netw, addr string) (net.Conn, error) { + return TimeoutDialerCtx(cTimeout, rwTimeout)(context.Background(), netw, addr) + } +} + +func TimeoutDialerCtx(cTimeout time.Duration, + rwTimeout time.Duration) func(ctx context.Context, net, addr string) (c net.Conn, err error) { + return func(ctx context.Context, netw, addr string) (net.Conn, error) { conn, err := net.DialTimeout(netw, addr, cTimeout) if err != nil { return nil, err diff --git a/client/httplib/httplib_test.go b/client/httplib/httplib_test.go index b8cd1112..36948de7 100644 --- a/client/httplib/httplib_test.go +++ b/client/httplib/httplib_test.go @@ -345,6 +345,29 @@ func TestNewBeegoRequest(t *testing.T) { req := NewBeegoRequest("http://beego.me", "GET") assert.NotNil(t, req) assert.Equal(t, "GET", req.req.Method) + + // invalid case but still go request + req = NewBeegoRequest("httpa\ta://beego.me", "GET") + assert.NotNil(t, req) +} + +func TestBeegoHTTPRequest_SetProtocolVersion(t *testing.T) { + req := NewBeegoRequest("http://beego.me", "GET") + req.SetProtocolVersion("HTTP/3.10") + assert.Equal(t, "HTTP/3.10", req.req.Proto) + assert.Equal(t, 3, req.req.ProtoMajor) + assert.Equal(t, 10, req.req.ProtoMinor) + + req.SetProtocolVersion("") + assert.Equal(t, "HTTP/1.1", req.req.Proto) + assert.Equal(t, 1, req.req.ProtoMajor) + assert.Equal(t, 1, req.req.ProtoMinor) + + // invalid case + req.SetProtocolVersion("HTTP/aaa1.1") + assert.Equal(t, "HTTP/1.1", req.req.Proto) + assert.Equal(t, 1, req.req.ProtoMajor) + assert.Equal(t, 1, req.req.ProtoMinor) } func TestPut(t *testing.T) { @@ -384,6 +407,16 @@ func TestBeegoHTTPRequest_Body(t *testing.T) { req.Body([]byte(body)) assert.Equal(t, int64(len(body)), req.req.ContentLength) assert.NotNil(t, req.req.GetBody) + assert.NotNil(t, req.req.Body) + + body = "hhhh, i am test" + req.Body(body) + assert.Equal(t, int64(len(body)), req.req.ContentLength) + assert.NotNil(t, req.req.GetBody) + assert.NotNil(t, req.req.Body) + + // invalid case + req.Body(13) } diff --git a/client/httplib/error.go b/client/httplib/module.go similarity index 91% rename from client/httplib/error.go rename to client/httplib/module.go index 974fa5bb..8503133c 100644 --- a/client/httplib/error.go +++ b/client/httplib/module.go @@ -1,4 +1,4 @@ -// Copyright 2020 beego +// Copyright 2021 beego // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,4 +14,4 @@ package httplib - +const moduleName = "httplib" diff --git a/core/berror/codes.go b/core/berror/codes.go new file mode 100644 index 00000000..e3a616e8 --- /dev/null +++ b/core/berror/codes.go @@ -0,0 +1,91 @@ +// Copyright 2020 beego +// +// 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 berror + +import ( + "fmt" + "sync" +) + + + +// A Code is an unsigned 32-bit error code as defined in the beego spec. +type Code interface { + Code() uint32 + Module() string + Desc() string + Name() string +} + +var defaultCodeRegistry = &codeRegistry{ + codes: make(map[uint32]*codeDefinition, 127), +} + +// DefineCode defining a new Code +// Before defining a new code, please read Beego specification. +// desc could be markdown doc +func DefineCode(code uint32, module string, name string, desc string) Code { + res := &codeDefinition{ + code: code, + module: module, + desc: desc, + } + defaultCodeRegistry.lock.Lock() + defer defaultCodeRegistry.lock.Unlock() + + if _, ok := defaultCodeRegistry.codes[code]; ok { + panic(fmt.Sprintf("duplicate code, code %d has been registered", code)) + } + defaultCodeRegistry.codes[code] = res + return res +} + +type codeRegistry struct { + lock sync.RWMutex + codes map[uint32]*codeDefinition +} + + +func (cr *codeRegistry) Get(code uint32) (Code, bool) { + cr.lock.RLock() + defer cr.lock.RUnlock() + c, ok := cr.codes[code] + return c, ok +} + +type codeDefinition struct { + code uint32 + module string + desc string + name string +} + + +func (c *codeDefinition) Name() string { + return c.name +} + +func (c *codeDefinition) Code() uint32 { + return c.code +} + +func (c *codeDefinition) Module() string { + return c.module +} + +func (c *codeDefinition) Desc() string { + return c.desc +} + diff --git a/core/berror/error.go b/core/berror/error.go new file mode 100644 index 00000000..ca09798a --- /dev/null +++ b/core/berror/error.go @@ -0,0 +1,69 @@ +// Copyright 2020 beego +// +// 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 berror + +import ( + "fmt" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +// code, msg +const errFmt = "ERROR-%d, %s" + +// Err returns an error representing c and msg. If c is OK, returns nil. +func Error(c Code, msg string) error { + return fmt.Errorf(errFmt, c.Code(), msg) +} + +// Errorf returns error +func Errorf(c Code, format string, a ...interface{}) error { + return Error(c, fmt.Sprintf(format, a...)) +} + +func Wrap(err error, c Code, msg string) error { + if err == nil { + return nil + } + return errors.Wrap(err, fmt.Sprintf(errFmt, c.Code(), msg)) +} + +func Wrapf(err error, c Code, format string, a ...interface{}) error { + return Wrap(err, c, fmt.Sprintf(format, a...)) +} + +// FromError is very simple. It just parse error msg and check whether code has been register +// if code not being register, return unknown +// if err.Error() is not valid beego error code, return unknown +func FromError(err error) (Code, bool) { + msg := err.Error() + codeSeg := strings.SplitN(msg, ",", 2) + if strings.HasPrefix(codeSeg[0], "ERROR-") { + codeStr := strings.SplitN(codeSeg[0], "-", 2) + if len(codeStr) < 2 { + return Unknown, false + } + codeInt, e := strconv.ParseUint(codeStr[1], 10, 32) + if e != nil { + return Unknown, false + } + if code, ok := defaultCodeRegistry.Get(uint32(codeInt)); ok { + return code, true + } + } + return Unknown, false +} diff --git a/core/berror/error_test.go b/core/berror/error_test.go new file mode 100644 index 00000000..7a14d933 --- /dev/null +++ b/core/berror/error_test.go @@ -0,0 +1,77 @@ +// Copyright 2020 beego +// +// 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 berror + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +var testCode1 = DefineCode(1, "unit_test", "TestError", "Hello, test code1") + +var testErr = errors.New("hello, this is error") + +func TestErrorf(t *testing.T) { + msg := Errorf(testCode1, "errorf %s", "aaaa") + assert.NotNil(t, msg) + assert.Equal(t, "ERROR-1, errorf aaaa", msg.Error()) +} + +func TestWrapf(t *testing.T) { + err := Wrapf(testErr, testCode1, "Wrapf %s", "aaaa") + assert.NotNil(t, err) + assert.True(t, errors.Is(err, testErr)) +} + +func TestFromError(t *testing.T) { + err := errors.New("ERROR-1, errorf aaaa") + code, ok := FromError(err) + assert.True(t, ok) + assert.Equal(t, testCode1, code) + assert.Equal(t, "unit_test", code.Module()) + assert.Equal(t, "Hello, test code1", code.Desc()) + + err = errors.New("not beego error") + code, ok = FromError(err) + assert.False(t, ok) + assert.Equal(t, Unknown, code) + + err = errors.New("ERROR-2, not register") + code, ok = FromError(err) + assert.False(t, ok) + assert.Equal(t, Unknown, code) + + err = errors.New("ERROR-aaa, invalid code") + code, ok = FromError(err) + assert.False(t, ok) + assert.Equal(t, Unknown, code) + + err = errors.New("aaaaaaaaaaaaaa") + code, ok = FromError(err) + assert.False(t, ok) + assert.Equal(t, Unknown, code) + + err = errors.New("ERROR-2-3, invalid error") + code, ok = FromError(err) + assert.False(t, ok) + assert.Equal(t, Unknown, code) + + err = errors.New("ERROR, invalid error") + code, ok = FromError(err) + assert.False(t, ok) + assert.Equal(t, Unknown, code) +} diff --git a/core/berror/pre_define_code.go b/core/berror/pre_define_code.go new file mode 100644 index 00000000..01992957 --- /dev/null +++ b/core/berror/pre_define_code.go @@ -0,0 +1,52 @@ +// Copyright 2021 beego +// +// 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 berror + +import ( + "fmt" +) + +// pre define code + +// Unknown indicates got some error which is not defined +var Unknown = DefineCode(5000001, "error", "Unknown",fmt.Sprintf(` +Unknown error code. Usually you will see this code in three cases: +1. You forget to define Code or function DefineCode not being executed; +2. This is not Beego's error but you call FromError(); +3. Beego got unexpected error and don't know how to handle it, and then return Unknown error + +A common practice to DefineCode looks like: +%s + +In this way, you may forget to import this package, and got Unknown error. + +Sometimes, you believe you got Beego error, but actually you don't, and then you call FromError(err) + +`, goCodeBlock(` +import your_package + +func init() { + DefineCode(5100100, "your_module", "detail") + // ... +} +`))) + +func goCodeBlock(code string) string { + return codeBlock("go", code) +} +func codeBlock(lan string, code string) string { + return fmt.Sprintf("```%s\n%s\n```", lan, code) +} + diff --git a/core/error/codes.go b/core/error/codes.go deleted file mode 100644 index 929714e8..00000000 --- a/core/error/codes.go +++ /dev/null @@ -1,14 +0,0 @@ -package error - -// A Code is an unsigned 32-bit error code as defined in the beego spec. -type Code uint32 - -const ( - // SessionSessionStartError means func SessionStart error in session module. - SessionSessionStartError Code = 5001001 -) - -// CodeToStr is a map about Code and Code's message -var CodeToStr = map[Code]string{ - SessionSessionStartError: `"SESSION_MODULE_SESSION_START_ERROR"`, -} \ No newline at end of file diff --git a/core/error/error.go b/core/error/error.go deleted file mode 100644 index 5844325b..00000000 --- a/core/error/error.go +++ /dev/null @@ -1,52 +0,0 @@ -package error - -import ( - "fmt" - "strconv" -) - -// Error type defines custom error for Beego. It is used by every module -// in Beego. Each `Error` message contains three pieces of data: error code, -// error message. -// More docs http://beego.me/docs/module/error.md. -type Error struct { - Code Code - Msg string -} - -// New returns a Error representing c and msg. -func New(c Code, msg string) *Error { - return &Error{Code: c, Msg: msg} -} - -// Err returns an error representing c and msg. If c is OK, returns nil. -func Err(c Code, msg string) error { - return New(c, msg) -} - -// Errorf returns Error(c, fmt.Sprintf(format, a...)). -func Errorf(c Code, format string, a ...interface{}) error { - return Err(c, fmt.Sprintf(format, a...)) -} - -// Error returns formatted message for user. -func (e *Error) Error() string { - codeSrt := strconv.FormatUint(uint64(e.GetCode()), 10) - return fmt.Sprintf("beego error: code = %s desc = %s", codeSrt, e.GetMessage()) -} - -// GetCode returns Error's Code. -func (e *Error) GetCode() Code { - if e != nil { - return e.Code - } - return 0 -} - -// GetMessage returns Error's Msg. -func (e *Error) GetMessage() string { - if e != nil { - return e.Msg - } - return "" -} \ No newline at end of file diff --git a/core/error/error_test.go b/core/error/error_test.go deleted file mode 100644 index 85974623..00000000 --- a/core/error/error_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package error - -import ( - "reflect" - "testing" -) - -func TestErr(t *testing.T) { - type args struct { - c Code - msg string - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - {name: "1", args: args{SessionSessionStartError, CodeToStr[SessionSessionStartError]}, wantErr: true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := Err(tt.args.c, tt.args.msg); (err != nil) != tt.wantErr { - t.Errorf("Err() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestError_Error(t *testing.T) { - type fields struct { - Code Code - Msg string - } - tests := []struct { - name string - fields fields - want string - }{ - // TODO: Add test cases. - {name: "1", fields: fields{SessionSessionStartError, CodeToStr[SessionSessionStartError]}, want: "beego error: code = 5001001 desc = \"SESSION_MODULE_SESSION_START_ERROR\""}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := &Error{ - Code: tt.fields.Code, - Msg: tt.fields.Msg, - } - if got := e.Error(); got != tt.want { - t.Errorf("Error() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestError_GetCode(t *testing.T) { - type fields struct { - Code Code - Msg string - } - tests := []struct { - name string - fields fields - want Code - }{ - // TODO: Add test cases. - {name: "1", fields: fields{SessionSessionStartError, CodeToStr[SessionSessionStartError]}, want: SessionSessionStartError}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := &Error{ - Code: tt.fields.Code, - Msg: tt.fields.Msg, - } - if got := e.GetCode(); got != tt.want { - t.Errorf("GetCode() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestError_GetMessage(t *testing.T) { - type fields struct { - Code Code - Msg string - } - tests := []struct { - name string - fields fields - want string - }{ - // TODO: Add test cases. - {name: "1", fields: fields{SessionSessionStartError, CodeToStr[SessionSessionStartError]}, want: CodeToStr[SessionSessionStartError]}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := &Error{ - Code: tt.fields.Code, - Msg: tt.fields.Msg, - } - if got := e.GetMessage(); got != tt.want { - t.Errorf("GetMessage() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestErrorf(t *testing.T) { - type args struct { - c Code - format string - a []interface{} - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - {name: "1", args: args{SessionSessionStartError, "%s", []interface{}{CodeToStr[SessionSessionStartError]}}, wantErr: true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := Errorf(tt.args.c, tt.args.format, tt.args.a...); (err != nil) != tt.wantErr { - t.Errorf("Errorf() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestNew(t *testing.T) { - type args struct { - c Code - msg string - } - tests := []struct { - name string - args args - want *Error - }{ - // TODO: Add test cases. - {name: "1", args: args{SessionSessionStartError, CodeToStr[SessionSessionStartError]}, want: &Error{Code: SessionSessionStartError, Msg: CodeToStr[SessionSessionStartError]}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := New(tt.args.c, tt.args.msg); !reflect.DeepEqual(got, tt.want) { - t.Errorf("New() = %v, want %v", got, tt.want) - } - }) - } -}