You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
132 lines
3.0 KiB
132 lines
3.0 KiB
// Copyright 2014 The Go Authors. All rights reserved. |
|
// Use of this source code is governed by a BSD-style |
|
// license that can be found in the LICENSE file. |
|
|
|
package oauth2 |
|
|
|
import ( |
|
"errors" |
|
"io" |
|
"net/http" |
|
"sync" |
|
) |
|
|
|
// Transport is an http.RoundTripper that makes OAuth 2.0 HTTP requests, |
|
// wrapping a base RoundTripper and adding an Authorization header |
|
// with a token from the supplied Sources. |
|
// |
|
// Transport is a low-level mechanism. Most code will use the |
|
// higher-level Config.Client method instead. |
|
type Transport struct { |
|
// Source supplies the token to add to outgoing requests' |
|
// Authorization headers. |
|
Source TokenSource |
|
|
|
// Base is the base RoundTripper used to make HTTP requests. |
|
// If nil, http.DefaultTransport is used. |
|
Base http.RoundTripper |
|
|
|
mu sync.Mutex // guards modReq |
|
modReq map[*http.Request]*http.Request // original -> modified |
|
} |
|
|
|
// RoundTrip authorizes and authenticates the request with an |
|
// access token. If no token exists or token is expired, |
|
// tries to refresh/fetch a new token. |
|
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { |
|
if t.Source == nil { |
|
return nil, errors.New("oauth2: Transport's Source is nil") |
|
} |
|
token, err := t.Source.Token() |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
req2 := cloneRequest(req) // per RoundTripper contract |
|
token.SetAuthHeader(req2) |
|
t.setModReq(req, req2) |
|
res, err := t.base().RoundTrip(req2) |
|
if err != nil { |
|
t.setModReq(req, nil) |
|
return nil, err |
|
} |
|
res.Body = &onEOFReader{ |
|
rc: res.Body, |
|
fn: func() { t.setModReq(req, nil) }, |
|
} |
|
return res, nil |
|
} |
|
|
|
// CancelRequest cancels an in-flight request by closing its connection. |
|
func (t *Transport) CancelRequest(req *http.Request) { |
|
type canceler interface { |
|
CancelRequest(*http.Request) |
|
} |
|
if cr, ok := t.base().(canceler); ok { |
|
t.mu.Lock() |
|
modReq := t.modReq[req] |
|
delete(t.modReq, req) |
|
t.mu.Unlock() |
|
cr.CancelRequest(modReq) |
|
} |
|
} |
|
|
|
func (t *Transport) base() http.RoundTripper { |
|
if t.Base != nil { |
|
return t.Base |
|
} |
|
return http.DefaultTransport |
|
} |
|
|
|
func (t *Transport) setModReq(orig, mod *http.Request) { |
|
t.mu.Lock() |
|
defer t.mu.Unlock() |
|
if t.modReq == nil { |
|
t.modReq = make(map[*http.Request]*http.Request) |
|
} |
|
if mod == nil { |
|
delete(t.modReq, orig) |
|
} else { |
|
t.modReq[orig] = mod |
|
} |
|
} |
|
|
|
// cloneRequest returns a clone of the provided *http.Request. |
|
// The clone is a shallow copy of the struct and its Header map. |
|
func cloneRequest(r *http.Request) *http.Request { |
|
// shallow copy of the struct |
|
r2 := new(http.Request) |
|
*r2 = *r |
|
// deep copy of the Header |
|
r2.Header = make(http.Header, len(r.Header)) |
|
for k, s := range r.Header { |
|
r2.Header[k] = append([]string(nil), s...) |
|
} |
|
return r2 |
|
} |
|
|
|
type onEOFReader struct { |
|
rc io.ReadCloser |
|
fn func() |
|
} |
|
|
|
func (r *onEOFReader) Read(p []byte) (n int, err error) { |
|
n, err = r.rc.Read(p) |
|
if err == io.EOF { |
|
r.runFunc() |
|
} |
|
return |
|
} |
|
|
|
func (r *onEOFReader) Close() error { |
|
err := r.rc.Close() |
|
r.runFunc() |
|
return err |
|
} |
|
|
|
func (r *onEOFReader) runFunc() { |
|
if fn := r.fn; fn != nil { |
|
fn() |
|
r.fn = nil |
|
} |
|
}
|
|
|