Como parar http.ListenAndServe ()

91

Estou usando a biblioteca Mux do Gorilla Web Toolkit junto com o servidor Go http incluído.

O problema é que em meu aplicativo o servidor HTTP é apenas um componente e é necessário parar e iniciar conforme meu critério.

Quando eu chamo, http.ListenAndServe(fmt.Sprintf(":%d", service.Port()), service.router)ele bloqueia e não consigo parar a execução do servidor.

Sei que isso foi um problema no passado, ainda é o caso? Existem novas soluções?

Jim
fonte

Respostas:

92

Com relação ao desligamento normal (introduzido no Go 1.8), um exemplo um pouco mais concreto:

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")
}
joonas.fi
fonte
1
Sim, o recurso é Shutdown (), cujo uso concreto estou demonstrando aqui. Obrigado, eu deveria ter sido mais claro, mudei o título para este agora: "Com relação ao desligamento
normal
Quando eu passo nilpara srv.Shutdowneu consigo panic: runtime error: invalid memory address or nil pointer dereference. Em context.Todo()vez disso, passar funciona.
Hubro
1
@Hubro que estranho, acabei de experimentar na última versão do Golang (1.10) e funcionou bem. context.Background () ou context.TODO () é claro que funciona e, se funcionar para você, ótimo. :)
joonas.fi
1
@ newplayer65 há várias maneiras de fazer isso. Uma maneira poderia ser criar sync.WaitGroup em main (), chamar Add (1) nele e passar um ponteiro para startHttpServer () e chamar adiar waitGroup.Done () no início do goroutine que tem uma chamada para o ListenAndServe (). em seguida, basta chamar waitGroup.Wait () no final de main () para esperar que a goroutine termine seu trabalho.
joonas.fi
1
@ newplayer65 Eu olhei seu código. Usar um canal é uma opção boa, provavelmente melhor do que a minha sugestão. Meu código era principalmente para demonstrar Shutdown () - não mostrar código de qualidade de produção :) Ps o logotipo "server gopher" do seu projeto é adorável! : D
joonas.fi
73

Conforme mencionado na yo.ian.gresposta de. Go 1.8 incluiu esta funcionalidade na biblioteca padrão.

Exemplo mínimo para 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.

Resposta original - Pre Go 1.8:

Com base na resposta de Uvelichitel .

Você pode criar sua própria versão do ListenAndServeque retorna um io.Closere não bloqueia.

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
}

Código completo disponível aqui .

O servidor HTTP fechará com o erro accept tcp [::]:8080: use of closed network connection

John S Perayil
fonte
Eu criei um pacote que faz o boilerplate para você github.com/pseidemann/finish
pseidemann
24

O Go 1.8 incluirá desligamento normal e forçado, disponível via Server::Shutdown(context.Context)e Server::Close()respectivamente.

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

srv.Shutdown(context)

O commit relevante pode ser encontrado aqui

yo.ian.g
fonte
7
desculpe ser exigente, e eu sei que seu código era puramente de exemplo de uso, mas como regra geral: go func() { X() }()seguido por Y()faz a falsa suposição para o leitor que X()irá executar antes Y(). Waitgroups etc. garantem que bugs de tempo como este não o mordam quando menos se espera!
colm.anseo
20

Você pode construir net.Listener

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

qual você pode Close()

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

e http.Serve()sobre isso

http.Serve(l, service.router)
Uvelichitel
fonte
1
obrigado, mas isso não responde à minha pergunta. Estou perguntando sobre http.ListenAndServepor motivos específicos. É assim que eu uso a biblioteca GWT MUX, não tenho certeza de como usar net.listen para isso ..
Jim
6
Use http.Serve () em vez de http.ListenAndServe () exatamente da mesma maneira, com a mesma sintaxe, apenas com o próprio Listener. http.Serve (net.Listener, gorilla.mux.Router)
Uvelichitel
Ah ótimo, obrigado. Ainda não testei, mas deve funcionar.
Jim
1
Um pouco tarde, mas estamos usando o pacote de boas maneiras para este caso de uso. É um substituto imediato para o pacote http padrão que permite o desligamento normal (ou seja, conclui todas as solicitações ativas enquanto recusa as novas e, em seguida, sai).
Kaedys
13

Como nenhuma das respostas anteriores diz por que você não pode fazer isso se usar http.ListenAndServe (), fui para o código-fonte http v1.8 e aqui está o que diz:

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

Como você pode ver, a função http.ListenAndServe não retorna a variável do servidor. Isso significa que você não pode acessar o 'servidor' para usar o comando Desligar. Portanto, você precisa criar sua própria instância de 'servidor' em vez de usar esta função para que o desligamento normal seja implementado.

juz
fonte
2

Você pode fechar o servidor fechando seu contexto.

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
}

E quando você estiver pronto para fechá-lo, ligue:

ctx, closeServer := context.WithCancel(context.Background())
err := ServeReqs(ctx, etc)
closeServer()
Lukas Lukac
fonte
"Desligar o servidor não é algo ruim ffs Go ..." :)
Paul Knopf
Uma coisa que vale a pena notar é que, para um desligamento normal, antes de sair, você precisa aguardar o retorno do desligamento, o que parece não estar acontecendo aqui.
Marcin Bilski
O uso de ctxpara server.Shutdownestá incorreto. O contexto já foi cancelado, portanto, não será encerrado corretamente. Você bem poderia ter pedido server.Closeum desligamento impuro. (Para um desligamento limpo, este código precisaria ser amplamente retrabalhado.
Dave C
0

É possível resolver isso context.Contextusando um net.ListenConfig. No meu caso, eu não queria usar um sync.WaitGroupou http.Serverde Shutdown()chamada, e em vez disso dependem de um context.Context(que foi fechado com um sinal).

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
}
Sean
fonte
-7

O que fiz para esses casos em que o aplicativo é apenas o servidor e não realizando nenhuma outra função foi instalar um http.HandleFuncpara um padrão como /shutdown. Algo como

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)
    }
})

Não requer 1.8. Mas se 1.8 estiver disponível, então essa solução pode ser incorporada aqui em vez da os.Exit(0)chamada, se desejável, acredito.

O código para realizar todo esse trabalho de limpeza é deixado como um exercício para o leitor.

Crédito extra se você puder dizer onde esse código de limpeza pode ser colocado de maneira mais razoável, pois eu não recomendaria fazê-lo aqui e como essa ocorrência de endpoint deve causar a invocação desse código.

Mais crédito extra se você puder dizer onde essa os.exit(0)chamada (ou qualquer saída de processo que você escolher usar), fornecida aqui apenas para fins ilustrativos, seria mais razoavelmente colocada.

Ainda mais crédito extra se você puder explicar por que esse mecanismo de sinalização de processo do servidor HTTP deve ser considerado acima de todos os outros mecanismos considerados viáveis ​​neste caso.

greg.carter
fonte
Claro, respondi à pergunta conforme perguntado, sem quaisquer suposições adicionais sobre a natureza do problema e, em particular, nenhuma suposição sobre qualquer ambiente de produção. Mas para minha própria edificação, @MarcinBilski, exatamente quais requisitos tornariam essa solução não adequada para qualquer ambiente, produção ou outro?
greg.carter
2
Significou mais irônico do que qualquer coisa porque está claro que você não teria um manipulador / shutdown em um aplicativo de produção. :) Vale tudo para ferramentas internas, eu acho. Deixando isso de lado, existem maneiras de desligar o servidor normalmente para que ele não interrompa as conexões repentinamente ou trave no meio de uma transação de banco de dados ou, pior, ao gravar no disco etc.
Marcin Bilski
Certamente, não pode ser o caso de os eleitores em baixa não terem imaginação. Deve ser que eu suponho muita imaginação. Eu atualizei a resposta (incluindo o exemplo) para corrigir meu erro.
greg.carter