diff --git a/build.py b/build.py index c273c14..e92ed28 100644 --- a/build.py +++ b/build.py @@ -125,11 +125,13 @@ def buildPlatform(pairs, filePrefix): ./build.py windows/386 - 3. Build binaries for windows/386, linux/386: + 3. Build binaries for windows/386 and linux/386: ./build.py windows/386 linux/386 -""" + 4. Build binaries for linux/386 and linux/amd64: + + ./build.py linux/386 linux/amd64""" errmsg = "Arguments are not valid GOOS/GOARCH pairs, use -h for help" diff --git a/global/config.go b/global/config.go index fdb4aa2..cfa4217 100755 --- a/global/config.go +++ b/global/config.go @@ -5,6 +5,7 @@ import "fmt" import "flag" import "strings" import "path/filepath" +import phelper "github.com/m3ng9i/go-utils/path" import "github.com/m3ng9i/ran/server" @@ -18,19 +19,67 @@ var versionInfo = fmt.Sprintf("Version: %s, Branch: %s, Build: %s, Build time: % _version_, _branch_, _commitId_, _buildTime_) +type TLSPolicy string +const ( + TLSRedirect TLSPolicy = "redirect" + TLSBoth TLSPolicy = "both" + TLSOnly TLSPolicy = "only" +) + + +// TLSOption contains options used in TLS encryption +type TLSOption struct { + PublicKey string // Path of public key (certificate) + PrivateKey string // Path of private key + Port uint // HTTPS port. Default is DefaultTLSPort. + Policy TLSPolicy // TLS policy. Default is DefaultTLSPolicy. +} + +const DefaultTLSPort uint = 443 +const DefaultTLSPolicy = TLSOnly + + // Setting about ran server type Setting struct { Port uint // HTTP port. Default is 8080. ShowConf bool // If show config info in the log. Debug bool // If turns on debug mode. Default is false. + TLS *TLSOption // If is nil, TLS is off. server.Config } +// check if this.Path404 or this.Path401 is correct and return a absolute form of path (newPath). +// if the path is not correct, return an error +// p: path of 404 or 401 file, example: /404.html +// name: name of the error file, 401 or 404 +func (this *Setting) checkCustomErrorFile(p, name string) (newPath string, err error) { + newPath = filepath.Join(this.Root, p) + + // check if the custom error file is under root + root := this.Root + if !strings.HasSuffix(root, string(filepath.Separator)) { + root = root + string(filepath.Separator) + } + if !strings.HasPrefix(newPath, root) { + err = fmt.Errorf("Path of %s file can not be out of root directory", name) + return + } + + // check if the file path is exist and not a directory + e := phelper.IsExistFile(newPath) + if e != nil { + err = fmt.Errorf("'%s': %s", newPath, e) + return + } + + return +} + func (this *Setting) check() (errmsg []string) { if this.Port > 65535 || this.Port <= 0 { - errmsg = append(errmsg, "Available port range is 1-65535") + errmsg = append(errmsg, "Available HTTP port range is 1-65535") } for _, index := range this.IndexName { @@ -49,44 +98,25 @@ func (this *Setting) check() (errmsg []string) { } else { errmsg = append(errmsg, fmt.Sprintf("Get stat of root directory error: %s", err.Error())) } - goto END + return } else { if info.IsDir() == false { - errmsg = append(errmsg, fmt.Sprintf("Root is not a directory")) - goto END + errmsg = append(errmsg, "Root is not a directory") + return } this.Root, err = filepath.Abs(this.Root) if err != nil { errmsg = append(errmsg, fmt.Sprintf("Can not convert root to absolute form: %s", err.Error())) - goto END + return } } if this.Path404 != nil { - *this.Path404 = filepath.Join(this.Root, *this.Path404) - - // check if 404 file is under root - root := this.Root - if !strings.HasSuffix(root, string(filepath.Separator)) { - root = root + string(filepath.Separator) - } - if !strings.HasPrefix(*this.Path404, root) { - errmsg = append(errmsg, "Path of 404 file can not be out of root directory") - goto END - } - - info, err = os.Stat(*this.Path404) + var err error + *this.Path404, err = this.checkCustomErrorFile(*this.Path404, "404") if err != nil { - if os.IsNotExist(err) { - errmsg = append(errmsg, fmt.Sprintf("404 file '%s' is not exist", *this.Path404)) - } else { - errmsg = append(errmsg, fmt.Sprintf("Get stat of 404 file error: %s", err.Error())) - } - } else { - if info.IsDir() { - errmsg = append(errmsg, fmt.Sprintf("404 file can not be a directory")) - } + errmsg = append(errmsg, err.Error()) } } @@ -100,14 +130,60 @@ func (this *Setting) check() (errmsg []string) { errmsg = append(errmsg, fmt.Sprintf(`Auth path must start with "/", got %s`, p)) } } + + if this.Path401 != nil { + var err error + *this.Path401, err = this.checkCustomErrorFile(*this.Path401, "401") + if err != nil { + errmsg = append(errmsg, err.Error()) + } + } + } + + if this.TLS != nil { + if this.TLS.PublicKey == "" || this.TLS.PrivateKey == "" { + errmsg = append(errmsg, "Both certificate path and key path should be provided") + } else { + if err := phelper.IsNonEmptyFile(this.TLS.PublicKey); err != nil { + errmsg = append(errmsg, fmt.Sprintf("'%s': %s", this.TLS.PublicKey, err)) + } + if err := phelper.IsNonEmptyFile(this.TLS.PrivateKey); err != nil { + errmsg = append(errmsg, fmt.Sprintf("'%s': %s", this.TLS.PrivateKey, err)) + } + } + + if this.TLS.Port > 65535 || this.TLS.Port <= 0 { + errmsg = append(errmsg, "Available HTTPS port range is 1-65535") + } + + if this.TLS.Policy != TLSRedirect && this.TLS.Policy != TLSBoth && this.TLS.Policy != TLSOnly { + errmsg = append(errmsg, `Value of TLS policy could only be "redirect", "both" or "only"`) + return // ignore the following checking + } + + if this.TLS.Policy != TLSOnly && this.TLS.Port == this.Port { + errmsg = append(errmsg, "HTTP port and HTTPS port cannot be the same.") + } } - END: return + return } func (this *Setting) String() string { +https := `TLS: on +Certificate: %s +Private key: %s +TLS port: %d +TLS policy: %s` + + if this.TLS != nil { + https = fmt.Sprintf(https, this.TLS.PublicKey, this.TLS.PrivateKey, this.TLS.Port, this.TLS.Policy) + } else { + https = "TLS: off" + } + s := `Root: %s Port: %d Path404: %s @@ -115,13 +191,20 @@ IndexName: %s ListDir: %t Gzip: %t Debug: %t -Digest auth: %t` +Digest auth: %t +Path401: %s +%s` path404 := "" if this.Path404 != nil { path404 = *this.Path404 } + path401 := "" + if this.Path401 != nil { + path401 = *this.Path401 + } + s = fmt.Sprintf(s, this.Root, this.Port, @@ -130,7 +213,9 @@ Digest auth: %t` this.ListDir, this.Gzip, this.Debug, - !(this.Auth == nil)) + !(this.Auth == nil), + path401, + https) return s } @@ -175,12 +260,30 @@ Options: if listdir is true, show file list of the directory, if listdir is false, return 404 not found error. Default is false. - -g, -gzip= If turn on gzip compression. Default is true. + -g, -gzip= Turn on or off gzip compression. Default value is true (means turn on). + -a, -auth= Turn on digest auth and set username and password (separate by colon). After turn on digest auth, all the page require authentication. + -401= Path of a custom 401 file, relative to Root. Example: /401.html. + If authentication fails and 401 file is set, + the file content will be sent to the client. + + -tls-port= HTTPS port. Default is 443. + -tls-policy= This option indicates how to handle HTTP and HTTPS traffic. + There are three option values: redirect, both and only. + redirect: redirect HTTP to HTTPS + both: both HTTP and HTTPS are enabled + only: only HTTPS is enabled, HTTP is disabled + The default value is: only. + -cert= Load a file as a certificate. + If use with -make-cert, will generate a certificate to the path. + -key= Load a file as a private key. + If use with -make-cert, will generate a private key to the path. Other options: + -make-cert Generate a self-signed certificate and a private key used in TLS encryption. + You should use -cert and -key to set the output paths. -showconf Show config info in the log. -debug Turn on debug mode. -v, -version Show version information. @@ -206,10 +309,10 @@ func LoadConfig() { os.Exit(1) } - var configPath, root, path404, auth string - var port uint + var configPath, root, path404, auth, path401, certPath, keyPath, tlsPolicy string + var port, tlsPort uint var indexName server.Index - var version, help bool + var version, help, makeCert bool flag.StringVar(&configPath, "c", "", "Path of config file") flag.StringVar(&configPath, "config", "", "Path of config file") @@ -218,25 +321,31 @@ func LoadConfig() { // TODO: load config file } - flag.UintVar( &port, "p", 0, "HTTP port") - flag.UintVar( &port, "port", 0, "HTTP port") - flag.StringVar(&root, "r", "", "Root path of the website") - flag.StringVar(&root, "root", "", "Root path of the website") - flag.StringVar(&path404, "404", "", "Path of a custom 404 file") - flag.StringVar(&auth, "a", "", "Username and password of digest auth, separate by colon") - flag.StringVar(&auth, "auth", "", "Username and password of digest auth, separate by colon") - flag.Var( &indexName, "i", "File name of index, separate by colon") - flag.Var( &indexName, "index", "File name of index, separate by colon") - flag.BoolVar( &Config.ListDir, "l", false, "Show file list of a directory") - flag.BoolVar( &Config.ListDir, "listdir", false, "Show file list of a directory") - flag.BoolVar( &Config.Gzip, "g", true, "Turn on/off gzip compression") - flag.BoolVar( &Config.Gzip, "gzip", true, "Turn on/off gzip compression") - flag.BoolVar( &Config.ShowConf, "showconf", false, "If show config info in the log") - flag.BoolVar( &Config.Debug, "debug", false, "Turn on debug mode") - flag.BoolVar( &version, "v", false, "Show version information") - flag.BoolVar( &version, "version", false, "Show version information") - flag.BoolVar( &help, "h", false, "-h") - flag.BoolVar( &help, "help", false, "-help") + flag.UintVar( &port, "p", 0, "HTTP port") + flag.UintVar( &port, "port", 0, "HTTP port") + flag.StringVar(&root, "r", "", "Root path of the website") + flag.StringVar(&root, "root", "", "Root path of the website") + flag.StringVar(&path404, "404", "", "Path of a custom 404 file") + flag.StringVar(&path401, "401", "", "Path of a custom 401 file") + flag.StringVar(&auth, "a", "", "Username and password of digest auth, separate by colon") + flag.StringVar(&auth, "auth", "", "Username and password of digest auth, separate by colon") + flag.Var( &indexName, "i", "File name of index, separate by colon") + flag.Var( &indexName, "index", "File name of index, separate by colon") + flag.BoolVar( &Config.ListDir, "l", false, "Show file list of a directory") + flag.BoolVar( &Config.ListDir, "listdir", false, "Show file list of a directory") + flag.BoolVar( &Config.Gzip, "g", true, "Turn on/off gzip compression") + flag.BoolVar( &Config.Gzip, "gzip", true, "Turn on/off gzip compression") + flag.BoolVar( &Config.ShowConf, "showconf", false, "If show config info in the log") + flag.BoolVar( &Config.Debug, "debug", false, "Turn on debug mode") + flag.BoolVar( &version, "v", false, "Show version information") + flag.BoolVar( &version, "version", false, "Show version information") + flag.BoolVar( &help, "h", false, "Show help message") + flag.BoolVar( &help, "help", false, "Show help message") + flag.BoolVar( &makeCert, "make-cert", false, "Generate a self-signed certificate and a private key") + flag.StringVar(&certPath, "cert", "", "Path of certificate") + flag.StringVar(&keyPath, "key", "", "Path of private key") + flag.UintVar( &tlsPort, "tls-port", 0, "HTTPS port") + flag.StringVar(&tlsPolicy, "tls-policy", "", "TLS policy") flag.Usage = usage @@ -251,6 +360,38 @@ func LoadConfig() { os.Exit(0) } + if makeCert { + err = makeCertFiles(certPath, keyPath, false) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %s\n", err) + os.Exit(1) + } + fmt.Println("Certificate and private key are created") + os.Exit(0) + } + + // load TLS config + if certPath != "" || keyPath != "" || tlsPort > 0 || tlsPolicy != "" { + if Config.TLS == nil { + Config.TLS = new(TLSOption) + } + Config.TLS.PublicKey = certPath + Config.TLS.PrivateKey = keyPath + Config.TLS.Port = tlsPort + Config.TLS.Policy = TLSPolicy(tlsPolicy) + + } + + // set default value for Config.TLS + if Config.TLS != nil { + if Config.TLS.Port == 0 { + Config.TLS.Port = DefaultTLSPort + } + if Config.TLS.Policy == "" { + Config.TLS.Policy = DefaultTLSPolicy + } + } + if port > 0 { Config.Port = port } @@ -273,11 +414,15 @@ func LoadConfig() { } authPair := strings.SplitN(auth, ":", 2) if len(authPair) != 2 { - fmt.Fprintf(os.Stderr, "Config error: format of auth not correct") + fmt.Fprintln(os.Stderr, "Config error: format of auth not correct") os.Exit(1) } Config.Auth.Username = authPair[0] Config.Auth.Password = authPair[1] + + if path401 != "" { + Config.Path401 = &path401 + } } // check Config diff --git a/global/makecert.go b/global/makecert.go new file mode 100644 index 0000000..8b6773a --- /dev/null +++ b/global/makecert.go @@ -0,0 +1,43 @@ +package global + +import "fmt" +import "os" +import "github.com/m3ng9i/go-utils/tls" + + +func makeCertFiles(cert, key string, overwrite bool) error { + if cert == "" || key == "" { + return fmt.Errorf("Both certificate path and key path should be provided") + } + + // if the certificate or private key is exist, return an error + if !overwrite { + certExist := false + keyExist := false + if _, err := os.Stat(cert); err == nil { + certExist = true + } + if _, err := os.Stat(key); err == nil { + keyExist = true + } + if certExist && keyExist { + return fmt.Errorf("Certificate and private key are all exist, remove them and try again.") + } + if certExist { + return fmt.Errorf("Certificate is exist, remove it and try again.") + } + if keyExist { + return fmt.Errorf("Private key is exist, remove it and try again.") + } + } + + // generate certificate and private key + + option := tls.DefaultCertOption() + option.PublicKey = cert + option.PrivateKey = key + option.Organization = "RanServer" + + return tls.MakeCert(option) +} + diff --git a/ran.go b/ran.go index 16b9324..8a3620c 100755 --- a/ran.go +++ b/ran.go @@ -24,6 +24,29 @@ func catchSignal() { } +func startLog() { + msg := "System: Ran is running on " + + if global.Config.TLS != nil { + switch global.Config.TLS.Policy { + case global.TLSRedirect: + msg += fmt.Sprintf("HTTPS port %d, all traffic from HTTP port %d will redirect to HTTPS port", + global.Config.TLS.Port, global.Config.Port) + + case global.TLSBoth: + msg += fmt.Sprintf("HTTP port %d and HTTPS port %d", global.Config.Port, global.Config.TLS.Port) + + case global.TLSOnly: + msg += fmt.Sprintf("HTTPS port %d", global.Config.TLS.Port) + } + } else { + msg += fmt.Sprintf("HTTP port %d", global.Config.Port) + } + + global.Logger.Info(msg) +} + + func main() { global.LoadConfig() @@ -46,16 +69,58 @@ func main() { var wg sync.WaitGroup defer wg.Wait() - global.Logger.Infof("System: Ran is running on port %d", global.Config.Port) + startLog() - server := server.NewRanServer(global.Config.Config, global.Logger) + ran := server.NewRanServer(global.Config.Config, global.Logger) - wg.Add(1) - go func() { - err := http.ListenAndServe(fmt.Sprintf(":%d", global.Config.Port), server.Serve()) - if err != nil { - global.Logger.Fatal(err) + startHTTPServer := func() { + wg.Add(1) + go func() { + err := http.ListenAndServe(fmt.Sprintf(":%d", global.Config.Port), ran.Serve()) + if err != nil { + global.Logger.Fatal(err) + } + wg.Done() + }() + } + + startTLSServer := func() { + wg.Add(1) + go func() { + err := http.ListenAndServeTLS(fmt.Sprintf(":%d", global.Config.TLS.Port), + global.Config.TLS.PublicKey, + global.Config.TLS.PrivateKey, + ran.Serve()) + if err != nil { + global.Logger.Fatal(err) + } + wg.Done() + }() + } + + redirectToHTTPS := func() { + wg.Add(1) + go func() { + err := http.ListenAndServe(fmt.Sprintf(":%d", global.Config.Port), ran.RedirectToHTTPS(global.Config.TLS.Port)) + if err != nil { + global.Logger.Fatal(err) + } + wg.Done() + }() + } + + if global.Config.TLS != nil { + // turn on TLS encryption + + startTLSServer() + + if global.Config.TLS.Policy == global.TLSRedirect { + redirectToHTTPS() + } else if global.Config.TLS.Policy == global.TLSBoth { + startHTTPServer() } - wg.Done() - }() + } else { + startHTTPServer() + } + } diff --git a/readme.md b/readme.md index 1f51ee1..d156d50 100755 --- a/readme.md +++ b/readme.md @@ -11,7 +11,8 @@ Ran is a simple web server for serving static files. - Automatic gzip compression - Digest authentication - Access logging -- Custom 404 error file +- Custom 401 and 404 error file +- TLS encryption ## What is Ran for? @@ -58,14 +59,15 @@ Index file | index.html, index.htm List files of directories | false Gzip | true Digest auth | false +TLS encryption | off Open http://127.0.0.1:8080 in browser to see your website. -You can use the options below to override the default configuration. +You can use the command line options to override the default configuration. -``` Options: +``` -r, -root= Root path of the site. Default is current working directory. -p, -port= HTTP port. Default is 8080. -404= Path of a custom 404 file, relative to Root. Example: /404.html. @@ -76,42 +78,104 @@ Options: if listdir is true, show file list of the directory, if listdir is false, return 404 not found error. Default is false. - -g, -gzip= If turn on gzip compression. Default is true. + -g, -gzip= Turn on or off gzip compression. Default value is true (means turn on). + -a, -auth= Turn on digest auth and set username and password (separate by colon). After turn on digest auth, all the page require authentication. + -401= Path of a custom 401 file, relative to Root. Example: /401.html. + If authentication fails and 401 file is set, + the file content will be sent to the client. + + -tls-port= HTTPS port. Default is 443. + -tls-policy= This option indicates how to handle HTTP and HTTPS traffic. + There are three option values: redirect, both and only. + redirect: redirect HTTP to HTTPS + both: both HTTP and HTTPS are enabled + only: only HTTPS is enabled, HTTP is disabled + The default value is: only. + -cert= Load a file as a certificate. + If use with -make-cert, will generate a certificate to the path. + -key= Load a file as a private key. + If use with -make-cert, will generate a private key to the path. +``` + +Other options: + +``` + -make-cert Generate a self-signed certificate and a private key used in TLS encryption. + You should use -cert and -key to set the output paths. + -showconf Show config info in the log. + -debug Turn on debug mode. + -v, -version Show version information. + -h, -help Show help message. ``` -Example 1: Start a server in the current directory and set port to 8888: +If you want to shutdown Ran, type `ctrl+c` in the terminal, or kill it in the task manager. + +### Examples + +Example 1: Start a server in the current directory and set port to 8888 ```bash ran -p=8888 ``` -Example 2: Set root to /tmp, list files of directories and set a custom 404 page: +Example 2: Set root to /tmp, list files of directories and set a custom 404 page ```bash ran -r=/tmp -l=true -404=/404.html ``` -Example 3: Turn off gzip compression, set access username and password: +`-l=true` can be shorted to `-l` for convenience. + +Example 3: Turn off gzip compression, set access username and password and set a custom 401 page ```bash -ran -g=false -a=user:pass +ran -g=false -a=user:pass -401=/401.html ``` -Example 4: Set custom index file: +Example 4: Set custom index file ```bash ran -i default.html:index.html ``` -Other options: +Example 5: Turn on TLS encryption + +If you want to turn on TLS encryption (https), you should use `-cert` to load a certificate and `-key` to load a private key. + +The default TLS port is 443, you can use `-tls-port` to set it to another port. + +The following command load a certificate and a private key, and set TLS port to 9999. It can be browsed at https://127.0.0.1:9999. +```bash +ran -cert=/path/to/cert.pem -key=/path/to/key.pem -tls-port=9999 ``` - -showconf Show config info in the log. - -debug Turn on debug mode. - -v, -version Show version information. - -h, -help Show help message. + +Example 6: Control HTTP and HTTPS traffic + +When you turn on TLS, you can choose to disable HTTP, redirect HTTP to HTTPS or let them work together. + +You can use `-tls-policy` to control HTTP and HTTPS traffic: + +- If set to "redirect", all HTTP traffic will be redirect to HTTPS. +- If set to "both", both HTTP and HTTPS are enabled. +- If set to "only", only HTTPS is enabled, HTTP is disabled. + +If not provide `-tls-policy`, the default value "only" will be used. + +An example: + +```bash +ran -cert=cert.pem -key=key.pem -tls-policy=redirect +``` + +Example 7: Create a self-signed certificate and a private key + +For testing purposes or internal usage, you can use `-make-cert` to create a self-signed certificate and a private key. + +```bash +ran -make-cert -cert=/path/to/cert.pem -key=/path/to/key.pem ``` ## Tips and tricks @@ -151,7 +215,6 @@ Read the source code of [CanBeCompressed()](https://github.com/m3ng9i/go-utils/b The following functionalities will be added in the future: - Load config from file -- TLS encryption - IP filter - Custom log format - etc diff --git a/server/config.go b/server/config.go index 2c225e0..45c41ea 100644 --- a/server/config.go +++ b/server/config.go @@ -30,6 +30,9 @@ type Config struct { Path404 *string // Abspath of custom 404 file, under directory of Root. // When a 404 not found error occurs, the file's content will be send to client. // nil means do not use 404 file. + Path401 *string // Abspath of custom 401 file, under directory of Root. + // When a 401 unauthorized error occurs, the file's content will be send to client. + // nil means do not use 401 file. IndexName Index // File name of index, priority depends on the order of values. // Default is []string{"index.html", "index.htm"}. ListDir bool // If no index file provide, show file list of the directory. diff --git a/server/error.go b/server/error.go index 7976ff8..a70f7ce 100644 --- a/server/error.go +++ b/server/error.go @@ -58,3 +58,27 @@ func ErrorFile404(w http.ResponseWriter, abspath string) (int64, error) { n, _ := w.Write(b) return int64(n), nil } + + +func errorFile401(config Config) (a *hhelper.AuthFile, err error) { + if config.Path401 != nil { + tp, _ := hhelper.FileContentType(path.Ext(*config.Path401)) + if tp == "" { + tp = "text/html; charset=utf-8" + } + + b, e := ioutil.ReadFile(*config.Path401) + if e != nil { + err = e + return + } + + a = new(hhelper.AuthFile) + a.ContentType = tp + a.Body = b + + return + } + + return +} diff --git a/server/log_handler.go b/server/log_handler.go index 0d40378..a78ae21 100644 --- a/server/log_handler.go +++ b/server/log_handler.go @@ -42,14 +42,15 @@ Below are format specifiers and there meanings: %n Number of bytes transferred %t Response time %c Compression status (gzip / none) +%S Scheme (http or https) */ type LogLayout string -var LogLayoutNormal LogLayout = `Access #%i: [Status: %s] [Host: %h] [IP: %a] [Method: %m] [URL: %l] [Referer: %r] [UA: %u] [Size: %n] [Time: %t] [Compression: %c]` +var LogLayoutNormal LogLayout = `Access #%i: [Status: %s] [Host: %h] [IP: %a] [Method: %m] [Scheme: %S] [URL: %l] [Referer: %r] [UA: %u] [Size: %n] [Time: %t] [Compression: %c]` -var LogLayoutShort LogLayout = `Access #%i: [%s] [%h] [%a] [%m] [%l] [%r] [%u] [%n] [%t] [%c]` +var LogLayoutShort LogLayout = `Access #%i: [%s] [%h] [%a] [%m] [%S] [%l] [%r] [%u] [%n] [%t] [%c]` var LogLayoutMin LogLayout = `Access #%i: [%s] [%a] [%m] [%l] [%n]` @@ -63,7 +64,7 @@ func (this *LogLayout) IsLegal() bool { OUTER: for _, c := range *this { if in { - for _, ch := range []rune("%ishamlruntc") { + for _, ch := range []rune("%ishamlruntcS") { if c == ch { in = false continue OUTER @@ -144,6 +145,16 @@ func (this *RanServer) accessLog(sniffer *hhelper.ResponseSniffer, r *http.Reque buf.WriteString("none") } + // scheme + case 'S': + // Because r.URL.Scheme from the request is always empty, + // so it's need to use r.TLS to check the scheme. + if r.TLS != nil { + buf.WriteString("https") + } else { + buf.WriteString("http") + } + default: return ErrInvalidLogLayout } diff --git a/server/server.go b/server/server.go index c37a8a4..d1fe4fd 100644 --- a/server/server.go +++ b/server/server.go @@ -144,6 +144,9 @@ func randTime(n ...int64) int { } +// make the request handler chain: +// log -> digest auth -> gzip -> original handler +// TODO: add ip filter: log -> [ip filter] -> digest auth -> gzip -> original handler func (this *RanServer) Serve() http.HandlerFunc { // original ran server handler @@ -177,7 +180,19 @@ func (this *RanServer) Serve() http.HandlerFunc { time.Sleep(time.Duration(randTime()) * time.Millisecond) } - handler = da.DigestAuthHandler(handler, nil, failFunc) + var authFile *hhelper.AuthFile + + // load custom 401 file + if this.config.Path401 != nil { + var err error + authFile, err = errorFile401(this.config) + if err != nil { + this.logger.Errorf("Load 401 file error: %s", err) + } + } + + // if authFile is nil, display the default 401 error message + handler = da.DigestAuthHandler(handler, authFile, failFunc) } // log handler @@ -188,3 +203,13 @@ func (this *RanServer) Serve() http.HandlerFunc { } } + +// redirect to https page +func (this *RanServer) RedirectToHTTPS(port uint) http.HandlerFunc { + handler := this.logHandler(hhelper.RedirectToHTTPS(port)) + return func(w http.ResponseWriter, r *http.Request) { + requestId := string(getRequestId(r.URL.String())) + w.Header().Set("X-Request-Id", requestId) + handler(w, r) + } +}