Skip to content
/ hlfhr Public

๐ŸŒ Redirecting from HTTP to HTTPS on the same port, similar to nginx's error_page 497. It can also redirect from port 80 to port 443.

License

Notifications You must be signed in to change notification settings

bddjr/hlfhr

Repository files navigation

HTTPS Listener For HTTP Redirect

Redirecting from HTTP to HTTPS on the same port, similar to nginx's error_page 497.
It can also redirect from port 80 to port 443.

This is my original work - the first solution to the issue without modifying the standard library.
If you like it, please give me a starโญ. Thanks! ๐Ÿ˜Š

Setup

go get github.com/bddjr/hlfhr
// Use hlfhr.New
srv := hlfhr.New(&http.Server{
	// Write something...
})

// Port 80 redirects to port 443.  
// This option only takes effect when listening on port 443.
srv.Listen80RedirectTo443 = true

// Then just use it like [http.Server]
err := srv.ListenAndServeTLS("example.crt", "example.key")

For example:

  • Listening on port 8443, http://127.0.0.1:8443 will redirect to https://127.0.0.1:8443.
  • Listening on port 443, http://127.0.0.1 will redirect to https://127.0.0.1.

If you need to customize the redirect handler, see HttpOnHttpsPortErrorHandler Example.


Logic

flowchart TD
	Read("Hijacking net.Conn.Read")

	IsLooksLikeHTTP("First byte looks like HTTP ?")

	CancelHijacking(["โœ… Cancel hijacking..."])

	ReadRequest("๐Ÿ” Read request")

	IsHandlerExist("`
	HttpOnHttpsPort
	ErrorHandler
	exist ?`")

	Redirect{{"๐ŸŸก 307 Redirect"}}

	Handler{{"๐Ÿ’ก Handler"}}

	Close(["โŒ Close."])

    Read --> IsLooksLikeHTTP
    IsLooksLikeHTTP -- "๐Ÿ”false" --> CancelHijacking
    IsLooksLikeHTTP -- "๐Ÿ“„true" --> ReadRequest --> IsHandlerExist
	IsHandlerExist -- "โœ–false" --> Redirect --> Close
	IsHandlerExist -- "โœ…true" --> Handler --> Close
Loading

HttpOnHttpsPortErrorHandler Example

If you need http.Hijacker or http.ResponseController.EnableFullDuplex, please use hahosp.

// Check Host Header
srv.HttpOnHttpsPortErrorHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	hostname, port := hlfhr.SplitHostnamePort(r.Host)
	switch hostname {
	case "localhost":
		//
	case "www.localhost", "127.0.0.1":
		r.Host = hlfhr.HostnameAppendPort("localhost", port)
	default:
		w.WriteHeader(421)
		return
	}
	hlfhr.RedirectToHttps(w, r, 307)
})
// Script Redirect
srv.HttpOnHttpsPortErrorHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html")
	w.WriteHeader(300)
	io.WriteString(w, "<script>location.protocol='https:'</script>")
})

Method Example

New

srv := hlfhr.New(&http.Server{
	// Write something...
})

NewServer

srv := hlfhr.NewServer(&http.Server{
	// Write something...
})

ListenAndServeTLS

// Just use it like [http.ListenAndServeTLS]
var h http.Handler
err := hlfhr.ListenAndServeTLS(":443", "localhost.crt", "localhost.key", h)

ServeTLS

// Just use it like [http.ServeTLS]
var l net.Listener
var h http.Handler
err := hlfhr.ServeTLS(l, h, "localhost.crt", "localhost.key")

Redirect

var w http.ResponseWriter
hlfhr.Redirect(w, 307, "https://example.com/")

RedirectToHttps

var w http.ResponseWriter
var r *http.Request
hlfhr.RedirectToHttps(w, r, 307)

SplitHostnamePort

hostname, port := hlfhr.SplitHostnamePort("[::1]:5678")
// hostname: [::1]
// port: 5678

Hostname

hostname := hlfhr.Hostname("[::1]:5678")
// hostname: [::1]

Port

port := hlfhr.Port("[::1]:5678")
// port: 5678

HostnameAppendPort

Host := hlfhr.HostnameAppendPort("[::1]", "5678")
// Host: [::1]:5678

ReplaceHostname

Host := hlfhr.ReplaceHostname("[::1]:5678", "localhost")
// Host: localhost:5678

ReplacePort

Host := hlfhr.ReplacePort("[::1]:5678", "7890")
// Host: [::1]:7890

Ipv6CutPrefixSuffix

v6 := hlfhr.Ipv6CutPrefixSuffix("[::1]")
// v6: ::1

IsHttpServerShuttingDown

var srv *http.Server
isShuttingDown := hlfhr.IsHttpServerShuttingDown(srv)

Server.IsShuttingDown

var srv *hlfhr.Server
isShuttingDown := srv.IsShuttingDown()

NewResponse

var c net.Conn
var h http.Handler
var r *http.Request

w := NewResponse(c, true)

h.ServeHTTP(w, r)
err := w.FlushError()
c.Close()

ConnFirstByteLooksLikeHttp

b := []byte("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
looksLikeHttp := hlfhr.ConnFirstByteLooksLikeHttp(b[0])

NewBufioReaderWithBytes

var c net.Conn
var b []byte
n, err := c.Read(b)
br := hlfhr.NewBufioReaderWithBytes(b, n, c)

BufioSetReader

var r io.Reader
lr := &io.LimitedReader{R: r, N: 4096}
// Read header
br := bufio.NewReader(lr)
// Read body
hlfhr.BufioSetReader(br, r)

Test

git clone https://github.com/bddjr/hlfhr
cd hlfhr
chmod +x run.sh
./run.sh

Then run the test command printed in the terminal to verify the response content is Hello hlfhr!.

Then access it via browser and use developer tools to confirm the protocol is h2.


Reference

golang/go#49310
https://github.com/golang/go

https://tls12.xargs.org/#client-hello
https://tls13.xargs.org/#client-hello

https://developer.mozilla.org/docs/Web/HTTP

https://nginx.org/en/docs/http/ngx_http_ssl_module.html#errors


License

BSD-3-clause license

About

๐ŸŒ Redirecting from HTTP to HTTPS on the same port, similar to nginx's error_page 497. It can also redirect from port 80 to port 443.

Topics

Resources

License

Stars

Watchers

Forks