diff options
Diffstat (limited to 'content/context/google/google.go')
-rw-r--r-- | content/context/google/google.go | 88 |
1 files changed, 88 insertions, 0 deletions
diff --git a/content/context/google/google.go b/content/context/google/google.go new file mode 100644 index 0000000..7f8a984 --- /dev/null +++ b/content/context/google/google.go @@ -0,0 +1,88 @@ +// Package google provides a function to do Google searches using the Google Web +// Search API. See https://developers.google.com/web-search/docs/ +package google + +import ( + "encoding/json" + "net/http" + + "code.google.com/p/go.blog/content/context/userip" + "code.google.com/p/go.net/context" +) + +// Results is an ordered list of search results. +type Results []Result + +// A Result contains the title and URL of a search result. +type Result struct { + Title, URL string +} + +// Search sends query to Google search and returns the results. +func Search(ctx context.Context, query string) (Results, error) { + // Prepare the Google Search API request. + req, err := http.NewRequest("GET", "https://ajax.googleapis.com/ajax/services/search/web?v=1.0", nil) + if err != nil { + return nil, err + } + q := req.URL.Query() + q.Set("q", query) + + // If ctx is carrying the user IP address, forward it to the server. + // Google APIs use the user IP to distinguish server-initiated requests + // from end-user requests. + if userIP, ok := userip.FromContext(ctx); ok { + q.Set("userip", userIP.String()) + } + req.URL.RawQuery = q.Encode() + + // Issue the HTTP request and handle the response. The httpDo function + // cancels the request if ctx.Done is closed. + var results Results + err = httpDo(ctx, req, func(resp *http.Response, err error) error { + if err != nil { + return err + } + defer resp.Body.Close() + + // Parse the JSON search result. + // https://developers.google.com/web-search/docs/#fonje + var data struct { + ResponseData struct { + Results []struct { + TitleNoFormatting string + URL string + } + } + } + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { + return err + } + for _, res := range data.ResponseData.Results { + results = append(results, Result{Title: res.TitleNoFormatting, URL: res.URL}) + } + return nil + }) + // httpDo waits for the closure we provided to return, so it's safe to + // read results here. + return results, err +} + +// httpDo issues the HTTP request and calls f with the response. If ctx.Done is +// closed while the request or f is running, httpDo cancels the request, waits +// for f to exit, and returns ctx.Err. Otherwise, httpDo returns f's error. +func httpDo(ctx context.Context, req *http.Request, f func(*http.Response, error) error) error { + // Run the HTTP request in a goroutine and pass the response to f. + tr := &http.Transport{} + client := &http.Client{Transport: tr} + c := make(chan error, 1) + go func() { c <- f(client.Do(req)) }() + select { + case <-ctx.Done(): + tr.CancelRequest(req) + <-c // Wait for f to return. + return ctx.Err() + case err := <-c: + return err + } +} |