httpsify.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package main
  2. import (
  3. "crypto/tls"
  4. "flag"
  5. "fmt"
  6. "io"
  7. "log"
  8. "net"
  9. "net/http"
  10. "net/http/httputil"
  11. "net/url"
  12. "strings"
  13. "golang.org/x/crypto/acme/autocert"
  14. )
  15. var (
  16. // CMD options
  17. listen = flag.String("listen", ":443", "the local listen address")
  18. domains = flag.String("domains", "", "a comma separated strings of domain[->[ip]:port]")
  19. backend = flag.String("backend", ":80", "the default backend to be used")
  20. sslCacheDir = flag.String("ssl-cache-dir", "./httpsify-ssl-cache", "the cache directory to cache generated ssl certs")
  21. // internal vars
  22. domain_backend = map[string]string{}
  23. whitelisted = []string{}
  24. )
  25. func main() {
  26. flag.Parse()
  27. if *domains == "" {
  28. flag.Usage()
  29. fmt.Println(`Example(template): httpsify -domains "example.org,api.example.org->localhost:366, api2.example.org->:367"`)
  30. fmt.Println(`Example(real-life1): httpsify -domains "www.site.com,apiv1.site.com->:8080,apiv2.site.com->:8081" -minify=true -gzip=9`)
  31. fmt.Println(`Example(real-life2): httpsify -domains "www.site.com,site.com" -backend=:8080 -minify=true -gzip=0`)
  32. return
  33. }
  34. for _, zone := range strings.Split(*domains, ",") {
  35. parts := strings.SplitN(zone, "->", 2)
  36. if len(parts) < 2 {
  37. parts = append(parts, *backend)
  38. }
  39. parts[1] = fixUrl(parts[1])
  40. domain_backend[parts[0]] = parts[1]
  41. whitelisted = append(whitelisted, parts[0])
  42. }
  43. m := autocert.Manager{
  44. Prompt: autocert.AcceptTOS,
  45. HostPolicy: autocert.HostWhitelist(whitelisted...),
  46. Cache: autocert.DirCache(*sslCacheDir),
  47. }
  48. h := handler()
  49. s := &http.Server{
  50. Addr: *listen,
  51. Handler: h,
  52. TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
  53. }
  54. log.Fatal(s.ListenAndServeTLS("", ""))
  55. }
  56. // fix the specified url
  57. // this function will make sure that "http://" already exists,
  58. // also it will make sure that it has a hostname .
  59. func fixUrl(u string) string {
  60. u = strings.TrimPrefix(strings.TrimSpace(u), "https://")
  61. if strings.Index(u, ":") == 0 {
  62. u = "localhost" + u
  63. }
  64. if !strings.HasPrefix(u, "ws://") && !strings.HasPrefix(u, "http://") {
  65. u = "http://" + u
  66. }
  67. u = strings.TrimRight(u, "/")
  68. return u
  69. }
  70. // the proxy handler
  71. func handler() http.Handler {
  72. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  73. r.Host = strings.SplitN(r.Host, ":", 2)[0]
  74. if _, found := domain_backend[r.Host]; !found {
  75. http.Error(w, r.Host+": not found", http.StatusNotImplemented)
  76. return
  77. }
  78. r.Header["X-Forwarded-Proto"] = []string{"https"}
  79. r.Header["X-Forwarded-For"] = append(r.Header["X-Forwarded-For"], strings.SplitN(r.RemoteAddr, ":", 2)[0])
  80. u, _ := url.Parse(domain_backend[r.Host] + "/" + strings.TrimLeft(r.URL.RequestURI(), "/"))
  81. if strings.ToLower(r.Header.Get("Upgrade")) == "websocket" {
  82. NewWebsocketReverseProxy(u).ServeHTTP(w, r)
  83. return
  84. } else {
  85. proxy := httputil.NewSingleHostReverseProxy(u)
  86. defaultDirector := proxy.Director
  87. proxy.Director = func(req *http.Request) {
  88. defaultDirector(req)
  89. req.Host = r.Host
  90. req.URL = u
  91. }
  92. proxy.ServeHTTP(w, r)
  93. return
  94. }
  95. })
  96. }
  97. // the websocket proxy handler
  98. func NewWebsocketReverseProxy(u *url.URL) http.Handler {
  99. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  100. backConn, err := net.Dial("tcp", u.Host)
  101. if err != nil {
  102. http.Error(w, err.Error(), http.StatusInternalServerError)
  103. return
  104. }
  105. defer backConn.Close()
  106. hj, ok := w.(http.Hijacker)
  107. if !ok {
  108. http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError)
  109. return
  110. }
  111. clientConn, _, err := hj.Hijack()
  112. if err != nil {
  113. http.Error(w, err.Error(), http.StatusInternalServerError)
  114. return
  115. }
  116. defer clientConn.Close()
  117. message := r.Method + " " + r.URL.RequestURI() + " " + r.Proto + "\n"
  118. message += "Host: " + r.Host + "\n"
  119. for k, vals := range r.Header {
  120. for _, v := range vals {
  121. message += k + ": " + v + "\n"
  122. }
  123. }
  124. message += "\n"
  125. go io.Copy(backConn, io.MultiReader(strings.NewReader(message), r.Body, clientConn))
  126. io.Copy(clientConn, backConn)
  127. })
  128. }