I am starting to port Udacity course CS253 Web Development from python to Go. This seems to be a good way to get into Go while covering App Engine SDK.


Note:If you have any comments please do so, I will be more than happy to update and improve my code and learn to better develop in Go code.


Starting with Go and App Engine


To start with Go and App Engine there are a lot of resources out there. Here are the ones I used the most:


Installation and commands


Go App Engine SDK does not have a GoogleAppEngineLauncher (like for python). So you will have to launch and deploy your application via the command line.
To run my application I do as follows:

~/google_appengine/dev_appserver.py udacity.cs253.go/
I put the google_appengine sdk in ~. I had some troubles when I put the Go GAE scripts in my PATH as I also have the python GAE SDK, I think it conflicts. This is the reason why I am explicit when running dev_appserver.py.
To deploy my application I do as follows:
~/google_appengine/appcfg.py update udacity.cs253.go
Note: udacity.cs253.go is the name of my application.


Unit 1: Hello World and Handlers


So I started with Unit 1. Here is what the python code of my main.py looks like in python, 3 handlers a simple hello world handler and 2 handlers date and thanks that comunicate through a Form:

import webapp2
import re
from unit1 import *
class MainHandler(webapp2.RequestHandler):
def get(self):
self.response.out.write('Hello Udacity!')
app = webapp2.WSGIApplication([('/', MainHandler),
('/unit1/date',DateHandler),
('/unit1/thanks',ThanksHandler)],debug=True)
And here is what my main.go looks like after some work.

package main
import (
"fmt"
"net/http"
"appengine"
"unit1"
)
func init(){
http.HandleFunc("/", mainHandler)
http.HandleFunc("/unit1/date", unit1.DateHandler)
http.HandleFunc("/unit1/thanks", unit1.ThanksHandler)
}
func mainHandler(w http.ResponseWriter, r *http.Request){
c := appengine.NewContext(r)
c.Infof("Requested URL: %v", r.URL)
fmt.Fprint(w,"hello Udacity with Go!")
}
I am using the context interface to log in the console.

c.Infof("Requested URL: %v", r.URL)
This will display on your console as follows:

2013/06/09 19:38:14 INFO: Requested URL: /
Also, I decided to do a package for every unit so that I can simply import each package like this:

import (
"fmt"
"net/http"
"appengine"
"unit1"
"unit2"
"unit3"
)

I won't be externalizing this so there is no need to do the github/santiaago/.. organization for packages.


Unit 1: Date and Thanks Handlers


The /unit1/date url has a simple form with month, day and year inputs and a submit button.
In Jinja this is the template that was used for the course:

<form method="post">
What is your birthday?
<br>
<label>Month
<input name="month" value="%(month)s">
</label>
<label>Day
<input name="day" value="%(day)s">
</label>
<label>Year
<input name="year" value="%(year)s">
</label>
<div style="color:red">%(error)s</div>
<br>
<br>
<input type="submit">
</form>
Go handles templates in the html package, so there is no need to install jinja or any template API, that is a great plus. Here is my resulting template for this part of the course.

<html>
<body>
<form method="post">
What is your birthday?
<br>
<label>Month
<input name="month" value="{{.Month}}">
</label>
<label>Day
<input name="day" value="{{.Day}}">
</label>
<label>Year
<input name="year" value="{{.Year}}">
</label>
<div style="color:red">{{.Error}}</div>
<br>
<br>
<input type="submit">
</form>
</body>
</html>
Some notes about this:
  • Templates in Go work with curly braces.
  • Notice that I had to add the html, and body tags to this template else it would display an error as the html is not a valid web page.
This template works with its corresponding structure. I made a structure Date for all the fields present in the html form:

type Date struct{
Month string
Day string
Year string
Error string
}
To render the form I now do the following:

func DateHandler(w http.ResponseWriter, r *http.Request){
if r.Method == "GET" {
date := Date{
Month: "",
Day: "",
Year: "",
}
if err := dateTemplate.Execute(w,date); err != nil{
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
} else if r.Method == "POST"{
// ...
}
}
where dateHTML is the html template defined above and dateTemplate is defined as follows and uses the template package:

var dateTemplate = template.Must(template.New("MyDate").Parse(dateHTML))


Post and Get methods

To handle Post and Get methods in python you would have a class for each handler and a Post and Get method as follows:

class DateHandler(webapp2.RequestHandler):
def get(self):
# to something

def post(self):
# do something else

In Go you don't have classes and each handler is a function itself. Right now I am testing the http.Request Method to differentiate each case:

func DateHandler(w http.ResponseWriter, r *http.Request){
c := appengine.NewContext(r)
c.Infof("cs253: Requested URL: %v", r.URL)
c.Infof("cs253: Http METHOD: %v",r.Method)
if r.Method == "GET" {
// do something
} else if r.Method == "POST"{
// do something else
}else{
// this is an error
}
}


Extracting information from the Form


On the post method we will extract the form information, this is a very common case.
In python you would get the information as follows:

def post(self):
user_month = self.request.get('month')
user_day = self.request.get('day')
user_year = self.request.get('year')

month = valid_month(user_month)
day = valid_day(user_day)
year = valid_year(user_year)
if not(month and day and year):
self.write_form("That's an error!",user_month,user_day,user_year)
else:
self.redirect('/unit1/thanks')

In Go this is how I handled this:

func DateHandler(w http.ResponseWriter, r *http.Request){
c := appengine.NewContext(r)
c.Infof("cs253: Requested URL: %v", r.URL)
c.Infof("cs253: Http METHOD: %v",r.Method)
if r.Method == "GET" {
// the GET method
} else if r.Method == "POST"{
d := Date{
Month: validMonth(r.FormValue("month")),
Day: validDay(r.FormValue("day")),
Year: validYear(r.FormValue("year")),
}
if d.Day == "" || d.Month == "" || d.Year == ""{
d.Error = "That's an error!"
if err := dateTemplate.Execute(w,d); err != nil{
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
http.Redirect(w,r, "/unit1/thanks", http.StatusFound)
}
}

To get the values I use the FormValue method on the http.Request. Then just execute the template with the structure.
This covers the first CS253 Unit. Next time will be about Unit 2.
Santiaago


comments powered by Disqus
b