dongmu3187
dongmu3187
采纳率0%
2017-11-23 06:08

返回响应后关闭HTTP服务器

  • httpserver
已采纳

I'm in the process of building a little command line based Go bot that interacts with the Instagram API.

The Instagram API is OAuth based, and so not overly great for command line based apps.

To get around this, I am opening the appropriate authorization URL in the browser and using a local server I spin up for the redirect URI - this way I can capture and gracefully show the access token as opposed to the user needing to get this from the URL manually.

So far so good, the application can successfully open the browser to the authorisation URL, you authorise it and it redirects you to the local HTTP server.

Now, I have no need for the HTTP server after the access token has been displayed to the user and so I am wanting to manually shut the server down after doing this.

To do this, I drew inspiration from this answer and drummed up the below:

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

However, the above causes this error:

2017/11/23 16:02:03 Httpserver: ListenAndServe() error: http: Server closed

2017/11/23 16:02:03 http: panic serving [::1]:61793: runtime error: invalid memory address or nil pointer dereference

If I comment out these lines in the handler then it works flawlessly, albeit without shutting down the server when I hit the callback route:

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

Where am I going wrong? What do I need to change so that I can shut the server down when I hit the callback route, after displaying the text to the user.

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

2条回答

  • dsavz66262 dsavz66262 4年前

    Use:

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

    Try this:

    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
    ")
        })
        http.HandleFunc("/quit", func(w http.ResponseWriter, r *http.Request) {
            io.WriteString(w, "Bye
    ")
            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.")
    }
    

    And see: https://blog.golang.org/context

    点赞 评论 复制链接分享
  • douruyun8153 douruyun8153 4年前

    Shutdown function accepts parameter ctx context.Context. Try to pass it an empty context.

    ctx := context.Background()
    

    Also:

    When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS immediately return ErrServerClosed. Make sure the program doesn't exit and waits instead for Shutdown to return.

    点赞 评论 复制链接分享