go语言实现一个端口同时支持HTTP和HTTPS REST API
go语言的http.Server模块能够提供HTTP服务和HTTPS服务,但是对同一个端口只能要么提供HTTP服务,要么提供HTTPS服务,不能同时提供服务。
我们提供的办法是使用一个公共的端口作为监听,然后让HTTP和HTTPS服务分别监听在各自的端口上,在公共端口服务区分这是HTTP请求还是HTTPS请求,各自转发到所服务的监听。
完整代码如下
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"}