http.ListenAndServe()を停止する方法


90

バンドルされているGohttpサーバーと一緒にGorillaWebToolkitのMuxライブラリを使用しています。

問題は、私のアプリケーションではHTTPサーバーが1つのコンポーネントにすぎず、自分の裁量で停止および開始する必要があることです。

http.ListenAndServe(fmt.Sprintf(":%d", service.Port()), service.router)それをブロックと呼ぶと、サーバーの実行を停止できないようです。

私はこれが過去に問題であったことを知っています、それはまだそうですか?新しい解決策はありますか?

回答:


92

グレースフルシャットダウン(Go 1.8で導入)に関して、もう少し具体的な例:

package main

import (
    "context"
    "io"
    "log"
    "net/http"
    "sync"
    "time"
)

func startHttpServer(wg *sync.WaitGroup) *http.Server {
    srv := &http.Server{Addr: ":8080"}

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "hello world\n")
    })

    go func() {
        defer wg.Done() // let main know we are done cleaning up

        // always returns error. ErrServerClosed on graceful close
        if err := srv.ListenAndServe(); err != http.ErrServerClosed {
            // unexpected error. port in use?
            log.Fatalf("ListenAndServe(): %v", err)
        }
    }()

    // returning reference so caller can call Shutdown()
    return srv
}

func main() {
    log.Printf("main: starting HTTP server")

    httpServerExitDone := &sync.WaitGroup{}

    httpServerExitDone.Add(1)
    srv := startHttpServer(httpServerExitDone)

    log.Printf("main: serving for 10 seconds")

    time.Sleep(10 * time.Second)

    log.Printf("main: stopping HTTP server")

    // now close the server gracefully ("shutdown")
    // timeout could be given with a proper context
    // (in real world you shouldn't use TODO()).
    if err := srv.Shutdown(context.TODO()); err != nil {
        panic(err) // failure/timeout shutting down the server gracefully
    }

    // wait for goroutine started in startHttpServer() to stop
    httpServerExitDone.Wait()

    log.Printf("main: done. exiting")
}

1
はい、その機能はShutdown()であり、その具体的な使用法をここで示しています。おかげで、もっと明確にすべきだったので、見出しを次のように変更しました。「グレースフルシャットダウン(Go 1.8で導入)に関して、もう少し具体的な例:」
joonas.fi 2017

私がに渡すときnilsrv.Shutdown私は得るpanic: runtime error: invalid memory address or nil pointer dereferencecontext.Todo()代わりにパスが機能します。
Hubro 2018年

1
@Hubroそれは奇妙です、私はこれを最新のGolangバージョン(1.10)で試したところ、うまく動作しました。context.Background()またはcontext.TODO()はもちろん機能しますが、機能する場合は問題ありません。:)
joonas.fi 2018年

1
@ newplayer65これを行うには複数の方法があります。1つの方法は、main()でsync.WaitGroupを作成し、その上でAdd(1)を呼び出し、それへのポインターをstartHttpServer()に渡し、への呼び出しがあるゴルーチンの開始時にdefer waitGroup.Done()を呼び出すことです。 ListenAndServe()。次に、main()の最後でwaitGroup.Wait()を呼び出して、ゴルーチンがジョブを終了するのを待ちます。
joonas.fi 2018

1
@ newplayer65私はあなたのコードを見ました。チャネルを使用することは、私の提案よりも良い、おそらくより良いオプションです。私のコードは主にShutdown()をデモンストレーションするためのものでした-プロダクション品質のコードを紹介するものではありません:)プロジェクトの「servergopher」ロゴは崇高です!:D
joonas.fi 2018

70

yo.ian.gの答えで述べたように。Go 1.8には、この機能が標準ライブラリに含まれています。

の最小例Go 1.8+

    server := &http.Server{Addr: ":8080", Handler: handler}

    go func() {
        if err := server.ListenAndServe(); err != nil {
            // handle err
        }
    }()

    // Setting up signal capturing
    stop := make(chan os.Signal, 1)
    signal.Notify(stop, os.Interrupt)

    // Waiting for SIGINT (pkill -2)
    <-stop

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    if err := server.Shutdown(ctx); err != nil {
        // handle err
    }

    // Wait for ListenAndServe goroutine to close.

元の回答-PreGo 1.8:

Uvelichitelの答えに基づいて構築します。

ListenAndServeを返し、io.Closerブロックしない独自のバージョンを作成できます。

func ListenAndServeWithClose(addr string, handler http.Handler) (io.Closer,error) {

    var (
        listener  net.Listener
        srvCloser io.Closer
        err       error
    )

    srv := &http.Server{Addr: addr, Handler: handler}

    if addr == "" {
        addr = ":http"
    }

    listener, err = net.Listen("tcp", addr)
    if err != nil {
        return nil, err
    }

    go func() {
        err := srv.Serve(tcpKeepAliveListener{listener.(*net.TCPListener)})
        if err != nil {
            log.Println("HTTP Server Error - ", err)
        }
    }()

    srvCloser = listener
    return srvCloser, nil
}

完全なコードはこちらから入手できます

HTTPサーバーはエラーで閉じます accept tcp [::]:8080: use of closed network connection


私はあなたのために定型を行い、パッケージ作成github.com/pseidemann/finish
pseidemann

24

Go 1.8には、とを介しServer::Shutdown(context.Context)Server::Close()それぞれ利用可能な、正常なシャットダウンと強制的なシャットダウンが含まれます。

go func() {
    httpError := srv.ListenAndServe(address, handler)
    if httpError != nil {
        log.Println("While serving HTTP: ", httpError)
    }
}()

srv.Shutdown(context)

関連するコミットはここにあります


7
気難しいことをお詫び申し上げます。あなたのコードは純粋に使用例でしたが、原則として:にgo func() { X() }()続くと、前に実行されるY()リーダーに対して誤った仮定が行われます。ウェイトグループなどは、このようなタイミングのバグが予期しないときにあなたを噛まないようにします!X()Y()
colm.anseo 2018

20

あなたは構築することができます net.Listener

l, err := net.Listen("tcp", fmt.Sprintf(":%d", service.Port()))
if err != nil {
    log.Fatal(err)
}

あなたができる Close()

go func(){
    //...
    l.Close()
}()

そしてそのhttp.Serve()上に

http.Serve(l, service.router)

1
ありがとう、しかしそれは私の質問に答えません。私はhttp.ListenAndServe特定の理由で質問しています。それはどのように私はGWT MUXライブラリを使用して、私はその..ためnet.listen使用するかどうかはわかりませんです
ジム・

6
http.ListenAndServe()の代わりにhttp.Serve()を使用し、独自のリスナーを使用するだけで同じ構文を使用します。http.Serve(net.Listener、gorilla.mux.Router)
Uvelichitel 2016

ああ、ありがとう。私はまだテストしていませんが、動作するはずです。
ジム2016

1
少し遅れましたが、このユースケースではマナーパッケージを使用しています。これは、正常なシャットダウンを可能にする標準のhttpパッケージのドロップイン代替品です(つまり、新しい要求を拒否しながらすべてのアクティブな要求を終了してから終了します)。
Kaedys 2016

13

これまでの回答のいずれも、http.ListenAndServe()を使用した場合にそれを実行できない理由を示していないため、v1.8 httpソースコードを調べました。

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

ご覧のとおり、http.ListenAndServe関数はサーバー変数を返しません。これは、「サーバー」にアクセスしてシャットダウンコマンドを使用できないことを意味します。したがって、グレースフルシャットダウンを実装するには、この関数を使用する代わりに、独自の「サーバー」インスタンスを作成する必要があります。


2

サーバーのコンテキストを閉じることで、サーバーを閉じることができます。

type ServeReqs func(ctx context.Context, cfg Config, deps ReqHandlersDependencies) error

var ServeReqsImpl = func(ctx context.Context, cfg Config, deps ReqHandlersDependencies) error {
    http.Handle(pingRoute, decorateHttpRes(pingHandlerImpl(deps.pingRouteResponseMessage), addJsonHeader()))

    server := &http.Server{Addr: fmt.Sprintf(":%d", cfg.port), Handler: nil}

    go func() {
        <-ctx.Done()
        fmt.Println("Shutting down the HTTP server...")
        server.Shutdown(ctx)
    }()

    err := server.ListenAndServeTLS(
        cfg.certificatePemFilePath,
        cfg.certificatePemPrivKeyFilePath,
    )

    // Shutting down the server is not something bad ffs Go...
    if err == http.ErrServerClosed {
        return nil
    }

    return err
}

そして、あなたがそれを閉じる準備ができているときはいつでも、電話してください:

ctx, closeServer := context.WithCancel(context.Background())
err := ServeReqs(ctx, etc)
closeServer()

「サーバーのシャットダウンは悪いことではありません...」:)
Paul Knopf

注目に値することの1つは、正常なシャットダウンを行うには、終了する前に、シャットダウンが戻るのを待つ必要があるということです。これは、ここでは発生していないようです。
MarcinBilski19年

ご利用ctxにはserver.Shutdown正しくありません。コンテキストはすでにキャンセルされているため、正常にシャットダウンされませんserver.Close汚れたシャットダウンを要求したかもしれません。(クリーンシャットダウンのために、このコードでは、広範囲に再加工する必要があるだろう。
デイブCに

0

を使用しcontext.Contextてこれを解決することができnet.ListenConfigます。私の場合、sync.WaitGroupまたはhttp.ServerShutdown()呼び出しを使用したくなく、代わりにcontext.Context(シグナルで閉じられた)。

import (
  "context"
  "http"
  "net"
  "net/http/pprof"
)

func myListen(ctx context.Context, cancel context.CancelFunc) error {
  lc := net.ListenConfig{}
  ln, err := lc.Listen(ctx, "tcp4", "127.0.0.1:6060")
  if err != nil {
    // wrap the err or log why the listen failed
    return err
  }

  mux := http.NewServeMux()
  mux.Handle("/debug/pprof/", pprof.Index)
  mux.Handle("/debug/pprof/cmdline", pprof.CmdLine)
  mux.Handle("/debug/pprof/profile", pprof.Profile)
  mux.Handle("/debug/pprof/symbol", pprof.Symbol)
  mux.Handle("/debug/pprof/trace", pprof.Trace)

  go func() {
    if err := http.Serve(l, mux); err != nil {
      cancel()
      // log why we shut down the context
      return err
    }
  }()

  // If you want something semi-synchronous, sleep here for a fraction of a second

  return nil
}

-6

アプリケーションが単なるサーバーであり、他の機能を実行しないような場合に私が行ったことは、のhttp.HandleFuncようなパターンのforをインストールすることです/shutdown。何かのようなもの

http.HandleFunc("/shutdown", func(w http.ResponseWriter, r *http.Request) {
    if <credentials check passes> {
        // - Turn on mechanism to reject incoming requests.
        // - Block until "in-flight" requests complete.
        // - Release resources, both internal and external.
        // - Perform all other cleanup procedures thought necessary
        //   for this to be called a "graceful shutdown".
        fmt.Fprint(w, "Goodbye!\n")
        os.Exit(0)
    }
})

1.8は必要ありません。しかし、1.8が利用可能であれば、os.Exit(0)必要に応じて、呼び出しの代わりにそのソリューションをここに埋め込むことができると私は信じています。

そのクリーンアップ作業をすべて実行するためのコードは、読者の練習問題として残されています。

そのクリーンアップコードが最も合理的に配置される可能性がある場所、およびこのエンドポイントヒットがそのコードの呼び出しを引き起こす方法をここで行うことはお勧めしません。

os.exit(0)ここでは説明のみを目的として、その呼び出し(または使用することを選択したプロセス出口)がどこに配置されるかを言うことができれば、より多くの追加のクレジットが最も合理的に配置されます。

HTTPサーバープロセスシグナリングのこのメカニズムが、この場合に実行可能であると考えられる他のすべてのそのようなメカニズムよりも考慮されるべきである理由を説明できれば、さらに多くのクレジットがあります。


もちろん、私は、問題の性質についてそれ以上の仮定なしに、特に、特定の実稼働環境についての仮定なしで、尋ねられた質問に答えました。しかし、私自身の啓蒙活動である@MarcinBilskiの場合、このソリューションを環境や本番環境などに適さないものにする要件は何でしょうか。
greg.carter

2
本番アプリに/ shutdownハンドラーがないことは明らかなので、何よりも冗談めかして意味します。:)私が推測する内部ツールには何でも行きます。それ以外に、それは突然など、ディスクへの書き込み中に、悪化し、データベーストランザクションを通じて接続またはクラッシュ途中で落としたりしないように優雅にサーバーをシャットダウンする方法があります
マルチンBilski事件

確かに、反対票を投じた人が想像を絶するということはあり得ません。想像力が強すぎるのではないでしょうか。エラーを修正するために、応答(例を含む)を更新しました。
greg.carter
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.