In part one of this tutorial, we installed the Google App Engine SDK and then built and ran a simple web app locally. In part two, we deployed this simple web app to the Google App Engine and shared it with users worldwide. In part three, we learned about package template (namely text/template
and html/template
).
Now, we shall learn how to handle forms, build and deploy our Go web app GoView
.
Create folders
First, create folders as shown in the diagram below:
c:go_projects +---go +---src +---github.com +---SatishTalim +---goview | goview.go | app.yaml | favicon.ico | +---images | gsv.png | +---stylesheets goview.css
File goview.css
Here are the contents of the file goview.css
:
body { background-color: #C2A7F2; font-family: sans-serif; } h1 { color: #2A1959; border-bottom: 2px solid #2A1959; } h2 { color: #474B94; font-size: 1.2em; } h2, p { margin-left: 120px; }
File app.yaml
Here are the contents of the file app.yaml
:
application: geocodeweb version: 1-0 runtime: go api_version: go1 handlers: - url: /favicon.ico static_files: favicon.ico upload: favicon.ico # All URLs beginning with /stylesheets are treated # as paths to static files in the stylesheets/ directory. - url: /stylesheets static_dir: stylesheets - url: /images static_dir: images - url: /.* script: _go_app
In part one of this series, we have already talked about the configuration file app.yaml
.
Program goview.go
In the goview
folder, create a file named goview.go
, open it in your favorite editor, and add the following lines:
package goview import ( "encoding/json" "fmt" "html/template" "io/ioutil" "net/http" "net/url" "appengine" "appengine/urlfetch" ) func init() { http.HandleFunc("/", handler) http.HandleFunc("/showimage", showimage) } func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, rootForm) } const rootForm = ` <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Go View</title> <link rel="stylesheet" href="/stylesheets/goview.css"> </head> <body> <h1><img style="margin-left: 120px;" src="images/gsv.png" alt="Go View" />GoView</h1> <h2>Accept Address</h2> <p>Please enter your address:</p> <form style="margin-left: 120px;" action="/showimage" method="post" accept-charset="utf-8"> <input type="text" name="str" value="Type address..." id="str" /> <input type="submit" value=".. and see the image!" /> </form> </body> </html> ` var upperTemplate = template.Must(template.New("showimage").Parse(upperTemplateHTML)) func showimage(w http.ResponseWriter, r *http.Request) { addr := r.FormValue("str") safeAddr := url.QueryEscape(addr) fullUrl := fmt.Sprintf("http://maps.googleapis.com/maps/api/geocode/json?sensor=false&address=%s", safeAddr) c := appengine.NewContext(r) client := urlfetch.Client(c) resp, err := client.Get(fullUrl) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer resp.Body.Close() // Read the content into a byte array body, dataReadErr := ioutil.ReadAll(resp.Body) if dataReadErr != nil { panic(dataReadErr) } res := make(map[string][]map[string]map[string]map[string]interface{}, 0) json.Unmarshal(body, &res) lat, _ := res["results"][0]["geometry"]["location"]["lat"] lng, _ := res["results"][0]["geometry"]["location"]["lng"] // %.13f is used to convert float64 to a string queryUrl := fmt.Sprintf("http://maps.googleapis.com/maps/api/streetview?sensor=false&size=600x300&location=%.13f,%.13f", lat, lng) tempErr := upperTemplate.Execute(w, queryUrl) if tempErr != nil { http.Error(w, tempErr.Error(), http.StatusInternalServerError) } } const upperTemplateHTML = ` <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Display Image</title> <link rel="stylesheet" href="/stylesheets/goview.css"> </head> <body> <h1><img style="margin-left: 120px;" src="images/gsv.png" alt="Street View" />GoView</h1> <h2>Image at your Address</h2> <img style="margin-left: 120px;" src="{{html .}}" alt="Image" /> </body> </html> `
Let us now analyze the above program.
User Form
A user form give us the ability to communicate between clients and servers. We use the form tag <form> to define a form. Go has many convenient functions to deal with a user form making it easy to integrate it in our web applications.
In the program, we have used:
<form style="margin-left: 120px;" action="/showimage" method="post" accept-charset="utf-8"> <input type="text" name="str" value="Type address..." id="str" /> <input type="submit" value=".. and see the image!" /> </form>
This form will submit to /showimage
on the server. After the user clicks the see the image!
button, the data will be sent to the showimage
handler on the server.
The statement addr := r.FormValue("str")
returns the value for the named component namely str
of the query, which in our case is the address entered by the user.
QueryEscape
The QueryEscape function escapes the addr
string so that it can be safely placed inside a URL query.
App Engine specific
For our app to work on the Google App. Engine we have modified some of our code. App Engine applications can communicate with other applications or access other resources on the web by fetching URLs. An app can use the URL Fetch service to issue HTTP and HTTPS requests and receive responses. The URL Fetch service uses Google’s network infrastructure for efficiency and scaling purposes.
defer
Callers should close resp.Body
when done reading from it. Defer the closing of the body by defer resp.Body.Close()
Unmarshal
We will be using the Unmarshal
function to transform our JSON bytes into the appropriate structure. The Unmarshal
function accepts a byte array and a reference to the object which shall be filled with the JSON data (this is simplifying, it actually accepts an interface) – json.Unmarshal(body, &res)
.
Here, we successfully convert an addresses (like “1600 Amphitheatre Parkway, Mountain View, CA”) into its geographic coordinates (like latitude 37.423021 and longitude -122.083739).
Using Google Geocoding and Street View Image APIs
We shall use the following apis:
to build our Go web app GoView
that accepts an address as input and displays the street-view image of that address.
Open your web browser window and type the following URL:
http://maps.googleapis.com/maps/api/geocode/json?address=1600+Amphitheatre+Parkway,+Mountain+View,+CA&sensor=false
Based on the parameters from our HTTP request, Google responds with a lot of geographical information about the address “1600 Amphitheatre Parkway, Mountain View, CA” in the JSON format. In Google’s JSON response, you can see how the ‘<lat>’ tag, wrapped in between the ‘<location>’ tags, contains the ‘latitude’ coordinates for our given address.
Note that the base Google url we used to test out the API was:
http://maps.googleapis.com/maps/api/geocode/json?sensor=false&address=
A Street View Image request is an HTTP URL of the following form:
http://maps.googleapis.com/maps/api/streetview?parameters
The image is specified using request parameters. As is standard in URLs, all parameters are separated using the ampersand (&) character.
Required parameters
size
specifies the output size of the image in pixels. Size is specified as {width}x{height} – for example,size=600x400
returns an image 600 pixels wide, and 400 high. Street View images can be returned in any size up to 640 by 640 pixels.location
can be either a text string (such asChagrin Falls, OH
) or a lat/lng value (40.457375,-80.009353
). The Street View Image API will snap to the panorama photographed closest to this location. Because Street View imagery is periodically refreshed, and photographs may be taken from slightly different positions each time, it’s possible that yourlocation
may snap to a different panorama when imagery is updated.sensor
indicates whether or not the request came from a device using a location sensor (e.g. a GPS) to determine the location sent in this request. This value must be eithertrue
orfalse
.
An example request is shown below.
http://maps.googleapis.com/maps/api/streetview?size=600x300&location=37.4214111,-122.0840372&sensor=false
Here is the output:
Deploy our app GoView
In part two you have already learned how to deploy an app to the Google App Engine. Deploy our app goview.go
to the App Engine.
You can check out this app. running on the Google App. Engine here.
Almost all addresses in the USA and Europe will render a photo but I am not too sure about addresses outside of USA/Europe.
Things to do
You can download all the files used in this article from here.
As an exercise, you can modify goview.go
to handle errors and also modify/optimize the goview.css
file.
You can learn more about writing and deploying web apps with Go from my new book.
I hope you’ve enjoyed this post, please leave any feedback in the comments section.
You can find the other parts of the series at the following links:
I am a bit confused about how to implement / request webservices specially across different language. Reading your code I am not sure if there is any webservice being presented, althought I do see a get method and some data being uploaded/served. I am more used to having a protocol (REST, XMLRPC) datatype (encode64, JSON, XML) being exchanged. From my readings on Go it seems this can be handled from the net/http library which is used on your app, but I am still can’t seem to grasp its used as a WS. From my reading (http://blog.smartbear.com/web-development/how-to-build-a-web-service-in-5-minutes-with-go/) it seems they use other frameworks like Martini, Gorilla, GoWeb etc. So I get confused trying to learn a new framework for just one action.
You can see how this is becoming a incremental task. Thanks or your help.
Is there any way of writing log files in the same way as you would do with log.Print(“error!”) (at least in development mode)? I find it difficult to debug without even this very basic debug option.
It’s being a while since this question was asked, but just for the record you can use app engine context to achieve this task.
c := appengine.NewContext(r)
c.Errorf(“This is a error log message: %v”, err)
Alternatively you can use other methods of logging such as Info
c.Infof(“This is a info log message: %v”, err)
You can find the complete documentation for logging on AppEngine with go on the link below.
https://cloud.google.com/appengine/docs/go/logs/reference
I hope it helps,
JB
Thanks for the reply, I did find out myself though.