├── consts.go ├── doc.go ├── .gitignore ├── options_test.go ├── martini_test.go ├── martini.go ├── options.go ├── wercker.yml ├── static.go ├── LICENSE ├── README.md └── static_test.go /consts.go: -------------------------------------------------------------------------------- 1 | package staticbin 2 | 3 | const ( 4 | defaultDir = "public" 5 | defaultIndexFile = "index.html" 6 | ) 7 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package staticbin provides a Martini middleware/handler for serving static files from binary data. 2 | package staticbin 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /options_test.go: -------------------------------------------------------------------------------- 1 | package staticbin 2 | 3 | import "testing" 4 | 5 | func TestRetrieveOptions(t *testing.T) { 6 | opt := Options{SkipLogging: true} 7 | retOpt := retrieveOptions([]Options{opt}) 8 | if opt.SkipLogging != retOpt.SkipLogging { 9 | t.Errorf( 10 | "returned value is invalid [actual: %+v][expected: %+v]", 11 | opt, 12 | retOpt, 13 | ) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /martini_test.go: -------------------------------------------------------------------------------- 1 | package staticbin 2 | 3 | import "testing" 4 | 5 | func TestClassicWithoutStatic(t *testing.T) { 6 | m := ClassicWithoutStatic() 7 | if m == nil { 8 | t.Errorf("returned value should not be nil.") 9 | } 10 | } 11 | 12 | func TestClassic(t *testing.T) { 13 | fnc := func(s string) ([]byte, error) { 14 | return []byte("test"), nil 15 | } 16 | m := Classic(fnc) 17 | if m == nil { 18 | t.Errorf("returned value should not be nil.") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /martini.go: -------------------------------------------------------------------------------- 1 | package staticbin 2 | 3 | import "github.com/go-martini/martini" 4 | 5 | // ClassicWithoutStatic creates a classic Martini without a default Static. 6 | func ClassicWithoutStatic() *martini.ClassicMartini { 7 | r := martini.NewRouter() 8 | m := martini.New() 9 | m.Use(martini.Logger()) 10 | m.Use(martini.Recovery()) 11 | m.MapTo(r, (*martini.Routes)(nil)) 12 | m.Action(r.Handle) 13 | return &martini.ClassicMartini{m, r} 14 | } 15 | 16 | // Classic creates a classic Martini with a default Static. 17 | func Classic(asset func(string) ([]byte, error)) *martini.ClassicMartini { 18 | m := ClassicWithoutStatic() 19 | m.Use(Static(defaultDir, asset)) 20 | return m 21 | } 22 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package staticbin 2 | 3 | // Options represents configuration options for the staticbin.Static middleware. 4 | type Options struct { 5 | // SkipLogging will disable [Static] log messages when a static file is served. 6 | SkipLogging bool 7 | // IndexFile defines which file to serve as index if it exists. 8 | IndexFile string 9 | } 10 | 11 | // retrieveOptions retrieves an options from the array of options. 12 | func retrieveOptions(options []Options) Options { 13 | var opt Options 14 | 15 | if len(options) > 0 { 16 | opt = options[0] 17 | } 18 | 19 | // Set the default value to opt.IndexFile. 20 | if opt.IndexFile == "" { 21 | opt.IndexFile = defaultIndexFile 22 | } 23 | 24 | return opt 25 | } 26 | -------------------------------------------------------------------------------- /wercker.yml: -------------------------------------------------------------------------------- 1 | box: wercker/golang@1.1.2 2 | # Build definition 3 | build: 4 | # The steps that will be executed on build 5 | steps: 6 | # Sets the go workspace and places you package 7 | # at the right place in the workspace tree 8 | - setup-go-workspace 9 | 10 | # Gets the dependencies 11 | - script: 12 | name: go get 13 | code: | 14 | cd $WERCKER_SOURCE_DIR 15 | go version 16 | go get -t ./... 17 | 18 | # Build the project 19 | - script: 20 | name: go build 21 | code: | 22 | go build ./... 23 | 24 | # Test the project 25 | - script: 26 | name: go test 27 | code: | 28 | go test ./... 29 | -------------------------------------------------------------------------------- /static.go: -------------------------------------------------------------------------------- 1 | package staticbin 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "net/http" 7 | "path" 8 | "time" 9 | 10 | "github.com/go-martini/martini" 11 | ) 12 | 13 | // Static returns a middleware handler that serves static files in the given directory. 14 | func Static(dir string, asset func(string) ([]byte, error), options ...Options) martini.Handler { 15 | if asset == nil { 16 | return func() {} 17 | } 18 | 19 | opt := retrieveOptions(options) 20 | 21 | modtime := time.Now() 22 | 23 | return func(res http.ResponseWriter, req *http.Request, log *log.Logger) { 24 | if req.Method != "GET" && req.Method != "HEAD" { 25 | return 26 | } 27 | 28 | url := req.URL.Path 29 | 30 | b, err := asset(dir + url) 31 | 32 | if err != nil { 33 | // Try to serve the index file. 34 | b, err = asset(path.Join(dir+url, opt.IndexFile)) 35 | 36 | if err != nil { 37 | // Exit if the asset could not be found. 38 | return 39 | } 40 | } 41 | 42 | if !opt.SkipLogging { 43 | log.Println("[Static] Serving " + url) 44 | } 45 | 46 | http.ServeContent(res, req, url, modtime, bytes.NewReader(b)) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Keiji Yoshida 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StaticBin [![wercker status](https://app.wercker.com/status/226be787d16a3204c43211a2232b24f8/s/ "wercker status")](https://app.wercker.com/project/bykey/226be787d16a3204c43211a2232b24f8) [![GoDoc](https://godoc.org/github.com/martini-contrib/staticbin?status.png)](https://godoc.org/github.com/martini-contrib/staticbin) 2 | 3 | Martini middleware/handler for serving static files from binary data 4 | 5 | ## Usage 6 | 7 | ```go 8 | package main 9 | 10 | import ( 11 | "github.com/go-martini/martini" 12 | "github.com/martini-contrib/staticbin" 13 | ) 14 | 15 | func main() { 16 | m := martini.Classic() 17 | 18 | // Serves the "static" directory's files from binary data. 19 | // You have to pass the "Asset" function generated by 20 | // go-bindata (https://github.com/jteeuwen/go-bindata). 21 | m.Use(staticbin.Static("static", Asset)) 22 | 23 | m.Get("/", func() string { 24 | return "Hello world!" 25 | }) 26 | 27 | m.Run() 28 | } 29 | ``` 30 | 31 | ## Get a classic Martini which serves the "public" directory's files from binary data by default 32 | 33 | A classic Martini generated by `martini.Classic` serves the "public" directory's **files** by default. You can get one which serves the "public" directory's files from **binary data** by default by using `staticbin.Classic`. 34 | 35 | ```go 36 | package main 37 | 38 | import "github.com/martini-contrib/staticbin" 39 | 40 | func main() { 41 | // staticbin.Classic(Asset) instance automatically serves the "public" directory's files 42 | // from binary data by default. 43 | m := staticbin.Classic(Asset) 44 | 45 | // You can serve from more directories by adding more staticbin.Static handlers. 46 | // m.Use(staticbin.Static("static", Asset)) 47 | 48 | m.Get("/", func() string { 49 | return "Hello world!" 50 | }) 51 | 52 | m.Run() 53 | } 54 | ``` 55 | 56 | ## Sample package using StaticBin 57 | 58 | * [yosssi/gold.yoss.si](https://github.com/yosssi/gold.yoss.si) 59 | 60 | ## Doc 61 | 62 | * [GoDoc](https://godoc.org/github.com/martini-contrib/staticbin) 63 | -------------------------------------------------------------------------------- /static_test.go: -------------------------------------------------------------------------------- 1 | package staticbin 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestStatic(t *testing.T) { 12 | // Case when Asset is nil. 13 | h := Static("public", nil) 14 | if h == nil { 15 | t.Errorf("returned value should not be nil") 16 | } 17 | 18 | // Case when req.Method != "GET" && req.Method != "HEAD". 19 | fnc := func(s string) ([]byte, error) { 20 | return []byte("test"), nil 21 | } 22 | res := httptest.NewRecorder() 23 | m := Classic(fnc) 24 | req, err := http.NewRequest("POST", "http://localhost:3000/static.go", nil) 25 | if err != nil { 26 | t.Error(err) 27 | } 28 | m.ServeHTTP(res, req) 29 | if body := strings.TrimSpace(res.Body.String()); body != "404 page not found" { 30 | t.Errorf( 31 | "returned value is invalid [actual: %s][expected: %s]", 32 | body, 33 | "404 page not found", 34 | ) 35 | } 36 | 37 | // Case when Asset(dir + path) returns an error. 38 | fnc = func(s string) ([]byte, error) { 39 | return nil, fmt.Errorf("test error") 40 | } 41 | res = httptest.NewRecorder() 42 | m = Classic(fnc) 43 | req, err = http.NewRequest("GET", "http://localhost:3000/static.go", nil) 44 | if err != nil { 45 | t.Error(err) 46 | } 47 | m.ServeHTTP(res, req) 48 | if body := strings.TrimSpace(res.Body.String()); body != "404 page not found" { 49 | t.Errorf( 50 | "returned value is invalid [actual: %s][expected: %s]", 51 | body, 52 | "404 page not found", 53 | ) 54 | } 55 | 56 | // Case when Static serves a file. 57 | fnc = func(s string) ([]byte, error) { 58 | return []byte("test"), nil 59 | } 60 | res = httptest.NewRecorder() 61 | m = Classic(fnc) 62 | req, err = http.NewRequest("GET", "http://localhost:3000/static.go", nil) 63 | if err != nil { 64 | t.Error(err) 65 | } 66 | m.ServeHTTP(res, req) 67 | if body := strings.TrimSpace(res.Body.String()); body != "test" { 68 | t.Errorf( 69 | "returned value is invalid [actual: %s][expected: %s]", 70 | body, 71 | "test", 72 | ) 73 | } 74 | } 75 | --------------------------------------------------------------------------------