On this post I continue to port Udacity course CS253 Web Development from python to Go. This time I am working on Unit 3. This is a long unit with a lot of ground so let's get started.
You can check out my previous blog post if you haven't done it yet:


The code is at the following locations:
Note: Feel free to comment and share your thoughts, I will be more than happy to update and improve my code and learn to better develop in Go.

This unit has three parts. The first part concerns the new Post handler which let you create a Post Entity in the Datastore. The second part is about the Permalink handler, this deals with RegexHandlers, memcache and templates. The third part deals with the FrontBlog Handler which displays the most recent posts.


1. NewPostHandler:


The main architecture of the NewPostHandler is the following:
A GET method to display the Form.
A POST method to read the html Form, create the post and do a redirection to the created post.

// NewPostHandler is the HTTP handler to create a new Post
func NewPostHandler(w http.ResponseWriter, r *http.Request){
c := appengine.NewContext(r)
if r.Method == "GET" {
// display the form
}else if r.Method == "POST"{
// read the html form
// create a post entity
// redirect to the created post
}
}


1.1 The GET method:

The NewPostHandler displays a simple Form to create a new post. This is how it looks like:

<form method="post">
<label>
<div>subject</div>
<input type="text" name="subject" value="{{.Subject}}">
</label>
<label>
<div>content</div><textarea name="content">{{.Content}}</textarea>
</label>
<div class="error">{{.Error}}</div>
<br>
<input type="submit">
</form>
This template goes hand in hand with this type structure to hold the new post information:

// NewPostForm is the type used to hold the new post information.
type NewPostForm struct{
Subject string
Content string
Error string
}
We display the Form by calling writeNewPostForm:

// executes the newpost.html template with NewPostForm type as param.
func writeNewPostForm(c appengine.Context, w http.ResponseWriter, postForm *NewPostForm){
tmpl, err := template.ParseFiles("templates/newpost.html")
if err != nil{
c.Errorf(err.Error())
}
err = tmpl.Execute(w,postForm)
if err != nil{
c.Errorf(err.Error())
}
}


1.2 The POST method:

As always we read the information from the form by calling r.FormValue(), then as in the previous unit we check the validity of the inputs before doing any further work.

postForm := NewPostForm{
r.FormValue("subject"),
r.FormValue("content"),
"",
}
if !(tools.IsStringValid(postForm.Subject) &&
tools.IsStringValid(postForm.Content)){

postForm.Error = "We need to set both a subject and some content"
writeNewPostForm(w, &postForm)
}else{
// create a blog post here.
// ...
}
Once we are sure about the inputs, we can create a blog post. The first step, as for the python version, is to have an entity Post:

// Post is the type used to hold the Post information.
type Post struct {
Id int64
Subject string
Content string
Created time.Time
}
Note: You will need to import "appengine/datastore" to perform the following operations.
To create an entity Post, I start by creating an ID, a Key and then I persist the Post entity via a Put method.

postID, _, _ := datastore.AllocateIDs(c, "Post", nil, 1)
key := datastore.NewKey(c, "Post", "", postID, nil)
p := models.Post{
postID,
postForm.Subject,
postForm.Content,
time.Now(),
}
key, err := datastore.Put(c, key, &p)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
I decided to store the postID into the entity, it is easiear the retreive it later in order to perform a permalink redirect "/blog/postid". In python this is easier as you can just do the following: p.key().id()
In python this is how I created the Post:

def post(self):
user_subject = self.request.get('subject')
user_content = self.request.get('content')

subject = valid_str(user_subject)
content = valid_str(user_content)
if not(subject and content):
self.write_form("We need to set both a subject and some content",user_subject,user_content)
else:
p = Post(parent = blog_key(), subject = user_subject,content = user_content)

p.put()
#redirect to permalink
self.redirect("/unit3/blog/%s" % str(p.key().id()))
where blog_key returns a Key for the blog entity:

def blog_key(name = 'default'):
return db.Key.from_path('blogs', name)
Next step now, perform a redirection to the postID permalink:

// build url and redirect
permalinkURL := "/blog/"+strconv.FormatInt(p.Id,10)
http.Redirect(w, r, permalinkURL, http.StatusFound)
the Post entity is in the datastore now. We can now deal with the Permalink handler.


2. Permalink



2.1 Regex handlers

The first thing to work on when starting the permalink is how to dispatch the handlers.
In python this is easily done as the WSGIApplication is able to parse regular expressions.

('/unit3/blog/?',BlogFront),
('/unit3/blog/newpost',NewPostHandler),
('/unit3/blog/([0-9]+)', Permalink),
In Go you cannot do this so I had to do some searching and this is the solution I came up with.
Note: I used the following sources and made some changes in order to have the same behavior as in python:To use it, I create a RegexpHandler type that has it's own HandleFunc method.
This is how my init() method looks like after using the RegexpHandler:

func init(){
h := new( tools.RegexpHandler)

h.HandleFunc("/",mainHandler)
h.HandleFunc("/blog/?", unit3.BlogFrontHandler)
h.HandleFunc("/blog/newpost", unit3.NewPostHandler)
h.HandleFunc("/blog/[0-9]+/?",unit3.PermalinkHandler)

http.Handle("/",h)
}
And this is how the RegexpHandler looks like:

package tools
import (
"net/http"
"regexp"
)

type route struct {
pattern *regexp.Regexp
handler http.Handler
}

type RegexpHandler struct {
routes []*route
}

func (h *RegexpHandler) Handler(pattern *regexp.Regexp, handler http.Handler) {
h.routes = append(h.routes, &route{pattern, handler})
}

func (h *RegexpHandler) HandleFunc(strPattern string, handler func(http.ResponseWriter, *http.Request)) {
// encapsulate string pattern with start and end constraints
// so that HandleFunc would work as for Python GAE
pattern := regexp.MustCompile("^"+strPattern+"$")
h.routes = append(h.routes, &route{pattern, http.HandlerFunc(handler)})
}

func (h *RegexpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for _, route := range h.routes {
if route.pattern.MatchString(r.URL.Path) {
route.handler.ServeHTTP(w, r)
return
}
}
// no pattern matched; send 404 response
http.NotFound(w, r)
}
Now that my permalinkHandler is ready we can focus on the permalink handler implementation.


2.2 Retrieve the Post id

First thing to do is retrieve the id from the url. In python this is really easy as the id is a parameter of your get() method:

class Permalink(renderHandler):
def get(self, post_id):
# do something awesome with post_id
In Go I did not find a way to do this so I had to retreive the id from the URL.

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

if r.Method == "GET" {

path := strings.Split(r.URL.String(), "/")
intID, _ := strconv.ParseInt(path[2], 0, 64)
// do something awesome with the intID
Now that I have the post ID I can perform a query to the datastore and cache it into memcache if it does not yet exist.


2.3 Query the datastore and memcache

I created a function called PostAndTimeByID which returns me the following structure:

// PostAndTime is the type used to hold an entity Post and it's "cache hit time" information.
type PostAndTime struct{
Post Post
Cache_hit_time time.Time
}
I had to work a little bit more on this as you need to encode the information you want to put in the cache. To add the PostAndTime structure to memcache you have to encapsulate first in a memcache Item.

func Add(c appengine.Context, item *Item) error
An Item has a Key and its value. The value needs to be a slice of bytes. So to pass the bytes of the PostAndTime structure, I decided to use gob (the other option was to use the json encoder) to encode and decode the bytes of my structure.


2.4 GOB encode and decode

Here is how to encode the structure to bytes with gob and set it to memcache:

// record information in cache for next time
mCache := new(bytes.Buffer)
encCache := gob.NewEncoder(mCache)
encCache.Encode(postAndTime)

postItem := &memcache.Item{
Key: memcacheKey,
Value: mCache.Bytes(),
}
if err := memcache.Add(c, postItem); err == memcache.ErrNotStored {
c.Errorf("cs253: postAndTime with key %q already exists", item.Key)
} else if err != nil {
c.Errorf("error adding item: %v", err)
}
Note: You need to register the type you want to encode before doing the above code, else it will panic. This is done by calling the Register method. It is important to mention that you cannot register a type more than once or it will panic as well so the best is to have this in the init function of your package as follows:

func init(){
gob.Register(PostAndTime{})
}
When the item is found in the memcache, you will have to do the opposite and decode the bytes into the structure. This is how I did it:

//Memcache item found
var postAndTime PostAndTime

pCache := bytes.NewBuffer(item.Value)
decCache := gob.NewDecoder(pCache)
decCache.Decode(&postAndTime)


2.5 Code of PostAndTimeByID

Here is the full code for PostAndTimeByID function:

// PostAndTimeByID returns a PostAndTime for the requested id
func PostAndTimeByID(c appengine.Context, id int64)( PostAndTime){
memcacheKey := "posts_and_time"+strconv.FormatInt(id, 10)

var postAndTime PostAndTime
//query cache first with memcache key
if item, err := memcache.Get(c, memcacheKey); err == memcache.ErrCacheMiss {
//item not in the cache : will perform query instead

key := datastore.NewKey(c, "Post", "", id, nil)

if err := datastore.Get(c, key, &postAndTime.Post); err != nil {
c.Errorf("cs253: post not found : %v", err)
}
// get current hit time
postAndTime.Cache_hit_time = time.Now()
// record information in cache for next time
mCache := new(bytes.Buffer)
encCache := gob.NewEncoder(mCache)
encCache.Encode(postAndTime)

postItem := &memcache.Item{
Key: memcacheKey,
Value: mCache.Bytes(),
}
if err := memcache.Add(c, postItem); err == memcache.ErrNotStored {
c.Errorf("cs253: postAndTime with key %q already exists", item.Key)
} else if err != nil {
c.Errorf("error adding item: %v", err)
}

} else if err != nil {
c.Errorf("cs253: Memcache error getting item: %v",err)
} else {
//Memcache item found

pCache := bytes.NewBuffer(item.Value)
decCache := gob.NewDecoder(pCache)
decCache.Decode(&postAndTime)
}
return postAndTime
}
Note some memcache operations like the Get method:

memcache.Get(c, memcacheKey)
and Add method:
memcache.Add(c, postItem)
Also notice the datastore operation:

datastore.Get(c, key, &postAndTime.Post)


2.6 Using multiple templates

Once you have the post and the memcache hit time you can render the permalink page. This time we are defining the template as a separate file and we are using two templates to do this, a permalink.html template for the structure of the page and a post.html template of the post information (the post.html will be used later on in another handler).
This is how the permalink.html template looks like:

{{define "permalink"}}
<!DOCTYPE html>
<html>
<head>
<link type="text/css" rel="stylesheet" href="/static/main.css" />
<title>CS 253 Blog in Go!</title>
</head>
<body>
<a href="/blog" class="main-title">
Blog
</a>
{{template "post" .Post}}
<div class="age">
queried {{.Cache_hit_time}} seconds
</div>
</body>
</html>
{{end}}
and the post.html template looks like this:

{{define "post"}}
<div class="post">
<div class="post-heading">
<div class="post-title">
<a href="/blog/{{.Id}}" class="post-link">{{.Subject}}</a>
</div>

<div class="post-date">
{{.Created.Format "02-01-2006 15:04:05"}}
</div>
</div>

<div class="post-content">
{{.Content}}
</div>
</div>
{{end}}
Notice that I pass .Post to the post template like this:
{{template "post" .Post}}
To render these two templates together we have to parse them first and then execute the permalink template.
// writePermalink executes the permalink.html template with a PostAndTime type as param.
func writePermalink(c appengine.Context, w http.ResponseWriter, p models.PostAndTime){
tmpl, err := template.ParseFiles("templates/permalink.html","templates/post.html")
if err != nil{
c.Errorf(err.Error())
}
err = tmpl.ExecuteTemplate(w,"permalink",p)
if err !=nil{
c.Errorf(err.Error())
}
}
This is all concerning the permalink handler, next is the blog front handler where we display the first 10 blog posts.


3. BlogFrontHandler


The BlogFrontHandler is a simple one. It performs a query for the recent posts and renders them. This is how it looks:

// BlogFrontHandler is the HTTP handler for displaying the most recent posts.
func BlogFrontHandler(w http.ResponseWriter, r *http.Request){
c := appengine.NewContext(r)
if r.Method == "GET" {
posts := models.RecentPosts(c)
writeBlog(w, posts)
}
}
The RecentPosts function performs a query in the datastore as follows:

// RecentPosts returns a pointer to a slice of Posts.
// orderBy creation time, limit = 20
func RecentPosts(c appengine.Context)([]*Post){
q := datastore.NewQuery("Post").Limit(20).Order("-Created")
var posts []*Post
if _, err := q.GetAll(c, &posts); err != nil {
c.Errorf("cs253: Error: %v",err)
return nil
}
return posts
}
The writeBlog function uses two templates, the post.html template and the blog.html template.

{{define "blog"}}
<!-- some html content -->
<div id="content">
{{range $i, $p :=.}}
{{template "post" $p}}
{{end}}
<div class="age">
queried cache_last_hit seconds ago
</div>
</div>
<!-- some closing tags -->
{{end}}
writeBlog looks exactly as writePost (I should factorize this at some point...)

// writeBlog executes the blog template with a slice of Posts.
func writeBlog(c appengine.Context, w http.ResponseWriter, posts []*models.Post){
tmpl, err := template.ParseFiles("templates/blog.html","templates/post.html")
if err != nil{
c.Errorf(err.Error())
}
err = tmpl.ExecuteTemplate(w,"blog",posts)
if err != nil{
c.Errorf(err.Error())
}
}
This covers the third CS253 unit. Next time will be about Unit 4.


comments powered by Disqus