programing tip

http.ListenAndServe () 중지 방법

itbloger 2020. 11. 30. 07:54
반응형

http.ListenAndServe () 중지 방법


번들 Go http 서버와 함께 Gorilla Web Toolkit의 Mux 라이브러리를 사용하고 있습니다.

문제는 내 응용 프로그램에서 HTTP 서버가 하나의 구성 요소 일 뿐이며 재량에 따라 중지하고 시작해야한다는 것입니다.

내가 http.ListenAndServe(fmt.Sprintf(":%d", service.Port()), service.router)그것을 블록 이라고 부르면 서버가 실행되는 것을 막을 수 없습니다.

나는 이것이 과거에 문제 였다는 것을 알고 있는데, 여전히 그럴까요? 새로운 솔루션이 있습니까?


정상 종료 (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")
}

yo.ian.g의 답변 에서 언급했듯이 . Go 1.8에는 표준 lib에이 기능이 포함되어 있습니다.

에 대한 최소 예 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 :

바탕 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


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)

관련 커밋은 여기 에서 찾을 수 있습니다 .


당신은 건설 할 수 있습니다 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)

이전 답변 중 http.ListenAndServe ()를 사용하면 왜 할 수 없는지 말하지 않았기 때문에 v1.8 http 소스 코드로 들어가서 다음과 같이 말합니다.

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

보시다시피 http.ListenAndServe 함수는 서버 변수를 반환하지 않습니다. 즉, Shutdown 명령을 사용하기 위해 '서버'에 액세스 할 수 없습니다. 따라서 정상적인 종료를 구현하려면이 기능을 사용하는 대신 고유 한 '서버'인스턴스를 만들어야합니다.


컨텍스트를 닫아 서버를 닫을 수 있습니다.

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

이건 어때?

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

응용 프로그램이 서버 일 뿐이고 다른 기능을 수행하지 않는 경우 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)있다면 원하는 경우 호출 대신 해당 솔루션을 여기에 포함시킬 수 있습니다 .

모든 정리 작업을 수행하는 코드는 독자를위한 연습으로 남겨집니다.

Extra credit if you can say where that cleanup code might be most reasonably be placed, for I would not recommend doing it here, and how this endpoint hit should cause the invocation that code.

More extra credit if you can say where that os.exit(0) call (or whatever process exit you choose to use), given here for illustrative purposes only, would be most reasonably placed.

Yet even more extra credit if you can explain why this mechanism of HTTP server process signaling should be considered above all other such mechanisms thought workable in this case.

참고URL : https://stackoverflow.com/questions/39320025/how-to-stop-http-listenandserve

반응형