Skip to content

Commit

Permalink
fix: Remove leading underscore (#113)
Browse files Browse the repository at this point in the history
  • Loading branch information
arbourd authored Dec 14, 2022
1 parent afaed52 commit 39117f9
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 29 deletions.
30 changes: 23 additions & 7 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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`
Expand All @@ -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"),
Expand All @@ -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"),
Expand All @@ -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.
Expand All @@ -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"),
Expand All @@ -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)
Expand All @@ -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")
Expand All @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
12 changes: 6 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
22 changes: 12 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
14 changes: 10 additions & 4 deletions vk/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"crypto/tls"
"net/http"
"strings"

"github.com/pkg/errors"
"github.com/sethvargo/go-envconfig"
Expand All @@ -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
Expand Down Expand Up @@ -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))
}
Expand Down
2 changes: 1 addition & 1 deletion vk/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion vk/test/routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 39117f9

Please sign in to comment.