Open15

go - net/http

fmnfmn

net/http を使用してAPIを叩く。
それを標準出力
以降のスクラップでコードの深掘りをしていく

main.go
package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

type Post struct {
	UserId int    `json:"userId"`
	Id     int    `json:"id"`
	Title  string `json:"title"`
	Body   string `json:"body"`
}

func main() {
	var posts []Post

	resp, err := http.Get("https://jsonplaceholder.typicode.com/posts")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		fmt.Println("Error: status code", resp.StatusCode)
		return
	}

	body, _ := io.ReadAll(resp.Body)

	if err := json.Unmarshal(body, &posts); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%+v\n", posts)
}
fmnfmn

respの中身を見てみよう

main.go
resp, err := http.Get("https://jsonplaceholder.typicode.com/posts")

fmt.Printf("%+v\n", resp)
fish
❯ go run main.go
&{Status:200 OK StatusCode:200 Proto:HTTP/2.0 ProtoMajor:2 ProtoMinor:0 Header:map[Access-Control-Allow-Credentials:[true] Age:[24555] Alt-Svc:[h3=":443"; ma=86400, h3-29=":443"; ma=86400] Cache-Control:[max-age=43200] Cf-Cache-Status:[HIT] Cf-Ray:[6dbf1b91182c1f3b-NRT] Content-Type:[application/json; charset=utf-8] Date:[Fri, 11 Feb 2022 16:55:56 GMT] Etag:[W/"6b80-Ybsq/K6GwwqrYkAsFxqDXGC7DoM"] Expect-Ct:[max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"] Expires:[-1] Nel:[{"success_fraction":0,"report_to":"cf-nel","max_age":604800}] Pragma:[no-cache] Report-To:[{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=o05o0Kx%2B7Tu9KP3hZAEoAV5QWGiS%2FIeXnRHVC5ZHDvb1S3Pgk2hGMQaiqu5RGjTzakcwb3rEn%2BLUOML21HzYqQq103NLrXEjm0QTYwW7r8b%2FNRziD%2FQ1mcltL7nhvbkSFKTUu82GbauoQrLN5DtN%2F85xrmfqxfrPd2b8"}],"group":"cf-nel","max_age":604800}] Server:[cloudflare] Vary:[Origin, Accept-Encoding] Via:[1.1 vegur] X-Content-Type-Options:[nosniff] X-Powered-By:[Express] X-Ratelimit-Limit:[1000] X-Ratelimit-Remaining:[999] X-Ratelimit-Reset:[1644545002]] Body:0x1400007d1a0 ContentLength:-1 TransferEncoding:[] Close:false Uncompressed:true Trailer:map[] Request:0x14000144000 TLS:0x140003360b0}

どう考えたって見づらいの整形します

fish
&{
	Status:200 OK 
	StatusCode:200 
	Proto:HTTP/2.0
	ProtoMajor:2
	ProtoMinor:0
	Header:map[
		Access-Control-Allow-Credentials:[true]
		Age:[24555]
		Alt-Svc:[h3=":443"; ma=86400, h3-29=":443"; ma=86400]
		Cache-Control:[max-age=43200]
		Cf-Cache-Status:[HIT]
		Cf-Ray:[6dbf1b91182c1f3b-NRT]
		Content-Type:[application/json; charset=utf-8]
		Date:[Fri, 11 Feb 2022 16:55:56 GMT]
		Etag:[W/"6b80-Ybsq/K6GwwqrYkAsFxqDXGC7DoM"]
		Expect-Ct:[max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"]
		Expires:[-1]
		Nel:[{"success_fraction":0,"report_to":"cf-nel","max_age":604800}]
		Pragma:[no-cache]
		Report-To:[{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=o05o0Kx%2B7Tu9KP3hZAEoAV5QWGiS%2FIeXnRHVC5ZHDvb1S3Pgk2hGMQaiqu5RGjTzakcwb3rEn%2BLUOML21HzYqQq103NLrXEjm0QTYwW7r8b%2FNRziD%2FQ1mcltL7nhvbkSFKTUu82GbauoQrLN5DtN%2F85xrmfqxfrPd2b8"}],"group":"cf-nel","max_age":604800}]
		Server:[cloudflare]
		Vary:[Origin, Accept-Encoding]
		Via:[1.1 vegur]
		X-Content-Type-Options:[nosniff]
		X-Powered-By:[Express]
		X-Ratelimit-Limit:[1000]
		X-Ratelimit-Remaining:[999]
		X-Ratelimit-Reset:[1644545002]
	]
	Body:0x1400007d1a0
	ContentLength:-1
	TransferEncoding:[]
	Close:false
	Uncompressed:true
	Trailer:map[]
	Request:0x14000144000
	TLS:0x140003360b0
}
fmnfmn

公式ドキュメントにこちらの構造体が定義されています
https://pkg.go.dev/net/http#Response

type Response
type Response struct {
	Status     string // e.g. "200 OK"
	StatusCode int    // e.g. 200
	Proto      string // e.g. "HTTP/1.0"
	ProtoMajor int    // e.g. 1
	ProtoMinor int    // e.g. 0

	// Header maps header keys to values. If the response had multiple
	// headers with the same key, they may be concatenated, with comma
	// delimiters.  (RFC 7230, section 3.2.2 requires that multiple headers
	// be semantically equivalent to a comma-delimited sequence.) When
	// Header values are duplicated by other fields in this struct (e.g.,
	// ContentLength, TransferEncoding, Trailer), the field values are
	// authoritative.
	//
	// Keys in the map are canonicalized (see CanonicalHeaderKey).
	Header Header

	// Body represents the response body.
	//
	// The response body is streamed on demand as the Body field
	// is read. If the network connection fails or the server
	// terminates the response, Body.Read calls return an error.
	//
	// The http Client and Transport guarantee that Body is always
	// non-nil, even on responses without a body or responses with
	// a zero-length body. It is the caller's responsibility to
	// close Body. The default HTTP client's Transport may not
	// reuse HTTP/1.x "keep-alive" TCP connections if the Body is
	// not read to completion and closed.
	//
	// The Body is automatically dechunked if the server replied
	// with a "chunked" Transfer-Encoding.
	//
	// As of Go 1.12, the Body will also implement io.Writer
	// on a successful "101 Switching Protocols" response,
	// as used by WebSockets and HTTP/2's "h2c" mode.
	Body io.ReadCloser

	// ContentLength records the length of the associated content. The
	// value -1 indicates that the length is unknown. Unless Request.Method
	// is "HEAD", values >= 0 indicate that the given number of bytes may
	// be read from Body.
	ContentLength int64

	// Contains transfer encodings from outer-most to inner-most. Value is
	// nil, means that "identity" encoding is used.
	TransferEncoding []string

	// Close records whether the header directed that the connection be
	// closed after reading Body. The value is advice for clients: neither
	// ReadResponse nor Response.Write ever closes a connection.
	Close bool

	// Uncompressed reports whether the response was sent compressed but
	// was decompressed by the http package. When true, reading from
	// Body yields the uncompressed content instead of the compressed
	// content actually set from the server, ContentLength is set to -1,
	// and the "Content-Length" and "Content-Encoding" fields are deleted
	// from the responseHeader. To get the original response from
	// the server, set Transport.DisableCompression to true.
	Uncompressed bool

	// Trailer maps trailer keys to values in the same
	// format as Header.
	//
	// The Trailer initially contains only nil values, one for
	// each key specified in the server's "Trailer" header
	// value. Those values are not added to Header.
	//
	// Trailer must not be accessed concurrently with Read calls
	// on the Body.
	//
	// After Body.Read has returned io.EOF, Trailer will contain
	// any trailer values sent by the server.
	Trailer Header

	// Request is the request that was sent to obtain this Response.
	// Request's Body is nil (having already been consumed).
	// This is only populated for Client requests.
	Request *Request

	// TLS contains information about the TLS connection on which the
	// response was received. It is nil for unencrypted responses.
	// The pointer is shared between responses and should not be
	// modified.
	TLS *tls.ConnectionState
}
fmnfmn

英語わかんないマンなので翻訳します

type Response struct {
	Status          string  // e.g. "200 OK"
	StatusCode int       // e.g. 200
	Proto            string // e.g. "HTTP/1.0"
	ProtoMajor   int      // e.g. 1
	ProtoMinor   int      // e.g. 0

        // Header は、ヘッダのキーと値を対応付ける。
        // レスポンスが同じキーを持つ複数のヘッダを持っていた場合、
        // それらはカンマ区切りで連結されるかもしれません。
        // (RFC7230のセクション3.2.2は、複数のヘッダーが意味的にカンマ区切りの
        // シーケンスと等価であることを要求している)。
        // Header の値がこの構造体の他のフィールド(例えば ContentLength, TransferEncoding, Trailer)
        // と重複している場合、そのフィールドの値を使用する。authoritative(権威的)である。
	//
	// マップ内のキーは正規化されます (CanonicalHeaderKey 参照)。
	Header Header

	// Body はレスポンスボディを表します.
	//
	// レスポンスボディはBodyフィールドが読み込まれる際にオンデマンドでストリーミングされる。
        // ネットワーク接続に失敗したり、サーバーが応答を終了した場合、
        // Body.Read の呼び出しはエラーを返します。
        //
        // httpクライアントとトランスポートは、ボディがないレスポンスや長さがゼロのレスポンス
        // であっても、ボディが常にゼロでないことを保証します。Bodyを閉じるのは呼び出し側の責任である。 
        // デフォルトのHTTPクライアントのTransportは、Bodyが最後まで読まれずに閉じられた場合、 
        // HTTP/1.xの「キープアライブ」TCPコネクションを再利用しないかもしれません。
        //
        // サーバーから返信があった場合、Bodyは自動的にデチャンクされます。
        // で、Transfer-Encodingを "chunked "にする。
        //
        // Go 1.12 では、Body は「101 Switching Protocols」レスポンスに成功すると io.Writer も実装されます。 
        // WebSocket や HTTP/2 の "h2c" モードで使用されるように。
	Body io.ReadCloser

        // ContentLength は、関連するコンテンツの長さを記録する。
        // 値-1は、長さが不明であることを示す。Request.Methodが "HEAD "でない限り,
        // 値 >= 0は,与えられたバイト数がBodyから読み取られるかもしれないことを示す。
	ContentLength int64

        // 最外周から最内周への転送エンコーディングが含まれる。
        // 値がnilの場合、"identity "エンコーディングが使用されることを意味する。
	TransferEncoding []string

        // Close は、Body を読んだ後、ヘッダが接続を閉じるように指示したかどうか
        // を記録する。 この値はクライアントへのアドバイスです。
        // ReadResponseもResponse.Writeも決してコネクションを閉じません。
	Close bool

        // Uncompressed は、レスポンスが圧縮されて送信されたが http パッケージによって
        // 伸長されたかどうかを報告します。Trueの場合、Bodyから読み込むと、
        // サーバーから実際に設定された圧縮コンテンツではなく、圧縮されていないコンテンツが得られ、 
        // ContentLengthは-1に設定され、responseHeaderから "Content-Length" と "Content-Encoding"
        // フィールドが削除されます。サーバーからオリジナルのレスポンスを取得するには、 
        // Transport.DisableCompressionをtrueに設定します。
	Uncompressed bool

        // Trailer は、Header と同じフォーマットでトレーラーのキーと値を対応付ける。
        //
        // Trailerは、最初はnil値のみを含み、サーバーの "Trailer "ヘッダー値で指定された各キーに対して
        // 1つずつ含まれる。これらの値はHeaderに追加されない。
        //
        // Trailerは、BodyのReadコールと同時にアクセスしてはならない。
        //
        // Body.Readがio.EOFを返した後、Trailerにはサーバから送られたトレイラ値が格納される
	Trailer Header

        // Request は、この Response を取得するために送信されたリクエストである。
        // RequestのBodyはnilである(すでに消費されている)。
        // これはClientのリクエストに対してのみ入力される。
	Request *Request

        // TLS は、応答を受信した TLS 接続に関する情報を含む。
        // 暗号化されていないレスポンスでは、これはnilである。
        // このポインターはレスポンス間で共有され、変更されるべきではありません。
	TLS *tls.ConnectionState
}
fmnfmn

それでは上からそれぞれ見ていく

1. Status, StatusCode

レスポンスのステータスですね。エンジニアであれば説明は不要でしょう。
注意点としては、
Status => string, 例) "200 OK"
StatusCode => int, 例) 200
と型違いの2つのステータスが返ることでしょうか。

fmnfmn

2. Proto, ProtoMajor, ProtoMinor

ソースコードをみると、Protoに関してこう説明されています。

// The protocol version for incoming server requests.
...
Proto      string // "HTTP/1.0"
ProtoMajor int    // 1
ProtoMinor int    // 0

(https://pkg.go.dev/net/http#:~:text=// The protocol version for incoming server requests.)
つまり、HTTPプロトコルのverisonのようです.
Proto => string, httpプロトコルバージョン
ProtoMajor => int, httpプロトコルバージョンのメジャーバージョンNo.
ProtoMinor => int, httpプロトコルバージョンのマイナーバージョンNo.

ちなみに...
HTTPレスポンスヘッダーにX-Forwarded-Protoというフィールドがあります。
これは、「ロードバランサーへ接続するのに使っていたクライアントのプロトコル (HTTP または HTTPS) を特定するために事実上の標準となっているヘッダー」です。
(cf. https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-Forwarded-Proto)

fmnfmn

3. Header

ソースコードをみると

   // Header は、ヘッダのキーと値を対応付ける

とあります。
レスポンスのヘッダーを格納しているところですね。

fmnfmn

4. Body

とうぜん、レスポンスのBodyを表すかと思います。
しかし、ソースコードに気になる文言があります。

// Bodyを閉じるのは呼び出し側の責任である。

です。
そして、Bodyの型であるio.ReadCloserです。

Body io.ReadCloser

この辺は、main.goで書いた

defer resp.Body.Close()

という文と関係してくるのかと思います。
正直なぜcloseが必要なのか全くわかりませんが、
こちらの記事で説明がありました。
https://qiita.com/stk0724/items/dc400dccd29a4b3d6471
「TCPコネクションがクローズされない」とあるので、Closeしないとやばそうです。

fmnfmn

5. ContentLength

そのまま。
一般的なhttpヘッダにもContent-Lengthがあります。

fmnfmn

6. TransferEncoding, Uncompressed

上記の2つのフィールドはセット感があります。
Uncompressed => サーバーからクライアントに送られるデータが、圧縮されて送られてきたかどうかを示す。
TransferEncoding => データ転送に使用された形式

Goではわかりませんが、一般的にTransferEncodingには以下の選択肢があるようです。
chunked => チャンク (塊) の連続で送られます
compress => Lempel-Ziv-Welch (LZW) アルゴリズムを使用した形式. 過去の遺物
deflate => zlib 構造体と deflate 圧縮アルゴリズムを使用します。
gzip => gzipを使用
identity => 圧縮なし

fmnfmn

7. Close

ソースコードにもあるように、Bodyを読み込んだ後にちゃんとCloseしたかどうかを保存しておくところ?
クライアントへのアドバイスとしてのフィールドらいしので、Closeしないまま終わろうとしたら例外吐くようになってるのかな?

fmnfmn

8. Trailer

一般的なhttpのTrailer

Trailer応答ヘッダは、メッセージの完全性チェック、デジタル署名、後処理の状態など、メッセージ本体の送信中に動的に生成される可能性のあるメタデータを提供するために、送信者がチャンクされたメッセージの末尾に追加フィールドを含めることを可能にします。

fmnfmn

9. Request

リクエスト情報が記載

fmnfmn

10. TLS

TLSに関する情報

fmnfmn

論点2つ目

body, _ := ioutil.ReadAll(resp.Body)

ioライブラリ