GolangでHTTP / 1.1&HTTP / 2クライアントとサーバーを作成する





Golangは、幅広い機能を備えた優れたプログラミング言語です。 この記事では、HTTP / 1.1およびHTTP / 2プロトコル用にGoでクライアントとサーバーを作成する方法を示します。



Skillboxの推奨事項: Python開発者のゼロからの 実践コース。



「Habr」の読者には、「Habr」プロモーションコードを使用してSkillboxコースに登録すると10,000ルーブルの割引があります。



HTTP / 2は、World Wide Webへのアクセスに使用されるHTTPネットワークプロトコルの2番目のメジャーバージョンです。 プロトコルは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サーバーとクライアントのハンドシェイクの詳細です。 2番目は、HTTP / 1.1リクエストとレスポンスの詳細です。 最後に-「Hello Go!」というテキストメッセージ。



* 127.0.0.1を試す...

* localhost(127.0.0.1)ポート9191(#0)に接続

* http / 1.1を提供するALPN

*暗号の選択: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ハンドシェイク、クライアントhello(1):

* TLSv1.2(IN)、TLSハンドシェイク、Server hello(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変更暗号、クライアントhello(1):

* TLSv1.2(OUT)、TLSハンドシェイク、終了(20):

* TLSv1.2(IN)、TLS変更暗号、クライアントhello(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 =チャナカラクマル; emailAddress=ldclakmal@gmail.com

*開始日:2019年4月20日03:03:58 GMT

*有効期限:4月19日03:03:58 2020 GMT

*発行者:C = LK; ST =西部; L =コロンボ; O = LDCLAKMAL; OU =エンジニアリング; CN =チャナカラクマル; emailAddress=ldclakmal@gmail.com

* SSL証明書の検証結果:自己署名証明書(18)、とにかく続行します。

> POST / hello / sayHello HTTP / 1.1

>ホスト:localhost:9191

>ユーザーエージェント:curl / 7.46.0

>受け入れる:* / *

>コンテンツの長さ:9

> Content-Type:application / x-www-form-urlencoded

>

*完全に送信されたアップロード:9バイトのうち9バイト

<HTTP / 1.1 200 OK

<日付:2019年4月20日(土)06:56:19 GMT

<コンテンツの長さ:10

<Content-Type:text / plain; 文字セット= utf-8

<

こんにちは!

*ホストlocalhostへの接続#0はそのまま



HTTP / 1.1クライアント



新しいコードは、Goで単純なHTTP / 1.1クライアントを作成する例です。 このクライアントは、HTTP(S)POSTリクエストをローカルホストに送信します:9191 / hello / sayHelloメッセージ「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ハンドシェイクで、2番目はHTTP / 2リクエストとレスポンスの詳細です。



* 127.0.0.1を試す...

* localhost(127.0.0.1)ポート9191(#0)に接続

* ALPN、h2を提供

* http / 1.1を提供するALPN

*暗号の選択:ALL :! EXPORT :! EXPORT40 :! EXPORT56 :! ANULL :! LOW :! RC4:@STRENGTH

*証明書の検証場所を正常に設定しました:

* CAfile:src / hello / cert / server.crt

CApath:なし

* TLSv1.2(OUT)、TLSヘッダー、証明書ステータス(22):

* TLSv1.2(OUT)、TLSハンドシェイク、クライアントhello(1):

* TLSv1.2(IN)、TLSハンドシェイク、Server hello(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変更暗号、クライアントhello(1):

* TLSv1.2(OUT)、TLSハンドシェイク、終了(20):

* TLSv1.2(IN)、TLS変更暗号、クライアントhello(1):

* TLSv1.2(IN)、TLSハンドシェイク、終了(20):

* TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256を使用したSSL接続

* ALPN、サーバーはh2の使用を許可

*サーバー証明書:

*件名:C = LK; ST =西部; L =コロンボ; O = LDCLAKMAL; OU =エンジニアリング; CN = localhost; emailAddress=ldclakmal@gmail.com

*開始日:2019年4月20日05:27:33 GMT

*有効期限:4月19日05:27:33 2020 GMT

*共通名:localhost(一致)

*発行者:C = LK; ST =西部; L =コロンボ; O = LDCLAKMAL; OU =エンジニアリング; CN = localhost; emailAddress=ldclakmal@gmail.com

* SSL証明書は問題ありません。

* HTTP2を使用して、サーバーは複数使用をサポートします

*接続状態が変更されました(HTTP / 2確認済み)

* TCP_NODELAYセット

*アップグレード後にストリームバッファのHTTP / 2データを接続バッファにコピー:len = 0

*ストリームIDの使用:1(簡単なハンドル0x10ddf20)

> POST / hello / sayHello HTTP / 1.1

>ホスト:localhost:9191

>ユーザーエージェント:curl / 7.46.0

>受け入れる:* / *

>コンテンツの長さ:9

> Content-Type:application / x-www-form-urlencoded

>

*完全にアップロードされており、問題ありません

<HTTP / 2.0 200

<content-type:text / plain; 文字セット= utf-8

<コンテンツの長さ:10

<日付:2019年4月20日(土)06:54:50 GMT

<

こんにちは!

*ホストlocalhostへの接続#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という名前で証明書に保存されます。



クライアントの起動:



 $ 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の推奨事項:






All Articles