Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Run both HTTP and HTTPS in same program

Tags:

http

https

go

Why can't I run both HTTP and HTTPS from the same golang program?

Here is the code where the two servers are initiated.. The server which is initiated first will run - the second won't.. If they are switched arround the other will run and the other won't..

No errors are returned when running the program, but the requests http://www.localhost or https://secure.localhost times out

//  Start HTTP
err_http := http.ListenAndServe(fmt.Sprintf(":%d", port), http_r)
if err_http != nil {
    log.Fatal("Web server (HTTP): ", err_http)
}

//  Start HTTPS
err_https := http.ListenAndServeTLS(fmt.Sprintf(":%d", ssl_port), "D:/Go/src/www/ssl/public.crt", "D:/Go/src/www/ssl/private.key", https_r)
if err_https != nil {
    log.Fatal("Web server (HTTPS): ", err_https)
}

Here is the complete code

package main

import (
    "net/http"
    "fmt"
    "log"
    "os"
    "io"
    "runtime"

    // go get github.com/gorilla/mux
    "github.com/gorilla/mux"
)

const (
    HOST = "localhost"
)

func Handler_404(w http.ResponseWriter, r *http.Request){
    fmt.Fprint(w, "Oops, something went wrong!")
}

func Handler_www(w http.ResponseWriter, r *http.Request){
    fmt.Fprint(w, "Hello world :)")
}

func Handler_api(w http.ResponseWriter, r *http.Request){
    fmt.Fprint(w, "This is the API")
}

func Handler_secure(w http.ResponseWriter, r *http.Request){
    fmt.Fprint(w, "This is Secure")
}

func redirect(r *mux.Router, from string, to string){
    r.Host(from).Subrouter().HandleFunc("/", func (w http.ResponseWriter, r *http.Request){
        http.Redirect(w, r, to, 301)
    })
}

func main(){
    port := 9000
    ssl_port := 443

    runtime.GOMAXPROCS(runtime.NumCPU())

    http_r := mux.NewRouter()
    https_r := mux.NewRouter()

    //  HTTP 404
    http_r.NotFoundHandler = http.HandlerFunc(Handler_404)

    //  Redirect "http://HOST" => "http://www.HOST"
    redirect(http_r, HOST, fmt.Sprintf("http://www.%s:%d", HOST, port))

    //  Redirect "http://secure.HOST" => "https://secure.HOST"
    redirect(http_r, "secure."+HOST, fmt.Sprintf("https://secure.%s", HOST))

    www := http_r.Host("www."+HOST).Subrouter()
    www.HandleFunc("/", Handler_www)

    api := http_r.Host("api."+HOST).Subrouter()
    api.HandleFunc("/", Handler_api)

    secure := https_r.Host("secure."+HOST).Subrouter()
    secure.HandleFunc("/", Handler_secure)

    //  Start HTTP
    err_http := http.ListenAndServe(fmt.Sprintf(":%d", port), http_r)
    if err_http != nil {
        log.Fatal("Web server (HTTP): ", err_http)
    }

    //  Start HTTPS
    err_https := http.ListenAndServeTLS(fmt.Sprintf(":%d", ssl_port), "D:/Go/src/www/ssl/public.crt", "D:/Go/src/www/ssl/private.key", https_r)
    if err_https != nil {
        log.Fatal("Web server (HTTPS): ", err_https)
    }
}
like image 981
clarkk Avatar asked Sep 28 '14 22:09

clarkk


People also ask

Can a website run on both HTTP and HTTPS?

1 Answer. Show activity on this post. http runs on port 80, and https runs on TCP port 443. They can both be open at the same time, they can even serve different websites.

Can you run HTTP and HTTPS on the same port?

There isn't simple way to have http / https listen on the same port. You best bet is to create proxy server on a simple net socket that pipes to (http or https) based on the nature of the incoming connection (http vs. https). The connection gets refused for the HTTPS redirect.

What is the port of HTTP and HTTPS on Apache?

The default HTTP and HTTPS ports for the Web server are port 80 and 443, respectively.


4 Answers

ListenAndServe and ListenAndServeTLS open the listening socket and then loop forever serving client connections. These functions only return on an error.

The main goroutine never gets to the starting the TLS server because the main goroutine is busy waiting for HTTP connections in ListenAndServe.

To fix the problem, start the HTTP server in a new goroutine:

//  Start HTTP
go func() {
    err_http := http.ListenAndServe(fmt.Sprintf(":%d", port), http_r)
    if err_http != nil {
        log.Fatal("Web server (HTTP): ", err_http)
    }
 }()

//  Start HTTPS
err_https := http.ListenAndServeTLS(fmt.Sprintf(":%d", ssl_port),     "D:/Go/src/www/ssl/public.crt", "D:/Go/src/www/ssl/private.key", https_r)
if err_https != nil {
    log.Fatal("Web server (HTTPS): ", err_https)
}
like image 88
Simon Fox Avatar answered Oct 19 '22 15:10

Simon Fox


As previously said, both ListenAndServe and ListenAndServeTLS are blocking. That being said, I would agree that examples above are in fact resolving your issue as the point is to be in goroutine BUT same examples are not quite following go idioms.

You should be using error channels here as you want to capture ALL errors that are sent to you instead of having just one error returned back. Here's fully working sample that starts HTTP as HTTPS servers and return errors as channel that's later on used just to display errors.

package main

import (
    "log"
    "net/http"
)

func Run(addr string, sslAddr string, ssl map[string]string) chan error {

    errs := make(chan error)

    // Starting HTTP server
    go func() {
        log.Printf("Staring HTTP service on %s ...", addr)

        if err := http.ListenAndServe(addr, nil); err != nil {
            errs <- err
        }

    }()

    // Starting HTTPS server
    go func() {
        log.Printf("Staring HTTPS service on %s ...", addr)
        if err := http.ListenAndServeTLS(sslAddr, ssl["cert"], ssl["key"], nil); err != nil {
            errs <- err
        }
    }()

    return errs
}

func sampleHandler(w http.ResponseWriter, req *http.Request) {
    w.Header().Set("Content-Type", "text/plain")
    w.Write([]byte("This is an example server.\n"))
}

func main() {
    http.HandleFunc("/", sampleHandler)

    errs := Run(":8080", ":10443", map[string]string{
        "cert": "/path/to/cert.pem",
        "key":  "/path/to/key.pem",
    })

    // This will run forever until channel receives error
    select {
    case err := <-errs:
        log.Printf("Could not start serving service due to (error: %s)", err)
    }

}

Hope this helps! :)

like image 12
Nevio Vesić Avatar answered Oct 19 '22 14:10

Nevio Vesić


The ListenAndServe (and ListenAndServeTLS) functions do not return to their caller (unless an error is encountered). You can test this by trying to print something in between the two calls.

like image 4
Greg Hewgill Avatar answered Oct 19 '22 16:10

Greg Hewgill


func serveHTTP(mux *http.ServeMux, errs chan<- error) {
  errs <- http.ListenAndServe(":80", mux)
}

func serveHTTPS(mux *http.ServeMux, errs chan<- error) {
  errs <- http.ListenAndServeTLS(":443", "fullchain.pem", "privkey.pem", mux)
}


func main() {
  mux := http.NewServeMux()
  // setup routes for mux     // define your endpoints
  errs := make(chan error, 1) // a channel for errors
  go serveHTTP(mux, errs)     // start the http server in a thread
  go serveHTTPS(mux, errs)    // start the https server in a thread
  log.Fatal(<-errs)           // block until one of the servers writes an error
}
like image 3
Emre Avatar answered Oct 19 '22 15:10

Emre