返回响应后关闭HTTP服务器

时间:2017-11-23 06:08:20

标签: go httpserver

我正在构建一个基于Go bot的小命令行,与Instagram API交互。

Instagram API基于OAuth,因此对于基于命令行的应用程序来说并不算太好。

为了解决这个问题,我在浏览器中打开了相应的授权URL并使用本地服务器启动了重定向URI - 这样我就可以捕获并优雅地显示访问令牌而不是需要获取的用户这是来自URL的手动。

到目前为止,应用程序可以成功地将浏览器打开到授权URL,您对其进行授权并将其重定向到本地HTTP服务器。

现在,在向用户显示访问令牌后我不需要HTTP服务器,因此我想在执行此操作后手动关闭服务器。

为了做到这一点,我从这个answer中汲取灵感并鼓励下面的事情:

package main

import (
    "fmt"
    "io"
    "log"
    "net/http"
    "os/exec"
    "runtime"
    "time"
)

var client_id = "my_client_id"
var client_secret = "my_client_secret"
var redirect_url = "http://localhost:8000/instagram/callback"

func main() {

    srv := startHttpServer()

    openbrowser(fmt.Sprintf("https://api.instagram.com/oauth/authorize/?client_id=%v&redirect_uri=%v&response_type=code", client_id, redirect_url))

    // Backup to gracefully shutdown the server
    time.Sleep(20 * time.Second)
    if err := srv.Shutdown(nil); err != nil {
        panic(err) // failure/timeout shutting down the server gracefully
    }
}

func showTokenToUser(w http.ResponseWriter, r *http.Request, srv *http.Server) {
    io.WriteString(w, fmt.Sprintf("Your access token is: %v", r.URL.Query().Get("code")))
    if err := srv.Shutdown(nil); err != nil {
        log.Fatal(err) // failure/timeout shutting down the server gracefully
    }
}

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

    http.HandleFunc("/instagram/callback", func(w http.ResponseWriter, r *http.Request) {
        showTokenToUser(w, r, srv)
    })

    go func() {
        if err := srv.ListenAndServe(); err != nil {
            // cannot panic, because this probably is an intentional close
            log.Printf("Httpserver: ListenAndServe() error: %s", err)
        }
    }()

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

func openbrowser(url string) {
    var err error

    switch runtime.GOOS {
    case "linux":
        err = exec.Command("xdg-open", url).Start()
    case "windows":
        err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
    case "darwin":
        err = exec.Command("open", url).Start()
    default:
        err = fmt.Errorf("unsupported platform")
    }
    if err != nil {
        log.Fatal(err)
    }
}

但是,上述原因导致此错误:

  

2017/11/23 16:02:03 Httpserver:ListenAndServe()错误:http:服务器关闭

     

2017/11/23 16:02:03 http:panic serving [:: 1]:61793:运行时错误:无效的内存地址或nil指针取消引用

如果我在处理程序中注释掉这些行,那么它可以完美地工作,虽然在我点击回调路径时没有关闭服务器:

if err := srv.Shutdown(nil); err != nil {
    log.Fatal(err) // failure/timeout shutting down the server gracefully
}

我哪里错了?我需要更改什么才能在向用户显示文本后点击回调路径时关闭服务器。

2 个答案:

答案 0 :(得分:5)

使用:

ctx, cancel := context.WithCancel(context.Background())
err := srv.Shutdown(ctx);

试试这个:

package main

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

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "Hi\n")
    })
    http.HandleFunc("/quit", func(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "Bye\n")
        cancel()
    })
    srv := &http.Server{Addr: ":8080"}
    go func() {
        if err := srv.ListenAndServe(); err != nil {
            log.Printf("Httpserver: ListenAndServe() error: %s", err)
        }
    }()
    <-ctx.Done()
    if err := srv.Shutdown(ctx); err != nil && err != context.Canceled {
        log.Println(err)
    }
    log.Println("done.")
}

请参阅:https://blog.golang.org/context

答案 1 :(得分:1)

Shutdown函数接受参数ctx context.Context。尝试将其传递给空的上下文。

ctx := context.Background()

此外:

  

当调用Shutdown时,Serve,ListenAndServe和ListenAndServeTLS会立即返回ErrServerClosed。确保程序没有退出并等待Shutdown返回。