Learning to Go: Part 4 - The Search Handler

Learning to Go: Part 4 - The Search Handler

·

4 min read

Oh hi! This is a multi-part tutorial series, please make sure you reads the posts in order! You can find the list at the bottom of the page.

Now we’ve got a basic page up and running, in this step we’ll add the Search handler, which will actually deal with requests for searches.

First we’ll need to add fmt and net/url to our imports section:

import (
    "fmt"
    "html/template"
    "net/http"
    "net/url"
)

Next we’ll write our searchHandler function. First let’s add the skeleton of the function to main.go. You can add this after the indexHandler function.

func searchHandler(w http.ResponseWriter, r *http.Request) {
}

Just like our indexHandler, we define our function with its name and the inputs it will accept. Next, add the following code inside the function:

u, err := url.Parse(r.URL.String())
if err != nil {
    w.WriteHeader(http.StatusInternalServerError)
    w.Write([]byte("Internal server error"))
    return
}

The first thing we do here is create a variable called u and assign to it the output of this function call: url.Parse(r.URL.String()). But what does that do?

Remember that r is passed into our function as a pointer to the http.Request. So that gives us lots of information about the request itself, and in this case we’re accessing the full URL of the request. We’re also using Parse which breaks it down into easier variables for us to access.

If you’ve looked in index.html you may have noticed that our search form uses the GET method. This means that our search parameters simply become part of the URL of the request. As they are part of the URL, we can access the search terms just by reading the URL.

But you may have also noticed that we don’t just assign u here, we also assign another variable called err. Then we immediately check to see if err is not nil, in which case we write out an internal server error, and quit.

This is a common programming pattern in Go for exception handling. If everything goes according to plan, we have our URL in the u variable. But if something bad happens, err will not be empty, or nil, and we can handle it gracefully.

Next, let’s move on to what we do with u:

params := u.Query()
searchKey := params.Get("q")

First we grab the parameters of the search by using the Query() function on u. This function grabs all of the parameters that are part of the URL path, without the website address. We can then access the parameters individually, which we do to get the searchKey.

Again, going back to our index.html, you’ll see that the text of our search is sent as the value of q. So we grab that paramater and assign to it searchKey. For now, rather than rendering any different HTML output, we’ll just ouput the value of searchKey to the console using fmt.Println:

fmt.Println("Search Query is: ", searchKey)

You’ll see the output come up in your terminal when you run the the program. That’s all the code we need for our searchHandler function.

The last thing to do is add our new searchHandler to our HTTP mux server. Add this before the HandleFunc line for indexHandler:

mux.HandleFunc("/search", searchHandler)

Now let’s test our search function:

go run main.go

Enter some search terms at http://localhost:8080 and observe them in the URL path in your browser. You’ll see a blank web page, but you should see your search terms in your terminal:

$ go run main.go
Search Query is: enterprise

Nicely done! We’ve added a new handler and learnt how to process parameters from an HTTP GET request. Now move on to part 5, where we’ll actually do something with that request.

Here’s the complete code of main.go if you need to check yours for errors:

package main

import (
    "fmt"
    "html/template"
    "net/http"
    "net/url"
)

var tpl = template.Must(template.ParseFiles("index.html"))

func indexHandler(w http.ResponseWriter, r *http.Request) {
    tpl.Execute(w, nil)
}

func searchHandler(w http.ResponseWriter, r *http.Request) {
    u, err := url.Parse(r.URL.String())
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte("Internal server error"))
        return
    }
    params := u.Query()
    searchKey := params.Get("q")
    fmt.Println("Search Query is: ", searchKey)
}

func main() {
    mux := http.NewServeMux()
    fs := http.FileServer(http.Dir("assets"))
    mux.Handle("/assets/", http.StripPrefix("/assets/", fs))
    mux.HandleFunc("/search", searchHandler)
    mux.HandleFunc("/", indexHandler)
    http.ListenAndServe(":8080", mux)
}