Details how Golang implements http redirects https

  • 2020-06-15 09:15:56
  • OfStack

In the past, the program was directly bound to the only port 1 to provide http/https service, and the reverse proxy (nginx/caddy) was used to switch between http and https. As more and more services come online, one of them cannot provide such redirection directly through a reverse proxy and has to rely on the code itself. So brief note 1 on how to implement http to https redirects in your code.

Analysis of the

Whether it is a reverse proxy or the code itself, the essence of the problem is determining whether the request is an https request. If yes, it handles it directly; if not, it modifies the url address in the request and returns the client with a redirected status code (301/302/303/307). But if you analyze it carefully, another question arises, which redirection code is reasonable to return?

It would take a full page to discuss this question and probably not reach a conclusion. So I'm not going to worry about which one I'm going to return, I'm going to use 307.

implementation

How to start the analysis from the scenario where the problem occurs, we can basically draw a conclusion: in the scenario that needs to be transformed, the user habitually issues the http request first, and then the server needs to return a redirect of https. So the first step of the implementation is to create a port that listens for http requests:


go http.ListenAndServe(":8000", http.HandlerFunc(redirect))

Port 8000 is dedicated to listening for http requests and cannot block the https main process, so it is thrown to a separate coroutine for processing. redirect for redirection:


func redirect(w http.ResponseWriter, req *http.Request) { 
  _host := strings.Split(req.Host, ":")
  _host[1] = "8443"

  target := "https://" + strings.Join(_host, ":") + req.URL.Path
  if len(req.URL.RawQuery) > 0 {
    target += "?" + req.URL.RawQuery
  }

  http.Redirect(w, req, target, http.StatusTemporaryRedirect)
}

8443 is the port on which https listens. If listening on the default port 443, you can add it or not. Finally, the Redirect function in sdk is called to wrap Response.

After handling the redirection, it becomes easy to work with https again:


router := mux.NewRouter() 
  router.Path("/").HandlerFunc(handleHttps)
  c := cors.New(cors.Options{
    AllowedOrigins:  []string{"*.devexp.cn"},
    AllowedMethods:  []string{"HEAD", "GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
    AllowedHeaders:  []string{"*"},
    AllowCredentials: true,
    Debug:      false,
    AllowOriginFunc: func(origin string) bool {
      return true
    },
  })

  handler := c.Handler(router)
  logrus.Fatal(http.ListenAndServeTLS(":8443", "cert.crt", "cert.key", handler))

The complete code is as follows:


package main

import ( 
  "github.com/gorilla/mux"
  "github.com/rs/cors"
  "github.com/sirupsen/logrus"
  "net/http"
  "encoding/json"
  "log"
  "strings"
)

func main() { 
  go http.ListenAndServe(":8000", http.HandlerFunc(redirect))

  router := mux.NewRouter()
  router.Path("/").HandlerFunc(handleHttps)
  c := cors.New(cors.Options{
    AllowedOrigins:  []string{"*.devexp.cn"},
    AllowedMethods:  []string{"HEAD", "GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
    AllowedHeaders:  []string{"*"},
    AllowCredentials: true,
    Debug:      false,
    AllowOriginFunc: func(origin string) bool {
      return true
    },
  })

  handler := c.Handler(router)
  logrus.Fatal(http.ListenAndServeTLS(":8443", "cert.crt", "cert.key", handler))
}

func redirect(w http.ResponseWriter, req *http.Request) { 
  _host := strings.Split(req.Host, ":")
  _host[1] = "8443"

  // remove/add not default ports from req.Host
  target := "https://" + strings.Join(_host, ":") + req.URL.Path
  if len(req.URL.RawQuery) > 0 {
    target += "?" + req.URL.RawQuery
  }
  log.Printf("redirect to: %s", target)
  http.Redirect(w, req, target,
    // see @andreiavrammsd comment: often 307 > 301
    http.StatusTemporaryRedirect)
}

func handleHttps(w http.ResponseWriter, r *http.Request) { 
  json.NewEncoder(w).Encode(struct {
    Name string
    Age  int
    Https bool
  }{
    "lala",
    11,
    true,
  })
}


Related articles: