Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Developing Web Applications with Go

Developing Web Applications with Go

Developing Web Applications with Go.

Ee191858f0d96ad93098694537f71998?s=128

Sau Sheong Chang

August 19, 2014
Tweet

Transcript

  1. Developing Web Apps with Go Chang  Sau  Sheong   Sep

     2014
  2. None
  3. What are 
 Web applications?

  4. How does web applications work?

  5. Browser/Client Server

  6. Browser/Client Server HTTP  Request

  7. Browser/Client Server HTTP  Request • Request  line GET /some/index.html HTTP/1.1

  8. Browser/Client Server HTTP  Request • Request  line GET /some/index.html HTTP/1.1

    • Request  headers
  9. Browser/Client Server HTTP  Request • Request  line GET /some/index.html HTTP/1.1

    • Request  headers • Empty  line
  10. Browser/Client Server HTTP  Request • Request  line GET /some/index.html HTTP/1.1

    • Request  headers • Empty  line • Message  body  (optional)
  11. Browser/Client Server HTTP  Request HTTP  Response • Request  line GET

    /some/index.html HTTP/1.1 • Request  headers • Empty  line • Message  body  (optional)
  12. Browser/Client Server HTTP  Request HTTP  Response • Request  line GET

    /some/index.html HTTP/1.1 • Request  headers • Empty  line • Message  body  (optional) • Status  line HTTP/1.1 200 OK
  13. Browser/Client Server HTTP  Request HTTP  Response • Request  line GET

    /some/index.html HTTP/1.1 • Request  headers • Empty  line • Message  body  (optional) • Status  line HTTP/1.1 200 OK • Response  headers
  14. Browser/Client Server HTTP  Request HTTP  Response • Request  line GET

    /some/index.html HTTP/1.1 • Request  headers • Empty  line • Message  body  (optional) • Status  line HTTP/1.1 200 OK • Response  headers • Empty  line
  15. Browser/Client Server HTTP  Request HTTP  Response • Request  line GET

    /some/index.html HTTP/1.1 • Request  headers • Empty  line • Message  body  (optional) • Status  line HTTP/1.1 200 OK • Response  headers • Empty  line • Message  body  (optional)
  16. Server

  17. Server HTTP  Request

  18. Server HTTP  Request 1.Process  request

  19. Server HTTP  Request 1.Process  request 2.Execute  application   logic

  20. Server HTTP  Request 1.Process  request 2.Execute  application   logic 3.Generate

     response
  21. Server HTTP  Request HTTP  Response 1.Process  request 2.Execute  application  

    logic 3.Generate  response
  22. Parts of a web app • Routes
 Processing  the  request

      • Templates
 Generating  response   • Store
 Persisting  data
  23. What does Go have?

  24. net/http

  25. net/http 10

  26. net/http • Server  -­‐  ServeMux(type),  Handler(type),   Handle(func),  HandleFunc(func),  

    ListenAndServe(func),  Serve(func) 10
  27. net/http • Server  -­‐  ServeMux(type),  Handler(type),   Handle(func),  HandleFunc(func),  

    ListenAndServe(func),  Serve(func) • Client  -­‐  Get,  Post,  Head  etc  (func) 10
  28. net/http • Server  -­‐  ServeMux(type),  Handler(type),   Handle(func),  HandleFunc(func),  

    ListenAndServe(func),  Serve(func) • Client  -­‐  Get,  Post,  Head  etc  (func) • Request,  Response,  ResponseWriter  (type) 10
  29. net/http • Server  -­‐  ServeMux(type),  Handler(type),   Handle(func),  HandleFunc(func),  

    ListenAndServe(func),  Serve(func) • Client  -­‐  Get,  Post,  Head  etc  (func) • Request,  Response,  ResponseWriter  (type) • Cookie(type) 10
  30. net/http • Server  -­‐  ServeMux(type),  Handler(type),   Handle(func),  HandleFunc(func),  

    ListenAndServe(func),  Serve(func) • Client  -­‐  Get,  Post,  Head  etc  (func) • Request,  Response,  ResponseWriter  (type) • Cookie(type) • Redirect(func) 10
  31. html/template

  32. Routes Process the request

  33. Routes package main! ! import (! "fmt"! "net/http"! )! !

    func handler(w http.ResponseWriter, r *http.Request) {! fmt.Fprintf(w, "Hello World, %s!", r.URL.Path[1:])! }! ! func main() {! http.HandleFunc("/", handler)! http.ListenAndServe(":8080", nil)! }!
  34. Routes package main! ! import (! "fmt"! "net/http"! )! !

    func main() {! mux := http.NewServeMux()! mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {! fmt.Fprintf(w, "Hello World, %s!", r.URL.Path[1:])! })! http.ListenAndServe(":8080", mux)! }!
  35. Routes package main! ! import (! "fmt"! "net/http"! )! !

    type MyHandler struct {}! ! func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {! fmt.Fprintf(w, "Hello World, %s!", r.URL.Path[1:])! }! ! func main() {! http.ListenAndServe(":8080", &MyHandler{})! }!
  36. Routes type MyHandler struct {}! ! func (h *MyHandler) ServeHTTP(w

    http.ResponseWriter, r *http.Request) {! fmt.Fprintf(w, "Hello World, %s!", r.URL.Path[1:])! }! ! func main() {! server := &http.Server{! ! Addr: "0.0.0.0:8080",! ! Handler: &MyHandler{},! ! ReadTimeout: 10 * time.Second,! ! WriteTimeout: 600 * time.Second,! ! MaxHeaderBytes: 1 << 20,! }! server.ListenAndServe() ! }
  37. Routes package main! ! import (! "fmt"! "net/http"! "github.com/julienschmidt/httprouter"! )!

    ! func main() {! router := httprouter.New() ! router.GET("/:name", hello) ! http.ListenAndServe(":8080", router)! }! ! func hello(w http.ResponseWriter, r *http.Request, p httprouter.Params) {! fmt.Fprintf(w, "Hello World, %s!", p.ByName("name"))! }!
  38. Routes package main! ! import (! "fmt"! "net/http"! "github.com/julienschmidt/httprouter"! )!

    ! func main() {! router := httprouter.New() ! router.GET("/:name", hello) ! http.ListenAndServe(":8080", router)! }! ! func hello(w http.ResponseWriter, r *http.Request, p httprouter.Params) {! fmt.Fprintf(w, "Hello World, %s!", p.ByName("name"))! }! Not  part  of  standard   Go  libraries
  39. Static assets? • Images   • Stylesheets  (CSS)   •

    Javascript   • Fonts
  40. Serve Files func main() {! router := httprouter.New() ! router.ServeFiles("/images/*filepath",

    ! http.Dir("public/images"))! router.ServeFiles("/css/*filepath", ! http.Dir("public/css"))! router.ServeFiles(“/fonts/*filepath",! http.Dir("public/fonts"))! router.ServeFiles("/js/*filepath", ! http.Dir("public/js"))! http.ListenAndServe(":8080", router)! }! !
  41. Templates Generate the response

  42. Templates 21 Hello  World!  This  is   {  name  }

    name  =  “Sau  Sheong” Hello  World!  This  is   Sau  Sheong +
  43. Templates

  44. Templates • text/template  -­‐  data  driven  templates

  45. Templates • text/template  -­‐  data  driven  templates • html/template  -­‐

     data  driven  templates  for  HTML
  46. Templates • text/template  -­‐  data  driven  templates • html/template  -­‐

     data  driven  templates  for  HTML • Actions  (data  evaluations  or  control  structure)  -­‐   delimited  by  {{  and  }}
  47. Templates • text/template  -­‐  data  driven  templates • html/template  -­‐

     data  driven  templates  for  HTML • Actions  (data  evaluations  or  control  structure)  -­‐   delimited  by  {{  and  }} • Arguments  -­‐  values/variables  in  the  template
  48. Templates • text/template  -­‐  data  driven  templates • html/template  -­‐

     data  driven  templates  for  HTML • Actions  (data  evaluations  or  control  structure)  -­‐   delimited  by  {{  and  }} • Arguments  -­‐  values/variables  in  the  template • Pass  variables  to  template,  accessible  through   the  ‘dot’  {{ . }}
  49. Templates • text/template  -­‐  data  driven  templates • html/template  -­‐

     data  driven  templates  for  HTML • Actions  (data  evaluations  or  control  structure)  -­‐   delimited  by  {{  and  }} • Arguments  -­‐  values/variables  in  the  template • Pass  variables  to  template,  accessible  through   the  ‘dot’  {{ . }} • Can  define  variables  (start  with  $)
  50. Templates

  51. Templates • Pipeline  -­‐  a  chained  sequence  of  commands

  52. Templates • Pipeline  -­‐  a  chained  sequence  of  commands •

    Function  -­‐  global  functions  (predefined)  or   custom  functions
  53. Templates • Pipeline  -­‐  a  chained  sequence  of  commands •

    Function  -­‐  global  functions  (predefined)  or   custom  functions • Include  other  templates  with  keyword   ‘template’
  54. Generating response

  55. Generating response 1. Set  {{ define <template name> }}  in

     the   template
  56. Generating response 1. Set  {{ define <template name> }}  in

     the   template 2. Allocate  the  template  (in  the  route)  using  New()
  57. Generating response 1. Set  {{ define <template name> }}  in

     the   template 2. Allocate  the  template  (in  the  route)  using  New() 3. Create  the  template  by  parsing  one  or  more   files  using  Parse(),  ParseFiles()  or   ParseGlob()  
  58. Generating response 1. Set  {{ define <template name> }}  in

     the   template 2. Allocate  the  template  (in  the  route)  using  New() 3. Create  the  template  by  parsing  one  or  more   files  using  Parse(),  ParseFiles()  or   ParseGlob()   4. Execute  the  template,  passing  the  output   writer  and  the  argument  (or  nil  if  none)
  59. t1.html 25 {{ define "t1" }}! ! <!DOCTYPE html>! <html>!

    {{ template "header" }}! <body>! Hello World, {{ . }}!! </body>! </html>! ! {{ end }}!
  60. header.html 26 {{ define "header" }}! <head>! <meta http-equiv="Content-Type" content="text/html;

    charset=utf-8">! <title>Template Example</title>! </head>! {{ end }}!
  61. templates.go 27 package main! ! import (! "os"! "html/template"! )!

    ! func main() {! t := template.New("t1")! t = template.Must(t.ParseGlob("*.html")) ! t.Execute(writer, "Sau Sheong")! }!
  62. templates.go 27 package main! ! import (! "os"! "html/template"! )!

    ! func main() {! t := template.New("t1")! t = template.Must(t.ParseGlob("*.html")) ! t.Execute(writer, "Sau Sheong")! }! Allocate
  63. templates.go 27 package main! ! import (! "os"! "html/template"! )!

    ! func main() {! t := template.New("t1")! t = template.Must(t.ParseGlob("*.html")) ! t.Execute(writer, "Sau Sheong")! }! Create
  64. templates.go 27 package main! ! import (! "os"! "html/template"! )!

    ! func main() {! t := template.New("t1")! t = template.Must(t.ParseGlob("*.html")) ! t.Execute(writer, "Sau Sheong")! }! Execute
  65. templates.go 27 package main! ! import (! "os"! "html/template"! )!

    ! func main() {! t := template.New("t1")! t = template.Must(t.ParseGlob("*.html")) ! t.Execute(writer, "Sau Sheong")! }!
  66. Response 28 <!DOCTYPE html>! <html>! ! <head>! <meta http-equiv="Content-Type" content="text/html;

    charset=utf-8">! <title>Template Example</title>! </head>! ! <body>! Hello World, Sau Sheong!! </body>! </html>
  67. Response 28 <!DOCTYPE html>! <html>! ! <head>! <meta http-equiv="Content-Type" content="text/html;

    charset=utf-8">! <title>Template Example</title>! </head>! ! <body>! Hello World, Sau Sheong!! </body>! </html> from  header.html
  68. Response 28 <!DOCTYPE html>! <html>! ! <head>! <meta http-equiv="Content-Type" content="text/html;

    charset=utf-8">! <title>Template Example</title>! </head>! ! <body>! Hello World, Sau Sheong!! </body>! </html> from  header.html replaces  {{  .  }}
  69. templates.go 29 type Presentation struct {! Title string! Author string!

    }! ! func main() {! t := template.New("t2")! t = template.Must(t.ParseGlob("*.html")) ! presso := Presentation{! Title: "Write Web Applications with Go",! Author: "Chang Sau Sheong",! }! t.Execute(writer, presso)! }!
  70. t2.html 30 {{ define "t2" }}! ! <!DOCTYPE html>! <html>!

    {{ template "header" }}! <body>! <h1>{{ .Title }}</h1>! <h3>{{ .Author}}</h3>! </body>! </html>! ! {{ end }}!
  71. Response 31 <!DOCTYPE html>! <html>! ! <head>! <meta http-equiv="Content-Type" content="text/html;

    charset=utf-8">! <title>Template Example</title>! </head>! ! <body>! <h1>Write Web Applications with Go</h1>! <h3>Chang Sau Sheong</h3>! </body>! </html>!
  72. Store Persist application data

  73. SQL

  74. SQL • Direct  SQL  statements

  75. SQL • Direct  SQL  statements • Mappers  (ORMs)

  76. SQL • Direct  SQL  statements • Mappers  (ORMs) • gorp

  77. SQL • Direct  SQL  statements • Mappers  (ORMs) • gorp

    • hood
  78. SQL • Direct  SQL  statements • Mappers  (ORMs) • gorp

    • hood • beego  orm
  79. SQL • Direct  SQL  statements • Mappers  (ORMs) • gorp

    • hood • beego  orm • gorm
  80. database/sql

  81. 35 package main! ! import(! "log"! "fmt" ! "database/sql"! _

    "github.com/lib/pq"! )! ! func main() {! db, _ := sql.Open("postgres", "dbname=blog sslmode=disable”)! defer db.Close() ! ! rows, err := db.Query("SELECT name, email FROM users WHERE email = $1", “sausheong@gmail.com”); if err != nil {! log.Fatal(err)! }! for rows.Next() {! var name, email string! if err := rows.Scan(&name, &email); err != nil {! log.Fatal(err)! }! fmt.Printf("%s - %s\n", name, email)! }! }!
  82. Ok when small codebase

  83. Ok when small codebase Painful otherwise

  84. 37 https://github.com/jinzhu/gorm

  85. 37 https://github.com/jinzhu/gorm This  is  NOT  part  of     standard

     Go  libraries
  86. Define models 38 type User struct {! Id int64! Uuid

    string `sql:"size:255;not null;unique"`! Email string `sql:"size:255"`! Password string `sql:"size:255"`! Name string `sql:"size:255"`! CreatedAt time.Time! }! ! type Session struct {! Id int64! Uuid string `sql:"size:255;not null;unique"`! UserId int64 ! CreatedAt time.Time! } // foreign key for User table
  87. Initialize database 39 var DB gorm.DB! ! // initialize gorm!

    func init() {! var err error! DB, err = gorm.Open("postgres", "user=test password=test dbname=test sslmode=disable")! if err != nil {! panic(fmt.Sprintf("Got error when connect database, the error is '%v'", err))! }! migrate()! }
  88. Migrate tables 40 // Create tables! func migrate() {! DB.Exec("DROP

    TABLE users;DROP TABLE sessions;")! DB.AutoMigrate(User{})! DB.AutoMigrate(Session{})! }
  89. Create 41 user := User{Name: "Sau Sheong", ! Email: "sausheong@gmail.com",

    ! Password: “passw0rd”,! }! DB.Save(&user) ! !
  90. Query 42 var user = User{}! // Get the first

    record! err := DB.Where("email = ?", ! "sausheong@gmail.com").First(&user).Error! if err != nil {! fmt.Println("Cannot retrieve this user:", err)! } ! ! // Get the multiple records! var users []User! err := DB.Where("email LIKE ?", ! "%@gmail.com").Find(&users).Error! if err != nil {! fmt.Println("Cannot retrieve users:", err)! }
  91. Update & Delete 43 var user = User{}! // Get

    the first record! err := DB.Where("email = ?", ! "sausheong@gmail.com").First(&user).Error! if err != nil {! fmt.Println("Cannot retrieve this user:", err)! } ! ! // Update the name! user.Name = "Batman"! DB.Save(&user)! ! // Delete the record! DB.Delete(&user)!
  92. Callbacks 44 // Before creating a user, add in the

    uuid! func (u *User) BeforeCreate() (err error) {! ! ! u.Password = encrypt(u.Password)! ! u.Uuid = createUUID()! ! return! }
  93. Associations 45 user := User{Name: "Sau Sheong", ! Email: "sausheong@gmail.com",

    ! Password: "test",! }! DB.Save(&user) ! ! sess := Session{UserId: user.Id}! DB.Save(&sess)! ! var usr User! err := DB.Model(&sess).Related(&usr).Error! if err != nil {! fmt.Println("Cannot retrieve this user:", err)! } ! ! var session Session! err := DB.Model(&user).Related(&session).Error! if err != nil {! fmt.Println("Cannot retrieve this session:", err)! }
  94. Associations 45 user := User{Name: "Sau Sheong", ! Email: "sausheong@gmail.com",

    ! Password: "test",! }! DB.Save(&user) ! ! sess := Session{UserId: user.Id}! DB.Save(&sess)! ! var usr User! err := DB.Model(&sess).Related(&usr).Error! if err != nil {! fmt.Println("Cannot retrieve this user:", err)! } ! ! var session Session! err := DB.Model(&user).Related(&session).Error! if err != nil {! fmt.Println("Cannot retrieve this session:", err)! } Retrieve  user  given   the  session
  95. Associations 45 user := User{Name: "Sau Sheong", ! Email: "sausheong@gmail.com",

    ! Password: "test",! }! DB.Save(&user) ! ! sess := Session{UserId: user.Id}! DB.Save(&sess)! ! var usr User! err := DB.Model(&sess).Related(&usr).Error! if err != nil {! fmt.Println("Cannot retrieve this user:", err)! } ! ! var session Session! err := DB.Model(&user).Related(&session).Error! if err != nil {! fmt.Println("Cannot retrieve this session:", err)! } Retrieve  session   given  the  user
  96. Associations 45 user := User{Name: "Sau Sheong", ! Email: "sausheong@gmail.com",

    ! Password: "test",! }! DB.Save(&user) ! ! sess := Session{UserId: user.Id}! DB.Save(&sess)! ! var usr User! err := DB.Model(&sess).Related(&usr).Error! if err != nil {! fmt.Println("Cannot retrieve this user:", err)! } ! ! var session Session! err := DB.Model(&user).Related(&session).Error! if err != nil {! fmt.Println("Cannot retrieve this session:", err)! }
  97. Other features

  98. Other features • Query  chains

  99. Other features • Query  chains • Batch  updates  and  deletes

  100. Other features • Query  chains • Batch  updates  and  deletes

    • Soft  delete  
  101. Other features • Query  chains • Batch  updates  and  deletes

    • Soft  delete   • Associations
  102. Other features • Query  chains • Batch  updates  and  deletes

    • Soft  delete   • Associations • Has  One
  103. Other features • Query  chains • Batch  updates  and  deletes

    • Soft  delete   • Associations • Has  One • Belongs  To
  104. Other features • Query  chains • Batch  updates  and  deletes

    • Soft  delete   • Associations • Has  One • Belongs  To • Has  Many
  105. Other features • Query  chains • Batch  updates  and  deletes

    • Soft  delete   • Associations • Has  One • Belongs  To • Has  Many • Many  to  Many
  106. Other features

  107. Other features • FirstOrInit,  FirstOrCreate

  108. Other features • FirstOrInit,  FirstOrCreate • Order,  Limit,  Offset,  Count

  109. Other features • FirstOrInit,  FirstOrCreate • Order,  Limit,  Offset,  Count

    • Group,  Having,  Joins
  110. Other features • FirstOrInit,  FirstOrCreate • Order,  Limit,  Offset,  Count

    • Group,  Having,  Joins • Transactions
  111. Other features • FirstOrInit,  FirstOrCreate • Order,  Limit,  Offset,  Count

    • Group,  Having,  Joins • Transactions • Logger
  112. Frameworks • Gorilla  web  toolkit   • Martini   •

    Gin  Gonic   • Beego   • Revel  
  113. Testing Testing Go Web Apps

  114. Testing is built-in

  115. **_test.go

  116. func TestXXX(t *testing.T) { }

  117. func TestXXX(t *testing.T) { } //  Some  test  code

  118. > go build

  119. > go build Compile  Go  code

  120. > go build > go test Compile  Go  code

  121. > go build > go test Compile  Go  code Run

     tests  on  Go  code
  122. testing net/http/httptest

  123. testing net/http/httptest Generic  testing   library

  124. testing net/http/httptest Generic  testing   library Testing  library  for  

    web  applications
  125. Unit Testing 55 package main! ! import( ! "testing"! )!

    ! // Test package function that adds a new user! func TestAddUser(t *testing.T) {! addUser("Sau Sheong", "sausheong@gmail.com", "password")! if val, ok := users["sausheong@gmail.com"]; !ok {! t.Errorf("Cannot add user")! } else {! if val.Name != "Sau Sheong" {! t.Errorf("User name is wrong")! }! }! }
  126. HTTP Testing 56 package main! ! ...! ! func TestIndex(t

    *testing.T) { ! router := httprouter.New()! router.GET("/", index)! writer := httptest.NewRecorder()! request, _ := http.NewRequest("GET", "/", nil) ! router.ServeHTTP(writer, request)! ! if writer.Code != 302 {! t.Errorf("Response code is %v", writer.Code)! } ! }
  127. HTTP Testing 57 func TestSignUp(t *testing.T) {! router := httprouter.New()!

    router.POST("/signup", createUser)! writer := httptest.NewRecorder()! body := strings.NewReader("name=Sau Sheong&email=sausheong@gmail.com&password=password")! request, _ := http.NewRequest("POST", "/signup", body)! request.Header.Add("Content-Type", "application/x-www- form-urlencoded")! router.ServeHTTP(writer, request)! ! if writer.Code != 302 {! t.Errorf("Response code is %v", writer.Code)! }! if writer.Header().Get("Location") != "/login" {! t.Errorf("Location is %v", writer.Header().Get("Location"))! }! }
  128. HTTP Testing 58 func TestAuthenticate(t *testing.T) {! addUser("Sau Sheong", "sausheong@gmail.com",

    "password")! router := httprouter.New()! router.POST("/login", authenticate) ! writer := httptest.NewRecorder()! body := strings.NewReader("email=sausheong@gmail.com&password=pa ssword")! request, _ := http.NewRequest("POST", "/login", body)! request.Header.Add("Content-Type", "application/x-www- form-urlencoded")! router.ServeHTTP(writer, request)! ...! if !strings.HasPrefix(writer.Header().Get("Set- Cookie"), "pixelate_cookie") {! t.Errorf("Cookie not set")! }! }
  129. Go on the Cloud Deploying to Google App Engine

  130. Go App Engine 60

  131. Go App Engine • Download  the  Go  App  Engine  SDK

    60
  132. Go App Engine • Download  the  Go  App  Engine  SDK

    • Install  Mercurial 60
  133. Go App Engine • Download  the  Go  App  Engine  SDK

    • Install  Mercurial • Add  a  file  app.yaml  to  the  same  directory 60
  134. Go App Engine • Download  the  Go  App  Engine  SDK

    • Install  Mercurial • Add  a  file  app.yaml  to  the  same  directory 60 application: pixelate! version: 1! runtime: go! api_version: go1! ! handlers:! - url: /.*! script: _go_app
  135. Go App Engine 61

  136. Go App Engine • Code  changes  required: 61

  137. Go App Engine • Code  changes  required: • Change  all

     package  names  from  main  to  the   application  name  (in  this  case  it  is   sausheong-pixelate) 61
  138. Go App Engine • Code  changes  required: • Change  all

     package  names  from  main  to  the   application  name  (in  this  case  it  is   sausheong-pixelate) • Change  the  main()  function  to  init() 61
  139. Go App Engine • Code  changes  required: • Change  all

     package  names  from  main  to  the   application  name  (in  this  case  it  is   sausheong-pixelate) • Change  the  main()  function  to  init() • Change  from  http.ListenAndServe()  to   http.Handle(mux) 61
  140. Run locally 62

  141. Run locally • Run  at  command  line: 62

  142. Run locally • Run  at  command  line: 62 go_appengine/dev_appserver.py sausheong-pixelate

  143. Run locally • Run  at  command  line: • Open  browser

     to: 62 go_appengine/dev_appserver.py sausheong-pixelate
  144. Run locally • Run  at  command  line: • Open  browser

     to: 62 go_appengine/dev_appserver.py sausheong-pixelate http://localhost:8080
  145. Deploy to Google App Engine 63

  146. Deploy to Google App Engine • Create  app  on  Google

     App  Engine 63
  147. Deploy to Google App Engine • Create  app  on  Google

     App  Engine • Run  at  command  line: 63 go_appengine/goapp deploy sausheong-pixelate
  148. Deploy to Google App Engine • Create  app  on  Google

     App  Engine • Run  at  command  line: 63 go_appengine/goapp deploy sausheong-pixelate
  149. Deploy to Google App Engine • Create  app  on  Google

     App  Engine • Run  at  command  line: • Open  browser  to: 63 go_appengine/goapp deploy sausheong-pixelate
  150. Deploy to Google App Engine • Create  app  on  Google

     App  Engine • Run  at  command  line: • Open  browser  to: 63 go_appengine/goapp deploy sausheong-pixelate http://sausheong-pixelate.appspot.com
  151. My projects 64 • Pixelate
 Convert  a  JPEG  file  to

     a  pixelated  version   • TodayReader
 A  faster,  simpler  Today  (newspaper)  reader   • GoAuthServ
 Simple  authentication  service   • Polyglot
 Multi-­‐language  web  framework
  152. Thank you sausheong@gmail.com sauchang@paypal.com http://github.com/sausheong @sausheong