序章
プログラムが別のプログラムと通信する必要がある場合、多くの開発者はHTTPを使用します。 Goの強みの1つはその標準ライブラリの幅広さであり、HTTPも例外ではありません。 Go net / http パッケージは、 HTTPサーバーの作成をサポートするだけでなく、クライアントとしてHTTPリクエストを作成することもできます。
このチュートリアルでは、HTTPサーバーに対していくつかのタイプのHTTPリクエストを作成するプログラムを作成します。 まず、あなたは GET
デフォルトのGoHTTPクライアントを使用してリクエストします。 次に、プログラムを強化して、 POST
ボディでリクエスト。 最後に、あなたはあなたをカスタマイズします POST
HTTPヘッダーを含めるように要求し、要求に時間がかかりすぎる場合にトリガーされるタイムアウトを追加します。
前提条件
このチュートリアルに従うには、次のものが必要です。
- バージョン1.16以降をインストールしてください。 これを設定するには、オペレーティングシステムのGoチュートリアルをインストールする方法に従ってください。
- GoでHTTPサーバーを作成した経験。これはチュートリアルGoでHTTPサーバーを作成する方法にあります。
- ゴルーチンと読書チャンネルに精通していること。 詳細については、チュートリアルGoで複数の関数を同時に実行する方法を参照してください。
- HTTPリクエストがどのように構成および送信されるかを理解することをお勧めします。
GETリクエストを行う
Go net / http パッケージには、クライアントとして使用するためのいくつかの異なる方法があります。 http.Get などの関数を備えた一般的なグローバルHTTPクライアントを使用して、HTTPをすばやく作成できます GET
URLと本文のみを使用してリクエストするか、 http.Request を作成して、個々のリクエストの特定の側面のカスタマイズを開始できます。 このセクションでは、を使用して初期プログラムを作成します http.Get
HTTPリクエストを作成し、それを更新して http.Request
デフォルトのHTTPクライアントを使用します。
使用する http.Get
リクエストする
プログラムの最初の反復では、 http.Get
プログラムで実行しているHTTPサーバーにリクエストを送信する関数。 The http.Get
リクエストを行うためにプログラムに追加の設定を行う必要がないため、この関数は便利です。 簡単なリクエストを1回行う必要がある場合は、 http.Get
最良の選択肢かもしれません。
プログラムの作成を開始するには、プログラムのディレクトリを保持するためのディレクトリが必要です。 このチュートリアルでは、という名前のディレクトリを使用します projects
.
まず、 projects
ディレクトリとそれに移動します:
- mkdir projects
- cd projects
次に、プロジェクトのディレクトリを作成し、そこに移動します。 この場合、ディレクトリを使用します httpclient
:
- mkdir httpclient
- cd httpclient
内部 httpclient
ディレクトリ、使用 nano
、またはお気に入りのエディタを開いて main.go
ファイル:
- nano main.go
の中に main.go
ファイル、次の行を追加することから始めます。
package main
import (
"errors"
"fmt"
"net/http"
"os"
"time"
)
const serverPort = 3333
追加します package
名前 main
あなたのプログラムがあなたが実行できるプログラムとしてコンパイルされるように、そしてそれから import
このプログラムで使用するさまざまなパッケージを含むステートメント。 その後、作成します const
と呼ばれる serverPort
値で 3333
、HTTPサーバーがリッスンしているポート、およびHTTPクライアントが接続するポートとして使用します。
次に、 main
の機能 main.go
ファイルを作成し、HTTPサーバーを起動するためのゴルーチンを設定します。
...
func main() {
go func() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("server: %s /\n", r.Method)
})
server := http.Server{
Addr: fmt.Sprintf(":%d", serverPort),
Handler: mux,
}
if err := server.ListenAndServe(); err != nil {
if !errors.Is(err, http.ErrServerClosed) {
fmt.Printf("error running http server: %s\n", err)
}
}
}()
time.Sleep(100 * time.Millisecond)
HTTPサーバーが使用するように設定されている fmt.Printf
ルートがいつでも着信リクエストに関する情報を出力する /
パスが要求されます。 また、聞くように設定されています serverPort
. 最後に、サーバーゴルーチンを起動すると、プログラムは time.Sleep
短時間。 このスリープ時間により、HTTPサーバーは、起動して次に行う要求への応答の提供を開始するために必要な時間を確保できます。
さて、 main
関数、を使用してリクエストURLを設定します fmt.Sprintf
組み合わせるには http://localhost
ホスト名と serverPort
サーバーがリッスンしている値。 次に、 http.Get
以下に示すように、そのURLにリクエストを送信するには:
...
requestURL := fmt.Sprintf("http://localhost:%d", serverPort)
res, err := http.Get(requestURL)
if err != nil {
fmt.Printf("error making http request: %s\n", err)
os.Exit(1)
}
fmt.Printf("client: got response!\n")
fmt.Printf("client: status code: %d\n", res.StatusCode)
}
いつ http.Get
関数が呼び出されると、GoはデフォルトのHTTPクライアントを使用して指定されたURLにHTTPリクエストを送信し、http.Responseまたは error
リクエストが失敗した場合の値。 リクエストが失敗した場合、エラーが出力され、os.Exitを使用してプログラムを終了します。エラーコードは 1
. リクエストが成功すると、プログラムはレスポンスと受け取ったHTTPステータスコードを受け取ったことを出力します。
完了したら、ファイルを保存して閉じます。
プログラムを実行するには、 go run
コマンドを実行し、 main.go
それにファイル:
- go run main.go
次の出力が表示されます。
Outputserver: GET /
client: got response!
client: status code: 200
出力の最初の行で、サーバーは受信したことを出力します GET
クライアントからのリクエスト /
道。 次に、次の2行は、クライアントがサーバーから応答を受け取り、応答のステータスコードが 200
.
The http.Get
関数は、このセクションで行ったような迅速なHTTPリクエストに役立ちます。 でも、 http.Request
リクエストをカスタマイズするための幅広いオプションを提供します。
使用する http.Request
リクエストする
とは対照的に http.Get
、 http.Request
関数を使用すると、HTTPメソッドと要求されているURLだけでなく、要求をより細かく制御できます。 まだ追加機能を使用することはありませんが、 http.Request
これで、このチュートリアルの後半でこれらのカスタマイズを追加できるようになります。
コードでは、最初の更新は、HTTPサーバーハンドラーを変更して、を使用して偽のJSONデータ応答を返すことです。 fmt.Fprintf
. これが完全なHTTPサーバーの場合、このデータはGoの encoding /jsonパッケージを使用して生成されます。 GoでのJSONの使用について詳しく知りたい場合は、GoでJSONを使用する方法チュートリアルを利用できます。 さらに、含める必要があります io/ioutil
このアップデートの後半で使用するためのインポートとして。
今、あなたの main.go
再度ファイルし、プログラムを更新して使用を開始します http.Request
以下に示すように:
package main
import (
...
"io/ioutil"
...
)
...
func main() {
...
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("server: %s /\n", r.Method)
fmt.Fprintf(w, `{"message": "hello!"}`)
})
...
次に、HTTPリクエストコードを更新して、を使用する代わりに http.Get
サーバーにリクエストを送信するには、 http.NewRequest
と http.DefaultClient
の Do
方法:
...
requestURL := fmt.Sprintf("http://localhost:%d", serverPort)
req, err := http.NewRequest(http.MethodGet, requestURL, nil)
if err != nil {
fmt.Printf("client: could not create request: %s\n", err)
os.Exit(1)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Printf("client: error making http request: %s\n", err)
os.Exit(1)
}
fmt.Printf("client: got response!\n")
fmt.Printf("client: status code: %d\n", res.StatusCode)
resBody, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Printf("client: could not read response body: %s\n", err)
os.Exit(1)
}
fmt.Printf("client: response body: %s\n", resBody)
}
このアップデートでは、 http.NewRequest
を生成する関数 http.Request
値、または値を作成できない場合はエラーを処理します。 とは異なり http.Get
ただし、関数は http.NewRequest
関数はHTTPリクエストをサーバーにすぐに送信しません。 リクエストはすぐには送信されないため、送信する前にリクエストに必要な変更を加えることができます。
一度 http.Request
が作成および構成されている場合は、 Do
の方法 http.DefaultClient
サーバーにリクエストを送信します。 The http.DefaultClient
値はGoのデフォルトのHTTPクライアントであり、これまで使用してきたものと同じです。 http.Get
. ただし、今回は直接使用して、送信するように指示しています。 http.Request
. The Do
HTTPクライアントのメソッドは、 http.Get
同じように応答を処理できるように機能します。
リクエスト結果を出力したら、ioutil.ReadAll関数を使用してHTTP応答を読み取ります Body
. The Body
はio.ReadCloser値であり、io.Readerとio.Closerの組み合わせです。つまり、から読み取ることができるものを使用して、本体のデータを読み取ることができます。 io.Reader値。 The ioutil.ReadAll
関数はから読み取るので便利です io.Reader
データの最後に到達するか、 error
. 次に、データを次のように返します。 []byte
を使用して印刷できる値 fmt.Printf
、 または error
遭遇した値。
更新されたプログラムを実行するには、変更を保存して、 go run
指図:
- go run main.go
今回は、出力は以前と非常によく似ているはずですが、次の1つが追加されています。
Outputserver: GET /
client: got response!
client: status code: 200
client: response body: {"message": "hello!"}
最初の行では、サーバーがまだ受信していることがわかります GET
にリクエスト /
道。 クライアントはまた、 200
サーバーからの応答ですが、読み取りと印刷も行っています Body
サーバーの応答の。 より複雑なプログラムでは、あなたはそれから取ることができます {"message": "hello!"}
サーバーから本文として受け取った値を、 encoding /jsonパッケージを使用してJSONとして処理します。
このセクションでは、さまざまな方法でHTTPリクエストを行ったHTTPサーバーを使用してプログラムを作成しました。 まず、 http.Get
を作るための関数 GET
サーバーのURLのみを使用してサーバーにリクエストします。 次に、使用するプログラムを更新しました http.NewRequest
を作成するには http.Request
価値。 それが作成されたら、 Do
GoのデフォルトのHTTPクライアントのメソッド。 http.DefaultClient
、リクエストを作成して印刷するには http.Response
Body
出力に。
HTTPプロトコルは単なるもの以上のものを使用します GET
ただし、プログラム間の通信要求。 A GET
requestは、他のプログラムから情報を受け取りたいが、別のHTTPメソッドである POST
メソッドは、プログラムからサーバーに情報を送信する場合に使用できます。
POSTリクエストの送信
REST API では、 GET
リクエストはサーバーから情報を取得するためにのみ使用されるため、プログラムがREST APIに完全に参加するには、プログラムが送信をサポートする必要もあります。 POST
リクエスト。 A POST
リクエストはほぼ逆です GET
リクエスト。クライアントはリクエストの本文でサーバーにデータを送信します。
このセクションでは、プログラムを更新して、リクエストを次のように送信します。 POST
の代わりにリクエスト GET
リクエスト。 君の POST
リクエストにはリクエスト本文が含まれ、サーバーを更新して、クライアントからのリクエストに関する詳細情報を出力します。
これらの更新を開始するには、 main.go
ファイルを作成し、使用する新しいパッケージをいくつか追加します import
声明:
...
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"time"
)
...
次に、サーバーハンドラー関数を更新して、クエリ文字列値、ヘッダー値、リクエスト本文など、着信するリクエストに関するさまざまな情報を出力します。
...
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("server: %s /\n", r.Method)
fmt.Printf("server: query id: %s\n", r.URL.Query().Get("id"))
fmt.Printf("server: content-type: %s\n", r.Header.Get("content-type"))
fmt.Printf("server: headers:\n")
for headerName, headerValue := range r.Header {
fmt.Printf("\t%s = %s\n", headerName, strings.Join(headerValue, ", "))
}
reqBody, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Printf("server: could not read request body: %s\n", err)
}
fmt.Printf("server: request body: %s\n", reqBody)
fmt.Fprintf(w, `{"message": "hello!"}`)
})
...
サーバーのHTTPリクエストハンドラーに対するこのアップデートでは、さらに役立つものをいくつか追加します fmt.Printf
入ってくるリクエストに関する情報を確認するためのステートメント。 あなたが使う r.URL.Query().Get
名前の付いたクエリ文字列値を取得するには id
、 と r.Header.Get
と呼ばれるヘッダーの値を取得するには content-type
. また、 for
ループ r.Header
サーバーが受信した各HTTPヘッダーの名前と値を出力します。 この情報は、クライアントまたはサーバーが期待どおりに動作していない場合の問題のトラブルシューティングに役立ちます。 最後に、あなたも使用しました ioutil.ReadAll
HTTPリクエストの本文を読み取る関数 r.Body
.
サーバーハンドラー機能を更新した後、 main
関数のリクエストコードを送信するように POST
リクエスト本文を使用したリクエスト:
...
time.Sleep(100 * time.Millisecond)
jsonBody := []byte(`{"client_message": "hello, server!"}`)
bodyReader := bytes.NewReader(jsonBody)
requestURL := fmt.Sprintf("http://localhost:%d?id=1234", serverPort)
req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader)
...
あなたのアップデートで main
関数のリクエスト、あなたが定義している新しい値の1つは jsonBody
価値。 この例では、値は次のように表されます。 []byte
標準の代わりに string
あなたが使用する場合 encoding/json
JSONデータをエンコードするパッケージ、それはあなたに []byte
の代わりに戻る string
.
次の値、 bodyReader
、はbytes.Readerであり jsonBody
データ。 アン http.Request
本体には値が必要です io.Reader
、 と jsonBody
の []byte
値は実装されません io.Reader
、したがって、それ自体をリクエスト本文として使用することはできません。 The bytes.Reader
それを提供する価値が存在する io.Reader
インターフェースなので、 jsonBody
リクエスト本文としての値。
The requestURL
値も更新され、 id=1234
クエリ文字列値。主に、クエリ文字列値を他の標準URLコンポーネントとともにリクエストURLに含める方法を示します。
最後に、 http.NewRequest
関数呼び出しは、を使用するように更新されます POST
との方法 http.MethodPost
、およびリクエスト本文は、から最後のパラメータを更新することで含まれます nil
体に bodyReader
、JSONデータ io.Reader
.
変更を保存したら、次を使用できます go run
プログラムを実行するには:
- go run main.go
追加情報を表示するためのサーバーの更新により、出力は以前より長くなります。
Outputserver: POST /
server: query id: 1234
server: content-type:
server: headers:
Accept-Encoding = gzip
User-Agent = Go-http-client/1.1
Content-Length = 36
server: request body: {"client_message": "hello, server!"}
client: got response!
client: status code: 200
client: response body: {"message": "hello!"}
サーバーからの最初の行は、リクエストが次のように送信されていることを示しています。 POST
にリクエスト /
道。 2行目は 1234
の値 id
リクエストのURLに追加したクエリ文字列の値。 3行目は、 Content-Type
クライアントが送信したヘッダー。このリクエストではたまたま空です。
4行目は、上記の出力とは少し異なる場合があります。 Goでは、 map
を使用してそれらを反復処理する場合、値は保証されません。 range
、だからあなたのヘッダーは r.Headers
別の順序で印刷される場合があります。 使用しているGoのバージョンによっては、異なる場合もあります。 User-Agent
上記のものよりバージョン。
最後に、出力の最後の変更は、サーバーがクライアントから受信したリクエスト本文を表示していることです。 その後、サーバーは encoding/json
クライアントが送信したJSONデータを解析し、応答を作成するためのパッケージ。
このセクションでは、HTTPを送信するようにプログラムを更新しました POST
の代わりにリクエスト GET
リクエスト。 また、プログラムを更新して、リクエスト本文を次のように送信しました []byte
によって読み取られているデータ bytes.Reader
. 最後に、サーバーハンドラー関数を更新して、HTTPクライアントが行っている要求に関する詳細情報を出力しました。
通常、HTTPリクエストでは、クライアントまたはサーバーは、本文で送信しているコンテンツのタイプを相手に通知します。 ただし、最後の出力で見たように、HTTPリクエストには Content-Type
本体のデータを解釈する方法をサーバーに指示するヘッダー。 次のセクションでは、HTTPリクエストをカスタマイズするために、いくつかの更新を行います。 Content-Type
送信するデータのタイプをサーバーに通知するヘッダー。
HTTPリクエストのカスタマイズ
時間の経過とともに、HTTP要求と応答は、クライアントとサーバー間でさまざまなデータを送信するために使用されてきました。 ある時点で、HTTPクライアントは、HTTPサーバーから受信しているデータがHTMLであり、正しい可能性が高いと想定する可能性があります。 ただし、HTML、JSON、音楽、ビデオ、またはその他の任意の数のデータ型である可能性があります。 HTTPを介して送信されるデータに関する詳細情報を提供するために、プロトコルにはHTTPヘッダーが含まれており、これらの重要なヘッダーの1つは Content-Type
ヘッダ。 このヘッダーは、サーバー(またはデータの方向によってはクライアント)に、受信しているデータを解釈する方法を指示します。
このセクションでは、プログラムを更新して、 Content-Type
サーバーがJSONデータを受信していることを認識できるようにHTTPリクエストのヘッダー。 また、Goのデフォルト以外のHTTPクライアントを使用するようにプログラムを更新します http.DefaultClient
リクエストの送信方法をカスタマイズできるようにします。
これらの更新を行うには、 main.go
もう一度ファイルして更新します main
そのように機能します:
...
req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader)
if err != nil {
fmt.Printf("client: could not create request: %s\n", err)
os.Exit(1)
}
req.Header.Set("Content-Type", "application/json")
client := http.Client{
Timeout: 30 * time.Second,
}
res, err := client.Do(req)
if err != nil {
fmt.Printf("client: error making http request: %s\n", err)
os.Exit(1)
}
...
このアップデートでは、 http.Request
を使用するヘッダー req.Header
、次に、の値を設定します Content-Type
リクエストのヘッダー application/json
. The application/json
メディアタイプは、メディアタイプのリストでJSONのメディアタイプとして定義されています。 このように、サーバーはリクエストを受信すると、本文をJSONとして解釈し、たとえばXMLとして解釈しないことを認識します。
次のアップデートはあなた自身を作成することです http.Client
のインスタンス client
変数。 このクライアントでは、 Timeout
30秒までの値。 これは、クライアントで行われたすべての要求が放棄され、30秒後に応答の受信を停止することを示しているため重要です。 Goのデフォルト http.DefaultClient
はタイムアウトを指定しないため、そのクライアントを使用して要求を行うと、応答を受信するか、サーバーによって切断されるか、プログラムが終了するまで待機します。 このように応答を待っているリクエストがたくさんある場合は、コンピューターで大量のリソースを使用している可能性があります。 設定 Timeout
値は、定義した時間までにリクエストが待機する時間を制限します。
最後に、を使用するようにリクエストを更新しました Do
あなたの方法 client
変数。 電話をかけてきたので、ここで他の変更を加える必要はありません Do
に http.Client
ずっと価値がある。 GoのデフォルトのHTTPクライアント、 http.DefaultClient
、は http.Client
これはデフォルトで作成されます。 だから、あなたが電話したとき http.Get
、関数はを呼び出していました Do
あなたのための方法、そしてあなたが使用するためにあなたの要求を更新したとき http.DefaultClient
、あなたはそれを使っていました http.Client
直接。 現在の唯一の違いは、 http.Client
今回使用している値。
次に、ファイルを保存し、を使用してプログラムを実行します go run
:
- go run main.go
出力は前の出力と非常に似ているはずですが、コンテンツタイプに関する詳細情報が含まれています。
Outputserver: POST /
server: query id: 1234
server: content-type: application/json
server: headers:
Accept-Encoding = gzip
User-Agent = Go-http-client/1.1
Content-Length = 36
Content-Type = application/json
server: request body: {"client_message": "hello, server!"}
client: got response!
client: status code: 200
client: response body: {"message": "hello!"}
サーバーからの値があることがわかります content-type
、そしてあります Content-Type
クライアントによって送信されているヘッダー。 これは、JSONとXMLAPIの両方を同時に提供する同じHTTPリクエストパスを持つことができる方法です。 リクエストのコンテンツタイプを指定することにより、サーバーとクライアントはデータを異なる方法で解釈できます。
ただし、この例では、構成したクライアントタイムアウトはトリガーされません。 リクエストに時間がかかりすぎてタイムアウトがトリガーされたときに何が起こるかを確認するには、 main.go
ファイルを追加し、 time.Sleep
HTTPサーバーハンドラー関数への関数呼び出し。 次に、 time.Sleep
指定したタイムアウトより長く持続します。 この場合、35秒間設定します。
...
func main() {
go func() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
...
fmt.Fprintf(w, `{"message": "hello!"}`)
time.Sleep(35 * time.Second)
})
...
}()
...
}
次に、変更を保存し、を使用してプログラムを実行します go run
:
- go run main.go
今回実行すると、HTTPリクエストが終了するまで終了しないため、以前よりも終了に時間がかかります。 追加したので time.Sleep(35 * time.Second)
、HTTPリクエストは、30秒のタイムアウトに達するまで完了しません。
Outputserver: POST /
server: query id: 1234
server: content-type: application/json
server: headers:
Content-Type = application/json
Accept-Encoding = gzip
User-Agent = Go-http-client/1.1
Content-Length = 36
server: request body: {"client_message": "hello, server!"}
client: error making http request: Post "http://localhost:3333?id=1234": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
exit status 1
このプログラム出力では、サーバーがリクエストを受信して処理したことがわかりますが、HTTPハンドラー関数の最後に到達すると time.Sleep
関数呼び出しは、35秒間スリープを開始しました。 同時に、HTTPリクエストのタイムアウトがカウントダウンされ、HTTPリクエストが終了する前に30秒の制限に達します。 これにより、 client.Do
メソッド呼び出しが失敗し、 context deadline exceeded
リクエストの30秒の期限が過ぎたため、エラーが発生しました。 次に、プログラムは次の障害ステータスコードで終了します。 1
を使用して os.Exit(1)
.
このセクションでは、プログラムを更新して、HTTPリクエストをカスタマイズします。 Content-Type
それにヘッダー。 また、プログラムを更新して新しいものを作成しました http.Client
30秒のタイムアウトで、そのクライアントを使用してHTTPリクエストを作成しました。 また、30秒のタイムアウトを追加してテストしました time.Sleep
HTTPリクエストハンドラに。 最後に、自分で使用することが重要である理由もわかりました http.Client
多くのリクエストが永久にアイドリングする可能性を回避したい場合は、タイムアウトが設定された値。
結論
このチュートリアルでは、HTTPサーバーを使用して新しいプログラムを作成し、Goを使用しました。 net/http
そのサーバーにHTTPリクエストを送信するパッケージ。 まず、 http.Get
を作るための関数 GET
GoのデフォルトのHTTPクライアントを使用してサーバーにリクエストします。 次に、 http.NewRequest
と http.DefaultClient
の Do
を作る方法 GET
リクエスト。 次に、リクエストを更新して、 POST
を使用してボディでリクエスト bytes.NewReader
. 最後に、 Set
のメソッド http.Request
の Header
リクエストを設定するフィールド Content-Type
ヘッダーを作成し、Goのデフォルトクライアントを使用する代わりに独自のHTTPクライアントを作成して、リクエストの期間に30秒のタイムアウトを設定します。
net / http パッケージには、このチュートリアルで使用した機能以上のものが含まれています。 また、http.Post関数を使用して作成することができます POST
リクエスト、 http.Get
関数。 このパッケージは、他の機能の中でも特に、Cookieの保存と取得をサポートしています。
このチュートリアルは、 DigitalOcean How to Code inGoシリーズの一部でもあります。 このシリーズでは、Goの初めてのインストールから、言語自体の使用方法まで、Goに関する多くのトピックを取り上げています。