開発者ドキュメント

GoでHTTPリクエストを作成する方法

序章

プログラムが別のプログラムと通信する必要がある場合、多くの開発者はHTTPを使用します。 Goの強みの1つはその標準ライブラリの幅広さであり、HTTPも例外ではありません。 Go net / http パッケージは、 HTTPサーバーの作成をサポートするだけでなく、クライアントとしてHTTPリクエストを作成することもできます。

このチュートリアルでは、HTTPサーバーに対していくつかのタイプのHTTPリクエストを作成するプログラムを作成します。 まず、あなたは GET デフォルトのGoHTTPクライアントを使用してリクエストします。 次に、プログラムを強化して、 POST ボディでリクエスト。 最後に、あなたはあなたをカスタマイズします POST 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 ディレクトリとそれに移動します:

  1. mkdir projects
  2. cd projects

次に、プロジェクトのディレクトリを作成し、そこに移動します。 この場合、ディレクトリを使用します httpclient:

  1. mkdir httpclient
  2. cd httpclient

内部 httpclient ディレクトリ、使用 nano、またはお気に入りのエディタを開いて main.go ファイル:

  1. nano main.go

の中に 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サーバーを起動するためのゴルーチンを設定します。

main.go
...
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にリクエストを送信するには:

main.go
...
	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 それにファイル:

  1. go run main.go

次の出力が表示されます。

Output
server: GET / client: got response! client: status code: 200

出力の最初の行で、サーバーは受信したことを出力します GET クライアントからのリクエスト / 道。 次に、次の2行は、クライアントがサーバーから応答を受け取り、応答のステータスコードが 200.

The http.Get 関数は、このセクションで行ったような迅速なHTTPリクエストに役立ちます。 でも、 http.Request リクエストをカスタマイズするための幅広いオプションを提供します。

使用する http.Request リクエストする

とは対照的に http.Gethttp.Request 関数を使用すると、HTTPメソッドと要求されているURLだけでなく、要求をより細かく制御できます。 まだ追加機能を使用することはありませんが、 http.Request これで、このチュートリアルの後半でこれらのカスタマイズを追加できるようになります。

コードでは、最初の更新は、HTTPサーバーハンドラーを変更して、を使用して偽のJSONデータ応答を返すことです。 fmt.Fprintf. これが完全なHTTPサーバーの場合、このデータはGoの encoding /jsonパッケージを使用して生成されます。 GoでのJSONの使用について詳しく知りたい場合は、GoでJSONを使用する方法チュートリアルを利用できます。 さらに、含める必要があります io/ioutil このアップデートの後半で使用するためのインポートとして。

今、あなたの main.go 再度ファイルし、プログラムを更新して使用を開始します http.Request 以下に示すように:

main.go
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.NewRequesthttp.DefaultClientDo 方法:

main.go
...
	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 Bodyio.ReadCloser値であり、io.Readerio.Closerの組み合わせです。つまり、から読み取ることができるものを使用して、本体のデータを読み取ることができます。 io.Reader値。 The ioutil.ReadAll 関数はから読み取るので便利です io.Reader データの最後に到達するか、 error. 次に、データを次のように返します。 []byte を使用して印刷できる値 fmt.Printf、 または error 遭遇した値。

更新されたプログラムを実行するには、変更を保存して、 go run 指図:

  1. go run main.go

今回は、出力は以前と非常によく似ているはずですが、次の1つが追加されています。

Output
server: 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 声明:

main.go
...

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"time"
)

...

次に、サーバーハンドラー関数を更新して、クエリ文字列値、ヘッダー値、リクエスト本文など、着信するリクエストに関するさまざまな情報を出力します。

main.go
...
  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 リクエスト本文を使用したリクエスト:

main.go
...
 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 プログラムを実行するには:

  1. go run main.go

追加情報を表示するためのサーバーの更新により、出力は以前より長くなります。

Output
server: 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 そのように機能します:

main.go
...

  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 変数。 電話をかけてきたので、ここで他の変更を加える必要はありません Dohttp.Client ずっと価値がある。 GoのデフォルトのHTTPクライアント、 http.DefaultClient、は http.Client これはデフォルトで作成されます。 だから、あなたが電話したとき http.Get、関数はを呼び出していました Do あなたのための方法、そしてあなたが使用するためにあなたの要求を更新したとき http.DefaultClient、あなたはそれを使っていました http.Client 直接。 現在の唯一の違いは、 http.Client 今回使用している値。

次に、ファイルを保存し、を使用してプログラムを実行します go run:

  1. go run main.go

出力は前の出力と非常に似ているはずですが、コンテンツタイプに関する詳細情報が含まれています。

Output
server: 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秒間設定します。

main.go
...

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:

  1. go run main.go

今回実行すると、HTTPリクエストが終了するまで終了しないため、以前よりも終了に時間がかかります。 追加したので time.Sleep(35 * time.Second)、HTTPリクエストは、30秒のタイムアウトに達するまで完了しません。

Output
server: 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.NewRequesthttp.DefaultClientDo を作る方法 GET リクエスト。 次に、リクエストを更新して、 POST を使用してボディでリクエスト bytes.NewReader. 最後に、 Set のメソッド http.RequestHeader リクエストを設定するフィールド Content-Type ヘッダーを作成し、Goのデフォルトクライアントを使用する代わりに独自のHTTPクライアントを作成して、リクエストの期間に30秒のタイムアウトを設定します。

net / http パッケージには、このチュートリアルで使用した機能以上のものが含まれています。 また、http.Post関数を使用して作成することができます POST リクエスト、 http.Get 関数。 このパッケージは、他の機能の中でも特に、Cookieの保存と取得をサポートしています。

このチュートリアルは、 DigitalOcean How to Code inGoシリーズの一部でもあります。 このシリーズでは、Goの初めてのインストールから、言語自体の使用方法まで、Goに関する多くのトピックを取り上げています。

モバイルバージョンを終了