用Golang编写HTTP / 1.1和HTTP / 2客户端和服务器



Golang是一种出色的编程语言,具有多种功能。 本文介绍了如何在Go上为HTTP / 1.1和HTTP / 2协议编写客户端和服务器。

Skillbox建议: Python开发人员从头开始 实践课程。

我们提醒您: 对于所有“哈勃”读者来说,使用“哈勃”促销代码注册任何Skillbox课程时均可享受10,000卢布的折扣。

HTTP / 2是用于访问万维网的HTTP网络协议的第二个主要版本。 该协议基于SPDY。 在GitHub上了解有关此版本的更多信息。

HTTP / 1.1服务器


下面的代码演示了如何在Go上编写HTTP / 1.1服务器。 主要功能使用端口9191和路径/ hello / sayHello启动HTTP(S)服务。 echoPayload负责回声逻辑,分析传入流量并做出相应反应。 如有必要,可以修改echoPayload。

package main import ( "fmt" "io/ioutil" "log" "net/http" ) func main() { http.HandleFunc("/hello/sayHello", echoPayload) log.Printf("Go Backend: { HTTPVersion = 1 }; serving on https://localhost:9191/hello/sayHello") log.Fatal(http.ListenAndServeTLS(":9191", "./cert/server.crt", "./cert/server.key", nil)) } func echoPayload(w http.ResponseWriter, req *http.Request) { log.Printf("Request connection: %s, path: %s", req.Proto, req.URL.Path[1:]) defer req.Body.Close() contents, err := ioutil.ReadAll(req.Body) if err != nil { log.Fatalf("Oops! Failed reading body of the request.\n %s", err) http.Error(w, err.Error(), 500) } fmt.Fprintf(w, "%s\n", string(contents)) 

由于HTTP(S)服务已经在运行,因此您需要提供证书和服务器密钥。 这两个对象都以名称server.crt和server.key存储在cert目录中。

证书和密钥的示例如下。

./cert/server.crt

-----开始证书-----
MIID + zCCAuOgAwIBAgIJAPsvGCCAC2i + MA0GCSqGSIb3DQEBCwUAMIGTMQswCQYD
VQQGEwJMSzEQMA4GA1UECAwHV2VzdGVybjEQMA4GA1UEBwwHQ29sb21ibzESMBAG
A1UECgwJTERDTEFLTUFMMRQwEgYDVQQLDAtFbmdpbmVlcmluZzESMBAGA1UEAwwJ
bG9jYWxob3N0MSIwIAYJKoZIhvcNAQkBFhNsZGNsYWttYWxAZ21haWwuY29tMB4X
DTE5MDQyMDA1MjczM1oXDTIwMDQxOTA1MjczM1owgZMxCzAJBgNVBAYTAkxLMRAw
DgYDVQQIDAdXZXN0ZXJuMRAwDgYDVQQHDAdDb2xvbWJvMRIwEAYDVQQKDAlMRENM
QUtNQUwxFDASBgNVBAsMC0VuZ2luZWVyaW5nMRIwEAYDVQQDDAlsb2NhbGhvc3Qx
IjAgBgkqhkiG9w0BCQEWE2xkY2xha21hbEBnbWFpbC5jb20wggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQC9PKAOlJcOBUI9CGnVjMjQHNRqYv01CaUdC4 / e
YFyegxLpoMpYvEC + nYlHT2j7BOhQBV + TkH1D4YOK2WP3V0FLv5hM7Nxsgf25WNHa
zi2DTBvcBgB9sDJA / avIvF + 63 + Btnyggp3xq6MaHy5DNH0kPnSiPiy7PRKToEUn6
oqPnB10rRBFZqs3ePmEDxVL3T / TUZSXR3P95fV1vDCqrJbr3YwWOzFCq8kEJFslK
B7GSEKpPgmK0g5krmAQqUOuCJ3 / xFlCP4trKg / lvSJZ5S / LZD5teDDg6Ax3Mvthj
kMh9 / OM5GGTTjRwhct9dHjFI8POj + TMbLZvoPVXjsmATEgtLAgMBAAGjUDBOMB0G
A1UdDgQWBBQ1CmWXmrHOv6b8f763 / bk80EpbajAfBgNVHSMEGDAWgBQ1CmWXmrHO
v6b8f763 / bk80EpbajAMBgNVHRMEBTADAQH / MA0GCSqGSIb3DQEBCwUAA4IBAQAH
D51Uoe2K4N9 / GxRgww5mMW2dUJ7Hc / tGsr / J1fNqHY8SXNAn5i + GwI + xBvwxFHL3
KZHbfq7eYDE5EItt3cZp5ySSscdTEay9ReH2 + 8k32gpH46CMwPV3XvtQuBVVAC4u
szrq1eWKhYI2zf4iUVpwvq89OynVGIp0atng + q3A2cBhi3NGo6Ho1s2rywQyqiq8
up4PUSVQ6WBoJFx5PEEDxD84VMS7Pan6dT34b9n56tq5R06retZTUZ8jMM88CGX4
88pSPU + XImp6DdNVBmW6Lz76jiSNHLkZGm4jumjeyUGzBjBEBOgSegeWlinMtWE9
gaVxeUHrqHk8xzwJ4oIu
-----结束证书-----

./cert/server.key

-----开始私钥-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC9PKAOlJcOBUI9
CGnVjMjQHNRqYv01CaUdC4 / eYFyegxLpoMpYvEC + nYlHT2j7BOhQBV + TkH1D4YOK
2WP3V0FLv5hM7Nxsgf25WNHazi2DTBvcBgB9sDJA / avIvF + 63 + Btnyggp3xq6MaH
y5DNH0kPnSiPiy7PRKToEUn6oqPnB10rRBFZqs3ePmEDxVL3T / TUZSXR3P95fV1v
DCqrJbr3YwWOzFCq8kEJFslKB7GSEKpPgmK0g5krmAQqUOuCJ3 / xFlCP4trKg / lv
SJZ5S / LZD5teDDg6Ax3MvthjkMh9 / OM5GGTTjRwhct9dHjFI8POj + TMbLZvoPVXj
smATEgtLAgMBAAECggEAbaS2yDvn2cPKQTqit4y + vXY2zP1V4GkaNd4BGcOTZnRj
fOIg25EXoln8tEiadva888BpREKvkakUYlraxPDVcGIuiEOk42nd7Io97R0Q2cY7
ThxcJHb2ZxmTctdSUCBvFJTm1ySzve3pOb0ExRSfbGCOo7zs / kKzmZKK3qFlffGS
Ga9O7hyLOuXPU22CM + 5Lq0JPTER73z0DpAweZc0L14j6dzhcG3qUwk0K6K47VZgE
NhEORul7xDj91bh2iEoSbaQe8HxLaMQoMXOC / 9oey2UKKTe9WZE3 + XCvg + vkw / sS
biQ + b4EZ9LuhAhCZ0UE6 + y7PZY + 8G / YsbGg0Zo8cAQKBgQDyTuG47rWBgbdHsEB /
MSKGU6w + a1SdLk6jG + Enji5Q624 / h0xt5nF9ah2eRf3Rlhn9WEKM / uE9ouEODBKE
8rnIDsjufEMI8moPEloRBSsxPNw + fNMSSCZjL + qPtTJUbRio7WA23sfdnE57ygBa
wlPQ9UBBWSm2se4veEZtHjtngQKBgQDH7gnH5Att6ZYazRTgD72g0C5v1l4LYVEQ
jxdBcs6TJA8wHfifZ45F67W95QunmM813UxfS + ybdjytlb8 / lmi2BnK6lDx5HWIL
31jnbg2CxCrNv9oZLjKVDmkp4WUcEp5W33R1 / MGDTRfyARP + 6QYQO / ATMdqtm5Uu
cD6clrL4ywKBgQCQ0niy0WmGaAMlQ8CoxLM / 2c6 +1 + OQtlalwkoGHEKudqhELBeQ
MAVw0fW13Vtg4vfRpejQ4J26 + xjMDocbEv / bBIsvjvF57XlaXLucJJy2Jwv0BSMa
cCkRa1gkYEYek74DaSzyXqDSYVO / RPKFTFRQNeUbqbD20s3rbVWablFPAQKBgB5y
zUCJJYh2w6qPQzegjhO4wOm9bxMyngL0l + ka0AUuv7VnSx8TyWIytLoX8P90UVJ1
wpTc3ksK5dDV9ot7n7ThJIXv34nehLkkKckNRLd + oro1FsUw + PkkebWsIxb0avL2
EymI9fvGOPhdW6s91 / OO / VAfDpvUDxNEevSkKtujAoGAcMOsXtn / UyT3Lssxgla3
K + DCaFhAQPSUXOmpZwEbQ0yQlksDe4flsam8bEDI5D5iHx1ziSfh583qJl3BEZ5u
VZTEO2YLvT9QRz7pv2qspqj7nzSyBU2BFAajq43 / G1b8FHfVgN + YdVtzVrigfql5
2a + JxOxFfpjnGQ7RfSxSb + Q =
-----结束私钥-----

现在该使用以下命令测试运行该服务的时间:

 $ go run http_server.go 

答案应该是这样的:

 Go Backend: { HTTPVersion = 1 }; serving on https://localhost:9191/hello/sayHello 

现在,我们使用HTTP / 1.1 POST请求(不安全模式和安全模式)启动服务:

 $ curl -k -v https://localhost:9191/hello/sayHello -d "Hello Go!" $ curl -v --cacert ./cert/server.crt https://localhost:9191/hello/sayHello -d "Hello Go!" 

以下是程序的结果。 第一部分是TLS服务器和客户端的握手详细信息。 第二个是HTTP / 1.1请求和响应的详细信息。 最后是文本消息“ Hello Go!”。

*尝试127.0.0.1 ...
*连接到localhost(127.0.0.1)端口9191(#0)
* ALPN,提供http / 1.1
*密码选择:ALL :! EXPORT :! EXPORT40 :! EXPORT56 :! ANULL :! LOW :! RC4:@STRENGTH
*成功设置证书验证位置:
* CAfile:/etc/ssl/certs/ca-certificates.crt
CApath:无
* TLSv1.2(OUT),TLS标头,证书状态(22):
* TLSv1.2(OUT),TLS握手,客户端问候(1):
* TLSv1.2(IN),TLS握手,服务器问候(2):
* TLSv1.2(IN),TLS握手,证书(11):
* TLSv1.2(IN),TLS握手,服务器密钥交换(12):
* TLSv1.2(IN),TLS握手,服务器完成(14):
* TLSv1.2(OUT),TLS握手,客户端密钥交换(16):
* TLSv1.2(OUT),TLS更改密码,客户端问候(1):
* TLSv1.2(OUT),TLS握手,已完成(20):
* TLSv1.2(IN),TLS更改密码,客户端问候(1):
* TLSv1.2(IN),TLS握手,完成(20):
*使用TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256的SSL连接
* ALPN,服务器接受使用http / 1.1
*服务器证书:
*主题:C = LK; ST =西方; L =可伦坡; O = LDCLAKMAL; OU =工程; CN = Chanaka Lakmal; emailAddress=ldclakmal@gmail.com
*开始日期:2019年4月20日03:03:58 GMT
*到期日期:2020年4月19日03:03:58 GMT
*发行人:C = LK; ST =西方; L =可伦坡; O = LDCLAKMAL; OU =工程; CN = Chanaka Lakmal; emailAddress=ldclakmal@gmail.com
* SSL证书验证结果:自签名证书(18),仍然继续。
> POST /你好/ sayHello HTTP / 1.1
>主机:localhost:9191
>用户代理:curl / 7.46.0
>接受:* / *
>内容长度:9
>内容类型:应用程序/ x-www-form-urlencoded
>
*上传完全发送:9个字节中的9个
<HTTP / 1.1 200确定
<日期:星期六,2019-4-20 06:56:19 GMT
<内容长度:10
<Content-Type:文本/纯文本; 字符集= utf-8
<
你好
*与主机本地主机的连接#0保持不变

HTTP / 1.1客户端


新代码是在Go上编写简单的HTTP / 1.1客户端的示例。 该客户端向localhost :9191 / hello / sayHello发送HTTP(S)POST请求,并显示消息“ Hello Go!”。

 package main import ( "bytes" "crypto/tls" "crypto/x509" "fmt" "io/ioutil" "log" "net/http" ) func main() { client := &http.Client{} // Create a pool with the server certificate since it is not signed // by a known CA caCert, err := ioutil.ReadFile("./cert/server.crt") if err != nil { log.Fatalf("Reading server certificate: %s", err) } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) // Create TLS configuration with the certificate of the server tlsConfig := &tls.Config{ RootCAs: caCertPool, } // Use the proper transport in the client client.Transport = &http.Transport{ TLSClientConfig: tlsConfig, } // Perform the request resp, err := client.Post("https://localhost:9191/hello/sayHello", "text/plain", bytes.NewBufferString("Hello Go!")) if err != nil { log.Fatalf("Failed get: %s", err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatalf("Failed reading response body: %s", err) } fmt.Printf("Got response %d: %s %s", resp.StatusCode, resp.Proto, string(body)) 

由于HTTP(S)服务也从此处开始,因此您需要提供证书和密钥。 必要的数据以名称server.crt和server.key放置在cert目录中。

为了启动客户端,您需要初始化服务器。 为此,我们执行第一部分中描述的操作,或者使用端口9191和路径/ hello / sayHello启动任何HTTP(S)服务。

 $ go run http_client.go 

输出应如下所示:

 Got response 200: HTTP/1.1 Hello Go! 

客户端显示curl命令的操作:

 $ curl -v --cacert ./cert/server.crt https://localhost:9191/hello/sayHello -d "Hello Go!" 

完成使用HTTP / 1.1的操作后,应尝试对HTTP / 2重复相同的操作。

HTTP / 2服务器


与上一节一样,您必须首先创建一个回显服务器。

 package main import ( "fmt" "golang.org/x/net/http2" "io/ioutil" "log" "net/http" ) func main() { var httpServer = http.Server{ Addr: ":9191", } var http2Server = http2.Server{} _ = http2.ConfigureServer(&httpServer, &http2Server) http.HandleFunc("/hello/sayHello", echoPayload) log.Printf("Go Backend: { HTTPVersion = 2 }; serving on https://localhost:9191/hello/sayHello") log.Fatal(httpServer.ListenAndServeTLS("./cert/server.crt", "./cert/server.key")) } func echoPayload(w http.ResponseWriter, req *http.Request) { log.Printf("Request connection: %s, path: %s", req.Proto, req.URL.Path[1:]) defer req.Body.Close() contents, err := ioutil.ReadAll(req.Body) if err != nil { log.Fatalf("Oops! Failed reading body of the request.\n %s", err) http.Error(w, err.Error(), 500) } fmt.Fprintf(w, "%s\n", string(contents)) 

为了检查服务器,您需要发送命令:

 $ go run http2_server.go 

答案应该是这样的:

 Go Backend: { HTTPVersion = 2 }; serving on https://localhost:9191/hello/sayHello 

现在服务器正在运行,您可以使用HTTP / 1.1或HTTP / 2请求对其进行初始化。 用于测试服务器操作的HTTP / 1.1 POST命令如下:

 $ curl -k -v https://localhost:9191/hello/sayHello -d "Hello Go!" $ curl -v --cacert ./cert/server.crt https://localhost:9191/hello/sayHello -d "Hello Go!" 

和同样的事情,但是使用HTTP / 2 POST:

 $ curl -k -v --http2 https://localhost:9191/hello/sayHello -d "Hello Go!" $ curl -v --http2 --cacert ./cert/server.crt https://localhost:9191/hello/sayHello -d "Hello Go!" 

以下是服务器操作输出的示例。 第一部分是服务器和客户端之间的TLS握手,第二部分是HTTP / 2请求和响应的详细信息。

*尝试127.0.0.1 ...
*连接到localhost(127.0.0.1)端口9191(#0)
* ALPN,提供h2
* ALPN,提供http / 1.1
*密码选择:ALL :! EXPORT :! EXPORT40 :! EXPORT56 :! ANULL :! LOW :! RC4:@STRENGTH
*成功设置证书验证位置:
* CAfile:src / hello / cert / server.crt
CApath:无
* TLSv1.2(OUT),TLS标头,证书状态(22):
* TLSv1.2(OUT),TLS握手,客户端问候(1):
* TLSv1.2(IN),TLS握手,服务器问候(2):
* TLSv1.2(IN),TLS握手,证书(11):
* TLSv1.2(IN),TLS握手,服务器密钥交换(12):
* TLSv1.2(IN),TLS握手,服务器完成(14):
* TLSv1.2(OUT),TLS握手,客户端密钥交换(16):
* TLSv1.2(OUT),TLS更改密码,客户端问候(1):
* TLSv1.2(OUT),TLS握手,已完成(20):
* TLSv1.2(IN),TLS更改密码,客户端问候(1):
* TLSv1.2(IN),TLS握手,完成(20):
*使用TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256的SSL连接
* ALPN,服务器接受使用h2
*服务器证书:
*主题:C = LK; ST =西方; L =可伦坡; O = LDCLAKMAL; OU =工程; CN =本地主机; emailAddress=ldclakmal@gmail.com
*开始日期:2019年4月20日05:27:33 GMT
*到期日期:2020年4月19日05:27:33 GMT
*通用名称:localhost(匹配)
*发行人:C = LK; ST =西方; L =可伦坡; O = LDCLAKMAL; OU =工程; CN =本地主机; emailAddress=ldclakmal@gmail.com
* SSL证书验证成功。
*使用HTTP2,服务器支持多用途
*连接状态已更改(HTTP / 2已确认)
* TCP_NODELAY设置
*升级后将流缓冲区中的HTTP / 2数据复制到连接缓冲区中:len = 0
*使用流ID:1(易于处理0x10ddf20)
> POST /你好/ sayHello HTTP / 1.1
>主机:localhost:9191
>用户代理:curl / 7.46.0
>接受:* / *
>内容长度:9
>内容类型:应用程序/ x-www-form-urlencoded
>
*我们已经完全上载并且很好
<HTTP / 2.0 200
<content-type:文本/纯文本; 字符集= utf-8
<内容长度:10
<日期:2019年4月20日,星期六06:54:50 GMT
<
你好
*与主机本地主机的连接#0保持不变

HTTP / 2客户端


最后一部分是创建HTTP / 2客户端。 这是为localhost发送HTTP(S)POST请求的实现:9191 / hello / sayHello:

 package main import ( "bytes" "crypto/tls" "crypto/x509" "fmt" "golang.org/x/net/http2" "io/ioutil" "log" "net/http" ) func main() { client := &http.Client{} // Create a pool with the server certificate since it is not signed // by a known CA caCert, err := ioutil.ReadFile("./cert/server.crt") if err != nil { log.Fatalf("Reading server certificate: %s", err) } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) // Create TLS configuration with the certificate of the server tlsConfig := &tls.Config{ RootCAs: caCertPool, } // Use the proper transport in the client client.Transport = &http2.Transport{ TLSClientConfig: tlsConfig, } // Perform the request resp, err := client.Post("https://localhost:9191/hello/sayHello", "text/plain", bytes.NewBufferString("Hello Go!")) if err != nil { log.Fatalf("Failed get: %s", err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatalf("Failed reading response body: %s", err) } fmt.Printf("Got response %d: %s %s", resp.StatusCode, resp.Proto, string(body)) 

和以前一样,您需要密钥和证书才能工作。 它们以名称server.crt和server.key存储在cert中。

客户启动:

 $ go run http2_client.go 

和预期的答案:

 Got response 200: HTTP/2 Hello Go! 

详细介绍客户的工作:

 $ curl -v --http2 --cacert ./cert/server.crt https://localhost:9191/hello/sayHello -d "Hello Go!" 

仅此而已。 这项工作相对简单,但是可以理解如何在Go中实现基本的网络服务。

Skillbox建议:

Source: https://habr.com/ru/post/zh-CN451032/


All Articles