在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称:gocraft/web开源软件地址:https://github.com/gocraft/web开源编程语言:Go 99.9%开源软件介绍:gocraft/webgocraft/web is a Go mux and middleware package. We deal with casting and reflection so YOUR code can be statically typed. And we're fast. Getting StartedFrom your GOPATH: go get github.com/gocraft/web Add a file package main
import (
"github.com/gocraft/web"
"fmt"
"net/http"
"strings"
)
type Context struct {
HelloCount int
}
func (c *Context) SetHelloCount(rw web.ResponseWriter, req *web.Request, next web.NextMiddlewareFunc) {
c.HelloCount = 3
next(rw, req)
}
func (c *Context) SayHello(rw web.ResponseWriter, req *web.Request) {
fmt.Fprint(rw, strings.Repeat("Hello ", c.HelloCount), "World!")
}
func main() {
router := web.New(Context{}). // Create your router
Middleware(web.LoggerMiddleware). // Use some included middleware
Middleware(web.ShowErrorsMiddleware). // ...
Middleware((*Context).SetHelloCount). // Your own middleware!
Get("/", (*Context).SayHello) // Add a route
http.ListenAndServe("localhost:3000", router) // Start the server!
} Run the server. It will be available on go run src/myapp/server.go Features
PerformancePerformance is a first class concern. Every update to this package has its performance measured and tracked in BENCHMARK_RESULTS. For minimal 'hello world' style apps, added latency is about 3μs. This grows to about 10μs for more complex apps (6 middleware functions, 3 levels of contexts, 150+ routes). One key design choice we've made is our choice of routing algorithm. Most competing libraries use simple O(N) iteration over all routes to find a match. This is fine if you have only a handful of routes, but starts to break down as your app gets bigger. We use a tree-based router which grows in complexity at O(log(N)). Application StructureMaking your routerThe first thing you need to do is make a new router. Routers serve requests and execute middleware. router := web.New(YourContext{}) Your contextWait, what is YourContext{} and why do you need it? It can be any struct you want it to be. Here's an example of one: type YourContext struct {
User *User // Assumes you've defined a User type as well
} Your context can be empty or it can have various fields in it. The fields can be whatever you want - it's your type! When a new request comes into the router, we'll allocate an instance of this struct and pass it to your middleware and handlers. This allows, for instance, a SetUser middleware to set a User field that can be read in the handlers. Routes and handlersOnce you have your router, you can add routes to it. Standard HTTP verbs are supported. router := web.New(YourContext{})
router.Get("/users", (*YourContext).UsersList)
router.Post("/users", (*YourContext).UsersCreate)
router.Put("/users/:id", (*YourContext).UsersUpdate)
router.Delete("/users/:id", (*YourContext).UsersDelete)
router.Patch("/users/:id", (*YourContext).UsersUpdate)
router.Get("/", (*YourContext).Root) What is that funny func (c *YourContext) Root(rw web.ResponseWriter, req *web.Request) {
if c.User != nil {
fmt.Fprint(rw, "Hello,", c.User.Name)
} else {
fmt.Fprint(rw, "Hello, anonymous person")
}
} All method expressions do is return a function that accepts the type as the first argument. So your handler can also look like this: func Root(c *YourContext, rw web.ResponseWriter, req *web.Request) {} Of course, if you don't need a context for a particular action, you can also do that: func Root(rw web.ResponseWriter, req *web.Request) {} Note that handlers always need to accept two input parameters: web.ResponseWriter, and *web.Request, both of which wrap the standard http.ResponseWriter and *http.Request, respectively. MiddlewareYou can add middleware to a router: router := web.New(YourContext{})
router.Middleware((*YourContext).UserRequired)
// add routes, more middleware This is what a middleware handler looks like: func (c *YourContext) UserRequired(rw web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
user := userFromSession(r) // Pretend like this is defined. It reads a session cookie and returns a *User or nil.
if user != nil {
c.User = user
next(rw, r)
} else {
rw.Header().Set("Location", "/")
rw.WriteHeader(http.StatusMovedPermanently)
// do NOT call next()
}
} Some things to note about the above example:
Of course, generic middleware without contexts is supported: func GenericMiddleware(rw web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
// ...
} Nested routersNested routers let you run different middleware and use different contexts for different parts of your app. Some common scenarios:
Let's implement that. Your contexts would look like this: type Context struct {
Session map[string]string
}
type AdminContext struct {
*Context
CurrentAdmin *User
}
type ApiContext struct {
*Context
AccessToken string
} Note that we embed a pointer to the parent context in each subcontext. This is required. Now that we have our contexts, let's create our routers: rootRouter := web.New(Context{})
rootRouter.Middleware((*Context).LoadSession)
apiRouter := rootRouter.Subrouter(ApiContext{}, "/api")
apiRouter.Middleware((*ApiContext).OAuth)
apiRouter.Get("/tickets", (*ApiContext).TicketsIndex)
adminRouter := rootRouter.Subrouter(AdminContext{}, "/admin")
adminRouter.Middleware((*AdminContext).AdminRequired)
// Given the path namesapce for this router is "/admin", the full path of this route is "/admin/reports"
adminRouter.Get("/reports", (*AdminContext).Reports) Note that each time we make a subrouter, we need to supply the context as well as a path namespace. The context CAN be the same as the parent context, and the namespace CAN just be "/" for no namespace. Request lifecycleThe following is a detailed account of the request lifecycle:
Capturing path params; regexp conditionsYou can capture path variables like this: router.Get("/suggestions/:suggestion_id/comments/:comment_id") In your handler, you can access them like this: func (c *YourContext) Root(rw web.ResponseWriter, req *web.Request) {
fmt.Fprint(rw, "Suggestion ID:", req.PathParams["suggestion_id"])
fmt.Fprint(rw, "Comment ID:", req.PathParams["comment_id"])
} You can also validate the format of your path params with a regexp. For instance, to ensure the 'ids' start with a digit: router.Get("/suggestions/:suggestion_id:\\d.*/comments/:comment_id:\\d.*") You can match any route past a certain point like this: router.Get("/suggestions/:suggestion_id/comments/:comment_id/:*") The path params will contain a “” member with the rest of your path. It is illegal to add any more paths past the “” path param, as it’s meant to match every path afterwards, in all cases. For Example: /suggestions/123/comments/321/foo/879/bar/834 Elicits path params: * “suggestion_id”: 123, * “comment_id”: 321, * “*”: “foo/879/bar/834” One thing you CANNOT currently do is use regexps outside of a path segment. For instance, optional path segments are not supported - you would have to define multiple routes that both point to the same handler. This design decision was made to enable efficient routing. Not Found handlersIf a route isn't found, by default we'll return a 404 status and render the text "Not Found". You can supply a custom NotFound handler on your root router: router.NotFound((*Context).NotFound) Your handler can optionally accept a pointer to the root context. NotFound handlers look like this: func (c *Context) NotFound(rw web.ResponseWriter, r *web.Request) {
rw.WriteHeader(http.StatusNotFound) // You probably want to return 404. But you can also redirect or do whatever you want.
fmt.Fprintf(rw, "My Not Found") // Render you own HTML or something!
} OPTIONS handlersIf an OPTIONS request is made and routes with other methods are found for the requested path, then by default we'll return an empty response with an appropriate You can supply a custom OPTIONS handler on your root router: router.OptionsHandler((*Context).OptionsHandler) Your handler can optionally accept a pointer to the root context. OPTIONS handlers look like this: func (c *Context) OptionsHandler(rw web.ResponseWriter, r *web.Request, methods []string) {
rw.Header().Add("Access-Control-Allow-Methods", strings.Join(methods, ", "))
rw.Header().Add("Access-Control-Allow-Origin", "*")
} Error handlersBy default, if there's a panic in middleware or a handler, we'll return a 500 status and render the text "Application Error". If you use the included middleware You can also supply a custom Error handler on any router (not just the root router): router.Error((*Context).Error) Your handler can optionally accept a pointer to its corresponding context. Error handlers look like this: func (c *Context) Error(rw web.ResponseWriter, r *web.Request, err interface{}) {
rw.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, "Error", err)
} Included middlewareWe ship with three basic pieces of middleware: a logger, an exception printer, and a static file server. To use them: router := web.New(Context{})
router.Middleware(web.LoggerMiddleware).
Middleware(web.ShowErrorsMiddleware)
// The static middleware serves files. Examples:
// "GET /" will serve an index file at pwd/public/index.html
// "GET /robots.txt" will serve the file at pwd/public/robots.txt
// "GET /images/foo.gif" will serve the file at pwd/public/images/foo.gif
currentRoot, _ := os.Getwd()
router.Middleware(web.StaticMiddleware(path.Join(currentRoot, "public"), web.StaticOption{IndexFile: "index.html"})) NOTE: You might not want to use web.ShowErrorsMiddleware in production. You can easily do something like this: router := web.New(Context{})
router.Middleware(web.LoggerMiddleware)
if MyEnvironment == "development" {
router.Middleware(web.ShowErrorsMiddleware)
}
// ... Starting your serverSince web.Router implements http.Handler (eg, ServeHTTP(ResponseWriter, *Request)), you can easily plug it in to the standard Go http machinery: router := web.New(Context{})
// ... Add routes and such.
http.ListenAndServe("localhost:8080", router) Rendering responsesSo now you routed a request to a handler. You have a web.ResponseWriter (http.ResponseWriter) and web.Request (http.Request). Now what? // You can print to the ResponseWriter!
fmt.Fprintf(rw, "<html>I'm a web page!</html>") This is currently where the implementation of this library stops. I recommend you read the documentation of net/http. Extra MiddlwareThis package is going to keep the built-in middlware simple and lean. Extra middleware can be found across the web:
If you'd like me to link to your middleware, let me know with a pull request to this README. gocraftgocraft offers a toolkit for building web apps. Currently these packages are available:
These packages were developed by the engineering team at UserVoice and currently power much of its infrastructure and tech stack. Thanks & AuthorsI use code/got inspiration from these excellent libraries:
Authors:
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论