Пакет google
Функция google.Search выполняет HTTP-запрос в Google Web Search API и анализирует результат, закодированный в JSON. Он принимает параметр Context ctx и немедленно возвращается, если ctx.Done закрыт, пока запрос отправляется.
Запрос Google Web Search API включает в себя поисковый запрос и IP-адрес пользователя в качестве параметров запроса:
func Search(ctx context.Context, query string) (Results, error) {
// Подготовка Google Search API запроса.
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)
// Если ctx содержит IP-адрес пользователя, его перенаправляет на сервер.
// Google APIs использует IP-адрес пользователя, чтобы отличать запросы,
// инициированные сервером, от запросов конечных пользователей.
if userIP, ok := userip.FromContext(ctx); ok {
q.Set("userip", userIP.String())
}
req.URL.RawQuery = q.Encode()
Search использует вспомогательную функцию, httpDo, для выдачи HTTP-запроса и отмены, если ctx.Done закрыт во время обработки запроса или ответа. Search передает сигнал закрытия в httpDo для обработки HTTP-ответа:
var results Results
err = httpDo(ctx, req, func(resp *http.Response, err error) error {
if err != nil {
return err
}
defer resp.Body.Close()
// Разбор JSON результатов поиска.
// 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 ждет закрытия, которое мы предоставили для возврата,
// так что здесь можно безопасно считывать результаты.
return results, err
Функция httpDo запускает HTTP-запрос и обрабатывает его ответ в новой версии goroutine. Он отменяет запрос, если ctx.Done закрылся до выхода goroutine:
func httpDo(ctx context.Context, req *http.Request, f func(*http.Response, error) error) error {
// Запустк HTTP-запроса в goroutine и передача ответа в 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 // Ожидает пока не вернется f.
return ctx.Err()
case err := <-c:
return err
}
}