github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/blog/content/context/google/google.go (about) 1 // Package google provides a function to do Google searches using the Google Web 2 // Search API. See https://developers.google.com/web-search/docs/ 3 package google 4 5 import ( 6 "encoding/json" 7 "net/http" 8 9 "golang.org/x/blog/content/context/userip" 10 "golang.org/x/net/context" 11 ) 12 13 // Results is an ordered list of search results. 14 type Results []Result 15 16 // A Result contains the title and URL of a search result. 17 type Result struct { 18 Title, URL string 19 } 20 21 // Search sends query to Google search and returns the results. 22 func Search(ctx context.Context, query string) (Results, error) { 23 // Prepare the Google Search API request. 24 req, err := http.NewRequest("GET", "https://ajax.googleapis.com/ajax/services/search/web?v=1.0", nil) 25 if err != nil { 26 return nil, err 27 } 28 q := req.URL.Query() 29 q.Set("q", query) 30 31 // If ctx is carrying the user IP address, forward it to the server. 32 // Google APIs use the user IP to distinguish server-initiated requests 33 // from end-user requests. 34 if userIP, ok := userip.FromContext(ctx); ok { 35 q.Set("userip", userIP.String()) 36 } 37 req.URL.RawQuery = q.Encode() 38 39 // Issue the HTTP request and handle the response. The httpDo function 40 // cancels the request if ctx.Done is closed. 41 var results Results 42 err = httpDo(ctx, req, func(resp *http.Response, err error) error { 43 if err != nil { 44 return err 45 } 46 defer resp.Body.Close() 47 48 // Parse the JSON search result. 49 // https://developers.google.com/web-search/docs/#fonje 50 var data struct { 51 ResponseData struct { 52 Results []struct { 53 TitleNoFormatting string 54 URL string 55 } 56 } 57 } 58 if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { 59 return err 60 } 61 for _, res := range data.ResponseData.Results { 62 results = append(results, Result{Title: res.TitleNoFormatting, URL: res.URL}) 63 } 64 return nil 65 }) 66 // httpDo waits for the closure we provided to return, so it's safe to 67 // read results here. 68 return results, err 69 } 70 71 // httpDo issues the HTTP request and calls f with the response. If ctx.Done is 72 // closed while the request or f is running, httpDo cancels the request, waits 73 // for f to exit, and returns ctx.Err. Otherwise, httpDo returns f's error. 74 func httpDo(ctx context.Context, req *http.Request, f func(*http.Response, error) error) error { 75 // Run the HTTP request in a goroutine and pass the response to f. 76 tr := &http.Transport{} 77 client := &http.Client{Transport: tr} 78 c := make(chan error, 1) 79 go func() { c <- f(client.Do(req)) }() 80 select { 81 case <-ctx.Done(): 82 tr.CancelRequest(req) 83 <-c // Wait for f to return. 84 return ctx.Err() 85 case err := <-c: 86 return err 87 } 88 }