1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
|
package webapp
import (
"context"
"fmt"
"io"
"net"
"net/http"
)
// CodeResponse represents the code received by the local server's callback handler.
type CodeResponse struct {
Code string
State string
}
// bindLocalServer initializes a LocalServer that will listen on a randomly available TCP port.
func bindLocalServer() (*localServer, error) {
listener, err := net.Listen("tcp4", "127.0.0.1:0")
if err != nil {
return nil, err
}
return &localServer{
listener: listener,
resultChan: make(chan CodeResponse, 1),
}, nil
}
type localServer struct {
CallbackPath string
WriteSuccessHTML func(w io.Writer)
resultChan chan (CodeResponse)
listener net.Listener
}
func (s *localServer) Port() int {
return s.listener.Addr().(*net.TCPAddr).Port
}
func (s *localServer) Close() error {
return s.listener.Close()
}
func (s *localServer) Serve() error {
return http.Serve(s.listener, s)
}
func (s *localServer) WaitForCode(ctx context.Context) (CodeResponse, error) {
select {
case <-ctx.Done():
return CodeResponse{}, ctx.Err()
case code := <-s.resultChan:
return code, nil
}
}
// ServeHTTP implements http.Handler.
func (s *localServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if s.CallbackPath != "" && r.URL.Path != s.CallbackPath {
w.WriteHeader(404)
return
}
defer func() {
_ = s.Close()
}()
params := r.URL.Query()
s.resultChan <- CodeResponse{
Code: params.Get("code"),
State: params.Get("state"),
}
w.Header().Add("content-type", "text/html")
if s.WriteSuccessHTML != nil {
s.WriteSuccessHTML(w)
} else {
defaultSuccessHTML(w)
}
}
func defaultSuccessHTML(w io.Writer) {
fmt.Fprintf(w, "<p>You may now close this page and return to the client app.</p>")
}
|