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