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  }