5991a21818
* First batch of changes for the refactor Added the support of gin in routes and other services/utils Begining implementation of JetHTML * Remove os folder * Move scrapers to own repo * Second batch of changes All .jet.html are the working templates. You can now test this PR, the index Page and upload works. If you want to complete the other html templates, you're welcome * Move captcha to util * Move uploadService to utils * Use govalidator instead of regex * Third batch of changes All the front end should as previously. I also fixed some minor things unrelated to the refactor (mostly style issues on static pages) Now errors can be accessed by importing the "errors" helpers and using the `yield errors(name="xxx")` command in templates. Same for infos. Templates are now more hierarchized with a base template "base.jet.html" which is extended depending on the context in "index_site" or "index_admin" layouts. Those layouts are extended than in every pages. Other helpers are captcha to render a captcha `yield captcha(captchaid="xxx")` And also csrf, with the command `yield csrf_field()` To translate, you don't have anymore to do `call $.T "xxx"`, you just have to do `T("xxx")`. Pages for the website part are in folders in the folder "templates/site". Pages for the admin part are in "templates/admin". Layouts are separated in "templates/layouts". Helpers and menu are in "templates/layouts/helpers" and "templates/layouts/menu". Error pages should be put in "templates/errors" * Added test on templates When adding a new template, you have to tell to template_test.go, the context of the new template (if it doesn't use the common context) * Panel admin works Now the templating part should work. The PR can now be fully tested. I think we should push the templating PR and do the routes/controllers/removal of services in another branch. So we know that this one is functional * Updated dependencies * Fixed test for modelhelper * Fix testing for commentlist * Fix travis :') * Just renamed router and removed network * Applying same SEO fix * Update form_validator.go * Added back regexp package
370 lignes
11 Kio
Go
370 lignes
11 Kio
Go
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
|
// Use of this source code is governed by a MIT style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package gin
|
|
|
|
import (
|
|
"html/template"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"sync"
|
|
|
|
"github.com/gin-gonic/gin/render"
|
|
)
|
|
|
|
// Version is Framework's version
|
|
const Version = "v1.0rc2"
|
|
|
|
var default404Body = []byte("404 page not found")
|
|
var default405Body = []byte("405 method not allowed")
|
|
|
|
type HandlerFunc func(*Context)
|
|
type HandlersChain []HandlerFunc
|
|
|
|
// Last returns the last handler in the chain. ie. the last handler is the main own.
|
|
func (c HandlersChain) Last() HandlerFunc {
|
|
length := len(c)
|
|
if length > 0 {
|
|
return c[length-1]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type (
|
|
RoutesInfo []RouteInfo
|
|
RouteInfo struct {
|
|
Method string
|
|
Path string
|
|
Handler string
|
|
}
|
|
|
|
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
|
// Create an instance of Engine, by using New() or Default()
|
|
Engine struct {
|
|
RouterGroup
|
|
HTMLRender render.HTMLRender
|
|
allNoRoute HandlersChain
|
|
allNoMethod HandlersChain
|
|
noRoute HandlersChain
|
|
noMethod HandlersChain
|
|
pool sync.Pool
|
|
trees methodTrees
|
|
|
|
// 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 307 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 307 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
|
|
ForwardedByClientIP bool
|
|
}
|
|
)
|
|
|
|
var _ IRouter = &Engine{}
|
|
|
|
// New returns a new blank Engine instance without any middleware attached.
|
|
// By default the configuration is:
|
|
// - RedirectTrailingSlash: true
|
|
// - RedirectFixedPath: false
|
|
// - HandleMethodNotAllowed: false
|
|
// - ForwardedByClientIP: true
|
|
func New() *Engine {
|
|
debugPrintWARNINGNew()
|
|
engine := &Engine{
|
|
RouterGroup: RouterGroup{
|
|
Handlers: nil,
|
|
basePath: "/",
|
|
root: true,
|
|
},
|
|
RedirectTrailingSlash: true,
|
|
RedirectFixedPath: false,
|
|
HandleMethodNotAllowed: false,
|
|
ForwardedByClientIP: true,
|
|
trees: make(methodTrees, 0, 9),
|
|
}
|
|
engine.RouterGroup.engine = engine
|
|
engine.pool.New = func() interface{} {
|
|
return engine.allocateContext()
|
|
}
|
|
return engine
|
|
}
|
|
|
|
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
|
|
func Default() *Engine {
|
|
engine := New()
|
|
engine.Use(Logger(), Recovery())
|
|
return engine
|
|
}
|
|
|
|
func (engine *Engine) allocateContext() *Context {
|
|
return &Context{engine: engine}
|
|
}
|
|
|
|
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
|
if IsDebugging() {
|
|
debugPrintLoadTemplate(template.Must(template.ParseGlob(pattern)))
|
|
engine.HTMLRender = render.HTMLDebug{Glob: pattern}
|
|
} else {
|
|
templ := template.Must(template.ParseGlob(pattern))
|
|
engine.SetHTMLTemplate(templ)
|
|
}
|
|
}
|
|
|
|
func (engine *Engine) LoadHTMLFiles(files ...string) {
|
|
if IsDebugging() {
|
|
engine.HTMLRender = render.HTMLDebug{Files: files}
|
|
} else {
|
|
templ := template.Must(template.ParseFiles(files...))
|
|
engine.SetHTMLTemplate(templ)
|
|
}
|
|
}
|
|
|
|
func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
|
|
if len(engine.trees) > 0 {
|
|
debugPrintWARNINGSetHTMLTemplate()
|
|
}
|
|
engine.HTMLRender = render.HTMLProduction{Template: templ}
|
|
}
|
|
|
|
// NoRoute adds handlers for NoRoute. It return a 404 code by default.
|
|
func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
|
|
engine.noRoute = handlers
|
|
engine.rebuild404Handlers()
|
|
}
|
|
|
|
// NoMethod sets the handlers called when... TODO
|
|
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
|
|
engine.noMethod = handlers
|
|
engine.rebuild405Handlers()
|
|
}
|
|
|
|
// Use attachs a global middleware to the router. ie. the middleware attached though Use() will be
|
|
// included in the handlers chain for every single request. Even 404, 405, static files...
|
|
// For example, this is the right place for a logger or error management middleware.
|
|
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
|
|
engine.RouterGroup.Use(middleware...)
|
|
engine.rebuild404Handlers()
|
|
engine.rebuild405Handlers()
|
|
return engine
|
|
}
|
|
|
|
func (engine *Engine) rebuild404Handlers() {
|
|
engine.allNoRoute = engine.combineHandlers(engine.noRoute)
|
|
}
|
|
|
|
func (engine *Engine) rebuild405Handlers() {
|
|
engine.allNoMethod = engine.combineHandlers(engine.noMethod)
|
|
}
|
|
|
|
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
|
assert1(path[0] == '/', "path must begin with '/'")
|
|
assert1(len(method) > 0, "HTTP method can not be empty")
|
|
assert1(len(handlers) > 0, "there must be at least one handler")
|
|
|
|
debugPrintRoute(method, path, handlers)
|
|
root := engine.trees.get(method)
|
|
if root == nil {
|
|
root = new(node)
|
|
engine.trees = append(engine.trees, methodTree{method: method, root: root})
|
|
}
|
|
root.addRoute(path, handlers)
|
|
}
|
|
|
|
// Routes returns a slice of registered routes, including some useful information, such as:
|
|
// the http method, path and the handler name.
|
|
func (engine *Engine) Routes() (routes RoutesInfo) {
|
|
for _, tree := range engine.trees {
|
|
routes = iterate("", tree.method, routes, tree.root)
|
|
}
|
|
return routes
|
|
}
|
|
|
|
func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
|
|
path += root.path
|
|
if len(root.handlers) > 0 {
|
|
routes = append(routes, RouteInfo{
|
|
Method: method,
|
|
Path: path,
|
|
Handler: nameOfFunction(root.handlers.Last()),
|
|
})
|
|
}
|
|
for _, child := range root.children {
|
|
routes = iterate(path, method, routes, child)
|
|
}
|
|
return routes
|
|
}
|
|
|
|
// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
|
|
// It is a shortcut for http.ListenAndServe(addr, router)
|
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
|
func (engine *Engine) Run(addr ...string) (err error) {
|
|
defer func() { debugPrintError(err) }()
|
|
|
|
address := resolveAddress(addr)
|
|
debugPrint("Listening and serving HTTP on %s\n", address)
|
|
err = http.ListenAndServe(address, engine)
|
|
return
|
|
}
|
|
|
|
// RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests.
|
|
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
|
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
|
func (engine *Engine) RunTLS(addr string, certFile string, keyFile string) (err error) {
|
|
debugPrint("Listening and serving HTTPS on %s\n", addr)
|
|
defer func() { debugPrintError(err) }()
|
|
|
|
err = http.ListenAndServeTLS(addr, certFile, keyFile, engine)
|
|
return
|
|
}
|
|
|
|
// RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests
|
|
// through the specified unix socket (ie. a file).
|
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
|
func (engine *Engine) RunUnix(file string) (err error) {
|
|
debugPrint("Listening and serving HTTP on unix:/%s", file)
|
|
defer func() { debugPrintError(err) }()
|
|
|
|
os.Remove(file)
|
|
listener, err := net.Listen("unix", file)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer listener.Close()
|
|
err = http.Serve(listener, engine)
|
|
return
|
|
}
|
|
|
|
// Conforms to the http.Handler interface.
|
|
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
c := engine.pool.Get().(*Context)
|
|
c.writermem.reset(w)
|
|
c.Request = req
|
|
c.reset()
|
|
|
|
engine.handleHTTPRequest(c)
|
|
|
|
engine.pool.Put(c)
|
|
}
|
|
|
|
func (engine *Engine) handleHTTPRequest(context *Context) {
|
|
httpMethod := context.Request.Method
|
|
path := context.Request.URL.Path
|
|
|
|
// Find root of the tree for the given HTTP method
|
|
t := engine.trees
|
|
for i, tl := 0, len(t); i < tl; i++ {
|
|
if t[i].method == httpMethod {
|
|
root := t[i].root
|
|
// Find route in tree
|
|
handlers, params, tsr := root.getValue(path, context.Params)
|
|
if handlers != nil {
|
|
context.handlers = handlers
|
|
context.Params = params
|
|
context.Next()
|
|
context.writermem.WriteHeaderNow()
|
|
return
|
|
|
|
} else if httpMethod != "CONNECT" && path != "/" {
|
|
if tsr && engine.RedirectTrailingSlash {
|
|
redirectTrailingSlash(context)
|
|
return
|
|
}
|
|
if engine.RedirectFixedPath && redirectFixedPath(context, root, engine.RedirectFixedPath) {
|
|
return
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
// TODO: unit test
|
|
if engine.HandleMethodNotAllowed {
|
|
for _, tree := range engine.trees {
|
|
if tree.method != httpMethod {
|
|
if handlers, _, _ := tree.root.getValue(path, nil); handlers != nil {
|
|
context.handlers = engine.allNoMethod
|
|
serveError(context, 405, default405Body)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
context.handlers = engine.allNoRoute
|
|
serveError(context, 404, default404Body)
|
|
}
|
|
|
|
var mimePlain = []string{MIMEPlain}
|
|
|
|
func serveError(c *Context, code int, defaultMessage []byte) {
|
|
c.writermem.status = code
|
|
c.Next()
|
|
if !c.writermem.Written() {
|
|
if c.writermem.Status() == code {
|
|
c.writermem.Header()["Content-Type"] = mimePlain
|
|
c.Writer.Write(defaultMessage)
|
|
} else {
|
|
c.writermem.WriteHeaderNow()
|
|
}
|
|
}
|
|
}
|
|
|
|
func redirectTrailingSlash(c *Context) {
|
|
req := c.Request
|
|
path := req.URL.Path
|
|
code := 301 // Permanent redirect, request with GET method
|
|
if req.Method != "GET" {
|
|
code = 307
|
|
}
|
|
|
|
if len(path) > 1 && path[len(path)-1] == '/' {
|
|
req.URL.Path = path[:len(path)-1]
|
|
} else {
|
|
req.URL.Path = path + "/"
|
|
}
|
|
debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
|
|
http.Redirect(c.Writer, req, req.URL.String(), code)
|
|
c.writermem.WriteHeaderNow()
|
|
}
|
|
|
|
func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
|
|
req := c.Request
|
|
path := req.URL.Path
|
|
|
|
fixedPath, found := root.findCaseInsensitivePath(
|
|
cleanPath(path),
|
|
trailingSlash,
|
|
)
|
|
if found {
|
|
code := 301 // Permanent redirect, request with GET method
|
|
if req.Method != "GET" {
|
|
code = 307
|
|
}
|
|
req.URL.Path = string(fixedPath)
|
|
debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
|
|
http.Redirect(c.Writer, req, req.URL.String(), code)
|
|
c.writermem.WriteHeaderNow()
|
|
return true
|
|
}
|
|
return false
|
|
}
|