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"}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,860评论 18 139
  • 这次探店之前,唯有锅炉咖啡我没有做任何功课,所以进入这个空间的感觉是全新的。也许正是因为之前没有看过任何有关锅炉咖...
    苗苗在故乡阅读 114评论 0 0
  • 电影《神探》可以说是银河映像第二个十年的扛鼎之作。也再一次印证了杜琪峰和韦家辉的创作组合必出精品。如果说进入200...
    重庆森林cqsenlin阅读 540评论 0 0
  • 有一种逃避方式 是不停地在寻找 清晨眯着眼儿瞅着朝阳 打开衣柜找你 追着花瓣里的小水珠 我在花园里找你 青石板上的...
    妃卿阅读 634评论 6 4
  • 无论你起多早,总有人比你更早。无论你睡多晚,总有人比你更晚。 无论你多努力,总有人比你更努力。 不管你有多辛苦,总...
    鹏城祁林阅读 551评论 0 0