Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Learning Go - Build your first Slack app using ...
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
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
170
Inception. Go programs that generate Go code
ernesto_jimenez
3
470
Other Decks in Programming
See All in Programming
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
640
Everything will be SERVERLESS — 信じて運用した10年の経験値 / Everything Will be Serverless — Lessons Learned from 10 Years of Operational Experience
seike460
PRO
1
990
Unity6 URPによる海底をテーマにしたVJシステムの解説 | Unity Shader 完全に理解した 勉強会
gam0022
0
360
更なる可用性を求めて、5年間運用したKotlinのアプリケーションをGoでリプレイスする話
ken_tunc
0
580
難しいけど、読めた。- OSSの入口に立った話。
sts11142
0
150
AIに “答え“を書かせるのを やめてみた
___akari11
0
120
HTML-in-Canvasで作る未来のWeb UI表現
clockmaker
4
4.7k
dotNET_vulnclicker_intro.pdf
deka_morita
0
140
Findy - エンジニア向け会社紹介/Findy Company Deck
findyinc
6
400k
[Rails World 2026] Durable orchestration on Rails: from continuation to workflow
palkan
1
730
contenteditable と日本語入力に向き合う
colorful12
0
190
残高管理から台帳サービスへの進化
artoy
1
190
Featured
See All Featured
WCS-LA-2024
lcolladotor
0
880
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
370
Principles of Awesome APIs and How to Build Them.
keavy
128
18k
Building a A Zero-Code AI SEO Workflow
portentint
PRO
0
780
Paper Plane
katiecoart
PRO
4
54k
Java REST API Framework Comparison - PWX 2021
mraible
34
9.8k
エンジニアに許された特別な時間の終わり
watany
109
250k
Everyday Curiosity
cassininazir
0
350
The Limits of Empathy - UXLibs8
cassininazir
2
720
Navigating the Design Leadership Dip - Product Design Week Design Leaders+ Conference 2024
apolaine
2
470
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
560
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]