1
0
Fork 0
mirror of https://github.com/imjasonh/staticmaps synced 2026-07-16 20:42:04 +00:00

remove Client struct, use context values for API key, Work client ID and private key, and http Client

This commit is contained in:
Jason Hall 2014-10-21 15:10:41 -04:00
parent 9166c9bae7
commit 64ec22f709
10 changed files with 127 additions and 97 deletions

67
context.go Normal file
View file

@ -0,0 +1,67 @@
package maps
import (
"net/http"
"code.google.com/p/go.net/context"
)
type contextKey int
// NewContext returns a new context that uses the provided http.Client and API key.
func NewContext(key string, c *http.Client) context.Context {
return WithContext(context.Background(), key, c)
}
// WithContext returns a new context in a similar way NewContext does, but initiates the new context with the specified parent.
func WithContext(parent context.Context, key string, c *http.Client) context.Context {
vals := map[string]interface{}{}
vals["key"] = key
vals["httpClient"] = c
return context.WithValue(parent, contextKey(0), vals)
}
// NewWorkContext returns a new context that uses the provided http.Client and Google Maps API for Work client ID and private key.
func NewWorkContext(clientID, privateKey string, c *http.Client) context.Context {
return WithWorkCredentials(context.Background(), clientID, privateKey, c)
}
// WithWorkCredentials returns a new context in a similar way NewWorkContext does, but initiates the new context with the specified parent.
func WithWorkCredentials(parent context.Context, clientID, privateKey string, c *http.Client) context.Context {
vals := map[string]interface{}{}
vals["workClientID"] = clientID
vals["workPrivKey"] = privateKey
vals["httpClient"] = c
return context.WithValue(parent, contextKey(0), vals)
}
func key(ctx context.Context) string {
k, found := ctx.Value(contextKey(0)).(map[string]interface{})["key"]
if found {
return k.(string)
}
return ""
}
func workCreds(ctx context.Context) (string, string) {
var clientID string
cid, found := ctx.Value(contextKey(0)).(map[string]interface{})["workClientID"]
if found {
clientID = cid.(string)
}
var privateKey string
pkey, found := ctx.Value(contextKey(0)).(map[string]interface{})["workPrivKey"]
if found {
privateKey = pkey.(string)
}
return clientID, privateKey
}
func httpClient(ctx context.Context) *http.Client {
cl, found := ctx.Value(contextKey(0)).(map[string]interface{})["httpClient"]
if found && cl != nil {
return cl.(*http.Client)
}
return &http.Client{Transport: &backoff{Transport: http.DefaultTransport}}
}

View file

@ -37,9 +37,9 @@ const (
// Directions requests routes between orig and dest Locations. // Directions requests routes between orig and dest Locations.
// //
// See https://developers.google.com/maps/documentation/directions/ // See https://developers.google.com/maps/documentation/directions/
func (c Client) Directions(ctx context.Context, orig, dest Location, opts *DirectionsOpts) ([]Route, error) { func Directions(ctx context.Context, orig, dest Location, opts *DirectionsOpts) ([]Route, error) {
var d directionsResponse var d directionsResponse
if err := c.doDecode(ctx, baseURL+directions(orig, dest, opts), &d); err != nil { if err := doDecode(ctx, baseURL+directions(orig, dest, opts), &d); err != nil {
return nil, err return nil, err
} }
if d.Status != StatusOK { if d.Status != StatusOK {

View file

@ -11,9 +11,9 @@ import (
// DistanceMatrix requests travel distance and time for a matrix of origins and destinations. // DistanceMatrix requests travel distance and time for a matrix of origins and destinations.
// //
// See https://developers.google.com/maps/documentation/distancematrix/ // See https://developers.google.com/maps/documentation/distancematrix/
func (c Client) DistanceMatrix(ctx context.Context, orig, dest []Location, opts *DistanceMatrixOpts) (*DistanceMatrixResult, error) { func DistanceMatrix(ctx context.Context, orig, dest []Location, opts *DistanceMatrixOpts) (*DistanceMatrixResult, error) {
var d distanceResponse var d distanceResponse
if err := c.doDecode(ctx, baseURL+distancematrix(orig, dest, opts), &d); err != nil { if err := doDecode(ctx, baseURL+distancematrix(orig, dest, opts), &d); err != nil {
return nil, err return nil, err
} }
if d.Status != StatusOK { if d.Status != StatusOK {

View file

@ -10,9 +10,9 @@ import (
// Elevation requests elevation data for a series of locations. // Elevation requests elevation data for a series of locations.
// //
// See https://developers.google.com/maps/documentation/elevation/ // See https://developers.google.com/maps/documentation/elevation/
func (c Client) Elevation(ctx context.Context, ll []LatLng) ([]ElevationResult, error) { func Elevation(ctx context.Context, ll []LatLng) ([]ElevationResult, error) {
var r elevationResponse var r elevationResponse
if err := c.doDecode(ctx, baseURL+elevation(ll), &r); err != nil { if err := doDecode(ctx, baseURL+elevation(ll), &r); err != nil {
return nil, err return nil, err
} }
if r.Status != StatusOK { if r.Status != StatusOK {
@ -24,9 +24,9 @@ func (c Client) Elevation(ctx context.Context, ll []LatLng) ([]ElevationResult,
// ElevationPolyline requests elevation data for a series of locations as specified as an encoded polyline. // ElevationPolyline requests elevation data for a series of locations as specified as an encoded polyline.
// //
// See https://developers.google.com/maps/documentation/elevation/#Locations // See https://developers.google.com/maps/documentation/elevation/#Locations
func (c Client) ElevationPolyline(ctx context.Context, p string) ([]ElevationResult, error) { func ElevationPolyline(ctx context.Context, p string) ([]ElevationResult, error) {
var r elevationResponse var r elevationResponse
if err := c.doDecode(ctx, baseURL+elevationpoly(p), &r); err != nil { if err := doDecode(ctx, baseURL+elevationpoly(p), &r); err != nil {
return nil, err return nil, err
} }
if r.Status != StatusOK { if r.Status != StatusOK {
@ -36,9 +36,9 @@ func (c Client) ElevationPolyline(ctx context.Context, p string) ([]ElevationRes
} }
// ElevationPath requests elevation data for a number of samples along a path described as a series of locations. // ElevationPath requests elevation data for a number of samples along a path described as a series of locations.
func (c Client) ElevationPath(ctx context.Context, ll []LatLng, samples int) ([]ElevationResult, error) { func ElevationPath(ctx context.Context, ll []LatLng, samples int) ([]ElevationResult, error) {
var r elevationResponse var r elevationResponse
if err := c.doDecode(ctx, baseURL+elevationpath(ll, samples), &r); err != nil { if err := doDecode(ctx, baseURL+elevationpath(ll, samples), &r); err != nil {
return nil, err return nil, err
} }
if r.Status != StatusOK { if r.Status != StatusOK {
@ -48,9 +48,9 @@ func (c Client) ElevationPath(ctx context.Context, ll []LatLng, samples int) ([]
} }
// ElevationPathPoly requests elevation data for a number of samples along a path described as a series of locations specified as an encoded polyline. // ElevationPathPoly requests elevation data for a number of samples along a path described as a series of locations specified as an encoded polyline.
func (c Client) ElevationPathPoly(ctx context.Context, p string, samples int) ([]ElevationResult, error) { func ElevationPathPoly(ctx context.Context, p string, samples int) ([]ElevationResult, error) {
var r elevationResponse var r elevationResponse
if err := c.doDecode(ctx, baseURL+elevationpathpoly(p, samples), &r); err != nil { if err := doDecode(ctx, baseURL+elevationpathpoly(p, samples), &r); err != nil {
return nil, err return nil, err
} }
if r.Status != StatusOK { if r.Status != StatusOK {

View file

@ -33,9 +33,9 @@ const (
) )
// Geocode requests conversion of an address to a latitude/longitude pair. // Geocode requests conversion of an address to a latitude/longitude pair.
func (c Client) Geocode(ctx context.Context, opts *GeocodeOpts) ([]GeocodeResult, error) { func Geocode(ctx context.Context, opts *GeocodeOpts) ([]GeocodeResult, error) {
var r geocodeResponse var r geocodeResponse
if err := c.doDecode(ctx, baseURL+geocode(opts), &r); err != nil { if err := doDecode(ctx, baseURL+geocode(opts), &r); err != nil {
return nil, err return nil, err
} }
if r.Status != StatusOK { if r.Status != StatusOK {
@ -194,9 +194,9 @@ type GeocodeResult struct {
// ReverseGeocode requests conversion of a location to its nearest address. // ReverseGeocode requests conversion of a location to its nearest address.
// //
// See https://developers.google.com/maps/documentation/geocoding/#ReverseGeocoding // See https://developers.google.com/maps/documentation/geocoding/#ReverseGeocoding
func (c Client) ReverseGeocode(ctx context.Context, ll LatLng, opts *ReverseGeocodeOpts) ([]GeocodeResult, error) { func ReverseGeocode(ctx context.Context, ll LatLng, opts *ReverseGeocodeOpts) ([]GeocodeResult, error) {
var r geocodeResponse var r geocodeResponse
if err := c.doDecode(ctx, baseURL+reversegeocode(ll, opts), &r); err != nil { if err := doDecode(ctx, baseURL+reversegeocode(ll, opts), &r); err != nil {
return nil, err return nil, err
} }
if r.Status != StatusOK { if r.Status != StatusOK {

64
maps.go
View file

@ -35,61 +35,23 @@ const (
StatusOverQueryLimit = "OVER_QUERY_LIMIT" StatusOverQueryLimit = "OVER_QUERY_LIMIT"
) )
// Client provides methods to make requests of various Maps APIs. func do(ctx context.Context, url string) (*http.Response, error) {
type Client struct { cl := httpClient(ctx)
// The underlying http.RoundTripper to use. If this is not specified, http.DefaultTransport will be used.
Transport http.RoundTripper
// Key is the API key to use to authorize requests.
Key string
// ClientID is the Google Maps API for Work client ID to use.
//
// See https://developers.google.com/maps/documentation/business/webservices/auth
ClientID string
// PrivateKey is the base64-encoded private key to use for Google Maps API for Work requests.
//
// See https://developers.google.com/maps/documentation/business/webservices/auth
PrivateKey string
}
// New returns a Client using the specified API key and RoundTripper.
func New(key string, rt http.RoundTripper) Client {
return Client{Key: key, Transport: rt}
}
// NewForWork returns a Client using the specified Google Maps API for Work client ID and signature, and RoundTripper.
//
// See https://developers.google.com/maps/documentation/business/
func NewForWork(clientID, privateKey string, rt http.RoundTripper) Client {
return Client{ClientID: clientID, PrivateKey: privateKey, Transport: rt}
}
func (c Client) do(ctx context.Context, url string) (*http.Response, error) {
// TODO: Use ctx here.
t := c.Transport
if t == nil {
t = http.DefaultTransport
}
cl := &http.Client{Transport: &backoff{
Transport: t,
}}
req, err := http.NewRequest("GET", url, nil) req, err := http.NewRequest("GET", url, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
q := req.URL.Query() q := req.URL.Query()
if c.Key != "" { if k := key(ctx); k != "" {
q.Set("key", c.Key) q.Set("key", k)
} }
if c.ClientID != "" { clientID, privKey := workCreds(ctx)
q.Set("client", c.ClientID) if clientID != "" {
q.Set("client", clientID)
} }
enc := q.Encode() enc := q.Encode()
if c.PrivateKey != "" { if privKey != "" {
sig, err := c.genSig(req.URL.Path, enc) sig, err := genSig(privKey, req.URL.Path, enc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -100,9 +62,9 @@ func (c Client) do(ctx context.Context, url string) (*http.Response, error) {
} }
// See https://developers.google.com/maps/documentation/business/webservices/auth // See https://developers.google.com/maps/documentation/business/webservices/auth
func (c Client) genSig(path, query string) (string, error) { func genSig(privKey, path, query string) (string, error) {
toSign := path + "?" + query toSign := path + "?" + query
decodedKey, err := base64.URLEncoding.DecodeString(c.PrivateKey) decodedKey, err := base64.URLEncoding.DecodeString(privKey)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -113,8 +75,8 @@ func (c Client) genSig(path, query string) (string, error) {
return base64.URLEncoding.EncodeToString(d.Sum(nil)), nil return base64.URLEncoding.EncodeToString(d.Sum(nil)), nil
} }
func (c Client) doDecode(ctx context.Context, url string, r interface{}) error { func doDecode(ctx context.Context, url string, r interface{}) error {
resp, err := c.do(ctx, url) resp, err := do(ctx, url)
if err != nil { if err != nil {
return err return err
} }

View file

@ -2,16 +2,17 @@ package maps
import ( import (
"image/color" "image/color"
"net/http"
"testing" "testing"
"time" "time"
"code.google.com/p/go.net/context"
) )
var ctx = context.Background() var (
ctx = NewContext("key", &http.Client{})
wctx = NewWorkContext("clientID", "privKey", &http.Client{})
)
func TestDirections(t *testing.T) { func TestDirections(t *testing.T) {
c := New("", nil)
orig, dest := Address("111 8th Ave, NYC"), Address("170 E 92nd St, NYC") orig, dest := Address("111 8th Ave, NYC"), Address("170 E 92nd St, NYC")
opts := &DirectionsOpts{ opts := &DirectionsOpts{
Mode: ModeTransit, Mode: ModeTransit,
@ -19,7 +20,7 @@ func TestDirections(t *testing.T) {
Alternatives: true, Alternatives: true,
} }
t.Logf("%s", baseURL+directions(orig, dest, opts)) t.Logf("%s", baseURL+directions(orig, dest, opts))
r, err := c.Directions(ctx, orig, dest, opts) r, err := Directions(ctx, orig, dest, opts)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -27,7 +28,6 @@ func TestDirections(t *testing.T) {
} }
func TestStaticMap(t *testing.T) { func TestStaticMap(t *testing.T) {
c := New("", nil)
s := Size{512, 512} s := Size{512, 512}
opts := &StaticMapOpts{ opts := &StaticMapOpts{
Center: LatLng{-5, -5}, Center: LatLng{-5, -5},
@ -77,13 +77,12 @@ func TestStaticMap(t *testing.T) {
}, },
} }
t.Logf("%s", baseURL+staticmap(s, opts)) t.Logf("%s", baseURL+staticmap(s, opts))
if _, err := c.StaticMap(ctx, s, opts); err != nil { if _, err := StaticMap(ctx, s, opts); err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
} }
func TestStreetView(t *testing.T) { func TestStreetView(t *testing.T) {
c := New("", nil)
s := Size{600, 300} s := Size{600, 300}
opts := &StreetViewOpts{ opts := &StreetViewOpts{
Location: &LatLng{46.414382, 10.013988}, Location: &LatLng{46.414382, 10.013988},
@ -91,17 +90,16 @@ func TestStreetView(t *testing.T) {
Pitch: -0.76, Pitch: -0.76,
} }
t.Logf("%s", baseURL+streetview(s, opts)) t.Logf("%s", baseURL+streetview(s, opts))
if _, err := c.StreetView(ctx, s, opts); err != nil { if _, err := StreetView(ctx, s, opts); err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
} }
func TestTimeZone(t *testing.T) { func TestTimeZone(t *testing.T) {
c := New("", nil)
ll := LatLng{40.7142700, -74.0059700} ll := LatLng{40.7142700, -74.0059700}
tm := time.Now() tm := time.Now()
t.Logf("%s", baseURL+timezone(ll, tm, nil)) t.Logf("%s", baseURL+timezone(ll, tm, nil))
r, err := c.TimeZone(ctx, ll, tm, nil) r, err := TimeZone(ctx, ll, tm, nil)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -109,10 +107,9 @@ func TestTimeZone(t *testing.T) {
} }
func TestElevation(t *testing.T) { func TestElevation(t *testing.T) {
c := New("", nil)
ll := []LatLng{{39.7391536, -104.9847034}} ll := []LatLng{{39.7391536, -104.9847034}}
t.Logf("%s", baseURL+elevation(ll)) t.Logf("%s", baseURL+elevation(ll))
r, err := c.Elevation(ctx, ll) r, err := Elevation(ctx, ll)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -120,7 +117,7 @@ func TestElevation(t *testing.T) {
p := "gfo}EtohhU" p := "gfo}EtohhU"
t.Logf("%s", baseURL+elevationpoly(p)) t.Logf("%s", baseURL+elevationpoly(p))
r, err = c.ElevationPolyline(ctx, p) r, err = ElevationPolyline(ctx, p)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -129,7 +126,7 @@ func TestElevation(t *testing.T) {
samples := 3 samples := 3
ll = []LatLng{{36.578581, -118.291994}, {36.23998, -116.83171}} ll = []LatLng{{36.578581, -118.291994}, {36.23998, -116.83171}}
t.Logf("%s", baseURL+elevationpath(ll, samples)) t.Logf("%s", baseURL+elevationpath(ll, samples))
r, err = c.ElevationPath(ctx, ll, samples) r, err = ElevationPath(ctx, ll, samples)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -137,7 +134,7 @@ func TestElevation(t *testing.T) {
p = "gfo}EtohhUxD@bAxJmGF" p = "gfo}EtohhUxD@bAxJmGF"
t.Logf("%s", baseURL+elevationpathpoly(p, samples)) t.Logf("%s", baseURL+elevationpathpoly(p, samples))
r, err = c.ElevationPathPoly(ctx, p, samples) r, err = ElevationPathPoly(ctx, p, samples)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -145,12 +142,11 @@ func TestElevation(t *testing.T) {
} }
func TestGeocode(t *testing.T) { func TestGeocode(t *testing.T) {
c := New("", nil)
opts := &GeocodeOpts{ opts := &GeocodeOpts{
Address: Address("1600 Amphitheatre Parkway, Mountain View, CA"), Address: Address("1600 Amphitheatre Parkway, Mountain View, CA"),
} }
t.Logf("%s", baseURL+geocode(opts)) t.Logf("%s", baseURL+geocode(opts))
r, err := c.Geocode(ctx, opts) r, err := Geocode(ctx, opts)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -158,7 +154,7 @@ func TestGeocode(t *testing.T) {
ll := LatLng{40.714224, -73.961452} ll := LatLng{40.714224, -73.961452}
t.Logf("%s", baseURL+reversegeocode(ll, nil)) t.Logf("%s", baseURL+reversegeocode(ll, nil))
r, err = c.ReverseGeocode(ctx, ll, nil) r, err = ReverseGeocode(ctx, ll, nil)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -166,7 +162,6 @@ func TestGeocode(t *testing.T) {
} }
func TestDistanceMatrix(t *testing.T) { func TestDistanceMatrix(t *testing.T) {
c := New("", nil)
orig := []Location{Address("Vancouver, BC"), Address("Seattle")} orig := []Location{Address("Vancouver, BC"), Address("Seattle")}
dst := []Location{Address("San Francisco"), Address("Victoria, BC")} dst := []Location{Address("San Francisco"), Address("Victoria, BC")}
opts := &DistanceMatrixOpts{ opts := &DistanceMatrixOpts{
@ -174,7 +169,7 @@ func TestDistanceMatrix(t *testing.T) {
Language: "fr-FR", Language: "fr-FR",
} }
t.Logf("%s", baseURL+distancematrix(orig, dst, opts)) t.Logf("%s", baseURL+distancematrix(orig, dst, opts))
r, err := c.DistanceMatrix(ctx, orig, dst, opts) r, err := DistanceMatrix(ctx, orig, dst, opts)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -184,13 +179,19 @@ func TestDistanceMatrix(t *testing.T) {
// Based on https://developers.google.com/maps/documentation/business/webservices/auth#signature_examples // Based on https://developers.google.com/maps/documentation/business/webservices/auth#signature_examples
func TestSignature(t *testing.T) { func TestSignature(t *testing.T) {
c := NewForWork("", "vNIXE0xscrmjlyV-12Nj_BvUPaw=", nil) clientID := "clientID"
sig, err := c.genSig("/maps/api/geocode/json", "address=New+York&client=clientID") privateKey := "vNIXE0xscrmjlyV-12Nj_BvUPaw="
sig, err := genSig(privateKey, "/maps/api/geocode/json", "address=New+York&client="+clientID)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
exp := "chaRF2hTJKOScPr-RQCEhZbSzIE=" exp := "chaRF2hTJKOScPr-RQCEhZbSzIE="
if sig != exp { if sig != exp {
t.Errorf("wrong signature, got %q, want %q", sig, exp) t.Errorf("unexpected signature, got %q, want %q", sig, exp)
}
ctx := NewWorkContext(clientID, privateKey, nil)
if gcid, gkey := workCreds(ctx); gcid != clientID || gkey != privateKey {
t.Errorf("unepxected credentials from context, got %q and %q, want %q and %q", gcid, gkey, clientID, privateKey)
} }
} }

View file

@ -48,8 +48,8 @@ const (
) )
// StaticMap requests a static map image of a requested size. // StaticMap requests a static map image of a requested size.
func (c Client) StaticMap(ctx context.Context, s Size, opts *StaticMapOpts) (io.ReadCloser, error) { func StaticMap(ctx context.Context, s Size, opts *StaticMapOpts) (io.ReadCloser, error) {
resp, err := c.do(ctx, baseURL+staticmap(s, opts)) resp, err := do(ctx, baseURL+staticmap(s, opts))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -10,8 +10,8 @@ import (
) )
// StreetView requests a static StreetView image of the requested size. // StreetView requests a static StreetView image of the requested size.
func (c Client) StreetView(ctx context.Context, s Size, opts *StreetViewOpts) (io.ReadCloser, error) { func StreetView(ctx context.Context, s Size, opts *StreetViewOpts) (io.ReadCloser, error) {
resp, err := c.do(ctx, baseURL+streetview(s, opts)) resp, err := do(ctx, baseURL+streetview(s, opts))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -11,9 +11,9 @@ import (
// TimeZone requests time zone information about a location. // TimeZone requests time zone information about a location.
// //
// See https://developers.google.com/maps/documentation/timezone/ // See https://developers.google.com/maps/documentation/timezone/
func (c Client) TimeZone(ctx context.Context, ll LatLng, t time.Time, opts *TimeZoneOpts) (*TimeZoneResult, error) { func TimeZone(ctx context.Context, ll LatLng, t time.Time, opts *TimeZoneOpts) (*TimeZoneResult, error) {
var r timeZoneResponse var r timeZoneResponse
if err := c.doDecode(ctx, baseURL+timezone(ll, t, opts), &r); err != nil { if err := doDecode(ctx, baseURL+timezone(ll, t, opts), &r); err != nil {
return nil, err return nil, err
} }
if r.Status != StatusOK { if r.Status != StatusOK {