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 python repository.

The Go repository.

1. NewPostHandler:

The code is at the following locations:: 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.

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>



// NewPostForm is the type used to hold the new post information.

type NewPostForm struct{

Subject string

Content string

Error string

}

// 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:

This template goes hand in hand with this type structure to hold the new post information:We display the Form by calling

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.

// ...

}

// Post is the type used to hold the Post information.

type Post struct {

Id int64

Subject string

Content string

Created time.Time

}

"appengine/datastore"

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

}

"/blog/postid"

p.key().id()

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()))

def blog_key(name = 'default'):

return db.Key.from_path('blogs', name)

// build url and redirect

permalinkURL := "/blog/"+strconv.FormatInt(p.Id,10)

http.Redirect(w, r, permalinkURL, http.StatusFound)

2. Permalink

2.1 Regex handlers

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:: You will need to importto 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.I decided to store the postID into the entity, it is easiear the retreive it later in order to perform a permalink redirect. In python this is easier as you can just do the following:In python this is how I created the Post:wherereturns a Key for the blog entity:Next step now, perform a redirection to the postID permalink:the Post entity is in the datastore now. We can now deal with the Permalink handler.

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),

init()

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)

}

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)

}

2.2 Retrieve the Post id

In Go you cannot do this so I had to do some searching and this is the solution I came up with.: I used the following sources and made some changes in order to have the same behavior as in python:To use it, I create atype that has it's ownmethod.This is how mymethod looks like after using theAnd this is how thelooks like:Now that myis ready we can focus on the permalink handler implementation.

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

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

2.3 Query the datastore and memcache

In Go I did not find a way to do this so I had to retreive the id from the URL.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.

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

}

func Add(c appengine.Context, item *Item) error

2.4 GOB encode and decode

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 thestructure to memcache you have to encapsulate first in a memcache Item 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.

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)

}

Register

func init(){

gob.Register(PostAndTime{})

}

//Memcache item found

var postAndTime PostAndTime



pCache := bytes.NewBuffer(item.Value)

decCache := gob.NewDecoder(pCache)

decCache.Decode(&postAndTime)

2.5 Code of PostAndTimeByID

: You need to register the type you want to encode before doing the above code, else it will panic. This is done by calling themethod. 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: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:

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

}

memcache.Get(c, memcacheKey)

memcache.Add(c, postItem)

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

2.6 Using multiple templates

Note some memcache operations like the Get method:and Add method:Also notice the datastore operation:

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}}



{{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}}



.Post

{{template "post" .Post}}

// 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())

}

}

3. BlogFrontHandler

and the post.html template looks like this:Notice that I passto the post template like this:To render these two templates together we have to parse them first and then execute the permalink template.This is all concerning the permalink handler, next is the blog front handler where we display the first 10 blog posts.

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)

}

}

// 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

}

{{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 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())

}

}

Thefunction performs a query in the datastore as follows:The writeBlog function uses two templates, the post.html template and the blog.html template.looks exactly as(I should factorize this at some point...)This covers the third CS253 unit. Next time will be about Unit 4.