go语言实现一个端口同时支持HTTP和HTTPS REST API

go语言实现一个端口同时支持HTTP和HTTPS REST API

go语言的http.Server模块能够提供HTTP服务和HTTPS服务,但是对同一个端口只能要么提供HTTP服务,要么提供HTTPS服务,不能同时提供服务。

我们提供的办法是使用一个公共的端口作为监听,然后让HTTP和HTTPS服务分别监听在各自的端口上,在公共端口服务区分这是HTTP请求还是HTTPS请求,各自转发到所服务的监听。

proxy.jpg

完整代码如下

package main

import (
    "os"
    "fmt"
    "log"
    "net"
    "flag"
    "bytes"
    "net/http"
    "io/ioutil"
    "crypto/tls"
    "crypto/x509"
    "encoding/json"
    "github.com/gorilla/mux"
)

var (
    port       int
    caroots    string
    keyfile    string
    signcert   string
)

func init() {
    flag.IntVar(&port,          "port",     8080,       "The host port on which the REST server will listen")
    flag.StringVar(&caroots,    "caroot",   "",         "Path to file containing PEM-encoded trusted certificate(s) for clients")
    flag.StringVar(&keyfile,    "key",      "",         "Path to file containing PEM-encoded key file for service")
    flag.StringVar(&signcert,   "signcert", "",         "Path to file containing PEM-encoded sign certificate for service")
}

// Start a proxy server listen on listenport
// This proxy will forward all HTTP request to httpport, and all HTTPS request to httpsport
func proxyStart(listenport, httpport, httpsport int) {
    proxylistener, err := net.Listen("tcp", fmt.Sprintf(":%d", listenport))
    if err != nil {
        fmt.Println("Unable to listen on: %d, error: %s\n", listenport, err.Error())
        os.Exit(1)
    }
    defer proxylistener.Close()

    for {
        proxyconn, err := proxylistener.Accept()
        if err != nil {
            fmt.Printf("Unable to accept a request, error: %s\n", err.Error())
            continue
        }

        // Read a header firstly in case you could have opportunity to check request
        // whether to decline or proceed the request
        buffer := make([]byte, 1024)
        n, err := proxyconn.Read(buffer)
        if err != nil {
            //fmt.Printf("Unable to read from input, error: %s\n", err.Error())
            continue
        }

        var targetport int
        if isHTTPRequest(buffer) {
            targetport = httpport
        } else {
            targetport = httpsport
        }

        targetconn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", targetport))
        if err != nil {
            fmt.Println("Unable to connect to: %d, error: %s\n", targetport, err.Error())
            proxyconn.Close()
            continue
        }

        n, err = targetconn.Write(buffer[:n])
        if err != nil {
            fmt.Printf("Unable to write to output, error: %s\n", err.Error())
            proxyconn.Close()
            targetconn.Close()
            continue 
        }

        go proxyRequest(proxyconn, targetconn)
        go proxyRequest(targetconn, proxyconn)
    }
}

// Forward all requests from r to w
func proxyRequest(r net.Conn, w net.Conn) {
    defer r.Close()
    defer w.Close()

    var buffer = make([]byte, 4096000)
    for {
        n, err := r.Read(buffer)
        if err != nil {
            //fmt.Printf("Unable to read from input, error: %s\n", err.Error())
            break
        }

        n, err = w.Write(buffer[:n])
        if err != nil {
            fmt.Printf("Unable to write to output, error: %s\n", err.Error())
            break
        }
    }
}

func isHTTPRequest(buffer []byte) bool {
    httpMethod := []string{"GET", "PUT", "HEAD", "POST", "DELETE", "PATCH", "OPTIONS"}
    for cnt := 0; cnt < len(httpMethod); cnt++ {
        if bytes.HasPrefix(buffer, []byte(httpMethod[cnt])) {
            return true
        }
    }
    return false
}

func startHTTPSServer(address string, router *mux.Router, caroots string, keyfile string, signcert string) {
    pool := x509.NewCertPool()

    caCrt, err := ioutil.ReadFile(caroots)
    if err != nil {
        log.Fatalln("ReadFile err:", err)
    }
    pool.AppendCertsFromPEM(caCrt)

    s := &http.Server{
            Addr:    address,
            Handler: router,
            TLSConfig: &tls.Config{
                MinVersion: tls.VersionTLS12,
                ClientCAs:  pool,
                ClientAuth: tls.RequireAndVerifyClientCert,
            },
    }
    err = s.ListenAndServeTLS(signcert, keyfile)
    if err != nil {
        log.Fatalln("ListenAndServeTLS err:", err)
    }
}

func startHTTPServer(address string, router *mux.Router) {
    err := http.ListenAndServe(address, router)
    if err != nil {
        log.Fatalln("ListenAndServe err:", err)
    }
}

func SayHello(w http.ResponseWriter, r *http.Request) {
    log.Println("Entry SayHello")
    res := map[string]string {"hello": "world"}

    b, err := json.Marshal(res)
    if err == nil {
        w.WriteHeader(http.StatusOK)
        w.Header().Set("Content-Type", "application/json")
        w.Write(b)
    }

    log.Println("Exit SayHello")
}

func main() {
    flag.Parse()
    fmt.Println("Server listen on", port)

    router := mux.NewRouter().StrictSlash(true)
    router.HandleFunc("/service/hello", SayHello).Methods("GET")

    listenport, httpport, httpsport := port, port + 10, port + 20
    go startHTTPServer (fmt.Sprintf("localhost:%d", httpport), router)
    go startHTTPSServer(fmt.Sprintf("localhost:%d", httpsport), router, caroots, keyfile, signcert)
    
    proxyStart(listenport, httpport, httpsport)

    fmt.Println("Exit main")
}

上述例子中,服务端口监听在8080,然后我们为HTTP服务监听在8090,HTTPS监听在8100端口,这样所有的外部访问都只需要访问8080端口即可,proxy服务端口会转发请求到8090或者8100。

server端:

$ ./main -caroot ./ca.cer -key ./server.key -signcert ./server.cer
Server listen on 8080
2017/12/19 22:13:03 Entry SayHello
2017/12/19 22:13:03 Exit SayHello
2017/12/19 22:13:10 Entry SayHello
2017/12/19 22:13:10 Exit SayHello

client端:

$ # send HTTP request
$ curl http://localhost:8080/service/hello
{"hello":"world"}
$
$ # send HTTPS request
$ curl --cacert ./ca.cer --key ./client.key --cert ./client.cer https://localhost:8080/service/hello
{"hello":"world"}

你可能感兴趣的:(go语言实现一个端口同时支持HTTP和HTTPS REST API)