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.