| // +build ignore,OMIT | |
| // The server program issues Google search requests. It serves on port 8080. | |
| // | |
| // The /search endpoint accepts these query params: | |
| // q=the Google search query | |
| // | |
| // For example, http://localhost:8080/search?q=golang serves the first | |
| // few Google search results for "golang". | |
| package main | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "html/template" | |
| "log" | |
| "net/http" | |
| "net/url" | |
| "time" | |
| ) | |
| func main() { | |
| http.HandleFunc("/search", handleSearch) // HL | |
| fmt.Println("serving on http://localhost:8080/search") | |
| log.Fatal(http.ListenAndServe("localhost:8080", nil)) | |
| } | |
| // handleSearch handles URLs like "/search?q=golang" by running a | |
| // Google search for "golang" and writing the results as HTML to w. | |
| func handleSearch(w http.ResponseWriter, req *http.Request) { | |
| log.Println("serving", req.URL) | |
| // Check the search query. | |
| query := req.FormValue("q") // HL | |
| if query == "" { | |
| http.Error(w, `missing "q" URL parameter`, http.StatusBadRequest) | |
| return | |
| } | |
| // ENDQUERY OMIT | |
| // Run the Google search. | |
| start := time.Now() | |
| results, err := Search(query) // HL | |
| elapsed := time.Since(start) | |
| if err != nil { | |
| http.Error(w, err.Error(), http.StatusInternalServerError) | |
| return | |
| } | |
| // ENDSEARCH OMIT | |
| // Render the results. | |
| type templateData struct { | |
| Results []Result | |
| Elapsed time.Duration | |
| } | |
| if err := resultsTemplate.Execute(w, templateData{ // HL | |
| Results: results, | |
| Elapsed: elapsed, | |
| }); err != nil { | |
| log.Print(err) | |
| return | |
| } | |
| // ENDRENDER OMIT | |
| } | |
| // A Result contains the title and URL of a search result. | |
| type Result struct { // HL | |
| Title, URL string // HL | |
| } // HL | |
| var resultsTemplate = template.Must(template.New("results").Parse(` | |
| <html> | |
| <head></head> | |
| <body> | |
| <ol> | |
| {{range .Results}} | |
| <li>{{.Title}} - <a href="{{.URL}}">{{.URL}}</a></li> | |
| {{end}} | |
| </ol> | |
| <p>{{len .Results}} results in {{.Elapsed}}</p> | |
| </body> | |
| </html> | |
| `)) | |
| // Search sends query to Google search and returns the results. | |
| func Search(query string) ([]Result, error) { | |
| // Prepare the Google Search API request. | |
| u, err := url.Parse("https://ajax.googleapis.com/ajax/services/search/web?v=1.0") | |
| if err != nil { | |
| return nil, err | |
| } | |
| q := u.Query() | |
| q.Set("q", query) // HL | |
| u.RawQuery = q.Encode() | |
| // Issue the HTTP request and handle the response. | |
| resp, err := http.Get(u.String()) // HL | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer resp.Body.Close() // HL | |
| // Parse the JSON search result. | |
| // https://developers.google.com/web-search/docs/#fonje | |
| var jsonResponse struct { | |
| ResponseData struct { | |
| Results []struct { | |
| TitleNoFormatting, URL string | |
| } | |
| } | |
| } | |
| if err := json.NewDecoder(resp.Body).Decode(&jsonResponse); err != nil { // HL | |
| return nil, err | |
| } | |
| // Extract the Results from jsonResponse and return them. | |
| var results []Result | |
| for _, r := range jsonResponse.ResponseData.Results { // HL | |
| results = append(results, Result{Title: r.TitleNoFormatting, URL: r.URL}) | |
| } | |
| return results, nil | |
| } |