go语言之http2.0
- Server
- client
之前主要说的是基于http1.1的。然后因为http1.1存在的局限性,现在http2.0越来越流行,这里来根据go的源码来分析一下http2.0的实现。首先需要注意的是http2.0是在https的基础之上,因此推荐有https的基础或者看前面openssl https分析。首先看一下源码的实现。
Server
package main
import (
"fmt"
"net/http"
"os"
"runtime"
"strings"
)
type textHandler struct {
responseText string
}
func (th *textHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, th.responseText)
}
type indexHandler struct{}
func (ih *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte("Hello"))
}
func main() {
mux := http.NewServeMux()
// 获取当前的目录
_, fp, _, _ := runtime.Caller(0)
dir := getParentDirectory(fp)
mux.Handle("/", &indexHandler{})
thWelcome := &textHandler{"TextHandler !"}
mux.Handle("/text", thWelcome)
// 监听服务 server.pem是签名证书 server.key是私钥
err := http.ListenAndServeTLS(":8084", fmt.Sprintf("%s/%s", dir, "server.pem"), fmt.Sprintf("%s/%s", dir, "server.key"), mux)
fmt.Printf("err:%v\n", err)
}
// 获取当前的目录
func getParentDirectory(directory string) string {
return substr(directory, 0, strings.LastIndex(directory, string(os.PathSeparator)))
}
// 截取字符串
func substr(s string, pos, length int) string {
runes := []rune(s)
l := pos + length
if l > len(runes) {
l = len(runes)
}
return string(runes[pos:l])
}
需要注意的是这里因为之所以使用的runtime.Caller还不是使用./server.pem和./server.key的方式去,是因为往往受到运行目录的影响,并不是当前的目录,所以使用runtime.Caller去获取当前的目录。
然后ListenAndServeTLS 是在监听https的目录,第一个就是端口,然后第二个和第三个参数分别是签名的证书和生成的私钥。
client
package main
import (
"crypto/x509"
"fmt"
"io/ioutil"
"crypto/tls"
"net/http"
"net/http2"
"log"
"os"
"runtime"
"strings"
"time"
)
func main() {
_, fp, _, _ := runtime.Caller(0)
dir := getParentDirectory(fp)
// 客户端证书
clientCertFile := fmt.Sprintf("%s/%s", dir, "client.pem")
// 客户端私钥
clientKeyFile := fmt.Sprintf("%s/%s", dir, "client.key")
// CA证书
caCertFile := clientKeyFile := fmt.Sprintf("%s/%s", dir, "ca.pem")
var cert tls.Certificate
var err error
if clientCertFile != "" && clientKeyFile != "" {
// 加载客户端的私钥和证书
cert, err = tls.LoadX509KeyPair(clientCertFile, clientKeyFile)
if err != nil {
log.Fatalf("Error creating x509 keypair from client cert file %s and client key file %s", clientCertFile, clientKeyFile)
return
}
}
// 读取ca的证书
caCert, err := ioutil.ReadFile(caCertFile)
if err != nil {
fmt.Printf("Error opening cert file %s, Error: %s", caCertFile, err)
return
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
// 使用http2的Transport
t := &http2.Transport{
TLSClientConfig: &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
},
}
// 调用
client := http.Client{Transport: t, Timeout: 15 * time.Second}
resp, err := client.Get("https://localhost:8084/")
if err != nil {
fmt.Printf("Failed get: %s\r\n", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Failed reading response body: %s\r\n", err)
}
fmt.Printf("Client Got response %d: %s %s\r\n", resp.StatusCode, resp.Proto, string(body))
}
func getParentDirectory(directory string) string {
return substr(directory, 0, strings.LastIndex(directory, string(os.PathSeparator)))
}
func substr(s string, pos, length int) string {
runes := []rune(s)
l := pos + length
if l > len(runes) {
l = len(runes)
}
return string(runes[pos:l])
}
如果生成秘钥这些比较麻烦,也可以进行跳过,简化代码,如下:
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"time"
"golang.org/x/net/http2"
)
func main() {
t := &http2.Transport{
TLSClientConfig: &tls.Config{
// 指定跳过校验
InsecureSkipVerify: true,
},
}
client := http.Client{Transport: t, Timeout: 15 * time.Second}
resp, err := client.Get("https://localhost:8084/")
if err != nil {
fmt.Printf("Failed get: %s\r\n", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Failed reading response body: %s\r\n", err)
}
fmt.Printf("Client Got response %d: %s %s\r\n", resp.StatusCode, resp.Proto, string(body))
}
这里运行一下。可以看出来已经是运行成功了。