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
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Ernesto Jiménez
October 08, 2016
Programming
170
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
120
Keep packages backwards compatible
ernesto_jimenez
0
150
Inception. Go programs that generate Go code
ernesto_jimenez
3
460
Other Decks in Programming
See All in Programming
Lemonade + Foundry Toolkit でお手軽アプリ開発
seosoft
1
310
The Arts and Crafts of Work in the AI Era — Toward Mastery in Software Development
kuranuki
1
730
CLIであることを活かしたGitHub Copilot CLI活用術 / GitHub Copilot CLI Pro Tips & Tricks
nao_mk2
1
1.2k
dRuby over BLE
makicamel
2
320
Oxlintのカスタムルールの現況
syumai
6
1k
Observability in Practice:Grafana 與 Edge Device SRE 的那些事
blueswen
0
130
軽量Java基盤の設計 DIコンテナに頼らない、長期保守と1秒起動の実現 JJUG CCC 2026 Spring
macha64
0
460
気づいたらRubyで100作品 ー クリエイティブコーディングが生活の一部になるまで / 100 Ruby Sketches Later: How Creative Coding Became Part of My Life
chobishiba
3
550
Semantic Version 単位で戦略を柔軟に変えて、パッケージアップデートを自動化する
daitasu
0
170
「AIで開発し、AIを届ける」をEvalでつなぐ 〜AIネイティブに始めるプロダクト開発の実践〜 / Connecting "Develop with AI, deliver AI" with Eval
rkaga
4
4.6k
Technical Debt: Understanding it Rightly, Engaging it Rightly #LaravelLiveJP
shogogg
0
200
OSもどきOS
arkw
0
460
Featured
See All Featured
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.1k
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
Practical Orchestrator
shlominoach
191
11k
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
210
Utilizing Notion as your number one productivity tool
mfonobong
4
320
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
55
3.4k
Side Projects
sachag
455
43k
Typedesign – Prime Four
hannesfritz
42
3.1k
We Are The Robots
honzajavorek
0
240
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
The Curse of the Amulet
leimatthew05
1
13k
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
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]