diff --git a/README.md b/README.md index 791326e..61c4977 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,10 @@ # Goppy Microservice Toolkit -[![GoDoc](https://godoc.org/github.com/dewep-online/goppy?status.svg)](https://godoc.org/github.com/dewep-online/goppy) -[![Coverage Status](https://coveralls.io/repos/github/dewep-online/goppy/badge.svg?branch=master)](https://coveralls.io/github/dewep-online/goppy?branch=master) [![Release](https://img.shields.io/github/release/dewep-online/goppy.svg?style=flat-square)](https://github.com/dewep-online/goppy/releases/latest) [![Go Report Card](https://goreportcard.com/badge/github.com/dewep-online/goppy)](https://goreportcard.com/report/github.com/dewep-online/goppy) [![CI](https://github.com/dewep-online/goppy/actions/workflows/ci.yml/badge.svg)](https://github.com/dewep-online/goppy/actions/workflows/ci.yml) [![Codeql](https://github.com/dewep-online/goppy/actions/workflows/codeql.yml/badge.svg)](https://github.com/dewep-online/goppy/actions/workflows/codeql.yml) - -## Installation - -```bash -go get -u github.com/dewep-online/goppy -``` +![GitHub](https://img.shields.io/github/license/dewep-online/goppy) ## Features @@ -23,96 +16,9 @@ go get -u github.com/dewep-online/goppy - Built-in dependency container - Data binding for JSON -## Plugins - -| Plugin |Comment| Import | -|--------------|---|------------------------------------------------------| -| **debug** |profiling application (pprof) with HTTP access.| `http.WithHTTPDebug()` | -| **http** |Out of the box multi-server launch of web servers with separate routing. Grouping of routers with connection to a group of dedicated middleware.| `http.WithHTTP()` | -| **database** |Multi connection pools with MySQL and SQLite databases (with initialization migration setup).| `database.WithMySQL()` `database.WithSQLite()` | -| **geoip** |Definition of geo-IP information.| `geoip.WithMaxMindGeoIP()` + `middlewares.CloudflareMiddleware()` `middlewares.MaxMindMiddleware()` | - - -## Quick Start - -Config: - -```yaml -env: dev -pid: "" -level: 4 -log: /dev/stdout - -debug: - addr: 127.0.0.1:12000 - -http: - main: - addr: 127.0.0.1:8080 -``` - -Code: - -```go -package main - -import ( - "github.com/dewep-online/goppy" - "github.com/dewep-online/goppy/plugins" - "github.com/dewep-online/goppy/plugins/http" - "github.com/dewep-online/goppy/middlewares" -) - -func main() { - - app := goppy.New() - app.WithConfig("./config.yaml") - app.Plugins( - http.WithHTTPDebug(), - http.WithHTTP(), - ) - app.Plugins( - plugins.Plugin{ - Inject: NewController, - Resolve: func(routes *http.RouterPool, c *Controller) { - router := routes.Main() - router.Use(middlewares.ThrottlingMiddleware(100)) - router.Get("/users", c.Users) - - api := router.Collection("/api/v1", middlewares.ThrottlingMiddleware(100)) - api.Get("/user/{id}", c.User) - }, - }, - ) - app.Run() - -} - -type Controller struct{} - -func NewController() *Controller { - return &Controller{} -} - -func (v *Controller) Users(ctx http.Ctx) { - data := []int64{1, 2, 3, 4} - ctx.SetBody().JSON(data) -} - -func (v *Controller) User(ctx http.Ctx) { - id, _ := ctx.Param("id").Int() - - ctx.SetBody().Error(http.ErrMessage{ - HTTPCode: 400, - InternalCode: "x1000", - Message: "user not found", - Ctx: map[string]interface{}{"id": id}, - }) - - ctx.Log().Infof("user - %d", id) -} +## Guide -``` +[![guide](https://img.shields.io/badge/giude-goppy.ru-green)](https://goppy.ru) ## Contribute @@ -131,7 +37,7 @@ func (v *Controller) User(ctx http.Ctx) { ## Community -- [Forum](https://github.com/dewep-online/goppy/discussions) +[![Forum](https://img.shields.io/badge/community-forum-red)](https://github.com/dewep-online/goppy/discussions) ## License diff --git a/config.yaml b/config.yaml new file mode 100755 index 0000000..6ed7823 --- /dev/null +++ b/config.yaml @@ -0,0 +1,43 @@ +env: dev +pid: "" +level: 4 +log: /dev/stdout + +debug: + addr: 127.0.0.1:12000 + read_timeout: 0s + write_timeout: 0s + idle_timeout: 0s + shutdown_timeout: 0s + +http: + main: + addr: 127.0.0.1:8080 + read_timeout: 0s + write_timeout: 0s + idle_timeout: 0s + shutdown_timeout: 0s + +mysql: + - name: main + host: 127.0.0.1 + port: 3306 + schema: test_database + user: test + password: test + maxidleconn: 5 + maxopenconn: 5 + maxconnttl: 50s + interpolateparams: false + timezone: UTC + txisolevel: "" + charset: utf8mb4,utf8 + timeout: 5s + readtimeout: 5s + writetimeout: 5s + +sqlite: + - name: main + file: ./sqlite.db + init_migration: + - ./migration.sql diff --git a/CNAME b/docs/CNAME similarity index 100% rename from CNAME rename to docs/CNAME diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..e3ae67d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,98 @@ +# Goppy Microservice Toolkit + + +[![Release](https://img.shields.io/github/release/dewep-online/goppy.svg?style=flat-square)](https://github.com/dewep-online/goppy/releases/latest) +![GitHub](https://img.shields.io/github/license/dewep-online/goppy) +[![Forum](https://img.shields.io/badge/community-forum-red)](https://github.com/dewep-online/goppy/discussions) + +## Installation + +```bash +go get -u github.com/dewep-online/goppy +``` + +## Features + +- Config auto generation +- Custom pool of HTTP servers with configuration via config +- Group APIs with middleware hanging on each group +- Extensible middleware framework +- Application customization via plugins +- Built-in dependency container +- Data binding for JSON + +## Plugins + +| Plugin |Comment| Import | +|--------------|---|------------------------------------------------------| +| **debug** |profiling application (pprof) with HTTP access.| `http.WithHTTPDebug()` | +| **http** |Out of the box multi-server launch of web servers with separate routing. Grouping of routers with connection to a group of dedicated middleware.| `http.WithHTTP()` | +| **database** |Multi connection pools with MySQL and SQLite databases (with initialization migration setup).| `database.WithMySQL()` `database.WithSQLite()` | +| **geoip** |Definition of geo-IP information.| `geoip.WithMaxMindGeoIP()` + `middlewares.CloudflareMiddleware()` `middlewares.MaxMindMiddleware()` | + + +## Quick Start + +Config: + +```yaml +env: dev +level: 4 +log: /dev/stdout + +http: + main: + addr: 127.0.0.1:8088 +``` + +Code: + +```go +package main + +import ( + "github.com/dewep-online/goppy" + "github.com/dewep-online/goppy/middlewares" + "github.com/dewep-online/goppy/plugins" + "github.com/dewep-online/goppy/plugins/http" +) + +func main() { + app := goppy.New() + app.WithConfig("./config.yaml") + app.Plugins( + http.WithHTTP(), + ) + app.Plugins( + plugins.Plugin{ + Inject: NewController, + Resolve: func(routes http.RouterPool, c *Controller) { + router := routes.Main() + router.Use(middlewares.ThrottlingMiddleware(100)) + router.Get("/users", c.Users) + + api := router.Collection("/api/v1", middlewares.ThrottlingMiddleware(100)) + api.Get("/user/{id}", c.User) + }, + }, + ) + app.Run() +} + +type Controller struct{} + +func NewController() *Controller { + return &Controller{} +} + +func (v *Controller) Users(ctx http.Ctx) { + data := []int64{1, 2, 3, 4} + ctx.SetBody(200).JSON(data) +} + +func (v *Controller) User(ctx http.Ctx) { + id, _ := ctx.Param("id").Int() + ctx.SetBody(200).String("user id: %d", id) + ctx.Log().Infof("user - %d", id) +} +``` diff --git a/_config.yml b/docs/_config.yml similarity index 100% rename from _config.yml rename to docs/_config.yml diff --git a/examples/demo-basic/config.yaml b/examples/demo-basic/config.yaml new file mode 100755 index 0000000..21c860c --- /dev/null +++ b/examples/demo-basic/config.yaml @@ -0,0 +1,7 @@ +env: dev +level: 4 +log: /dev/stdout + +http: + main: + addr: 127.0.0.1:8088 \ No newline at end of file diff --git a/examples/demo-basic/main.go b/examples/demo-basic/main.go new file mode 100644 index 0000000..87743f5 --- /dev/null +++ b/examples/demo-basic/main.go @@ -0,0 +1,47 @@ +package main + +import ( + "github.com/dewep-online/goppy" + "github.com/dewep-online/goppy/middlewares" + "github.com/dewep-online/goppy/plugins" + "github.com/dewep-online/goppy/plugins/http" +) + +func main() { + app := goppy.New() + app.WithConfig("./config.yaml") + app.Plugins( + http.WithHTTP(), + ) + app.Plugins( + plugins.Plugin{ + Inject: NewController, + Resolve: func(routes http.RouterPool, c *Controller) { + router := routes.Main() + router.Use(middlewares.ThrottlingMiddleware(100)) + router.Get("/users", c.Users) + + api := router.Collection("/api/v1", middlewares.ThrottlingMiddleware(100)) + api.Get("/user/{id}", c.User) + }, + }, + ) + app.Run() +} + +type Controller struct{} + +func NewController() *Controller { + return &Controller{} +} + +func (v *Controller) Users(ctx http.Ctx) { + data := []int64{1, 2, 3, 4} + ctx.SetBody(200).JSON(data) +} + +func (v *Controller) User(ctx http.Ctx) { + id, _ := ctx.Param("id").Int() + ctx.SetBody(200).String("user id: %d", id) + ctx.Log().Infof("user - %d", id) +} diff --git a/examples/demo-database/config.yaml b/examples/demo-database/config.yaml index 6ed7823..ee052c3 100755 --- a/examples/demo-database/config.yaml +++ b/examples/demo-database/config.yaml @@ -12,7 +12,7 @@ debug: http: main: - addr: 127.0.0.1:8080 + addr: 127.0.0.1:8088 read_timeout: 0s write_timeout: 0s idle_timeout: 0s diff --git a/examples/demo-database/main.go b/examples/demo-database/main.go index e3e7b1c..ab63f5b 100644 --- a/examples/demo-database/main.go +++ b/examples/demo-database/main.go @@ -1,6 +1,8 @@ package main import ( + "fmt" + "github.com/dewep-online/goppy" "github.com/dewep-online/goppy/middlewares" "github.com/dewep-online/goppy/plugins" @@ -47,7 +49,7 @@ func NewController(v database.MySQL) *Controller { func (v *Controller) Users(ctx http.Ctx) { data := []int64{1, 2, 3, 4} - ctx.SetBody().JSON(data) + ctx.SetBody(200).JSON(data) } func (v *Controller) User(ctx http.Ctx) { @@ -58,12 +60,7 @@ func (v *Controller) User(ctx http.Ctx) { ctx.Log().Errorf("db: %s", err.Error()) } - ctx.SetBody().Error(http.ErrMessage{ - HTTPCode: 400, - InternalCode: "x1000", - Message: "user not found", - Ctx: map[string]interface{}{"id": id}, - }) + ctx.SetBody(400).ErrorJSON(fmt.Errorf("user not found"), "x1000", http.ErrCtx{"id": id}) ctx.Log().Infof("user - %d", id) } diff --git a/examples/demo-geoip/main.go b/examples/demo-geoip/main.go index 1c78227..9ed62cb 100644 --- a/examples/demo-geoip/main.go +++ b/examples/demo-geoip/main.go @@ -35,7 +35,7 @@ func main() { Country: geoip.GetCountryName(ctx), ProxyIPs: geoip.GetProxyIPs(ctx), } - ctx.SetBody().JSON(&m) + ctx.SetBody(200).JSON(&m) }) }, }, diff --git a/plugins/http/router.go b/plugins/http/router.go index daa0bc2..9f1deef 100644 --- a/plugins/http/router.go +++ b/plugins/http/router.go @@ -15,7 +15,6 @@ import ( "github.com/dewep-online/goppy/middlewares" "github.com/deweppro/go-http/pkg/httputil" "github.com/deweppro/go-http/pkg/httputil/dec" - "github.com/deweppro/go-http/pkg/httputil/enc" "github.com/deweppro/go-http/pkg/routes" "github.com/deweppro/go-http/servers" "github.com/deweppro/go-http/servers/web" @@ -39,7 +38,7 @@ type ( GetCookie(key string) *nethttp.Cookie SetCookie(value *nethttp.Cookie) GetBody() BodyReader - SetBody() BodyWriter + SetBody(code int) BodyWriter Context() context.Context Log() logger.LogWriter } @@ -153,43 +152,85 @@ type ( JSON(in interface{}) Stream(in []byte, filename string) Raw(in []byte) - Error(in ErrMessage) + String(b string, args ...interface{}) + ErrorJSON(err error, code string, ctx ErrCtx) + Error(err error) } - //ErrMessage standard error message type //easyjson:json - ErrMessage struct { - HTTPCode int `json:"-"` - InternalCode string `json:"code"` - Message string `json:"msg"` - Ctx map[string]interface{} `json:"ctx,omitempty"` + errMessage struct { + InternalCode string `json:"code,omitempty"` + Message string `json:"msg"` + Ctx ErrCtx `json:"ctx,omitempty"` } + ErrCtx map[string]interface{} + bodyWriter struct { - w nethttp.ResponseWriter + code int + w nethttp.ResponseWriter } ) //Raw recording the response in raw format -func (v *bodyWriter) Raw(in []byte) { enc.Raw(v.w, in) } +func (v *bodyWriter) Raw(b []byte) { + v.w.WriteHeader(v.code) + v.w.Write(b) //nolint: errcheck +} + +//String recording the response in string format +func (v *bodyWriter) String(b string, args ...interface{}) { + v.w.WriteHeader(v.code) + fmt.Fprintf(v.w, b, args...) //nolint: errcheck +} //JSON recording the response in json format -func (v *bodyWriter) JSON(in interface{}) { enc.JSON(v.w, in) } +func (v *bodyWriter) JSON(in interface{}) { + b, err := json.Marshal(in) + if err != nil { + v.code = nethttp.StatusInternalServerError + v.ErrorJSON(err, "x0", nil) + return + } + v.w.Header().Set("Content-Type", "application/json; charset=utf-8") + v.w.WriteHeader(v.code) + v.w.Write(b) //nolint: errcheck +} //Stream sending raw data in response with the definition of the content type by the file name -func (v *bodyWriter) Stream(in []byte, filename string) { enc.Stream(v.w, in, filename) } +func (v *bodyWriter) Stream(in []byte, filename string) { + v.w.Header().Set("Content-Type", "application/octet-stream") + v.w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename)) + v.w.WriteHeader(v.code) + v.w.Write(in) //nolint: errcheck +} -//Error recording an error response -func (v *bodyWriter) Error(in ErrMessage) { - b, _ := json.Marshal(&in) //nolint: errcheck - v.w.Header().Add("Content-Type", "application/json; charset=utf-8") - v.w.WriteHeader(in.HTTPCode) +//ErrorJSON recording an error response +func (v *bodyWriter) ErrorJSON(err error, code string, ctx ErrCtx) { + if err == nil { + err = fmt.Errorf("unknown error") + } + model := errMessage{ + InternalCode: code, + Message: err.Error(), + Ctx: ctx, + } + b, _ := json.Marshal(&model) //nolint: errcheck + v.w.Header().Set("Content-Type", "application/json; charset=utf-8") + v.w.WriteHeader(v.code) v.w.Write(b) //nolint: errcheck } +func (v *bodyWriter) Error(err error) { + if err == nil { + err = fmt.Errorf("unknown error") + } + nethttp.Error(v.w, err.Error(), v.code) +} + //SetBody response body handler -func (v *ctx) SetBody() BodyWriter { - return &bodyWriter{w: v.w} +func (v *ctx) SetBody(code int) BodyWriter { + return &bodyWriter{w: v.w, code: code} } //Context provider the request context diff --git a/plugins/http/router_easyjson.go b/plugins/http/router_easyjson.go index 99232e7..885725c 100644 --- a/plugins/http/router_easyjson.go +++ b/plugins/http/router_easyjson.go @@ -17,7 +17,7 @@ var ( _ easyjson.Marshaler ) -func easyjson124e5e3DecodeGithubComDewepOnlineGoppyPluginsHttp(in *jlexer.Lexer, out *ErrMessage) { +func easyjson124e5e3DecodeGithubComDewepOnlineGoppyPluginsHttp(in *jlexer.Lexer, out *errMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -76,18 +76,13 @@ func easyjson124e5e3DecodeGithubComDewepOnlineGoppyPluginsHttp(in *jlexer.Lexer, in.Consumed() } } -func easyjson124e5e3EncodeGithubComDewepOnlineGoppyPluginsHttp(out *jwriter.Writer, in ErrMessage) { +func easyjson124e5e3EncodeGithubComDewepOnlineGoppyPluginsHttp(out *jwriter.Writer, in errMessage) { out.RawByte('{') first := true _ = first { const prefix string = ",\"code\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } + out.RawString(prefix[1:]) out.String(string(in.InternalCode)) } { @@ -124,25 +119,25 @@ func easyjson124e5e3EncodeGithubComDewepOnlineGoppyPluginsHttp(out *jwriter.Writ } // MarshalJSON supports json.Marshaler interface -func (v ErrMessage) MarshalJSON() ([]byte, error) { +func (v errMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} easyjson124e5e3EncodeGithubComDewepOnlineGoppyPluginsHttp(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface -func (v ErrMessage) MarshalEasyJSON(w *jwriter.Writer) { +func (v errMessage) MarshalEasyJSON(w *jwriter.Writer) { easyjson124e5e3EncodeGithubComDewepOnlineGoppyPluginsHttp(w, v) } // UnmarshalJSON supports json.Unmarshaler interface -func (v *ErrMessage) UnmarshalJSON(data []byte) error { +func (v *errMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} easyjson124e5e3DecodeGithubComDewepOnlineGoppyPluginsHttp(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *ErrMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { +func (v *errMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { easyjson124e5e3DecodeGithubComDewepOnlineGoppyPluginsHttp(l, v) }