Подтвердить что ты не робот

Как остановить http.ListenAndServe()

Я использую библиотеку Mux из Gorilla Web Toolkit вместе со встроенным http-сервером Go.

Проблема в том, что в моем приложении HTTP-сервер является только одним компонентом, и его необходимо остановить и запустить по своему усмотрению.

Когда я вызываю http.ListenAndServe(fmt.Sprintf(":%d", service.Port()), service.router), он блокируется, и я не могу остановить работу сервера.

Я знаю, что это было проблемой в прошлом, это все еще так? Есть ли новые решения?

4b9b3361

Ответ 1

Что касается постепенного завершения работы (введено в Go 1.8), приведу более конкретный пример:

package main

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

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

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

    go func() {
        // returns ErrServerClosed on graceful close
        if err := srv.ListenAndServe(); err != http.ErrServerClosed {
            // NOTE: there is a chance that next line won't have time to run,
            // as main() doesn't wait for this goroutine to stop. don't use
            // code with race conditions like these for production. see post
            // comments below on more discussion on how to handle this.
            log.Fatalf("ListenAndServe(): %s", err)
        }
    }()

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

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

    srv := startHttpServer()

    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
    }

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

Ответ 2

Как упомянуто в ответе yo.ian.g. Go 1.8 включил эту функциональность в стандартную библиотеку.

Минимальный пример для for 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
    }

Оригинальный ответ - Pre Go 1.8:

Опираясь на увеличитель ответа.

Вы можете создать свою собственную версию 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

Ответ 3

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)

Соответствующая фиксация может быть найдена здесь

Ответ 4

Вы можете построить 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)

Ответ 5

Поскольку ни один из предыдущих ответов не говорит, почему вы не можете этого сделать, если используете http.ListenAndServe(), я попал в исходный код http-версии v1.8 и вот что он говорит:

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

Как вы видите, функция http.ListenAndServe не возвращает переменную сервера. Это означает, что вы не можете перейти на "сервер", чтобы использовать команду "Завершение". Поэтому вам нужно создать свой собственный "серверный" экземпляр вместо использования этой функции для корректного завершения работы.

Ответ 6

Вы можете закрыть сервер, закрыв его контекст.

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

Ответ 7

ребята, как насчет этого

package gracefull_shutdown_server

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "time"
)

func startHttpServer() *http.Server {
    mux := http.NewServeMux()
    mux.HandleFunc("/", defaultRoute)
    srv := &http.Server{
        Addr:    ":8080",
        Handler: mux,
    }
    go func() {
        if err := srv.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatalf("ListenAndServe(): %s", err)
        }

    }()
    return srv
}

func defaultRoute(w http.ResponseWriter, r *http.Request) {
    time.Sleep(time.Second * 30)
    w.Write([]byte("it working"))
}

func MainStartHttpServer() {
    srv := startHttpServer()
    stop := make(chan os.Signal)
    signal.Notify(stop, os.Interrupt)
    select {
    case <-stop:
        fmt.Println("server going to shut down")
        ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
        defer cancel()
        err := srv.Shutdown(ctx)
        if err != nil {
            fmt.Println(err)
        }
    }
}

Ответ 8

То, что я сделал для таких случаев, когда приложение является просто сервером и не выполняет никакой другой функции, это установил http.HandleFunc для шаблона типа /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-сервера следует рассматривать выше всех других подобных механизмов, которые в этом случае считаются работоспособными, это еще больше.