Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Learning Go - Build your first Slack app using ...
Search
Ernesto Jiménez
October 08, 2016
Programming
180
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Learning Go - Build your first Slack app using Go and App Engine
Ernesto Jiménez
October 08, 2016
More Decks by Ernesto Jiménez
See All by Ernesto Jiménez
From Zero to 400 webhooks/second in 24 hours
ernesto_jimenez
0
130
Keep packages backwards compatible
ernesto_jimenez
0
160
Inception. Go programs that generate Go code
ernesto_jimenez
3
470
Other Decks in Programming
See All in Programming
Discordを用いたラボオートメーション関連情報収集の自動化
noguhiro2002
0
480
ソフトウェアエンジニアにとっての生成AI - 特性を知って使い倒す / generative ai for software enginner
kishida
7
2.3k
GKE アップグレード前に知っておきたい Blue/Green と PDB の関係
stkk
0
130
Can LLMs Replicate 4 Years of Compose Migration? Exploring the boundaries of automation with 279 XML files from a real product
makun
0
120
レビュー履歴をAIに食わせて、 Compose移行を加速するs
shihochan
0
260
Loosening the Reins: Go Generics Get More Flexible
kuro_kurorrr
0
360
Go を使い始めて 2 ヶ月の学び / My first two months with Go
contour_gara
0
400
[DroidKaigi 26] How We Moved from Android Studio to Slack
thisaay
0
150
T3DD26: From RAGs to Riches
martinhelmich
0
130
Hono + Inertia + React で LP を構築した話
oukayuka
2
190
Deep dive into the select statement (GopherCon UK)
jespino
0
160
Pythonの実行はどこまで賢くなったのか? CPythonとPyPyから見る最適化のしくみ
curekoshimizu
4
2.4k
Featured
See All Featured
Bootstrapping a Software Product
garrettdimon
PRO
306
120k
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
430
KATA
mclloyd
PRO
35
15k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
1k
Site-Speed That Sticks
csswizardry
13
1.5k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8.3k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
3k
How Software Deployment tools have changed in the past 20 years
geshan
1
34k
Bash Introduction
62gerente
615
220k
Highjacked: Video Game Concept Design
rkendrick25
PRO
1
450
YesSQL, Process and Tooling at Scale
rocio
174
15k
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
Transcript
Learning Go Build your first Slack app using Go and
App Engine
1. Why Go? 2. Learning the basics of Go 3.
Deploying to App Engine 4. Building our Slack app
Building HTTP APIs in Go is extremely easy
Why go?
Go is what C++ should have been @ernesto_jimenez's joke
272 pages
1.368 pages just C++?
400 pages
None
it is very simple
and very powerful
it has great tooling
and the stdlib is amazing
You can be productive with go really quickly
Why am I here?
None
None
The very basics
Hello World
package main import "fmt" func main() { fmt.Println("Hello DevFest!") }
$ go run main.go Hello DevFest!
Hello World API
package main import ( "fmt" "net/http" ) func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") if name == "" { name = "DevFest" } fmt.Fprintf(w, "Hello %s!", name) }) http.ListenAndServe(":8080", http.DefaultServeMux) }
$ curl http://localhost:8080/ Hello DevFest! $ curl "http://localhost:8080/?name=world" Hello world!
Hello World JSON API
package main import ( "encoding/json" "fmt" "net/http" )
type request struct { Name string `json:"name"` } type response
struct { Message string `json:"message,omitempty"` Error string `json:"error,omitempty"` }
func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { var
data request err := json.NewDecoder(r.Body).Decode(&data) if err != nil { w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(response{ Error: err.Error(), }) return } json.NewEncoder(w).Encode(response{ Message: fmt.Sprintf("Hello %s!", data.Name), }) }) http.ListenAndServe(":8080", http.DefaultServeMux) }
$ curl http://localhost:8080/ -d '{"name": "DevFest"}' {"message":"Hello DevFest!"} $ curl
http://localhost:8080/ -d 'Something' {"error":"invalid character 'S' looking for beginning of value"}
Deploying to App Engine
Create your app.yaml
runtime: go api_version: go1 handlers: - url: /.* script: _go_app
Tweak your code
package app // rename your package import ( "encoding/json" "fmt"
"net/http" ) func init() { // change main for init http.HandleFunc("/", handler) // remove http.ListenAndServe } func handler(w http.ResponseWriter, r *http.Request) { // ... }
Install the Go App Engine SDK https://cloud.google.com/appengine/downloads
Run your API locally
$ goapp serve Skipping SDK update check. Starting API server
at: http://localhost:63885 Starting module "default" running at: http://localhost:8080 Starting admin server at: http://localhost:8000 default: "POST / HTTP/1.1" 200 29 $ curl http://localhost:8080/ -d '{"name": "DevFest"}' {"message":"Hello DevFest!"}
None
Deploying your application
Create a Google Cloud Project https://console.cloud.google.com/ save your project id
$ goapp deploy \ --application [ YOUR PROJECT ID ]
\ --version [ OPTIONAL VERSION ID ] \ app.yaml
$ goapp deploy \ --application devfest-bot \ --version hello-world \
app.yaml
$ curl http://hello-world.devfest-bot.appspot.com/ \ -d '{"name": "DevFest"}' {"message":"Hello DevFest!"}
Building our Slack app
None
token=uitkEUWQ9mLLyNZPKsitLjGE team_id=T0001 team_domain=example channel_id=C2147483705 channel_name=test user_id=U2147483697 user_name=Steve command=/weather text=London response_url=https://hooks.slack.com/commands/1234/5678
/echo
package echo import ( "fmt" "net/http" ) func init() {
http.HandleFunc("/command", command) } func command(w http.ResponseWriter, r *http.Request) { user := r.PostFormValue("user_name") text := r.PostFormValue("text") fmt.Fprintf(w, "%s, %s", user, text) }
Testing it out locally
$ goapp serve $ ngrok http 8080 Web Interface http://127.0.0.1:4040
Forwarding http://274a0d24.ngrok.io -> localhost:8080 Forwarding https://274a0d24.ngrok.io -> localhost:8080
None
None
None
Deploying
$ goapp deploy \ --application devfest-bot \ --version echo \
app.yaml
None
None
None
None
Securing the command
go get google.golang.org/appengine
import ( "fmt" "net/http" "os" "google.golang.org/appengine" "google.golang.org/appengine/log" )
func init() { http.HandleFunc("/command", checkToken(os.Getenv("TOKEN"), command)) } func checkToken(token string,
h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := appengine.NewContext(r) if r.PostFormValue("token") != token { log.Warningf(ctx, "Invalid token") w.WriteHeader(http.StatusUnauthorized) return } h(w, r) } } func command(w http.ResponseWriter, r *http.Request) { user := r.PostFormValue("user_name") text := r.PostFormValue("text") fmt.Fprintf(w, "%s, %s", user, text) }
$ appcfg.py update \ --application devfest-bot \ --version echo \
-E TOKEN:[ YOUR TOKEN ] \ app.yaml
$ curl -iX POST http://echo.devfest-bot.appspot.com/command \ -d token=invalid HTTP/1.1 401
Unauthorized Content-Type: text/html; charset=utf-8 X-Cloud-Trace-Context: 249651e6b5324a76026990575350b6b4;o=1 Date: Sun, 02 Oct 2016 17:31:55 GMT Server: Google Frontend Content-Length: 0
/translate
go get google.golang.org/api/translate/v2
import ( "fmt" "net/http" "os" "google.golang.org/appengine" "google.golang.org/appengine/log" "google.golang.org/api/googleapi/transport" "google.golang.org/api/translate/v2" "google.golang.org/appengine/urlfetch"
)
var apiKey = os.Getenv("TRANSLATE_KEY") func command(w http.ResponseWriter, r *http.Request) {
ctx := appengine.NewContext(r) ts, err := translate.New(&http.Client{ Transport: &transport.APIKey{ Key: apiKey, Transport: &urlfetch.Transport{Context: ctx}, }, }) if err != nil { log.Errorf(ctx, "Error translate.New: %s", err.Error()) http.Error(w, "internal error", http.StatusInternalServerError) return } // ...
// ... t := ts.Translations.List([]string{ r.PostFormValue("text"), }, "en") res, err
:= t.Do() if err != nil { log.Errorf(ctx, "Error translating: %s", err.Error()) fmt.Fprintf(w, "Error: %s", err.Error()) return } for _, t := range res.Translations { text := t.TranslatedText lang := t.DetectedSourceLanguage fmt.Fprintf(w, "%s (%s)\n", text, lang) } }
None
None
A nicer output
type message struct { Text string `json:"text"` Attachments []attachment `json:"attachments,omitempty"`
ResponseType string `json:"response_type,omitempty"` } type attachment struct { Text string `json:"text"` Color string `json:"color"` }
func command(w http.ResponseWriter, r *http.Request) { // [ initialise ts
] w.Header().Set("Content-Type", "application/json") t := ts.Translations.List([]string{r.PostFormValue("text")}, "en") res, err := t.Do() if err != nil { log.Errorf(ctx, "Error translating: %s", err.Error()) json.NewEncoder(w).Encode(message{ Text: "Error", Attachments: []attachment{ {Color: "danger", Text: err.Error()}, }, }) return } // ...
// ... msg := message{ Text: fmt.Sprintf("Translating %q", r.PostFormValue("text")), ResponseType:
"in_channel", } for _, t := range res.Translations { msg.Attachments = append(msg.Attachments, attachment{ Color: "good", Text: fmt.Sprintf( "%s (%s)", t.TranslatedText, t.DetectedSourceLanguage, ), }) } json.NewEncoder(w).Encode(msg) }
None
under 100 lines of code
production ready
the source code git.io/devfest-london-2016
the Slack invite.slack.golangbridge.org #golang-newbies
Happy Hacking! @ernesto_jimenez
[email protected]