Source file blog/context/server/server.go

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

View as plain text