Http.ListenAndServe () nasıl durdurulur


91

Gorilla Web Toolkit'teki Mux kitaplığını, birlikte verilen Go http sunucusuyla birlikte kullanıyorum.

Sorun şu ki, benim uygulamamda HTTP sunucusu sadece bir bileşen ve benim takdirime göre durup başlaması gerekiyor.

Ben çağırdığınızda http.ListenAndServe(fmt.Sprintf(":%d", service.Port()), service.router)onu blokları ve çalışmasını sunucuyu durdurmak için görünmüyor olabilir.

Bunun geçmişte bir sorun olduğunun farkındayım, hala öyle mi? Yeni çözümler var mı?

Yanıtlar:


92

Zarif kapatmayla ilgili olarak (Go 1.8'de tanıtıldı), biraz daha somut bir örnek:

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
Evet, özellik, somut kullanımını burada gösterdiğim Shutdown (). Teşekkürler, daha net olmalıydım, şimdi başlığı şu şekilde değiştirdim: "Zarif kapatmayla ilgili olarak (Go 1.8'de tanıtıldı), biraz daha somut bir örnek:"
joonas.fi

Ben geçtikten sonra nilhiç srv.Shutdownalıyorum panic: runtime error: invalid memory address or nil pointer dereference. context.Todo()Bunun yerine geçmek işe yarar .
Hubro

1
@Hubro bu garip, bunu en son Golang sürümünde (1.10) denedim ve iyi gitti. context.Background () veya context.TODO () elbette çalışıyor ve eğer sizin için çalışıyorsa, iyi. :)
joonas.fi

1
@ newplayer65 Bunu yapmanın birden çok yolu var. Bunun bir yolu, main () 'de sync.WaitGroup oluşturmak, üzerinde Add (1)' i çağırmak ve startHttpServer () 'a bir işaretçi iletmek ve ListenAndServe (). sonra main () 'in sonunda waitGroup.Wait ()' i çağırarak gorutinin işini bitirmesini bekleyin.
joonas.fi

1
@ newplayer65 Kodunuza baktım. Kanal kullanmak iyi, muhtemelen benim önerimden daha iyi bir seçenektir. Kodum esas olarak Shutdown () göstermekti - üretim kalitesi kodunu sergilemek değil :) Ps projenizin "server gopher" logosu adorbs! : D
joonas.fi

71

Cevabında belirtildiği gibi yo.ian.g. Go 1.8 bu işlevselliği standart kitaplığa dahil etmiştir.

Aşağıdakiler için asgari örnek 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.

Orijinal Cevap - Ön Git 1.8:

Bina Uvelichitel en cevabı.

Bir ListenAndServedöndüren io.Closerve engellemeyen kendi sürümünüzü oluşturabilirsiniz .

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
}

Tam kod burada mevcuttur .

HTTP Sunucusu hata vererek kapanacak accept tcp [::]:8080: use of closed network connection


Sizin için standart hazırlayan bir paket oluşturdum github.com/pseidemann/finish
pseidemann

24

Git 1.8, sırasıyla Server::Shutdown(context.Context)ve üzerinden erişilebilen zarif ve zorla kapatmayı içerecektir Server::Close().

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

srv.Shutdown(context)

İlgili taahhüt burada bulunabilir


7
seçici olduğum için özür dilerim ve kodunuzun tamamen örnek bir kullanım olduğunu biliyorum, ancak genel bir kural olarak: go func() { X() }()ardından Y()gelen, okuyucuya X()daha önce çalıştıracak yanlış varsayımda bulunur Y(). Bekleme grupları vb. Bunun gibi zamanlama hatalarının sizi beklenmedik zamanda ısırmamasını sağlar!
colm.anseo

20

İnşa edebilirsin net.Listener

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

hangisini yapabilirsin Close()

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

ve http.Serve()üzerinde

http.Serve(l, service.router)

1
teşekkürler ama bu soruma cevap vermiyor. http.ListenAndServeBelirli nedenlerle soruyorum . GWT MUX kitaplığını böyle kullanıyorum, bunun için net.listen'i nasıl kullanacağımdan emin değilim ..
jim

6
Aynı sözdizimiyle http.ListenAndServe () yerine http.Serve () 'yi sadece kendi Listener ile aynı şekilde kullanırsınız. http.Serve (net.Listener, gorilla.mux.Router)
Uvelichitel

Ah harika, teşekkürler. Henüz test etmedim ama çalışmalıyım.
jim

1
Biraz geç ama bu kullanım durumu için görgü paketini kullanıyoruz. Bu, standart http paketinin, zarif bir şekilde kapatmaya izin veren (yani, yenilerini reddederken tüm aktif istekleri bitirir ve sonra çıkar) yerine getirilen bir yerine geçer.
Kaedys

13

Önceki cevapların hiçbiri http.ListenAndServe () kullanırsanız neden yapamayacağınızı söylemediğinden, v1.8 http kaynak koduna girdim ve işte şöyle diyor:

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

Gördüğünüz gibi http.ListenAndServe işlevi sunucu değişkenini döndürmüyor. Bu, Kapat komutunu kullanmak için 'sunucuya' ulaşamayacağınız anlamına gelir. Bu nedenle, nazikçe kapatmanın uygulanabilmesi için bu işlevi kullanmak yerine kendi 'sunucu' örneğinizi oluşturmanız gerekir.


2

Sunucuyu bağlamını kapatarak kapatabilirsiniz.

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
}

Ve kapatmaya hazır olduğunuzda şu numarayı arayın:

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

"Sunucuyu kapatmak kötü bir şey değil ffs Go ..." :)
Paul Knopf

Kayda değer bir şey, zarif bir kapatma için, çıkmadan önce Kapatma'nın geri dönmesini beklemeniz gerektiğidir, bu burada gerçekleşmiyor gibi görünüyor.
Marcin Bilski

Kullanımınız ctxiçin server.Shutdownyanlıştır. İçerik zaten iptal edildi, bu nedenle temiz bir şekilde kapatılmayacak . server.CloseTemiz olmayan bir kapatma çağrısında bulunmuş olabilirsiniz . (Temiz bir kapatma için bu kodun kapsamlı bir şekilde yeniden çalışılması gerekir.
Dave C

0

Bunu a context.Contextkullanarak çözmek mümkündür net.ListenConfig. Benim durumumda, a sync.WaitGroupveya http.Server's Shutdown()çağrısını kullanmak istemedim ve bunun yerine a'ya güveniyorum context.Context(bir sinyalle kapatıldı).

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

Uygulamanın sadece sunucu olduğu ve başka hiçbir işlevi gerçekleştirmediği bu tür durumlar için yaptığım şey http.HandleFunc, bir kalıp için yükleme yapmaktır /shutdown. Gibi bir şey

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 gerektirmez. Ancak 1.8 mevcutsa, os.Exit(0)arzu edilirse bu çözüm arama yerine buraya yerleştirilebilir , inanıyorum.

Tüm bu temizleme çalışmasını gerçekleştirecek kod, okuyucu için bir alıştırma olarak bırakılmıştır.

Bu temizleme kodunun en makul şekilde nereye yerleştirilebileceğini söyleyebiliyorsanız fazladan kredi, çünkü burada yapmayı ve bu uç nokta isabetinin bu kodu çağırmaya nasıl neden olması gerektiğini önermem.

Bu os.exit(0)aramanın (veya kullanmayı seçtiğiniz herhangi bir işlem çıkışının) nerede olduğunu söyleyebilirseniz , burada yalnızca açıklama amacıyla verilen daha fazla kredi en makul şekilde yerleştirilecektir.

Yine de, bu HTTP sunucu işlem sinyalizasyon mekanizmasının bu durumda işe yarayacağı düşünülen tüm diğer mekanizmaların üzerinde değerlendirilmesi gerektiğini açıklayabilirseniz daha da fazla itibar edersiniz .


Tabii ki, sorulan soruyu sorunun doğası hakkında daha fazla varsayımda bulunmadan ve özellikle de herhangi bir üretim ortamı hakkında varsayımda bulunmadan yanıtladım. Ancak kendi editörlüğüm için @MarcinBilski, tam olarak hangi gereksinimler bu çözümü herhangi bir çevre, üretim veya başka türlü için uygun kılmaz?
greg.carter

2
Bu, her şeyden daha fazla yanak anlamına geliyordu çünkü bir üretim uygulamasında / kapatma işleyicinizin olmayacağı açık. :) Her şey dahili takım için geçerli sanırım. Bunun yanı sıra, bir veritabanı işlemi sırasında veya daha kötüsü diske yazarken vb. Aniden bağlantıları kesmemesi veya çökmemesi için sunucuyu zarif bir şekilde kapatmanın yolları vardır.
Marcin Bilski

Elbette, aşağı seçmenlerin hayal gücünden yoksun olmaları söz konusu olamaz. Çok fazla hayal gücü varsayıyor olmalıyım. Hatamı düzeltmek için yanıtı (örnek dahil) güncelledim.
greg.carter
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.