代码拉取完成,页面将自动刷新
// Copyright 2013 Julien Schmidt. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found
// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE.
package clevergo
import (
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"os/signal"
"strings"
"syscall"
"time"
"clevergo.tech/log"
)
// errors
var (
ErrRendererNotRegister = errors.New("renderer not registered")
ErrDecoderNotRegister = errors.New("decoder not registered")
)
var requestMethods = []string{
http.MethodGet,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodOptions,
http.MethodDelete,
http.MethodHead,
http.MethodConnect,
http.MethodTrace,
}
// Handle is a function which handle incoming request and manage outgoing response.
type Handle func(c *Context) error
// HandleHandler converts http.Handler to Handle.
func HandleHandler(handler http.Handler) Handle {
return func(c *Context) error {
handler.ServeHTTP(c.Response, c.Request)
return nil
}
}
// HandleHandlerFunc converts http.HandlerFunc to Handle.
func HandleHandlerFunc(f http.HandlerFunc) Handle {
return func(c *Context) error {
f(c.Response, c.Request)
return nil
}
}
// Decoder is an interface that decodes request's input.
type Decoder interface {
// Decode decodes request's input and stores it in the value pointed to by v.
Decode(req *http.Request, v interface{}) error
}
// Renderer is an interface for template engine.
type Renderer interface {
Render(w io.Writer, name string, data interface{}, c *Context) error
}
// Application is a http.Handler which can be used to dispatch requests to different
// handler functions via configurable routes
type Application struct {
Server *http.Server
// Graceful shutdown timeout.
ShutdownTimeout time.Duration
// Graceful shutdown signals.
ShutdownSignals []os.Signal
certFile string
keyFile string
trees map[string]*node
// Named routes.
routes map[string]*Route
maxParams uint16
// Enables automatic redirection if the current route can't be matched but a
// handler for the path with (without) the trailing slash exists.
// For example if /foo/ is requested but a route only exists for /foo, the
// client is redirected to /foo with http status code 301 for Get requests
// and 308 for all other request methods.
RedirectTrailingSlash bool
// If enabled, the router tries to fix the current request path, if no
// handle is registered for it.
// First superfluous path elements like ../ or // are removed.
// Afterwards the router does a case-insensitive lookup of the cleaned path.
// If a handle can be found for this route, the router makes a redirection
// to the corrected path with status code 301 for Get requests and 308 for
// all other request methods.
// For example /FOO and /..//Foo could be redirected to /foo.
// RedirectTrailingSlash is independent of this option.
RedirectFixedPath bool
// If enabled, the router checks if another method is allowed for the
// current route, if the current request can not be routed.
// If this is the case, the request is answered with 'Method Not Allowed'
// and HTTP status code 405.
// If no other Method is allowed, the request is delegated to the NotFound
// handler.
HandleMethodNotAllowed bool
// If enabled, the router automatically replies to OPTIONS requests.
// Custom OPTIONS handlers take priority over automatic replies.
HandleOPTIONS bool
// An optional http.Handler that is called on automatic OPTIONS requests.
// The handler is only called if HandleOPTIONS is true and no OPTIONS
// handler for the specific path was set.
// The "Allowed" header is set before calling the handler.
GlobalOPTIONS http.Handler
// Cached value of global (*) allowed methods
globalAllowed string
// Configurable http.Handler which is called when no matching route is
// found. If it is not set, http.NotFound is used.
NotFound http.Handler
// Configurable http.Handler which is called when a request
// cannot be routed and HandleMethodNotAllowed is true.
// If it is not set, http.Error with http.StatusMethodNotAllowed is used.
// The "Allow" header with allowed request methods is set before the handler
// is called.
MethodNotAllowed http.Handler
// If enabled, use the request.URL.RawPath instead of request.URL.Path.
UseRawPath bool
middlewares []MiddlewareFunc
handle Handle
// Template Renderer.
Renderer Renderer
// Request input decoder.
Decoder Decoder
Logger log.Logger
}
// Make sure the Application conforms with the http.Handler interface
var _ http.Handler = (*Application)(nil)
// Pure returns a new initialized application.
// Path auto-correction, including trailing slashes, is enabled by default.
func Pure() *Application {
return &Application{
ShutdownTimeout: 5 * time.Second,
ShutdownSignals: []os.Signal{os.Interrupt, syscall.SIGINT, syscall.SIGTERM},
RedirectTrailingSlash: true,
RedirectFixedPath: true,
HandleMethodNotAllowed: true,
HandleOPTIONS: true,
Logger: logger,
}
}
// New returns an application which enable recovery, error handler, server header and logging middleware by default.
func New() *Application {
app := Pure()
app.Use(Logging(), ErrorHandler(), Recovery(), ServerHeader(serverName))
return app
}
// RouteURL creates an url with the given route name and arguments.
func (app *Application) RouteURL(name string, args ...string) (*url.URL, error) {
if route, ok := app.routes[name]; ok {
return route.URL(args...)
}
return nil, fmt.Errorf("route %q does not exist", name)
}
// Group implements Router.Group.
func (app *Application) Group(path string, opts ...RouteGroupOption) Router {
return newRouteGroup(app, path, opts...)
}
// Use attaches global middlewares.
func (app *Application) Use(middlewares ...MiddlewareFunc) {
app.middlewares = append(app.middlewares, middlewares...)
app.handle = Chain(app.handleRequest, app.middlewares...)
}
// Get implements Router.Get.
func (app *Application) Get(path string, handle Handle, opts ...RouteOption) {
app.Handle(http.MethodGet, path, handle, opts...)
}
// Head implements Router.Head.
func (app *Application) Head(path string, handle Handle, opts ...RouteOption) {
app.Handle(http.MethodHead, path, handle)
}
// Options implements Router.Options.
func (app *Application) Options(path string, handle Handle, opts ...RouteOption) {
app.Handle(http.MethodOptions, path, handle)
}
// Post implements Router.Post.
func (app *Application) Post(path string, handle Handle, opts ...RouteOption) {
app.Handle(http.MethodPost, path, handle)
}
// Put implements Router.Put.
func (app *Application) Put(path string, handle Handle, opts ...RouteOption) {
app.Handle(http.MethodPut, path, handle)
}
// Patch implements Router.Patch.
func (app *Application) Patch(path string, handle Handle, opts ...RouteOption) {
app.Handle(http.MethodPatch, path, handle)
}
// Delete implements Router.Delete.
func (app *Application) Delete(path string, handle Handle, opts ...RouteOption) {
app.Handle(http.MethodDelete, path, handle, opts...)
}
// Any implements Router.Any.
func (app *Application) Any(path string, handle Handle, opts ...RouteOption) {
app.Handle(requestMethods[0], path, handle, opts...)
// Removes route name option before registering handler by the rest of methods.
for i, opt := range opts {
if isRouteNameOption(opt) {
opts = append(opts[:i], opts[i+1:]...)
}
}
for i := 1; i < len(requestMethods); i++ {
app.Handle(requestMethods[i], path, handle, opts...)
}
}
// Handle implements Router.Handle.
func (app *Application) Handle(method, path string, handle Handle, opts ...RouteOption) {
if method == "" {
panic("method must not be empty")
}
if len(path) < 1 || path[0] != '/' {
panic("path must begin with '/' in path '" + path + "'")
}
if handle == nil {
panic("handle must not be nil")
}
if app.trees == nil {
app.trees = make(map[string]*node)
}
root := app.trees[method]
if root == nil {
root = new(node)
app.trees[method] = root
app.globalAllowed = app.allowed("*", "")
}
route := newRoute(path, handle, opts...)
if route.name != "" {
if _, ok := app.routes[route.name]; ok {
panic("route name " + route.name + " is already registered")
}
if app.routes == nil {
app.routes = make(map[string]*Route)
}
app.routes[route.name] = route
}
root.addRoute(path, route)
// Update maxParams
if pc := countParams(path); pc > app.maxParams {
app.maxParams = pc
}
}
// Handler implements Router.Handler.
func (app *Application) Handler(method, path string, handler http.Handler, opts ...RouteOption) {
app.Handle(method, path, HandleHandler(handler), opts...)
}
// HandlerFunc implements Router.HandlerFunc.
func (app *Application) HandlerFunc(method, path string, f http.HandlerFunc, opts ...RouteOption) {
app.Handle(method, path, HandleHandlerFunc(f), opts...)
}
// ServeFiles serves files from the given file system root.
func (app *Application) ServeFiles(path string, root http.FileSystem, opts ...RouteOption) {
fileServer := http.FileServer(root)
app.Get(strings.TrimSuffix(path, "/")+"/*filepath", func(c *Context) error {
c.Request.URL.Path = c.Params.String("filepath")
fileServer.ServeHTTP(c.Response, c.Request)
return nil
}, opts...)
}
// Lookup allows the manual lookup of a method + path combo.
// This is e.g. useful to build a framework around this router.
// If the path was found, it returns the handle function and the path parameter
// values. Otherwise the third return value indicates whether a redirection to
// the same path with an extra / without the trailing slash should be performed.
func (app *Application) Lookup(method, path string) (*Route, Params, bool) {
ps := make(Params, 0, app.maxParams)
if root := app.trees[method]; root != nil {
route, tsr := root.getValue(path, &ps, app.UseRawPath)
return route, ps, tsr
}
return nil, nil, false
}
func (app *Application) allowed(path, reqMethod string) (allow string) {
allowed := make([]string, 0, 9)
if path == "*" { // server-wide
// empty method is used for internal calls to refresh the cache
if reqMethod == "" {
for method := range app.trees {
if method == http.MethodOptions {
continue
}
// Add request method to list of allowed methods
allowed = append(allowed, method)
}
} else {
return app.globalAllowed
}
} else { // specific path
for method := range app.trees {
// Skip the requested method - we already tried this one
if method == reqMethod || method == http.MethodOptions {
continue
}
handle, _ := app.trees[method].getValue(path, nil, app.UseRawPath)
if handle != nil {
// Add request method to list of allowed methods
allowed = append(allowed, method)
}
}
}
if len(allowed) > 0 {
// Add request method to list of allowed methods
allowed = append(allowed, http.MethodOptions)
// Sort allowed methods.
// sort.Strings(allowed) unfortunately causes unnecessary allocations
// due to allowed being moved to the heap and interface conversion
for i, l := 1, len(allowed); i < l; i++ {
for j := i; j > 0 && allowed[j] < allowed[j-1]; j-- {
allowed[j], allowed[j-1] = allowed[j-1], allowed[j]
}
}
// return as comma separated list
return strings.Join(allowed, ", ")
}
return
}
// ServeHTTP makes the router implement the http.Handler interface.
func (app *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c := getContext(app, w, r)
defer putContext(c)
var err error
if app.handle != nil {
err = app.handle(c)
} else {
err = app.handleRequest(c)
}
if err != nil {
app.Logger.Errorf("clevergo: %s", err.Error())
switch e := err.(type) {
case Error:
c.Error(e.Status(), err.Error())
default:
c.Error(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
}
}
func (app *Application) handleRequest(c *Context) (err error) {
path := c.Request.URL.Path
if app.UseRawPath && c.Request.URL.RawPath != "" {
path = c.Request.URL.RawPath
}
if root := app.trees[c.Request.Method]; root != nil {
if route, tsr := root.getValue(path, &c.Params, app.UseRawPath); route != nil {
c.Route = route
err = route.handle(c)
return
} else if c.Request.Method != http.MethodConnect && path != "/" {
// Moved Permanently, request with Get method
code := http.StatusMovedPermanently
if c.Request.Method != http.MethodGet {
// Permanent Redirect, request with same method
code = http.StatusPermanentRedirect
}
if tsr && app.RedirectTrailingSlash {
if len(path) > 1 && path[len(path)-1] == '/' {
path = path[:len(path)-1]
} else {
path = path + "/"
}
c.Redirect(code, path)
return
}
// Try to fix the request path
if app.RedirectFixedPath {
fixedPath, found := root.findCaseInsensitivePath(
CleanPath(path),
app.RedirectTrailingSlash,
)
if found {
c.Redirect(code, fixedPath)
return
}
}
}
}
if c.Request.Method == http.MethodOptions && app.HandleOPTIONS {
// Handle OPTIONS requests
if allow := app.allowed(path, http.MethodOptions); allow != "" {
c.Response.Header().Set("Allow", allow)
if app.GlobalOPTIONS != nil {
app.GlobalOPTIONS.ServeHTTP(c.Response, c.Request)
}
return
}
} else if app.HandleMethodNotAllowed { // Handle 405
if allow := app.allowed(path, c.Request.Method); allow != "" {
c.Response.Header().Set("Allow", allow)
if app.MethodNotAllowed != nil {
app.MethodNotAllowed.ServeHTTP(c.Response, c.Request)
return
}
return ErrMethodNotAllowed
}
}
// Handle 404
if app.NotFound != nil {
app.NotFound.ServeHTTP(c.Response, c.Request)
return
}
return ErrNotFound
}
func (app *Application) initServer() {
if app.Server == nil {
app.Server = &http.Server{}
}
app.Server.Handler = app
}
// Run starts a HTTP server with the given address.
func (app *Application) Run(address string) error {
ln, err := net.Listen("tcp", address)
if err != nil {
return err
}
return app.Serve(ln)
}
// RunTLS starts a HTTPS server with the given address, certfile and keyfile.
func (app *Application) RunTLS(address, certFile, keyFile string) error {
app.certFile = certFile
app.keyFile = keyFile
ln, err := net.Listen("tcp", address)
if err != nil {
return err
}
defer ln.Close()
return app.Serve(ln)
}
// RunUnix starts a HTTP Server which listening and serving HTTP requests
// through the specified unix socket with the given address.
func (app *Application) RunUnix(address string) error {
ln, err := net.Listen("unix", address)
if err != nil {
return err
}
return app.Serve(ln)
}
// Serve accepts incoming connections on the Listener ln.
func (app *Application) Serve(ln net.Listener) (err error) {
app.initServer()
stop := make(chan os.Signal, 1)
signal.Notify(stop, app.ShutdownSignals...)
go func() {
app.Logger.Infof("clevergo: listening on %s.\n", ln.Addr().String())
if app.certFile != "" && app.keyFile != "" {
err = app.Server.ServeTLS(ln, app.certFile, app.keyFile)
} else {
err = app.Server.Serve(ln)
}
app.Logger.Errorf("clevergo: failed to start server: %s.\n", err)
stop <- os.Interrupt
}()
<-stop
ctx, cancel := context.WithTimeout(context.Background(), app.ShutdownTimeout)
defer cancel()
app.Logger.Infof("clevergo: shutting down server...\n")
err = app.Server.Shutdown(ctx)
return
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。