Serving http and https on the same port?

2,547 views
Skip to first unread message

Peter Kleiweg

unread,
Jun 17, 2014, 6:32:20 AM6/17/14
to golan...@googlegroups.com
Is it possible with the standard library to serve http and https on the same port?

Is it possible to detect if a request is http or https?

Mat Evans

unread,
Jun 17, 2014, 7:52:21 AM6/17/14
to golan...@googlegroups.com
Hi,

Can I ask why you need this?
Browsers are setup to query different ports for http and https and therefore you would end up with a non standard frontend.

Not sure it's that simple or possible using just the standard library, but it should be possible none the less.

I'm not entirely sure how, but I suspect it would be a good start to look at net/http and the server.Serve function.
I think creating your own Server struct with a new Serve function that works out the correct proto to use could work - but having only looked at the code just now I'd have to give it some more thought.

Hope that helps.

Peter Kleiweg

unread,
Jun 17, 2014, 8:45:20 AM6/17/14
to golan...@googlegroups.com
Op dinsdag 17 juni 2014 13:52:21 UTC+2 schreef Mat Evans:

Hi,

Can I ask why you need this?
Browsers are setup to query different ports for http and https and therefore you would end up with a non standard frontend.

I can't use the default ports because they are used by a regular web server.

So I need to specify the port number in the url. The problem is, when someone tries to open an https webpage with a http connection, the browser gets some binary data, and tries so save it. (That is what Google Chrome does.) I want to send the user a http error page or redirect to the same location via https.
 
Not sure it's that simple or possible using just the standard library, but it should be possible none the less.

I'm not entirely sure how, but I suspect it would be a good start to look at net/http and the server.Serve function.
I think creating your own Server struct with a new Serve function that works out the correct proto to use could work - but having only looked at the code just now I'd have to give it some more thought.

I have been looking at it. So far, I haven't been able to figure it out.

Robert Johnstone

unread,
Jun 17, 2014, 8:50:35 AM6/17/14
to golan...@googlegroups.com
Hello,

Not using Go, but I have done this in the past.  You need to inspect the first few bytes of the connection, which should be enough to determine whether the request is unencrypted HTTP or HTTPS.  You need enough access to the server library to hook up the plumbing and buffer or push back the bytes you consumed, but it was not that complicated.  For my use case, sharing the port worked without problem, but it was not a production server.

Good luck,

Robert

Mat Evans

unread,
Jun 17, 2014, 10:07:06 AM6/17/14
to golan...@googlegroups.com
Ok - so your server has a http server on 80 and https server on 443, correct?

Is this to get around the one website per IP per server issue?

James Bardin

unread,
Jun 17, 2014, 10:22:15 AM6/17/14
to golan...@googlegroups.com


On Tuesday, June 17, 2014 6:32:20 AM UTC-4, Peter Kleiweg wrote:
Is it possible with the standard library to serve http and https on the same port?

Is it possible to detect if a request is http or https?


Here's a rough example of using bufio to Peek into a new connection. http://play.golang.org/p/5M2V9GeTZ-

Peter Kleiweg

unread,
Jun 17, 2014, 10:29:40 AM6/17/14
to golan...@googlegroups.com
I got a working example. It listens on port 12345. When it detects plain http, it forwards to port 12346, where a regular server is listening. Otherwise, it forwards to port 12347 where a TLS server is listening.

At the moment, I check for GET and POST to detect regular http. That is not sufficient. I would like to check for SSH/TLS. TLS starts with byte 0x16. But what starts SSH with?

    package main

    import (
        "fmt"
        "log"
        "io"
        "net"
        "net/http"
    )

    func main() {

        go forwarder()

        http.HandleFunc("/", hello)
        go http.ListenAndServe(":12346", Http(http.DefaultServeMux))
        go http.ListenAndServeTLS(":12347", "cert.pem", "key.pem", Https(http.DefaultServeMux))

        select{}
    }

    func forwarder() {

        listener, err := net.Listen("tcp", ":12345")
        if err != nil {
            log.Fatal(err)
        }
        for {
            local, err := listener.Accept()
            if err != nil {
                log.Fatal(err)
            }
            sniff := make([]byte, 3)
            n, err := local.Read(sniff)
            if err != nil && err != io.EOF {
                log.Fatal(err)
            }
            s := string(sniff[:n])

            var addr string
            if s == "GET"[:n] || s == "POS"[:n] {
                addr = ":12346"
            } else {
                addr = ":12347"
            }

            remote, err := net.Dial("tcp", addr)
            if err != nil {
                log.Fatal(err)
            }
            go io.Copy(local, remote)
            go func() {
                remote.Write(sniff[:n])
                io.Copy(remote, local)
            }()
        }
    }

    func Http(handler http.Handler) http.Handler {
            return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                fmt.Fprintln(w, "HTTP")
                handler.ServeHTTP(w, r)})
    }

    func Https(handler http.Handler) http.Handler {
            return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                fmt.Fprintln(w, "HTTPS")
                handler.ServeHTTP(w, r)})
    }

    func hello(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello world!")
    }

Peter Kleiweg

unread,
Jun 17, 2014, 10:52:46 AM6/17/14
to golan...@googlegroups.com
Op dinsdag 17 juni 2014 16:29:40 UTC+2 schreef Peter Kleiweg:

I got a working example. It listens on port 12345. When it detects plain http, it forwards to port 12346, where a regular server is listening. Otherwise, it forwards to port 12347 where a TLS server is listening.

At the moment, I check for GET and POST to detect regular http. That is not sufficient. I would like to check for SSH/TLS. TLS starts with byte 0x16. But what starts SSH with?

SSH also starts with 0x16.

Peter Kleiweg

unread,
Jun 17, 2014, 1:25:45 PM6/17/14
to golan...@googlegroups.com
Op dinsdag 17 juni 2014 16:22:15 UTC+2 schreef James Bardin:

Here's a rough example of using bufio to Peek into a new connection. http://play.golang.org/p/5M2V9GeTZ-

Thanks. This a better then my previous three-port solution.
I modified your example somewhat, and now I got something that automaticle redirect http to https:


    package main

    import (
        "crypto/tls"
        "fmt"
        "io"
        "log"
        "net"
        "net/http"
        "net/url"
    )

    var (
        addr   = "127.0.0.1:8080"
        config *tls.Config
    )

    type Conn struct {
        net.Conn
        b byte
        e error
        f bool
    }

    func (c *Conn) Read(b []byte) (int, error) {
        if c.f {
            c.f = false
            b[0] = c.b
            if len(b) > 1 && c.e == nil {
                n, e := c.Conn.Read(b[1:])
                if e != nil {
                    c.Conn.Close()
                }
                return n + 1, e
            } else {
                return 1, c.e
            }
        }
        return c.Conn.Read(b)
    }

    type SplitListener struct {
        net.Listener
    }

    func (l *SplitListener) Accept() (net.Conn, error) {
        c, err := l.Listener.Accept()
        if err != nil {
            return nil, err
        }

        b := make([]byte, 1)
        _, err = c.Read(b)
        if err != nil {
            c.Close()
            if err != io.EOF {
                return nil, err
            }
        }

        con := &Conn{
            Conn: c,
            b:    b[0],
            e:    err,
            f:    true,
        }

        if b[0] == 22 {
            log.Println("HTTPS")
            return tls.Server(con, config), nil
        }

        log.Println("HTTP")
        return con, nil
    }

    func main() {

        var err error

        config = &tls.Config{}
        if config.NextProtos == nil {
            config.NextProtos = []string{"http/1.1"}
        }
        config.Certificates = make([]tls.Certificate, 1)
        config.Certificates[0], err = tls.LoadX509KeyPair("cert.pem", "key.pem")
        if err != nil {
            log.Fatal(err)
        }

        ln, err := net.Listen("tcp", addr)
        if err != nil {
            log.Fatal(err)
        }

        http.HandleFunc("/", hello)

        log.Fatal(http.Serve(
            &SplitListener{Listener: ln},
            http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                if r.TLS == nil {
                    u := url.URL{
                        Scheme:   "https",
                        Opaque:   r.URL.Opaque,
                        User:     r.URL.User,
                        Host:     addr,
                        Path:     r.URL.Path,
                        RawQuery: r.URL.RawQuery,
                        Fragment: r.URL.Fragment,
                    }
                    http.Redirect(w, r, u.String(), http.StatusMovedPermanently)
                } else {
                    http.DefaultServeMux.ServeHTTP(w, r)
                }
            })))

Peter Kleiweg

unread,
Jun 19, 2014, 11:25:06 AM6/19/14
to golan...@googlegroups.com
Op dinsdag 17 juni 2014 16:22:15 UTC+2 schreef James Bardin:

I see in the source of ListenAndServeTLS, some things are done with *srv.TLSConfig and with tcpKeepAliveListener.
Are these essential? Should I do something with the same effect when I adopt your example?

Message has been deleted

James Bardin

unread,
Jun 19, 2014, 12:42:02 PM6/19/14
to Peter Kleiweg, golan...@googlegroups.com
It's not essential, but you can enable tcp keepalive in your own Accpet method by adding the same options that are in tcpKeepAliveListener.Accept.


--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/4oZp1csAm2o/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
0 new messages