You've already forked speedtest-go
							
							Embed default assets
This commit is contained in:
		
							
								
								
									
										13
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								README.md
									
									
									
									
									
								
							@@ -34,16 +34,16 @@ Works with mobile versions too.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## Installation
 | 
					## Installation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You need Go 1.13+ to compile the binary. If you have an older version of Go and don't want to install the tarball
 | 
					You need Go 1.16+ to compile the binary. If you have an older version of Go and don't want to install the tarball
 | 
				
			||||||
manually, you can install newer version of Go into your `GOPATH`:
 | 
					manually, you can install newer version of Go into your `GOPATH`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
0. Install Go 1.14
 | 
					0. Install Go 1.17
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   ```
 | 
					   ```
 | 
				
			||||||
   $ go get golang.org/dl/go1.14.2
 | 
					   $ go get golang.org/dl/go1.17.1
 | 
				
			||||||
   # Assuming your GOPATH is default (~/go), Go 1.14.2 will be installed in ~/go/bin
 | 
					   # Assuming your GOPATH is default (~/go), Go 1.17.1 will be installed in ~/go/bin
 | 
				
			||||||
   $ ~/go/bin/go1.14.2 version
 | 
					   $ ~/go/bin/go1.17.1 version
 | 
				
			||||||
   go version go1.14.2 linux/amd64
 | 
					   go version go1.17.1 linux/amd64
 | 
				
			||||||
   ```
 | 
					   ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. Clone this repository:
 | 
					1. Clone this repository:
 | 
				
			||||||
@@ -98,6 +98,7 @@ manually, you can install newer version of Go into your `GOPATH`:
 | 
				
			|||||||
    ipinfo_api_key=""
 | 
					    ipinfo_api_key=""
 | 
				
			||||||
   
 | 
					   
 | 
				
			||||||
    # assets directory path, defaults to `assets` in the same directory
 | 
					    # assets directory path, defaults to `assets` in the same directory
 | 
				
			||||||
 | 
					    # if the path cannot be found, embedded default assets will be used
 | 
				
			||||||
    assets_path="./assets"
 | 
					    assets_path="./assets"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # password for logging into statistics page, change this to enable stats page
 | 
					    # password for logging into statistics page, change this to enable stats page
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,7 @@ type Config struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	configFile   string  = ""
 | 
						configFile   string
 | 
				
			||||||
	loadedConfig *Config = nil
 | 
						loadedConfig *Config = nil
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -40,7 +40,6 @@ func init() {
 | 
				
			|||||||
	viper.SetDefault("enable_cors", false)
 | 
						viper.SetDefault("enable_cors", false)
 | 
				
			||||||
	viper.SetDefault("statistics_password", "PASSWORD")
 | 
						viper.SetDefault("statistics_password", "PASSWORD")
 | 
				
			||||||
	viper.SetDefault("redact_ip_addresses", false)
 | 
						viper.SetDefault("redact_ip_addresses", false)
 | 
				
			||||||
	viper.SetDefault("assets_path", "./assets")
 | 
					 | 
				
			||||||
	viper.SetDefault("database_type", "postgresql")
 | 
						viper.SetDefault("database_type", "postgresql")
 | 
				
			||||||
	viper.SetDefault("database_hostname", "localhost")
 | 
						viper.SetDefault("database_hostname", "localhost")
 | 
				
			||||||
	viper.SetDefault("database_name", "speedtest")
 | 
						viper.SetDefault("database_name", "speedtest")
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
				
			|||||||
module github.com/librespeed/speedtest
 | 
					module github.com/librespeed/speedtest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
go 1.13
 | 
					go 1.16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/fsnotify/fsnotify v1.5.1 // indirect
 | 
						github.com/fsnotify/fsnotify v1.5.1 // indirect
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,16 +1,15 @@
 | 
				
			|||||||
package results
 | 
					package results
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						_ "embed"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"image"
 | 
						"image"
 | 
				
			||||||
	"image/color"
 | 
						"image/color"
 | 
				
			||||||
	"image/draw"
 | 
						"image/draw"
 | 
				
			||||||
	"image/png"
 | 
						"image/png"
 | 
				
			||||||
	"io/ioutil"
 | 
					 | 
				
			||||||
	"math/rand"
 | 
						"math/rand"
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"path/filepath"
 | 
					 | 
				
			||||||
	"regexp"
 | 
						"regexp"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
@@ -37,6 +36,12 @@ const (
 | 
				
			|||||||
	labelUpload   = "Upload"
 | 
						labelUpload   = "Upload"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//go:embed fonts/NotoSansDisplay-Medium.ttf
 | 
				
			||||||
 | 
					var fontMediumBytes []byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//go:embed fonts/NotoSansDisplay-Light.ttf
 | 
				
			||||||
 | 
					var fontLightBytes []byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	ipv4Regex     = regexp.MustCompile(`(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`)
 | 
						ipv4Regex     = regexp.MustCompile(`(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`)
 | 
				
			||||||
	ipv6Regex     = regexp.MustCompile(`(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))`)
 | 
						ipv6Regex     = regexp.MustCompile(`(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))`)
 | 
				
			||||||
@@ -83,25 +88,17 @@ type IPInfoResponse struct {
 | 
				
			|||||||
func Initialize(c *config.Config) {
 | 
					func Initialize(c *config.Config) {
 | 
				
			||||||
	// changed to use Noto Sans instead of OpenSans, due to issue:
 | 
						// changed to use Noto Sans instead of OpenSans, due to issue:
 | 
				
			||||||
	// https://github.com/golang/freetype/issues/8
 | 
						// https://github.com/golang/freetype/issues/8
 | 
				
			||||||
	if b, err := ioutil.ReadFile(filepath.Join(c.AssetsPath, "NotoSansDisplay-Light.ttf")); err != nil {
 | 
						fLight, err := freetype.ParseFont(fontLightBytes)
 | 
				
			||||||
		log.Fatalf("Error opening NotoSansDisplay-Light font: %s", err)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		f, err := freetype.ParseFont(b)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Fatalf("Error parsing NotoSansDisplay-Light font: %s", err)
 | 
							log.Fatalf("Error parsing NotoSansDisplay-Light font: %s", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
		fontLight = f
 | 
						fontLight = fLight
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if b, err := ioutil.ReadFile(filepath.Join(c.AssetsPath, "NotoSansDisplay-Medium.ttf")); err != nil {
 | 
						fMedium, err := freetype.ParseFont(fontMediumBytes)
 | 
				
			||||||
		log.Fatalf("Error opening NotoSansDisplay-Medium font: %s", err)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		f, err := freetype.ParseFont(b)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Fatalf("Error parsing NotoSansDisplay-Medium font: %s", err)
 | 
							log.Fatalf("Error parsing NotoSansDisplay-Medium font: %s", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
		fontBold = f
 | 
						fontBold = fMedium
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pingJitterLabelFace = truetype.NewFace(fontBold, &truetype.Options{
 | 
						pingJitterLabelFace = truetype.NewFace(fontBold, &truetype.Options{
 | 
				
			||||||
		Size:    12,
 | 
							Size:    12,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,13 +5,13 @@ listen_port=8989
 | 
				
			|||||||
# proxy protocol port, use 0 to disable
 | 
					# proxy protocol port, use 0 to disable
 | 
				
			||||||
proxyprotocol_port=0
 | 
					proxyprotocol_port=0
 | 
				
			||||||
# Server location
 | 
					# Server location
 | 
				
			||||||
server_lat=0
 | 
					server_lat=1
 | 
				
			||||||
server_lng=0
 | 
					server_lng=1
 | 
				
			||||||
# ipinfo.io API key, if applicable
 | 
					# ipinfo.io API key, if applicable
 | 
				
			||||||
ipinfo_api_key=""
 | 
					ipinfo_api_key=""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# assets directory path, defaults to `assets` in the same directory
 | 
					# assets directory path, defaults to `assets` in the same directory
 | 
				
			||||||
assets_path="./assets"
 | 
					assets_path=""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# password for logging into statistics page
 | 
					# password for logging into statistics page
 | 
				
			||||||
statistics_password="PASSWORD"
 | 
					statistics_password="PASSWORD"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										28
									
								
								web/web.go
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								web/web.go
									
									
									
									
									
								
							@@ -1,11 +1,14 @@
 | 
				
			|||||||
package web
 | 
					package web
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"embed"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
 | 
						"io/fs"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
	"regexp"
 | 
						"regexp"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
@@ -26,6 +29,9 @@ const (
 | 
				
			|||||||
	chunkSize = 1048576
 | 
						chunkSize = 1048576
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//go:embed assets
 | 
				
			||||||
 | 
					var defaultAssets embed.FS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	// generate random data for download test on start to minimize runtime overhead
 | 
						// generate random data for download test on start to minimize runtime overhead
 | 
				
			||||||
	randomData = getRandomData(chunkSize)
 | 
						randomData = getRandomData(chunkSize)
 | 
				
			||||||
@@ -46,9 +52,21 @@ func ListenAndServe(conf *config.Config) error {
 | 
				
			|||||||
	r.Use(middleware.NoCache)
 | 
						r.Use(middleware.NoCache)
 | 
				
			||||||
	r.Use(middleware.Recoverer)
 | 
						r.Use(middleware.Recoverer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var assetFS http.FileSystem
 | 
				
			||||||
 | 
						if fi, err := os.Stat(conf.AssetsPath); os.IsNotExist(err) || !fi.IsDir() {
 | 
				
			||||||
 | 
							log.Warnf("Configured asset path %s does not exist or is not a directory, using default assets", conf.AssetsPath)
 | 
				
			||||||
 | 
							sub, err := fs.Sub(defaultAssets, "assets")
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Fatalf("Failed when processing default assets: %s", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							assetFS = http.FS(sub)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							assetFS = justFilesFilesystem{fs: http.Dir(conf.AssetsPath), readDirBatchSize: 2}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	addr := net.JoinHostPort(conf.BindAddress, conf.Port)
 | 
						addr := net.JoinHostPort(conf.BindAddress, conf.Port)
 | 
				
			||||||
	log.Infof("Starting backend server on %s", addr)
 | 
						log.Infof("Starting backend server on %s", addr)
 | 
				
			||||||
	r.Get("/*", pages)
 | 
						r.Get("/*", pages(assetFS))
 | 
				
			||||||
	r.HandleFunc("/empty", empty)
 | 
						r.HandleFunc("/empty", empty)
 | 
				
			||||||
	r.HandleFunc("/backend/empty", empty)
 | 
						r.HandleFunc("/backend/empty", empty)
 | 
				
			||||||
	r.Get("/garbage", garbage)
 | 
						r.Get("/garbage", garbage)
 | 
				
			||||||
@@ -96,14 +114,16 @@ func listenProxyProtocol(conf *config.Config, r *chi.Mux) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func pages(w http.ResponseWriter, r *http.Request) {
 | 
					func pages(fs http.FileSystem) http.HandlerFunc {
 | 
				
			||||||
 | 
						fn := func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
		if r.RequestURI == "/" {
 | 
							if r.RequestURI == "/" {
 | 
				
			||||||
			r.RequestURI = "/index.html"
 | 
								r.RequestURI = "/index.html"
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	conf := config.LoadedConfig()
 | 
					 | 
				
			||||||
	fs := justFilesFilesystem{fs: http.Dir(conf.AssetsPath), readDirBatchSize: 2}
 | 
					 | 
				
			||||||
		http.FileServer(fs).ServeHTTP(w, r)
 | 
							http.FileServer(fs).ServeHTTP(w, r)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return fn
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func empty(w http.ResponseWriter, r *http.Request) {
 | 
					func empty(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user