Go Programming Blueprints 2nd
Go Programming Blueprints 2nd
www.packtpub.com
matryer.com
 @dahernan
 @tylerb
@mazondo
 @golangbridge
 https://www.linkedin.com/in/ham
rah
 www.PacktPub.com
www.PacktPub.com
service@packtpub.com
www.PacktPub.com
https://www.packtpub.com/mapt
Appendix
 https://github.com/matryer/gobluep
rints
 README
 htt
ps://github.com/matryer/goblueprints
Chapter 1
Chapter 2
Chapter 3
Chapter 4
Chapter 5
Chapter 6
 Chapter 5
http.HandlerFunc
Chapter 7
Chapter 8
Chapter 9
Chapter 10
Chapter 11
 Chapter 9
Appendix
 https://g
olang.org/doc/install#requirements
Appendix
 webApp.war
package meander
type Cost int8
const (
 _ Cost = iota
 Cost1
 Cost2
 Cost3
 Cost4
 Cost5
)
 feedback@packtpub.com
www.packtpub.com/authors
 http://www.p
acktpub.com http://www.packtpub.c
om/support
 https://github.com/PacktPubl
ishing/Go-Programming-Blueprints
 https://github.com/PacktPublishing/
http://www.packtpub.com/submit-errata
 https://www.packtpub.com/books/conten
t/support
 copyright@packtpub.com
questions@packtpub.com
 
net/http
net/http
 http.Handler
 https://github.co
 m/matryer/goblueprints/tree/master/chapter1/chat
GOPATH Appendix
package main
import (
 "log"
 "net/http"
)
func main() {
 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 w.Write([]byte(`
 <html>
 <head>
 <title>Chat</title>
 </head>
 <body>
 Let's chat!
 </body>
 </html>
 ))
 })
 // start the web server
 if err := http.ListenAndServe(":8080", nil); err != nil {
 log.Fatal("ListenAndServe:", err)
 }
 }
net/http
:8080 ListenAndServe
 http.HandleFunc /
 http://localhost:8080/
 func(w http.ResponseWriter, r
*http.Request)
package main
package chat
main.go
go run
go build
 http://localhost:8080
Hello {{name}}, how are you
{{name}}
 text/template
 html/template html/template
<html>
 <head>
 <title>Chat</title>
 </head>
 <body>
 Let's chat (from template)
 </body>
</html>
 struct
 filename
 sync.Once
Appendix
 templateHandler ServeHTTP
 http.HandleFunc
 http.ResponseWriter ServeHTTP
http.Handler http.Handle
 http://golang.org/pkg/net/http/#Handler
 http.Handler ServeHTTP
 net/http
 NewTemplateHandler
main
 ServeHTTP
 sync.Once
 ServeHTTP
ServeHTTP
ServeHTTP
templateHandler main
func main() {
 // root
 http.Handle("/", &templateHandler{filename: "chat.html"})
 // start the web server
 if err := http.ListenAndServe(":8080", nil); err != nil {
 log.Fatal("ListenAndServe:", err)
 }
}
 templateHandler http.Handler
http.Handle
 templateHandler
 chat.html &
 http.Handle
 templateHandler
 go run
 main.go
go build .go
 -o
 room
 client
 websocket
https://github.com/gorilla/websocket
package main
import (
 "github.com/gorilla/websocket"
)
// client represents a single chatting user.
type client struct {
 // socket is the web socket for this client.
 socket *websocket.Conn
 // send is a channel on which messages are sent.
 send chan []byte
 // room is the room this client is chatting in.
 room *room
}
 socket
 send
room
go get websocket
 room
 room.go
package main
type room struct {
 // forward is a channel that holds incoming messages
 // that should be forwarded to the other clients.
 forward chan []byte
}
forward
 client.go
 client read write
client
read ReadMessage
 forward room
 'the socket has died'
 write send
 WriteMessage
 for
 defer
 c.socket.Close()
 return
 defer
 defer
 close
c.room.forward <- msg
room.go
package main
type room struct {
 // forward is a channel that holds incoming messages
 // that should be forwarded to the other clients.
 forward chan []byte
 // join is a channel for clients wishing to join the room.
 join chan *client
 // leave is a channel for clients wishing to leave the room.
 leave chan *client
 // clients holds all current clients in this room.
 clients map[*client]bool
}
 join leave
 clients
 select
 select
for
r.clients
join r.clients
true
 true
 leave client
 send forward
 send
write
room http.Handler
 ServeHTTP
 room.go
 const (
 socketBufferSize = 1024
 messageBufferSize = 256
 )
 var upgrader = &websocket.Upgrader{ReadBufferSize: socketBufferSize,
 WriteBufferSize: socketBufferSize}
 func (r *room) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 socket, err := upgrader.Upgrade(w, req, nil)
 if err != nil {
 log.Fatal("ServeHTTP:", err)
 return
 }
 client := &client{
 socket: socket,
 send: make(chan []byte, messageBufferSize),
 room: r,
 }
 r.join <- client
 defer func() { r.leave <- client }()
 go client.write()
 client.read()
 }
ServeHTTP
websocket.Upgrader
 ServeHTTP
 upgrader.Upgrade
 join
 write
 go go
 read
r := &room{
 forward: make(chan []byte),
 join: make(chan *client),
 leave: make(chan *client),
 clients: make(map[*client]bool),
}
newRoom
 newRoom
 main main.go
func main() {
 r := newRoom()
 http.Handle("/", &templateHandler{filename: "chat.html"})
 http.Handle("/room", r)
 // get the room going
 go r.run()
 // start the web server
 if err := http.ListenAndServe(":8080", nil); err != nil {
 log.Fatal("ListenAndServe:", err)
 }
}
go
chat.html templates
<html>
 <head>
 <title>Chat</title>
 <style>
 input { display: block; }
 ul { list-style: none; }
 </style>
 </head>
 <body>
 <ul id="messages"></ul>
 <form id="chatbox">
 <textarea></textarea>
 <input type="submit" value="Send" />
 </form> </body>
</html>
messages
form </body>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
 <script>
 $(function(){
 var socket = null;
 var msgBox = $("#chatbox textarea");
 var messages = $("#messages");
 $("#chatbox").submit(function(){
 if (!msgBox.val()) return false;
 if (!socket) {
 alert("Error: There is no socket connection.");
 return false;
 }
 socket.send(msgBox.val());
 msgBox.val("");
 return false;
 });
 if (!window["WebSocket"]) {
 alert("Error: Your browser does not support web sockets.")
 } else {
 socket = new WebSocket("ws://localhost:8080/room");
 socket.onclose = function() {
 alert("Connection has been closed.");
 }
 socket.onmessage = function(e) {
 messages.append($("<li>").text(e.data));
 }
 }
 });
 </script>
http://localhost:8080/
 :8080
 main.go
8080
main main.go
func main() {
 var addr = flag.String("addr", ":8080", "The addr of the application.")
 flag.Parse() // parse the flags
 r := newRoom()
 http.Handle("/", &templateHandler{filename: "chat.html"})
 http.Handle("/room", r)
 // get the room going
 go r.run()
 // start the web server
 log.Println("Starting web server on", *addr)
 if err := http.ListenAndServe(*addr, nil); err != nil {
 log.Fatal("ListenAndServe:", err)
 }
}
 flag
addr :8080
 flag.Parse()
*addr
flag.String *string
 *
 log.Println
 templateHandler
 Execute main.go
ServeHTTP r data Execute
http.Request
Host http.Request
chat.html
 {{.Host}}
 request.Host r
text/template
http://golang.org/pkg/text/template
go build -o chat
./chat -addr=":3000"
{{.Host}}
 -addr="192.168.0.1:3000"
log.Println
main
Tracer
 templateHandler
 trace
chat
/chat
 client.go
 main.go
 room.go
/trace
tracer.go trace
package trace
// Tracer is the interface that describes an object capable of
// tracing events throughout code.
type Tracer interface {
 Trace(...interface{})
}
 trace
Tracer T
 Trace ...interface{}
 Trace
fmt.Sprint log.Fatal
Tracer
tracer_test.go trace
package trace
import (
 "testing"
)
func TestNew(t *testing.T) {
 t.Error("We haven't written our test yet")
}
 _test.go Test
 *testing.T
 trace
 t.Error TestNew
cls clear
TestNew
 import
 go get
 import "bytes"
 bytes.Buffer
 t.Errorf
 New
nil t.Error
 go test New
true true
 go test
 New
 trace.go
func New() {}
 go test
 New New
 New
 New
New
 io.Writer
 Write
 io.Writer
 bytes.Buffer
io.Writer
go test
 New Tracer
 go test
nil New
-cover
 New
 Tracer
 tracer.go
 tracer io.Writer
 out Trace
 Tracer
New
 go test
 Trace
 Trace io.Writer
 tracer t
 New
 Tracer
 tracer
 tracer
 ioutil.NopCloser
io.Reader io.ReadCloser Close
 io.Reader
 io.ReadCloser io.ReadCloser
 nopCloser
 http://golang.org/src/pkg/io/ioutil/ioutil.go
 nopCloser
trace
room.go Trace
 trace GOPATH
 $GOPATH/src trace
 $GOPATH/src/mycode/trace mycode/trace
room run()
 trace.Tracer room
Trace
 tracer nil
room main.go
r := newRoom()
r.tracer = trace.New(os.Stdout)
 New os.Stdout
 room
 trace trace.Off()
 Tracer
 Trace
Off Trace
tracer_test.go
tracer.go
 nilTracer Trace
Off() nilTracer
 nilTracer tracer
 io.Writer
 newRoom room.go
trace
 New() –
 Off() –
 Tracer –
 http:
 //blog.golang.org/godoc-documenting-go-code
 tracer.go
 trace htt
 ps://github.com/matryer/goblueprints/blob/master/chapter1/trac
 e/tracer.go
net/http
http.Handler trace.Tracer
 ServeHTTP room
 http.Handler
gomniauth
 http
http.Handler
 http.Handler
 Chaining pattern when applied to HTTP handlers
 http.Handler
 http.Handle
Logging ServeHTTP
 http.Handler
 Logging
 auth.go
 chat
 package main
 import ("net/http")
 type authHandler struct {
 next http.Handler
 }
 func (h *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 _, err := r.Cookie("auth")
 if err == http.ErrNoCookie {
 // not authenticated
 w.Header().Set("Location", "/login")
 w.WriteHeader(http.StatusTemporaryRedirect)
 return
 }
 if err != nil {
 // some other error
 http.Error(w, err.Error(), http.StatusInternalServerError)
 return
 }
 // success - call the next handler
 h.next.ServeHTTP(w, r)
 }
 func MustAuth(handler http.Handler) http.Handler {
 return &authHandler{next: handler}
 }
 authHandler ServeHTTP
http.Handler http.Handler next
MustAuth authHandler
http.Handler
main.go
MustAuth templateHandler
 templateHandler MustAuth
 authHandler templateHandler
 http://localhost:8080/chat
 /login
 assets main
http.Handle
 http.StripPrefix http.FileServer
 http.Handler
 MustAuth
main.go
MustAuth
login.html templates
<html>
 <head>
 <title>Login</title>
 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com
 /bootstrap/3.3.6/css/bootstrap.min.css">
 </head>
 <body>
 <div class="container">
 <div class="page-header">
 <h1>Sign in</h1>
 </div>
 <div class="panel panel-danger">
 <div class="panel-heading">
 <h3 class="panel-title">In order to chat, you must be signed
 in</h3>
 </div>
 <div class="panel-body">
 <p>Select the service you would like to sign in with:</p>
 <ul>
 <li>
 <a href="/auth/login/facebook">Facebook</a>
 </li>
 <li>
 <a href="/auth/login/github">GitHub</a>
 </li>
 <li>
 <a href="/auth/login/google">Google</a>
 </li>
 </ul>
 </div>
 </div>
 </div>
 </body>
 </html>
http://localhost:8080/login
http
"auth/:action/:provider_name"
 auth/login/google
params[:provider_name] google params[:action]
login
 http
"auth/"
 /auth/login/google
 /auth/login/facebook
 /auth/callback/google
 /auth/callback/facebook
 auth.go
 loginHandler
 strings.Split
 action provider
http.StatusNotFound 404
 loginHandler
 segs[2] segs[3]
/auth/nonsense
 loginHandler
http.Handler
 http.HandleFunc
 http.Handle main.go
 TODO
 goauth2 https://github.com
/golang/oauth2
 gomniauth https://github.com/stretchr/gomniauth
 omniauth gomniauth
 gomniauth
 gomniauth
 gomniauth
 http://wiki.bazaar.canonic
al.com
 localhost:8080
 loginHandler
 http://localhost:8080/auth/callback/google
 gomniauth
 WithProviders
 gomniauth main.go
 flag.Parse() main
// setup gomniauth
gomniauth.SetSecurityKey("PUT YOUR AUTH KEY HERE")
gomniauth.WithProviders(
 facebook.New("key", "secret",
 "http://localhost:8080/auth/callback/facebook"),
 github.New("key", "secret",
 "http://localhost:8080/auth/callback/github"),
 google.New("key", "secret",
 "http://localhost:8080/auth/callback/google"),
)
 key secret
callback
import (
 "github.com/stretchr/gomniauth/providers/facebook"
 "github.com/stretchr/gomniauth/providers/github"
 "github.com/stretchr/gomniauth/providers/google"
)
SetSecurityKey
 /auth/login/{provider}
 loginHandler auth.go
 gomniauth.Provider
 google github
 GetBeginAuthURL
GetBeginAuthURL(nil, nil)
/chat
scope
GetBeginAuthURL
 http.Error
non-200
 https://githu
b.com/pilu/fresh https://github.com/codegangsta/gin
 http://localhost:8080/chat
 Auth action callback not supported
 loginHandler
http://localhost:8080/auth/callback/googlecode=4/Q92xJ-
BQfoX6PHhzkjhgtyfLc0Ylm.QqV4u9AbA9sYguyfbjFEsNoJKMOjQI
auth.go
case "callback":
 provider, err := gomniauth.Provider(provider)
 if err != nil {
 http.Error(w, fmt.Sprintf("Error when trying to get provider %s: %s",
 provider, err), http.StatusBadRequest)
 return
 }
 creds, err :=
provider.CompleteAuth(objx.MustFromURLQuery(r.URL.RawQuery))
 if err != nil {
 http.Error(w, fmt.Sprintf("Error when trying to complete auth for
 %s: %s", provider, err), http.StatusInternalServerError)
 return
 }
 user, err := provider.GetUser(creds)
 if err != nil {
 http.Error(w, fmt.Sprintf("Error when trying to get user from %s: %s",
 provider, err), http.StatusInternalServerError)
 return
 }
 authCookieValue := objx.New(map[string]interface{}{
 "name": user.Name(),
 }).MustBase64()
 http.SetCookie(w, &http.Cookie{
 Name: "auth",
 Value: authCookieValue,
 Path: "/"})
 w.Header().Set("Location", "/chat")
 w.WriteHeader(http.StatusTemporaryRedirect)
 CompleteAuth RawQuery
objx.Map CompleteAuth
GetUser
 Name
 auth
/chat
 —
 auth
 eyJuYW1lIjoiTWF0IFJ5ZXIifQ==
 {"name":"Mat Ryer"}
templateHandler Execute
 http.Request
 map[string]interface{}
 Host UserData auth
 Host
 make
 data Execute
chatbox chat.html
 <form id="chatbox">
 {{.UserData.name}}:<br/>
 <textarea></textarea>
 <input type="submit" value="Send" />
 </form>
 {{.UserData.name}}
 textarea
 objx go get
 http://github.com/stretchr/objx
vendor
$GOPATH go get
 https://blog.gopheracademy.com/advent-2015/vendor-folder/
 vendoring in Go
 []byte
 chan
[]byte
 []byte
message.go chat
 package main
 import (
 "time"
 )
 // message represents a single message
 type message struct {
 Name string
 Message string
 When time.Time
 }
 message
Name When
client
 read write
client.go ReadJSON WriteJSON
 message
 Message
 When Name
 *message forward
 send chan []byte
 room.go forward chan *message
 send chan client.go
room.go
 client
 client
map[string]interface{} userData
 Cookie http.Request
 objx.MustFromBase64
 []byte
*message
chat.html socket.send
socket.send(JSON.stringify({"Message": msgBox.val()}));
 JSON.stringify
Message
 message
 message
 socket.onmessage
socket.onmessage = function(e) {
 var msg = JSON.parse(e.data);
 messages.append(
 $("<li>").append(
 $("<strong>").text(msg.Name + ": "),
 $("<span>").text(msg.Message)
 )
 );
}
 JSON.parse
 Gomniauth
http.Handler
auth MustAuth
message
 time.Time
message
 
https://en.gravatar.com/
 https://en.gravatar.com/
 https://en.gravatar.com/
struct
avatar_url picture
 url picture
 GetUser
 auth.go callback
 authCookieValue
 authCookieValue := objx.New(map[string]interface{}{
 "name": user.Name(),
 "avatar_url": user.AvatarURL(),
 }).MustBase64()
 AvatarURL
 avatar_url
 User
 map[string]interface{}
 message
message.go AvatarURL
 AvatarURL Name
 read client.go
 userData
 message
nil string
 socket.onmessage
 chat.html
socket.onmessage = function(e) {
 var msg = JSON.parse(e.data);
 messages.append(
 $("<li>").append(
 $("<img>").css({
 width:50,
 verticalAlign:"middle"
 }).attr("src", msg.AvatarURL),
 $("<strong>").text(msg.Name + ": "),
 $("<span>").text(msg.Message)
 )
 );
}
 img AvatarURL
 css 50
 auth
 avatar_url
auth
HandleFunc main.go
 http.SetCookie MaxAge
-1
 Value
 ServeHTTP authHandler
 auth.go
 Value
 Sign Out
 chat.html chatbox
 /logout
<form id="chatbox">
 {{.UserData.name}}:<br/>
 <textarea></textarea>
 <input type="submit" value="Send" />
 or <a href="/logout">sign out</a>
</form>
 localhost:8080/chat
 chat.html
style link
<link rel="stylesheet"href="//netdna.bootstrapcdn.com/bootstrap
 /3.3.6/css/bootstrap.min.css">
<style>
 ul#messages { list-style: none; }
 ul#messages li { margin-bottom: 2px; }
 ul#messages li img { margin-right: 10px; }
</style>
body script
<div class="container">
 <div class="panel panel-default">
 <div class="panel-body">
 <ul id="messages"></ul>
 </div>
 </div>
 <form id="chatbox" role="form">
 <div class="form-group">
 <label for="message">Send a message as {{.UserData.name}}
 </label> or <a href="/logout">Sign out</a>
 <textarea id="message" class="form-control"></textarea>
 </div>
 <input type="submit" value="Send" class="btn btn-default" />
 </form>
</div>
 socket.onmessage
socket.onmessage = function(e) {
 var msg = JSON.parse(e.data);
 messages.append(
 $("<li>").append(
 $("<img>").attr("title", msg.Name).css({
 width:50,
 verticalAlign:"middle"
 }).attr("src", msg.AvatarURL),
 $("<span>").text(msg.Message)
 )
 );
}
 GET
http.Handler
avatar.go
package main
import (
 "errors"
)
// ErrNoAvatar is the error that is returned when the
// Avatar instance is unable to provide an avatar URL.
var ErrNoAvatarURL = errors.New("chat: Unable to get an avatar URL.")
// Avatar represents types capable of representing
// user profile pictures.
type Avatar interface {
 // GetAvatarURL gets the avatar URL for the specified client,
 // or returns an error if something goes wrong.
 // ErrNoAvatarURL is returned if the object is unable to get
 // a URL for the specified client.
 GetAvatarURL(c *client) (string, error)
}
Avatar GetAvatarURL
Avatar GetAvatarURL
ErrNoAvatarURL ErrNoAvatarURL
 errors.New
 ErrNoAvatarURL
Avatar
avatar_test.go chat
 package main
 import "testing"
 func TestAuthAvatar(t *testing.T) {
 var authAvatar AuthAvatar
 client := new(client)
 url, err := authAvatar.GetAvatarURL(client)
 if err != ErrNoAvatarURL {
 t.Error("AuthAvatar.GetAvatarURL should return ErrNoAvatarURL
 when no value present")
 }
 // set a value
 testUrl := "http://url-to-gravatar/"
 client.userData = map[string]interface{}{"avatar_url": testUrl}
 url, err = authAvatar.GetAvatarURL(client)
 if err != nil {
 t.Error("AuthAvatar.GetAvatarURL should return no error
 when value present")
 }
 if url != testUrl {
 t.Error("AuthAvatar.GetAvatarURL should return correct URL")
 }
 }
 AuthAvatar GetAvatarURL
 ErrNoAvatarURL
 AuthAvatar
 authAvatar
authAvatar AuthAvatar
 nil
 client
 nil
avatar.go
 AuthAvatar
 GetAvatarURL UseAuthAvatar
 AuthAvatar nil
UseAuthAvatar Avatar
 GetAvatarURL
 if
 return urlStr, nil
 avatar_url
 http://bit.ly/lineofsightgolang
 nil
 avatar_url
 ErrNoAvatarURL
chat
Avatar
Avatar
 newRoom Avatar
 room
 newRoom main.go
 Avatar
 UseAuthAvatar
r := newRoom(UseAuthAvatar)
AuthAvatar
UseAuthAvatar
AuthAvatar
 Avatar AuthAvatar
 https://
en.gravatar.com/ avatar_test.go
 userData
 GetAvatarURL GravatarAvatar
 https://github.com/matryer/goblueprints
 go test
 avatar.go
 io
 AuthAvatar
UseGravatar GetAvatarURL
fmt.Sprintf
 crypto
 md5
 io.Writer io.WriteString
 Sum
 auth
authCookieValue auth.go Email
 authCookieValue := objx.New(map[string]interface{}{
 "name": user.Name(),
 "avatar_url": user.AvatarURL(),
 "email": user.Email(),
 }).MustBase64()
r := newRoom(UseGravatar)
 src img
GravatarAuth
auth.go authCookieValue
 m := md5.New()
 io.WriteString(m, strings.ToLower(user.Email()))
 userId := fmt.Sprintf("%x", m.Sum(nil))
 authCookieValue := objx.New(map[string]interface{}{
 "userid": userId,
 "name": user.Name(),
 "avatar_url": user.AvatarURL(),
 "email": user.Email(),
 }).MustBase64()
userid
avatar_test.go
 client.userData = map[string]interface{}{"email":
 "MyEmailAddress@example.com"}
 client.userData = map[string]interface{}{"userid":
 "0bc83cb571cd1c50ba6f3e8a78ef1346"}
 email
 userid go test
 avatar.go GetAvatarURL
GravatarAuth
chat/templates upload.html
<html>
 <head>
 <title>Upload</title>
 <link rel="stylesheet"
 href="//netdna.bootstrapcdn.com/bootstrap/3.6.6/css/bootstrap.min.css">
 </head>
 <body>
 <div class="container">
 <div class="page-header">
 <h1>Upload picture</h1>
 </div>
 <form role="form" action="/uploader" enctype="multipart/form-data"
 method="post">
 <input type="hidden" name="userid" value="{{.UserData.userid}}" />
 <div class="form-group">
 <label for="avatarFile">Select file</label>
 <input type="file" name="avatarFile" />
 </div>
 <input type="submit" value="Upload" class="btn" />
 </form>
 </div>
 </body>
</html>
 /uploader
 enctype multipart/form-data
 input
 file
 userid UserData
 name
/upload main.go
 /uploader
 HandlerFunc
chat avatars
 upload.go
 ioutils net/http io
path
nil
 multipart.File
io.Reader http://golang.org/pkg/mime/m
ultipart/#File
 multipart.File
 io.Reader multipart.File
 io.Reader
multipart.File io.Reader
Read
ioutil.ReadAll io.Reader
 path.Join path.Ext
 userid
 multipart.FileHeader
 ioutil.WriteFile avatars
 userid
 0777
 http.StatusInternalServerError
 /uploader main.go
 func main
http.HandleFunc("/uploader", uploaderHandler)
auth
 http://localhost:8080/upload
 chat/avatars
 userid
 net/http
 main.go
 http.Handle("/avatars/",
 http.StripPrefix("/avatars/",
 http.FileServer(http.Dir("./avatars"))))
http.Handle
/avatars/
http.StripPrefix http.FileServer http.Handler
 StripPrefix
 http.Handler
 http.FileServer
 404 Not
Found http.Dir
 /avatars/
 http.StripPrefix
 avatars avatars
 /avatars/avatars/filename /avatars/filename
http://localhost:8080/avatars/
avatars
http://localhost:8080/upload
Avatar
avatar_test.go
 GravatarAvatar
 avatars
 userid client.userData
GetAvatarURL
 avatar.go
userid
.jpg
main.go Avatar
r := newRoom(UseFileSystemAvatar)
http://localhost:8080/upload
http://localhost:8080/chat
 /upload
 /chat
 GetAvatarURL
FileSystemAvatar
ioutil.ReadDir
IsDir
 userid
 path.Match userid
ErrNoAvatarURL
avatar.go
avatar
 Avatar
 GetAvatarURL
 avatars
 auth Avatar
 client GetAvatarURL
 Avatar
 GetAvatarURL
Avatar
Avatar
auth.go package
 import common
 gomniauthcommon
 ChatUser
 Avatar
 chatUser
gomniauth/common.User struct
 ChatUser User
 AvatarURL
chatUser User
 User ChatUser
Avatar
avatar_test.go TestAuthAvatar
 gomniauth/test
 gomniauthtest
TestUser chatUser
 chatUser GetAvatarURL
 TestUser Testify
 https://github.com/stretchr/
 testify
 On Return TestUser
 AvatarURL
testUrl
UniqueID
avatar_test.go
 Avatar
 avatar.go GetAvatarURL Avatar
 ChatUser client
 ChatUser
 chatUser
 GetAvatarURL
GetAvatarURL client
FileSystemAvatar
AuthAvatar
 AvatarURL
 ErrNoAvatarURL
if
if...else
GravatarAvatar
Avatar room
 Avatar
 Avatar
 import main.go
avatars
 GetAvatarURL
 userData auth
msg.AvatarURL
 if avatarUrl, ok := c.userData["avatar_url"]; ok {
 msg.AvatarURL = avatarUrl.(string)
 }
 chatUser User
 User
userid uniqueID
 avatars.GetAvatarURL
 authCookieValue
 auth.go
 authCookieValue := objx.New(map[string]interface{}{
 "userid": chatUser.uniqueID,
 "name": user.Name(),
 "avatar_url": avatarURL,
 }).MustBase64()
Avatar
 Avatar room
 room.go avatar Avatar
room newRoom
golint go vet
Avatar
ErrNoAvatarURL
avatar.go Avatar
 TryAvatars Avatar
 GetAvatarURL
Avatar GetAvatarURL
ErrNoAvatarURL
avatars main.go
 TryAvatars
Avatar
 http://localhost:8080/logout
 avatars
 http://localhost:8080/chat
 http://localhost:8080/upload
 Avatar
https://en.gravatar.com/
 avatars
 http.FileServer
GetAvatarURL
ErrNoAvatarURL
 Avatars
Avatar Avatar
 ErrNoAvatarURL
 stdin
 stdout
 NUL /dev/null
 |
 echo Hello
 md5
Hello
 .com .net
 .com
 chat chatapp
talk talk time
math/rand
 $GOPATH/src
 ~/Work/projects/go
 ~/Work/projects/go/src
 package main
 import (
 "bufio"
 "fmt"
 "math/rand"
 "os"
 "strings"
 "time"
)
const otherWord = "*"
var transforms = []string{
otherWord,
 otherWord + "app",
 otherWord + "site",
 otherWord + "time",
 "get" + otherWord,
 "go" + otherWord,
 "lets " + otherWord,
 otherWord + "hq",
}
func main() {
 rand.Seed(time.Now().UTC().UnixNano())
 s := bufio.NewScanner(os.Stdin)
 for s.Scan() {
 t := transforms[rand.Intn(len(transforms))]
 fmt.Println(strings.Replace(t, otherWord, s.Text(), -1))
 }
}
 import
 Appendix
 main
 otherWord
 otherWord+"extra"
 “ ”
transforms
 app lets
 main
math/rand
 bufio.Scanner bufio.NewScanner
 os.Stdin
bufio.Scanner io.Reader
io.Reader
bufio.ScanWords
 Scan
 bool
 for
 Scan true for
Scan false
 Bytes Text
 []byte
 for rand.Intn
 transforms strings.Replace
 otherWord fmt.Println
 math/rand
crypto/rand
chat
chat
Scan false
echo
echo
 transformations
 sprinkle
domainify main.go
 package main
 var tlds = []string{"com", "net"}
 const allowedChars = "abcdefghijklmnopqrstuvwxyz0123456789_-"
 func main() {
 rand.Seed(time.Now().UTC().UnixNano())
 s := bufio.NewScanner(os.Stdin)
 for s.Scan() {
 text := strings.ToLower(s.Text())
 var newText []rune
 for _, r := range text {
 if unicode.IsSpace(r) {
 r = '-'
 }
 if !strings.ContainsRune(allowedChars, r) {
 continue
 }
 newText = append(newText, r)
 }
 fmt.Println(string(newText) + "." +
 tlds[rand.Intn(len(tlds))])
 }
 }
 rune
newText rune allowedChars
 strings.ContainsRune rune
 unicode.IsSpace
 rune
 int32
 h
 ttp://blog.golang.org/strings
domainify
“ ”
 $GOPATH/src sprinkle
domainify
 sprinkle domainify
 sprinkle domanify
 chat
 .com .net
 chat
 a
 cht a chaat
 package main
 const (
 duplicateVowel bool = true
 removeVowel bool = false
 )
 func randBool() bool {
 return rand.Intn(2) == 0
 }
 func main() {
 rand.Seed(time.Now().UTC().UnixNano())
 s := bufio.NewScanner(os.Stdin)
 for s.Scan() {
 word := []byte(s.Text())
 if randBool() {
 var vI int = -1
 for i, char := range word {
 switch char {
 case 'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U':
 if randBool() {
 vI = i
 }
 }
 }
 if vI >= 0 {
 switch randBool() {
 case duplicateVowel:
 word = append(word[:vI+1], word[vI:]...)
 case removeVowel:
 word = append(word[:vI], word[vI+1:]...)
 }
 }
 }
 fmt.Println(string(word))
 }
 }
duplicateVowel removeVowel
switch
 true false
 randBool true
false rand
 0 1
 true
 main
 rand.Seed
 randBool
 randBool
 true vI
randBool
 switch randBool() {
 case true:
 word = append(word[:vI+1], word[vI:]...)
 case false:
 word = append(word[:vI], word[vI+1:]...) }
 true false
 duplicateVowel removeVowel
 randBool
append
switch
 []byte append
 blueprints
 e vI 3
word[:vI+1]
 +1
word[vI:]
word[:vI]
word[vI+1:]
 fmt.Println
 blueprints
cd
http://bighugelabs.com/
 GET
 https://github.
 com/matryer/goblueprints
http://words.bighugelabs.com/
const
BHT_APIKEY
 ~/.bashrc
 export
 export BHT_APIKEY=abc123def456ghi789jkl
love
{
 "noun":{
 "syn":[
 "passion",
 "beloved",
 "dear"
 ]
 },
 "verb":{
 "syn":[
 "love",
 "roll in the hay",
 "make out"
 ],
 "ant":[
 "hate"
 ]
 }
 }
syn ant
encoding/json
 thesaurus $GOPATH/src
 bighuge.go
 package thesaurus
 import (
 "encoding/json"
 "errors"
 "net/http"
 )
 type BigHuge struct {
 APIKey string
 }
 type synonyms struct {
 Noun *words `json:"noun"`
 Verb *words `json:"verb"`
 }
 type words struct {
 Syn []string `json:"syn"`
 }
 func (b *BigHuge) Synonyms(term string) ([]string, error) {
 var syns []string
 response, err := http.Get("http://words.bighugelabs.com/api/2/" +
 b.APIKey + "/" + term + "/json")
 if err != nil {
 return syns, errors.New("bighuge: Failed when looking for synonyms
 for "" + term + """ + err.Error())
 }
 var data synonyms
 defer response.Body.Close()
 if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
 return syns, err
 }
 if data.Noun != nil {
 syns = append(syns, data.Noun.Syn...)
 }
 if data.Verb != nil {
 syns = append(syns, data.Verb.Syn...)
 }
 return syns, nil
 }
 BigHuge
 Synonyms
synonyms words
 Syn
 encoding/json
encoding/json
 synonyms
 words
term
log.Fatalln
 1
 io.Reader
json.NewDecoder data
 synonyms
 append noun verb
 syns
 BigHuge
 Thesaurus thesaurus
 thesaurus.go
 package thesaurus
 type Thesaurus interface {
 Synonyms(term string) ([]string, error)
 }
term
BigHuge
 http://www.dictionary.com/
 $GOPATH/src synonyms
 main.go
 func main() {
 apiKey := os.Getenv("BHT_APIKEY")
 thesaurus := &thesaurus.BigHuge{APIKey: apiKey}
 s := bufio.NewScanner(os.Stdin)
 for s.Scan() {
 word := s.Text()
 syns, err := thesaurus.Synonyms(word)
 if err != nil {
 log.Fatalln("Failed when looking for synonyms for "+word+", err)
 }
 if len(syns) == 0 {
 log.Fatalln("Couldn't find any synonyms for " + word + ")
 }
 for _, syn := range syns {
 fmt.Println(syn)
 }
 }
 }
 main BHT_APIKEY
 os.Getenv
os.Stdin Synonyms
 chat
 synonyms
 domainify
chat
 http://tool
s.ietf.org/html/rfc3912
 No match
 available main.go
 exists
 43 whoisServer net.Dial
 Close() conn
 rn
 “ ”
 exists
bufio.Scanner
 NewScanner net.Conn io.Reader
strings.ToLower
strings.Contains no match
 false true
 com.whois-servers.net .com .net
main exists
Yes No
main.go
 main
os.Stdin fmt.Print
fmt.Println exists
 fmt.Println
 time.Sleep
 marks bool exists
fmt.Println(marks[!exist])
 chat
chat synonyms
sprinkle
 coolify
 domainify
available
os/exec
 domainfinder
 lib lib
lib
build.sh build.bat
#!/bin/bash
echo Building domainfinder...
go build -o domainfinder
echo Building synonyms...
cd ../synonyms
go build -o ../domainfinder/lib/synonyms
echo Building available...
cd ../available
go build -o ../domainfinder/lib/available
cd ../build
echo Building sprinkle...
cd ../sprinkle
go build -o ../domainfinder/lib/sprinkle
cd ../build
echo Building coolify...
cd ../coolify
go build -o ../domainfinder/lib/coolify
cd ../build
echo Building domainify...
cd ../domainify
go build -o ../domainfinder/lib/domainify
cd ../build
echo Done.
 domainfinder
 go build lib
 chmod +x build.sh
 lib
main.go domainfinder
package main
var cmdChain = []*exec.Cmd{
 exec.Command("lib/synonyms"),
 exec.Command("lib/sprinkle"),
 exec.Command("lib/coolify"),
 exec.Command("lib/domainify"),
 exec.Command("lib/available"),
}
func main() {
 cmdChain[0].Stdin = os.Stdin
 cmdChain[len(cmdChain)-1].Stdout = os.Stdout
 for i := 0; i < len(cmdChain)-1; i++ {
 thisCmd := cmdChain[i]
 nextCmd := cmdChain[i+1]
 stdout, err := thisCmd.StdoutPipe()
 if err != nil {
 log.Fatalln(err)
 }
 nextCmd.Stdin = stdout
 }
 for _, cmd := range cmdChain {
 if err := cmd.Start(); err != nil {
 log.Fatalln(err)
 } else {
 defer cmd.Process.Kill()
 }
 }
 for _, cmd := range cmdChain {
 if err := cmd.Wait(); err != nil {
 log.Fatalln(err)
 }
 }
}
os/exec
 cmdChain *exec.Cmd
 main Stdin
 os.Stdin Stdout
 os.Stdout
Stdin Stdout
Stdin domainfinder
 Stdout domainfinder
 Start
 Run
log.Fatalln
main domainfinder
domainfinder
 clouds
 synonyms
math/rand
 domainfinder
 
go-nsq
twittervotes
 counter
web
 twittervotes
counter web
 ballots
polls
 {
 "_id": "",
 "title": "Poll title",
 "options": ["one", "two", "three"],
 "results": {
 "one": 100,
 "two": 200,
 "three": 300
 }
}
_id
options
 results
 https://github.com/matryer/g
 oblueprints
mongod nsqd
 counter
 http://nsq.io/deployment/installing.html
 install nsq
bin PATH
nsqlookupd
 nsqlookupd nsqd
nsqlookupd
 nsqd
 nsqd
 http://nsq.io/
 go get
people
{"name":"Mat","lang":"en","points":57}
{"name":"Laurie","position":"Scrum Master"}
{"position":"Traditional Manager","exists":false}
mongod
 http://www.mongodb.org/downloads
 bin PATH
 mongod
mgo http://labix.org/mgo
 nsqlookupd nsqd
 nsqd nsqlookupd
 mongod
4160
 --lookupd-tcp-address
 nsqlookupd nsqd
 nsqlookupd nsqd
 dbpath
 mongod
dbpath
 $GOPATH/src
socialpoll
 socialpoll
 twittervotes main.go main
 main
 package main
 func main(){}
twittervotes
 mgo
 options
 Chapter 3 https://apps
.twitter.com SocialPoll
 SP_TWITTER_KEY
 SP_TWITTER_SECRET
 SP_TWITTER_ACCESSTOKEN
 SP_TWITTER_ACCESSSECRET
setup.sh setup.bat
setup.sh
 #!/bin/bash
 export SP_TWITTER_KEY=yC2EDnaNrEhN5fd33g...
 export SP_TWITTER_SECRET=6n0rToIpskCo1ob...
 export SP_TWITTER_ACCESSTOKEN=2427-13677...
 export SP_TWITTER_ACCESSSECRET=SpnZf336u...
 SET SP_TWITTER_KEY=yC2EDnaNrEhN5fd33g...
 SET SP_TWITTER_SECRET=6n0rToIpskCo1ob...
 SET SP_TWITTER_ACCESSTOKEN=2427-13677...
 SET SP_TWITTER_ACCESSSECRET=SpnZf336u...
 .bashrc C:\cmdauto.cmd
net.Conn
dial http.Transport
twitter.go twittervotes
 dial conn
 conn
 io.ReadCloser
 twitter.go
closeConn
closeConn
OAuth
 twitter.go
var (
 authClient *oauth.Client
 creds *oauth.Credentials
)
func setupTwitterAuth() {
 var ts struct {
 ConsumerKey string `env:"SP_TWITTER_KEY,required"`
 ConsumerSecret string `env:"SP_TWITTER_SECRET,required"`
 AccessToken string `env:"SP_TWITTER_ACCESSTOKEN,required"`
 AccessSecret string `env:"SP_TWITTER_ACCESSSECRET,required"`
 }
 if err := envdecode.Decode(&ts); err != nil {
 log.Fatalln(err)
 }
 creds = &oauth.Credentials{
 Token: ts.AccessToken,
 Secret: ts.AccessSecret,
 }
 authClient = &oauth.Client{
 Credentials: oauth.Credentials{
 Token: ts.ConsumerKey,
 Secret: ts.ConsumerSecret,
 },
 }
}
 struct
 ts
 var ts struct... envdecode
 go get
github.com/joeshaw/envdecode log
 required
 struct
 envdecode
 required
 oauth.Credentials oauth.Client
 go-oauth
twitter.go
 var (
 authSetupOnce sync.Once
 httpClient *http.Client
 )
 func makeRequest(req *http.Request, params url.Values) (*http.Response,
 error) {
 authSetupOnce.Do(func() {
 setupTwitterAuth()
 httpClient = &http.Client{
 Transport: &http.Transport{
 Dial: dial,
 },
 }
 })
 formEnc := params.Encode()
 req.Header.Set("Content-Type", "application/x-www-form- urlencoded")
 req.Header.Set("Content-Length", strconv.Itoa(len(formEnc)))
 req.Header.Set("Authorization", authClient.AuthorizationHeader(creds,
 "POST",
 req.URL, params))
 return httpClient.Do(req)
 }
 sync.Once
 makeRequest setupTwitterAuth
http.Client http.Transport dial
params
var db *mgo.Session
func dialdb() error {
 var err error
 log.Println("dialing mongodb: localhost")
 db, err = mgo.Dial("localhost")
 return err
}
func closedb() {
 db.Close()
 log.Println("closed database connection")
}
 mgo mgo.Session
 db
loadOptions main.go
 Options
 poll db
 polls ballots mgo
Find nil
 mgo
 query := col.Find(q).Sort("field").Limit(10).Skip(10)
Iter
poll All
append options
twittervotes
poll
twittervotes
append variadic
...
 Err mgo.Iter
 closeConn
twitter.go
 twitter.go readFromTwitter
 votes
 t
 Text
 votes
 votes
 chan<- string
 chan<-
 <-chan
 readFromTwitter
 Decode
readFromTwitter votes
struct{}
 struct{}
 struct{}{}
 bool true
false
 http://play.golang.org
 bool
 fmt.Println(reflect.TypeOf(true).Size()) = 1
 struct{}{}
 fmt.Println(reflect.TypeOf(struct{}{}).Size()) = 0
 twitter.go
 stopchan <-chan
struct{}
votes
 <-chan struct{}
 startTwitterStream stoppedchan
 struct{}{}
 stoppedchan
 for
stopchan
 stoppedchan
 readFromTwitter votes
 time.Sleep
 stopchan
votes
twittervotes
 publishVotes votes
 <-chan string
 votes chan<-
 string <-chan string
 make(chan string)
 <-
 votes
publishVotes main.go
 stopchan
 struct{}{} stopchan
stopchan
stopchan
 NewProducer
 localhost
votes for...range
 votes for
 votes
 stopchan
publishVotes
main
main
startTwitterStream
signalChan <-
stop true
readFromTwitter
 closeConn
 readFromTwitter
 main
 // start things
 votes := make(chan string) // chan for votes
 publisherStoppedChan := publishVotes(votes)
 twitterStoppedChan := startTwitterStream(stopChan, votes)
 go func() {
 for {
 time.Sleep(1 * time.Minute)
 closeConn()
 stoplock.Lock()
 if stop {
 stoplock.Unlock()
 return
 }
 stoplock.Unlock()
 }
 }()
 <-twitterStoppedChan
 close(votes)
 <-publisherStoppedChan
 votes
 chan<- <-
chan
publishVotes votes
 publisherStoppedChan
startTwitterStream stopChan main
 votes
 twitterStoppedChan
 for
 closeConn bool
stoplock
 twitterStoppedChan
 stopChan
votes for...range
 publisherStoppedChan
twittervotes
 mongo
 polls ballots
 results
 counter
 results
nsq_tail
 twittervotes
twittervotes
nsq_tail
counter
 counter twittervotes
 main.go
package main
import (
 "flag"
 "fmt"
 "os"
 )
 var fatalErr error
 func fatal(e error) {
 fmt.Println(e)
 flag.PrintDefaults()
 fatalErr = e
 }
 func main() {
 defer func() {
 if fatalErr != nil {
 os.Exit(1)
 }
 }()
 }
 log.Fatal
os.Exit
fatal
 os.Exit(1)
 1
main
defer
 log.Println("Connecting to database...")
 db, err := mgo.Dial("localhost")
 if err != nil {
 fatal(err)
 return
 }
 defer func() {
 log.Println("Closing database connection...")
 db.Close()
 }()
 pollData := db.DB("ballots").C("polls")
mgo.Dial
 mgo
ballots.polls pollData
 votes
 main
sync.Mutex
main
 log.Println("Connecting to nsq...")
 q, err := nsq.NewConsumer("votes", "counter", nsq.NewConfig())
 if err != nil {
 fatal(err)
 return
 }
 NewConsumer votes
 twittervotes
 NewConsumer fatal
 AddHandler nsq.Consumer
 votes
countsLock
NewConsumer
 Lock
counts nil
 int
 nil
 if err := q.ConnectToNSQLookupd("localhost:4161");
 err !=nil {
 fatal(err)
 return
 }
 nsqlookupd
doCount
 doCount countsLock
 counts
 *
 *counts = nil
nil
 counts
 mgo mgo/bson
 mgo bson bson.M
 bson.M
 map[string]interface{}
 {
 "options": {
 "$in": ["happy"]
 }
 }
 "happy"
 options
 {
 "$inc": {
 "results.happy": 3
 }
 }
 results.happy
 results
happy results
UpdateAll pollsData
Update
 ok false counts
 updateDuration
main
 time.Ticker doCount
 select
 doCount main
 twittervotes
 main
 ticker := time.NewTicker(updateDuration)
 termChan := make(chan os.Signal, 1)
 signal.Notify(termChan, syscall.SIGINT, syscall.SIGTERM,syscall.SIGHUP)
 for {
 select {
 case <-ticker.C:
 doCount(&countsLock, &counts,pollData) case <- termChan:ticker.Stop()
 q.Stop()
 case <-q.StopChan:
 // finished
 return
 }
 }
 time.Ticker C
 updateDuration
select doCount termChan q.StopChan
 termChan
 select
 termChan StopChan
 termChan
 time.Ticker
StopChan
 twittervotes
counter
 counter
 results
find pretty
results
 mgo
 bson.M
map[string]interface{}
 nsqlookupd nsqd
twittervotes
 counter
twittervotes
twittervotes
 
Chapter 5
http.HandlerFunc
 context
 GET
 POST
GET /polls
GET /polls/{id}
POST /polls
DELETE /polls/{id}
 {id}
 context
 http.Request context.Context
 request.Context()
 request.WithContext() http.Request
 Context
context.WithValue
ctx
Handler.ServeHTTP(w, r.WithContext(ctx))
 https://golang.org/pk
g/context/
interface{}
struct
main.go api
package main
func main(){}
contextKey
name
 contextKey
 contextKeyAPIKey contextKey
 api-key
 context.Context ok
 contextKey contextKeyAPIKey
 APIKey main
 Chapter 2
 http.Handler
http.HandlerFunc
 HandlerFunc withAPIKey
 main.go
withAPIKey
 http.HandlerFunc key
 isValidAPIKey false
 invalid API key
 http.HandlerFunc
 key
http.HandlerFunc
 http.HandleFunc
isValidAPIKey
 abc123
 false
isValidAPIKey withAPIKey
 Access-Control-Allow-Origin *
 Location –
Access-Control-Expose-Headers main.go
ResponseWriter http.HandlerFunc
 https://github.com/faster
 ness/cors
main.go
Server
 – net/http
 respond.go
 Request
 ResponseWriter
respond.go
 ResponseWriter
 encodeBody
respondErr
 respond
 error
http.StatusText
http.Request
 net/http
 –
 /people/1/books/2 http.Request
 URL.Path
 1 2
mux
 /users/{userID}/comments/{commentID}
 /users/1/comments/2
path.go
package main
import (
 "strings"
)
const PathSeparator = "/"
type Path struct {
 Path string
 ID string
}
func NewPath(p string) *Path {
 var id string
 p = strings.Trim(p, PathSeparator)
 s := strings.Split(p, PathSeparator)
 if len(s) > 1 {
 id = s[len(s)-1]
 p = strings.Join(s[:len(s)-1], PathSeparator)
 }
 return &Path{Path: p, ID: id}
}
func (p *Path) HasID() bool {
 return len(p.ID) > 0
}
 NewPath
 Path
 strings.Trim strings.Split
 PathSeparator
 len(s) > 1
 s[len(s)-1]
 s[:len(s)-1]
 PathSeparator
 collection/id
 Path
/ / nil false
/people/ people nil false
/people/1/ people 1 true
main
 main
 main.go main
func main() {
 var (
 addr = flag.String("addr", ":8080", "endpoint
 address")
 mongo = flag.String("mongo", "localhost", "mongodb
 address")
 )
 log.Println("Dialing mongo", *mongo)
 db, err := mgo.Dial(*mongo)
 if err != nil {
 log.Fatalln("failed to connect to mongo:", err)
 }
 defer db.Close()
 s := &Server{
 db: db,
 }
 mux := http.NewServeMux()
 mux.HandleFunc("/polls/",
 withCORS(withAPIKey(s.handlePolls)))
 log.Println("Starting web server on", *addr)
 http.ListenAndServe(":8080", mux)
 log.Println("Stopping...")
}
 main
 addr mongo flag
 log.Fatalln
 db
 s
 http.ServeMux
/polls/ handlePolls
HandleFunc ServeMux
withCORS(withAPIKey(handlePolls))
http.HandlerFunc
/polls/
 withCORS
 withAPIKey
 handlePolls
 respond.go
 withAPIKey
 withCORS
handlePolls
polls.go
package main
import "gopkg.in/mgo.v2/bson"
type poll struct {
 ID bson.ObjectId `bson:"_id" json:"id"`
 Title string `json:"title"`
 Options []string `json:"options"`
 Results map[string]int `json:"results,omitempty"`
 APIKey string `json:"apikey"`
}
poll
APIKey
ID
struct
 reflect
 bson json
 encoding/json
 gopkg.in/mgo.v2/bson
struct
ID id _id
 switch
polls.go handlePolls
 mux
 func (s *Server) handlePollsGet(w http.ResponseWriter,
 r *http.Request) {
 session := s.db.Copy()
 defer session.Close()
 c := session.DB("ballots").C("polls")
 var q *mgo.Query
 p := NewPath(r.URL.Path)
 if p.HasID() {
 // get specific poll
 q = c.FindId(bson.ObjectIdHex(p.ID))
 } else {
 // get all polls
 q = c.Find(nil)
 }
 var result []*poll
 if err := q.All(&result); err != nil {
 respondErr(w, r, http.StatusInternalServerError, err)
 return
 }
 respond(w, r, http.StatusOK, &result)
 }
 mgo
 polls –
 mgo.Query
FindId polls nil Find
bson.ObjectId ObjectIdHex
 All poll
 []*poll All
 mgo result
 Iter
 Limit Skip
polls
mongo
api
 GET /polls/
http://localhost:8080/polls/key=abc123
 http://localhost:8080/polls/5415b060a02cd4adb487c3aekey=abc123
 mgo.Query
 POST /polls/
 POST
 mgo Insert
 Location 201
http.StatusCreated
 DELETE
 /polls/5415b060a02cd4adb487c3ae
 200 Success
 StatusMethodNotAllowed
 RemoveId
 bson.ObjectId
 http.StatusOK
 DELETE
 DELETE
 OPTIONS
 DELETE Access-Control-Request-Method
switch OPTIONS
 case "OPTIONS":
 w.Header().Add("Access-Control-Allow-Methods", "DELETE")
 respond(w, r, http.StatusOK, nil)
 return
 DELETE
 Access-Control-Allow-Methods DELETE *
 withCORS
Access-Control-Allow-Methods
 DELETE
 http://enable
 -cors.org/
 http://curl.haxx.se/dlwiz/type=bin
api
 curl -X
GET
 DELETE
 index.html
 view.html
 new.html
 web api
main.go
package main
import (
 "flag"
 "log"
 "net/http"
)
func main() {
 var addr = flag.String("addr", ":8081", "website address")
 flag.Parse()
 mux := http.NewServeMux()
 mux.Handle("/", http.StripPrefix("/",
 http.FileServer(http.Dir("public"))))
 log.Println("Serving website at:", *addr)
 http.ListenAndServe(*addr, mux)
}
 addr http.ServeMux
 public
– –
 https://github.com/
 matryer/goblueprints
<!DOCTYPE html>
<html>
<head>
 <title>Polls</title>
 <link rel="stylesheet"
 href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/
 bootstrap.min.css">
</head>
<body>
</body>
</html>
body
<div class="container">
 <div class="col-md-4"></div>
 <div class="col-md-4">
 <h1>Polls</h1>
 <ul id="polls"></ul>
 <a href="new.html" class="btn btn-primary">Create new poll</a>
 </div>
 <div class="col-md-4"></div>
</div>
new.html
script
<script
src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
 <script>
 $(function(){
 var update = function(){
 $.get("http://localhost:8080/polls/key=abc123", null, null, "json")
 .done(function(polls){
 $("#polls").empty();
 for (var p in polls) {
 var poll = polls[p];
 $("#polls").append(
 $("<li>").append(
 $("<a>")
 .attr("href", "view.htmlpoll=polls/" + poll.id)
 .text(poll.title)
 )
 )
 }
 }
 );
 window.setTimeout(update, 10000);
 }
 update();
 });
</script>
 $.get
 – –
 view.html
 new.html public
<!DOCTYPE html>
<html>
<head>
 <title>Create Poll</title>
 <link rel="stylesheet"
 href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/
 bootstrap.min.css">
</head>
<body>
 <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
</script>
</body>
</html>
body
<div class="container">
 <div class="col-md-4"></div>
 <form id="poll" role="form" class="col-md-4">
 <h2>Create Poll</h2>
 <div class="form-group">
 <label for="title">Title</label>
 <input type="text" class="form-control" id="title"
 placeholder="Title">
 </div>
 <div class="form-group">
 <label for="options">Options</label>
 <input type="text" class="form-control" id="options"
 placeholder="Options">
 <p class="help-block">Comma separated</p>
 </div>
 <button type="submit" class="btn btn-primary">
 Create Poll</button> or <a href="/">cancel</a>
 </form>
 <div class="col-md-4"></div>
</div>
 script
 <script>
 $(function(){
 var form = $("form#poll");
 form.submit(function(e){
 e.preventDefault();
 var title = form.find("input[id='title']").val();
 var options = form.find("input[id='options']").val();
 options = options.split(",");
 for (var opt in options) {
 options[opt] = options[opt].trim();
 }
 $.post("http://localhost:8080/polls/key=abc123",
 JSON.stringify({
 title: title, options: options
 })
 ).done(function(d, s, r){
 location.href = "view.htmlpoll=" +
 r.getResponseHeader("Location");
 });
 });
 });
 </script>
submit val
 $.post POST
JSON.stringify
Location view.html
 view.html
 view.html public
 <!DOCTYPE html>
 <html>
 <head>
 <title>View Poll</title>
 <link rel="stylesheet"
 href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
</head>
<body>
 <div class="container">
 <div class="col-md-4"></div>
 <div class="col-md-4">
 <h1 data-field="title">...</h1>
 <ul id="options"></ul>
 <div id="chart"></div>
 <div>
 <button class="btn btn-sm" id="delete">Delete this poll</button>
 </div>
 </div>
 <div class="col-md-4"></div>
 </div>
</body>
</html>
 div view.html
 body script
<script src="//www.google.com/jsapi"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
</script>
<script>
google.load('visualization', '1.0', {'packages':['corechart']});
google.setOnLoadCallback(function(){
 $(function(){
 var chart;
 var poll = location.href.split("poll=")[1];
 var update = function(){
 $.get("http://localhost:8080/"+poll+"key=abc123", null, null,
 "json")
 .done(function(polls){
 var poll = polls[0];
 $('[data-field="title"]').text(poll.title);
 $("#options").empty();
 for (var o in poll.results) {
 $("#options").append(
 $("<li>").append(
 $("<small>").addClass("label label
 default").text(poll.results[o]),
 " ", o
 )
 )
 }
 if (poll.results) {
 var data = new google.visualization.DataTable();
 data.addColumn("string","Option");
 data.addColumn("number","Votes");
 for (var o in poll.results) {
 data.addRow([o, poll.results[o]])
 }
 if (!chart) {
 chart = new google.visualization.PieChart
 (document.getElementById('chart'));
 }
 chart.draw(data, {is3D: true});
 }
 }
 );
 window.setTimeout(update, 1000);
 };
 update();
 $("#delete").click(function(){
 if (confirm("Sure")) {
 $.ajax({
 url:"http://localhost:8080/"+poll+"key=abc123",
 type:"DELETE"
 })
 .done(function(){
 location.href = "/";
 })
 }
 });
 });
 });
 </script>
poll= update
 window.setTimeout
 update $.get GET
/polls/{id} {id}
results
google.visualization.PieChart
google.visualization.DataTable draw
setTimeout update
 click delete
 DELETE
 OPTIONS
handlePolls
nsqlookupd
nsqd
 counter
 twittervotes
api
web
 http://localhost:8081/
 Moods
happy,sad,fail,success
 bitballoon.com
 –
encoding/json –
–
http.Get
math/rand
 http.Request
 bar cafe movie_theater math/rand
 https://de
 velopers.google.com/places/documentation/supported_types
GET /journeys
[
 {
 name: "Romantic",
 journey: "park|bar|movie_theater|restaurant|florist"
 },
 {
 name: "Shopping",
 journey: "department_store|clothing_store|jewelry_store"
 }
]
name
 journey
GET /recommendations
 lat=1&lng=2&journey=bar|cafe&radius=10&cost=$...$$$$$
 lat lng
radius
cost
 $
 $$$$$ $...$$
 $$$$...$$$$$
 [
 {
 icon: "http://maps.gstatic.com/mapfiles/place_api/icons/cafe-
 71.png",
 lat: 51.519583, lng: -0.146251,
 vicinity: "63 New Cavendish St, London",
 name: "Asia House",
 photos: [{
 url: "https://maps.googleapis.com/maps/api/place/photo
 maxwidth=400&photoreference=CnRnAAAAyLRN"
 }]
 }, ...
 ]
 lat lng
 name vicinity
photos vicinity
 icon
 meander GOPATH journeys.go
package meander
type j struct {
 Name string
 PlaceTypes []string
}
var Journeys = []interface{}{
 j{Name: "Romantic", PlaceTypes: []string{"park", "bar",
 "movie_theater", "restaurant", "florist", "taxi_stand"}},
 j{Name: "Shopping", PlaceTypes: []string{"department_store", "cafe",
 "clothing_store", "jewelry_store", "shoe_store"}},
 j{Name: "Night Out", PlaceTypes: []string{"bar", "casino", "food",
 "bar", "night_club", "bar", "bar", "hospital"}},
 j{Name: "Culture", PlaceTypes: []string{"museum", "cafe", "cemetery",
 "library", "art_gallery"}},
 j{Name: "Pamper", PlaceTypes: []string{"hair_care", "beauty_salon",
 "cafe", "spa"}},
}
 j meander
 Journeys
 golint
 golint
 golint
 https://github.com/golang/lint
 []interface{}
meander main
 meander/cmd/meander
meander
 meander
 main cmd
 meander
 cmd meander
cmd/meander main.go
 package main
 func main() {
 //meander.APIKey = "TODO"
 http.HandleFunc("/journeys", func(w http.ResponseWriter,
 r *http.Request) {
 respond(w, r, meander.Journeys)
 })
 http.ListenAndServe(":8080", http.DefaultServeMux)
 }
 func respond(w http.ResponseWriter, r *http.Request, data []interface{})
 error {
 return json.NewEncoder(w).Encode(data)
 }
/journeys
 cmd/meander
 go run
http://localhost:8080/journeys Journeys
 [{
 Name: "Romantic",
 PlaceTypes: [
 "park",
 "bar",
 "movie_theater",
 "restaurant",
 "florist",
 "taxi_stand"
 ]
 }, ...]
 PlaceTypes Types
 journey meander
 public.go
 package meander
 type Facade interface {
 Public() interface{}
 }
 func Public(o interface{}) interface{} {
 if p, ok := o.(Facade); ok {
 return p.Public()
 }
 return o
 }
 Facade Public
 Public
 Facade Public() interface{}
 Public
 ResponseWriter
 Facade
 Reader Writer
 Publicer
 Public j
journeys.go
 j PlaceTypes
 cmd/meander/main.go respond
 Public
 meander.Public
 j Public
[{
 journey: "park|bar|movie_theater|restaurant|florist|taxi_stand",
 name: "Romantic"
}, ...]
[]string MarshalJSON
Facade Public
 meander
 query.go
package meander
type Place struct {
 *googleGeometry `json:"geometry"`
 Name string `json:"name"`
 Icon string `json:"icon"`
 Photos []*googlePhoto `json:"photos"`
 Vicinity string `json:"vicinity"`
 }
 type googleResponse struct {
 Results []*Place `json:"results"`
 }
 type googleGeometry struct {
 *googleLocation `json:"location"`
 }
 type googleLocation struct {
 Lat float64 `json:"lat"`
 Lng float64 `json:"lng"`
 }
 type googlePhoto struct {
 PhotoRef string `json:"photo_reference"`
 URL string `json:"url"`
 }
 http://developers.google.com/pla
 ces/documentation/search
 Place
 googleGeometry
 googleLocation
googleGeometry Lat Lng
 Place
 Place
 Public
 meander
 query.go
 main.go // APIKey
TODO
 iota const
 String
ParseType
cost_level.go meander
 package meander
 type Cost int8
 const (
 _ Cost = iota
 Cost1
 Cost2
 Cost3
 Cost4
 Cost5
 )
 Cost
 int8
 iota
Cost
 Cost
 iota
 iota
 String
 Cost
fmt.Println
 String()
 Stringer GoStringer fmt http://gola
 ng.org/pkg/fmt/#Stringer
 cost_level.go cost_level_test.go
 package meander_test
 import (
 "testing"
 "github.com/cheekybits/is"
 "path/to/meander"
 )
 func TestCostValues(t *testing.T) {
 is := is.New(t)
 is.Equal(int(meander.Cost1), 1)
 is.Equal(int(meander.Cost2), 2)
 is.Equal(int(meander.Cost3), 3)
 is.Equal(int(meander.Cost4), 4)
 is.Equal(int(meander.Cost5), 5)
 }
 go get is https://git
hub.com/cheekybits/is
is
 meander
 meander meander_test
 meander
 go test
 Cost
 cost_level_test.go
 String
 String
Cost String
 map[string]Cost
String
 strings.Repeat("$", int(l))
Cost3 $$$
ParseCost
cost_value_test.go
ParseCost
cost_value.go
Cost
 CostRange
 cost_value_test.go
 meander.CostRange
From meander.Cost2 To meander.Cost3 is.NoErr
CostRange.String
 CostRange String
ParseString
 $...$$$$$ Cost
 From To
 query.go
find
 url.Values map[string][]string
 make new
 types
 CostRangeStr minprice maxprice
 http.Get
 json.Decoder
 googleResponse
 find Run
 Query
Run rand
 Query.find sync.WaitGroup
 sync.Mutex
photoreference
 rand.Intn
 places
 sync.Mutex
 w.Wait
 /recommendations main.go
cmd/meander main
 http.HandleFunc("/recommendations", cors(func(w
 http.ResponseWriter, r *http.Request) {
 q := &meander.Query{
 Journey: strings.Split(r.URL.Query().Get("journey"), "|"),
 }
 var err error
 q.Lat, err = strconv.ParseFloat(r.URL.Query().Get("lat"), 64)
 if err != nil {
 http.Error(w, err.Error(), http.StatusBadRequest)
 return
 }
 q.Lng, err = strconv.ParseFloat(r.URL.Query().Get("lng"), 64)
 if err != nil {
 http.Error(w, err.Error(), http.StatusBadRequest)
 return
 }
 q.Radius, err = strconv.Atoi(r.URL.Query().Get("radius"))
 if err != nil {
 http.Error(w, err.Error(), http.StatusBadRequest)
 return
 }
 q.CostRangeStr = r.URL.Query().Get("cost")
 places := q.Run()
 respond(w, r, places)
 }))
 meander.Query Run
 http.Request
 Query Get
 bar|cafe|movie_theater
 strconv
 http.Error http.StatusBadRequest
 Access-Control-Allow-Origin *
 http.HandlerFunc
cmd/meander
main.go cors
http.HandlerFunc
 cors
 main
func main() {
 meander.APIKey = "YOUR_API_KEY"
 http.HandleFunc("/journeys", cors(func(w http.ResponseWriter,
 r *http.Request)
 {
 respond(w, r, meander.Journeys)
 }))
 http.HandleFunc("/recommendations", cors(func(w http.ResponseWriter,
 r *http.Request) {
 q := &meander.Query{
 Journey: strings.Split(r.URL.Query().Get("journey"), "|"),
 }
 var err error
 q.Lat, err = strconv.ParseFloat(r.URL.Query().Get("lat"), 64)
 if err != nil {
 http.Error(w, err.Error(), http.StatusBadRequest)
 return
 }
 q.Lng, err = strconv.ParseFloat(r.URL.Query().Get("lng"), 64)
 if err != nil {
 http.Error(w, err.Error(), http.StatusBadRequest)
 return
 }
 q.Radius, err = strconv.Atoi(r.URL.Query().Get("radius"))
 if err != nil {
 http.Error(w, err.Error(), http.StatusBadRequest)
 return
 }
 q.CostRangeStr = r.URL.Query().Get("cost")
 places := q.Run()
 respond(w, r, places)
 }))
 log.Println("serving meander API on :8080")
 http.ListenAndServe(":8080", http.DefaultServeMux)
 }
r.URL.Query()
cmd/meander meander
 meander
http://mygeoposition.com/ x,y
 51.520707 x 0.153809
 40.7127840 x -74.0059410
 35.6894870 x 139.6917060
 37.7749290 x -122.4194160
/recommendations
http://localhost:8080/recommendations
 lat=51.520707&lng=-0.153809&radius=5000&
 journey=cafe|bar|casino|restaurant&
 cost=$...$$$
 https
://github.com/matryer/goblueprints/tree/master/chapter7/meanderweb
 meanderweb GOPATH
meanderweb
 localhost:8081
 localhost:8080
 http://localhost:8081/
 Facade
iota
String
 
 ioutil os
os
filepath.Walk
 archive/zip
 cmd cmds
gofmt goimports
backup
/backup - package
/backup/cmds/backup - user interaction tool
/backup/cmds/backupd - worker daemon
 cmd
 go install
cmd
backup
Archiver
 GOPATH/src backup
archiver.go
 package backup
 type Archiver interface {
 Archive(src, dest string) error
 }
Archiver Archive
 io
Archiver
Archiver
struct archiver.go
 ZIP Archiver
 Archiver
 nil *zipper nil
 zipper zipper
zipper
 Archiver
 zipper Archive
Archive
 Archive
 zipper
archiver.go
 archive/zip
 Archive
os.MkdirAll 0777
 os.Create dest
 defer
 out.Close()
 zip.NewWriter zip.Writer
 zip.Writer filepath.Walk
 src
filepath.Walk
 filepath.Walk
 filepath.WalkFunc
filepath.WalkFunc
 filepath.WalkFunc func(path
 string, info os.FileInfo, err error) error
 filepath.Walk
 os.FileInfo
 SkipDir
filepath.Walk Archive
 info.IsDir nil
 Archive
 filepath.Walk
os.Open
Create ZipWriter
 io.Copy
ZipWriter
 nil
 golint
 fsnotify https://fsnotify.org
 https://github.com/fsnotify
os.FileInfo
dirhash.go
package backup
import (
 "crypto/md5"
 "fmt"
 "io"
 "os"
 "path/filepath"
)
func DirHash(path string) (string, error) {
 hash := md5.New()
 err := filepath.Walk(path, func(path string, info os.FileInfo, err error)
 error {
 if err != nil {
 return err
 }
 io.WriteString(hash, path)
 fmt.Fprintf(hash, "%v", info.IsDir())
 fmt.Fprintf(hash, "%v", info.ModTime())
 fmt.Fprintf(hash, "%v", info.Mode())
 fmt.Fprintf(hash, "%v", info.Name())
 fmt.Fprintf(hash, "%v", info.Size())
 return nil
 })
 if err != nil {
 return "", err
 }
 return fmt.Sprintf("%x", hash.Sum(nil)), nil
 }
 hash.Hash
filepath.Walk
 io.WriteString io.Writer
 fmt.Fprintf
 %v
nil %x
 Monitor Monitor
 Archiver
backup.ZIP
monitor.go
Now
act
Now
act
 Archiver
 Archive
 Archive act Now
act time.Now().UnixNano()
 .zip
 Archiver
 .zip
 Archiver
 Ext()
 act
 Archiver
archiver.go Archiver
zipper
 act Archiver
 act
backup main.go
func main() {
 var fatalErr error
 defer func() {
 if fatalErr != nil {
 flag.PrintDefaults()
 log.Fatalln(fatalErr)
 }
 }()
 var (
 dbpath = flag.String("db", "./backupdata", "path to database
directory")
 )
 flag.Parse()
 args := flag.Args()
 if len(args) < 1 {
 fatalErr = errors.New("invalid usage; must specify command")
 return
 }
}
 fatalErr
 nil
 db filedb
github.com/matryer/filedb
 mgo
 filedb
filedb
vendor
main
 filedb.Dial filedb
 mgo C
 col
fatalErr
 path
 filedb
 struct main
 flag.Args os.Args
 main
 switch strings.ToLower(args[0]) {
 case "list":
 case "add":
 case "remove":
 }
backup LIST
ForEach col
 ForEach
 path
fmt.Printf false filedb
true
 path
 InsertJSON add
 if len(args[1:]) == 0 {
 fatalErr = errors.New("must specify path to add")
 return
 }
 for _, p := range args[1:] {
 path := &path{Path: p, Hash: "Not yet archived"}
 if err := col.InsertJSON(path); err != nil {
 fatalErr = err
 return
 }
 fmt.Printf("+ %s\n", path)
 }
 backup
add
 +
 Not yet archived
 RemoveEach
 remove
RemoveEach
 backup
 backupdata backup/cmds/backup filedb
 main.go
 test3 remove
 filedb
 backup
backup backupd
 filedb
 backup
backupd backup/cmds/backup
func main() {
 var fatalErr error
 defer func() {
 if fatalErr != nil {
 log.Fatalln(fatalErr)
 }
 }()
 var (
 interval = flag.Duration("interval", 10 * time.Second, "interval
between
 checks")
 archive = flag.String("archive", "archive", "path to archive
location")
 dbpath = flag.String("db", "./db", "path to filedb database")
 )
 flag.Parse()
}
 interval archive db interval
archive db
 filedb backup
 flag.Parse
 Monitor
 main
 m := &backup.Monitor{
 Destination: *archive,
 Archiver: backup.ZIP,
 Paths: make(map[string]string),
 }
main
 paths fatalErr
 main
LastChecked backupd
 backup
 path
Paths
Paths
main
 ForEach
 path
 Paths
Paths
break
for {}
select
 check(m, col)
 signalChan := make(chan os.Signal, 1)
 signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
 for {
 select {
 case <-time.After(*interval):
 check(m, col)
 case <-signalChan:
 // stop
 fmt.Println()
 log.Printf("Stopping...")
 return
 }
 }
 check
 signal.Notify
 for
 timer
 timer check
signalChan
 time.After
 flag.Duration
 * time.Duration
flag.Duration
 10s 1m
 check Now
 Monitor
main
 check
Now Monitor
SelectEach
backupd
backup
 backupd
test test2
backupd
 db
 backup
 archive
 5
 backupd
 Not yet archived
 archive backup/cmds/backupd
 test test2
backupd
 test2
test one.txt backupd
 archive/test2
 one.txt
 test
 Archiver Restore
encoding/zip
os io
 encoding/zip
 app.yaml
https://cloud.google.com/appengine/downloads
 go_appengine
 GOPATH /Users/yourname/work/go_appengine
 https://github.com/matryer/goblueprint
s
 go_appengine $PATH
 go
 goapp go
 goapp test goapp vet
https://console.cloud.google.com
answersapp
 init
 http.Handle http.HandleFunc
 main
 GOPATH answersapp/api
 main.go
package api
import (
 "io"
 "net/http"
)
func init() {
 http.HandleFunc("/", handleHello)
}
func handleHello(w http.ResponseWriter, r *http.Request) {
 io.WriteString(w, "Hello from App Engine")
}
 ListenAndServe
 init main
 handleHello
 app.yaml
 answersapp/api
application: YOUR_APPLICATION_ID_HERE
version: 1
runtime: go
api_version: go1
handlers:
- url: /.*
 script: _go_app
application
version
runtime
 go
api_version go1
 go2
handlers
 _go_app
 answersapp/api
 :56443
 8000 default
localhost:8080
Hello from App Engine
 :8080 :8000
https://YOUR_APPLICATION_ID_HERE.appspot.com/
config
 api
default
 default main.go
 package defaultmodule
 func init() {}
default
default
 web
 api default web
https://github.com/matryer/goblueprints
 Chapter 9
 README
 web
 /answersapp/api
 /answersapp/default
 /answersapp/web
 api
app.yaml module
 application: YOUR_APPLICATION_ID_HERE
 version: 1
 runtime: go
 module: api
 api_version: go1
 handlers:
 - url: /.*
 script: _go_app
app.yaml api/app.yaml
default/app.yaml default
 application: YOUR_APPLICATION_ID_HERE
 version: 1
 runtime: go
 module: default
 api_version: go1
 handlers:
 - url: /.*
 script: _go_app
dispatch.yaml
 /api/ api
 web default
 answersapp
 dispatch.yaml
 application: YOUR_APPLICATION_ID_HERE
 dispatch:
 - url: "*/api/*"
 module: api
 - url: "*/*"
 module: web
 application
 dispatch
TweetBody
– AvatarURL UserAvatarURL
 AvatarURL UserAvatarURL
 “ ”
datastore
 Answer
Create datastore
 UserCard
 User
 datastore
 import "google.golang.org/appengine/datastore"
datastore.Key
datastore.NewKey datastore.NewIncompleteKey
datastore.Get datastore.Put
 Key
 Question datastore:"-" json
 Key
func (q Question) OK() error {
 if len(q.Question) < 10 {
 return errors.New("question is too short")
 }
 return nil
}
OK
 nil
 Question
 questions.go
Create Question
(q Question) *
 Question
 log https://godoc.org/google.golang.org/appen
gine/log
nil
 context.Context
 nil
 User CTime
 time.Now
 Question datastore.Put
 context.Context
datastore.Put error
 datastore.Key
 Key Question
nil
 CTime User
 datastore.Get
 datastore
 questions.go
datastore.Get datastore.Put
users.go
 https://godoc.org/google.golang.org/appengine/user
 user.Current(context.Context)
 user.User
User
goimports os/user
users.go
 appUser User
datastore.Key
datastore.NewKey
 User
 User
Get
datastore.Get User
datastore.ErrNoSuchEntity
 datastore.Put User
https://medium.com/@matryer
users.go
 User
UserCard
 Account
 answers.go
Answer datastore.Key
CTime UserCard
 Score
Question AnswerCount
AnswerCount
 datastore.RunInTransaction
answers.go
Answer
 UserFromAEUser
UserCard Answer CTime
datastore.RunInTransaction
 datastore.TransactionOptions
 XG true
 Answer Question
 TransactionOptions
 RunInTransaction
 GetQuestion
 AnswerCount
 AnswerCount
Answer.Create
GetAnswer GetQuestion
Put answers.go
 GetQuestion Question.Put
 datastore.Query
Score
answers.go
 answer.Key datastore.Key
GetAll
 Authorized
true
 datastore.NewQuery("Answer").
 Filter("Authorized =", true)
 Query Question Order
Limit
questions.go TopQuestions
 Question
questions.go datastore Question
datastore:",noindex"
,noindex
json
 noindex
 Answer
Vote
 noindex AnswerCard
UserCard QuestionCard
noindex
 Vote
votes.go
 *Card Vote
 Vote
UserCard
questions.go QuestionCard
AnswerCard answers.go
 Answer
Answer
 “
 ”
 Score
votes.go
castVoteInTransaction
 Parent
 http://bit.ly/lineofsightincode
CastVote
votes.go
Vote
Vote
Vote
 datastore.Get
datastore.ErrNoSuchEntity Vote
 delta
 1 -1
 -1 1 2
 -1 err !=
datastore.ErrNoSuchEntity
 delta
 Vote
CastVote datastore.RunInTransaction
v interface{} OK
 v ok true obj
 ok
 http.go
 http.Request v
 OK
 OK
respond http.go
respondErr http.go
 error
error
func TestPathParams(t *testing.T) {
 r, err := http.NewRequest("GET", "1/2/3/4/5", nil)
 if err != nil {
 t.Errorf("NewRequest: %s", err)
 }
 params := pathParams(r, "one/two/three/four")
 if len(params) != 4 {
 t.Errorf("expected 4 params but got %d: %v", len(params), params)
 }
 for k, v := range map[string]string{
 "one": "1",
 "two": "2",
 "three": "3",
 "four": "4",
 } {
 if params[k] != v {
 t.Errorf("%s: %s != %s", k, params[k], v)
 }
 }
 params = pathParams(r, "one/two/three/four/five/six")
 if len(params) != 5 {
 t.Errorf("expected 5 params but got %d: %v", len(params), params)
 }
 for k, v := range map[string]string{
 "one": "1",
 "two": "2",
 "three": "3",
 "four": "4",
 "five": "5",
 } {
 if params[k] != v {
 t.Errorf("%s: %s != %s", k, params[k], v)
 }
 }
}
http.Request
 go test -v
 http.go
 http.Request
 /questions/id
 /questions/123
questions id
POST /questions
GET /questions/{id}
GET /questions
 switch
 pathParams
 handle_questions.go
http.HandlerFunc
context.Context
Context
 Context
 ht
 tps://blog.golang.org/context
 appengine.NewContext
 http.Request
Context ctx
OK Create
respondErr
Question http.StatusCreated
 datastore.Key id
json
 datastore.Key datastore
 datastore.DecodeKey
 handle_questions.go
 question ID
 datastore.Key question ID
 question ID
datastore.Key GetQuestion Question
datastore.ErrNoSuchEntity
 http.StatusInternalServerError
 respond
POST /answers
GET /answers
 handle_answers.go http.HandlerFunc
/api/answersquestion_id=abc123
/api/answers
 Answer
datastore.Key
handle_answers.go handleAnswerCreate
Create
/votes
handle_votes.go
OK
-1 1
votes.go validScore
nil
 handleVote
handle_votes.go
handle_
main.go init
func init() {
 http.HandleFunc("/api/questions/", handleQuestions)
 http.HandleFunc("/api/answers/", handleAnswers)
 http.HandleFunc("/api/votes/", handleVotes)
}
 handleHello
 goapp
 :8080
dispatch.yaml
 localhost:8080
localhost:8000
Automatically generated indexes
index.yaml
appcfg.py
https://YOUR_APPLICATION_ID_HERE.appspot.com/
https://gokit.io
 @peterbourgon
“
”—
 vault –
 func veryLongFunctionWithLotsOfArguments(one string, two int, three
 http.Handler, four string) (bool, error) {
 log.Println("first line of the function")
 }
 POST
PUT DELETE
 POST /users
 {
 "name": "Mat",
 "twitter": "@matryer"
 }
201 Created
{
 "id": 1,
 "name": "Mat",
 "twitter": "@matryer"
}
protobuf
<person>
 <name>MAT</name>
</person>
{"name":"MAT"}
 “
 .proto
 https://github.com/google/proto
buf/releases protoc
$PATH
 proto3
 $GOPATH vault pb
 pb
 Vault Hash
Validate
Hash
Validate
 pb
 vault.proto
 syntax = "proto3";
 package pb;
 service Vault {
 rpc Hash(HashRequest) returns (HashResponse) {}
 rpc Validate(ValidateRequest) returns (ValidateResponse) {}
 }
 message HashRequest {
 string password = 1;
 }
 message HashResponse {
 string hash = 1;
 string err = 2;
 }
 message ValidateRequest {
 string password = 1;
 string hash = 2;
 }
 message ValidateResponse {
 bool valid = 1;
 }
 proto3
 pb
 https://developers.go
 ogle.com/protocol-buffers/docs/proto3
HashRequest
 HashResponse ValidateResponse
 error
 pb
vault.pb.go
VaultClient VaultServer
pb
vault service.go
 VaultService
 Service
 vault.Service
struct
service.go struct
service_test.go
package vault
import (
 "testing"
 "golang.org/x/net/context"
)
func TestHasherService(t *testing.T) {
 srv := NewService()
 ctx := context.Background()
 h, err := srv.Hash(ctx, "password")
 if err != nil {
 t.Errorf("Hash: %s", err)
 }
 ok, err := srv.Validate(ctx, "password", h)
 if err != nil {
 t.Errorf("Valid: %s", err)
 }
 if !ok {
 t.Error("expected true from Valid")
 }
 ok, err = srv.Validate(ctx, "wrong password", h)
 if err != nil {
 t.Errorf("Valid: %s", err)
 }
 if ok {
 t.Error("expected false from Valid")
 }
 }
 NewService Hash
 Validate
 Validate false –
vaultService Service
vaultService NewService
vaultService
Hash
Validate
 https://codahale.co
 m/how-to-safely-store-a-password/
bcrypt
service.go Hash
 Hash (vaultService)
 struct
Validate
Hash bcrypt.CompareHashAndPassword
false true
struct
 Hash
 service.go
 struct
 struct
Validate Service
 http.DecodeRequestFunc
 http.Request service.go
 decodeHashRequest
 json.Decoder
 hashRequest
Validate
hashResponse validateResponse
service.go
 encodeResponse json.Encoder
 response interface{}
 http.ResponseWriter
endpoint
 http.Handler http.HandlerFunc
 Endpoint
Service
 endpoint.Endpoint
 hashRequest Hash
 hashResponse
service.go MakeHashEndpoint
 Service
 Service
 hashRequest
Hash Password hashRequest
 hashResponse Hash
Validate
Endpoint
 Hash
 hashResponse
 Error
vault.Service
service.go
 vault.Service
 Endpoints
 Hash
 HashEndpoint hashRequest
 hashResponse
 package vault
 import (
 "net/http"
 httptransport "github.com/go-kit/kit/transport/http"
 "golang.org/x/net/context"
 )
 func NewHTTPServer(ctx context.Context, endpoints
 Endpoints) http.Handler {
 m := http.NewServeMux()
 m.Handle("/hash", httptransport.NewServer(
 ctx,
 endpoints.HashEndpoint,
 decodeHashRequest,
 encodeResponse,
 ))
 m.Handle("/validate", httptransport.NewServer(
 ctx,
 endpoints.ValidateEndpoint,
 decodeValidateRequest,
 encodeResponse,
 ))
 return m
 }
 github.com/go-kit/kit/transport/http
 net/http
 httptransport
 NewServeMux http.Handler
 /hash /validate
Endpoints
 httptransport.NewServer
context.Context
 pb
 pb.VaultServer
Service
server_grpc.go
package vault
import (
 "golang.org/x/net/context"
 grpctransport "github.com/go-kit/kit/transport/grpc"
)
type grpcServer struct {
 hash grpctransport.Handler
 validate grpctransport.Handler
}
func (s *grpcServer) Hash(ctx context.Context,
 r *pb.HashRequest) (*pb.HashResponse, error) {
 _, resp, err := s.hash.ServeGRPC(ctx, r)
 if err != nil {
 return nil, err
 }
 return resp.(*pb.HashResponse), nil
}
func (s *grpcServer) Validate(ctx context.Context,
 r *pb.ValidateRequest) (*pb.ValidateResponse, error) {
 _, resp, err := s.validate.ServeGRPC(ctx, r)
 if err != nil {
 return nil, err
 }
 return resp.(*pb.ValidateResponse), nil
}
 github.com/go-kit/kit/transport/grpc
grpctransport pb
 grpcServer
grpctransport.Handler
ServeGRPC
 pb
 service.go
 https://github.com/matryer/gobluepri
 nts
server_grpc.go
 EncodeRequestFunc
 hashRequest
 interface{}
 hashRequest
 pb.HashRequest
server_grpc.go
Endpoints
vault
 main
 htt
 ps://github.com/matryer/drop main
go install
import (
 "flag"
 "fmt"
 "log"
 "net"
 "net/http"
 "os"
 "os/signal"
 "syscall"
 "your/path/to/vault"
 "your/path/to/vault/pb"
 "golang.org/x/net/context"
 "google.golang.org/grpc"
)
your/path/to $GOPATH
 grpc
 main
 main
 func main() {
 var (
 httpAddr = flag.String("http", ":8080",
 "http listen address")
 gRPCAddr = flag.String("grpc", ":8081",
 "gRPC listen address")
 )
 flag.Parse()
 ctx := context.Background()
 srv := vault.NewService()
 errChan := make(chan error)
 :8080
 8081
context.Background()
NewService Service
errChan
 go func() {
 c := make(chan os.Signal, 1)
 signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
 errChan <- fmt.Errorf("%s", <-c)
 }()
 signal.Notify SIGINT
SIGTERM c
 String()
 errChan
 hashEndpoint := vault.MakeHashEndpoint(srv)
 validateEndpoint := vault.MakeValidateEndpoint(srv)
 endpoints := vault.Endpoints{
 HashEndpoint: hashEndpoint,
 ValidateEndpoint: validateEndpoint,
 }
 endpoints
 srv
 // HTTP transport
 go func() {
 log.Println("http:", *httpAddr)
 handler := vault.NewHTTPServer(ctx, endpoints)
 errChan <- http.ListenAndServe(*httpAddr, handler)
 }()
NewHTTPServer
http.ListenAndServe
httpAddr
 go func() {
 listener, err := net.Listen("tcp", *gRPCAddr)
 if err != nil {
 errChan <- err
 return
 }
 log.Println("grpc:", *gRPCAddr)
 handler := vault.NewGRPCServer(ctx, endpoints)
 gRPCServer := grpc.NewServer()
 pb.RegisterVaultServer(gRPCServer, handler)
 errChan <- gRPCServer.Serve(listener)
 }()
 gRPCAddr
errChan vault.NewGRPCServer
 Endpoints
 grpc
 pb RegisterVaultServer
 RegisterVaultService RegisterService
 grpcServer
 vault.pb.go
 RegisterVaultServer
 &_Vault_serviceDesc
 Serve
 errChan
<-errChan
 log.Fatalln(<-errChan)
}
 curl
 –
 vault/cmd/vaultd
 curl
hernandez
vault/client/grpc
 grpc
 vault.Service
vault/client/grpc
client.go
grpctransport github.com/go-kit/kit/transport/grpc
 Vault
Hash Validate
 vault.Endpoints
 vault.Service
 cmd vaultcli
func main() {
 var (
 grpcAddr = flag.String("addr", ":8081",
 "gRPC address")
 )
 flag.Parse()
 ctx := context.Background()
 conn, err := grpc.Dial(*grpcAddr, grpc.WithInsecure(),
 grpc.WithTimeout(1*time.Second))
 if err != nil {
 log.Fatalln("gRPC dial:", err)
 }
 defer conn.Close()
 vaultService := grpcclient.New(conn)
 args := flag.Args()
 var cmd string
 cmd, args = pop(args)
 switch cmd {
 case "hash":
 var password string
 password, args = pop(args)
 hash(ctx, vaultService, password)
 case "validate":
 var password, hash string
 password, args = pop(args)
 hash, args = pop(args)
 validate(ctx, vaultService, password, hash)
 default:
 log.Fatalln("unknown command", cmd)
 }
 }
 vault/client/grpc grpcclient
google.golang.org/grpc grpc vault
vault.Service
 os.Args
 flags.Args()
pop
pop
vaultcli main_test.go
main.go pop
vaultcli
 $GOPATH/bin
 $PATH
 cmd
 cmd/vaultd
$'PASTE_HASH_HERE'
!PASTE_HASH_HERE!
 valid
invalid
 h
ttps://en.wikipedia.org/wiki/Token_bucket
 github.com/juju/ratelimit
github.com/juju/ratelimit hashEndpoint
rlbucket := ratelimit.NewBucket(1*time.Second, 5)
NewBucket
ratelimit
endpoint.Middleware
 Endpoint Endpoint
 Endpoint
http.HandlerFunc Endpoint
 Endpoint
 Middleware
 NewTokenBucketLimiter ratelimit
 TakeAvailable
 next
e := getEndpoint(srv)
{
 e = getSomeMiddleware()(e)
 e = getLoggingMiddleware(logger)(e)
 e = getAnotherMiddleware(something)(e)
}
 hashEndpoint := vault.MakeHashEndpoint(srv)
 {
 hashEndpoint = ratelimitkit.NewTokenBucketLimiter
 (rlbucket)(hashEndpoint)
 }
 validateEndpoint := vault.MakeValidateEndpoint(srv)
 {
 validateEndpoint = ratelimitkit.NewTokenBucketLimiter
 (rlbucket)(validateEndpoint)
 }
 endpoints := vault.Endpoints{
 HashEndpoint: hashEndpoint,
 ValidateEndpoint: validateEndpoint,
 }
 hashEndpoint
validateEndpoint vault.Endpoints
errChan
 –
 NewTokenBucketThrottler
hashEndpoint := vault.MakeHashEndpoint(srv)
{
 hashEndpoint = ratelimitkit.NewTokenBucketThrottler(rlbucket,
 time.Sleep)(hashEndpoint)
}
validateEndpoint := vault.MakeValidateEndpoint(srv)
{
 validateEndpoint = ratelimitkit.NewTokenBucketThrottler(rlbucket,
 time.Sleep)(validateEndpoint)
}
endpoints := vault.Endpoints{
 HashEndpoint: hashEndpoint,
 ValidateEndpoint: validateEndpoint,
}
 NewTokenBucketThrottler
 time.Sleep
 time.Sleep
 proto3
bcrypt
https://gokit.io #go-kit
 
Chapter 9
https://github.com/docker/docker
 docker
 Chapter 10
https://www.docker.com/products/docker
 https://github.com/matryer/gob
lueprints
 Dockerfile
 vault Chapter 10
 Dockerfile
 FROM scratch
 MAINTAINER Your Name <your@email.address>
 ADD vaultd vaultd
 EXPOSE 8080 8081
 ENTRYPOINT ["/vaultd"]
Dockerfile
FROM
 https://hub.docker.com/_/scratch/
ADD vaultd
 vaultd
EXPOSE :8080
 :8081
ENTRYPOINT vaultd
MAINTAINER
 https://docs.docker.com/engine/reference/builde
 r/#dockerfile-reference
 CGO_ENABLED GOOS
 -a ./cmd/vaultd/
 vaultd
CGO_ENABLED=0
GOOS
 https://github.com/golang/go/
blob/master/src/go/build/syslist.go
vaultd
Dockerfile
 docker
 -t
 vaultd
 scratch
 ADD
-p
 localtest --name
 --rm
 curl
docker ps
0b5e35dca7cc
vaultd
/bin/sh -c /go/bin/vaultd
3 seconds ago
Up 2 seconds
0.0.0.0:6060->8080/tcp, 0.0.0.0:6061->8081/tcp
localtest
 docker stop
localtest
https://hub.docker.com
login
vault USERNAME/vault
 vault
 vaultd
push
https://hub.docker.com/r/matryer/vault/
USERNAME/vault
curl
 https://www.digitalocean.com
vault-service-1
Access console
 root
docker
USERNAME
 docker pull
 matryer/vault
 docker run -d
--rm
curl
IPADDRESS
 curl
 /validate
GOPATH
 .go
 https://g
olang.org/dl/
go/bin PATH
 PATH=$PATH:/opt/go/bin
 .bashrc
 PATH
 go/bin
PATH
go/bin
 PATH
 go go/bin
GOPATH PATH
 import
 GOPATH go get
 GOPATH
 GOPATH PATH
 GOPATH
 GOPATH
 go Users Work
 GOPATH
 PATH
 GOPATH go
 silk
$GOPATH/src/github.com/matryer/silk
 silk
 matryer
 GOPATH
 github.com/matryer/goblueprints
 GOPATH
 GOPATH tooling main.go
package main
import (
 "fmt"
)
func main() {
 return
 var name string
 name = "Mat"
 fmt.Println("Hello ", name)
}
package main
import (
 "fmt"
)
 func main() {
 return
 var name string
 name = "Mat"
 fmt.Println("Hello ", name)
 }
go fmt
 return go
vet
go vet
 https://golang
.org/cmd/vet/
 goimports
 import
 goimports import
 goimports
fmt
 import (
 "net/http"
 "sync"
 )
 go run main.go
fmt
goimports
 goimports -w
 .go
 goimports fmt
 import
 https://github.com/golang/go/wiki/ID
EsAndTextEditorPlugins
 http://www.sublimetext.co
m/
https://github.com/DisposaBoy
GoSublime
 GoSublime
 https://sublime.wbond.net/
 "on_save": [
 {
 "cmd": "gs9o_open",
 "args": {
 "run": ["sh", "go build . errors && go test -i && go test &&
 go vet && golint"],
 "focus_view": false
 }
 }
 ]
 on_save
 …
tooling
 main.go
main.go
on_save
on_save
 goimports go fmt
 main.go net/http fmt
 fmt
https://code.visualstudio.com
 .go
https://github.com/matryer/goblueprints/blob/master/appendixA/messycode/main.go
)
GOPATH
 82
 74
 171 74 76
 28 76 78
 78 79
290
 291 66
 290 66
 67
 292 68
 71 72
231 69 70
 233
 232
 233
 232
 220
 227
 51 52
 226
 228
136
 220
 137
 221 223
 138 139
 224 226
 118 119 121
 57
87
 304
 89
 304
 98 99
 112
 156
 87
 90
 90
 86
 83 84 10
 84 86
 81 16 17
19 22 238
 23 255
22 277
 15 16 19 282
 20 281
 23 283 285
 277 278
 348 279 280 281
 348
 163
 304 163 164 165
 109 336
 117 118 337
113 348
 113 116 117 339
 109 111 341
 339 340
166 213 346 347
 348
 339
 235
 237 338
 237
 239 334
 60 62 63
 333 56 58
 58 59
335 336
 334
 333
 334 219
 334
 335
 300 301
 107
 108
 295
 108
 294
 344 345 346
 123 126
 341 342 343 344
 200 201
 167
 9
 351
 351
 357 351
 359 354
 307 353 354
 180 244
 173 246 247
 308 246
 174 245
 174 250 251
 47 48 49
 309 249 250
 255 247 248
 249
 205 208
 259 260 261
 131 261 262
 131 132
 134 252
 257 258
 309 259
 256
 52 53 55 203
 59
 209 258
 352
 73 74
 73
 296 194 195
 195 196
 324
 321 305
 323 305
 325 294
 326
 252
312 251
 313 134
 133
 133
173
 166 243
 165 133
 42
 128 131
 132
 23 25 132
 311
 66
356
 356 50
 29 30 50
 50
 174
 286 180
 285 178
 287 179
 288 176 178
 256
 357
 28
 193 10
 15
 214 15
 210 12
213 13
 203 204 294
 201
 203 45
 209 210
 212 219
 219
327 240
 331
 328 329 158 159
 330
 102 103
 103 105 106
 305
 167 357 358
 278 357
 112
 305
 162 129 130
 130
25 26 27 301
 28 74 302
 302
 355 128
 355
 355 14
 12 13
 328 14
 107
28 359 360 361
 359 361
 39
 34 35 269
 29 30 270 271 273
 36
 28 29 273
 38 39 274 275 277
 35 274
 30 31
 28 151
 155 156
262 263 152
 267 153 154
 263 264 266 157
 320
 135 135
 136 137
 140
 146
 34 148 149
 148 150
30 31 142
 32 33 144 145 146
 150 151
 42
229
 231 216
 230
234 182
 185
 186 188
103