├── .travis.yml ├── screenshot-1.png ├── screenshot-2.png ├── main.go ├── .gitignore ├── LICENSE ├── configuration.go ├── binary_file_system.go ├── README.md └── http_server.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - tip 5 | -------------------------------------------------------------------------------- /screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtojek/bigfiles/HEAD/screenshot-1.png -------------------------------------------------------------------------------- /screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtojek/bigfiles/HEAD/screenshot-2.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | createHttpServer(readConfiguration(), createBinaryFileSystem()).listenAndServe() 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | # IDEA 17 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Marcin Tojek 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. 22 | -------------------------------------------------------------------------------- /configuration.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "math" 9 | 10 | "github.com/c2h5oh/datasize" 11 | ) 12 | 13 | type configuration struct { 14 | hostPort string 15 | maxFileSize int64 16 | } 17 | 18 | func readConfiguration() *configuration { 19 | var hostPort string 20 | var maxFileSize string 21 | 22 | flag.StringVar(&hostPort, "hostPort", ":5000", "host:port of the HTTP server") 23 | flag.StringVar(&maxFileSize, "maxFileSize", "9223372036854775807B", "maximum size of file that can be downloaded") 24 | flag.Parse() 25 | 26 | var c *configuration 27 | var err error 28 | if c, err = validate(hostPort, maxFileSize); err != nil { 29 | log.Fatal(err) 30 | } 31 | return c 32 | } 33 | 34 | func validate(hostPort, maxFileSize string) (*configuration, error) { 35 | if hostPort == "" { 36 | return nil, errors.New("host:post must not be empty") 37 | } 38 | 39 | if maxFileSize == "" { 40 | return nil, errors.New("maxFileSize must no be empty") 41 | } 42 | 43 | var uSize datasize.ByteSize 44 | if err := uSize.UnmarshalText([]byte(maxFileSize)); err != nil { 45 | return nil, err 46 | } 47 | 48 | if uSize > math.MaxInt64 { 49 | return nil, fmt.Errorf("file size cannot be greater than %v B", math.MaxInt64) 50 | } 51 | 52 | return &configuration{hostPort: hostPort, maxFileSize: int64(uSize)}, nil 53 | } 54 | -------------------------------------------------------------------------------- /binary_file_system.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "math/rand" 7 | "net/http" 8 | "os" 9 | "regexp" 10 | "time" 11 | 12 | "github.com/c2h5oh/datasize" 13 | ) 14 | 15 | var ( 16 | errDirListingUnsupported = errors.New("not supported") 17 | kindNameRegexp = regexp.MustCompile("/files/([^/]+)/([^/]+)") 18 | ) 19 | 20 | type binaryFileInfo struct { 21 | fileSize int64 22 | } 23 | 24 | func (bfi *binaryFileInfo) Name() string { 25 | return "" 26 | } 27 | 28 | func (bfi *binaryFileInfo) Size() int64 { 29 | return bfi.fileSize 30 | } 31 | 32 | func (bfi *binaryFileInfo) Mode() os.FileMode { 33 | return os.FileMode(os.ModePerm) 34 | } 35 | 36 | func (bfi *binaryFileInfo) ModTime() time.Time { 37 | return time.Now() 38 | } 39 | 40 | func (bfi *binaryFileInfo) IsDir() bool { 41 | return false 42 | } 43 | 44 | func (bfi *binaryFileInfo) Sys() interface{} { 45 | return nil 46 | } 47 | 48 | type binaryFile struct { 49 | fileType string 50 | fileSize int64 51 | } 52 | 53 | func (bf *binaryFile) Close() error { 54 | return nil 55 | } 56 | 57 | func (bf *binaryFile) Read(p []byte) (n int, err error) { 58 | for i := 0; i < len(p); i++ { 59 | if bf.fileType == "sparse" { 60 | p[i] = 0 61 | } else if bf.fileType == "random" { 62 | p[i] = byte(rand.Int()) 63 | } else { 64 | log.Fatal("unsupported file type selected") 65 | } 66 | } 67 | return len(p), nil 68 | } 69 | 70 | func (bf *binaryFile) Readdir(count int) ([]os.FileInfo, error) { 71 | return nil, errDirListingUnsupported 72 | } 73 | 74 | func (bf *binaryFile) Stat() (os.FileInfo, error) { 75 | return &binaryFileInfo{fileSize: bf.fileSize}, nil 76 | } 77 | 78 | func (bf *binaryFile) Seek(offset int64, whence int) (int64, error) { 79 | return -1, nil 80 | } 81 | 82 | type binaryFileSystemImpl struct{} 83 | 84 | func (bfsi *binaryFileSystemImpl) Open(name string) (http.File, error) { 85 | m := kindNameRegexp.FindStringSubmatch(name) 86 | fileType := m[1] 87 | fileSize := m[2] 88 | 89 | var fileSizeValue datasize.ByteSize 90 | fileSizeValue.UnmarshalText([]byte(fileSize)) 91 | return &binaryFile{fileType, int64(fileSizeValue.Bytes())}, nil 92 | } 93 | 94 | func createBinaryFileSystem() http.FileSystem { 95 | return &binaryFileSystemImpl{} 96 | } 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BigFiles 2 | 3 | Status: **Done** (waiting for feedback) 4 | 5 | [![Build Status](https://travis-ci.org/mtojek/bigfiles.svg?branch=master)](https://travis-ci.org/mtojek/bigfiles) 6 | 7 | ## Description 8 | 9 | Are you bored with overloaded speed test services? You don't have to use them at all, because you can run own webserver, serving **large test files** (custom size: **100 MB**, **100 GB**, **1 TB**..., upto **8192 PB**). 10 | 11 | There is no more need to use publicly hosted storage services to download ordinary **100MB.bin**. With a single command spawn **own speed test** instance to verify your Internet provider. 12 | 13 | ### Screenshots 14 | 15 | #### Index view 16 | 17 | Screenshot Desktop 18 | 19 | #### Chrome "Downloads" page 20 | 21 | Screenshot Mobile 22 | 23 | ## Features 24 | 25 | * download huge files < **8192 PB** 26 | * choose between **zeros** file or random content 27 | * **easy to use** HTTP GET endpoints (Chrome, curl, etc.) 28 | * user-defined **file size limit** 29 | 30 | ## Quickstart 31 | 32 | Download and install BigFiles: 33 | ```bash 34 | go get github.com/mtojek/bigfiles 35 | ``` 36 | Run the application: 37 | ```bash 38 | bigfiles 39 | ``` 40 | 41 | Use wget to download a sparse file: 42 | ```bash 43 | wget http://localhost:5000/files/sparse/100MB 44 | --2017-10-01 00:34:18-- http://localhost:5000/files/sparse/100MB 45 | Resolving localhost... ::1, fe80::1, 127.0.0.1 46 | Connecting to localhost|::1|:5000... connected. 47 | HTTP request sent, awaiting response... 200 OK 48 | Length: 104857600 (100M) [application/octet-stream] 49 | Saving to: '100MB' 50 | 51 | 100MB 100%[===================================================>] 100.00M 218MB/s in 0.5s 52 | 53 | 2017-10-01 00:34:19 (218 MB/s) - '100MB' saved [104857600/104857600] 54 | 55 | ``` 56 | 57 | or go to web panel to download files: 58 | 59 | [http://localhost:5000](http://localhost:5000) 60 | 61 | ## Contact 62 | 63 | Please feel free to leave any comment or feedback by opening a new issue or contacting me directly via [email](mailto:marcin@tojek.pl). Thank you. 64 | 65 | ## License 66 | 67 | MIT License, see [LICENSE](https://github.com/mtojek/bigfiles/blob/master/LICENSE) file. 68 | -------------------------------------------------------------------------------- /http_server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/c2h5oh/datasize" 9 | "github.com/julienschmidt/httprouter" 10 | ) 11 | 12 | var ( 13 | indexMessage = []byte(` 14 | 15 | 16 | BigFiles 17 |

Welcome to mtojek/bigfiles

18 |

Try following sample links to download binary files:

19 |
20 |
/files/random/1KB
21 |
/files/random/1GB
22 |
/files/sparse/100MB
23 |
/files/sparse/8191PB
24 |
25 |
26 |
27 | Feel free to visit project Github page - github.com/mtojek/bigfiles. 28 |
29 | 30 | `) 31 | errTooBigFileSize = errors.New("too big file size") 32 | errMissingFileSize = errors.New("missing file size") 33 | errUnsupportedFileType = errors.New("unsupported file type") 34 | ) 35 | 36 | type httpServer struct { 37 | config *configuration 38 | fileSystem http.FileSystem 39 | } 40 | 41 | func (s *httpServer) listenAndServe() { 42 | router := httprouter.New() 43 | router.GET("/", s.index) 44 | router.GET("/files/:type/:size", s.createFilesHandler()) 45 | http.ListenAndServe(s.config.hostPort, router) 46 | } 47 | 48 | func (s *httpServer) index(rw http.ResponseWriter, req *http.Request, _ httprouter.Params) { 49 | rw.Header().Set("Content-Type", "text/html") 50 | rw.Write(indexMessage) 51 | } 52 | 53 | func (s *httpServer) createFilesHandler() httprouter.Handle { 54 | fileServer := http.FileServer(s.fileSystem) 55 | return func(rw http.ResponseWriter, req *http.Request, ps httprouter.Params) { 56 | fileType := ps.ByName("type") 57 | fileSize := ps.ByName("size") 58 | 59 | if fileType != "sparse" && fileType != "random" { 60 | http.Error(rw, errUnsupportedFileType.Error(), http.StatusBadRequest) 61 | return 62 | } 63 | 64 | if fileSize == "" { 65 | http.Error(rw, errMissingFileSize.Error(), http.StatusBadRequest) 66 | return 67 | } 68 | 69 | var bs datasize.ByteSize 70 | err := bs.UnmarshalText([]byte(fileSize)) 71 | if err != nil { 72 | http.Error(rw, err.Error(), http.StatusBadRequest) 73 | return 74 | } 75 | 76 | if int64(bs.Bytes()) > s.config.maxFileSize { 77 | http.Error(rw, errTooBigFileSize.Error(), http.StatusBadRequest) 78 | return 79 | } 80 | 81 | rw.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.bin"`, fileSize)) 82 | fileServer.ServeHTTP(rw, req) 83 | } 84 | } 85 | 86 | func createHttpServer(c *configuration, fs http.FileSystem) *httpServer { 87 | return &httpServer{c, fs} 88 | } 89 | --------------------------------------------------------------------------------