SlideShare a Scribd company logo
神に近づくx/net/context
Finding God with x/net/context
11 March 2015
Gregory Roseberry
Gunosy
※ This talk has nothing to do with religion.
※ Go Gopher by Renee French
Let's make an API server
Attempt #1: Standard library
Everyone told me to use the standard library, let's use it.
Index page says hello
Secret message page requires key
func main() {
http.HandleFunc("/", indexHandler)
http.HandleFunc("/secret/message", requireKey(secretMessageHandler))
http.ListenAndServe(":8000", nil)
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "hello world")
}
Secret message
func secretMessageHandler(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "42")
}
Here's a way to write middleware with just the standard library:
func requireKey(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.FormValue("key") != "12345" {if r.FormValue("key") != "12345" {
http.Error(w, "bad key", http.StatusForbidden)
return
}
h(w, r)
}
}
In main.go:
http.HandleFunc("/secret/message", requireKey(secretMessageHandler))
There's been a change of plans...
We were hard-coding the key, but your boss says now we need to check Redis.
Let's just make our Redis connection a global variable for now...
var redisDB *redis.Client
func main() {
redisDB = ... // set up redis
}
func requireKeyRedis(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
key := r.FormValue("key")
userID, err := redisDB.Get("auth:" + key).Result()userID, err := redisDB.Get("auth:" + key).Result()
if key == "" || err != nil {if key == "" || err != nil {
http.Error(w, "bad key", http.StatusForbidden)
return
}
log.Println("user", userID, "viewed message")
h(w, r)
}
}
Just one quick addition...
We need to issue temporary session tokens for some use cases, so we need to check if
either a key or a session is provided.
func requireKeyOrSession(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
key := r.FormValue("key")
// set key from db if we have a session
if session := r.FormValue("session"); session != "" {if session := r.FormValue("session"); session != "" {
var err error
if key, err = redisDB.Get("session:" + session).Result(); err != nil {if key, err = redisDB.Get("session:" + session).Result(); err != nil {
http.Error(w, "bad session", http.StatusForbidden)
return
}
}
userID, err := redisDB.Get("auth:" + key).Result()
if key == "" || err != nil {
http.Error(w, "bad key", http.StatusForbidden)
return
}
log.Println("user", userID, "viewed message")
h(w, r)
}
}
By the way...
Your boss also asks:
Can we also check the X-API-Key header?
Can we restrict certain keys to certain IP addresses?
Can we ...?
There's too much to shove into one middleware: so we make an auth package.
package auth
type Auth struct {
Key string
Session string
UserID string
}
func Check(r *http.Request) (auth Auth, ok bool) {
// lots of complicated checks
}
What about Redis?
We need to reference the DB from our new auth package as well.
Should we pass the connection to Check?
func Check(redisDB *redis.Client, r *http.Request) (Auth, bool) { ... }
What happens we need to check MySQL as well?
func Check(redisDB *redis.Client, archiveDB *sql.DB, r *http.Request) (Auth, bool) { ... }
Your boss says MongoDB is web scale, so that gets added too.
func Check(redisDB *redis.Client, archiveDB *sql.DB, mongo *mgo.Session, r *http.Request) (Auth, bool) { ...
This isn't going to work...
How about an init method?
Making a global here too?
var redisDB *redis.Client
func Init(r *redis.Client, ...) {
redisDB = r
}
That doesn't solve our arguments problem. Let's shove them in a struct.
package config
type Context struct {
RedisDB *redis.Client
ArchiveDB *sql.DB
...
}
Init with this guy?
auth.Init(appContext)
Who inits who?
What about tests?
Just one more thing...
Your boss says it's vital that we log every request now, and include the key and user ID if
possible.
It's easy to write logging middleware, but how can we make our logger aware of our
Auth credentials?
Session table
Let's try making a global map of connections to auths.
var authMap map[*http.Request]*auth.Auth
Then populate it during our check.
func requireKeyOrSession(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
...
a, ok := auth.Check(dbContext, r)
authMapMutex.Lock()
authMap[r] = &a
...
}
}
Should work, but will our *http.Requests leak? We need to make sure to clean them up.
What happens when we need to keep track of more than just Auth?
How do we coordinate this data across packages? What about concurrency?
(This is kind of how gorilla/sessions works)
There's got to be another way...
Attempt #2: Goji
Goji is a popular web micro-framework. Goji handlers take an extra parameter called
web.C (probably short for Context).
c.Env is a map[interface{}]interface{} for storing arbitrary data — perfect for our auth
token! This used to be a map[string]interface{}, more on this later.
Let's rewrite our auth middleware for Goji:
func requiresKey(c *web.C, h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
a := c.Env["auth"]
if a == nil {
http.Error(w, "bad key", http.StatusForbidden)
return
}
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
Goji groups
We can set up groups of routes:
package main
import (
"github.com/zenazn/goji"
"github.com/zenazn/goji/web"
)
func main() {
...
secretGroup := web.New()
secretGroup.Use(requiresKey)
secretGroup.Get("/secret/message", secretMessageHandler)
goji.Handle("/secret/*", secretGroup)
goji.Serve()
}
This will run our checkAuth for all routes under /secret/.
Goji benefits
Fast routing
Middleware groups
Request context
Einhorn support (zero-downtime deploys)
Downside: Goji-flavored context
Let's say we want to re-use our auth package elsewhere, like a batch process.
Do we want to put our database connections in web.C, even if we're not running a web
server? Should all of our internal packages be importing Goji?
package auth
func Check(c web.C, session, key string) bool {
// How do we call this if we're not using goji?
redisDB, _ := c.Env["redis"].(*redis.Client) // kind of ugly...
}
Having to do a type assertion every time we use this DB is annoying. Also, what happens
when some other library wants to use this "redis" key?
Downside: Groups need to be set up once, in main.go
Defining middleware for a group is tricky. What happens if you have code like...
package addon
func init() {
goji.Get("/secret/addon", addonHandler) // will secretGroup handle this?
}
Everything works will if your entire app is set up in main.go, but in my experience it's
very finicky and hard to reason about handlers that are set up in other ways.
There's got to be another way...!
Attempt #3: kami & x/net/context
What is x/net/context?
It's an almost-standard package for sharing context across your entire app.
Includes facilities for setting deadlines and cancelling requests.
Includes a way to store data similar to Goji's web.C.
Immutable, must be replaced to update
Check out this official blog post, which focuses mostly on x/net/context for cancellation:
blog.golang.org/context(https://blog.golang.org/context)
Quick example:
ctx := context.Background() // blank context
ctx = context.WithValue(ctx, "my_key", "my_value")
fmt.Println(ctx.Value("my_key").(string)) // "my_value"
kami
kami is a mix of HttpRouter, x/net/context, and Goji, with a very simple middleware
system included.
package main
import (
"fmt"
"net/http"
"github.com/guregu/kami"
"golang.org/x/net/context"
)
func hello(ctx context.Context, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", kami.Param(ctx, "name"))
}
func main() {
kami.Get("/hello/:name", hello)
kami.Serve()
}
Example: sharing DB connections
import "github.com/guregu/db"
I made a simple package for storing DB connections in your context. At Gunosy, we use
something similar. db.OpenSQL() returns a new context containing a named SQL
connection.
func main() {
ctx := context.Background()
mysqlURL := "root:hunter2@unix(/tmp/mysql.sock)/myCoolDB"
ctx = db.OpenSQL(ctx, "main", "mysql", mysqlURL)ctx = db.OpenSQL(ctx, "main", "mysql", mysqlURL)
defer db.Close(ctx) // closes all DB connectionsdefer db.Close(ctx) // closes all DB connections
kami.Context = ctxkami.Context = ctx
kami.Get("/hello/:name", hello)
kami.Serve()
}
kami.Context is our "god context" from which all request contexts are derived.
Example: sharing DB connections (2)
Within a request, we use db.SQL(ctx, name) to retrieve the connection.
func hello(ctx context.Context, w http.ResponseWriter, r *http.Request) {
mainDB := db.SQL(ctx, "main") // *sql.DBmainDB := db.SQL(ctx, "main") // *sql.DB
var greeting string
mainDB.QueryRow("SELECT content FROM greetings WHERE name = ?", kami.Param(ctx, "name")).
Scan(&greeting)
fmt.Fprintf(w, "Hello, %s!", greeting)
}
Tests
For tests, you can put a mock DB connection in your context.
main_test.go:
import _ "github.com/mycompany/testhelper"
testhelper/testhelper.go:
import (
"github.com/guregu/db"
"github.com/guregu/kami"
_ "github.com/guregu/mogi"
)
func init() {
ctx := context.Background()
// use mogi for tests
ctx = db.OpenSQL("main", "mogi", "")
kami.Context = ctx
}
How does it work?
Because context.Value() takes an interface{}, we can use unexported type as the key to
"protect" it. This way, other packages can't screw with your data. In order to interact with
a database, you have to use the exported functions like OpenSQL, and Close.
package db
import (
"database/sql"
"golang.org/x/net/context"
)
type sqlkey string // lowercase!
// SQL retrieves the *sql.DB with the given name or nil.
func SQL(ctx context.Context, name string) *sql.DB {
db, _ := ctx.Value(sqlkey(name)).(*sql.DB)
return db
}
BTW: This is why Goji switched its web.C from a map[string]interface{} to
map[interface{}]interface{}.
Middleware
kami has no concept of middleware "groups". Middleware is strictly hierarchical.
For example, a request for /secret/message would run the middleware registered under
the following paths in order:
/
/secret/
/secret/message
This means that you can define your paths anywhere and still get predictable
middleware behavior.
kami.Use("/secret/", requireKey)
Middleware (2)
kami.Middleware is defined as:
type Middleware func(context.Context, http.ResponseWriter, *http.Request) context.Context
The context you return will be used for the next middleware or handler.
Unlike Goji, you don't have control of how the next handler will be called. But, you can
return nil to halt the execution chain.
Middleware (3)
import "github.com/mycompany/auth"
func init() {
kami.Use("/", doAuth)
kami.Use("/secret/", requiresKey)
}
// doAuth returns a new context with the appropiate auth object inside
func doAuth(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
if a, err := auth.ByKey(ctx, r.FormValue("key")); err == nil {
// put auth object in context
ctx = auth.NewContext(ctx, a)
}
return ctx
}
// requiresKey stops the request if we don't have an auth object
func requiresKey(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
if _, ok := auth.FromContext(ctx); !ok {
http.Error(w, "bad key", http.StatusForbidden)
return nil // stop request
}
return ctx
}
Hooks
kami provides special hooks for logging and recovering from panics, kami.LogHandler
and kami.PanicHandler.
Handling panics.
kami.PanicHandler = func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
err := kami.Exception(ctx)
a, _ := auth.FromContext(ctx)
log.Println("panic", err, a)
}
Logging request statuses. Notice how the function signature is different, it takes a writer
proxy that includes the status code.
kami.LogHandler = func(ctx context.Context, w mutil.WriterProxy, r *http.Request) {
a, _ := auth.FromContext(ctx)
log.Println("access", w.Status(), r.URL.Path, "from:", a.Key, a.UserID)
}
LogHandler will run after PanicHandler, unless LogHandler is the one panicking.
Graceful
This is the "Goji" part of kami. Literally copy and pasted from Goji.
kami.Serve() // works *exactly* like goji.Serve()
Supports Einhorn for graceful restarts.
Thank you, Goji.
Downsides
kami isn't perfect. It is rather inflexible and may not fit your needs.
You can't define separate groups of middleware, or separate groups of handlers,
everything is global. You could mount kami.Handler() outside of "/" and use another
router...
You can't register middleware under wildcard paths: kami.Use("/user/:id/profile",
middleware) won't work. Register it under /user/ and do your best.
I will probably fix these issues eventually. Might have to fork HttpRouter...
Pull requests are always welcome.
Production ready!
We use kami to power the Gunosy API and it works just fine!
Switching to x/net/context eliminates nearly all global variables.
No more somepkg.Init() madness.
Easy to test: just put mocks inside your context.
Check it out!
Thank you
Gregory Roseberry
Gunosy
greg@toki.waseda.jp(mailto:greg@toki.waseda.jp)
https://github.com/guregu(https://github.com/guregu)

More Related Content

What's hot (20)

CouchDB on Android
CouchDB on AndroidCouchDB on Android
CouchDB on Android
Sven Haiges
 
Rest in flask
Rest in flaskRest in flask
Rest in flask
Yehor Nazarkin
 
Pycon - Python for ethical hackers
Pycon - Python for ethical hackers Pycon - Python for ethical hackers
Pycon - Python for ethical hackers
Mohammad Reza Kamalifard
 
BlockChain implementation by python
BlockChain implementation by pythonBlockChain implementation by python
BlockChain implementation by python
wonyong hwang
 
Caching and tuning fun for high scalability
Caching and tuning fun for high scalabilityCaching and tuning fun for high scalability
Caching and tuning fun for high scalability
Wim Godden
 
Redis for your boss
Redis for your bossRedis for your boss
Redis for your boss
Elena Kolevska
 
Redis for your boss 2.0
Redis for your boss 2.0Redis for your boss 2.0
Redis for your boss 2.0
Elena Kolevska
 
REST with Eve and Python
REST with Eve and PythonREST with Eve and Python
REST with Eve and Python
PiXeL16
 
Socket programming with php
Socket programming with phpSocket programming with php
Socket programming with php
Elizabeth Smith
 
Mongo db for c# developers
Mongo db for c# developersMongo db for c# developers
Mongo db for c# developers
Simon Elliston Ball
 
The emerging world of mongo db csp
The emerging world of mongo db   cspThe emerging world of mongo db   csp
The emerging world of mongo db csp
Carlos Sánchez Pérez
 
OWASP Proxy
OWASP ProxyOWASP Proxy
OWASP Proxy
Security B-Sides
 
My app is secure... I think
My app is secure... I thinkMy app is secure... I think
My app is secure... I think
Wim Godden
 
Mongo db for C# Developers
Mongo db for C# DevelopersMongo db for C# Developers
Mongo db for C# Developers
Simon Elliston Ball
 
Node.js - async for the rest of us.
Node.js - async for the rest of us.Node.js - async for the rest of us.
Node.js - async for the rest of us.
Mike Brevoort
 
Going crazy with Node.JS and CakePHP
Going crazy with Node.JS and CakePHPGoing crazy with Node.JS and CakePHP
Going crazy with Node.JS and CakePHP
Mariano Iglesias
 
Redis for the Everyday Developer
Redis for the Everyday DeveloperRedis for the Everyday Developer
Redis for the Everyday Developer
Ross Tuck
 
Beyond php it's not (just) about the code
Beyond php   it's not (just) about the codeBeyond php   it's not (just) about the code
Beyond php it's not (just) about the code
Wim Godden
 
Inside MongoDB: the Internals of an Open-Source Database
Inside MongoDB: the Internals of an Open-Source DatabaseInside MongoDB: the Internals of an Open-Source Database
Inside MongoDB: the Internals of an Open-Source Database
Mike Dirolf
 
Ruby gems
Ruby gemsRuby gems
Ruby gems
Papp Laszlo
 
CouchDB on Android
CouchDB on AndroidCouchDB on Android
CouchDB on Android
Sven Haiges
 
BlockChain implementation by python
BlockChain implementation by pythonBlockChain implementation by python
BlockChain implementation by python
wonyong hwang
 
Caching and tuning fun for high scalability
Caching and tuning fun for high scalabilityCaching and tuning fun for high scalability
Caching and tuning fun for high scalability
Wim Godden
 
Redis for your boss 2.0
Redis for your boss 2.0Redis for your boss 2.0
Redis for your boss 2.0
Elena Kolevska
 
REST with Eve and Python
REST with Eve and PythonREST with Eve and Python
REST with Eve and Python
PiXeL16
 
Socket programming with php
Socket programming with phpSocket programming with php
Socket programming with php
Elizabeth Smith
 
My app is secure... I think
My app is secure... I thinkMy app is secure... I think
My app is secure... I think
Wim Godden
 
Node.js - async for the rest of us.
Node.js - async for the rest of us.Node.js - async for the rest of us.
Node.js - async for the rest of us.
Mike Brevoort
 
Going crazy with Node.JS and CakePHP
Going crazy with Node.JS and CakePHPGoing crazy with Node.JS and CakePHP
Going crazy with Node.JS and CakePHP
Mariano Iglesias
 
Redis for the Everyday Developer
Redis for the Everyday DeveloperRedis for the Everyday Developer
Redis for the Everyday Developer
Ross Tuck
 
Beyond php it's not (just) about the code
Beyond php   it's not (just) about the codeBeyond php   it's not (just) about the code
Beyond php it's not (just) about the code
Wim Godden
 
Inside MongoDB: the Internals of an Open-Source Database
Inside MongoDB: the Internals of an Open-Source DatabaseInside MongoDB: the Internals of an Open-Source Database
Inside MongoDB: the Internals of an Open-Source Database
Mike Dirolf
 

Viewers also liked (20)

golang.tokyo #6 (in Japanese)
golang.tokyo #6 (in Japanese)golang.tokyo #6 (in Japanese)
golang.tokyo #6 (in Japanese)
Yuichi Murata
 
Operations: Production Readiness Review – How to stop bad things from Happening
Operations: Production Readiness Review – How to stop bad things from HappeningOperations: Production Readiness Review – How to stop bad things from Happening
Operations: Production Readiness Review – How to stop bad things from Happening
Amazon Web Services
 
AWS X-Rayによるアプリケーションの分析とデバッグ
AWS X-Rayによるアプリケーションの分析とデバッグAWS X-Rayによるアプリケーションの分析とデバッグ
AWS X-Rayによるアプリケーションの分析とデバッグ
Amazon Web Services Japan
 
AndApp開発における全て #denatechcon
AndApp開発における全て #denatechconAndApp開発における全て #denatechcon
AndApp開発における全て #denatechcon
DeNA
 
ScalaからGoへ
ScalaからGoへScalaからGoへ
ScalaからGoへ
James Neve
 
Apache Spark Streaming + Kafka 0.10 with Joan Viladrosariera
Apache Spark Streaming + Kafka 0.10 with Joan ViladrosarieraApache Spark Streaming + Kafka 0.10 with Joan Viladrosariera
Apache Spark Streaming + Kafka 0.10 with Joan Viladrosariera
Spark Summit
 
What’s New in Amazon Aurora
What’s New in Amazon AuroraWhat’s New in Amazon Aurora
What’s New in Amazon Aurora
Amazon Web Services
 
Streaming Data Analytics with Amazon Redshift and Kinesis Firehose
Streaming Data Analytics with Amazon Redshift and Kinesis FirehoseStreaming Data Analytics with Amazon Redshift and Kinesis Firehose
Streaming Data Analytics with Amazon Redshift and Kinesis Firehose
Amazon Web Services
 
An introduction and future of Ruby coverage library
An introduction and future of Ruby coverage libraryAn introduction and future of Ruby coverage library
An introduction and future of Ruby coverage library
mametter
 
MongoDBの可能性の話
MongoDBの可能性の話MongoDBの可能性の話
MongoDBの可能性の話
Akihiro Kuwano
 
Blockchain on Go
Blockchain on GoBlockchain on Go
Blockchain on Go
Seiji Takahashi
 
Spiderストレージエンジンの使い方と利用事例 他ストレージエンジンの紹介
Spiderストレージエンジンの使い方と利用事例 他ストレージエンジンの紹介Spiderストレージエンジンの使い方と利用事例 他ストレージエンジンの紹介
Spiderストレージエンジンの使い方と利用事例 他ストレージエンジンの紹介
Kentoku
 
SLOのすすめ
SLOのすすめSLOのすすめ
SLOのすすめ
Takeo Sawada
 
Microservices at Mercari
Microservices at MercariMicroservices at Mercari
Microservices at Mercari
Google Cloud Platform - Japan
 
Fast and Reliable Swift APIs with gRPC
Fast and Reliable Swift APIs with gRPCFast and Reliable Swift APIs with gRPC
Fast and Reliable Swift APIs with gRPC
Tim Burks
 
Swaggerでのapi開発よもやま話
Swaggerでのapi開発よもやま話Swaggerでのapi開発よもやま話
Swaggerでのapi開発よもやま話
KEISUKE KONISHI
 
メルカリアッテの実務で使えた、GAE/Goの開発を効率的にする方法
メルカリアッテの実務で使えた、GAE/Goの開発を効率的にする方法メルカリアッテの実務で使えた、GAE/Goの開発を効率的にする方法
メルカリアッテの実務で使えた、GAE/Goの開発を効率的にする方法
Takuya Ueda
 
So You Wanna Go Fast?
So You Wanna Go Fast?So You Wanna Go Fast?
So You Wanna Go Fast?
Tyler Treat
 
Solving anything in VCL
Solving anything in VCLSolving anything in VCL
Solving anything in VCL
Fastly
 
Google Home and Google Assistant Workshop: Build your own serverless Action o...
Google Home and Google Assistant Workshop: Build your own serverless Action o...Google Home and Google Assistant Workshop: Build your own serverless Action o...
Google Home and Google Assistant Workshop: Build your own serverless Action o...
Bret McGowen - NYC Google Developer Advocate
 
golang.tokyo #6 (in Japanese)
golang.tokyo #6 (in Japanese)golang.tokyo #6 (in Japanese)
golang.tokyo #6 (in Japanese)
Yuichi Murata
 
Operations: Production Readiness Review – How to stop bad things from Happening
Operations: Production Readiness Review – How to stop bad things from HappeningOperations: Production Readiness Review – How to stop bad things from Happening
Operations: Production Readiness Review – How to stop bad things from Happening
Amazon Web Services
 
AWS X-Rayによるアプリケーションの分析とデバッグ
AWS X-Rayによるアプリケーションの分析とデバッグAWS X-Rayによるアプリケーションの分析とデバッグ
AWS X-Rayによるアプリケーションの分析とデバッグ
Amazon Web Services Japan
 
AndApp開発における全て #denatechcon
AndApp開発における全て #denatechconAndApp開発における全て #denatechcon
AndApp開発における全て #denatechcon
DeNA
 
ScalaからGoへ
ScalaからGoへScalaからGoへ
ScalaからGoへ
James Neve
 
Apache Spark Streaming + Kafka 0.10 with Joan Viladrosariera
Apache Spark Streaming + Kafka 0.10 with Joan ViladrosarieraApache Spark Streaming + Kafka 0.10 with Joan Viladrosariera
Apache Spark Streaming + Kafka 0.10 with Joan Viladrosariera
Spark Summit
 
Streaming Data Analytics with Amazon Redshift and Kinesis Firehose
Streaming Data Analytics with Amazon Redshift and Kinesis FirehoseStreaming Data Analytics with Amazon Redshift and Kinesis Firehose
Streaming Data Analytics with Amazon Redshift and Kinesis Firehose
Amazon Web Services
 
An introduction and future of Ruby coverage library
An introduction and future of Ruby coverage libraryAn introduction and future of Ruby coverage library
An introduction and future of Ruby coverage library
mametter
 
MongoDBの可能性の話
MongoDBの可能性の話MongoDBの可能性の話
MongoDBの可能性の話
Akihiro Kuwano
 
Spiderストレージエンジンの使い方と利用事例 他ストレージエンジンの紹介
Spiderストレージエンジンの使い方と利用事例 他ストレージエンジンの紹介Spiderストレージエンジンの使い方と利用事例 他ストレージエンジンの紹介
Spiderストレージエンジンの使い方と利用事例 他ストレージエンジンの紹介
Kentoku
 
Fast and Reliable Swift APIs with gRPC
Fast and Reliable Swift APIs with gRPCFast and Reliable Swift APIs with gRPC
Fast and Reliable Swift APIs with gRPC
Tim Burks
 
Swaggerでのapi開発よもやま話
Swaggerでのapi開発よもやま話Swaggerでのapi開発よもやま話
Swaggerでのapi開発よもやま話
KEISUKE KONISHI
 
メルカリアッテの実務で使えた、GAE/Goの開発を効率的にする方法
メルカリアッテの実務で使えた、GAE/Goの開発を効率的にする方法メルカリアッテの実務で使えた、GAE/Goの開発を効率的にする方法
メルカリアッテの実務で使えた、GAE/Goの開発を効率的にする方法
Takuya Ueda
 
So You Wanna Go Fast?
So You Wanna Go Fast?So You Wanna Go Fast?
So You Wanna Go Fast?
Tyler Treat
 
Solving anything in VCL
Solving anything in VCLSolving anything in VCL
Solving anything in VCL
Fastly
 

Similar to 神に近づくx/net/context (Finding God with x/net/context) (20)

Original slides from Ryan Dahl's NodeJs intro talk
Original slides from Ryan Dahl's NodeJs intro talkOriginal slides from Ryan Dahl's NodeJs intro talk
Original slides from Ryan Dahl's NodeJs intro talk
Aarti Parikh
 
Writing robust Node.js applications
Writing robust Node.js applicationsWriting robust Node.js applications
Writing robust Node.js applications
Tom Croucher
 
node.js: Javascript's in your backend
node.js: Javascript's in your backendnode.js: Javascript's in your backend
node.js: Javascript's in your backend
David Padbury
 
Fun Teaching MongoDB New Tricks
Fun Teaching MongoDB New TricksFun Teaching MongoDB New Tricks
Fun Teaching MongoDB New Tricks
MongoDB
 
Serverless Ballerina
Serverless BallerinaServerless Ballerina
Serverless Ballerina
Ballerina
 
Catalyst MVC
Catalyst MVCCatalyst MVC
Catalyst MVC
Sheeju Alex
 
Using and scaling Rack and Rack-based middleware
Using and scaling Rack and Rack-based middlewareUsing and scaling Rack and Rack-based middleware
Using and scaling Rack and Rack-based middleware
Alona Mekhovova
 
A language for the Internet: Why JavaScript and Node.js is right for Internet...
A language for the Internet: Why JavaScript and Node.js is right for Internet...A language for the Internet: Why JavaScript and Node.js is right for Internet...
A language for the Internet: Why JavaScript and Node.js is right for Internet...
Tom Croucher
 
NodeJS
NodeJSNodeJS
NodeJS
Alok Guha
 
Having Fun with Play
Having Fun with PlayHaving Fun with Play
Having Fun with Play
Clinton Dreisbach
 
Net/http and the http.handler interface
Net/http and the http.handler interfaceNet/http and the http.handler interface
Net/http and the http.handler interface
Joakim Gustin
 
Net/http and the http.handler interface
Net/http and the http.handler interfaceNet/http and the http.handler interface
Net/http and the http.handler interface
Evolve
 
A language for the Internet: Why JavaScript and Node.js is right for Internet...
A language for the Internet: Why JavaScript and Node.js is right for Internet...A language for the Internet: Why JavaScript and Node.js is right for Internet...
A language for the Internet: Why JavaScript and Node.js is right for Internet...
Tom Croucher
 
JDD 2017: Nginx + Lua = OpenResty (Marcin Stożek)
JDD 2017: Nginx + Lua = OpenResty (Marcin Stożek)JDD 2017: Nginx + Lua = OpenResty (Marcin Stożek)
JDD 2017: Nginx + Lua = OpenResty (Marcin Stożek)
PROIDEA
 
2019 11-bgphp
2019 11-bgphp2019 11-bgphp
2019 11-bgphp
dantleech
 
deepjs - tools for better programming
deepjs - tools for better programmingdeepjs - tools for better programming
deepjs - tools for better programming
nomocas
 
Tips
TipsTips
Tips
mclee
 
服务框架: Thrift & PasteScript
服务框架: Thrift & PasteScript服务框架: Thrift & PasteScript
服务框架: Thrift & PasteScript
Qiangning Hong
 
Future Decoded - Node.js per sviluppatori .NET
Future Decoded - Node.js per sviluppatori .NETFuture Decoded - Node.js per sviluppatori .NET
Future Decoded - Node.js per sviluppatori .NET
Gianluca Carucci
 
async/await in Swift
async/await in Swiftasync/await in Swift
async/await in Swift
Peter Friese
 
Original slides from Ryan Dahl's NodeJs intro talk
Original slides from Ryan Dahl's NodeJs intro talkOriginal slides from Ryan Dahl's NodeJs intro talk
Original slides from Ryan Dahl's NodeJs intro talk
Aarti Parikh
 
Writing robust Node.js applications
Writing robust Node.js applicationsWriting robust Node.js applications
Writing robust Node.js applications
Tom Croucher
 
node.js: Javascript's in your backend
node.js: Javascript's in your backendnode.js: Javascript's in your backend
node.js: Javascript's in your backend
David Padbury
 
Fun Teaching MongoDB New Tricks
Fun Teaching MongoDB New TricksFun Teaching MongoDB New Tricks
Fun Teaching MongoDB New Tricks
MongoDB
 
Serverless Ballerina
Serverless BallerinaServerless Ballerina
Serverless Ballerina
Ballerina
 
Using and scaling Rack and Rack-based middleware
Using and scaling Rack and Rack-based middlewareUsing and scaling Rack and Rack-based middleware
Using and scaling Rack and Rack-based middleware
Alona Mekhovova
 
A language for the Internet: Why JavaScript and Node.js is right for Internet...
A language for the Internet: Why JavaScript and Node.js is right for Internet...A language for the Internet: Why JavaScript and Node.js is right for Internet...
A language for the Internet: Why JavaScript and Node.js is right for Internet...
Tom Croucher
 
Net/http and the http.handler interface
Net/http and the http.handler interfaceNet/http and the http.handler interface
Net/http and the http.handler interface
Joakim Gustin
 
Net/http and the http.handler interface
Net/http and the http.handler interfaceNet/http and the http.handler interface
Net/http and the http.handler interface
Evolve
 
A language for the Internet: Why JavaScript and Node.js is right for Internet...
A language for the Internet: Why JavaScript and Node.js is right for Internet...A language for the Internet: Why JavaScript and Node.js is right for Internet...
A language for the Internet: Why JavaScript and Node.js is right for Internet...
Tom Croucher
 
JDD 2017: Nginx + Lua = OpenResty (Marcin Stożek)
JDD 2017: Nginx + Lua = OpenResty (Marcin Stożek)JDD 2017: Nginx + Lua = OpenResty (Marcin Stożek)
JDD 2017: Nginx + Lua = OpenResty (Marcin Stożek)
PROIDEA
 
2019 11-bgphp
2019 11-bgphp2019 11-bgphp
2019 11-bgphp
dantleech
 
deepjs - tools for better programming
deepjs - tools for better programmingdeepjs - tools for better programming
deepjs - tools for better programming
nomocas
 
Tips
TipsTips
Tips
mclee
 
服务框架: Thrift & PasteScript
服务框架: Thrift & PasteScript服务框架: Thrift & PasteScript
服务框架: Thrift & PasteScript
Qiangning Hong
 
Future Decoded - Node.js per sviluppatori .NET
Future Decoded - Node.js per sviluppatori .NETFuture Decoded - Node.js per sviluppatori .NET
Future Decoded - Node.js per sviluppatori .NET
Gianluca Carucci
 
async/await in Swift
async/await in Swiftasync/await in Swift
async/await in Swift
Peter Friese
 

Recently uploaded (20)

Q4_TLE-7-Lesson-6-Week-6.pptx 4th quarter
Q4_TLE-7-Lesson-6-Week-6.pptx 4th quarterQ4_TLE-7-Lesson-6-Week-6.pptx 4th quarter
Q4_TLE-7-Lesson-6-Week-6.pptx 4th quarter
MariaBarbaraPaglinaw
 
UiPath Agentic Automation Capabilities and Opportunities
UiPath Agentic Automation Capabilities and OpportunitiesUiPath Agentic Automation Capabilities and Opportunities
UiPath Agentic Automation Capabilities and Opportunities
DianaGray10
 
DealBook of Ukraine: 2025 edition | AVentures Capital
DealBook of Ukraine: 2025 edition | AVentures CapitalDealBook of Ukraine: 2025 edition | AVentures Capital
DealBook of Ukraine: 2025 edition | AVentures Capital
Yevgen Sysoyev
 
Build with AI on Google Cloud Session #4
Build with AI on Google Cloud Session #4Build with AI on Google Cloud Session #4
Build with AI on Google Cloud Session #4
Margaret Maynard-Reid
 
Unlocking DevOps Secuirty :Vault & Keylock
Unlocking DevOps Secuirty :Vault & KeylockUnlocking DevOps Secuirty :Vault & Keylock
Unlocking DevOps Secuirty :Vault & Keylock
HusseinMalikMammadli
 
Wondershare Filmora Crack 14.3.2.11147 Latest
Wondershare Filmora Crack 14.3.2.11147 LatestWondershare Filmora Crack 14.3.2.11147 Latest
Wondershare Filmora Crack 14.3.2.11147 Latest
udkg888
 
Revolutionizing-Government-Communication-The-OSWAN-Success-Story
Revolutionizing-Government-Communication-The-OSWAN-Success-StoryRevolutionizing-Government-Communication-The-OSWAN-Success-Story
Revolutionizing-Government-Communication-The-OSWAN-Success-Story
ssuser52ad5e
 
What Makes "Deep Research"? A Dive into AI Agents
What Makes "Deep Research"? A Dive into AI AgentsWhat Makes "Deep Research"? A Dive into AI Agents
What Makes "Deep Research"? A Dive into AI Agents
Zilliz
 
30B Images and Counting: Scaling Canva's Content-Understanding Pipelines by K...
30B Images and Counting: Scaling Canva's Content-Understanding Pipelines by K...30B Images and Counting: Scaling Canva's Content-Understanding Pipelines by K...
30B Images and Counting: Scaling Canva's Content-Understanding Pipelines by K...
ScyllaDB
 
How Discord Indexes Trillions of Messages: Scaling Search Infrastructure by V...
How Discord Indexes Trillions of Messages: Scaling Search Infrastructure by V...How Discord Indexes Trillions of Messages: Scaling Search Infrastructure by V...
How Discord Indexes Trillions of Messages: Scaling Search Infrastructure by V...
ScyllaDB
 
FinTech - US Annual Funding Report - 2024.pptx
FinTech - US Annual Funding Report - 2024.pptxFinTech - US Annual Funding Report - 2024.pptx
FinTech - US Annual Funding Report - 2024.pptx
Tracxn
 
Stronger Together: Combining Data Quality and Governance for Confident AI & A...
Stronger Together: Combining Data Quality and Governance for Confident AI & A...Stronger Together: Combining Data Quality and Governance for Confident AI & A...
Stronger Together: Combining Data Quality and Governance for Confident AI & A...
Precisely
 
L01 Introduction to Nanoindentation - What is hardness
L01 Introduction to Nanoindentation - What is hardnessL01 Introduction to Nanoindentation - What is hardness
L01 Introduction to Nanoindentation - What is hardness
RostislavDaniel
 
Endpoint Backup: 3 Reasons MSPs Ignore It
Endpoint Backup: 3 Reasons MSPs Ignore ItEndpoint Backup: 3 Reasons MSPs Ignore It
Endpoint Backup: 3 Reasons MSPs Ignore It
MSP360
 
Deno ...................................
Deno ...................................Deno ...................................
Deno ...................................
Robert MacLean
 
Wondershare Dr.Fone Crack Free Download 2025
Wondershare Dr.Fone Crack Free Download 2025Wondershare Dr.Fone Crack Free Download 2025
Wondershare Dr.Fone Crack Free Download 2025
maharajput103
 
Early Adopter's Guide to AI Moderation (Preview)
Early Adopter's Guide to AI Moderation (Preview)Early Adopter's Guide to AI Moderation (Preview)
Early Adopter's Guide to AI Moderation (Preview)
nick896721
 
MIND Revenue Release Quarter 4 2024 - Finacial Presentation
MIND Revenue Release Quarter 4 2024 - Finacial PresentationMIND Revenue Release Quarter 4 2024 - Finacial Presentation
MIND Revenue Release Quarter 4 2024 - Finacial Presentation
MIND CTI
 
[Webinar] Scaling Made Simple: Getting Started with No-Code Web Apps
[Webinar] Scaling Made Simple: Getting Started with No-Code Web Apps[Webinar] Scaling Made Simple: Getting Started with No-Code Web Apps
[Webinar] Scaling Made Simple: Getting Started with No-Code Web Apps
Safe Software
 
Future-Proof Your Career with AI Options
Future-Proof Your  Career with AI OptionsFuture-Proof Your  Career with AI Options
Future-Proof Your Career with AI Options
DianaGray10
 
Q4_TLE-7-Lesson-6-Week-6.pptx 4th quarter
Q4_TLE-7-Lesson-6-Week-6.pptx 4th quarterQ4_TLE-7-Lesson-6-Week-6.pptx 4th quarter
Q4_TLE-7-Lesson-6-Week-6.pptx 4th quarter
MariaBarbaraPaglinaw
 
UiPath Agentic Automation Capabilities and Opportunities
UiPath Agentic Automation Capabilities and OpportunitiesUiPath Agentic Automation Capabilities and Opportunities
UiPath Agentic Automation Capabilities and Opportunities
DianaGray10
 
DealBook of Ukraine: 2025 edition | AVentures Capital
DealBook of Ukraine: 2025 edition | AVentures CapitalDealBook of Ukraine: 2025 edition | AVentures Capital
DealBook of Ukraine: 2025 edition | AVentures Capital
Yevgen Sysoyev
 
Build with AI on Google Cloud Session #4
Build with AI on Google Cloud Session #4Build with AI on Google Cloud Session #4
Build with AI on Google Cloud Session #4
Margaret Maynard-Reid
 
Unlocking DevOps Secuirty :Vault & Keylock
Unlocking DevOps Secuirty :Vault & KeylockUnlocking DevOps Secuirty :Vault & Keylock
Unlocking DevOps Secuirty :Vault & Keylock
HusseinMalikMammadli
 
Wondershare Filmora Crack 14.3.2.11147 Latest
Wondershare Filmora Crack 14.3.2.11147 LatestWondershare Filmora Crack 14.3.2.11147 Latest
Wondershare Filmora Crack 14.3.2.11147 Latest
udkg888
 
Revolutionizing-Government-Communication-The-OSWAN-Success-Story
Revolutionizing-Government-Communication-The-OSWAN-Success-StoryRevolutionizing-Government-Communication-The-OSWAN-Success-Story
Revolutionizing-Government-Communication-The-OSWAN-Success-Story
ssuser52ad5e
 
What Makes "Deep Research"? A Dive into AI Agents
What Makes "Deep Research"? A Dive into AI AgentsWhat Makes "Deep Research"? A Dive into AI Agents
What Makes "Deep Research"? A Dive into AI Agents
Zilliz
 
30B Images and Counting: Scaling Canva's Content-Understanding Pipelines by K...
30B Images and Counting: Scaling Canva's Content-Understanding Pipelines by K...30B Images and Counting: Scaling Canva's Content-Understanding Pipelines by K...
30B Images and Counting: Scaling Canva's Content-Understanding Pipelines by K...
ScyllaDB
 
How Discord Indexes Trillions of Messages: Scaling Search Infrastructure by V...
How Discord Indexes Trillions of Messages: Scaling Search Infrastructure by V...How Discord Indexes Trillions of Messages: Scaling Search Infrastructure by V...
How Discord Indexes Trillions of Messages: Scaling Search Infrastructure by V...
ScyllaDB
 
FinTech - US Annual Funding Report - 2024.pptx
FinTech - US Annual Funding Report - 2024.pptxFinTech - US Annual Funding Report - 2024.pptx
FinTech - US Annual Funding Report - 2024.pptx
Tracxn
 
Stronger Together: Combining Data Quality and Governance for Confident AI & A...
Stronger Together: Combining Data Quality and Governance for Confident AI & A...Stronger Together: Combining Data Quality and Governance for Confident AI & A...
Stronger Together: Combining Data Quality and Governance for Confident AI & A...
Precisely
 
L01 Introduction to Nanoindentation - What is hardness
L01 Introduction to Nanoindentation - What is hardnessL01 Introduction to Nanoindentation - What is hardness
L01 Introduction to Nanoindentation - What is hardness
RostislavDaniel
 
Endpoint Backup: 3 Reasons MSPs Ignore It
Endpoint Backup: 3 Reasons MSPs Ignore ItEndpoint Backup: 3 Reasons MSPs Ignore It
Endpoint Backup: 3 Reasons MSPs Ignore It
MSP360
 
Deno ...................................
Deno ...................................Deno ...................................
Deno ...................................
Robert MacLean
 
Wondershare Dr.Fone Crack Free Download 2025
Wondershare Dr.Fone Crack Free Download 2025Wondershare Dr.Fone Crack Free Download 2025
Wondershare Dr.Fone Crack Free Download 2025
maharajput103
 
Early Adopter's Guide to AI Moderation (Preview)
Early Adopter's Guide to AI Moderation (Preview)Early Adopter's Guide to AI Moderation (Preview)
Early Adopter's Guide to AI Moderation (Preview)
nick896721
 
MIND Revenue Release Quarter 4 2024 - Finacial Presentation
MIND Revenue Release Quarter 4 2024 - Finacial PresentationMIND Revenue Release Quarter 4 2024 - Finacial Presentation
MIND Revenue Release Quarter 4 2024 - Finacial Presentation
MIND CTI
 
[Webinar] Scaling Made Simple: Getting Started with No-Code Web Apps
[Webinar] Scaling Made Simple: Getting Started with No-Code Web Apps[Webinar] Scaling Made Simple: Getting Started with No-Code Web Apps
[Webinar] Scaling Made Simple: Getting Started with No-Code Web Apps
Safe Software
 
Future-Proof Your Career with AI Options
Future-Proof Your  Career with AI OptionsFuture-Proof Your  Career with AI Options
Future-Proof Your Career with AI Options
DianaGray10
 

神に近づくx/net/context (Finding God with x/net/context)

  • 1. 神に近づくx/net/context Finding God with x/net/context 11 March 2015 Gregory Roseberry Gunosy
  • 2. ※ This talk has nothing to do with religion. ※ Go Gopher by Renee French
  • 3. Let's make an API server
  • 4. Attempt #1: Standard library Everyone told me to use the standard library, let's use it. Index page says hello Secret message page requires key func main() { http.HandleFunc("/", indexHandler) http.HandleFunc("/secret/message", requireKey(secretMessageHandler)) http.ListenAndServe(":8000", nil) } func indexHandler(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "hello world") }
  • 5. Secret message func secretMessageHandler(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "42") } Here's a way to write middleware with just the standard library: func requireKey(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.FormValue("key") != "12345" {if r.FormValue("key") != "12345" { http.Error(w, "bad key", http.StatusForbidden) return } h(w, r) } } In main.go: http.HandleFunc("/secret/message", requireKey(secretMessageHandler))
  • 6. There's been a change of plans... We were hard-coding the key, but your boss says now we need to check Redis. Let's just make our Redis connection a global variable for now... var redisDB *redis.Client func main() { redisDB = ... // set up redis } func requireKeyRedis(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { key := r.FormValue("key") userID, err := redisDB.Get("auth:" + key).Result()userID, err := redisDB.Get("auth:" + key).Result() if key == "" || err != nil {if key == "" || err != nil { http.Error(w, "bad key", http.StatusForbidden) return } log.Println("user", userID, "viewed message") h(w, r) } }
  • 7. Just one quick addition... We need to issue temporary session tokens for some use cases, so we need to check if either a key or a session is provided. func requireKeyOrSession(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { key := r.FormValue("key") // set key from db if we have a session if session := r.FormValue("session"); session != "" {if session := r.FormValue("session"); session != "" { var err error if key, err = redisDB.Get("session:" + session).Result(); err != nil {if key, err = redisDB.Get("session:" + session).Result(); err != nil { http.Error(w, "bad session", http.StatusForbidden) return } } userID, err := redisDB.Get("auth:" + key).Result() if key == "" || err != nil { http.Error(w, "bad key", http.StatusForbidden) return } log.Println("user", userID, "viewed message") h(w, r) } }
  • 8. By the way... Your boss also asks: Can we also check the X-API-Key header? Can we restrict certain keys to certain IP addresses? Can we ...? There's too much to shove into one middleware: so we make an auth package. package auth type Auth struct { Key string Session string UserID string } func Check(r *http.Request) (auth Auth, ok bool) { // lots of complicated checks }
  • 9. What about Redis? We need to reference the DB from our new auth package as well. Should we pass the connection to Check? func Check(redisDB *redis.Client, r *http.Request) (Auth, bool) { ... } What happens we need to check MySQL as well? func Check(redisDB *redis.Client, archiveDB *sql.DB, r *http.Request) (Auth, bool) { ... } Your boss says MongoDB is web scale, so that gets added too. func Check(redisDB *redis.Client, archiveDB *sql.DB, mongo *mgo.Session, r *http.Request) (Auth, bool) { ... This isn't going to work...
  • 10. How about an init method? Making a global here too? var redisDB *redis.Client func Init(r *redis.Client, ...) { redisDB = r } That doesn't solve our arguments problem. Let's shove them in a struct. package config type Context struct { RedisDB *redis.Client ArchiveDB *sql.DB ... } Init with this guy? auth.Init(appContext) Who inits who? What about tests?
  • 11. Just one more thing... Your boss says it's vital that we log every request now, and include the key and user ID if possible. It's easy to write logging middleware, but how can we make our logger aware of our Auth credentials?
  • 12. Session table Let's try making a global map of connections to auths. var authMap map[*http.Request]*auth.Auth Then populate it during our check. func requireKeyOrSession(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ... a, ok := auth.Check(dbContext, r) authMapMutex.Lock() authMap[r] = &a ... } } Should work, but will our *http.Requests leak? We need to make sure to clean them up. What happens when we need to keep track of more than just Auth? How do we coordinate this data across packages? What about concurrency? (This is kind of how gorilla/sessions works)
  • 13. There's got to be another way...
  • 14. Attempt #2: Goji Goji is a popular web micro-framework. Goji handlers take an extra parameter called web.C (probably short for Context). c.Env is a map[interface{}]interface{} for storing arbitrary data — perfect for our auth token! This used to be a map[string]interface{}, more on this later. Let's rewrite our auth middleware for Goji: func requiresKey(c *web.C, h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { a := c.Env["auth"] if a == nil { http.Error(w, "bad key", http.StatusForbidden) return } h.ServeHTTP(w, r) } return http.HandlerFunc(fn) }
  • 15. Goji groups We can set up groups of routes: package main import ( "github.com/zenazn/goji" "github.com/zenazn/goji/web" ) func main() { ... secretGroup := web.New() secretGroup.Use(requiresKey) secretGroup.Get("/secret/message", secretMessageHandler) goji.Handle("/secret/*", secretGroup) goji.Serve() } This will run our checkAuth for all routes under /secret/.
  • 16. Goji benefits Fast routing Middleware groups Request context Einhorn support (zero-downtime deploys)
  • 17. Downside: Goji-flavored context Let's say we want to re-use our auth package elsewhere, like a batch process. Do we want to put our database connections in web.C, even if we're not running a web server? Should all of our internal packages be importing Goji? package auth func Check(c web.C, session, key string) bool { // How do we call this if we're not using goji? redisDB, _ := c.Env["redis"].(*redis.Client) // kind of ugly... } Having to do a type assertion every time we use this DB is annoying. Also, what happens when some other library wants to use this "redis" key?
  • 18. Downside: Groups need to be set up once, in main.go Defining middleware for a group is tricky. What happens if you have code like... package addon func init() { goji.Get("/secret/addon", addonHandler) // will secretGroup handle this? } Everything works will if your entire app is set up in main.go, but in my experience it's very finicky and hard to reason about handlers that are set up in other ways.
  • 19. There's got to be another way...!
  • 20. Attempt #3: kami & x/net/context What is x/net/context? It's an almost-standard package for sharing context across your entire app. Includes facilities for setting deadlines and cancelling requests. Includes a way to store data similar to Goji's web.C. Immutable, must be replaced to update Check out this official blog post, which focuses mostly on x/net/context for cancellation: blog.golang.org/context(https://blog.golang.org/context) Quick example: ctx := context.Background() // blank context ctx = context.WithValue(ctx, "my_key", "my_value") fmt.Println(ctx.Value("my_key").(string)) // "my_value"
  • 21. kami kami is a mix of HttpRouter, x/net/context, and Goji, with a very simple middleware system included. package main import ( "fmt" "net/http" "github.com/guregu/kami" "golang.org/x/net/context" ) func hello(ctx context.Context, w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %s!", kami.Param(ctx, "name")) } func main() { kami.Get("/hello/:name", hello) kami.Serve() }
  • 22. Example: sharing DB connections import "github.com/guregu/db" I made a simple package for storing DB connections in your context. At Gunosy, we use something similar. db.OpenSQL() returns a new context containing a named SQL connection. func main() { ctx := context.Background() mysqlURL := "root:hunter2@unix(/tmp/mysql.sock)/myCoolDB" ctx = db.OpenSQL(ctx, "main", "mysql", mysqlURL)ctx = db.OpenSQL(ctx, "main", "mysql", mysqlURL) defer db.Close(ctx) // closes all DB connectionsdefer db.Close(ctx) // closes all DB connections kami.Context = ctxkami.Context = ctx kami.Get("/hello/:name", hello) kami.Serve() } kami.Context is our "god context" from which all request contexts are derived.
  • 23. Example: sharing DB connections (2) Within a request, we use db.SQL(ctx, name) to retrieve the connection. func hello(ctx context.Context, w http.ResponseWriter, r *http.Request) { mainDB := db.SQL(ctx, "main") // *sql.DBmainDB := db.SQL(ctx, "main") // *sql.DB var greeting string mainDB.QueryRow("SELECT content FROM greetings WHERE name = ?", kami.Param(ctx, "name")). Scan(&greeting) fmt.Fprintf(w, "Hello, %s!", greeting) }
  • 24. Tests For tests, you can put a mock DB connection in your context. main_test.go: import _ "github.com/mycompany/testhelper" testhelper/testhelper.go: import ( "github.com/guregu/db" "github.com/guregu/kami" _ "github.com/guregu/mogi" ) func init() { ctx := context.Background() // use mogi for tests ctx = db.OpenSQL("main", "mogi", "") kami.Context = ctx }
  • 25. How does it work? Because context.Value() takes an interface{}, we can use unexported type as the key to "protect" it. This way, other packages can't screw with your data. In order to interact with a database, you have to use the exported functions like OpenSQL, and Close. package db import ( "database/sql" "golang.org/x/net/context" ) type sqlkey string // lowercase! // SQL retrieves the *sql.DB with the given name or nil. func SQL(ctx context.Context, name string) *sql.DB { db, _ := ctx.Value(sqlkey(name)).(*sql.DB) return db } BTW: This is why Goji switched its web.C from a map[string]interface{} to map[interface{}]interface{}.
  • 26. Middleware kami has no concept of middleware "groups". Middleware is strictly hierarchical. For example, a request for /secret/message would run the middleware registered under the following paths in order: / /secret/ /secret/message This means that you can define your paths anywhere and still get predictable middleware behavior. kami.Use("/secret/", requireKey)
  • 27. Middleware (2) kami.Middleware is defined as: type Middleware func(context.Context, http.ResponseWriter, *http.Request) context.Context The context you return will be used for the next middleware or handler. Unlike Goji, you don't have control of how the next handler will be called. But, you can return nil to halt the execution chain.
  • 28. Middleware (3) import "github.com/mycompany/auth" func init() { kami.Use("/", doAuth) kami.Use("/secret/", requiresKey) } // doAuth returns a new context with the appropiate auth object inside func doAuth(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context { if a, err := auth.ByKey(ctx, r.FormValue("key")); err == nil { // put auth object in context ctx = auth.NewContext(ctx, a) } return ctx } // requiresKey stops the request if we don't have an auth object func requiresKey(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context { if _, ok := auth.FromContext(ctx); !ok { http.Error(w, "bad key", http.StatusForbidden) return nil // stop request } return ctx }
  • 29. Hooks kami provides special hooks for logging and recovering from panics, kami.LogHandler and kami.PanicHandler. Handling panics. kami.PanicHandler = func(ctx context.Context, w http.ResponseWriter, r *http.Request) { err := kami.Exception(ctx) a, _ := auth.FromContext(ctx) log.Println("panic", err, a) } Logging request statuses. Notice how the function signature is different, it takes a writer proxy that includes the status code. kami.LogHandler = func(ctx context.Context, w mutil.WriterProxy, r *http.Request) { a, _ := auth.FromContext(ctx) log.Println("access", w.Status(), r.URL.Path, "from:", a.Key, a.UserID) } LogHandler will run after PanicHandler, unless LogHandler is the one panicking.
  • 30. Graceful This is the "Goji" part of kami. Literally copy and pasted from Goji. kami.Serve() // works *exactly* like goji.Serve() Supports Einhorn for graceful restarts. Thank you, Goji.
  • 31. Downsides kami isn't perfect. It is rather inflexible and may not fit your needs. You can't define separate groups of middleware, or separate groups of handlers, everything is global. You could mount kami.Handler() outside of "/" and use another router... You can't register middleware under wildcard paths: kami.Use("/user/:id/profile", middleware) won't work. Register it under /user/ and do your best. I will probably fix these issues eventually. Might have to fork HttpRouter... Pull requests are always welcome.
  • 32. Production ready! We use kami to power the Gunosy API and it works just fine! Switching to x/net/context eliminates nearly all global variables. No more somepkg.Init() madness. Easy to test: just put mocks inside your context. Check it out!