├── internal ├── README.md ├── errors │ └── errors.go ├── internalcolor │ └── internalcolor.go └── git │ └── gitignore.go ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── container ├── README.md ├── js │ ├── js.go │ └── v8 │ │ └── v8.go └── command │ └── strings.go ├── utils.go ├── utils_test.go ├── .gitignore ├── go.mod ├── router_test.go ├── README.md ├── context.go ├── tree_test.go ├── tree.go ├── router.go ├── LICENSE ├── go.sum ├── godzilla.go └── godzilla_test.go /internal/README.md: -------------------------------------------------------------------------------- 1 | ## INTERNAL -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: pranaOS -------------------------------------------------------------------------------- /container/README.md: -------------------------------------------------------------------------------- 1 | ## CONTAINER. 2 | this folder contains all the required functionalities for the whole application -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package godzilla 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | func GetString(b []byte) string { 8 | return *(*string)(unsafe.Pointer(&b)) 9 | } 10 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package godzilla 2 | 3 | import "fmt" 4 | 5 | func ExampleGetString() { 6 | b := []byte("ABC") 7 | str := GetString(b) 8 | fmt.Println(str) 9 | fmt.Println(len(b) == len(str)) 10 | 11 | b = nil 12 | str = GetString(b) 13 | fmt.Println(str) 14 | fmt.Println(len(b) == len(str)) 15 | fmt.Println(len(str)) 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | vendor/ 16 | 17 | .DS_Store 18 | 19 | .idea/ 20 | .vscode/ 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | 6 | jobs: 7 | linuxbuild: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Setup go 11 | uses: actions/setup-go@v2 12 | with: 13 | go-version: '^1.16' 14 | - name: Checkout repository 15 | uses: actions/checkout@v2 16 | 17 | - name: Install Go Deps 18 | run: | 19 | go mod tidy 20 | 21 | macosbuild: 22 | runs-on: macos-latest 23 | steps: 24 | - name: Setup go 25 | uses: actions/setup-go@v2 26 | with: 27 | go-version: '^1.16' 28 | - name: Checkout repository 29 | uses: actions/checkout@v2 30 | 31 | - name: Install Go Deps 32 | run: | 33 | go mod tidy 34 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/godzillaframework/godzilla 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 7 | github.com/json-iterator/go v1.1.12 8 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 9 | github.com/valyala/fasthttp v1.31.0 10 | rogchap.com/v8go v0.9.0 11 | ) 12 | 13 | require ( 14 | github.com/modern-go/reflect2 v1.0.2 // indirect 15 | github.com/stretchr/testify v1.7.0 // indirect 16 | github.com/valyala/bytebufferpool v1.0.0 // indirect 17 | github.com/valyala/tcplisten v1.0.0 // indirect 18 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect 19 | ) 20 | 21 | require ( 22 | github.com/andybalholm/brotli v1.0.4 // indirect 23 | github.com/klauspost/compress v1.13.6 // indirect 24 | github.com/livebud/bud v0.2.8 25 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 26 | go.kuoruan.net/v8go-polyfills v0.5.1-0.20220727011656-c74c5b408ebd 27 | ) 28 | -------------------------------------------------------------------------------- /container/js/js.go: -------------------------------------------------------------------------------- 1 | /** 2 | @author: Krisna Pranav, GodzillaFrameworkDevelopers 3 | @filename: js/js.go 4 | 5 | Copyright [2021 - 2023] [Krisna Pranav, GodzillaFrameworkDeveloeprs] 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | package js 21 | 22 | type VM interface { 23 | // script(path, script) functioalities 24 | Script(path, script string) error 25 | 26 | // eval(path, expression) functionalities 27 | Eval(path, expression string) (string, error) 28 | } 29 | -------------------------------------------------------------------------------- /internal/errors/errors.go: -------------------------------------------------------------------------------- 1 | /** 2 | @author: Krisna Pranav, GodzillaFrameworkDevelopers 3 | @filename: internal/errors/errors.go 4 | 5 | Copyright [2021 - 2023] [Krisna Pranav, GodzillaFrameworkDeveloeprs] 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | package errs 21 | 22 | import ( 23 | "errors" 24 | "fmt" 25 | "strings" 26 | ) 27 | 28 | type Errors interface { 29 | Errors() []error 30 | } 31 | 32 | func joi(errs ...error) error { 33 | var agg error 34 | for _, err := range errs { 35 | if err == nil { 36 | continue 37 | } else if agg == nil { 38 | agg = err 39 | continue 40 | } else if errors.Is(err, agg) { 41 | agg = fmt.Errorf("%w. %s", agg, err) 42 | } else { 43 | agg = fmt.Errorf("%s. %s", agg, err) 44 | } 45 | } 46 | return agg 47 | } 48 | 49 | func format(err error) string { 50 | lines := strings.Split(err.Error(), ". ") 51 | lineLen := len(lines) 52 | stack := make([]string, lineLen) 53 | j := lineLen - 1 54 | for i := 0; i < lineLen; i++ { 55 | line := lines[j] 56 | if i > 0 { 57 | // line = " " + interncolor.dim(line) 58 | line = " " + line 59 | } 60 | stack[i] = line 61 | j-- 62 | } 63 | return strings.Join(stack, "\n") 64 | } 65 | -------------------------------------------------------------------------------- /container/command/strings.go: -------------------------------------------------------------------------------- 1 | /** 2 | @author: Krisna Pranav, GodzillaFrameworkDevelopers 3 | @filename: container/command/strings.go 4 | 5 | Copyright [2021 - 2023] [Krisna Pranav, GodzillaFrameworkDeveloeprs] 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | package command 21 | 22 | import ( 23 | "fmt" 24 | "strings" 25 | ) 26 | 27 | type Strings struct { 28 | target *[]string 29 | defval *[]string // default value 30 | optional bool 31 | } 32 | 33 | type stringsValue struct { 34 | inner *Strings 35 | set bool 36 | } 37 | 38 | func (v *stringsValue) verify(displayName string) error { 39 | if v.set { 40 | return nil 41 | } else if v.inner.defval != nil { 42 | *v.inner.target = *v.inner.defval 43 | return nil 44 | } else if v.inner.optional { 45 | return nil 46 | } 47 | return fmt.Errorf("missing %s", displayName) 48 | } 49 | 50 | func (v *Strings) Default(values ...string) { 51 | v.defval = &values 52 | } 53 | 54 | func (v *stringsValue) Set(val string) error { 55 | *v.inner.target = append(*v.inner.target, val) 56 | v.set = true 57 | return nil 58 | } 59 | 60 | func (v *stringsValue) String() string { 61 | if v.inner == nil { 62 | return "" 63 | } else if v.set { 64 | return strings.Join(*v.inner.target, ", ") 65 | } else if v.inner.defval != nil { 66 | return strings.Join(*v.inner.defval, ", ") 67 | } 68 | return "" 69 | } 70 | -------------------------------------------------------------------------------- /internal/internalcolor/internalcolor.go: -------------------------------------------------------------------------------- 1 | /** 2 | @author: Krisna Pranav, GodzillaFrameworkDevelopers 3 | @filename: internal/color/color.go 4 | 5 | Copyright [2021 - 2023] [Krisna Pranav, GodzillaFrameworkDeveloeprs] 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | package interncolor 21 | 22 | import ( 23 | "os" 24 | 25 | "github.com/aybabtme/rgbterm" 26 | ) 27 | 28 | // get local variable NO_COLOR 29 | var noColor = os.Getenv("NO_COLOR") != "" 30 | 31 | // COLORS 32 | func white(msg string) string { 33 | return paint(msg, 226, 232, 240) 34 | } 35 | 36 | func green(msg string) string { 37 | return paint(msg, 43, 255, 99) 38 | } 39 | 40 | func blue(msg string) string { 41 | return paint(msg, 43, 199, 255) 42 | } 43 | 44 | func yellow(msg string) string { 45 | return paint(msg, 255, 237, 43) 46 | } 47 | 48 | func pink(msg string) string { 49 | return paint(msg, 192, 38, 211) 50 | } 51 | 52 | func red(msg string) string { 53 | return paint(msg, 255, 43, 43) 54 | } 55 | 56 | // COLOR METHODS 57 | func paint(msg string, r, g, b uint8) string { 58 | if noColor { 59 | return msg 60 | } 61 | return rgbterm.FgString(msg, r, g, b) 62 | } 63 | 64 | func dim(msg string) string { 65 | if noColor { 66 | return msg 67 | } 68 | return "\033[37m" + msg + "\033[0m" 69 | } 70 | 71 | func bold(msg string) string { 72 | if noColor { 73 | return msg 74 | } 75 | return "\033[1m" + msg + "\033[0m" 76 | } 77 | -------------------------------------------------------------------------------- /internal/git/gitignore.go: -------------------------------------------------------------------------------- 1 | /** 2 | @author: Krisna Pranav, GodzillaFrameworkDevelopers 3 | @filename: internal/ignore/gitignore.go 4 | 5 | Copyright [2021 - 2023] [Krisna Pranav, GodzillaFrameworkDeveloeprs] 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | package gitignore 21 | 22 | import ( 23 | "io/fs" 24 | "os" 25 | "path/filepath" 26 | "strings" 27 | 28 | gitignore "github.com/sabhiram/go-gitignore" 29 | ) 30 | 31 | // ignoreable values. 32 | var alwaysIgnore = []string{ 33 | "node_modules", 34 | ".git", 35 | ".DS_Store", 36 | ".vscode/", 37 | ".idea/", 38 | "bud", 39 | } 40 | 41 | // default ignores values 42 | var defaultIgnores = append([]string{"/bud"}, alwaysIgnore...) 43 | var defaultIgnore = gitignore.CompileIgnoreLines(defaultIgnores...).MatchesPath 44 | 45 | // from filesystem 46 | func fromFS(fsys fs.FS) (ignore func(path string) bool) { 47 | code, err := fs.ReadFile(fsys, ".gitignore") 48 | if err != nil { 49 | return defaultIgnore 50 | } 51 | lines := strings.Split(string(code), "\n") 52 | lines = append(lines, alwaysIgnore...) 53 | ignorer := gitignore.CompileIgnoreLines(lines...) 54 | return ignorer.MatchesPath 55 | } 56 | 57 | // from 58 | func from(dir string) (ignore func(path string) bool) { 59 | code, err := os.ReadFile(filepath.Join(dir, ".gitignore")) 60 | if err != nil { 61 | return defaultIgnore 62 | } 63 | lines := strings.Split(string(code), "\n") 64 | lines = append(lines, alwaysIgnore...) 65 | ignorer := gitignore.CompileIgnoreLines(lines...) 66 | return ignorer.MatchesPath 67 | } 68 | -------------------------------------------------------------------------------- /router_test.go: -------------------------------------------------------------------------------- 1 | package godzilla 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | func TestHandle(t *testing.T) { 9 | 10 | routes := []struct { 11 | method string 12 | path string 13 | conflict bool 14 | handlers handlersChain 15 | }{ 16 | {method: MethodGet, path: "/articles/search", conflict: false, handlers: fakeHandlersChain}, 17 | {method: MethodGet, path: "/articles/test", conflict: false, handlers: fakeHandlersChain}, 18 | {method: MethodGet, path: "", conflict: true, handlers: fakeHandlersChain}, 19 | {method: "", path: "/articles/test", conflict: true, handlers: fakeHandlersChain}, 20 | {method: MethodGet, path: "orders/test", conflict: true, handlers: fakeHandlersChain}, 21 | {method: MethodGet, path: "/books/test", conflict: true, handlers: emptyHandlersChain}, 22 | } 23 | 24 | router := &router{ 25 | settings: &Settings{}, 26 | cache: make(map[string]*matchResult), 27 | pool: sync.Pool{ 28 | New: func() interface{} { 29 | return new(context) 30 | }, 31 | }, 32 | } 33 | 34 | for _, route := range routes { 35 | recv := catchPanic(func() { 36 | router.handle(route.method, route.path, route.handlers) 37 | }) 38 | 39 | if route.conflict { 40 | if recv == nil { 41 | t.Errorf("no panic for conflicting route '%s'", route.path) 42 | } 43 | } else if recv != nil { 44 | t.Errorf("unexpected panic for route '%s': %v", route.path, recv) 45 | } 46 | } 47 | 48 | } 49 | 50 | func TestHandler(t *testing.T) { 51 | routes := []struct { 52 | method string 53 | path string 54 | handlers handlersChain 55 | }{ 56 | {method: MethodGet, path: "/articles/search", handlers: fakeHandlersChain}, 57 | {method: MethodGet, path: "/articles/test", handlers: fakeHandlersChain}, 58 | } 59 | 60 | router := &router{ 61 | settings: &Settings{}, 62 | cache: make(map[string]*matchResult), 63 | pool: sync.Pool{ 64 | New: func() interface{} { 65 | return new(context) 66 | }, 67 | }, 68 | } 69 | 70 | for _, route := range routes { 71 | router.handle(route.method, route.path, route.handlers) 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # godzilla 2 | 3 | [![forthebadge](https://forthebadge.com/images/badges/made-with-go.svg)](https://forthebadge.com) 4 | 5 | 6 | ## About: 7 | - A powerfull go web framework 8 | - Fast 🚀 9 | - Secure 🔒 10 | - Easy Peasy :) 11 | 12 | ## Features: 13 | - Log Middleware 14 | 15 | ## Installation: 16 | ``` 17 | go get -u github.com/godzillaframework/godzilla 18 | ``` 19 | 20 | ## Examples: 21 | 22 | - a simple api 23 | 24 | ```golang 25 | package main 26 | 27 | import "github.com/godzillaframework/godzilla" 28 | 29 | func main() { 30 | gz := godzilla.New() 31 | 32 | gz.Get("/index", func(ctx godzilla.Context) { 33 | ctx.SendString("Hello EveryOne!!!") 34 | }) 35 | 36 | gz.Start(":9090") 37 | } 38 | ``` 39 | 40 | - params 41 | ```golang 42 | package main 43 | 44 | import "github.com/godzillaframework/godzilla" 45 | 46 | func main() { 47 | gz := godzilla.New() 48 | 49 | gz.Get("/users/:user", func(ctx godzilla.Context) { 50 | ctx.SendString(ctx.Param("user")) 51 | }) 52 | 53 | gz.Start(":8080") 54 | } 55 | ``` 56 | 57 | - static files 58 | ```golang 59 | package main 60 | 61 | import "github.com/godzillaframework/godzilla" 62 | 63 | func main() { 64 | gz := godzilla.New() 65 | 66 | gz.Static("/imgs", "./images") 67 | 68 | /* go to localhost:8080/imgs/image.png */ 69 | 70 | gz.Start(":8080") 71 | } 72 | ``` 73 | 74 | ## middleware: 75 | 76 | - Log middleware: 77 | ```golang 78 | package main 79 | 80 | import ( 81 | "log" 82 | 83 | "github.com/godzillaframework/godzilla" 84 | ) 85 | 86 | func main() { 87 | gz := godzilla.New() 88 | 89 | logMiddleware := func(ctx godzilla.Context) { 90 | log.Printf("log message!") 91 | 92 | ctx.Next() 93 | } 94 | 95 | gz.Use(logMiddleware) 96 | 97 | gz.Start(":8080") 98 | ``` 99 | 100 | - Unauthorized middleware: 101 | ```golang 102 | package main 103 | 104 | import ( 105 | "log" 106 | 107 | "github.com/godzillaframework/godzilla" 108 | ) 109 | 110 | func main() { 111 | 112 | gz := godzilla.New() 113 | 114 | unAuthorizedMiddleware := func(ctx godzilla.Context) { 115 | ctx.Status(godzilla.StatusUnauthorized).SendString("You are unauthorized to access this page!") 116 | } 117 | 118 | gz.Get("/hello", func(ctx godzilla.Context) { 119 | ctx.SendString("Hello World!") 120 | }) 121 | 122 | gz.Get("/protected", unAuthorizedMiddleware, func(ctx godzilla.Context) { 123 | ctx.SendString("You accessed a protected page") 124 | }) 125 | 126 | 127 | gz.Start(":8080") 128 | } 129 | 130 | ``` 131 | 132 | - example [app](https://github.com/godzillaframework/godzilla-app) 133 | 134 | - for more tutorials visit the [docs](https://github.com/godzillaframework/godzilla/blob/master/docs/learngodzilla.md) 135 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | package godzilla 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | jsoniter "github.com/json-iterator/go" 8 | "github.com/valyala/fasthttp" 9 | ) 10 | 11 | const ( 12 | MimeApplicationJSON = "application/json" 13 | ) 14 | 15 | type Context interface { 16 | Next() 17 | Context() *fasthttp.RequestCtx 18 | Param(key string) string 19 | Query(key string) string 20 | SendBytes(value []byte) Context 21 | SendString(value string) Context 22 | SendJSON(in interface{}) error 23 | Status(status int) Context 24 | Set(key string, value string) 25 | Get(key string) string 26 | SetLocal(key string, value interface{}) 27 | GetLocal(key string) interface{} 28 | Body() string 29 | ParseBody(out interface{}) error 30 | } 31 | 32 | type handlerFunc func(ctx Context) 33 | 34 | type handlersChain []handlerFunc 35 | 36 | type context struct { 37 | requestCtx *fasthttp.RequestCtx 38 | paramValues map[string]string 39 | handlers handlersChain 40 | index int 41 | } 42 | 43 | func (ctx *context) Next() { 44 | ctx.index++ 45 | if ctx.index < len(ctx.handlers) { 46 | ctx.handlers[ctx.index](ctx) 47 | } 48 | } 49 | 50 | func (ctx *context) Param(key string) string { 51 | return ctx.paramValues[key] 52 | } 53 | 54 | func (ctx *context) Context() *fasthttp.RequestCtx { 55 | return ctx.requestCtx 56 | } 57 | 58 | func (ctx *context) SendBytes(value []byte) Context { 59 | ctx.requestCtx.Response.SetBodyRaw(value) 60 | return ctx 61 | } 62 | 63 | func (ctx *context) SendString(value string) Context { 64 | ctx.requestCtx.SetBodyString(value) 65 | return ctx 66 | } 67 | 68 | func (ctx *context) SendJSON(in interface{}) error { 69 | json := jsoniter.ConfigCompatibleWithStandardLibrary 70 | raw, err := json.Marshal(in) 71 | 72 | if err != nil { 73 | return err 74 | } 75 | 76 | ctx.requestCtx.Response.Header.SetContentType(MimeApplicationJSON) 77 | ctx.requestCtx.Response.SetBodyRaw(raw) 78 | 79 | return nil 80 | } 81 | 82 | func (ctx *context) Status(status int) Context { 83 | ctx.requestCtx.Response.SetStatusCode(status) 84 | return ctx 85 | } 86 | 87 | func (ctx *context) Get(key string) string { 88 | return GetString(ctx.requestCtx.Request.Header.Peek(key)) 89 | } 90 | 91 | func (ctx *context) Set(key, value string) { 92 | ctx.requestCtx.Response.Header.Set(key, value) 93 | } 94 | 95 | func (ctx *context) Query(key string) string { 96 | return GetString(ctx.requestCtx.QueryArgs().Peek(key)) 97 | } 98 | 99 | func (ctx *context) Body() string { 100 | return GetString(ctx.requestCtx.Request.Body()) 101 | } 102 | 103 | func (ctx *context) SetLocal(key string, value interface{}) { 104 | ctx.requestCtx.SetUserValue(key, value) 105 | } 106 | 107 | func (ctx *context) GetLocal(key string) interface{} { 108 | return ctx.requestCtx.UserValue(key) 109 | } 110 | 111 | func (ctx *context) ParseBody(out interface{}) error { 112 | contentType := GetString(ctx.requestCtx.Request.Header.ContentType()) 113 | if strings.HasPrefix(contentType, MimeApplicationJSON) { 114 | json := jsoniter.ConfigCompatibleWithStandardLibrary 115 | return json.Unmarshal(ctx.requestCtx.Request.Body(), out) 116 | } 117 | 118 | return fmt.Errorf("content type '%s' is not supported, "+ 119 | "please open a request to support it "+ 120 | "(https://github.com/godzillaframework//godzilla/issues", 121 | contentType) 122 | } 123 | -------------------------------------------------------------------------------- /tree_test.go: -------------------------------------------------------------------------------- 1 | package godzilla 2 | 3 | import "testing" 4 | 5 | func catchPanic(f func()) (recv interface{}) { 6 | defer func() { 7 | recv = recover() 8 | }() 9 | 10 | f() 11 | return 12 | } 13 | 14 | type testRoute struct { 15 | path string 16 | conflict bool 17 | } 18 | 19 | func TestAddRoute(t *testing.T) { 20 | tree := createRootNode() 21 | 22 | routes := []testRoute{ 23 | {"/cmd/:tool/:sub", false}, 24 | {"/cmd/vet", true}, 25 | {"/src/*", false}, 26 | {"/src/*", true}, 27 | {"/src/test", true}, 28 | {"/src/:test", true}, 29 | {"/src/", false}, 30 | {"/src1/", false}, 31 | {"/src1/*", false}, 32 | {"/search/:query", false}, 33 | {"/search/invalid", true}, 34 | {"/user_:name", false}, 35 | {"/user_x", false}, 36 | {"/id:id", false}, 37 | {"/id/:id", false}, 38 | {"/id/:value", true}, 39 | {"/id/:id/settings", false}, 40 | {"/id/:id/:type", true}, 41 | {"/*", true}, 42 | {"books/*/get", true}, 43 | {"/file/test", false}, 44 | {"/file/test", true}, 45 | {"/file/:test", true}, 46 | {"/orders/:id/settings/:id", true}, 47 | {"/accounts/*/settings", true}, 48 | {"/results/*", false}, 49 | {"/results/*/view", true}, 50 | } 51 | for _, route := range routes { 52 | recv := catchPanic(func() { 53 | tree.addRoute(route.path, emptyHandlersChain) 54 | }) 55 | 56 | if route.conflict { 57 | if recv == nil { 58 | t.Errorf("no panic for conflicting route '%s'", route.path) 59 | } 60 | } else if recv != nil { 61 | t.Errorf("unexpected panic for route '%s': %v", route.path, recv) 62 | } 63 | } 64 | } 65 | 66 | type testRequests []struct { 67 | path string 68 | match bool 69 | params map[string]string 70 | } 71 | 72 | func TestMatchRoute(t *testing.T) { 73 | tree := createRootNode() 74 | 75 | routes := [...]string{ 76 | "/hi", 77 | "/contact", 78 | "/users/:id/", 79 | "/books/*", 80 | "/search/:item1/settings/:item2", 81 | "/co", 82 | "/c", 83 | "/a", 84 | "/ab", 85 | "/doc/", 86 | "/doc/go_faq.html", 87 | "/doc/go1.html", 88 | "/α", 89 | "/β", 90 | } 91 | for _, route := range routes { 92 | tree.addRoute(route, emptyHandlersChain) 93 | } 94 | 95 | requests := testRequests{ 96 | {"/a", true, nil}, 97 | {"/", false, nil}, 98 | {"/hi", true, nil}, 99 | {"/contact", true, nil}, 100 | {"/co", true, nil}, 101 | {"/con", false, nil}, // key mismatch 102 | {"/cona", false, nil}, // key mismatch 103 | {"/no", false, nil}, // no matching child 104 | {"/ab", true, nil}, 105 | {"/α", true, nil}, 106 | {"/β", true, nil}, 107 | {"/users/test", true, map[string]string{"id": "test"}}, 108 | {"/books/title", true, nil}, 109 | {"/search/test1/settings/test2", true, map[string]string{"item1": "test1", "item2": "test2"}}, 110 | {"/search/test1", false, nil}, 111 | {"test", false, nil}, 112 | } 113 | for _, request := range requests { 114 | ctx := &context{paramValues: make(map[string]string)} 115 | handler := tree.matchRoute(request.path, ctx) 116 | 117 | if handler == nil { 118 | if request.match { 119 | t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path) 120 | } 121 | } else if !request.match { 122 | t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path) 123 | } 124 | 125 | for expectedKey, expectedValue := range request.params { 126 | actualValue := ctx.Param(expectedKey) 127 | if actualValue != expectedValue { 128 | t.Errorf(" mismatch for route '%s' parameter '%s' actual '%s', expected '%s'", 129 | request.path, expectedKey, actualValue, expectedValue) 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /container/js/v8/v8.go: -------------------------------------------------------------------------------- 1 | /** 2 | @author: Krisna Pranav, GodzillaFrameworkDevelopers 3 | @filename: js/js.go 4 | 5 | Copyright [2021 - 2023] [Krisna Pranav, GodzillaFrameworkDeveloeprs] 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | package v8 21 | 22 | import ( 23 | "os" 24 | 25 | "github.com/livebud/bud/package/js" 26 | "go.kuoruan.net/v8go-polyfills/console" 27 | "go.kuoruan.net/v8go-polyfills/fetch" 28 | "go.kuoruan.net/v8go-polyfills/timers" 29 | "go.kuoruan.net/v8go-polyfills/url" 30 | "rogchap.com/v8go" 31 | ) 32 | 33 | type Value = v8go.Value 34 | type Error = v8go.JSError 35 | 36 | var _ js.VM = (*VM)(nil) 37 | 38 | type VM struct { 39 | isolate *v8go.Isolate 40 | context *v8go.Context 41 | } 42 | 43 | func (vm *VM) Eval(path, expr string) (string, error) { 44 | value, err := vm.context.RunScript(expr, path) 45 | if err != nil { 46 | return "", err 47 | } 48 | 49 | if value.IsPromise() { 50 | prom, err := value.AsPromise() 51 | if err != nil { 52 | return "", err 53 | } 54 | 55 | for prom.State() == v8go.Pending { 56 | continue 57 | } 58 | return prom.Result().String(), nil 59 | } 60 | return value.String(), nil 61 | } 62 | 63 | func Eval(path, code string) (string, error) { 64 | vm, err := Load() 65 | if err != nil { 66 | return "", err 67 | } 68 | return vm.Eval(path, code) 69 | } 70 | 71 | func load() (*v8go.Isolate, *v8go.Context, error) { 72 | isolate := v8go.NewIsolate() 73 | global := v8go.NewObjectTemplate(isolate) 74 | if err := fetch.InjectTo(isolate, global); err != nil { 75 | isolate.TerminateExecution() 76 | isolate.Dispose() 77 | return nil, nil, err 78 | } 79 | 80 | if err := timers.InjectTo(isolate, global); err != nil { 81 | isolate.TerminateExecution() 82 | isolate.Dispose() 83 | return nil, nil, err 84 | } 85 | 86 | context := v8go.NewContext(isolate, global) 87 | 88 | if err := url.InjectTo(context); err != nil { 89 | context.Close() 90 | isolate.TerminateExecution() 91 | isolate.Dispose() 92 | return nil, nil, err 93 | } 94 | 95 | if err := console.InjectMultipleTo(context, 96 | console.NewConsole(console.WithOutput(os.Stderr), console.WithMethodName("error")), 97 | console.NewConsole(console.WithOutput(os.Stderr), console.WithMethodName("warn")), 98 | console.NewConsole(console.WithOutput(os.Stdout), console.WithMethodName("log")), 99 | ); err != nil { 100 | context.Close() 101 | isolate.TerminateExecution() 102 | isolate.Dispose() 103 | return nil, nil, err 104 | } 105 | return isolate, context, nil 106 | } 107 | 108 | func Load() (*VM, error) { 109 | isolate, context, err := load() 110 | if err != nil { 111 | return nil, err 112 | } 113 | return &VM{ 114 | isolate: isolate, 115 | context: context, 116 | }, nil 117 | } 118 | 119 | func Compile(path, code string) (*VM, error) { 120 | isolate, context, err := load() 121 | if err != nil { 122 | return nil, err 123 | } 124 | script, err := isolate.CompileUnboundScript(code, path, v8go.CompileOptions{}) 125 | if err != nil { 126 | return nil, err 127 | } 128 | 129 | if _, err := script.Run(context); err != nil { 130 | return nil, err 131 | } 132 | return &VM{ 133 | isolate: isolate, 134 | context: context, 135 | }, nil 136 | } 137 | 138 | func (vm *VM) Script(path, code string) error { 139 | script, err := vm.isolate.CompileUnboundScript(code, path, v8go.CompileOptions{}) 140 | if err != nil { 141 | return err 142 | } 143 | 144 | if _, err := script.Run(vm.context); err != nil { 145 | return err 146 | } 147 | return nil 148 | } 149 | 150 | func (vm *VM) Close() error { 151 | vm.context.Close() 152 | vm.isolate.TerminateExecution() 153 | vm.isolate.Dispose() 154 | return nil 155 | } 156 | -------------------------------------------------------------------------------- /tree.go: -------------------------------------------------------------------------------- 1 | package godzilla 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type nodeType uint8 8 | 9 | const ( 10 | static nodeType = iota 11 | root 12 | param 13 | catchAll 14 | ) 15 | 16 | type node struct { 17 | path string 18 | param *node 19 | children map[string]*node 20 | nType nodeType 21 | handlers handlersChain 22 | } 23 | 24 | func (n *node) addRoute(path string, handlers handlersChain) { 25 | currentNode := n 26 | originalPath := path 27 | path = path[1:] 28 | 29 | paramNames := make(map[string]bool) 30 | 31 | for { 32 | pathLen := len(path) 33 | if pathLen == 0 { 34 | if currentNode.handlers != nil { 35 | panic("handlers are already registered for path '" + originalPath + "'") 36 | } 37 | 38 | routeHandlers := make(handlersChain, len(handlers)) 39 | copy(routeHandlers, handlers) 40 | 41 | currentNode.handlers = routeHandlers 42 | break 43 | } 44 | 45 | segmentDelimiter := strings.Index(path, "/") 46 | if segmentDelimiter == -1 { 47 | segmentDelimiter = pathLen 48 | } 49 | 50 | pathSegment := path[:segmentDelimiter] 51 | if pathSegment[0] == ':' || pathSegment[0] == '*' { 52 | 53 | if len(currentNode.children) > 0 { 54 | panic("parameter " + pathSegment + 55 | " conflicts with existing static children in path '" + 56 | originalPath + "'") 57 | } 58 | 59 | if currentNode.param != nil { 60 | if currentNode.param.path[0] == '*' { 61 | panic("parameter " + pathSegment + 62 | " conflicts with catch all (*) route in path '" + 63 | originalPath + "'") 64 | } else if currentNode.param.path != pathSegment { 65 | panic("parameter " + pathSegment + " in new path '" + 66 | originalPath + "' conflicts with existing wildcard '" + 67 | currentNode.param.path) 68 | } 69 | } 70 | 71 | if currentNode.param == nil { 72 | var nType nodeType 73 | if pathSegment[0] == '*' { 74 | nType = catchAll 75 | if pathLen > 1 { 76 | panic("catch all (*) routes are only allowed " + 77 | "at the end of the path in path '" + 78 | originalPath + "'") 79 | } 80 | } else { 81 | nType = param 82 | if _, ok := paramNames[pathSegment]; ok { 83 | panic("parameter " + pathSegment + 84 | " must be unique in path '" + originalPath + "'") 85 | } else { 86 | paramNames[pathSegment] = true 87 | } 88 | } 89 | 90 | currentNode.param = &node{ 91 | path: pathSegment, 92 | nType: nType, 93 | children: make(map[string]*node), 94 | } 95 | } 96 | currentNode = currentNode.param 97 | } else { 98 | 99 | if currentNode.param != nil { 100 | panic(pathSegment + "' conflicts with existing parameter " + 101 | currentNode.param.path + " in path '" + originalPath + "'") 102 | } 103 | if child, ok := currentNode.children[pathSegment]; ok { 104 | currentNode = child 105 | 106 | } else { 107 | child = &node{ 108 | path: pathSegment, 109 | nType: static, 110 | children: make(map[string]*node), 111 | } 112 | currentNode.children[pathSegment] = child 113 | currentNode = child 114 | } 115 | } 116 | 117 | if pathLen > segmentDelimiter { 118 | segmentDelimiter++ 119 | } 120 | path = path[segmentDelimiter:] 121 | } 122 | } 123 | 124 | func (n *node) matchRoute(path string, ctx *context) handlersChain { 125 | pathLen := len(path) 126 | if pathLen > 0 && path[0] != '/' { 127 | return nil 128 | } 129 | 130 | currentNode := n 131 | 132 | if pathLen > 0 { 133 | path = path[1:] 134 | } 135 | 136 | for { 137 | pathLen = len(path) 138 | 139 | if pathLen == 0 || currentNode.nType == catchAll { 140 | return currentNode.handlers 141 | } 142 | segmentDelimiter := strings.Index(path, "/") 143 | if segmentDelimiter == -1 { 144 | segmentDelimiter = pathLen 145 | } 146 | pathSegment := path[:segmentDelimiter] 147 | 148 | if pathLen > segmentDelimiter { 149 | segmentDelimiter++ 150 | } 151 | path = path[segmentDelimiter:] 152 | 153 | if currentNode.param != nil { 154 | currentNode = currentNode.param 155 | ctx.paramValues[currentNode.path[1:]] = pathSegment 156 | continue 157 | } 158 | 159 | if child, ok := currentNode.children[pathSegment]; ok { 160 | currentNode = child 161 | continue 162 | } 163 | 164 | return nil 165 | } 166 | } 167 | 168 | func createRootNode() *node { 169 | return &node{ 170 | nType: root, 171 | path: "/", 172 | children: make(map[string]*node), 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /router.go: -------------------------------------------------------------------------------- 1 | package godzilla 2 | 3 | import ( 4 | "log" 5 | "strings" 6 | "sync" 7 | 8 | "github.com/valyala/fasthttp" 9 | ) 10 | 11 | var ( 12 | defaultContentType = []byte("text/plain; charset=utf-8") 13 | ) 14 | 15 | type router struct { 16 | trees map[string]*node 17 | cache map[string]*matchResult 18 | cacheLen int 19 | mutex sync.RWMutex 20 | notFound handlersChain 21 | settings *Settings 22 | pool sync.Pool 23 | } 24 | 25 | type matchResult struct { 26 | handlers handlersChain 27 | params map[string]string 28 | } 29 | 30 | func (r *router) acquireCtx(fctx *fasthttp.RequestCtx) *context { 31 | ctx := r.pool.Get().(*context) 32 | 33 | ctx.index = 0 34 | ctx.paramValues = make(map[string]string) 35 | ctx.requestCtx = fctx 36 | 37 | return ctx 38 | } 39 | 40 | func (r *router) releaseCtx(ctx *context) { 41 | ctx.handlers = nil 42 | ctx.paramValues = nil 43 | ctx.requestCtx = nil 44 | r.pool.Put(ctx) 45 | } 46 | 47 | func (r *router) handle(method, path string, handlers handlersChain) { 48 | if path == "" { 49 | panic("path is empty") 50 | } else if method == "" { 51 | panic("method is empty") 52 | } else if path[0] != '/' { 53 | panic("path must begin with '/' in path '" + path + "'") 54 | } else if len(handlers) == 0 { 55 | panic("no handlers provided with path '" + path + "'") 56 | } 57 | 58 | if r.trees == nil { 59 | r.trees = make(map[string]*node) 60 | } 61 | 62 | root := r.trees[method] 63 | if root == nil { 64 | root = createRootNode() 65 | r.trees[method] = root 66 | } 67 | 68 | root.addRoute(path, handlers) 69 | } 70 | 71 | func (r *router) allowed(reqMethod, path string, ctx *context) string { 72 | var allow string 73 | 74 | pathLen := len(path) 75 | 76 | if (pathLen == 1 && path[0] == '*') || (pathLen > 1 && path[1] == '*') { 77 | for method := range r.trees { 78 | if method == MethodOptions { 79 | continue 80 | } 81 | 82 | if allow != "" { 83 | allow += ", " + method 84 | } else { 85 | allow = method 86 | } 87 | } 88 | return allow 89 | } 90 | 91 | for method, tree := range r.trees { 92 | if method == reqMethod || method == MethodOptions { 93 | continue 94 | } 95 | 96 | handlers := tree.matchRoute(path, ctx) 97 | if handlers != nil { 98 | if allow != "" { 99 | allow += ", " + method 100 | } else { 101 | allow = method 102 | } 103 | } 104 | } 105 | 106 | if len(allow) > 0 { 107 | allow += ", " + MethodOptions 108 | } 109 | return allow 110 | } 111 | 112 | func (r *router) Handler(fctx *fasthttp.RequestCtx) { 113 | context := r.acquireCtx(fctx) 114 | defer r.releaseCtx(context) 115 | 116 | if r.settings.AutoRecover { 117 | defer func(fctx *fasthttp.RequestCtx) { 118 | if rcv := recover(); rcv != nil { 119 | log.Printf("recovered from error: %v", rcv) 120 | fctx.Error(fasthttp.StatusMessage(fasthttp.StatusInternalServerError), 121 | fasthttp.StatusInternalServerError) 122 | } 123 | }(fctx) 124 | } 125 | 126 | path := GetString(fctx.URI().PathOriginal()) 127 | 128 | if r.settings.CaseInSensitive { 129 | path = strings.ToLower(path) 130 | } 131 | 132 | method := GetString(fctx.Method()) 133 | 134 | var cacheKey string 135 | useCache := !r.settings.DisableCaching && 136 | (method == MethodGet || method == MethodPost) 137 | if useCache { 138 | cacheKey = path + method 139 | r.mutex.RLock() 140 | cacheResult, ok := r.cache[cacheKey] 141 | 142 | if ok { 143 | context.handlers = cacheResult.handlers 144 | context.paramValues = cacheResult.params 145 | r.mutex.RUnlock() 146 | context.handlers[0](context) 147 | return 148 | } 149 | r.mutex.RUnlock() 150 | } 151 | 152 | if root := r.trees[method]; root != nil { 153 | if handlers := root.matchRoute(path, context); handlers != nil { 154 | context.handlers = handlers 155 | context.handlers[0](context) 156 | 157 | if useCache { 158 | r.mutex.Lock() 159 | 160 | if r.cacheLen == r.settings.CacheSize { 161 | r.cache = make(map[string]*matchResult) 162 | r.cacheLen = 0 163 | } 164 | r.cache[cacheKey] = &matchResult{ 165 | handlers: handlers, 166 | params: context.paramValues, 167 | } 168 | r.cacheLen++ 169 | r.mutex.Unlock() 170 | } 171 | return 172 | } 173 | } 174 | 175 | if method == MethodOptions && r.settings.HandleOPTIONS { 176 | if allow := r.allowed(method, path, context); len(allow) > 0 { 177 | fctx.Response.Header.Set("Allow", allow) 178 | return 179 | } 180 | } else if r.settings.HandleMethodNotAllowed { 181 | if allow := r.allowed(method, path, context); len(allow) > 0 { 182 | fctx.Response.Header.Set("Allow", allow) 183 | fctx.SetStatusCode(fasthttp.StatusMethodNotAllowed) 184 | fctx.SetContentTypeBytes(defaultContentType) 185 | fctx.SetBodyString(fasthttp.StatusMessage(fasthttp.StatusMethodNotAllowed)) 186 | return 187 | } 188 | } 189 | 190 | if r.notFound != nil { 191 | r.notFound[0](context) 192 | return 193 | } 194 | 195 | fctx.Error(fasthttp.StatusMessage(fasthttp.StatusNotFound), 196 | fasthttp.StatusNotFound) 197 | } 198 | 199 | func (r *router) SetNotFound(handlers handlersChain) { 200 | r.notFound = append(r.notFound, handlers...) 201 | } 202 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Bowery/prompt v0.0.0-20190916142128-fa8279994f75/go.mod h1:4/6eNcqZ09BZ9wLK3tZOjBA1nDj+B0728nlX5YRlSmQ= 2 | github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 3 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 4 | github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= 5 | github.com/RyanCarrier/dijkstra v1.1.0/go.mod h1:5agGUBNEtUAGIANmbw09fuO3a2htPEkc1jNH01qxCWA= 6 | github.com/RyanCarrier/dijkstra-1 v0.0.0-20170512020943-0e5801a26345/go.mod h1:OK4EvWJ441LQqGzed5NGB6vKBAE34n3z7iayPcEwr30= 7 | github.com/ajg/form v1.5.2-0.20200323032839-9aeb3cf462e1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= 8 | github.com/albertorestifo/dijkstra v0.0.0-20160910063646-aba76f725f72/go.mod h1:o+JdB7VetTHjLhU0N57x18B9voDBQe0paApdEAEoEfw= 9 | github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= 10 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 11 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 12 | github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= 13 | github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 14 | github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 h1:WWB576BN5zNSZc/M9d/10pqEx5VHNhaQ/yOVAkmj5Yo= 15 | github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= 16 | github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= 17 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 18 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 19 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 21 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 22 | github.com/evanw/esbuild v0.14.11/go.mod h1:GG+zjdi59yh3ehDn4ZWfPcATxjPDUH53iU4ZJbp7dkY= 23 | github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= 24 | github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= 25 | github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= 26 | github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813/go.mod h1:P+oSoE9yhSRvsmYyZsshflcR6ePWYLql6UU1amW13IM= 27 | github.com/gitchander/permutation v0.0.0-20201214100618-1f3e7285f953/go.mod h1:lP+DW8LR6Rw3ru9Vo2/y/3iiLaLWmofYql/va+7zJOk= 28 | github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 29 | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 30 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 31 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 32 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 33 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 34 | github.com/hexops/autogold v0.8.1/go.mod h1:97HLDXyG23akzAoRYJh/2OBs3kd80eHyKPvZw0S5ZBY= 35 | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= 36 | github.com/hexops/valast v1.4.1/go.mod h1:G+D6TExWuKs5he+hYlPMfYyhQ8w8qbc2vm4gDWwLdDg= 37 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 38 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 39 | github.com/keegancsmith/rpc v1.3.0/go.mod h1:6O2xnOGjPyvIPbvp0MdrOe5r6cu1GZ4JoTzpzDhWeo0= 40 | github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= 41 | github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= 42 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 43 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 44 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 45 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 46 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 47 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 48 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 49 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 50 | github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= 51 | github.com/livebud/bud v0.1.3/go.mod h1:YYfDmLuqq8nP5WzgCoA9xDNOeJaz9Zml+2uaglvR6AE= 52 | github.com/livebud/bud v0.2.8 h1:olSWt3LV0W7Akm070cdzTlGV+bj1p+OMQC4XgosRtOk= 53 | github.com/livebud/bud v0.2.8/go.mod h1:TuOr0ACyv9OfZ7fdYJbfOAK0wqfdNZ9IEaMlUjpqqmU= 54 | github.com/livebud/bud-test-nested-plugin v0.0.5/go.mod h1:M3QujkGG4ggZ6h75t5zF8MEJFrLTwa2USeIYHQdO2YQ= 55 | github.com/livebud/bud-test-plugin v0.0.9/go.mod h1:GTxMZ8W4BIyGIOgAA4hvPHMDDTkaZtfcuhnOcSu3y8M= 56 | github.com/livebud/transpiler v0.0.1/go.mod h1:vQYMN//Y2cnM55tw0lOmLGbEETugP7alxTyhQHzNdTI= 57 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= 58 | github.com/matthewmueller/diff v0.0.0-20220104030700-cb2fe910d90c/go.mod h1:xIxeV0n2W+WNB3ik/j9lUkcrQAk71VrvT9ch8u4pcvM= 59 | github.com/matthewmueller/gotext v0.0.0-20210424201144-265ed61725ac/go.mod h1:0mnotoJNdO4NPxld/GcrLppxwGWuMqgroWyu413oOvw= 60 | github.com/matthewmueller/text v0.0.0-20201215225457-a00346c71bb3/go.mod h1:vtPaEU72VzARd4tSSzHIX7DddCEamoO2X4ozlrdmtNY= 61 | github.com/matthewmueller/text v0.0.0-20210424201111-ec1e4af8dfe8/go.mod h1:vtPaEU72VzARd4tSSzHIX7DddCEamoO2X4ozlrdmtNY= 62 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 63 | github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 64 | github.com/mattomatic/dijkstra v0.0.0-20130617153013-6f6d134eb237/go.mod h1:UOnLAUmVG5paym8pD3C4B9BQylUDC2vXFJJpT7JrlEA= 65 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 66 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 67 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 68 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 69 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 70 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= 71 | github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= 72 | github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= 73 | github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= 74 | github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= 75 | github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= 76 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 77 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 78 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 79 | github.com/pointlander/compress v1.1.1-0.20190518213731-ff44bd196cc3/go.mod h1:q5NXNGzqj5uPnVuhGkZfmgHqNUhf15VLi6L9kW0VEc0= 80 | github.com/pointlander/jetset v1.0.1-0.20190518214125-eee7eff80bd4/go.mod h1:RdR1j20Aj5pB6+fw6Y9Ur7lMHpegTEjY1vc19hEZL40= 81 | github.com/pointlander/peg v1.0.1/go.mod h1:5hsGDQR2oZI4QoWz0/Kdg3VSVEC31iJw/b7WjqCBGRI= 82 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 83 | github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= 84 | github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= 85 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 86 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= 87 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= 88 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 89 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 90 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= 91 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 92 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 93 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 94 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 95 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 96 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 97 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 98 | github.com/timewasted/go-accept-headers v0.0.0-20130320203746-c78f304b1b09/go.mod h1:Uy/Rnv5WKuOO+PuDhuYLEpUiiKIZtss3z519uk67aF0= 99 | github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= 100 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 101 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 102 | github.com/valyala/fasthttp v1.31.0 h1:lrauRLII19afgCs2fnWRJ4M5IkV0lo2FqA61uGkNBfE= 103 | github.com/valyala/fasthttp v1.31.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= 104 | github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= 105 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 106 | github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= 107 | github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 108 | go.kuoruan.net/v8go-polyfills v0.5.0/go.mod h1:egHzK8RIHR7dPOYzhnRsomClFTVmYCtvhTWqec4JXaY= 109 | go.kuoruan.net/v8go-polyfills v0.5.1-0.20220727011656-c74c5b408ebd h1:lMfOO39WTD+CxBPmqZvLdISrLVsEjgNfWoV4viBt15M= 110 | go.kuoruan.net/v8go-polyfills v0.5.1-0.20220727011656-c74c5b408ebd/go.mod h1:egHzK8RIHR7dPOYzhnRsomClFTVmYCtvhTWqec4JXaY= 111 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 112 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 113 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 114 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= 115 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 116 | golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= 117 | golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= 118 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 119 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 120 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 121 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 122 | golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 123 | golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 124 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 125 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 126 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 127 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 128 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 129 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 130 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 131 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 132 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 133 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 134 | golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 135 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 136 | golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 137 | golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 138 | golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 139 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= 140 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 141 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 142 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 143 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 144 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 145 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 146 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 147 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 148 | golang.org/x/tools v0.1.8-0.20211102182255-bb4add04ddef/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= 149 | golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= 150 | golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= 151 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 152 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 153 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 154 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 155 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 156 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 157 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 158 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 159 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 160 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 161 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 162 | gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 163 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 164 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 165 | honnef.co/go/tools v0.3.3/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw= 166 | mvdan.cc/gofumpt v0.2.0/go.mod h1:TiGmrf914DAuT6+hDIxOqoDb4QXIzAuEUSXqEf9hGKY= 167 | rogchap.com/v8go v0.7.0/go.mod h1:MxgP3pL2MW4dpme/72QRs8sgNMmM0pRc8DPhcuLWPAs= 168 | rogchap.com/v8go v0.9.0 h1:wYbUCO4h6fjTamziHrzyrPnpFNuzPpjZY+nfmZjNaew= 169 | rogchap.com/v8go v0.9.0/go.mod h1:MxgP3pL2MW4dpme/72QRs8sgNMmM0pRc8DPhcuLWPAs= 170 | src.techknowlogick.com/xgo v1.4.1-0.20220413212431-091a0a22b814/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU= 171 | -------------------------------------------------------------------------------- /godzilla.go: -------------------------------------------------------------------------------- 1 | package godzilla 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "os" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | "github.com/valyala/fasthttp" 12 | "github.com/valyala/fasthttp/prefork" 13 | ) 14 | 15 | const ( 16 | version = "2.1.0" 17 | name = "Godzilla Framework" 18 | 19 | banner = ` 20 | Server Running on %s` 21 | ) 22 | 23 | const ( 24 | // defaultCacheSize is number of entries that can cache hold 25 | defaultCacheSize = 1000 26 | 27 | // defaultConcurrency is the maximum number of concurrent connections 28 | defaultConcurrency = 256 * 1024 29 | 30 | // defaultMaxRequestBodySize is the maximum request body size the server 31 | defaultMaxRequestBodySize = 4 * 1024 * 1024 32 | 33 | // defaultMaxRouteParams is the maximum number of routes params 34 | defaultMaxRouteParams = 1024 35 | 36 | // defaultMaxRequestURLLength is the maximum request url length 37 | defaultMaxRequestURLLength = 2048 38 | ) 39 | 40 | // HTTP methods were copied from net/http. 41 | const ( 42 | MethodGet = "GET" // RFC 7231, 4.3.1 43 | MethodHead = "HEAD" // RFC 7231, 4.3.2 44 | MethodPost = "POST" // RFC 7231, 4.3.3 45 | MethodPut = "PUT" // RFC 7231, 4.3.4 46 | MethodPatch = "PATCH" // RFC 5789 47 | MethodDelete = "DELETE" // RFC 7231, 4.3.5 48 | MethodConnect = "CONNECT" // RFC 7231, 4.3.6 49 | MethodOptions = "OPTIONS" // RFC 7231, 4.3.7 50 | MethodTrace = "TRACE" // RFC 7231, 4.3.8 51 | ) 52 | 53 | // HTTP status codes were copied from net/http. 54 | const ( 55 | StatusContinue = 100 // RFC 7231, 6.2.1 56 | StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2 57 | StatusProcessing = 102 // RFC 2518, 10.1 58 | 59 | StatusOK = 200 // RFC 7231, 6.3.1 60 | StatusCreated = 201 // RFC 7231, 6.3.2 61 | StatusAccepted = 202 // RFC 7231, 6.3.3 62 | StatusNonAuthoritativeInfo = 203 // RFC 7231, 6.3.4 63 | StatusNoContent = 204 // RFC 7231, 6.3.5 64 | StatusResetContent = 205 // RFC 7231, 6.3.6 65 | StatusPartialContent = 206 // RFC 7233, 4.1 66 | StatusMultiStatus = 207 // RFC 4918, 11.1 67 | StatusAlreadyReported = 208 // RFC 5842, 7.1 68 | StatusIMUsed = 226 // RFC 3229, 10.4.1 69 | 70 | StatusMultipleChoices = 300 // RFC 7231, 6.4.1 71 | StatusMovedPermanently = 301 // RFC 7231, 6.4.2 72 | StatusFound = 302 // RFC 7231, 6.4.3 73 | StatusSeeOther = 303 // RFC 7231, 6.4.4 74 | StatusNotModified = 304 // RFC 7232, 4.1 75 | StatusUseProxy = 305 // RFC 7231, 6.4.5 76 | _ = 306 // RFC 7231, 6.4.6 (Unused) 77 | StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7 78 | StatusPermanentRedirect = 308 // RFC 7538, 3 79 | 80 | StatusBadRequest = 400 // RFC 7231, 6.5.1 81 | StatusUnauthorized = 401 // RFC 7235, 3.1 82 | StatusPaymentRequired = 402 // RFC 7231, 6.5.2 83 | StatusForbidden = 403 // RFC 7231, 6.5.3 84 | StatusNotFound = 404 // RFC 7231, 6.5.4 85 | StatusMethodNotAllowed = 405 // RFC 7231, 6.5.5 86 | StatusNotAcceptable = 406 // RFC 7231, 6.5.6 87 | StatusProxyAuthRequired = 407 // RFC 7235, 3.2 88 | StatusRequestTimeout = 408 // RFC 7231, 6.5.7 89 | StatusConflict = 409 // RFC 7231, 6.5.8 90 | StatusGone = 410 // RFC 7231, 6.5.9 91 | StatusLengthRequired = 411 // RFC 7231, 6.5.10 92 | StatusPreconditionFailed = 412 // RFC 7232, 4.2 93 | StatusRequestEntityTooLarge = 413 // RFC 7231, 6.5.11 94 | StatusRequestURITooLong = 414 // RFC 7231, 6.5.12 95 | StatusUnsupportedMediaType = 415 // RFC 7231, 6.5.13 96 | StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4 97 | StatusExpectationFailed = 417 // RFC 7231, 6.5.14 98 | StatusTeapot = 418 // RFC 7168, 2.3.3 99 | StatusUnprocessableEntity = 422 // RFC 4918, 11.2 100 | StatusLocked = 423 // RFC 4918, 11.3 101 | StatusFailedDependency = 424 // RFC 4918, 11.4 102 | StatusUpgradeRequired = 426 // RFC 7231, 6.5.15 103 | StatusPreconditionRequired = 428 // RFC 6585, 3 104 | StatusTooManyRequests = 429 // RFC 6585, 4 105 | StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5 106 | StatusUnavailableForLegalReasons = 451 // RFC 7725, 3 107 | 108 | StatusInternalServerError = 500 // RFC 7231, 6.6.1 109 | StatusNotImplemented = 501 // RFC 7231, 6.6.2 110 | StatusBadGateway = 502 // RFC 7231, 6.6.3 111 | StatusServiceUnavailable = 503 // RFC 7231, 6.6.4 112 | StatusGatewayTimeout = 504 // RFC 7231, 6.6.5 113 | StatusHTTPVersionNotSupported = 505 // RFC 7231, 6.6.6 114 | StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1 115 | StatusInsufficientStorage = 507 // RFC 4918, 11.5 116 | StatusLoopDetected = 508 // RFC 5842, 7.2 117 | StatusNotExtended = 510 // RFC 2774, 7 118 | StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6 119 | ) 120 | 121 | type Godzilla interface { 122 | Start(address string) error 123 | Stop() error 124 | Get(path string, handlers ...handlerFunc) *Route 125 | Head(path string, handlers ...handlerFunc) *Route 126 | Post(path string, handlers ...handlerFunc) *Route 127 | Put(path string, handlers ...handlerFunc) *Route 128 | Patch(path string, handlers ...handlerFunc) *Route 129 | Delete(path string, handlers ...handlerFunc) *Route 130 | Connect(path string, handlers ...handlerFunc) *Route 131 | Options(path string, handlers ...handlerFunc) *Route 132 | Trace(path string, handlers ...handlerFunc) *Route 133 | Group(prefix string, routes []*Route) []*Route 134 | Static(prefix, root string) 135 | NotFound(handlers ...handlerFunc) 136 | Use(middlewares ...handlerFunc) 137 | } 138 | 139 | type godzilla struct { 140 | httpServer *fasthttp.Server 141 | router *router 142 | registeredRoutes []*Route 143 | address string // server address 144 | middlewares handlersChain 145 | settings *Settings 146 | } 147 | 148 | // Settings struct holds server settings 149 | type Settings struct { 150 | ReadBufferSize int 151 | 152 | CaseInSensitive bool 153 | 154 | CacheSize int 155 | 156 | HandleMethodNotAllowed bool 157 | 158 | HandleOPTIONS bool 159 | 160 | AutoRecover bool // default false 161 | 162 | // ServerName for sending in response headers 163 | ServerName string // default "" 164 | 165 | // Maximum request body size 166 | MaxRequestBodySize int // default 4 * 1024 * 1024 167 | 168 | // Maximum number of route params count 169 | MaxRouteParams int // default 1024 170 | 171 | // Max request url length 172 | MaxRequestURLLength int // default 2048 173 | 174 | // Maximum number of concurrent connections 175 | Concurrency int // default 256 * 1024 176 | 177 | // This will spawn multiple Go processes listening on the same port 178 | // Default: false 179 | Prefork bool 180 | 181 | // LRU caching used to speed up routing 182 | DisableCaching bool // default false 183 | 184 | DisableStartupMessage bool // default false 185 | 186 | // Disable keep-alive connections, the server will close incoming connections after sending the first response to client 187 | DisableKeepalive bool // default false 188 | 189 | // When set to true causes the default date header to be excluded from the response 190 | DisableDefaultDate bool // default false 191 | 192 | // When set to true, causes the default Content-Type header to be excluded from the Response 193 | DisableDefaultContentType bool // default false 194 | 195 | // By default all header names are normalized: conteNT-tYPE -> Content-Type 196 | DisableHeaderNormalizing bool // default false 197 | 198 | // The amount of time allowed to read the full request including body 199 | ReadTimeout time.Duration // default unlimited 200 | 201 | // The maximum duration before timing out writes of the response 202 | WriteTimeout time.Duration // default unlimited 203 | 204 | // The maximum amount of time to wait for the next request when keep-alive is enabled 205 | IdleTimeout time.Duration // default unlimited 206 | 207 | // Enable TLS or not 208 | TLSEnabled bool // default false 209 | 210 | // The path of the TLS certificate 211 | TLSCertPath string // default "" 212 | 213 | // The path of the TLS key 214 | TLSKeyPath string // default "" 215 | } 216 | 217 | // Route struct which holds each route info 218 | type Route struct { 219 | Method string 220 | Path string 221 | Handlers handlersChain 222 | } 223 | 224 | func New(settings ...*Settings) Godzilla { 225 | gz := new(godzilla) 226 | gz.registeredRoutes = make([]*Route, 0) 227 | 228 | if len(settings) > 0 { 229 | gz.settings = settings[0] 230 | } else { 231 | gz.settings = &Settings{} 232 | } 233 | 234 | // set default settings for settings that don't have values set 235 | if gz.settings.CacheSize <= 0 { 236 | gz.settings.CacheSize = defaultCacheSize 237 | } 238 | 239 | if gz.settings.MaxRequestBodySize <= 0 { 240 | gz.settings.MaxRequestBodySize = defaultMaxRequestBodySize 241 | } 242 | 243 | if gz.settings.MaxRouteParams <= 0 || gz.settings.MaxRouteParams > defaultMaxRouteParams { 244 | gz.settings.MaxRouteParams = defaultMaxRouteParams 245 | } 246 | 247 | if gz.settings.MaxRequestURLLength <= 0 || gz.settings.MaxRequestURLLength > defaultMaxRequestURLLength { 248 | gz.settings.MaxRequestURLLength = defaultMaxRequestURLLength 249 | } 250 | 251 | if gz.settings.Concurrency <= 0 { 252 | gz.settings.Concurrency = defaultConcurrency 253 | } 254 | 255 | // Initialize router 256 | gz.router = &router{ 257 | settings: gz.settings, 258 | cache: make(map[string]*matchResult), 259 | pool: sync.Pool{ 260 | New: func() interface{} { 261 | return new(context) 262 | }, 263 | }, 264 | } 265 | 266 | gz.httpServer = gz.newHTTPServer() 267 | 268 | return gz 269 | } 270 | 271 | // Start handling requests 272 | func (gz *godzilla) Start(address string) error { 273 | // Setup router 274 | gz.setupRouter() 275 | 276 | if gz.settings.Prefork { 277 | if !gz.settings.DisableStartupMessage { 278 | printStartupMessage(address) 279 | } 280 | 281 | pf := prefork.New(gz.httpServer) 282 | pf.Reuseport = true 283 | pf.Network = "tcp4" 284 | 285 | if gz.settings.TLSEnabled { 286 | return pf.ListenAndServeTLS(address, gz.settings.TLSCertPath, gz.settings.TLSKeyPath) 287 | } 288 | return pf.ListenAndServe(address) 289 | } 290 | 291 | ln, err := net.Listen("tcp4", address) 292 | if err != nil { 293 | return err 294 | } 295 | gz.address = address 296 | 297 | if !gz.settings.DisableStartupMessage { 298 | printStartupMessage(address) 299 | } 300 | 301 | if gz.settings.TLSEnabled { 302 | return gz.httpServer.ServeTLS(ln, gz.settings.TLSCertPath, gz.settings.TLSKeyPath) 303 | } 304 | return gz.httpServer.Serve(ln) 305 | } 306 | 307 | // customLogger Customized logger used to filter logging messages 308 | type customLogger struct{} 309 | 310 | func (dl *customLogger) Printf(format string, args ...interface{}) { 311 | } 312 | 313 | // newHTTPServer returns a new instance of fasthttp server 314 | func (gz *godzilla) newHTTPServer() *fasthttp.Server { 315 | return &fasthttp.Server{ 316 | Handler: gz.router.Handler, 317 | Logger: &customLogger{}, 318 | LogAllErrors: false, 319 | Name: gz.settings.ServerName, 320 | Concurrency: gz.settings.Concurrency, 321 | NoDefaultDate: gz.settings.DisableDefaultDate, 322 | NoDefaultContentType: gz.settings.DisableDefaultContentType, 323 | DisableHeaderNamesNormalizing: gz.settings.DisableHeaderNormalizing, 324 | DisableKeepalive: gz.settings.DisableKeepalive, 325 | NoDefaultServerHeader: gz.settings.ServerName == "", 326 | ReadTimeout: gz.settings.ReadTimeout, 327 | WriteTimeout: gz.settings.WriteTimeout, 328 | IdleTimeout: gz.settings.IdleTimeout, 329 | ReadBufferSize: gz.settings.ReadBufferSize, 330 | } 331 | } 332 | 333 | // registerRoute registers handlers with method and path 334 | func (gz *godzilla) registerRoute(method, path string, handlers handlersChain) *Route { 335 | if gz.settings.CaseInSensitive { 336 | path = strings.ToLower(path) 337 | } 338 | 339 | route := &Route{ 340 | Path: path, 341 | Method: method, 342 | Handlers: handlers, 343 | } 344 | 345 | // Add route to registered routes 346 | gz.registeredRoutes = append(gz.registeredRoutes, route) 347 | return route 348 | } 349 | 350 | // setupRouter initializes router with registered routes 351 | func (gz *godzilla) setupRouter() { 352 | for _, route := range gz.registeredRoutes { 353 | gz.router.handle(route.Method, route.Path, append(gz.middlewares, route.Handlers...)) 354 | } 355 | 356 | // Frees intermediate stores after initializing router 357 | gz.registeredRoutes = nil 358 | gz.middlewares = nil 359 | } 360 | 361 | // Stop serving 362 | func (gz *godzilla) Stop() error { 363 | err := gz.httpServer.Shutdown() 364 | 365 | // check if shutdown was ok and server had valid address 366 | if err == nil && gz.address != "" { 367 | log.Printf("%s stopped listening on %s", name, gz.address) 368 | return nil 369 | } 370 | 371 | return err 372 | } 373 | 374 | // Get registers an http relevant method 375 | func (gz *godzilla) Get(path string, handlers ...handlerFunc) *Route { 376 | return gz.registerRoute(MethodGet, path, handlers) 377 | } 378 | 379 | // Head registers an http relevant method 380 | func (gz *godzilla) Head(path string, handlers ...handlerFunc) *Route { 381 | return gz.registerRoute(MethodHead, path, handlers) 382 | } 383 | 384 | // Post registers an http relevant method 385 | func (gz *godzilla) Post(path string, handlers ...handlerFunc) *Route { 386 | return gz.registerRoute(MethodPost, path, handlers) 387 | } 388 | 389 | // Put registers an http relevant method 390 | func (gz *godzilla) Put(path string, handlers ...handlerFunc) *Route { 391 | return gz.registerRoute(MethodPut, path, handlers) 392 | } 393 | 394 | // Patch registers an http relevant method 395 | func (gz *godzilla) Patch(path string, handlers ...handlerFunc) *Route { 396 | return gz.registerRoute(MethodPatch, path, handlers) 397 | } 398 | 399 | // Delete registers an http relevant method 400 | func (gz *godzilla) Delete(path string, handlers ...handlerFunc) *Route { 401 | return gz.registerRoute(MethodDelete, path, handlers) 402 | } 403 | 404 | // Connect registers an http relevant method 405 | func (gz *godzilla) Connect(path string, handlers ...handlerFunc) *Route { 406 | return gz.registerRoute(MethodConnect, path, handlers) 407 | } 408 | 409 | // Options registers an http relevant method 410 | func (gz *godzilla) Options(path string, handlers ...handlerFunc) *Route { 411 | return gz.registerRoute(MethodOptions, path, handlers) 412 | } 413 | 414 | // Trace registers an http relevant method 415 | func (gz *godzilla) Trace(path string, handlers ...handlerFunc) *Route { 416 | return gz.registerRoute(MethodTrace, path, handlers) 417 | } 418 | 419 | // Group appends a prefix to registered routes 420 | func (gz *godzilla) Group(prefix string, routes []*Route) []*Route { 421 | for _, route := range routes { 422 | route.Path = prefix + route.Path 423 | } 424 | return routes 425 | } 426 | 427 | // Static serves files in root directory under specific prefix 428 | func (gz *godzilla) Static(prefix, root string) { 429 | if gz.settings.CaseInSensitive { 430 | prefix = strings.ToLower(prefix) 431 | } 432 | 433 | // remove trailing slash 434 | if len(root) > 1 && root[len(root)-1] == '/' { 435 | root = root[:len(root)-1] 436 | } 437 | 438 | if len(prefix) > 1 && prefix[len(prefix)-1] == '/' { 439 | prefix = prefix[:len(prefix)-1] 440 | } 441 | 442 | fs := &fasthttp.FS{ 443 | Root: root, 444 | IndexNames: []string{"index.html"}, 445 | PathRewrite: func(ctx *fasthttp.RequestCtx) []byte { 446 | path := ctx.Path() 447 | 448 | if len(path) >= len(prefix) { 449 | path = path[len(prefix):] 450 | } 451 | 452 | if len(path) > 0 && path[0] != '/' { 453 | path = append([]byte("/"), path...) 454 | } else if len(path) == 0 { 455 | path = []byte("/") 456 | } 457 | return path 458 | }, 459 | } 460 | 461 | fileHandler := fs.NewRequestHandler() 462 | handler := func(ctx Context) { 463 | fctx := ctx.Context() 464 | 465 | fileHandler(fctx) 466 | 467 | status := fctx.Response.StatusCode() 468 | if status != StatusNotFound && status != StatusForbidden { 469 | return 470 | } 471 | 472 | // Pass to custom not found handlers if there are 473 | if gz.router.notFound != nil { 474 | gz.router.notFound[0](ctx) 475 | return 476 | } 477 | 478 | // Default Not Found response 479 | fctx.Error(fasthttp.StatusMessage(fasthttp.StatusNotFound), 480 | fasthttp.StatusNotFound) 481 | } 482 | 483 | // TODO: Improve 484 | gz.Get(prefix, handler) 485 | 486 | if len(prefix) > 1 && prefix[len(prefix)-1] != '*' { 487 | gz.Get(prefix+"/*", handler) 488 | } 489 | } 490 | 491 | // NotFound registers an http handlers that will be called when no other routes 492 | // match with request 493 | func (gz *godzilla) NotFound(handlers ...handlerFunc) { 494 | gz.router.SetNotFound(handlers) 495 | } 496 | 497 | func (gz *godzilla) Use(middlewares ...handlerFunc) { 498 | gz.middlewares = append(gz.middlewares, middlewares...) 499 | } 500 | 501 | func printStartupMessage(addr string) { 502 | if prefork.IsChild() { 503 | log.Printf("Started child proc #%v\n", os.Getpid()) 504 | } else { 505 | log.Println(banner, addr, version) 506 | } 507 | } 508 | -------------------------------------------------------------------------------- /godzilla_test.go: -------------------------------------------------------------------------------- 1 | package godzilla 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/tls" 7 | "io/ioutil" 8 | "net" 9 | "net/http" 10 | "net/http/httputil" 11 | "strconv" 12 | "strings" 13 | "sync" 14 | "testing" 15 | "time" 16 | 17 | "github.com/valyala/fasthttp" 18 | ) 19 | 20 | type fakeConn struct { 21 | net.Conn 22 | r bytes.Buffer 23 | w bytes.Buffer 24 | } 25 | 26 | func (c *fakeConn) Close() error { 27 | return nil 28 | } 29 | 30 | func (c *fakeConn) Read(b []byte) (int, error) { 31 | return c.r.Read(b) 32 | } 33 | 34 | func (c *fakeConn) Write(b []byte) (int, error) { 35 | return c.w.Write(b) 36 | } 37 | 38 | func setupGodzilla(settings ...*Settings) *godzilla { 39 | gz := new(godzilla) 40 | gz.registeredRoutes = make([]*Route, 0) 41 | 42 | if len(settings) > 0 { 43 | gz.settings = settings[0] 44 | } else { 45 | gz.settings = &Settings{} 46 | } 47 | 48 | gz.router = &router{ 49 | settings: gz.settings, 50 | cache: make(map[string]*matchResult), 51 | pool: sync.Pool{ 52 | New: func() interface{} { 53 | return new(context) 54 | }, 55 | }, 56 | } 57 | 58 | return gz 59 | } 60 | 61 | func startGodzilla(gz *godzilla) { 62 | gz.setupRouter() 63 | gz.httpServer = &fasthttp.Server{ 64 | Handler: gz.router.Handler, 65 | Logger: &customLogger{}, 66 | LogAllErrors: false, 67 | } 68 | } 69 | 70 | // emptyHandler just an empty handler 71 | var emptyHandler = func(ctx Context) {} 72 | 73 | // empty Handlers chain is just an empty array 74 | var emptyHandlersChain = handlersChain{} 75 | 76 | var fakeHandlersChain = handlersChain{emptyHandler} 77 | 78 | // makeRequest makes an http request to http server and returns response or error 79 | func makeRequest(request *http.Request, gz *godzilla) (*http.Response, error) { 80 | // Dump request to send it 81 | dumpRequest, err := httputil.DumpRequest(request, true) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | // Write request to the connection 87 | c := &fakeConn{} 88 | if _, err = c.r.Write(dumpRequest); err != nil { 89 | return nil, err 90 | } 91 | 92 | // Handling connection 93 | ch := make(chan error) 94 | go func() { 95 | ch <- gz.httpServer.ServeConn(c) 96 | }() 97 | 98 | if err = <-ch; err != nil { 99 | return nil, err 100 | } 101 | 102 | // Parse response 103 | buffer := bufio.NewReader(&c.w) 104 | resp, err := http.ReadResponse(buffer, request) 105 | if err != nil { 106 | return nil, err 107 | } 108 | return resp, nil 109 | } 110 | 111 | // handler just an empty handler 112 | var handler = func(ctx Context) {} 113 | 114 | // errorHandler contains buggy code 115 | var errorHandler = func(ctx Context) { 116 | m := make(map[string]int) 117 | m["a"] = 0 118 | ctx.SendString(string(rune(5 / m["a"]))) 119 | } 120 | 121 | // headerHandler echos header's value of key "my-header" 122 | var headerHandler = func(ctx Context) { 123 | ctx.Set("custom", ctx.Get("my-header")) 124 | } 125 | 126 | // queryHandler answers with query's value of key "name" 127 | var queryHandler = func(ctx Context) { 128 | ctx.SendString(ctx.Query("name")) 129 | } 130 | 131 | // bodyHandler answers with request body 132 | var bodyHandler = func(ctx Context) { 133 | ctx.Context().Response.SetBodyString(ctx.Body()) 134 | } 135 | 136 | // unAuthorizedHandler sets status unauthorized in response 137 | var unAuthorizedHandler = func(ctx Context) { 138 | ctx.Status(StatusUnauthorized) 139 | } 140 | 141 | // pingHandler returns string pong in response body 142 | var pingHandler = func(ctx Context) { 143 | ctx.SendString("pong") 144 | } 145 | 146 | // fallbackHandler returns not found status with custom fallback handler in response body 147 | var fallbackHandler = func(ctx Context) { 148 | ctx.Status(StatusNotFound).SendString("custom fallback handler") 149 | } 150 | 151 | // emptyMiddleware does not stop the request and passes it to the next middleware/handler 152 | var emptyMiddleware = func(ctx Context) { 153 | // Try to set user data 154 | ctx.SetLocal("test-key", "value") 155 | 156 | ctx.Next() 157 | } 158 | 159 | // emptyMiddlewareHandler just an empty handler 160 | var emptyMiddlewareHandler = func(ctx Context) { 161 | data, ok := ctx.GetLocal("test-key").(string) 162 | if !ok || data != "value" { 163 | panic("test-key value is wrong") 164 | } 165 | } 166 | 167 | // registerRoute matches with register route request with available methods and calls it 168 | func registerRoute(gz Godzilla, method, path string, handler func(ctx Context)) { 169 | switch method { 170 | case MethodGet: 171 | gz.Get(path, handler) 172 | case MethodHead: 173 | gz.Head(path, handler) 174 | case MethodPost: 175 | gz.Post(path, handler) 176 | case MethodPut: 177 | gz.Put(path, handler) 178 | case MethodPatch: 179 | gz.Patch(path, handler) 180 | case MethodDelete: 181 | gz.Delete(path, handler) 182 | case MethodConnect: 183 | gz.Connect(path, handler) 184 | case MethodOptions: 185 | gz.Options(path, handler) 186 | case MethodTrace: 187 | gz.Trace(path, handler) 188 | } 189 | } 190 | 191 | // TestMethods tests creating Godzilla instance, registering routes, making 192 | // requests and getting proper responses 193 | func TestMethods(t *testing.T) { 194 | // testing routes 195 | routes := []struct { 196 | method string 197 | path string 198 | handler func(ctx Context) 199 | }{ 200 | {method: MethodGet, path: "/order/get", handler: queryHandler}, 201 | {method: MethodPost, path: "/order/add", handler: bodyHandler}, 202 | {method: MethodGet, path: "/books/find", handler: emptyHandler}, 203 | {method: MethodGet, path: "/articles/search", handler: emptyHandler}, 204 | {method: MethodPut, path: "/articles/search", handler: emptyHandler}, 205 | {method: MethodHead, path: "/articles/test", handler: emptyHandler}, 206 | {method: MethodPost, path: "/articles/204", handler: emptyHandler}, 207 | {method: MethodPost, path: "/articles/205", handler: unAuthorizedHandler}, 208 | {method: MethodGet, path: "/ping", handler: pingHandler}, 209 | {method: MethodPut, path: "/posts", handler: emptyHandler}, 210 | {method: MethodPatch, path: "/post/502", handler: emptyHandler}, 211 | {method: MethodDelete, path: "/post/a23011a", handler: emptyHandler}, 212 | {method: MethodConnect, path: "/user/204", handler: headerHandler}, 213 | {method: MethodOptions, path: "/user/204/setting", handler: errorHandler}, 214 | {method: MethodTrace, path: "/users/*", handler: emptyHandler}, 215 | } 216 | 217 | // get instance of Godzilla 218 | gz := setupGodzilla(&Settings{ 219 | CaseInSensitive: true, 220 | AutoRecover: true, 221 | HandleOPTIONS: true, 222 | HandleMethodNotAllowed: true, 223 | }) 224 | 225 | // register routes according to method 226 | for _, r := range routes { 227 | registerRoute(gz, r.method, r.path, r.handler) 228 | } 229 | 230 | // start serving 231 | startGodzilla(gz) 232 | 233 | // Requests that will be tested 234 | testCases := []struct { 235 | method string 236 | path string 237 | statusCode int 238 | requestBody string 239 | body string 240 | headers map[string]string 241 | }{ 242 | {method: MethodGet, path: "/order/get?name=art123", statusCode: StatusOK, body: "art123"}, 243 | {method: MethodPost, path: "/order/add", requestBody: "testOrder", statusCode: StatusOK, body: "testOrder"}, 244 | {method: MethodPost, path: "/books/find", statusCode: StatusMethodNotAllowed, body: "Method Not Allowed", headers: map[string]string{"Allow": "GET, OPTIONS"}}, 245 | {method: MethodGet, path: "/articles/search", statusCode: StatusOK}, 246 | {method: MethodGet, path: "/articles/search", statusCode: StatusOK}, 247 | {method: MethodGet, path: "/Articles/search", statusCode: StatusOK}, 248 | {method: MethodOptions, path: "/articles/search", statusCode: StatusOK}, 249 | {method: MethodOptions, path: "*", statusCode: StatusOK}, 250 | {method: MethodOptions, path: "/*", statusCode: StatusOK}, 251 | {method: MethodGet, path: "/articles/searching", statusCode: StatusNotFound, body: "Not Found"}, 252 | {method: MethodHead, path: "/articles/test", statusCode: StatusOK}, 253 | {method: MethodPost, path: "/articles/204", statusCode: StatusOK}, 254 | {method: MethodPost, path: "/articles/205", statusCode: StatusUnauthorized}, 255 | {method: MethodPost, path: "/Articles/205", statusCode: StatusUnauthorized}, 256 | {method: MethodPost, path: "/articles/206", statusCode: StatusNotFound, body: "Not Found"}, 257 | {method: MethodGet, path: "/ping", statusCode: StatusOK, body: "pong"}, 258 | {method: MethodPut, path: "/posts", statusCode: StatusOK}, 259 | {method: MethodPatch, path: "/post/502", statusCode: StatusOK}, 260 | {method: MethodDelete, path: "/post/a23011a", statusCode: StatusOK}, 261 | {method: MethodConnect, path: "/user/204", statusCode: StatusOK, headers: map[string]string{"custom": "testing"}}, 262 | {method: MethodOptions, path: "/user/204/setting", statusCode: StatusInternalServerError, body: "Internal Server Error"}, 263 | {method: MethodTrace, path: "/users/testing", statusCode: StatusOK}, 264 | } 265 | 266 | for _, tc := range testCases { 267 | 268 | req, _ := http.NewRequest(tc.method, tc.path, strings.NewReader(tc.requestBody)) 269 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 270 | req.Header.Add("Content-Length", strconv.Itoa(len(tc.requestBody))) 271 | req.Header.Set("my-header", "testing") 272 | 273 | response, err := makeRequest(req, gz) 274 | 275 | if err != nil { 276 | t.Fatalf("%s(%s): %s", tc.method, tc.path, err.Error()) 277 | } 278 | 279 | if response.StatusCode != tc.statusCode { 280 | t.Fatalf("%s(%s): returned %d expected %d", tc.method, tc.path, response.StatusCode, tc.statusCode) 281 | } 282 | 283 | body, err := ioutil.ReadAll(response.Body) 284 | if err != nil { 285 | t.Fatalf("%s(%s): %s", tc.method, tc.path, err.Error()) 286 | } 287 | 288 | if string(body) != tc.body { 289 | t.Fatalf("%s(%s): returned %s expected %s", tc.method, tc.path, body, tc.body) 290 | } 291 | 292 | for expectedKey, expectedValue := range tc.headers { 293 | actualValue := response.Header.Get(expectedKey) 294 | if actualValue != expectedValue { 295 | t.Errorf(" mismatch for route '%s' parameter '%s' actual '%s', expected '%s'", 296 | tc.path, expectedKey, actualValue, expectedValue) 297 | } 298 | } 299 | } 300 | } 301 | 302 | func TestStatic(t *testing.T) { 303 | 304 | gz := setupGodzilla(&Settings{ 305 | CaseInSensitive: true, 306 | AutoRecover: true, 307 | HandleOPTIONS: true, 308 | HandleMethodNotAllowed: true, 309 | }) 310 | 311 | gz.Static("/static/", "./assets/") 312 | 313 | // start serving 314 | startGodzilla(gz) 315 | 316 | // Requests that will be tested 317 | testCases := []struct { 318 | method string 319 | path string 320 | statusCode int 321 | body string 322 | }{ 323 | {method: MethodGet, path: "/static/Godzilla.png", statusCode: StatusOK}, 324 | } 325 | 326 | for _, tc := range testCases { 327 | // create and make http request 328 | req, _ := http.NewRequest(tc.method, tc.path, nil) 329 | 330 | response, err := makeRequest(req, gz) 331 | 332 | if err != nil { 333 | t.Fatalf("%s(%s): %s", tc.method, tc.path, err.Error()) 334 | } 335 | 336 | // check status code 337 | if response.StatusCode != tc.statusCode { 338 | t.Fatalf("%s(%s): returned %d expected %d", tc.method, tc.path, response.StatusCode, tc.statusCode) 339 | } 340 | 341 | // read body from response 342 | body, err := ioutil.ReadAll(response.Body) 343 | if err != nil { 344 | t.Fatalf("%s(%s): %s", tc.method, tc.path, err.Error()) 345 | } 346 | 347 | // check response body 348 | if tc.body != "" && string(body) != tc.body { 349 | t.Fatalf("%s(%s): returned %s expected %s", tc.method, tc.path, body, tc.body) 350 | } 351 | } 352 | } 353 | 354 | // TestStartWithPrefork tests start service method 355 | func TestStartWithPrefork(t *testing.T) { 356 | gz := New(&Settings{ 357 | Prefork: true, 358 | }) 359 | 360 | go func() { 361 | time.Sleep(1000 * time.Millisecond) 362 | gz.Stop() 363 | }() 364 | 365 | gz.Start(":3000") 366 | } 367 | 368 | // TestStart tests start service method 369 | func TestStart(t *testing.T) { 370 | gz := New() 371 | 372 | go func() { 373 | time.Sleep(1000 * time.Millisecond) 374 | gz.Stop() 375 | }() 376 | 377 | gz.Start(":3010") 378 | } 379 | 380 | // TestStartWithTLS tests start service method 381 | func TestStartWithTLS(t *testing.T) { 382 | gz := New(&Settings{ 383 | TLSKeyPath: "./assets/ssl-cert-snakeoil.key", 384 | TLSCertPath: "./assets/ssl-cert-snakeoil.crt", 385 | TLSEnabled: true, 386 | }) 387 | 388 | // use a channel to hand off the error ( if any ) 389 | errs := make(chan error, 1) 390 | 391 | go func() { 392 | _, err := tls.DialWithDialer( 393 | &net.Dialer{ 394 | Timeout: time.Second * 3, 395 | }, 396 | "tcp", 397 | "localhost:3050", 398 | &tls.Config{ 399 | InsecureSkipVerify: true, 400 | }) 401 | errs <- err 402 | gz.Stop() 403 | }() 404 | 405 | gz.Start(":3050") 406 | 407 | // wait for an error 408 | err := <-errs 409 | if err != nil { 410 | t.Fatalf("StartWithSSL failed to connect with TLS error: %s", err) 411 | } 412 | } 413 | 414 | // TestStartInvalidListener tests start with invalid listener 415 | func TestStartInvalidListener(t *testing.T) { 416 | gz := New() 417 | 418 | go func() { 419 | time.Sleep(1000 * time.Millisecond) 420 | gz.Stop() 421 | }() 422 | 423 | if err := gz.Start("invalid listener"); err == nil { 424 | t.Fatalf("invalid listener passed") 425 | } 426 | } 427 | 428 | // TestStop tests stop service method 429 | func TestStop(t *testing.T) { 430 | gz := New() 431 | 432 | go func() { 433 | time.Sleep(1000 * time.Millisecond) 434 | gz.Stop() 435 | }() 436 | 437 | gz.Start("") 438 | } 439 | 440 | // TestRegisterFallback tests router fallback handler 441 | func TestNotFound(t *testing.T) { 442 | // get instance of Godzilla 443 | gz := setupGodzilla() 444 | 445 | // register valid route 446 | gz.Get("/ping", pingHandler) 447 | 448 | // register not found handlers 449 | gz.NotFound(fallbackHandler) 450 | 451 | // start serving 452 | startGodzilla(gz) 453 | 454 | // One valid request, one invalid 455 | testCases := []struct { 456 | method string 457 | path string 458 | statusCode int 459 | body string 460 | }{ 461 | {method: MethodGet, path: "/ping", statusCode: StatusOK, body: "pong"}, 462 | {method: MethodGet, path: "/error", statusCode: StatusNotFound, body: "custom fallback handler"}, 463 | } 464 | 465 | for _, tc := range testCases { 466 | // create and make http request 467 | req, _ := http.NewRequest(tc.method, tc.path, nil) 468 | response, err := makeRequest(req, gz) 469 | 470 | if err != nil { 471 | t.Fatalf("%s(%s): %s", tc.method, tc.path, err.Error()) 472 | } 473 | 474 | // check status code 475 | if response.StatusCode != tc.statusCode { 476 | t.Fatalf("%s(%s): returned %d expected %d", tc.method, tc.path, response.StatusCode, tc.statusCode) 477 | } 478 | 479 | // read body from response 480 | body, err := ioutil.ReadAll(response.Body) 481 | if err != nil { 482 | t.Fatalf("%s(%s): %s", tc.method, tc.path, err.Error()) 483 | } 484 | 485 | // check response body 486 | if string(body) != tc.body { 487 | t.Fatalf("%s(%s): returned %s expected %s", tc.method, tc.path, body, tc.body) 488 | } 489 | } 490 | } 491 | 492 | // TestGroupRouting tests that you can do group routing 493 | func TestGroupRouting(t *testing.T) { 494 | // create Godzilla instance 495 | gz := setupGodzilla() 496 | routes := []*Route{ 497 | gz.Get("/id", emptyHandler), 498 | gz.Post("/abc", emptyHandler), 499 | gz.Post("/abcd", emptyHandler), 500 | } 501 | gz.Group("/account", gz.Group("/api", routes)) 502 | 503 | // start serving 504 | startGodzilla(gz) 505 | 506 | // One valid request, one invalid 507 | testCases := []struct { 508 | method string 509 | path string 510 | statusCode int 511 | body string 512 | }{ 513 | {method: MethodGet, path: "/account/api/id", statusCode: StatusOK}, 514 | {method: MethodPost, path: "/account/api/abc", statusCode: StatusOK}, 515 | {method: MethodPost, path: "/account/api/abcd", statusCode: StatusOK}, 516 | {method: MethodGet, path: "/id", statusCode: StatusNotFound, body: "Not Found"}, 517 | } 518 | 519 | for _, tc := range testCases { 520 | // create and make http request 521 | req, _ := http.NewRequest(tc.method, tc.path, nil) 522 | response, err := makeRequest(req, gz) 523 | 524 | if err != nil { 525 | t.Fatalf("%s(%s): %s", tc.method, tc.path, err.Error()) 526 | } 527 | 528 | // check status code 529 | if response.StatusCode != tc.statusCode { 530 | t.Fatalf("%s(%s): returned %d expected %d", tc.method, tc.path, response.StatusCode, tc.statusCode) 531 | } 532 | 533 | // read body from response 534 | body, err := ioutil.ReadAll(response.Body) 535 | if err != nil { 536 | t.Fatalf("%s(%s): %s", tc.method, tc.path, err.Error()) 537 | } 538 | 539 | // check response body 540 | if string(body) != tc.body { 541 | t.Fatalf("%s(%s): returned %s expected %s", tc.method, tc.path, body, tc.body) 542 | } 543 | } 544 | } 545 | 546 | // TestUse tries to register middlewares that work before all routes 547 | func TestUse(t *testing.T) { 548 | // get instance of Godzilla 549 | gz := setupGodzilla() 550 | 551 | // register valid route 552 | gz.Get("/ping", pingHandler) 553 | 554 | // Use authorized middleware for all the application 555 | gz.Use(unAuthorizedHandler) 556 | 557 | // start serving 558 | startGodzilla(gz) 559 | 560 | req, _ := http.NewRequest(MethodGet, "/ping", nil) 561 | response, err := makeRequest(req, gz) 562 | 563 | if err != nil { 564 | t.Fatalf("%s(%s): %s", MethodGet, "/ping", err.Error()) 565 | } 566 | 567 | // check status code 568 | if response.StatusCode != StatusUnauthorized { 569 | t.Fatalf("%s(%s): returned %d expected %d", MethodGet, "/ping", response.StatusCode, StatusUnauthorized) 570 | } 571 | } 572 | --------------------------------------------------------------------------------