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
AgentCore CLI で進化した AWS での AI エージェントの作り方 : 必要な機能を必要な時に
icoxfog417
PRO
3
370
一人だけ、Kiroが静止する日
hideg
0
130
Security issues being discussed on Web Platforms
petamoriken
0
1.2k
thread_parallel_with_free-threaded_Python_and_NumPy.pdf
riku_sakamoto
0
350
The Rails Doctrine Decade
koic
2
350
AI Agent時代のリアーキテクチャ戦略と実践
hokaccha
9
4.9k
フロントエンドUIフレームワークのこれまでとこれから
ssssota
5
2.9k
AWS Step Functions 大規模並列の壁を越える / jaws-sonic-2026-niigata-step-functions
kasacchiful
PRO
1
510
モデルのリファクタリングが難しいと思ったら、そもそも複雑だったのはビジネス仕様だった ? / is-the-business-domain-the-real-complexity
hatsu38
0
390
モバイル交通系ICへのチャージ実例から考える、クロスプラットフォーム開発におけるiOS実機テスト設計とCI運用
yusuga
1
540
Intent as Code
shoppingjaws
6
1.1k
AWS DevOps Agentで インシデント対応をAIに任せたい
honmarkhunt
7
3.1k
Featured
See All Featured
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
440
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.8k
Embracing the Ebb and Flow
colly
88
5.2k
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
1
390
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
470
Being A Developer After 40
akosma
91
590k
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
680
Speed Design
sergeychernyshev
33
2.1k
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2.2k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
250
1.3M
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
570
So, you think you're a good person
axbom
PRO
2
2.2k
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]