Goの孊習ネット/コンテキスト

Goを䜿甚する䞻なニッチがネットワヌクサヌビスであるこずは呚知の事実です。あらゆる皮類のサヌバヌ、バック゚ンド、マむクロサヌビス、分散デヌタベヌス、ファむルストレヌゞです。 このクラスのプログラムは、ネットワヌクク゚リを非垞に積極的に䜿甚したす。必芁な機胜はすべお暙準ラむブラリにありたすが、ネットワヌクアヌキテクチャの開発の1぀の偎面は、倚くのク゚リコンテキストのダヌクスポットのたたです。 この蚘事では、この偎面を詳しく芋お、このツヌルがいかに匷力で重芁であるかを瀺したいず思いたす。







コンテキストずは䜕ですか



䞻な質問から始めたしょう-これはどのような抂念、「コンテキスト」ですか このコンテキストは、トヌトロゞヌに申し蚳ありたせんが、コンテキストはAPI関数の境界間で送信されるオブゞェクトに関する情報です。 通垞、オブゞェクトはネットワヌクリク゚ストを意味し、APIの境界は異なるミドルりェア、異なるパッケヌゞ、抜象化の局を意味したすが、「コンテキスト」の抂念はネットワヌクリク゚ストだけに固有のものではありたせん。 ただし、この蚘事では䞻にそれらに぀いお説明したす。



兞型的な䟋は、JWTトヌクンからナヌザヌ情報を抜出し、この情報をハンドラヌに枡しお、アクセスの確認、ログぞの曞き蟌みなどを行うこずです。



このコンテキストの抂念は、少なくずもネットワヌクク゚リでは、倚くの蚀語ずフレヌムワヌクで利甚可胜です-CではHttpContext 、Java Netty では-ChannelHandlerContext 、Python Twisted では-twisted.python.contextなど。



コンテキストを移動



Go暙準ラむブラリには優れたHTTPスタックがあり、 「10K接続の問題」を恐れるこずなくマルチスレッドサヌバヌをすばやく䜜成し、 Handlerむンタヌフェむスを䜿甚しおさたざたなリク゚スト凊理スクリプトを簡単に実装できたす。 ただし、暙準ラむブラリにはhttpハンドラヌのコンテキストはありたせん。



しかし、Goは、メむンの暙準ラむブラリに加えお、Goの著者から、メむンのGoコヌドの倖郚で開発され、暙準ラむブラリのように䞋䜍互換性のハヌド玄束を持たないパッケヌゞの別個のグルヌプを持っおいたす。 これらはすべおgolang / x /にあるパッケヌゞです。 その䞭でも、長い間、コンテキストの本質を実装するネット/コンテキストパッケヌゞがあり、これに぀いおは本日お話ししたす。 執筆時点では、 GoDocによるず 、このパッケヌゞは1560パッケヌゞですでに䜿甚されおいたす。



䞀郚には、暙準ラむブラリにコンテキストがないため、Webフレヌムワヌクの動物園党䜓が出珟し、それぞれが独自の方法でリク゚ストコンテキストを送信する問題を解決したした。



Zooフレヌムワヌク



「zoo」ずいう蚀葉を䜿うず、特に問題はないので、少し誇匵したす。 暙準のnet / httpは基盀ずしおは良いですが、䜕らかのWebサヌビスの蚘述を開始するずすぐに、より高床な機胜グルヌプ化、高床なロギング、承認凊理、アクセス制埡などの耇雑なルヌティングを実装する必芁が生じたす、そしおこれは圓然䞀皮のフレヌムワヌクに倉わりたす-倚くの人はきっず同じ機胜を必芁ずし、同じアプロヌチが圌らの奜みになりたす。 しかし、党䜓ずしお、通垞、最も人気のあるフレヌムワヌクがいく぀かあり、それらは時々倉曎され、れロメモリの割り圓おず速床で競合したす 。 今、人気のダシは、 ゞンゎニックのようなものです 。



各フレヌムワヌクは、独自の方法でコンテキストを枡す問題に取り組みたした。 GorillaToolkitは、各芁求の倀のグロヌバルマップを保持し、mutexで芁求を保護したす。 Gojiずその他のナヌザヌは、リク゚ストごずに個別のマップを保持しおいたす。 gocraft / webは、リフレクションを通じおコン​​テキストを凊理したす。 前述のGin- Contextは、䞀般的にすべおのハンドラヌが機胜するキヌ構造です。 ゚コヌでは、コンテキストの抂念は䞀般に、リク゚スト凊理のすべおの機胜にかかっおいたす。



これらのアプロヌチにはそれぞれ長所ず短所がありたすが、すべおにマむナスの1぀がありたす-フレヌムワヌクぞのバむンドです。 䞊で述べたように、コンテキストはかなり抜象的な抂念であり、http-requestに限定されたせん。 これを䟋で芋おみたしょう。



䟋



簡単なWebサヌビスから始めたしょう。



package main import ( "net/http" ) func handler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello World")) } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":1234", nil) }
      
      





 $ curl http://localhost:1234 Hello World
      
      







しかし、ここでは、PINコヌドによるプリミティブな承認が必芁でした。http.HandlerFuncを䜿甚しお簡単なミドルりェアを䜜成したす。



 func needPin(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.FormValue("pin") != "9999" { http.Error(w, "wrong pin", http.StatusForbidden) return } h(w, r) } } ... http.HandleFunc("/", needPin(handler)) ...
      
      





 $ curl http://localhost:1234 wrong pin $ curl http://localhost:1234?pin=9999 Hello World
      
      





すばらしいですが、ハヌドコヌドではなく、SQLデヌタベヌスからピンを読み取る必芁がありたす。 OK、今のずころ、* sql.DBグロヌバル倉数を远加したす。



 func needPin(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var pin string if err := db.QueryRow("SELECT pin FROM pins").Scan(&pin); err != nil { http.Error(w, "database error", http.StatusInternalServerError) return } ...
      
      





コヌド党䜓
 $ sqlite3 pins.db SQLite version 3.8.4.3 2014-04-03 16:53:12 Enter ".help" for usage hints. sqlite> CREATE TABLE pins(pin STRING); sqlite> INSERT INTO pins(pin) VALUES ("9999"); sqlite> ^D
      
      





 package main import ( "database/sql" _ "github.com/mattn/go-sqlite3" "net/http" ) var ( db *sql.DB err error ) func handler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello World")) } func needPin(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var pin string if err := db.QueryRow("SELECT pin FROM pins").Scan(&pin); err != nil { http.Error(w, "database error", http.StatusInternalServerError) return } if r.FormValue("pin") != pin { http.Error(w, "wrong pin", http.StatusForbidden) return } h(w, r) } } func main() { if db, err = sql.Open("sqlite3", "./pins.db"); err != nil { panic(err) } defer db.Close() http.HandleFunc("/", needPin(handler)) http.ListenAndServe(":1234", nil) }
      
      







そしお、私たちの芁件ず欲望のリストが倧きくなり始めたす。





「もちろんできたす」新しいタスクごずにミドルりェアを考えお蚘述し、それらをチェヌンで組み立お始めたす。 Aliceなど、このための個別のパッケヌゞもありたす。 しかし、authMiddlewareからlogMiddlewareに認蚌゚ラヌを枡す方法は たた、䞀時的なパスワヌドのためにVaultで実行する必芁がある堎合、最初にセッションからトヌクンを抜出したすか しかし、グロヌバル倉数を取り陀き、デヌタベヌス接続オブゞェクトをハンドラヌに枡す方法は



ここで、コンテキストの抂念が圹立ちたす。 各ミドルりェア関数は、その倀をコンテキスト倉数に蚭定し、リク゚ストずコンテキストをさらに枡したす。 蚱可ステヌタス、ナヌザヌ名、アクセス蚱可、初期リク゚ストのヘッダヌから取埗した情報、デヌタベヌス接続ぞのポむンタヌ、さらに远跡するための䞀意のIDなど、このコンテキストに自由に入力できたす。



通垞、context.Contextはパラメヌタヌずしお関数に枡されたすミドルりェアず芁求で機胜する関数の䞡方。 原則ずしお、コンテキストを構造䜓フィヌルドずしお保存できたすが、コンテキストはリク゚ストに結び付けられる必芁があるため、これは慎重に行う必芁がありたす。リク゚ストごずに䜜成され、リク゚ストの凊理埌に削陀されたす。



しかし、 ネット/コンテキストは倀を保存するだけでなく、タむムアりトを管理しおリク゚ストをキャンセルするための統䞀されたアプロヌチでもありたす。 もっず詳しく芋おみたしょう。



内偎からのネット/コンテキスト



ネット/コンテキストAPIは少し珍しいので、最初は驚くかもしれたせん。



ここでは、Goで耇雑なリク゚スト凊理パむプラむンが通垞どのように䜜成されるかを理解するこずも重芁です。これは通垞、1぀のリク゚ストを受け入れるゎルヌチンで、このリク゚ストたたはリク゚ストのストリヌムを凊理する1぀以䞊のゎルヌチンを生成し、結果をトップに返したす。 これは、関数の単玔な同期呌び出しずしお、堎合によっおは個別のパッケヌゞに間隔を空けるか、チャネルを送信する新しいゎルヌチンずチャネルのカスケヌド党䜓ずしお実行できたす。 䞻なものは、各ミドルりェア、各パッケヌゞの責任の明確な境界があり、それぞれが芁求に぀いお䜕かを知りたい、そしおそれに぀いお䜕かをしたいずいうこずです。



したがっお、ネット/コンテキストの最初の䞻芁な原則は、 コンテキストがネストされ 、ツリヌ構造を持っおいるずいうこずです。 実際、net / contextパッケヌゞの䞻な機胜はそれを行いたす。 既存のコンテキストから新しいコンテキストのバリ゚ヌションを䜜成し、新しい「サブコンテキスト」を生成したす。



 func Background() Context func TODO() Context func WithCancel(parent Context) (ctx Context, cancel CancelFunc) func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) func WithValue(parent Context, key interface{}, val interface{}) Context
      
      





2番目の重芁な点は、context.Contextがむンタヌフェヌスであり、net / contextパッケヌゞが提䟛するバリ゚ヌションはわずかであるが、耇雑なコンテキストを自由に䜜成できるこずです。



 type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
      
      





既存のコンテキストの䞻なタむプを芋おみたしょう。





context.WithValueに぀いおもう少し



䞊蚘のフレヌムワヌクずは異なり、ネット/コンテキストは、ツリヌベヌスのネストされたアヌキテクチャのため、マップたたは同様のデヌタ構造を䜿甚したせん。 1぀のコンテキストには、1぀の倀ず芪コンテキストが含たれたす。 新しい意味-これはすでに新しいコンテキストです。 キヌず意味自䜓の䞡方は、どのタむプでもかたいたせん。



これを䜿甚するための暙準的なメカニズムは次のずおりです。キヌは、コンテキストで動䜜できる他のパッケヌゞ/ APIずの衝突を避けるために、゚クスポヌト䞍可胜なタむプでなければなりたせん。 䟋



 package userIP // ,       type key int //   IP .         - ,    . const userIPKey key = 0
      
      





倀自䜓のコンテキストは次のようになりたすhttps://github.com/golang/net/blob/master/context/context.go#L433



 type valueCtx struct { Context key, val interface{} } //   Value   func (c *valueCtx) Value(key interface{}) interface{} { if c.key == key { return c.val } return c.Context.Value(key) }
      
      





ご芧のずおり、任意のコンテキストの入れ子を䜿甚しお、Valueは、目的の型の目的の倀が芋぀かるたでコンテキストツリヌを䞊に移動したす。 たあ、たたは、nilを返すのは、Valueがすべおのコンテキストに察しお定矩されおいるためです芚えおいるように、Contextはむンタヌフェむスであり、したがっお、Backgroundコンテキストに察しおも定矩されおいたす。



コンテキスト倀で機胜するサンプルパッケヌゞの完党なコヌドは、次のようになりたす。



完党なコヌド
 // Package userip provides functions for extracting a user IP address from a // request and associating it with a Context. package userip import ( "fmt" "net" "net/http" "golang.org/x/net/context" ) // FromRequest extracts the user IP address from req, if present. func FromRequest(req *http.Request) (net.IP, error) { ip, _, err := net.SplitHostPort(req.RemoteAddr) if err != nil { return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr) } userIP := net.ParseIP(ip) if userIP == nil { return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr) } return userIP, nil } // The key type is unexported to prevent collisions with context keys defined in // other packages. type key int // userIPkey is the context key for the user IP address. Its value of zero is // arbitrary. If this package defined other context keys, they would have // different integer values. const userIPKey key = 0 // NewContext returns a new Context carrying userIP. func NewContext(ctx context.Context, userIP net.IP) context.Context { return context.WithValue(ctx, userIPKey, userIP) } // FromContext extracts the user IP address from ctx, if present. func FromContext(ctx context.Context) (net.IP, bool) { // ctx.Value returns nil if ctx has no value for the key; // the net.IP type assertion returns ok=false for nil. userIP, ok := ctx.Value(userIPKey).(net.IP) return userIP, ok }
      
      







䜕をどのようにコンテキストに入れるかは、特定のタスクに䟝存したす。 単玔なナヌザヌID、内郚に倚くの情報を含む耇雑な構造、たたはデヌタベヌスを操䜜するためのsql.DBのようなオブゞェクトのいずれかです。



パン



コンテキストをナニバヌサルむンタヌフェむスずしお実装するず、他のフレヌムワヌクのコンテキストを䜿甚するコヌドがnet / contextを䜿甚するコヌドずフレンドになりたす。



gorilla / contextを䜿甚したコンテキストの䟋 blog.golang.org/context/gorilla/gorilla.go



そしお、これが別のフレヌムワヌクであるtombでリク゚ストをキャンセルする䟋の䟋です blog.golang.org/context/tomb/tomb.go



たたは、Dapperスタむルでリク゚ストの寿呜を远跡するためにコンテキストを䜿甚する䟋ずしお、 net / traceパッケヌゞ。 芪コンテキストはcontext.WithValuectx、traceを生成し、リク゚ストの凊理䞭に生成されるすべおの埌続の呌び出しずコンテキストにはトレヌスIDが含たれ、既にネット/トレヌスコヌドにはWebペヌゞのトレヌスに関する情報を提䟛する必芁なハンドラヌが含たれおいたすパス/ debug / requestsおよび/ debug / events 。



明らかに、最倧の利点は、倖郚リ゜ヌスず通信するか、新しいゎルヌチンを生成するすべおのコヌドがネット/コンテキストを䜿甚する堎合です。 たずえば、GoのGoogleコヌドベヌスは既に玄1,000䞇行のコヌドを持っおいたすが、どこでもcontext.Contextを䜿甚しおいたす。 Protobuf3の新しいgPC RPCフレヌムワヌクは、Goのコヌドを生成するずきに、どこでもcontext.Contextを枡したす。



今埌の蚈画



ネット/コンテキストの将来の蚈画に぀いお掻発な議論があり、Go 1.7の暙準ラむブラリにコンテキストが衚瀺される可胜性がありたす。 おそらく小さな倉曎があり、おそらくないかもしれたせんが、いずれにせよ、興味ず欲求があるので、あなたは指を脈打぀べきです。



暙準ラむブラリはそれ自䜓で䞋䜍互換性があり、コヌドベヌスを断片化しないようにhttpずctxhttpに分割するようなこずはありたせん ctxhttpパッケヌゞは珟圚実隓ずしお存圚しおいたす。 Contextフィヌルドがhttp.Requestに远加されるか、他のオプションが衚瀺される可胜性がありたす。



net / contextからのnetずいう単語は消える可胜性がありたす。



参照資料



より詳现に理解し、䟋を参照したい堎合は、次のリンクを匷くお勧めしたす。



Good Goブログコンテキスト蚘事-blog.golang.org/context

GothamGo 2014コンテキストに関するトヌク-vimeo.com/115309491

http.Handlerずnet / contextに関する別の蚘事はjoeshaw.org/net-context-and-http-handlerです

トピックネット/コンテキストに関する優れたレポヌトの詳现なスラむド-go-talks.appspot.com/github.com/guregu/slides/kami/kami.slide#1

Goの高床なパむプラむンずキャンセルに関する蚘事-blog.golang.org/pipelines

さたざたなフレヌムワヌクでのコンテキスト実装の抂芁-www.nicolasmerouze.com/share-values-between-middlewares-context-golang

゜ヌスコヌドネット/コンテキスト-github.com/golang/net/tree/master/context



All Articles