From 39117f94686dbcb4f7aa36a5c721531a758264d8 Mon Sep 17 00:00:00 2001 From: Dylan Arbour Date: Wed, 14 Dec 2022 10:16:11 -0500 Subject: [PATCH] fix: Remove leading underscore (#113) --- docs/README.md | 30 +++++++++++++++++++++++------- go.mod | 12 ++++++------ go.sum | 22 ++++++++++++---------- vk/options.go | 14 ++++++++++---- vk/server.go | 2 +- vk/test/routes_test.go | 2 +- 6 files changed, 53 insertions(+), 29 deletions(-) diff --git a/docs/README.md b/docs/README.md index 48638c2..ac4f59e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,6 +3,7 @@ # The Vektor Guide 🗺 Vektor's goal is to help you develop web services faster. Vektor handles much of the boilerplate needed to start building a Go server, so you can serve a request in less than 10 lines of code: + ```golang import "github.com/suborbital/vektor/vk" @@ -18,6 +19,7 @@ func HandlePing(r *http.Request, ctx *vk.Ctx) (interface{}, error) { return "pong", nil } ``` + Those are the basics, but Vektor is capable of scaling up to serve powerful production workloads, using its full suite of API-oriented features. # Set up `vk` @@ -27,6 +29,7 @@ Those are the basics, but Vektor is capable of scaling up to serve powerful prod The `vk.Server` type contains everything needed to build a web service. It includes the router, a middleware system, customizable plug-in points, and handy built-in components like LetsEncrypt support and CORS handlers. Creating a server object is done with `vk.New()` and accepts an optional list of `OptionModifiers` which allow customization of the server: + ```golang server := vk.New( vk.UseAppName("Vektor API Server"), @@ -35,6 +38,7 @@ server := vk.New( ``` To create a server object without TLS support, omit the `vk.UseDomain()` modifier and specify an HTTP port to listen on. + ```golang server := vk.New( vk.UseAppName("Vektor API HTTP-only"), @@ -45,25 +49,27 @@ server := vk.New( The included `OptionsModifiers` are: Option | Description | ENV key ---- | --- | --- +------ | ----------- | ------- UseDomain(domain string) | Enable LetsEncrypt support with the provided domain name (will serve on :80 and :443 for challenge server and API server). LetsEncrypt is disabled by default. | `VK_DOMAIN` UseTLSConfig(config *tls.Config) | Enable TLS and use the provided TLS config to serve HTTPS. This will override the `domain` option. | N/A UseTLSPort(port int) | Choose an HTTPS port on which to serve requests. | `VK_TLS_PORT` UseHTTPPort(port int) | Choose an HTTP port on which to serve requests. When using TLS, the LetsEncrypt challenge server will run on the configured HTTP port. | `VK_HTTP_PORT` UseAppName(name string) | When the application starts, `name` will be logged. Empty by default. | `VK_APP_NAME` -UseEnvPrefix(prefix string) | Use `prefix` instead of `VK` for environment variables, for example `APP_HTTP_PORT` instead of `VK_HTTP_PORT`. | N/A -UseLogger(logger *vlog.Logger) | Set the logger object to be used. The logger is used internally by `vk` and is available to all handler functions via the `ctx` object. If this option is not passed, `vlog.Default` is used, and its environment variable prefix set to the same as vk's. (`VK` by default). | N/A +UseEnvPrefix(prefix string) | Use `prefix` instead of `VK_` for environment variables, for example `APP_HTTP_PORT` instead of `VK_HTTP_PORT`. | N/A +UseLogger(logger *vlog.Logger) | Set the logger object to be used. The logger is used internally by `vk` and is available to all handler functions via the `ctx` object. If this option is not passed, `vlog.Default` is used, and its environment variable prefix set to the same as vk's. (`VK_` by default). | N/A Each of the options can be set using the modifier function, or by setting the associated environment variable. The environment variable will override the modifier function. -> Note the use of `UseEnvPrefix` if you would prefer to use something other than `VK` for your environment variables! +> Note the use of `UseEnvPrefix` if you would prefer to use something other than `VK_` for your environment variables! ## Handler functions `vk`'s handler function definition is: + ```golang func HandlePing(r *http.Request, ctx *vk.Ctx) (interface{}, error) ``` + Here's a breakdown of each part: `r *http.Request`: The request object for the request being handled. @@ -72,10 +78,10 @@ Here's a breakdown of each part: `(interface{}, error)`: The return types of the handler allow you to respond to HTTP requests by simply returning values. If an error is returned, `vk` will interpret it as a failed request and respond with an error code, if error is `nil`, then the `interface{}` value is used to respond based on the response handling rules. **Responding to requests is handled in depth below in [Responding to requests](#responding-to-requests)** - ## Mounting routes To define routes for your `vk` server, use the HTTP method functions on the server object: + ```golang server := vk.New( vk.UseAppName("Vektor API Server"), @@ -92,15 +98,18 @@ If you prefer to pass the HTTP method as an argument, use `server.Handle()` inst ## Route groups `vk` allows grouping routes by a common path prefix. For example, if you want a group of routes to begin with the `/api/` path, you can create an API route group and then mount all of your handlers to that group. + ```golang apiGroup := vk.Group("/api") apiGroup.GET("/events", HandleGetEvents) server.AddGroup(apiGroup) ``` + Calling `AddGroup` will calculate the full paths for all routes and mount them to the server. In the example above, the handler would be mounted at `/api/events`. Groups can even be added to groups! + ```golang v1 := vk.Group("/v1") v1.GET("/events", HandleEventsV1) @@ -114,12 +123,13 @@ apiGroup.AddGroup(v2) server.AddGroup(api) ``` -This will create a natural grouping of your routes, with the above example creating the `/api/v1/events` and `/api/v2/events` routes. +This will create a natural grouping of your routes, with the above example creating the `/api/v1/events` and `/api/v2/events` routes. ## Middleware and Afterware Groups become even more powerful when combined with Middleware and Afterware. Middleware are pseudo request handlers that run in sequence before the mounted `vk.HandlerFunc` is run. Middleware functions can modify a request and its context, or they can return an error, which causes the request handling to be terminated immediately. Two examples: + ```golang func headerMiddleware(r *http.Request, ctx *vk.Ctx) error { ctx.Headers.Set("X-Vektor-Test", "foobar") @@ -137,13 +147,16 @@ func denyMiddleware(r *http.Request, ctx *vk.Ctx) error { return nil } ``` + Middleware have a similar function signature to `vk.HandlerFunc`, but only return an error. The first example modifies the request context to add a response header. The second example detects a hacker and returns an error, which is handled exactly like any other error response (see below). Returning an error from a Middleware prevents the request from ever reaching the registered handler. Middleware are applied to route groups with the `Before` method: + ```golang v1 := vk.Group("/v1").Before(vk.ContentTypeMiddleware("application/json"), denyMiddleware, headerMiddleware) v1.GET("/events", HandleEventsV1) ``` + This example shows a group created with three middleware. The first adds the `Content-Type` response header (and is included with `vk`), the second and third are the examples from above. When the group is mounted to the server, the chain of middleware are put in place, and are run before the registered handler. When groups are nested, the middleware from the parent group are run before the middleware of any child groups. In the example of nested groups above, any middleware set on the `apiGroup` groups would run before any middleware set on the `v1` or `v2` groups. Afterware is similar, but is run _after_ the request handler. Who knew! Afterware cannot modify response body or status code, but can modify response headers using the `ctx` object. Afterware will **always run**, even if something earlier in the request chain fails. Here's an example: @@ -159,7 +172,6 @@ v2.GET("/events", HandleEventsV2) Middleware and Afterware in `vk` is designed to be easily composable, creating chains of behaviour easily grouped to sets of routes. Middleware can also help increase security of applications, allowing authentication, request throttling, active defence, etc, to run before the registered handler and keeping sensitive code from even being reached in the case of an unauthorized request. - # Responding to requests ## Response types @@ -190,6 +202,7 @@ func HandleDelete(r *http.Request, ctx *vk.Ctx) (interface{}, error) { return nil, vk.Err(http.StatusConflict, "the user is already deleted") // responds with HTTP status 409 and body {"status": 409, "message": "the user is already deleted"} } ``` + `vk.Respond` and `vk.Err` can be used with their shortcuts `vk.R` and `vk.E` if you like your code to be terse. ## Response handling rules @@ -222,6 +235,7 @@ Handler returns... | Status Code | Response body | Content-Type `vk.Error` is an interface that can be used to control the behaviour of error responses. `vk.ErrorResponse` is a concrete type that implements `vk.Error`. Any errors that do NOT implement `vk.Error` will be treated as potentially unsafe, and their contents will be logged but not returned to the caller. Use `vk.Wrap(...)` if you'd like to wrap an `error` in `vk.ErrorResponse`. `vk.Err` returns a `vk.Error`. `vk.Error` looks like this: + ```golang type Error interface { Error() string // this ensures all Errors will also conform to the normal error interface @@ -245,9 +259,11 @@ Handler returns... | Status Code | Response body | Content-Type `return nil, vk.Wrap(http.StatusApplicationError, err)` | 434 Application Error | `{"status": 434, "message": err.Error()}` | `application/json` ## Standard http.HandlerFunc + `vk` can use standard `http.HandlerFunc` handlers by mounting them with `server.HandleHTTP`. This is useful for mounting handler functions provided by third party libraries (such as Prometheus), but they are not able to take advantage of many `vk` features such as middleware or route groups currently. ## The Ctx Object + Each request handler is passed a `vk.Ctx` object, which is a context object for the request. It is similar to the `context.Context` type (and uses one under the hood), but `Ctx` has been augmented for use in web service development. `Ctx` includes a standard Go `context.Context` which can be used as a pseudo key/value store using `ctx.Set()` and `ctx.Get()`. This allows passing things into request handlers such as database connections or other persistent objects. Middleware and Afterware can access the `Ctx` to modify it, or access data from it. diff --git a/go.mod b/go.mod index 5744beb..84eebb1 100644 --- a/go.mod +++ b/go.mod @@ -7,16 +7,16 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/julienschmidt/httprouter v1.3.0 github.com/pkg/errors v0.9.1 - github.com/sethvargo/go-envconfig v0.8.2 - github.com/stretchr/testify v1.8.0 - golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be + github.com/sethvargo/go-envconfig v0.8.3 + github.com/stretchr/testify v1.8.1 + golang.org/x/crypto v0.4.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/objx v0.4.0 // indirect - golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect - golang.org/x/text v0.3.7 // indirect + github.com/stretchr/objx v0.5.0 // indirect + golang.org/x/net v0.3.0 // indirect + golang.org/x/text v0.5.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 259e3c9..251fb75 100644 --- a/go.sum +++ b/go.sum @@ -12,20 +12,22 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sethvargo/go-envconfig v0.8.2 h1:DDUVuG21RMgeB/bn4leclUI/837y6cQCD4w8hb5797k= -github.com/sethvargo/go-envconfig v0.8.2/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0= +github.com/sethvargo/go-envconfig v0.8.3 h1:dXyUrDCJvCm3ybP7yNpiux93qoSORvuH23bdsgFfiJ0= +github.com/sethvargo/go-envconfig v0.8.3/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A= -golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= +golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= +golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vk/options.go b/vk/options.go index c43cde4..020a0a5 100644 --- a/vk/options.go +++ b/vk/options.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "net/http" + "strings" "github.com/pkg/errors" "github.com/sethvargo/go-envconfig" @@ -17,10 +18,10 @@ type RouterWrapper func(handler http.Handler) http.Handler // Options are the available options for Server type Options struct { - AppName string `env:"_APP_NAME"` - Domain string `env:"_DOMAIN"` - HTTPPort int `env:"_HTTP_PORT"` - TLSPort int `env:"_TLS_PORT"` + AppName string `env:"APP_NAME"` + Domain string `env:"DOMAIN"` + HTTPPort int `env:"HTTP_PORT"` + TLSPort int `env:"TLS_PORT"` TLSConfig *tls.Config EnvPrefix string QuietRoutes []string @@ -75,6 +76,11 @@ func (o *Options) ShouldUseHTTP() bool { // finalize "locks in" the options by overriding any existing options with the version from the environment, and setting the default logger if needed func (o *Options) finalize(prefix string) { + // Append trailing _ if prefix is missing one + if !strings.HasSuffix(prefix, "_") { + prefix = prefix + "_" + } + if o.Logger == nil { o.Logger = vlog.Default(vlog.EnvPrefix(prefix)) } diff --git a/vk/server.go b/vk/server.go index d1b9c78..b551a0c 100644 --- a/vk/server.go +++ b/vk/server.go @@ -12,7 +12,7 @@ import ( "golang.org/x/crypto/acme/autocert" ) -const defaultEnvPrefix = "VK" +const defaultEnvPrefix = "VK_" // Server represents a vektor API server type Server struct { diff --git a/vk/test/routes_test.go b/vk/test/routes_test.go index f5537da..29c8d04 100644 --- a/vk/test/routes_test.go +++ b/vk/test/routes_test.go @@ -32,7 +32,7 @@ func (vts *VektorSuite) SetupTest() { server := vk.New( vk.UseLogger(logger), vk.UseAppName("vk tester"), - vk.UseEnvPrefix("APP_"), + vk.UseEnvPrefix("APP"), ) test.AddRoutes(server)