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

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

Oct 09, 2020 pm 02:14 PM
go

次のコラム 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 サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Go WebSocket メッセージを送信するにはどうすればよいですか? Go WebSocket メッセージを送信するにはどうすればよいですか? Jun 03, 2024 pm 04:53 PM

Go では、gorilla/websocket パッケージを使用して WebSocket メッセージを送信できます。具体的な手順: WebSocket 接続を確立します。テキスト メッセージを送信します。 WriteMessage(websocket.TextMessage,[]byte("message")) を呼び出します。バイナリ メッセージを送信します。WriteMessage(websocket.BinaryMessage,[]byte{1,2,3}) を呼び出します。

Golang 関数のライフサイクルと変数スコープの深い理解 Golang 関数のライフサイクルと変数スコープの深い理解 Apr 19, 2024 am 11:42 AM

Go では、関数のライフ サイクルには定義、ロード、リンク、初期化、呼び出し、戻り値が含まれます。変数のスコープは関数レベルとブロック レベルに分割されますが、ブロック内の変数はブロック内でのみ表示されます。 。

GolangとGo言語の違い GolangとGo言語の違い May 31, 2024 pm 08:10 PM

Go と Go 言語は、異なる特性を持つ別個の存在です。 Go (Golang とも呼ばれます) は、同時実行性、高速なコンパイル速度、メモリ管理、およびクロスプラットフォームの利点で知られています。 Go 言語の欠点としては、他の言語に比べてエコシステムが充実していないこと、構文が厳格であること、動的型付けが欠如していることが挙げられます。

Go で正規表現を使用してタイムスタンプを照合するにはどうすればよいですか? Go で正規表現を使用してタイムスタンプを照合するにはどうすればよいですか? Jun 02, 2024 am 09:00 AM

Go では、正規表現を使用してタイムスタンプを照合できます。ISO8601 タイムスタンプの照合に使用されるような正規表現文字列をコンパイルします。 ^\d{4}-\d{2}-\d{2}T \d{ 2}:\d{2}:\d{2}(\.\d+)?(Z|[+-][0-9]{2}:[0-9]{2})$ 。 regexp.MatchString 関数を使用して、文字列が正規表現と一致するかどうかを確認します。

Golang の技術的なパフォーマンスの最適化でメモリ リークを回避するにはどうすればよいですか? Golang の技術的なパフォーマンスの最適化でメモリ リークを回避するにはどうすればよいですか? Jun 04, 2024 pm 12:27 PM

メモリ リークは、ファイル、ネットワーク接続、データベース接続などの使用されなくなったリソースを閉じることによって、Go プログラムのメモリを継続的に増加させる可能性があります。弱参照を使用してメモリ リークを防ぎ、強参照されなくなったオブジェクトをガベージ コレクションの対象にします。 go coroutine を使用すると、メモリ リークを避けるために、終了時にコルーチンのスタック メモリが自動的に解放されます。

IDE で Golang 関数のドキュメントを表示するにはどうすればよいですか? IDE で Golang 関数のドキュメントを表示するにはどうすればよいですか? Apr 18, 2024 pm 03:06 PM

IDE を使用して Go 関数のドキュメントを表示する: 関数名の上にカーソルを置きます。ホットキーを押します (GoLand: Ctrl+Q; VSCode: GoExtensionPack をインストールした後、F1 キーを押して「Go:ShowDocumentation」を選択します)。

Golang関数がマップパラメータを受け取る際の注意点 Golang関数がマップパラメータを受け取る際の注意点 Jun 04, 2024 am 10:31 AM

Go の関数にマップを渡すと、デフォルトでコピーが作成され、コピーへの変更は元のマップには影響しません。元のマップを変更する必要がある場合は、ポインタを介してそれを渡すことができます。空のマップは技術的には nil ポインターであり、空ではないマップを期待する関数に空のマップを渡すとエラーが発生するため、空のマップは慎重に扱う必要があります。

Golang のエラー ラッパーを使用するにはどうすればよいですか? Golang のエラー ラッパーを使用するにはどうすればよいですか? Jun 03, 2024 pm 04:08 PM

Golang では、エラー ラッパーを使用して、元のエラーにコンテキスト情報を追加することで新しいエラーを作成できます。これを使用すると、さまざまなライブラリまたはコンポーネントによってスローされるエラーの種類を統一し、デバッグとエラー処理を簡素化できます。手順は次のとおりです。errors.Wrap 関数を使用して、元のエラーを新しいエラーにラップします。新しいエラーには、元のエラーのコンテキスト情報が含まれています。 fmt.Printf を使用してラップされたエラーを出力し、より多くのコンテキストとアクション性を提供します。異なる種類のエラーを処理する場合は、errors.Wrap 関数を使用してエラーの種類を統一します。

See all articles