Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 94ac29b

Browse files
committed
Add context google demo
1 parent 6529c14 commit 94ac29b

File tree

4 files changed

+239
-0
lines changed

4 files changed

+239
-0
lines changed
File renamed without changes.

‎context/google_demo/google/google.go‎

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

‎context/google_demo/server.go‎

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// The server program issues Google search requests and demonstrates the use of
2+
// the go.net Context API. It serves on port 8080.
3+
//
4+
// The /search endpoint accepts these query params:
5+
// q=the Google search query
6+
// timeout=a timeout for the request, in time.Duration format
7+
//
8+
// For example, http://localhost:8080/search?q=golang&timeout=1s serves the
9+
// first few Google search results for "golang" or a "deadline exceeded" error
10+
// if the timeout expires.
11+
package main
12+
13+
import (
14+
"context"
15+
"html/template"
16+
"learn-golang/context/google_demo/google"
17+
"learn-golang/context/google_demo/userip"
18+
"log"
19+
"net/http"
20+
"time"
21+
)
22+
23+
func main() {
24+
http.HandleFunc("/search", handleSearch)
25+
log.Fatal(http.ListenAndServe(":8080", nil))
26+
}
27+
28+
// handleSearch handles URLs like /search?q=golang&timeout=1s by forwarding the
29+
// query to google.Search. If the query param includes timeout, the search is
30+
// canceled after that duration elapses.
31+
func handleSearch(w http.ResponseWriter, req *http.Request) {
32+
// ctx is the Context for this handler. Calling cancel closes the
33+
// ctx.Done channel, which is the cancellation signal for requests
34+
// started by this handler.
35+
var (
36+
ctx context.Context
37+
cancel context.CancelFunc
38+
)
39+
timeout, err := time.ParseDuration(req.FormValue("timeout"))
40+
if err == nil {
41+
// The request has a timeout, so create a context that is
42+
// canceled automatically when the timeout expires.
43+
ctx, cancel = context.WithTimeout(context.Background(), timeout)
44+
} else {
45+
ctx, cancel = context.WithCancel(context.Background())
46+
}
47+
defer cancel() // Cancel ctx as soon as handleSearch returns.
48+
49+
// Check the search query.
50+
query := req.FormValue("q")
51+
if query == "" {
52+
http.Error(w, "no query", http.StatusBadRequest)
53+
return
54+
}
55+
56+
// Store the user IP in ctx for use by code in other packages.
57+
userIP, err := userip.FromRequest(req)
58+
if err != nil {
59+
http.Error(w, err.Error(), http.StatusBadRequest)
60+
return
61+
}
62+
ctx = userip.NewContext(ctx, userIP)
63+
64+
// Run the Google search and print the results.
65+
start := time.Now()
66+
results, err := google.Search(ctx, query)
67+
elapsed := time.Since(start)
68+
if err != nil {
69+
http.Error(w, err.Error(), http.StatusInternalServerError)
70+
return
71+
}
72+
if err := resultsTemplate.Execute(w, struct {
73+
Results google.Results
74+
Timeout, Elapsed time.Duration
75+
}{
76+
Results: results,
77+
Timeout: timeout,
78+
Elapsed: elapsed,
79+
}); err != nil {
80+
log.Print(err)
81+
return
82+
}
83+
}
84+
85+
var resultsTemplate = template.Must(template.New("results").Parse(`
86+
<html>
87+
<head/>
88+
<body>
89+
<ol>
90+
{{range .Results}}
91+
<li>{{.Title}} - <a href="{{.URL}}">{{.URL}}</a></li>
92+
{{end}}
93+
</ol>
94+
<p>{{len .Results}} results in {{.Elapsed}}; timeout {{.Timeout}}</p>
95+
</body>
96+
</html>
97+
`))

‎context/google_demo/userip/userip.go‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Package userip provides functions for extracting a user IP address from a
2+
// request and associating it with a Context.
3+
//
4+
// This package is an example to accompany https://blog.golang.org/context.
5+
// It is not intended for use by others.
6+
package userip
7+
8+
import (
9+
"context"
10+
"fmt"
11+
"net"
12+
"net/http"
13+
)
14+
15+
// FromRequest extracts the user IP address from req, if present.
16+
func FromRequest(req *http.Request) (net.IP, error) {
17+
ip, _, err := net.SplitHostPort(req.RemoteAddr)
18+
if err != nil {
19+
return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
20+
}
21+
22+
userIP := net.ParseIP(ip)
23+
if userIP == nil {
24+
return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
25+
}
26+
return userIP, nil
27+
}
28+
29+
// The key type is unexported to prevent collisions with context keys defined in
30+
// other packages.
31+
type key int
32+
33+
// userIPkey is the context key for the user IP address. Its value of zero is
34+
// arbitrary. If this package defined other context keys, they would have
35+
// different integer values.
36+
const userIPKey key = 0
37+
38+
// NewContext returns a new Context carrying userIP.
39+
func NewContext(ctx context.Context, userIP net.IP) context.Context {
40+
return context.WithValue(ctx, userIPKey, userIP)
41+
}
42+
43+
// FromContext extracts the user IP address from ctx, if present.
44+
func FromContext(ctx context.Context) (net.IP, bool) {
45+
// ctx.Value returns nil if ctx has no value for the key;
46+
// the net.IP type assertion returns ok=false for nil.
47+
userIP, ok := ctx.Value(userIPKey).(net.IP)
48+
return userIP, ok
49+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /