ホームページ  >  記事  >  バックエンド開発  >  Go言語のhttp/2サーバー機能とクライアントの使い方について

Go言語のhttp/2サーバー機能とクライアントの使い方について

藏色散人
藏色散人転載
2020-10-09 14:14:147472ブラウズ

次のコラム golang チュートリアル では、Go 言語の http/2 サーバー機能とクライアントでの使用方法を紹介します。

まえがき

ご存知のとおり、Go の標準ライブラリ HTTP サーバーはデフォルトで HTTP/2 をサポートしています。さて、この記事では、まず Go の http/2 サーバー機能を紹介し、それらをクライアントとして使用する方法を説明します。

この記事では、まず Go の http/2 サーバー機能を紹介し、それをクライアントとして使用する方法を説明します。 Go の標準ライブラリ HTTP サーバーはデフォルトで HTTP/2 をサポートします。

以下では多くを語る必要はありません。詳細な紹介を見てみましょう

HTTP/2 サーバー

最初の部分それでは、Go で http/2 サーバーを作成しましょう。 http/2 ドキュメントによると、すべてが自動的に構成され、Go の標準ライブラリ http2 パッケージをインポートする必要さえありません。

HTTP/2 では TLS の使用が強制されます。これを実現するには、まず秘密キーと証明書が必要です。 Linux では、次のコマンドでこのタスクを実行します。

openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt

このコマンドは、server.key と server.crt の 2 つのファイルを生成します。

サーバー コードについては、最も単純な形式で次のようになります。 Go の標準ライブラリ HTTP サーバーを使用し、生成された SSL ファイルで TLS を有効にします。

package main

import (
 "log"
 "net/http"
)
 
func main() {
 // 在 8000 端口启动服务器
 // 确切地说,如何运行HTTP/1.1服务器。

 srv := &http.Server{Addr:":8000", Handler: http.HandlerFunc(handle)}
 // 用TLS启动服务器,因为我们运行的是http/2,它必须是与TLS一起运行。
 // 确切地说,如何使用TLS连接运行HTTP/1.1服务器。
 log.Printf("Serving on https://0.0.0.0:8000")
 log.Fatal(srv.ListenAndServeTLS("server.crt", "server.key"))
}

func handle(w http.ResponseWriter, r *http.Request) {
 // 记录请求协议
 log.Printf("Got connection: %s", r.Proto)
 // 向客户发送一条消息
 w.Write([]byte("Hello"))
}

HTTP/2 クライアント

Go では、標準の http.Client は http/2 リクエストにも使用されます。唯一の違いは、クライアントの Transport フィールドで http.Transport の代わりに http2.Transport が使用されることです。

私たちが生成したサーバー証明書は「自己署名」されており、既知の認証局 (CA) によって署名されていないことを意味します。これにより、クライアントは信じられなくなります:

package main

import (
 "fmt"
 "net/http"
)

const url = "https://localhost:8000"

func main() {
 _, err := http.Get(url)
 fmt.Println(err)
}

実行してみましょう:

$ go run h2-client.go 
Get https://localhost:8000: x509: certificate signed by unknown authority

サーバー ログには、クライアント (リモート) にエラーがあることもわかります :

http: [::1] からの TLS ハンドシェイク エラー:58228: リモート エラー: tls: 不正な証明書

この問題を解決するには、次のようにクライアントを構成します。カスタム TLS 構成。既知の CA によって署名されていない場合でも信頼できるため、サーバー証明書ファイルをクライアントの「証明書プール」に追加します。

コマンド ライン フラグに基づいて HTTP/1.1 トランスポートと HTTP/2 トランスポートを選択するオプションも追加します。

package main

import (
 "crypto/tls"
 "crypto/x509"
 "flag"
 "fmt"
 "io/ioutil"
 "log"
 "net/http"
 "golang.org/x/net/http2"
)
 
const url = "https://localhost:8000"

var httpVersion = flag.Int("version", 2, "HTTP version")

func main() {
 flag.Parse()
 client := &http.Client{}
 // Create a pool with the server certificate since it is not signed
 // by a known CA
 caCert, err := ioutil.ReadFile("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
 switch *httpVersion {
 case 1:
 client.Transport = &http.Transport{
 TLSClientConfig: tlsConfig,
 }
 case 2:
 client.Transport = &http2.Transport{
 TLSClientConfig: tlsConfig,
 }
 }
 // Perform the request
 resp, err := client.Get(url)

 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\n",
 resp.StatusCode, resp.Proto, string(body)
 )
}

今回は正しい応答が得られます:

$ go run h2-client.go 
Got response 200: HTTP/2.0 Hello

サーバー ログには、正しいログ行が表示されます: Got connection: HTTP/2.0!!
しかし、HTTP/1.1 トランスポートを使用しようとするとどうなるでしょうか?

$ go run h2-client.go -version 1
Got response 200: HTTP/1.1 Hello

私たちのサーバーには HTTP/2 に固有のものがないため、HTTP/1.1 接続をサポートします。これは下位互換性にとって重要です。さらに、サーバー ログには、接続が HTTP/1.1 であることが示されています: Got connection: HTTP/1.1。

HTTP/2 の高度な機能

HTTP/2 クライアント/サーバー接続を作成し、安全で効率的な接続のメリットを享受しています。 。しかし、HTTP/2 にはさらに多くの機能が用意されているので、詳しく見てみましょう。

サーバー プッシュ

HTTP/2 では、サーバー プッシュで「指定されたターゲットを使用して合成リクエストを構築」できます。
これはサーバー ハンドラーに簡単に実装できます (github で参照):

func handle(w http.ResponseWriter, r *http.Request) {
 // Log the request protocol
 log.Printf("Got connection: %s", r.Proto)
 // Handle 2nd request, must be before push to prevent recursive calls.
 // Don't worry - Go protect us from recursive push by panicking.
 if r.URL.Path == "/2nd" {
 log.Println("Handling 2nd")
 w.Write([]byte("Hello Again!"))
 return
 }

 // Handle 1st request
 log.Println("Handling 1st")
 // Server push must be before response body is being written.
 // In order to check if the connection supports push, we should use
 // a type-assertion on the response writer.
 // If the connection does not support server push, or that the push
 // fails we just ignore it - server pushes are only here to improve
 // the performance for HTTP/2 clients.
 pusher, ok := w.(http.Pusher)

 if !ok {
 log.Println("Can't push to client")
 } else {
 err := pusher.Push("/2nd", nil)
  if err != nil {
 log.Printf("Failed push: %v", err)
 }
 }
 // Send response body
 w.Write([]byte("Hello"))
}

サーバー プッシュの使用

サーバーを再実行して、クライアント。

HTTP/1.1 クライアントの場合:

$ go run ./h2-client.go -version 1
Got response 200: HTTP/1.1 Hello

サーバー ログには次のように表示されます:

接続を確立しました: HTTP/1.1Handling 1st
Can't Push to client

HTTP/1.1 クライアント トランスポート接続は、http.Pusher を実装しない http.ResponseWriter を生成しますが、これは当然のことです。サーバー コードでは、このクライアント状況で何を行うかを選択できます。

HTTP/2 クライアントの場合:

go run ./h2-client.go -version 2
Got response 200: HTTP/2.0 Hello

サーバー ログには次のように表示されます:

接続を取得しました: HTTP/2.0 の処理 1st
失敗したプッシュ: 機能がありませんサポートされています

#これは奇妙です。 HTTP/2 トランスポート クライアントは、最初の「Hello」応答のみを受け取りました。ログには、接続が http.Pusher インターフェイスを実装していることが示されていますが、実際に Push() 関数を呼び出すと失敗します。

調査の結果、HTTP/2 クライアントの送信により、プッシュが無効であることを示す HTTP/2 設定フラグが設定されたことが判明しました。

したがって、現在、Go クライアントを使用してサーバー プッシュを使用するオプションはありません。

余談ですが、Google Chrome はクライアントとしてサーバー プッシュを処理できます。

サーバー ログには、予想どおり、パス / と /2nd に対してハンドラーが 2 回呼び出されたことが示されます。クライアントは実際には path/:

のみ

Got connection: HTTP/2.0Handling 1st
Got connection: HTTP/2.0Handling 2nd

全双工通信

Go HTTP/2演示页面有一个echo示例,它演示了服务器和客户机之间的全双工通信。

让我们先用CURL来测试一下:

$ curl -i -XPUT --http2 https://http2.golang.org/ECHO -d hello
HTTP/2 200 
content-type: text/plain; charset=utf-8
date: Tue, 24 Jul 2018 12:20:56 GMT

HELLO

我们把curl配置为使用HTTP/2,并将一个PUT/ECHO发送给“hello”作为主体。服务器以“HELLO”作为主体返回一个HTTP/2 200响应。但我们在这里没有做任何复杂的事情,它看起来像是一个老式的HTTP/1.1半双工通信,有不同的头部。让我们深入研究这个问题,并研究如何使用HTTP/2全双工功能。

服务器实现

下面是HTTP echo处理程序的简化版本(不使用响应)。它使用 http.Flusher 接口,HTTP/2添加到http.ResponseWriter。

type flushWriter struct {
 w io.Writer
}

func (fw flushWriter) Write(p []byte) (n int, err error) {
 n, err = fw.w.Write(p)
 // Flush - send the buffered written data to the client
 if f, ok := fw.w.(http.Flusher); ok {
 f.Flush()
 }
  return
}

func echoCapitalHandler(w http.ResponseWriter, r *http.Request) {
 // First flash response headers
 if f, ok := w.(http.Flusher); ok {
 f.Flush()
 }
 // Copy from the request body to the response writer and flush
 // (send to client)
 io.Copy(flushWriter{w: w}, r.Body)
}

服务器将从请求正文读取器复制到写入ResponseWriter和 Flush() 的“冲洗写入器”。同样,我们看到了笨拙的类型断言样式实现,冲洗操作将缓冲的数据发送给客户机。

请注意,这是全双工,服务器读取一行,并在一个HTTP处理程序调用中重复写入一行。

GO客户端实现

我试图弄清楚一个启用了HTTP/2的go客户端如何使用这个端点,并发现了这个Github问题。提出了类似于下面的代码。

const url = "https://http2.golang.org/ECHO"

func main() {
  // Create a pipe - an object that implements `io.Reader` and `io.Writer`. 
  // Whatever is written to the writer part will be read by the reader part.

  pr, pw := io.Pipe() 
  // Create an `http.Request` and set its body as the reader part of the
  // pipe - after sending the request, whatever will be written to the pipe,
  // will be sent as the request body.  // This makes the request content dynamic, so we don't need to define it
  // before sending the request.
 req, err := http.NewRequest(http.MethodPut, url, ioutil.NopCloser(pr))

 if err != nil {
 log.Fatal(err)
 }
 
  // Send the request
 resp, err := http.DefaultClient.Do(req) if err != nil {
   log.Fatal(err)
   }
 log.Printf("Got: %d", resp.StatusCode) 
 // Run a loop which writes every second to the writer part of the pipe
 // the current time.
 go func() { for { 
 time.Sleep(1 * time.Second)
 fmt.Fprintf(pw, "It is now %v\n", time.Now())
 }
 }() 

  // Copy the server's response to stdout.
 _, err = io.Copy(os.Stdout, res.Body)

 log.Fatal(err)
}

总结

Go支持与服务器推送和全双工通信的HTTP/2连接,这也支持HTTP/1.1与标准库的标准TLS服务器的连接——这太不可思议了。对于标准的库HTTP客户端,它不支持服务器推送,但是支持标准库的标准HTTP的全双工通信。以上就是本篇的内容,大家有什么疑问可以在文章下面留言沟通。

以上がGo言語のhttp/2サーバー機能とクライアントの使い方についての詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjb51.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。