LoginSignup
19
13

More than 3 years have passed since last update.

context.ContextなしでHTTP Requestを送信しているコードを発見する `noctx`

Last updated at Posted at 2020-07-07

はじめに

noctxは context.Context なしでHTTPリクエストを送信しているコードを発見します。

もしあなたがライブラリ開発者であるなら context.Context をHTTPリクエストに渡していないと、ライブラリを利用するユーザはHTTPリクエストのキャンセルやトレース情報の取得ができません。

そのためライブラリの中でHTTPリクエストを送信している場合には noctx を利用することで、そのような問題を防ぐことができます。

特に会社からAPIのGo SDKを公開している場合には、noctxを利用することで多くのユーザが安心して利用することができるようになります。

インストール方法

$ go install github.com/sonatard/noctx/cmd/noctx@latest

使い方

$ go vet -vettool=`which noctx` main.go
./main.go:6:11: net/http.Get must not be called

golangci-lintからの実行

noctx を configで有効にする。

.golangci.yml

# デフォルトのlinterに加えてnoctxを追加する
linters:
  enable:
    - noctx

# もしくはすべてのlinterを有効にして不要なlinterを無効化する

linters:
  enable-all: true
  disable:
   - xxx # 不要なlinter
# configに基づいた実行
$ golangci-lint run

# noctxだけを実行
golangci-lint run --disable-all -E noctx

検出のルール

  • 以下の関数を実行している場合はエラー
    • net/http.Get
    • net/http.Head
    • net/http.Post
    • net/http.PostForm
    • (*net/http.Client).Get
    • (*net/http.Client).Head
    • (*net/http.Client).Post
    • (*net/http.Client).PostForm
  • http.NewRequest 関数から返された http.Request を他の関数に渡しているとエラー

検出した場合の修正方法

  • HTTPリクエストの送信では (*http.Client).Do(*http.Request) メソッドを利用する。
  • Go 1.13以降では、 http.NewRequest 関数の代わりに http.NewRequestWithContext 関数を利用する。
  • Go 1.12以前では、 http.NewRequest のあとに (http.Request).WithContext(ctx) を実行する。

(http.Request).WithContext(ctx)http.Request をコピーして返すためパフォーマンスのデメリットがあります。もしあなたの作成したライブラリがGo1.13以降しかサポートしないのであれば http.NewRequestWithContext を利用してください。

修正のサンプル

もしあなたがライブラリの作者で既にcontextを受け付けない関数を提供してしまっている場合は、別途contextを受け付ける関数を定義して、既存の関数はcontextを受け付ける関数のラッパーにすることをお勧めします。

// 修正前
// HTTPリクエストを送信しているが、contextを受け付けていない
func Send(body io.Reader)  error {
    req,err := http.NewRequest(http.MethodPost, "http://example.com", body)
    if err != nil {
        return nil
    }
    _, err = http.DefaultClient.Do(req)
    if err !=nil{
        return err
    }

    return nil
}
// 修正後
func Send(body io.Reader) error {
    // SendWithContextを呼び出し、contextにはcontext.Background()を渡す
    return SendWithContext(context.Background(), body)
}

// contextを受け付ける
func SendWithContext(ctx context.Context, body io.Reader) error {
    // NewRequestをNewRequestWithContextに変更してcontextを渡す
    req, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://example.com", body)
    if err != nil {
        return nil
    }
    _, err = http.DefaultClient.Do(req)
    if err != nil {
        return err
    }

    return nil
}

検出例

package main

import (
    "context"
    "net/http"
)

func main() {
    const url = "http://example.com"
    http.Get(url) // want `net/http\.Get must not be called`
    http.Head(url)          // want `net/http\.Head must not be called`
    http.Post(url, "", nil) // want `net/http\.Post must not be called`
    http.PostForm(url, nil) // want `net/http\.PostForm must not be called`

    cli := &http.Client{}
    cli.Get(url) // want `\(\*net/http\.Client\)\.Get must not be called`
    cli.Head(url)          // want `\(\*net/http\.Client\)\.Head must not be called`
    cli.Post(url, "", nil) // want `\(\*net/http\.Client\)\.Post must not be called`
    cli.PostForm(url, nil) // want `\(\*net/http\.Client\)\.PostForm must not be called`

    req, _ := http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext`
    cli.Do(req)

    ctx := context.Background()
    req2, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, nil) // OK
    cli.Do(req2)

    req3, _ := http.NewRequest(http.MethodPost, url, nil) // OK
    req3 = req3.WithContext(ctx)
    cli.Do(req3)

    f2 := func(req *http.Request, ctx context.Context) *http.Request {
        return req
    }
    req4, _ := http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext`
    req4 = f2(req4, ctx)
    cli.Do(req4)

    req5, _ := func() (*http.Request, error) {
        return http.NewRequest(http.MethodPost, url, nil) // want `should rewrite http.NewRequestWithContext or add \(\*Request\).WithContext`
    }()
    cli.Do(req5)

}

参照

19
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
13