SlideShare a Scribd company logo
1 of 33
Download to read offline
神に近づく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

CouchDB on Android
CouchDB on AndroidCouchDB on Android
CouchDB on AndroidSven Haiges
 
BlockChain implementation by python
BlockChain implementation by pythonBlockChain implementation by python
BlockChain implementation by pythonwonyong 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 scalabilityWim Godden
 
Redis for your boss 2.0
Redis for your boss 2.0Redis for your boss 2.0
Redis for your boss 2.0Elena Kolevska
 
REST with Eve and Python
REST with Eve and PythonREST with Eve and Python
REST with Eve and PythonPiXeL16
 
Socket programming with php
Socket programming with phpSocket programming with php
Socket programming with phpElizabeth Smith
 
My app is secure... I think
My app is secure... I thinkMy app is secure... I think
My app is secure... I thinkWim 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 CakePHPMariano Iglesias
 
Redis for the Everyday Developer
Redis for the Everyday DeveloperRedis for the Everyday Developer
Redis for the Everyday DeveloperRoss 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 codeWim 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 DatabaseMike Dirolf
 

What's hot (20)

CouchDB on Android
CouchDB on AndroidCouchDB on Android
CouchDB on Android
 
Rest in flask
Rest in flaskRest in flask
Rest in flask
 
Pycon - Python for ethical hackers
Pycon - Python for ethical hackers Pycon - Python for ethical hackers
Pycon - Python for ethical hackers
 
BlockChain implementation by python
BlockChain implementation by pythonBlockChain implementation by python
BlockChain implementation by python
 
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
 
Redis for your boss
Redis for your bossRedis for your boss
Redis for your boss
 
Redis for your boss 2.0
Redis for your boss 2.0Redis for your boss 2.0
Redis for your boss 2.0
 
REST with Eve and Python
REST with Eve and PythonREST with Eve and Python
REST with Eve and Python
 
Socket programming with php
Socket programming with phpSocket programming with php
Socket programming with php
 
Mongo db for c# developers
Mongo db for c# developersMongo db for c# developers
Mongo db for c# developers
 
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
 
OWASP Proxy
OWASP ProxyOWASP Proxy
OWASP Proxy
 
My app is secure... I think
My app is secure... I thinkMy app is secure... I think
My app is secure... I think
 
Mongo db for C# Developers
Mongo db for C# DevelopersMongo db for C# Developers
Mongo db for C# Developers
 
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.
 
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
 
Redis for the Everyday Developer
Redis for the Everyday DeveloperRedis for the Everyday Developer
Redis for the Everyday Developer
 
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
 
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
 
Ruby gems
Ruby gemsRuby gems
Ruby gems
 

Viewers also liked

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 HappeningAmazon Web Services
 
AWS X-Rayによるアプリケーションの分析とデバッグ
AWS X-Rayによるアプリケーションの分析とデバッグAWS X-Rayによるアプリケーションの分析とデバッグ
AWS X-Rayによるアプリケーションの分析とデバッグAmazon Web Services Japan
 
AndApp開発における全て #denatechcon
AndApp開発における全て #denatechconAndApp開発における全て #denatechcon
AndApp開発における全て #denatechconDeNA
 
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 ViladrosarieraSpark 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 FirehoseAmazon 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 librarymametter
 
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 gRPCTim 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 VCLFastly
 

Viewers also liked (20)

golang.tokyo #6 (in Japanese)
golang.tokyo #6 (in Japanese)golang.tokyo #6 (in Japanese)
golang.tokyo #6 (in Japanese)
 
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
 
AWS X-Rayによるアプリケーションの分析とデバッグ
AWS X-Rayによるアプリケーションの分析とデバッグAWS X-Rayによるアプリケーションの分析とデバッグ
AWS X-Rayによるアプリケーションの分析とデバッグ
 
AndApp開発における全て #denatechcon
AndApp開発における全て #denatechconAndApp開発における全て #denatechcon
AndApp開発における全て #denatechcon
 
ScalaからGoへ
ScalaからGoへScalaからGoへ
ScalaからGoへ
 
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
 
What’s New in Amazon Aurora
What’s New in Amazon AuroraWhat’s New in Amazon Aurora
What’s New in Amazon Aurora
 
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
 
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
 
MongoDBの可能性の話
MongoDBの可能性の話MongoDBの可能性の話
MongoDBの可能性の話
 
Blockchain on Go
Blockchain on GoBlockchain on Go
Blockchain on Go
 
Spiderストレージエンジンの使い方と利用事例 他ストレージエンジンの紹介
Spiderストレージエンジンの使い方と利用事例 他ストレージエンジンの紹介Spiderストレージエンジンの使い方と利用事例 他ストレージエンジンの紹介
Spiderストレージエンジンの使い方と利用事例 他ストレージエンジンの紹介
 
SLOのすすめ
SLOのすすめSLOのすすめ
SLOのすすめ
 
Microservices at Mercari
Microservices at MercariMicroservices at Mercari
Microservices at Mercari
 
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
 
Swaggerでのapi開発よもやま話
Swaggerでのapi開発よもやま話Swaggerでのapi開発よもやま話
Swaggerでのapi開発よもやま話
 
メルカリアッテの実務で使えた、GAE/Goの開発を効率的にする方法
メルカリアッテの実務で使えた、GAE/Goの開発を効率的にする方法メルカリアッテの実務で使えた、GAE/Goの開発を効率的にする方法
メルカリアッテの実務で使えた、GAE/Goの開発を効率的にする方法
 
So You Wanna Go Fast?
So You Wanna Go Fast?So You Wanna Go Fast?
So You Wanna Go Fast?
 
Solving anything in VCL
Solving anything in VCLSolving anything in VCL
Solving anything in VCL
 
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...
 

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

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 talkAarti Parikh
 
Writing robust Node.js applications
Writing robust Node.js applicationsWriting robust Node.js applications
Writing robust Node.js applicationsTom 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 backendDavid Padbury
 
Fun Teaching MongoDB New Tricks
Fun Teaching MongoDB New TricksFun Teaching MongoDB New Tricks
Fun Teaching MongoDB New TricksMongoDB
 
Serverless Ballerina
Serverless BallerinaServerless Ballerina
Serverless BallerinaBallerina
 
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 middlewareAlona 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 interfaceJoakim 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 interfaceEvolve
 
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-bgphpdantleech
 
deepjs - tools for better programming
deepjs - tools for better programmingdeepjs - tools for better programming
deepjs - tools for better programmingnomocas
 
Tips
TipsTips
Tipsmclee
 
服务框架: Thrift & PasteScript
服务框架: Thrift & PasteScript服务框架: Thrift & PasteScript
服务框架: Thrift & PasteScriptQiangning 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 .NETGianluca Carucci
 
async/await in Swift
async/await in Swiftasync/await in Swift
async/await in SwiftPeter Friese
 

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
 
Writing robust Node.js applications
Writing robust Node.js applicationsWriting robust Node.js applications
Writing robust Node.js applications
 
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
 
Fun Teaching MongoDB New Tricks
Fun Teaching MongoDB New TricksFun Teaching MongoDB New Tricks
Fun Teaching MongoDB New Tricks
 
Serverless Ballerina
Serverless BallerinaServerless Ballerina
Serverless Ballerina
 
Catalyst MVC
Catalyst MVCCatalyst MVC
Catalyst MVC
 
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
 
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...
 
NodeJS
NodeJSNodeJS
NodeJS
 
Having Fun with Play
Having Fun with PlayHaving Fun with Play
Having Fun with Play
 
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
 
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
 
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...
 
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)
 
2019 11-bgphp
2019 11-bgphp2019 11-bgphp
2019 11-bgphp
 
deepjs - tools for better programming
deepjs - tools for better programmingdeepjs - tools for better programming
deepjs - tools for better programming
 
Tips
TipsTips
Tips
 
服务框架: Thrift & PasteScript
服务框架: Thrift & PasteScript服务框架: Thrift & PasteScript
服务框架: Thrift & PasteScript
 
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
 
async/await in Swift
async/await in Swiftasync/await in Swift
async/await in Swift
 

Recently uploaded

Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Commit University
 
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxUse of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxLoriGlavin3
 
Time Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsTime Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsNathaniel Shimoni
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESSALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESmohitsingh558521
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxLoriGlavin3
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
Training state-of-the-art general text embedding
Training state-of-the-art general text embeddingTraining state-of-the-art general text embedding
Training state-of-the-art general text embeddingZilliz
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr BaganFwdays
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek SchlawackFwdays
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxLoriGlavin3
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024Lorenzo Miniero
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxLoriGlavin3
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Manik S Magar
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenHervé Boutemy
 
Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rick Flair
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii SoldatenkoFwdays
 

Recently uploaded (20)

Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxUse of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
 
Time Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsTime Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directions
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESSALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
Training state-of-the-art general text embedding
Training state-of-the-art general text embeddingTraining state-of-the-art general text embedding
Training state-of-the-art general text embedding
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache Maven
 
Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko
 

神に近づく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!