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.
To start with Go and App Engine there are a lot of resources out there. Here are the ones I used the most:
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.~/google_appengine/appcfg.py update udacity.cs253.goNote: udacity.cs253.go is the name of my application.
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 webapp2And here is what my main.go looks like after some work.
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)
package mainI am using the context interface to log in the console.
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!")
}
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"
)
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">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.
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>
<html>Some notes about this:
<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>
type Date struct{To render the form I now do the following:
Month string
Day string
Year string
Error string
}
func DateHandler(w http.ResponseWriter, r *http.Request){where dateHTML is the html template defined above and dateTemplate is defined as follows and uses the template package:
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"{
// ...
}
}
var dateTemplate = template.Must(template.New("MyDate").Parse(dateHTML))
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
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
}
}
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')
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)
}
}