WebGo v2.5.1
WebGo is a minimalistic framework for Go to build web applications (server side). Unlike full-fledged frameworks out there, it tries to get out of the developers' way as soon as possible. It has always been and will always be Go standard library compliant. With the HTTP handlers having the same signature as http.HandlerFunc.
WebGo provides the following features/capabilities
- Multiplexer
- Handler chaining
- Middleware
- Helper functions
- HTTPS ready
- Graceful shutdown
- Logging
- Sample
Multiplexer
The multiplexer/router is one of the most important component of a web application. It helps identifying HTTP requests and pass them on to respective handlers. A handler is uniquely identified using a URI. WebGo supports defining URIs with the following patterns
/api/users
- URI pattern with no variables
/api/users/:userID
- URI pattern with variable
userID
(named URI parameter) - This will not match
/api/users/johndoe/account
. It only matches till /api/users/johndoe/
- If TrailingSlash is set to true, refer to sample
/api/users/:misc*
- Named URI variable
misc
- This matches everything after
/api/users
If multiple patterns match the same URI, the first matching handler would be executed. Refer to the sample to see how routes are configured. A WebGo Route is defined as following:
webgo.Route{
Name string
Method string
Pattern string
TrailingSlash bool
FallThroughPostResponse bool
Handlers []http.HandlerFunc
}
You can access the URI named parameters using the Context
function.
func helloWorld(w http.ResponseWriter, r *http.Request) {
wctx := webgo.Context(r)
params := wctx.Params
route := wctx.Route
webgo.R200(
w,
fmt.Sprintf(
"Route name: '%s', params: '%s'",
route.Name,
params,
),
)
}
Handler chaining
Handler chaining lets you execute multiple handlers for a given route. Execution of a chain can be configured to run even after a previous handler has written a response to the http response. This is made possible by setting FallThroughPostResponse
to true
(refer sample).
webgo.Route{
Name: "chained",
Method: http.MethodGet,
Pattern: "/api",
TrailingSlash: false,
FallThroughPostResponse: true,
Handlers []http.HandlerFunc{
handler1,
handler2,
.
.
.
}
}
Middleware
WebGo middleware lets you wrap all the routes with a middleware. Unlike handler chaining, applies to the whole router. All middlewares should be of type Middlware. The router exposes a method Use to add a Middleware the to the router. Following code shows how a middleware can be used in WebGo.
import (
"github.com/bnkamalesh/webgo"
"github.com/bnkamalesh/webgo/middleware"
)
func routes() []*webgo.Route {
return []*webgo.Route{
&webo.Route{
Name: "home",
Pattern: "/",
Handlers: []http.HandlerFunc{
func(w http.ResponseWriter, r *http.Request) {
webgo.R200(w, "home")
}
},
},
}
}
func main() {
router := webgo.NewRouter(*webgo.Config, routes())
router.Use(middleware.AccessLog)
router.Start()
}
Any number of middleware can be added to the router, the order of execution of middleware would be LIFO (Last In First Out). i.e. in case of the following code
func main() {
router.Use(middleware.AccessLog)
router.Use(middleware.CorsWrap())
}
CorsWrap would be executed first, followed by AccessLog.
Helper functions
WebGo provides a few helper functions.
- SendHeader(w http.ResponseWriter, rCode int) - Send only an HTTP response header with the provided response code.
- Send(w http.ResponseWriter, contentType string, data interface{}, rCode int) - Send any response as is, with the provided content type and response code
- SendResponse(w http.ResponseWriter, data interface{}, rCode int) - Send a JSON response wrapped in WebGo's default response struct.
- SendError(w http.ResponseWriter, data interface{}, rCode int) - Send a JSON response wrapped in WebGo's default error response struct
- Render(w http.ResponseWriter, data interface{}, rCode int, tpl *template.Template) - Render renders a Go template, with the provided data & response code.
Few more helper functions are available, you can check them here.
When using Send
or SendResponse
, the response is wrapped in WebGo's response struct and is sent as JSON.
{
"data": "<any valid JSON payload>",
"status": "<HTTP status code, of type integer>"
}
When using SendError
, the response is wrapped in WebGo's error response struct and is sent as JSON.
{
"errors": "<any valid JSON payload>",
"status": "<HTTP status code, of type integer>"
}
HTTPS ready
HTTPS server can be started easily, by providing the key & cert file. You can also have both HTTP & HTTPS servers running side by side.
Start HTTPS server
cfg := &webgo.Config{
Port: "80",
HTTPSPort: "443",
CertFile: "/path/to/certfile",
KeyFile: "/path/to/keyfile",
}
router := webgo.NewRouter(cfg, routes())
router.StartHTTPS()
Starting both HTTP & HTTPS server
cfg := &webgo.Config{
Port: "80",
HTTPSPort: "443",
CertFile: "/path/to/certfile",
KeyFile: "/path/to/keyfile",
}
router := webgo.NewRouter(cfg, routes())
go router.StartHTTPS()
router.Start()
Graceful shutdown
Graceful shutdown lets you shutdown the server without affecting any live connections/clients connected to the server. It will complete executing all the active/live requests before shutting down.
Sample code to show how to use shutdown
func main() {
osSig := make(chan os.Signal, 5)
cfg := &webgo.Config{
Host: "",
Port: "8080",
ReadTimeout: 15 * time.Second,
WriteTimeout: 60 * time.Second,
ShutdownTimeout: 15 * time.Second,
}
router := webgo.NewRouter(cfg, routes())
go func() {
<-osSig
err := router.Shutdown()
if err != nil {
fmt.Println(err)
os.Exit(1)
} else {
fmt.Println("shutdown complete")
os.Exit(0)
}
}()
signal.Notify(osSig, os.Interrupt, syscall.SIGTERM)
router.Start()
for {
time.Sleep(time.Second * 1)
}
}
Logging
WebGo exposes a singleton+global logger variable LOGHANDLER with which you can plugin your custom logger. Any custom logger should implement WebGo's Logger interface.
type Logger interface {
Debug(data ...interface{})
Info(data ...interface{})
Warn(data ...interface{})
Error(data ...interface{})
Fatal(data ...interface{})
}
Sample
A fully functional sample is provided here. You can try the following API calls with the sample app.
http://localhost:8080/
- Route with no named parameters configured
http://localhost:8080/matchall/
- Route with wildcard parameter configured
- All URIs which begin with
/matchall
will be matched because it has a wildcard variable - e.g.
- `http://localhost:8080/api/
- Route with a named 'param' configured
- It will match all requests which match
/api/<single parameter>
- e.g.
How to run the sample
If you have Go installed on your system, open your terminal and:
$ cd $GOPATH/src
$ mkdir -p github.com/bnkamalesh
$ cd github.com/bnkamalesh
$ git clone https://github.com/bnkamalesh/webgo.git
$ cd webgo
$ go run cmd/main.go
Info 2019/07/09 18:35:54 HTTP server, listening on :8080
Or if you have Docker, open your terminal and:
$ git clone https://github.com/bnkamalesh/webgo.git
$ cd webgo
$ docker run \
-p 8080:8080 \
-v ${PWD}:/go/src/github.com/bnkamalesh/webgo/ \
-w /go/src/github.com/bnkamalesh/webgo/cmd \
--rm -ti golang:latest go run main.go
Info 2019/07/09 18:35:54 HTTP server, listening on :8080
The gopher
The gopher used here was created using Gopherize.me. WebGo stays out of developers' way, and they can sitback and enjoy a cup of coffee just like this gopher, while using WebGo.