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"}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

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