Skip to content

Instantly share code, notes, and snippets.

@JenniferMack JenniferMack/https.go
Last active Jul 4, 2019

Embed
What would you like to do?
Sample TLS server in #go
// Redirecting (http => https) server, will finish request before shutting down.
// All-in-one file for example purposes
// Everything below main would normally go into a `server.go` file to reduce clutter.
// Redirection is normally on, and can be disabled with `-redir=false` on the command line.
// Use with dummy certs for testing.
package main
import (
"context"
"crypto/tls"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"sync"
"time"
)
// Env is a basic DI container for handlers
type Env struct {
log *log.Logger
//*sql.DB
}
var (
flagHTTP = flag.String("http", ":80", "http server port")
flagTLS = flag.String("tls", ":443", "https server port")
flagCert = flag.String("cert", "server.crt", "server tls certificate")
flagKey = flag.String("key", "server.key", "server tls key")
flagRedir = flag.Bool("redir", true, "run the redirection server")
)
func init() {
flag.Parse()
}
func main() {
wg := sync.WaitGroup{}
endProcess := make(chan struct{})
go catchSignal(endProcess)
// Setup environment
e := &Env{
log: log.New(os.Stderr, "[demo] ", 0),
}
// HTTP server and redirection
if *flagRedir {
httpMux := http.NewServeMux()
httpServ := newServer(*flagHTTP, httpMux)
httpMux.Handle("/", redirHTTP(e))
wg.Add(1)
go func() {
defer wg.Done()
shutDown(httpServ, endProcess)
}()
go serveHTTP(httpServ, endProcess)
}
// TLS server
tlsMux := http.NewServeMux()
tlsServ := newServer(*flagTLS, tlsMux)
tlsServ.TLSConfig = setupTLS()
wg.Add(1)
go func() {
defer wg.Done()
shutDown(tlsServ, endProcess)
}()
// TLS routing
tlsMux.Handle("/", serveIndex(e))
if err := tlsServ.ListenAndServeTLS(*flagCert, *flagKey); err != http.ErrServerClosed {
log.Printf("TLS server: %v", err)
close(endProcess)
}
wg.Wait()
}
// Sample slow function w/ DI
func serveIndex(env *Env) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for _, v := range []byte("098765431") {
_, err := fmt.Fprint(w, string(v))
if err != nil {
env.log.Println(err)
return
}
time.Sleep(500 * time.Millisecond)
}
})
}
// Run HTTP server in Go routine
func serveHTTP(s *http.Server, ch chan struct{}) {
if err := s.ListenAndServe(); err != http.ErrServerClosed {
log.Printf("HTTP server: %v", err)
close(ch)
}
}
// Redirect handler used by the HTTP server
func redirHTTP(env *Env) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
u := r.URL
u.Scheme = "https"
host := r.Host
if *flagHTTP != ":80" {
h, _, err := net.SplitHostPort(r.Host)
if err != nil {
fmt.Fprintf(w, "malformed URL: %s", err)
if err != nil {
env.log.Println(err)
}
return
}
host = h
}
u.Host = host + *flagTLS
http.Redirect(w, r, u.String(), http.StatusMovedPermanently)
})
}
// Interrupt catcher
func catchSignal(ch chan struct{}) {
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, os.Interrupt)
<-sigint
close(ch)
}
// Shutdown handler, will let pending requests complete
func shutDown(s *http.Server, sig chan struct{}) {
log.Println("HTTP/S server starting on", s.Addr)
<-sig
if err := s.Shutdown(context.Background()); err != nil {
log.Printf("Server shutdown: %v", err)
}
log.Println("HTTP/S server has shut down on", s.Addr)
}
// New server setup, tuned a bit tighter than
// https://blog.cloudflare.com/exposing-go-on-the-internet/
func newServer(p string, m *http.ServeMux) *http.Server {
srv := &http.Server{
Addr: p,
Handler: m,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 25 * time.Second,
}
return srv
}
// Using "modern" tls setup, IE 11+ etc.
// https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
// https://blog.cloudflare.com/exposing-go-on-the-internet/
func setupTLS() *tls.Config {
tls := &tls.Config{
// Causes servers to use Go's default ciphersuite preferences,
// which are tuned to avoid attacks.
PreferServerCipherSuites: true,
// Only use curves which have assembly implementations
CurvePreferences: []tls.CurveID{
tls.CurveP256,
tls.X25519, // Go 1.8 only
},
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, // Go 1.8 only
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, // Go 1.8 only
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
}
return tls
}
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestRedir8080(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://localhost:8080/foo.html", nil)
res := httptest.NewRecorder()
*flagHTTP = ":8080"
*flagTLS = ":443"
redirHTTP(&Env{}).ServeHTTP(res, req)
got := res.Header().Get("Location")
want := "https://localhost:443/foo.html"
if got != want {
t.Error(got)
}
}
func TestRedir4433(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://localhost:8080/foo.html", nil)
res := httptest.NewRecorder()
*flagHTTP = ":8080"
*flagTLS = ":4433"
redirHTTP(&Env{}).ServeHTTP(res, req)
got := res.Header().Get("Location")
want := "https://localhost:4433/foo.html"
if got != want {
t.Error(got)
}
}
func TestRedirReverse(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://localhost/foo.html", nil)
res := httptest.NewRecorder()
*flagHTTP = ":80"
*flagTLS = ":4433"
redirHTTP(&Env{}).ServeHTTP(res, req)
got := res.Header().Get("Location")
want := "https://localhost:4433/foo.html"
if got != want {
t.Error(got)
}
}
func TestRedirDefault(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://localhost/foo.html", nil)
res := httptest.NewRecorder()
*flagHTTP = ":80"
*flagTLS = ":443"
redirHTTP(&Env{}).ServeHTTP(res, req)
got := res.Header().Get("Location")
want := "https://localhost:443/foo.html"
if got != want {
t.Error(got)
}
}
-----BEGIN CERTIFICATE-----
MIIDBjCCAe6gAwIBAgIBezANBgkqhkiG9w0BAQsFADAzMQswCQYDVQQGEwJnYjEN
MAsGA1UEChMEVGVzdDEVMBMGA1UEAxMMVGVzdCBSb290IENBMB4XDTE5MDYwODAw
NDcyN1oXDTE5MDcwODAwNDcyN1owMDELMAkGA1UEBhMCZ2IxDTALBgNVBAoTBFRl
c3QxEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAOBlFsqA/7VdVleYmR8Liyta86xbe6R9wc3Z6GaufJ59duXoNyi+vk4H
EZQ4/C6sglIK9KFSoVC5xyIM8HJ8eDLkl4eRajKnKielq4KlMQz7y3TWeCpWBwCs
D0LNHRRgsrLe8DU+3CX4eblKQrTlEWT8qTkhTFLbXSdwDi2JTI9vP6uYkGIa5nYP
nMiM3Hkt6GoQsAJNKY1zuDoGlEIvvjv2+YT9u8XcdfZvb2xUmrTAw5XEtRomEvqi
5B+tjx0UsPLQQ/F7kY1WMqQlwyd3koP+QTnOj9rZWZnFU0C+pCugQ1/dMNLsXYcD
Xre3eE6Qa+sKchv55BZiwJq3bUFI+ykCAwEAAaMoMCYwDgYDVR0PAQH/BAQDAgOI
MBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEACvlXnLVn
CRZDwZyioehXatKxtekLUiEYIyDfm3BS4qx94i/xBZNyRKYs9VVLtorfYhtAAyyh
ySe+1YMlDtgi+58wmnGbvtQiYXvV8S+CydvEOj0X3jW1/arhjvF4wh08aLa2pgh+
3LiXZXiU45Z0YDxHlsz0eunln38mOV/z89P2hEY3GosWPH6Aqj3lTkHw+wN5vH/o
PtKdQJ3Is43hALi5Ni7hcfhXMhKx+0jU75gSQQ2orCa22dpkhyIupp6fUv8rjt/b
WAIBhcJnYtrycmePtjQ2VgfOLboNWOJqjeb5kBADhIyMB4grI9oRWACzGRkrJOQG
yNy3WaLCGHTL6A==
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDgZRbKgP+1XVZX
mJkfC4srWvOsW3ukfcHN2ehmrnyefXbl6Dcovr5OBxGUOPwurIJSCvShUqFQucci
DPByfHgy5JeHkWoypyonpauCpTEM+8t01ngqVgcArA9CzR0UYLKy3vA1Ptwl+Hm5
SkK05RFk/Kk5IUxS210ncA4tiUyPbz+rmJBiGuZ2D5zIjNx5LehqELACTSmNc7g6
BpRCL7479vmE/bvF3HX2b29sVJq0wMOVxLUaJhL6ouQfrY8dFLDy0EPxe5GNVjKk
JcMnd5KD/kE5zo/a2VmZxVNAvqQroENf3TDS7F2HA163t3hOkGvrCnIb+eQWYsCa
t21BSPspAgMBAAECggEBAJ4EsgsC0o+eXfornNCu6V8rmmMqvSQ15u+WX0FH6LwX
cE4wu/82a385HRj7FCOuGcu6qVCdhrn5SZDh+cU1f9OfBFJUhauL6nSnBuNmfuc8
vabWjSKLGD1R7SFGng7GlbC+q/ti+9bFZrqj39vRX/F0t5pip4PFtJDcKS/J+x8Z
j0n/PTHWUKgqd/uPuti8CUXpr0W+cnTACBb+Pzjg7Ex8oz7g3TTbrLehP2miGACA
FSsJ7ulXM1A5DYBSqzrfAL0X2e0y8w7aYq/byvrwFFTKGs45WrRRN/0mWelfGAy7
NqDbm/IbMkJ9cKYgPJrlgy2ls7l5zfGaFYJHpx+PvM0CgYEA/z1wSnq6mRXPnd/i
6ClV53hISfnsW1BED4U6dnHeAuTZMkIUU6bBuLEkC86pfJYFUr8oQI9X5/EBCCTv
D5EgfY9z+CRZUqt3nYV+qQbhAKD9o1eA+DEKy1KGtPpVLLYvVZpSMJYEYHXMt3yO
L/6ZKLWEz+EyOFHdVZ3Y69H80icCgYEA4RAjXOa62ru4LpRlM9P/ENCStq2ltDND
8f+sriyILLl0PEt7NC5MYcKOpuQDRdJ+XrtyZ1VQnnldFslp5GY/5bq+rYAgFh+9
A7nJ8CvU5QJmz7/xrGCuWot7ZYDBYPu152L7fs+myuKb87LzFYo0CP90utt6u0wK
PCFSva38Ki8CgYBEu1axt7rn3me6K7/+pKLtYgaVZSewrzyksUu8+Yy/WsDiN8kh
fZZNFeaPB2e8lOl6Dt/YAsG5Q7zzZH67wmjtZuPjvmYPTP04/enNunx8nt2uTrH8
I7i0Z9S+h2rIT4cPli7rxnoHP7GQ94mSgwzbWYYaNJpBcEoZ9Bel6TYboQKBgED3
jZ21pN6bLZGUmJobaheKfa9p9NlNqyFiMpCcnjp61kjJaSko7DeUF+WpECDJoFzs
MxwNDpaQZgS742tg8LT58SzYPWrlgoRezyIbJPtudAsoVzTxO6M7fEVSo3/BaUL/
2aVDf9w1CduRHoZrPJYUV7fQv17tlk7BN8c1QANVAoGBAJXm7kPZqWZkIEdT1sHw
V1QoobZ7CGLlXxy4y4bXDSke5SZzgn73CC2SLljpn7t6SZeM+ubku8CUpJ/7txqt
IZ2w1RDEJFq63uYI5TBQdqA4zN4rpLJH9PbB+GK5magHHUrWjQG15vXFVDB54p6A
KQKmDgMcMFWmfIdaIVyxOM2i
-----END PRIVATE KEY-----
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.