Source file blog/context/google/google.go

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

View as plain text