From 6e5e397ffecc408a8984a26a6d646e5acb2fa555 Mon Sep 17 00:00:00 2001 From: Chase Hutchins Date: Thu, 17 Jan 2019 05:36:45 -0700 Subject: [PATCH] Optionally allow for SSL (#4) Optionally allow for SSL listener. --- .codecov.yml | 5 +-- README.md | 23 +++++++----- cmd/serve/main.go | 11 +++--- fixtures/cert.pem | 25 +++++++++++++ fixtures/key.pem | 28 +++++++++++++++ internal/commands/server.go | 31 +++++++++++++---- internal/commands/server_test.go | 60 +++++++++++++++++++++++--------- internal/config/flags.go | 9 +++-- mock/http.go | 23 ++++++++++++ 9 files changed, 174 insertions(+), 41 deletions(-) create mode 100644 fixtures/cert.pem create mode 100644 fixtures/key.pem create mode 100644 mock/http.go diff --git a/.codecov.yml b/.codecov.yml index 0d9c878..841a26f 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -13,10 +13,7 @@ coverage: patch: yes changes: no -comment: - layout: header, changes, diff, sunburst - behavior: default - require_changes: no +comment: off ignore: - "vendor/*" diff --git a/README.md b/README.md index b9f195b..47884ed 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ ## Installation -`serve` can be installed in a handeful of ways: +`serve` can be installed in a handful of ways: ### Homebrew on macOS @@ -58,17 +58,24 @@ To build from source, check out the instructions on getting started with ## Usage -Run a server from the current directory: - ```sh -serve +serve [options] [path] ``` -Or, specify the directory. Paths can be both relative and absolute: +> `[path]` defaults to `.` (relative path to the current directory) -```sh -serve /var/www # or serve -dir=/var/www -``` +Then simply open your browser to http://localhost:8080 to view your server. + +### Options + +The following configuration options are available: + +* `--host` host address to bind to (defaults to `0.0.0.0`) +* `--port` listening port (defaults to `8080`) +* `--ssl` enable https (defaults to `false`) +* `--cert` path to the ssl cert file (defaults to `cert.pem`) +* `--key` path to the ssl key file (defaults to `key.pem`) +* `--dir` directory path to serve (defaults to `.`, also configurable by `arg[0]`) ## Development diff --git a/cmd/serve/main.go b/cmd/serve/main.go index aa2f854..deeef5d 100644 --- a/cmd/serve/main.go +++ b/cmd/serve/main.go @@ -14,18 +14,21 @@ var version = "0.0.0-develop" func main() { var opt config.Flags - flag.StringVar(&opt.Host, "host", "", "host address to bind to") + flag.StringVar(&opt.Host, "host", "0.0.0.0", "host address to bind to") flag.IntVar(&opt.Port, "port", 8080, "listening port") - flag.StringVar(&opt.Dir, "dir", "", "directory to serve") + flag.BoolVar(&opt.EnableSSL, "ssl", false, "enable https") + flag.StringVar(&opt.CertFile, "cert", "cert.pem", "path to the ssl cert file") + flag.StringVar(&opt.KeyFile, "key", "key.pem", "path to the ssl key file") + flag.StringVar(&opt.Directory, "dir", "", "directory path to serve") flag.Parse() log := log.New(os.Stderr, "[serve] ", log.LstdFlags) cmd := flag.Arg(0) - dir, err := config.SanitizeDir(opt.Dir, cmd) + dir, err := config.SanitizeDir(opt.Directory, cmd) if err != nil { - log.Printf("sanitize dir: %v", err) + log.Printf("sanitize directory: %v", err) os.Exit(1) } diff --git a/fixtures/cert.pem b/fixtures/cert.pem new file mode 100644 index 0000000..8285ffd --- /dev/null +++ b/fixtures/cert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIUG4x9A3w/n65jwz3y7Wo8MDrU6QEwDQYJKoZIhvcNAQEL +BQAweTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMREwDwYDVQQHDAhOZXcgWW9y +azEVMBMGA1UECgwMRXhhbXBsZSwgTExDMRIwEAYDVQQDDAlzaXRlLnRlc3QxHzAd +BgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wHhcNMTkwMTE3MTA0NDM0WhcN +MjAwMTE3MTA0NDM0WjB5MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxETAPBgNV +BAcMCE5ldyBZb3JrMRUwEwYDVQQKDAxFeGFtcGxlLCBMTEMxEjAQBgNVBAMMCXNp +dGUudGVzdDEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALLlVETDAxfpbMrL9vlTKu2y+G8y7qNv +KIdp5FllHAtZVPMis1xV9U4xvpy7baKTKKPtKEYZGcy/gW4fEN9KlvHZSUqrLj7T +X0ySTNkwGItZy+gm1gbwvbQGtL4atgu0jPsJB662DIzq4dLL1OAFMV6VfmY9r2Hs +ARhe0XjGtXKlX+Fyqnbxsot02C01CtFDcEftHR5KUZeUHkoIHmO+5ZtRAgAIfhV/ +DQfyn+GfXOfM7PWGfy7RdyyLMrD+SwdfJFpkeeqQTi7p3PIIuHmieGOBjIOUhRv2 +IEA7PbMNwoernE3Ey6iwErPjshWhSdLFG4NfAPs/KxDKe0qByRLOfZECAwEAAaOB +nzCBnDAdBgNVHQ4EFgQUWlS44ZoMP/8IkJhHwxzJcfZ7IuIwHwYDVR0jBBgwFoAU +WlS44ZoMP/8IkJhHwxzJcfZ7IuIwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwFAYD +VR0RBA0wC4IJc2l0ZS50ZXN0MCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVy +YXRlZCBDZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQsFAAOCAQEASQ/wPIrRSsIEewDg +t6dehznWR+iBMGWGMpDEVw/IpRSN1zxLJp3i/4Yjcr98bEIP4tW27OODSJSKz11R +6/Kb/B04g3s7N4iSAehpeQXPGktNlgGojZSXi7u2y5ON6QBAle5csFxIkuOWDVwH +qM/lsVlNHGyM0BGVMm5VLi2OWSqspz6Lr6yguT7U/AJ/hPe+YjSU5Kc+OnCZ4IH0 +NcdVG5aPpDFeZ7c9v1uHa7b725lyXUYO8xfWR3QV6CsTLgRFWhwYBXF51sZbBBsr +fu78txegVWnYau4uh/nytqPoOnjoP4BAMKlynPfIpJ9TLWxosWeXro2xY5zvdFkp +XH/+0g== +-----END CERTIFICATE----- diff --git a/fixtures/key.pem b/fixtures/key.pem new file mode 100644 index 0000000..db2dc07 --- /dev/null +++ b/fixtures/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCy5VREwwMX6WzK +y/b5UyrtsvhvMu6jbyiHaeRZZRwLWVTzIrNcVfVOMb6cu22ikyij7ShGGRnMv4Fu +HxDfSpbx2UlKqy4+019MkkzZMBiLWcvoJtYG8L20BrS+GrYLtIz7CQeutgyM6uHS +y9TgBTFelX5mPa9h7AEYXtF4xrVypV/hcqp28bKLdNgtNQrRQ3BH7R0eSlGXlB5K +CB5jvuWbUQIACH4Vfw0H8p/hn1znzOz1hn8u0XcsizKw/ksHXyRaZHnqkE4u6dzy +CLh5onhjgYyDlIUb9iBAOz2zDcKHq5xNxMuosBKz47IVoUnSxRuDXwD7PysQyntK +gckSzn2RAgMBAAECggEBAIJ5/q80KHJtPnrermAER6AcU1QPKrwq271//xswQncI +jYvTeEvVKdgBMgvwK7NSb2a4FxKhRg7ucgEWSWECbvsvxmPeXBlYYv5fCguyJ4Sj +VrQYdyuStFm0Nmkc5D+/TL/fQyoq/xZcTZ5IKhfF0c8xa4I4ZU0fK2FR7qePDlHx +kAjInhIAPxCh7vhKk35duhr8r7IDQ33jVyPQ7DgsEIKRh85CVxkcwrtV1sY3LM/O +xmrYWxHzpke06qZBJROjAFKv1kV7NT3eKzgKg16yDkFqYdh38RnFsTB6/zgZ+rko +Jj23tynefYRx3e3feAvhnDQzY32HwKCA4fNm0brJrf0CgYEA7cdXzN0QLwvhvjem +t0gNdcfk0f9pM0wcYh0n7ESANsKAkjAOBqlvJ6tRV1LaqeIX+y1yeBnUIVH+dNfA +tM2nTiilvaasR1Er40c3eeyIhWJ8nC+wBGexxDg3Ys4B0azzcakCYkG6BuVdsAWD +aYdqWf6Tl80l7HwonCVFsu8nX+cCgYEAwJrX3agdZWTuAcFcdGIXWK1m8+4yGv6t +fvwh9X/rkDQHJ5HXDsHmTc8yh/Qa35OzcZJxBooW5azmzVpEbgE/HjnBpNDjp0VT +Xk5k+bZkWgp6wN8BFrh2Me8hliRs93vsUZ+fnFJWgxMTPMpOvhcw9YjucG6lGpwk +ynGkJ0/bZ8cCgYAs8hVioBbDDdfqANL+qhwBO3vBRio4jBaBZUl6m6gwsatj9rlw +AO8F7Jg/jWXP3vDxhbGxihBTDBCxPWcrxgPt/jj2FF9US7+kAn42CcP0kp1DWLBI +5ODxWj796jrly29o+K1+rTXgv9Jpx2EDvZkY0cpMU3brsLxsZ485N4OV2QKBgQCV +G0rinrOjO2/GjBs3Pnk0fYmmblD79Q37sNXZaR7ElIK1b4I+On5A3pcQCTqEu6O/ +2M8HcQAo7qH/eFJhlzV2AOCY595WMKVJ7QbfCwTFcDd3+Syumj9miOpHgguZzKY2 +yoyWSGgRMUNDXJt5LhsI+ukcwYuv/hG9aBzdEkWZIQKBgGLj5nwaJZWPJ381adJX +JhwQcnS7cZIKrAifCay1oOaOcdQq/07QdBEjR6YT/X7oZCPtiDOdat9vzWKLNEY/ +nYY+XFijSz2CKvT+CScjJSxmrsCtiNBQRtaTSKWAcgCpSqN5S+mocWmInZBVtZev +1OueDMUyPAsCabIR4HiTgAIs +-----END PRIVATE KEY----- diff --git a/internal/commands/server.go b/internal/commands/server.go index 381a740..4c5a972 100644 --- a/internal/commands/server.go +++ b/internal/commands/server.go @@ -12,6 +12,24 @@ import ( "github.com/syntaqx/serve/internal/middleware" ) +var getHTTPServerFunc = GetStdHTTPServer + +// HTTPServer defines a returnable interface type for http.Server +type HTTPServer interface { + ListenAndServe() error + ListenAndServeTLS(certFile, keyFile string) error +} + +// GetStdHTTPServer +func GetStdHTTPServer(addr string, h http.Handler) HTTPServer { + return &http.Server{ + Addr: addr, + Handler: h, + ReadTimeout: 15 * time.Second, + WriteTimeout: 15 * time.Second, + } +} + // Server implements the static http server command. func Server(log *log.Logger, opt config.Flags, dir string) error { fs := serve.NewFileServer(serve.Options{ @@ -24,13 +42,14 @@ func Server(log *log.Logger, opt config.Flags, dir string) error { middleware.CORS(), ) - server := &http.Server{ - Addr: net.JoinHostPort(opt.Host, strconv.Itoa(opt.Port)), - Handler: fs, - ReadTimeout: 15 * time.Second, - WriteTimeout: 15 * time.Second, + addr := net.JoinHostPort(opt.Host, strconv.Itoa(opt.Port)) + server := getHTTPServerFunc(addr, fs) + + if opt.EnableSSL { + log.Printf("https server listening at %s", addr) + return server.ListenAndServeTLS(opt.CertFile, opt.KeyFile) } - log.Printf("http server listening at %s", server.Addr) + log.Printf("http server listening at %s", addr) return server.ListenAndServe() } diff --git a/internal/commands/server_test.go b/internal/commands/server_test.go index a5c5066..b33548f 100644 --- a/internal/commands/server_test.go +++ b/internal/commands/server_test.go @@ -9,40 +9,68 @@ import ( "github.com/stretchr/testify/assert" "github.com/syntaqx/serve/internal/config" + "github.com/syntaqx/serve/mock" ) +func getMockHTTPServerFunc(shouldError bool) func(addr string, h http.Handler) HTTPServer { + return func(addr string, h http.Handler) HTTPServer { + return &mock.HTTPServer{ShouldError: shouldError} + } +} + +func TestGetStdHTTPServer(t *testing.T) { + _, ok := GetStdHTTPServer("", http.DefaultServeMux).(*http.Server) + assert.True(t, ok) +} + func TestServer(t *testing.T) { - t.Parallel() + getHTTPServerFunc = getMockHTTPServerFunc(false) + assert := assert.New(t) var b bytes.Buffer log := log.New(&b, "[test] ", 0) - opt := config.Flags{Port: 0} + opt := config.Flags{} - go func() { - assert.NoError(Server(log, opt, ".")) - }() + assert.NoError(Server(log, opt, ".")) + assert.Contains(b.String(), "http server listening at") - time.Sleep(200 * time.Millisecond) + getHTTPServerFunc = GetStdHTTPServer } func TestServerErr(t *testing.T) { - t.Parallel() + getHTTPServerFunc = getMockHTTPServerFunc(true) + assert := assert.New(t) var b bytes.Buffer - log := log.New(&b, "[test] ", 8888) - opt := config.Flags{Port: 8888} + log := log.New(&b, "[test] ", 0) + opt := config.Flags{} - go func() { - _ = http.ListenAndServe(":8888", nil) - }() + time.Sleep(200 * time.Millisecond) + assert.Error(Server(log, opt, ".")) time.Sleep(200 * time.Millisecond) - go func() { - assert.Error(Server(log, opt, ".")) - }() + getHTTPServerFunc = GetStdHTTPServer +} - time.Sleep(200 * time.Millisecond) +func TestServerHTTPS(t *testing.T) { + getHTTPServerFunc = getMockHTTPServerFunc(false) + + assert := assert.New(t) + + var b bytes.Buffer + log := log.New(&b, "[test] ", 0) + + opt := config.Flags{ + EnableSSL: true, + CertFile: "../../fixtures/cert.pem", + KeyFile: "../../fixtures/key.pem", + } + + assert.NoError(Server(log, opt, ".")) + assert.Contains(b.String(), "https server listening at") + + getHTTPServerFunc = GetStdHTTPServer } diff --git a/internal/config/flags.go b/internal/config/flags.go index 0326ef0..060cc21 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -9,9 +9,12 @@ var getwd = os.Getwd // Flags are the expose configuration flags available to the serve binary. type Flags struct { - Host string - Port int - Dir string + Host string + Port int + EnableSSL bool + CertFile string + KeyFile string + Directory string } // SanitizeDir allows a directory source to be set from multiple values. If any diff --git a/mock/http.go b/mock/http.go new file mode 100644 index 0000000..f5d066f --- /dev/null +++ b/mock/http.go @@ -0,0 +1,23 @@ +package mock + +import "errors" + +var ErrMock = errors.New("mock error") + +type HTTPServer struct { + ShouldError bool +} + +func (s *HTTPServer) ListenAndServe() error { + if s.ShouldError { + return ErrMock + } + return nil +} + +func (s *HTTPServer) ListenAndServeTLS(certFile, keyFile string) error { + if s.ShouldError { + return ErrMock + } + return nil +}