{"id":326830,"date":"2022-01-10T08:39:12","date_gmt":"2022-01-10T08:39:12","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=326830"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=326830","title":{"rendered":"<span>React Apollo, Gqlgen \u2013 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f. \u0427\u0430\u0441\u0442\u044c 2<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\" class=\"article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<blockquote>\n<p>\u0421\u0442\u0430\u0442\u044c\u044f \u0440\u0430\u0437\u0431\u0438\u0442\u0430 \u043d\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0447\u0430\u0441\u0442\u0435\u0439:<br \/>&#8212; <a href=\"https:\/\/habr.com\/ru\/post\/598359\/\" rel=\"noopener noreferrer nofollow\">\u0447\u0430\u0441\u0442\u044c 1<\/a><br \/>&#8212; \u0447\u0430\u0441\u0442\u044c 3 &#8212; \u0432 \u0440\u0430\u0431\u043e\u0442\u0435<br \/>&#8212;\u00a0<a href=\"https:\/\/github.com\/zhivulinal\/react-apollo-gqlgen-tutorial\/tree\/8330d4ad8c6f428e777aada3b047bebd7abf1a2c\" rel=\"noopener noreferrer nofollow\">\u0438\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0438 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438<\/a><\/p>\n<\/blockquote>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" height=\"629\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6f5\/da1\/5c9\/6f5da15c9824a44ffe3a6b267f5bfd4c.png\" data-width=\"1400\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0412 \u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043c\u044b \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u043c\u0435\u0442\u043e\u0434\u044b \u0431\u0438\u0437\u043d\u0435\u0441 \u043b\u043e\u0433\u0438\u043a\u0438, \u0437\u0430\u0449\u0438\u0442\u0438\u043b\u0438\u0441\u044c \u043e\u0442 CSRF \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043b\u0438 GraphQL \u0441\u0435\u0440\u0432\u0435\u0440.<\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043d\u0443\u0436\u043d\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u041a\u043b\u0438\u0435\u043d\u0442\u0430 \u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439 \u0433\u043e\u0442\u043e\u0432\u044b\u0445 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0441\u0435\u0441\u0441\u0438\u0438 \u041a\u043b\u0438\u0435\u043d\u0442\u0430. <\/p>\n<h2>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f<\/h2>\n<p>\u0423\u043f\u0440\u043e\u0449\u0435\u043d\u043d\u0430\u044f \u0441\u0445\u0435\u043c\u0430:<\/p>\n<ol>\n<li>\n<p>\u041f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u041a\u043b\u0438\u0435\u043d\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 <code>ClientID<\/code><\/p>\n<\/li>\n<li>\n<p>\u0421\u0435\u0440\u0432\u0435\u0440 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0441\u0435\u0441\u0441\u0438\u044e websocket \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d\u043d\u0443\u044e \u043a <code>ClientID<\/code><\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0432\u0441\u0435\u043c \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f\u043c \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u043c \u043a <code>ClientID<\/code><\/p>\n<\/li>\n<li>\n<p>\u0421\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439 <code>ClientID<\/code> \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043c\u0438\u043d\u0438\u043c\u0443\u043c 2, \u0434\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u0432 \u043d\u043e\u0432\u0443\u044e \u0432\u043a\u043b\u0430\u0434\u043a\u0443<\/p>\n<\/li>\n<\/ol>\n<h3>\u0417\u0430\u0434\u0430\u0447\u0438<\/h3>\n<ol>\n<li>\n<p>\u041c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 \u0441\u0435\u0441\u0441\u0438\u0439<\/p>\n<\/li>\n<li>\n<p>\u041a\u0430\u043a Gqlgen \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 websocket <\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u041a\u043b\u0438\u0435\u043d\u0442\u0430 \u0438 \u0435\u0433\u043e \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0442\u0435\u043b\u0435\u0439<\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u041a\u043b\u0438\u0435\u043d\u0442\u0430 \u0432 JWT-\u0442\u043e\u043a\u0435\u043d <\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439 websocket<\/p>\n<\/li>\n<li>\n<p>\u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043f\u043e websocket<\/p>\n<\/li>\n<\/ol>\n<h3>1. \u041c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 \u0441\u0435\u0441\u0441\u0438\u0439<\/h3>\n<p>\u041c\u044b \u0443\u0436\u0435 \u0438\u043c\u0435\u0435\u043c \u043c\u043e\u0434\u0435\u043b\u044c \u0441\u0435\u0441\u0441\u0438\u0438 <code>\/models\/models_gen.go<\/code> \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0438\u0437 \u0441\u0445\u0435\u043c\u044b <code>session.graphqls<\/code>. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432 \u044d\u0442\u043e\u0439 \u0436\u0435 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0444\u0430\u0439\u043b \u0441 \u043c\u0435\u0442\u043e\u0434\u0430\u043c\u0438 \u0434\u043b\u044f \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0441\u0435\u0441\u0441\u0438\u0435\u0439 <code>\/models\/session.go<\/code><\/p>\n<pre><code class=\"go\">\/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u0442 \u043d\u043e\u0432\u0443\u044e \u0441\u0435\u0441\u0441\u0438\u044e func NewSession() *Session { return &amp;Session{  \/\/ \u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u0442 UUID \/\/ go get github.com\/google\/uuid \/\/ \u0438\u043b\u0438 go mod vendor \u043f\u0440\u0438 \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u0430 Sid: uuid.New().String(), } }  \/\/ \u0421\u043e\u0437\u0434\u0430\u0441\u0442 \u0441\u0435\u0441\u0441\u0438\u044e \u0441 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u043c \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u043c func NewSessionWithSid(sid string) *Session { return &amp;Session{ Sid: sid, } }  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u0435\u0441\u0441\u0438\u0438 func (s *Session) GetSid() (sid string, err error) { if s.Sid == \"\" { return \"\", fmt.Errorf(\"session: not found\") } return s.Sid, nil }  \/\/ \u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u0430 func (s *Session) SetOnline() { s.Online = true return }  \/\/ \u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0441\u0435\u0441\u0441\u0438\u044e \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 func (s *Session) WithContext(ctx context.Context) context.Context { return context.WithValue(ctx, sessionCtxKey{\"session\"}, s) }  \/\/ \u041a\u043b\u044e\u0447 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 type sessionCtxKey struct { name string }<\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u044b\u0439 \u0444\u0430\u0439\u043b <code>session.go<\/code> \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 <code>pkg\/store<\/code><\/p>\n<pre><code class=\"go\">\/\/ \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0441\u0435\u0441\u0441\u0438\u044e \u043a\u043b\u0438\u0435\u043d\u0442\u0430 func (s *Store) SessionHandleClient(w http.ResponseWriter, r *http.Request) *http.Request {  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 ctx := r.Context()  \/\/ \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e var sess *model.Session  \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u0430 c ClientID cookie, err := r.Cookie(\"_sid\") if err != nil {  \/\/ \u041d\u0435\u0442 ClientID, \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e sess = model.NewSession()  } else {  \/\/ \u0422\u0443\u0442 \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u043b\u043e\u0433\u0438\u043a\u0430 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \/\/ \u041d\u043e \u043d\u0430\u043c \u0441\u0435\u0439\u0447\u0430\u0441 \u0443\u0434\u043e\u0431\u043d\u043e \u0432\u0438\u0434\u0435\u0442\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c sess = model.NewSessionWithSid(cookie.Value)  \/\/ \u041a\u043b\u0438\u0435\u043d\u0442 \u0438\u043c\u0435\u0435\u0442 ID     \/\/ \u0412\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c cookie,      \/\/ \u0437\u043d\u0430\u0447\u0438\u0442 \u0435\u0441\u043b\u0438 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442,      \/\/ \u0442\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f websocket      \/\/ \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438 \u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u044b     \/\/      \/\/ \u0412 \u0434\u0430\u043d\u043d\u044b\u0439 \u043a\u0435\u0439\u0441 \u043f\u043e\u043f\u0430\u0434\u0430\u0435\u0442 \u041a\u043b\u0438\u0435\u043d\u0442 \u0441 \u0440\u0430\u043d\u0435\u0435 \u0438\u043c\u0435\u044e\u0449\u0438\u043c\u0441\u044f      \/\/ ClientId, \u0432 \u044d\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435      \/\/ \u043f\u043e websocket \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u044d\u0442\u043e sess.SetOnline() }  \/\/ \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0430 \u2013 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u043d\u043e\u0432\u044b\u0435 cookie if err != nil {  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c ID \u043a\u043b\u0438\u0435\u043d\u0442\u0430 sid, err2 := sess.GetSid() if err2 != nil { fmt.Printf(err.Error()) return r }  \/\/ \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c cookie cookie = &amp;http.Cookie{ Name: \"_sid\", \/\/ \u0421id \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0437\u0430\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0432 \u0442\u043e\u043a\u0435\u043d, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 JWT Value: sid, HttpOnly: true, \/\/Secure: true, }  \/\/ \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c cookie http.SetCookie(w, cookie) }  \/\/ \u0421\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0438 \u0432\u0435\u0440\u043d\u0435\u043c *http.Request return r.WithContext(sess.WithContext(ctx)) }<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u0443\u043c\u0435\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e \u041a\u043b\u0438\u0435\u043d\u0442\u0430.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043c\u0435\u0442\u043e\u0434 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 HTTP \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432 \u0444\u0430\u0439\u043b\u0435 <code>pkg\/store\/auth.go<\/code> \u0438 \u0432 \u043d\u0435\u043c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0442\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434:<\/p>\n<pre><code class=\"go\">\/\/ \u0412\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 AuthMiddleware \/\/ \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 HTTP \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \/\/ \u041f\u0440\u043e\u0432\u043e\u0434\u0438\u0442 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f func (s *Store) HandleAuthHTTP(w http.ResponseWriter, r *http.Request) *http.Request {  \/\/ \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u043a\u043b\u0438\u0435\u043d\u0442\u0430 r = s.SessionHandleClient(w, r)  return r }<\/code><\/pre>\n<p>\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u0442\u044c <code>HandleAuthHTTP<\/code> \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u0433\u043e \u041f\u041e HTTP-\u0440\u043e\u0443\u0442\u0435\u0440\u0430. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b <code>auth.go<\/code> \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 <code>pkg\/middleware<\/code>:<\/p>\n<pre><code class=\"go\">func AuthMiddleware(store *store.Store) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {  \/\/ \u041c\u0435\u0442\u043e\u0434 \u0438\u0437 Store, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043b\u043e\u0433\u0438\u043a\u0443 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 r = store.HandleAuthHTTP(w, r) next.ServeHTTP(w, r) }) } }<\/code><\/pre>\n<p>\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u0435\u0433\u043e \u0432 <code>main.go<\/code>:<\/p>\n<pre><code class=\"go\">func main() {   \/\/ ...   router := mux.NewRouter()      \/\/ \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c Auth middleware \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0434\u0438\u043c store \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 router.Use(middleware.AuthMiddleware(store))      \/\/ ... }<\/code><\/pre>\n<p>\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0441\u0435\u0440\u0432\u0435\u0440, \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0431\u0440\u0430\u0443\u0437\u0435\u0440. \u0422\u0435\u043f\u0435\u0440\u044c <strong>\u041a\u043b\u0438\u0435\u043d\u0442<\/strong> \u0438\u043c\u0435\u0435\u0442 \u0441\u0432\u043e\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0432 \u0432\u0438\u0434\u0435 <code>cookie<\/code> \u0441 \u0438\u043c\u0435\u043d\u0435\u043c <code>_sid<\/code><\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" alt=\"\" title=\"\" height=\"336\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6d0\/0cb\/db0\/6d00cbdb008a7eb4607d75fb6b8434d5.png\" data-width=\"890\"\/><figcaption><\/figcaption><\/figure>\n<p><a href=\"https:\/\/github.com\/zhivulinal\/react-apollo-gqlgen-tutorial\/tree\/f8ad58fe4921a659125dee250bfc6ff0e4654b59\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u043c\u043c\u0438\u0442 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u044d\u0442\u0430\u043f\u0430<\/a><\/p>\n<h2>2. \u041a\u0430\u043a Gqlgen \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 websocket <\/h2>\n<p>\u041f\u043e \u043c\u0435\u0440\u0435 \u0440\u0430\u0437\u0432\u0438\u0442\u0438\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043c\u044b \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c\u0441\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e. \u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u0449\u0443\u044e \u043b\u043e\u0433\u0438\u043a\u0443 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439 \u043f\u0440\u043e\u0435\u043a\u0442\u0430.<\/p>\n<p><strong>\u0412\u0430\u0436\u043d\u043e \u0443\u0447\u0435\u0441\u0442\u044c:<\/strong><\/p>\n<ol>\n<li>\n<p><code>ClientID<\/code> \u2013 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0440\u0438\u0441\u0432\u043e\u0435\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0443<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0437\u043d\u044b\u0435 \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430, \u0438\u043c\u0435\u044e\u0442 \u0441\u0432\u043e\u0438 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0435 websocket \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u044b, \u043d\u0435 \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u0441 <code>ClientID<\/code><\/p>\n<\/li>\n<\/ol>\n<p>\u041a\u043b\u0438\u0435\u043d\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u044e\u0449\u0438\u0439\u0441\u044f \u043f\u043e websocket \u0434\u043e\u043b\u0436\u0435\u043d \u0438\u043c\u0435\u0442\u044c cookie \u0441 <code>ClientID<\/code>, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043e\u043d \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043f\u043e\u0441\u043b\u0435 GET-\u0437\u0430\u043f\u0440\u043e\u0441\u0430. \u041e\u0442\u0441\u0435\u043a\u0430\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043d\u0435 \u0438\u043c\u0435\u044e\u0449\u0438\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438: \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043c\u043e\u0436\u043d\u043e \u0443\u0437\u043d\u0430\u0442\u044c \u0438\u0437 <code>model.Session<\/code> \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u2013  \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434. <\/p>\n<p>\u0424\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0441\u043e\u0441\u0442\u043e\u043d\u0438\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442, \u0441\u043b\u0443\u0448\u0430\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 <code>subscription.auth<\/code>. \u0414\u043b\u044f \u044d\u0442\u0438\u0445 \u0446\u0435\u043b\u0435\u0439 \u0443 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0435\u0441\u0442\u044c <a href=\"https:\/\/github.com\/zhivulinal\/react-apollo-gqlgen-tutorial\/blob\/d01b3f859d0f932f6e582461b6038e304f04d050\/backoffice\/pkg\/graph\/auth.go#L12\" rel=\"noopener noreferrer nofollow\">\u043c\u0435\u0442\u043e\u0434 Auth<\/a> \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0449\u0438\u0439 <code>subscriptionResolver<\/code>. \u041d\u0430 \u0435\u0433\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u043c <a href=\"https:\/\/github.com\/zhivulinal\/react-apollo-gqlgen-tutorial\/blob\/d01b3f859d0f932f6e582461b6038e304f04d050\/backoffice\/models\/session.go#L10\" rel=\"noopener noreferrer nofollow\">2 \u043c\u0435\u0442\u043e\u0434\u0430<\/a> \u0432 <code>models\/session<\/code>:<\/p>\n<pre><code class=\"go\">\/\/ \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0441\u0435\u0441\u0441\u0438\u044e \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 func SessionFromContext(ctx context.Context) (*Session, error) { if meta := ctx.Value(sessionCtxKey{\"session\"}); meta != nil { return meta.(*Session), nil } return nil, fmt.Errorf(\"meta: not found\") }  \/\/ \u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u0430 func (s *Session) CheckOnline() bool { return s.Online }<\/code><\/pre>\n<p>\u041e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u0441\u0430\u043c \u043c\u0435\u0442\u043e\u0434 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 <code>Auth<\/code>, \u043e\u043d \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u0442 <a href=\"https:\/\/github.com\/zhivulinal\/react-apollo-gqlgen-tutorial\/blob\/d01b3f859d0f932f6e582461b6038e304f04d050\/backoffice\/pkg\/store\/auth.go#L30\" rel=\"noopener noreferrer nofollow\">AuthWebsocket<\/a> \u2013 \u0438\u0437 Store. \u041e\u0442\u043a\u0440\u043e\u0435\u043c \u0444\u0430\u0439\u043b <code>\/pkg\/store\/auth.go<\/code> \u0438 \u043e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u044d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434:<\/p>\n<pre><code class=\"go\">\/\/ \u0410\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0435\u0442 websocket \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u0442 \u0441\u0435\u0441\u0441\u0438\u044e \/\/ \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0438 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043a\u0430\u043d\u0430\u043b \/\/ \/\/ \u041a\u0430\u0436\u0434\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u0432\u044b\u0437\u044b\u0432\u0430\u0432\u0448\u0438\u0439 \u0434\u0430\u043d\u043d\u044b\u0439 \/\/ \u043c\u0435\u0442\u043e\u0434 \u2013 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u043c func (r *Store) AuthWebsocket(ctx context.Context) (&lt;-chan *model.Auth, error) {  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 sess, err := model.SessionFromContext(ctx) if err != nil {  \/\/ \u0415\u0441\u043b\u0438 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u0442\u043e \u043d\u0435 \u0441\u0442\u043e\u0438\u0442 \u0437\u0434\u0435\u0441\u044c \/\/ \u0435\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0434\u0430\u043b\u044c\u0448\u0435. \/\/ \/\/ \u0415\u0435 \u043d\u0443\u0436\u043d\u043e \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043d\u0430 \u0444\u0440\u043e\u043d\u0442 \/\/ \u0447\u0442\u043e-\u0442\u043e \u0431\u043e\u043b\u0435\u0435 \u043e\u0431\u043e\u0431\u0449\u0435\u043d\u043d\u043e\u0435 fmt.Printf(\"Auth subscriptionResolver. %v\", err)  return nil, gqlerror.Errorf(\"internal error\") }  \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u0438\u043d\u0438\u0446\u0438\u0430\u0442\u043e\u0440\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430. \/\/ \u0415\u0441\u043b\u0438 \u0437\u0430\u043f\u0440\u043e\u0441 \u043f\u043e\u0441\u0442\u0443\u043f\u0438\u043b \u043f\u043e \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u0438 \u043e\u0442 \u041a\u043b\u0438\u0435\u043d\u0442\u0430 \/\/ \u0440\u0430\u043d\u0435\u0435 \u043d\u0435 \u0438\u043c\u0435\u044e\u0449\u0435\u0433\u043e ClientID \u2013 \u043d\u0435 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0435\u0433\u043e if ok := sess.CheckOnline(); !ok {  \/\/ \u0415\u0441\u043b\u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u043d\u0435 \u0438\u043c\u0435\u0435\u0442 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 return nil, gqlerror.Errorf(\"unauthorized\") }  \/\/ \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0432\u0448\u0438\u0439\u0441\u044f \u043a\u043b\u0438\u0435\u043d\u0442 \u2013 \u0443\u043d\u0438\u043a\u0430\u043b\u0435\u043d \/\/ \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c websocket ID wsid := uuid.New().String()  \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043a\u0430\u043d\u0430\u043b \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f in := make(chan *model.Auth)  \/\/ \u0412\u044b\u0432\u0435\u0434\u0435\u043c \u0432 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043f\u0440\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 fmt.Printf(\"WS connect. ID: %v\\n\", wsid)  \/\/ \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f go func() {  \/\/ \u0427\u0442\u043e\u0431\u044b \u0443\u0437\u043d\u0430\u0442\u044c \u043e\u0431 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 websocket \/\/ \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0441\u043b\u0443\u0448\u0430\u0442\u044c \u0441\u0438\u0433\u043d\u0430\u043b \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 &lt;- ctx.Done() fmt.Printf(\"WS disconnect. ID: %v\\n\", wsid) }()  \/\/ \u0422\u0435\u0441\u0442\u043e\u0432\u0430\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f go func() {  \/\/ \u0421\u0440\u0430\u0437\u0443 \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 in &lt;- &amp;model.Auth{ ClientID: time.Now().String(), }  \/\/ \u041d\u0435\u0431\u043e\u043b\u044c\u0448\u0430\u044f \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435 time.Sleep(time.Second * 2) in &lt;- &amp;model.Auth{ ClientID: time.Now().String(), } }()  \/\/ \u0412\u0435\u0440\u043d\u0435\u043c \u043a\u0430\u043d\u0430\u043b return in, nil }<\/code><\/pre>\n<p>\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u0441\u0435\u0440\u0432\u0435\u0440, \u043e\u0442\u043a\u0440\u043e\u0435\u043c <a href=\"http:\/\/localhost:2000\/\" rel=\"noopener noreferrer nofollow\">http:\/\/localhost:2000\/<\/a> \u0432 \u043e\u0442\u043a\u0440\u044b\u0432\u0448\u0435\u043c\u0441\u044f \u043e\u043a\u043d\u0435 Playground \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u043c \u0437\u0430\u043f\u0440\u043e\u0441:<\/p>\n<pre><code class=\"bash\">subscription{   auth{     client_id   } }<\/code><\/pre>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c 2 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0441 \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u043e\u0439:<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" height=\"1606\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/898\/7a3\/f10\/8987a3f10b6cef0a08a38932a1a54600.png\" data-width=\"2400\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u043e\u0442\u0432\u0435\u0442 \u0435\u0441\u043b\u0438 <strong>\u041a\u043b\u0438\u0435\u043d\u0442<\/strong> \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u043c. \u041f\u0440\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043f\u043e\u043b\u0443\u0447\u0438\u043b cookie \u0441 <code>ClientID<\/code>, \u0437\u0430\u0439\u0434\u0435\u043c \u0432 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430, \u0443\u0434\u0430\u043b\u0438\u043c cookie \u0438 \u0441\u043d\u043e\u0432\u0430 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f:<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" height=\"892\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/870\/78d\/1a2\/87078d1a2c77ede74381aac4a7d92040.png\" data-width=\"2400\"\/><figcaption><\/figcaption><\/figure>\n<p>\u041c\u044b \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0438 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u0432 Gqlgen, \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u0442\u044c \u043a \u044d\u0442\u0430\u043f\u0443 \u0441\u0431\u043e\u0440\u043a\u0438 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f.<\/p>\n<p><a href=\"https:\/\/github.com\/zhivulinal\/react-apollo-gqlgen-tutorial\/tree\/d01b3f859d0f932f6e582461b6038e304f04d050\" rel=\"noopener noreferrer nofollow\">\u0418\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0438 \u044d\u0442\u0430\u043f\u0430<\/a><\/p>\n<h2>3. \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u041a\u043b\u0438\u0435\u043d\u0442\u0430 \u0438 \u0435\u0433\u043e \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0442\u0435\u043b\u0435\u0439<\/h2>\n<p>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u041a\u043b\u0438\u0435\u043d\u0442\u0430 \u0438 \u0435\u0433\u043e \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f:<\/p>\n<ol>\n<li>\n<p><code>Client<\/code>: \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0435\u0442 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439 \u0435\u0434\u0438\u043d\u044b\u043c <code>ClientID<\/code>. \u0412 \u043d\u0430\u0448\u0435\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 \u2013 \u0431\u0440\u0430\u0443\u0437\u0435\u0440<\/p>\n<\/li>\n<li>\n<p><code>Observer<\/code>: \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u043c \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u2013 \u0440\u0430\u0437\u043d\u044b\u0435 \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430. \u0418\u043c\u0435\u0435\u0442 \u0441\u0432\u043e\u0439 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440<\/p>\n<\/li>\n<\/ol>\n<h3>\u0412\u0430\u0436\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442!<\/h3>\n<p><code>Observer<\/code>-\u043e\u043c, \u043c\u043e\u0436\u0435\u0442 \u044f\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u043e\u0432\u0430\u044f \u0432\u043a\u043b\u0430\u0434\u043a\u0430. \u041d\u043e \u0438 \u043d\u043e\u0432\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0432 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0435. <\/p>\n<h4>\u041d\u0430 \u0441\u0445\u0435\u043c\u0435:<\/h4>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" height=\"706\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/abc\/703\/5a4\/abc7035a43ab4afa1f746b129756a295.png\" data-width=\"1004\"\/><figcaption><\/figcaption><\/figure>\n<blockquote>\n<p>Observer \u2013 \u0432\u043a\u043b\u0430\u0434\u043a\u0430 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430<\/p>\n<\/blockquote>\n<p>\u0421\u0438\u043d\u0438\u043c \u043e\u0431\u043e\u0437\u043d\u0430\u0447\u0435\u043d\u044b \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f <code>Client-1<\/code>. \u0412\u0438\u0434\u0438\u043c \u0447\u0442\u043e \u0447\u0435\u0440\u0435\u0437 <code>Observer-1<\/code> \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 2 \u043a\u0430\u043d\u0430\u043b\u0430. \u042d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0438\u0437-\u0437\u0430 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044f \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0430 \u0443 Observer. <\/p>\n<h4>\u041a\u0430\u043a \u0440\u0435\u0448\u0430\u0435\u043c?<\/h4>\n<p>\u041f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043d\u043e\u0432\u043e\u0433\u043e \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f \u2013 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e, \u0441\u043e \u0441\u0432\u043e\u0438\u043c \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u043c <code>SessionID<\/code>. <\/p>\n<ol>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430<\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e, \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f websocket \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439<\/p>\n<\/li>\n<li>\n<p>\u041a\u043b\u0438\u0435\u043d\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 <code>Session-ID<\/code> \u0432 \u043e\u0442\u0432\u0435\u0442\u0435 websocket<\/p>\n<\/li>\n<li>\n<p>\u0421\u0435\u0441\u0441\u0438\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u0430 \u2013 \u043f\u043e\u043a\u0430 \u0430\u043a\u0442\u0438\u0432\u043d\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 <strong>\u041a\u043b\u0438\u0435\u043d\u0442<\/strong> \u0438\u043c\u0435\u0435\u0442 <code>SessionID<\/code>, \u0442\u043e \u043f\u0440\u0438 \u043b\u044e\u0431\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u0445 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0435\u0433\u043e \u0432 HTTP-\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a: <code>Session-ID<\/code><\/p>\n<\/li>\n<\/ol>\n<h4>\u0421\u0445\u0435\u043c\u0430 \u043f\u043e\u0441\u043b\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0430:<\/h4>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" height=\"766\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/012\/e4f\/7b7\/012e4f7b7c2e4a99ee097d90aadc267f.png\" data-width=\"1004\"\/><figcaption><\/figcaption><\/figure>\n<h3>\u041f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u043c \u043a \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438<\/h3>\n<p>\u041e\u0442\u043a\u0440\u043e\u0435\u043c \u0444\u0430\u0439\u043b <code>\/pkg\/store\/session.go<\/code>, \u043d\u0430\u0441 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u0435\u0442 <a href=\"https:\/\/github.com\/zhivulinal\/react-apollo-gqlgen-tutorial\/blob\/d01b3f859d0f932f6e582461b6038e304f04d050\/backoffice\/pkg\/store\/session.go#L19\" rel=\"noopener noreferrer nofollow\">\u043a\u043e\u0434 \u0432 \u043c\u0435\u0442\u043e\u0434\u0435<\/a> <code>SessionHandleClient<\/code>:<\/p>\n<pre><code class=\"go\">func (s *Store) SessionHandleClient(w http.ResponseWriter, r *http.Request) *http.Request {   \/\/ ...      \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u0430 c ClientID cookie, err := r.Cookie(\"_cid\") if err != nil {  \/\/ \u041d\u0435\u0442 ClientID, \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e sess = model.NewSession() } else {          \/\/ ... }      \/\/ ... }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043f\u0440\u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438 \u0441\u0435\u0441\u0441\u0438\u0438 \u041a\u043b\u0438\u0435\u043d\u0442\u0430 \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u0443\u044e \u0441\u0435\u0441\u0441\u0438\u044e, \u0434\u0430\u043b\u0435\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0435\u0435 \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442. \u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u0435\u0441\u043b\u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u0438\u043c\u0435\u0435\u0442 <code>ClientID<\/code>, \u043c\u044b \u0441\u043d\u043e\u0432\u0430 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0441 <code>ClientID<\/code> \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u043c \u0438\u0437 cookie.<\/p>\n<p>\u0411\u0443\u0434\u0435\u0442 \u0440\u0430\u0437\u0443\u043c\u043d\u0435\u0435 \u0435\u0441\u043b\u0438 \u043f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438 cookie \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c, \u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u0441\u0435\u0441\u0441\u0438\u044e.<\/p>\n<p>\u041d\u0430\u043c \u043d\u0435 \u0432\u044b\u0433\u043e\u0434\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u0430. \u0417\u043d\u0430\u0447\u0438\u0442 \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0435\u0435 \u0443 \u043a\u043b\u0438\u0435\u043d\u0442\u0430. \u0417\u0434\u0435\u0441\u044c \u0431\u0443\u0434\u0443\u0442 \u0440\u0430\u0437\u0443\u043c\u043d\u044b 2 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430:<\/p>\n<ol>\n<li>\n<p>\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e \u0432 cookie \u043a\u0430\u043a JSON<\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e \u0432 JWT-\u0442\u043e\u043a\u0435\u043d<\/p>\n<\/li>\n<\/ol>\n<h2>4. \u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u041a\u043b\u0438\u0435\u043d\u0442\u0430 \u0432 JWT-\u0442\u043e\u043a\u0435\u043d <\/h2>\n<p>\u0412 \u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0433\u043b\u0430\u0432\u0435 \u043c\u044b \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u044c \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0441\u0435\u0441\u0441\u0438\u0438 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f, \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u0438\u0448\u043b\u0438 \u043a \u0432\u044b\u0432\u043e\u0434\u0443 \u0447\u0442\u043e \u0435\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0433\u0434\u0435-\u0442\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c.<\/p>\n<p>\u041d\u0430 \u043f\u0435\u0440\u0432\u044b\u0439 \u0432\u0437\u0433\u043b\u044f\u0434, \u0441\u0430\u043c\u043e\u0435 \u043f\u0440\u043e\u0441\u0442\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435: \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e \u0432 cookie. \u041d\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u0430 \u0441\u0435\u0441\u0441\u0438\u044f \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d\u043d\u0430\u044f \u043a \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0435. \u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u0441 cookie, \u043e\u043d\u0430 \u0441\u0442\u0430\u043d\u0435\u0442 \u0432\u0438\u0434\u043d\u0430 \u0432\u0441\u0435\u043c\u0443 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0443: \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0435 cookie. \u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u0441 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u043c cookie \u2013 \u043a\u0430\u043a \u0438\u0445 \u0447\u0438\u0441\u0442\u0438\u0442\u044c? \u0411\u0435\u0440\u0435\u043c \u0432\u043e \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u0447\u0442\u043e websocket, \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441 cookie \u0438 \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442 \u0438\u0445 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u043f\u043e\u0441\u043b\u0435 \u043e\u0442\u043f\u0438\u0441\u043a\u0438.<\/p>\n<p><strong>\u0427\u0442\u043e\u0431\u044b \u043d\u0435 \u0438\u0437\u043e\u0431\u0440\u0435\u0442\u0430\u0442\u044c:<\/strong> \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0432 JWT-\u0442\u043e\u043a\u0435\u043d \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0434\u0438\u043c \u0435\u0433\u043e <strong>\u041a\u043b\u0438\u0435\u043d\u0442\u0443<\/strong>. \u041f\u0440\u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0438 <strong>\u041a\u043b\u0438\u0435\u043d\u0442<\/strong>: \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442 \u0442\u043e\u043a\u0435\u043d \u0432 HTTP-\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0435 <code>Session-ID<\/code>. <\/p>\n<blockquote>\n<p>\u041f\u043e\u0447\u0435\u043c\u0443 \u0438\u043c\u0435\u043d\u043d\u043e Session-ID? <br \/>1. \u041c\u044b \u043c\u043e\u0436\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 <strong>\u0421\u0435\u0440\u0432\u0435\u0440\u0430<\/strong> \u0438 \u043f\u0440\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 <code>Session-ID<\/code> \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044c \u0435\u0435 \u0438\u0437 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430<br \/>2. \u0422\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438, JWT-\u0442\u043e\u043a\u0435\u043d \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441\u0435\u0441\u0441\u0438\u0435\u0439. \u0412 \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043e\u043c \u0432\u0438\u0434\u0435 \u0431\u0435\u0441\u043f\u043e\u043b\u0435\u0437\u0435\u043d \u0434\u043b\u044f \u041a\u043b\u0438\u0435\u043d\u0442\u0430<\/p>\n<\/blockquote>\n<h3>JWT-\u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f <\/h3>\n<p>\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043f\u0440\u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0442\u043e\u043a\u0435\u043d\u0430:<\/p>\n<ol>\n<li>\n<p><code>Token invalid<\/code> \u2013 \u043d\u0435 \u0440\u0430\u0431\u043e\u0447\u0435\u0435: \u043a\u043e\u0433\u0434\u0430 \u0442\u043e, \u0447\u0442\u043e \u043f\u0440\u0438\u0448\u043b\u043e \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u043e\u043a\u0435\u043d\u043e\u043c<\/p>\n<\/li>\n<li>\n<p><code>Claims invalid<\/code> \u2013 \u043d\u0435 \u0440\u0430\u0431\u043e\u0447\u0435\u0435: \u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0438\u0437\u0432\u043b\u0435\u0447\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u0443\u044e \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0443<\/p>\n<\/li>\n<li>\n<p><code>Expired<\/code> \u2013 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u0434\u043b\u044f \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0439 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438: \u0432 \u044d\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u043f\u043e\u0441\u043b\u0435 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0441\u0435\u0441\u0441\u0438\u0438<\/p>\n<\/li>\n<li>\n<p><code>Valid<\/code> \u2013 \u0440\u0430\u0431\u043e\u0447\u0438\u0439 \u0442\u043e\u043a\u0435\u043d<\/p>\n<\/li>\n<\/ol>\n<blockquote>\n<p>\u041f\u0440\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 <strong>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f<\/strong>, \u043d\u0430\u043c \u043f\u043e\u043d\u044f\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <code>Expired<\/code>. \u0412 \u044d\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435: \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0432 \u0441\u0435\u0431\u0435 \u043f\u043e\u043b\u0435: <code>AccessToken<\/code>. \u0421 \u043d\u0438\u043c \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043e\u0431\u0440\u0430\u0449\u0430\u0442\u044c\u0441\u044f \u0432 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0441\u0435\u0441\u0441\u0438\u0439 \u043d\u0430 <strong>\u0421\u0435\u0440\u0432\u0435\u0440\u0435<\/strong>. \u0418 \u0435\u0441\u043b\u0438 \u043e\u043d \u0432\u0430\u043b\u0438\u0434\u0435\u043d \u2013 \u0431\u0443\u0434\u0435\u043c \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c JWT-\u0442\u043e\u043a\u0435\u043d. <\/p>\n<p>\u2013 \u0418\u043c\u0435\u043d\u043d\u043e \u043f\u043e \u0442\u0430\u043a\u043e\u043c\u0443 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0443 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 OAuth<\/p>\n<\/blockquote>\n<p>\u041d\u0435 \u0431\u0443\u0434\u0435\u043c \u0443\u0433\u043b\u0443\u0431\u043b\u044f\u0442\u044c\u0441\u044f \u0432 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0442\u043e\u043a\u0435\u043d\u0430. \u0422\u0435\u043c \u0431\u043e\u043b\u0435\u0435, \u0435\u0441\u0442\u044c \u0445\u043e\u0440\u043e\u0448\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f:<br \/><a href=\"https:\/\/habr.com\/ru\/post\/340146\/\" rel=\"noopener noreferrer nofollow\">\u041f\u044f\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0448\u0430\u0433\u043e\u0432 \u0434\u043b\u044f \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f JSON Web Tokens (JWT)<\/a><\/p>\n<p>\u041d\u0430\u0441 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u0435\u0442 \u043a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 JWT \u0432 Golang, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u043e\u0437\u044c\u043c\u0435\u043c \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u044b\u0439 \u043f\u0430\u043a\u0435\u0442 <a href=\"https:\/\/github.com\/dgrijalva\/jwt-go\" rel=\"noopener noreferrer nofollow\">jwt-go<\/a><strong>. <\/strong>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0435\u0433\u043e \u043a \u043f\u0440\u043e\u0435\u043a\u0442\u0443:<\/p>\n<pre><code class=\"bash\">go get github.com\/dgrijalva\/jwt-go<\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b <code>\/pkg\/token\/jwt.go<\/code>:<\/p>\n<pre><code class=\"go\">package token  import ( \"fmt\" \"github.com\/dgrijalva\/jwt-go\" model \"react-apollo-gqlgen-tutorial\/backoffice\/models\" \"time\" )  \/\/ \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 Jwt type Jwt struct { SecretKey  string Issuer     string Expiration int64 }  \/\/ \u041e\u043f\u0446\u0438\u0438 \u043f\u0440\u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0442\u043e\u043a\u0435\u043d\u0430 type JwtClaims struct {  \/\/ \u041d\u0443\u0436\u0435\u043d \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430 AccessToken string  \/\/ \u041f\u0440\u0438\u043a\u0440\u0435\u043f\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e Sess *model.Session  jwt.StandardClaims }  \/\/ \u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430 func (j *Jwt) Generate(opt JwtClaims) (token string, err error) {  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c Claims claims := &amp;opt  \/\/ \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f StandardClaims \/\/ \/\/ \u0417\u0434\u0435\u0441\u044c \"\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u044e\u0442\u0441\u044f\" \u0432\u0441\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \/\/ \u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0434\u043b\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0442\u043e\u043a\u0435\u043d\u0430 \/\/ \/\/ \u0423\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u043f\u0440\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b Jwt claims.StandardClaims = jwt.StandardClaims{ ExpiresAt: time.Now().Local().Add(time.Second * time.Duration(j.Expiration)).Unix(), Issuer:    j.Issuer, }  \/\/ \u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430 t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)  return t.SignedString([]byte(j.SecretKey)) }  \/\/ \u041e\u043f\u0446\u0438\u0438 \u043f\u0440\u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0442\u043e\u043a\u0435\u043d\u0430 type JwtValidateOptions struct { Token string }  \/\/ \u0412\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430 func (j *Jwt) Validate(opt JwtValidateOptions) (claims *JwtClaims, err error) {  \/\/ \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u0443\u044e \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0443 token, err := jwt.ParseWithClaims( opt.Token, &amp;JwtClaims{}, func(token *jwt.Token) (interface{}, error) { return []byte(j.SecretKey), nil }, )  \/\/ \u041f\u043e\u043b\u0435\u0437\u043d\u043e\u0439 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043d\u0435\u0442 \/\/ \u0427\u0442\u043e-\u0442\u043e \u044f\u0432\u043d\u043e \u043d\u0435 \u0432\u0430\u043b\u0438\u0434\u043d\u043e\u0435 \u2013 \u0432\u0435\u0440\u043d\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443 if token == nil { return nil, fmt.Errorf(\"token invalid\") }  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c Claims claims, ok := token.Claims.(*JwtClaims) if !ok { return nil, fmt.Errorf(\"error token claims\") }  \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u0441\u0440\u043e\u043a \u0436\u0438\u0437\u043d\u0438 \u0442\u043e\u043a\u0435\u043d\u0430 if j.Expiration > 0 &amp;&amp; claims.ExpiresAt &lt; time.Now().Local().Unix() {  \/\/ \u0415\u0441\u043b\u0438 \u0442\u043e\u043a\u0435\u043d \u043f\u0440\u043e\u0442\u0443\u0445 \/\/ \u0412\u0435\u0440\u043d\u0435\u043c \u043f\u043e\u043b\u0435\u0437\u043d\u0443\u044e \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439     \/\/      \/\/ \u0414\u043b\u044f \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0439 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438:     \/\/ claims \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c AccessToken return claims, fmt.Errorf(\"token is expired\") }  return claims, nil }  \/\/ \u041e\u043f\u0446\u0438\u0438 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b Jwt type JwtOptions struct { SecretKey string Issuer string ExpSeconds int64 }  func NewJwt(opt JwtOptions) *Jwt { return &amp;Jwt{ SecretKey: opt.SecretKey, Issuer: opt.Issuer, Expiration: opt.ExpSeconds, } }<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e \u0432 \u0442\u043e\u043a\u0435\u043d\u0435, \u0432\u0435\u0440\u043d\u0435\u043c\u0441\u044f \u043a \u0444\u0430\u0439\u043b\u0443  <code>\/pkg\/store\/session.go<\/code>. \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043c\u0435\u0442\u043e\u0434 \u0432\u0430\u0434\u0438\u0434\u0438\u0440\u0443\u044e\u0449\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0438 \u043c\u0435\u0442\u043e\u0434 \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u044e\u0449\u0438\u0439 \u0441\u0435\u0441\u0441\u0438\u044e \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f:<\/p>\n<pre><code class=\"go\">\/\/ \u0412\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u0442 \u0441\u0435\u0441\u0441\u0438\u044e \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f func (s *Store) ValidateClientSession(ctx context.Context) (sessionID string, err error) {  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 sess, err := model.SessionFromContext(ctx) if err != nil { return \"\", fmt.Errorf(\"internal error\") }  if ok := sess.CheckOnline(); !ok {  \/\/ \u0415\u0441\u043b\u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d: SessionID \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \/\/ \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c SessionID, \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0443 sessionToken, err2 := s.token.SessionID.Generate(token.JwtClaims{ Sess: sess, })  if err2 != nil { fmt.Println(err2) return \"\", fmt.Errorf(\"internal error\") }  return sessionToken, nil }  return \"\", nil }  \/\/ \u0412\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u0442 \u0442\u043e\u043a\u0435\u043d \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f func (s *Store) ValidateSessionToken(sid string) (*model.Session, error) {  \/\/ \u0412\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c \u0442\u043e\u043a\u0435\u043d \/\/ \u0421\u0447\u0438\u0442\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u043d\u0435 \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u043c \u0435\u0441\u043b\u0438 \u043d\u0435\u0442 claims if claims, _ := s.token.SessionID.Validate(token.JwtValidateOptions{ Token: sid, }); claims != nil { sess := claims.Sess  \/\/ \u0421\u0435\u0441\u0441\u0438\u044e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u0438\u0437 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430: \u043a\u043b\u0438\u0435\u043d\u0442 \u043e\u043d\u043b\u0430\u0439\u043d sess.SetOnline()  \/\/ \u0421\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 return sess, nil }  return nil, fmt.Errorf(\"invalid session token\") }<\/code><\/pre>\n<p>\u0412 \u044d\u0442\u043e\u043c \u0436\u0435 \u0444\u0430\u0439\u043b\u0435 <code>\/pkg\/store\/session.go<\/code> , \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043a \u043c\u0435\u0442\u043e\u0434\u0443 <code>SessionHandleClient<\/code>, \u043e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u0435\u0433\u043e \u0442\u0430\u043a:<\/p>\n<pre><code class=\"go\">\/\/ \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0441\u0435\u0441\u0441\u0438\u044e \u043a\u043b\u0438\u0435\u043d\u0442\u0430 func (s *Store) SessionHandleClient(w http.ResponseWriter, r *http.Request) *http.Request {  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 ctx := r.Context()  \/\/ \u0421\u044e\u0434\u0430 \u0437\u0430\u043f\u0438\u0448\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e, \u0435\u0441\u043b\u0438 \u0441\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043a\u0435\u0439\u0441 var sess *model.Session var ClientID string  \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u043d\u0430\u043b\u0438\u0447\u0438\u0435 cookie c ClientID cookie, err := r.Cookie(\"_cid\") if err == nil { ClientID = cookie.Value  \/\/ \u0423 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0435\u0441\u0442\u044c ClientID \/\/ 1. \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430 Session-ID \/\/ 2. \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u0438 \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c \u0435\u0433\u043e \/\/ 2.1. \u0422\u043e\u043a\u0435\u043d \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0439: \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0438\u0437 \u0442\u043e\u043a\u0435\u043d\u0430 \/\/ 2.2. \u0422\u043e\u043a\u0435\u043d \u043f\u0440\u043e\u0442\u0443\u0445: \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0438\u0437 \u0442\u043e\u043a\u0435\u043d\u0430 \/\/ 2.3. \u0422\u043e\u043a\u0435\u043d Invalid: \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u0443\u044e \u0441\u0435\u0441\u0441\u0438\u0438  \/\/ \u0418\u0449\u0435\u043c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a Session-ID if t := r.Header.Get(\"Session-ID\"); t != \"\" {  \/\/ \u041d\u0430\u0448\u043b\u0438 \u0441\u0435\u0441\u0441\u0438\u044e if ss, err2 := s.ValidateSessionToken(t); err2 == nil { sess = ss } }  \/\/ \u042d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434 \u0442\u0435\u043f\u0435\u0440\u044c \u0443\u0434\u0430\u043b\u0435\u043d \/\/sess = model.NewSessionWithSid(cookie.Value) }  \/\/ \u0415\u0441\u043b\u0438 \u0441\u0435\u0441\u0441\u0438\u0438 \u043d\u0435\u0442: \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e if sess == nil { sess = model.NewSession()  if ClientID != \"\" { sess.AddClientID(ClientID) } }  \/\/ \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0447\u0442\u0435\u043d\u0438\u0438 cookie if err != nil {  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c ID \u043a\u043b\u0438\u0435\u043d\u0442\u0430 cid, err2 := sess.GetSid() if err2 != nil { fmt.Printf(err.Error()) return r }  \/\/ \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c cookie cookie = &amp;http.Cookie{ Name: \"_cid\", Value: cid, HttpOnly: true, \/\/Secure: true, }  \/\/ \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c cookie http.SetCookie(w, cookie) }  \/\/ \u0421\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0438 \u0432\u0435\u0440\u043d\u0435\u043c *http.Request return r.WithContext(sess.WithContext(ctx)) }<\/code><\/pre>\n<p>\u041c\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c HTTP-\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438. \u041d\u043e websocket \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0438\u043d\u0430\u0447\u0435. \u041e\u0442\u043a\u0440\u043e\u0435\u043c \u0444\u0430\u0439\u043b <code>\/pkg\/graph\/resolver.go<\/code>. \u0422\u0443\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0440\u0438\u043d\u044f\u0442\u044c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a <code>Session-ID<\/code>, \u043f\u0440\u043e\u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u043e\u043a\u0435\u043d, \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u0443\u0441\u043f\u0435\u0445\u0430: \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0435\u0435 \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442.<\/p>\n<pre><code class=\"go\">func NewServer(opt Options) *handler.Server { \/\/ ...    srv.AddTransport(transport.Websocket{          \/\/ ... InitFunc: transport.WebsocketInitFunc(func(ctx context.Context, initPayload transport.InitPayload) (context.Context, error) {  \/\/ \u0422\u0443\u0442 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f websocket \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \"Session-ID\" if sid, ok := initPayload[\"Session-ID\"]; ok { if sess, err := opt.Store.ValidateSessionToken(sid.(string)); err == nil {            \/\/ \u0421\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442           ctx = sess.WithContext(ctx) } }  return ctx, nil }), })      \/\/ ... }<\/code><\/pre>\n<h3>\u041c\u0435\u0442\u043e\u0434\u044b \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438<\/h3>\n<p>\u041c\u044b \u0432\u0441\u0435 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043b\u0438, \u0442\u0435\u043f\u0435\u0440\u044c \u043d\u0443\u0436\u043d\u043e, \u0433\u0434\u0435-\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c <s>\u0442\u043e\u043a\u0435\u043d<\/s> <code>SessionID<\/code>. \u041f\u0440\u0438 \u043b\u044e\u0431\u043e\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u0435 <code>model.Auth<\/code>, \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e. \u0417\u0430 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0441\u043b\u0443\u0447\u0430\u044f \u043a\u043e\u0433\u0434\u0430 \u043e\u043d\u0430 \u0438\u043c\u0435\u0435\u0442\u0441\u044f. <\/p>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 <code>model.Auth<\/code>: \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438, \u0437\u0430 \u043d\u0435\u0433\u043e \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 <code>Auth()<\/code> \u0432 \u0444\u0430\u0439\u043b\u0435 <code>\/pkg\/store\/auth.go<\/code>. \u041e\u0442\u043a\u0440\u043e\u0435\u043c \u0438 \u043e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u0435\u0433\u043e:<\/p>\n<pre><code class=\"go\">\/\/ \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 Auth \u0438\u0441\u0445\u043e\u0434\u044f \u0438\u0437 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 func (s *Store) Auth(ctx context.Context) (*model.Auth, error) {  \/\/ \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043c\u043e\u0434\u0435\u043b\u044c auth := &amp;model.Auth{}  \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e sid, err := s.ValidateClientSession(ctx) if err != nil { return nil, gqlerror.Errorf(\"internal error\") }  \/\/ \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c sid \u2013 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0435\u0433\u043e \u043a Auth if sid != \"\" { auth.AddSessionId(sid) }  \/\/ \u041e\u0442\u043f\u0440\u0430\u0432\u0438\u043c \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 return auth, nil }<\/code><\/pre>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u043c\u0435\u0442\u043e\u0434 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u043d\u0430 websocket <code>model.Auth<\/code>:<\/p>\n<pre><code class=\"go\">func (s *Store) AuthWebsocket(ctx context.Context) (&lt;-chan *model.Auth, error) {  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 auth, err := s.Auth(ctx) if err != nil { return nil, gqlerror.Errorf(\"internal error\") }  \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043a\u0430\u043d\u0430\u043b \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f ch := make(chan *model.Auth)  \/\/ \u041d\u0443\u0436\u043d\u043e \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 go func() { ch &lt;- auth }()  \/\/ \u0412\u0435\u0440\u043d\u0435\u043c \u043a\u0430\u043d\u0430\u043b return ch, nil }<\/code><\/pre>\n<h3>\u0414\u043b\u044f \u0447\u0438\u0441\u0442\u043e\u0442\u044b \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430<\/h3>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044c websocket \u0434\u043b\u044f <code>User<\/code>. \u041e\u0442\u043a\u0440\u043e\u0435\u043c \u0444\u0430\u0439\u043b <code>schema.graphqls<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043c\u0435\u0442\u043e\u0434:<\/p>\n<pre><code class=\"dart\">\"\"\" \u041f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u043d\u0430 websocket \"\"\" type Subscription {    \"\"\"   \u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u043c\u0435\u0442\u043e\u0434:   \u041f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u043d\u0430 Auth   \"\"\"   user: User!    \"\"\"   \u041f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u043d\u0430 Auth   \"\"\"   auth: Auth! }<\/code><\/pre>\n<p>\u0412\u0432\u0435\u0434\u0435\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u043c\u0435\u0445\u044b \u0432 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u0435:<\/p>\n<pre><code>go run cmd\/gqlgen.go<\/code><\/pre>\n<p>\u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u043c \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434:<br \/> <code>User(ctx context.Context) (&lt;-chan *model.User, error)<br \/><\/code> \u0432 <code>\/pkg\/graph\/user<\/code> \u0438 \u043e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u0435\u0433\u043e:<\/p>\n<pre><code class=\"go\">func (r *subscriptionResolver) User(ctx context.Context) (&lt;-chan *model.User, error) { user := make(chan *model.User)  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 sess, err := model.SessionFromContext(ctx) if err != nil { return nil, gqlerror.Errorf(\"internal error\") }  fmt.Printf(\"User. Session: %v\\n\", sess.Sid)  return user, nil }<\/code><\/pre>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u043e\u0445\u043e\u0436\u0438\u0439 \u043a\u043e\u0434 \u0432 \u043c\u0435\u0442\u043e\u0434 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u043d\u0430 <code>Auth<\/code>:<\/p>\n<pre><code class=\"go\">func (s *Store) AuthWebsocket(ctx context.Context) (&lt;-chan *model.Auth, error) {  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 sess, err := model.SessionFromContext(ctx) if err != nil { return nil, gqlerror.Errorf(\"internal error\") }  fmt.Printf(\"Auth. Session: %v\\n\", sess.Sid)      \/\/ ... }<\/code><\/pre>\n<p>\u041e\u0442\u043a\u0440\u043e\u0435\u043c \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b:<\/p>\n<pre><code class=\"bash\">go run cmd\/main.go<\/code><\/pre>\n<p>\u0412 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435: <a href=\"http:\/\/localhost:2000\/\" rel=\"noopener noreferrer nofollow\">http:\/\/localhost:2000\/<\/a><\/p>\n<p>\u0412\u0432\u043e\u0434\u0438\u043c \u0437\u0430\u043f\u0440\u043e\u0441:<\/p>\n<pre><code class=\"bash\">query{   auth{     sessionId   } }<\/code><\/pre>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d:<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" height=\"934\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b9f\/ca2\/703\/b9fca27038e24ecf464c482b28f8c882.png\" data-width=\"2266\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u0441\u044f \u043a <code>Auth<\/code>, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0434\u0430\u043d\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432 Playground \u0435\u0441\u0442\u044c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442: <\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" height=\"477\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f79\/d56\/52a\/f79d5652a78bc05b15405b8b6ce7246a.png\" data-width=\"1133\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0412\u0432\u043e\u0434\u0438\u043c \u0437\u0430\u043f\u0440\u043e\u0441:<\/p>\n<pre><code class=\"bash\">subscription{   auth{     sessionId   } }<\/code><\/pre>\n<p>\u0421\u043b\u0435\u0434\u043e\u043c \u0432\u0432\u043e\u0434\u0438\u043c \u0432\u0442\u043e\u0440\u043e\u0439:<\/p>\n<pre><code>subscription {   user{     uid   } }<\/code><\/pre>\n<p>\u0421\u043c\u043e\u0442\u0440\u0438\u043c \u0432 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b, \u0438 \u0432\u0438\u0434\u0438\u043c \u0447\u0442\u043e <code>Auth<\/code> \u0438 <code>User<\/code> \u0438\u043c\u0435\u044e\u0442 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0435 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u044b. \u042d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442 \u0447\u0442\u043e \u0441\u0435\u0441\u0441\u0438\u044f \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0438 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f:<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" height=\"882\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/204\/cc1\/447\/204cc14471a194f2af7ea1bd2cc3279e.png\" data-width=\"1364\"\/><figcaption><\/figcaption><\/figure>\n<p>\u041c\u044b \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u0441\u0435\u0441\u0441\u0438\u044e \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u043a\u043d\u0430, \u0442\u0435\u043f\u0435\u0440\u044c \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u0430\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439.<\/p>\n<h2>5. \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439 websocket<\/h2>\n<p>\u041f\u0440\u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f <code>Auth<\/code> \u0438\u043b\u0438 <code>User<\/code> \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e:<\/p>\n<ol>\n<li>\n<p>\u041f\u043e\u043b\u0443\u0447\u0438\u0442\u044c: <code>ClientID<\/code> \u0438 <code>SessionID<\/code><\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u041a\u043b\u0438\u0435\u043d\u0442\u0430 \u043f\u043e <code>ClientID<\/code><\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f \u043f\u043e <code>SessionID<\/code><\/p>\n<\/li>\n<\/ol>\n<p>\u041f\u0440\u0438 \u043e\u0442\u043f\u0438\u0441\u043a\u0435:<\/p>\n<ol>\n<li>\n<p>\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044c <code>Auth<\/code> \u0438\u043b\u0438 <code>User<\/code><\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439, \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044f: \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0437\u0430\u043f\u0438\u0441\u044c \u0441 \u0441\u0435\u0441\u0441\u0438\u0435\u0439 \u041a\u043b\u0438\u0435\u043d\u0442\u0430<\/p>\n<\/li>\n<\/ol>\n<p>\u041c\u044b \u0445\u043e\u0442\u0438\u043c \u0438\u043c\u0435\u0442\u044c \u0443\u043d\u0438\u0444\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0434\u043b\u044f <code>Auth<\/code> \u0438 <code>User<\/code> \u0438\u043b\u0438 \u0434\u043b\u044f \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439 \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u041f\u043e \u044d\u0442\u043e\u043c\u0443 \u043d\u0435 \u043c\u043e\u0436\u0435\u043c \u0437\u043d\u0430\u0442\u044c \u0442\u0438\u043f \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u043c\u043e\u0433\u043e \u043a\u0430\u043d\u0430\u043b\u0430 \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f. \u0414\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0435 \u0442\u0438\u043f\u0430 \u0431\u0443\u0434\u0435\u043c \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u043f\u0443\u0441\u0442\u043e\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0438 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0442\u044c \u043a\u0430\u043d\u0430\u043b \u0441\u043e\u0433\u043b\u0430\u0441\u043d\u043e \u0435\u0433\u043e \u0442\u0438\u043f\u0430.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b <code>\/pkg\/websocket\/observer.go<\/code> \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0449\u0438\u0439 \u0442\u0438\u043f \u043a\u0430\u043d\u0430\u043b\u0430:<\/p>\n<pre><code class=\"go\">type observer struct { auth chan *model.Auth user chan *model.User }  func (o *observer) Add(ch interface{}) error {  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c \u0442\u0438\u043f \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 switch ch.(type) { case chan *model.Auth: o.auth = ch.(chan *model.Auth) return nil case chan *model.User: o.user = ch.(chan *model.User) return nil default:          \/\/ \u0422\u0438\u043f \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d return fmt.Errorf(\"observer: unknown type\") } }  \/\/ \u0423\u0434\u0430\u043b\u044f\u0435\u0442 \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0442\u0435\u043b\u044f, \/\/ \u0435\u0441\u043b\u0438 \u0432\u0435\u0440\u043d\u0435\u0442 true - \u043c\u043e\u0436\u043d\u043e \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044c func (o *observer) Delete(ch interface{}) bool {  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c \u0442\u0438\u043f \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 switch ch.(type) { case chan *model.Auth: o.auth = nil case chan *model.User: o.user = nil }  return o.checkEmpty() }  \/\/ \u0412\u0435\u0440\u043d\u0435\u0442 \u0438\u0441\u0442\u0438\u043d\u0443 \u0435\u0441\u043b\u0438 \u043d\u0435\u0442 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439 func (o *observer) checkEmpty() bool { switch { case o.auth != nil: return false case o.user != nil: return false } return true }<\/code><\/pre>\n<p>\u0421\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0435\u0442 \u041a\u043b\u0438\u0435\u043d\u0442, \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b \/pkg\/websocket\/client.go:<\/p>\n<pre><code class=\"go\">type client struct { observers map[string]*observer mu sync.Mutex }  \/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f \u041a\u043b\u0438\u0435\u043d\u0442\u0430 func (c *client) Add(sid string, ch interface{}) error {  \/\/ \u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u043c \u043c\u0430\u043f\u0443 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439 \/\/ \u0447\u0442\u043e\u0431\u044b \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0441 \u043d\u0435\u0439 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c c.mu.Lock()  \/\/ \u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u043c \u043c\u0430\u043f\u0443 \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u0445\u043e\u0434\u0430 \u0438\u0437 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 defer c.mu.Unlock()  \/\/ \u041f\u043e\u0438\u0449\u0435\u043c \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f obs, ok := c.observers[sid] if !ok {  \/\/ \u0421\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d, \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c obs = &amp;observer{}  \/\/ \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0432 \u043c\u0430\u043f\u0443 c.observers[sid] = obs }  err := obs.Add(ch) if err != nil { return err }  return nil }  \/\/ \u0423\u0434\u0430\u043b\u044f\u0435\u0442 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f \/\/ \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043f\u0440\u0438\u0437\u043d\u0430\u043a \u043d\u0430\u043b\u0438\u0447\u0438\u044f \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439 func (c *client) Delete(sid string, ch interface{}) bool { c.mu.Lock() defer c.mu.Unlock()  obs, ok := c.observers[sid] if !ok { \/\/ \u041e\u0431\u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d? fmt.Println(\"panic\") }  \/\/ \u0423\u0434\u0430\u043b\u044f\u0435\u043c \u043a\u0430\u043d\u0430\u043b if ok = obs.Delete(ch); ok {  \/\/ \u0415\u0441\u043b\u0438 \u0432\u0435\u0440\u043d\u0443\u043b\u0441\u044f \u043f\u0440\u0438\u0437\u043d\u0430\u043a \u043f\u0443\u0441\u0442\u043e\u0442\u044b \/\/ \u0423\u0434\u0430\u043b\u0438\u043c \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044c delete(c.observers, sid) }  \/\/ \u041f\u043e\u0441\u0447\u0438\u0442\u0430\u0435\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439 \/\/ \u0438 \u0432\u0435\u0440\u043d\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 return len(c.observers) == 0 }  func newClient() *client { return &amp;client{ observers: make(map[string]*observer), } }<\/code><\/pre>\n<p>\u041e\u0441\u0442\u0430\u043b\u0441\u044f \u0444\u0430\u0439\u043b \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u0430 \u0441 \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u043c \u043c\u0435\u0442\u043e\u0434\u043e\u043c \u0441\u043e\u0437\u0434\u0430\u044e\u0449\u0438\u043c \u043d\u043e\u0432\u043e\u0433\u043e \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f <code>\/pkg\/websocket\/manager.go<\/code>:<\/p>\n<pre><code class=\"go\">type Websocket struct { clients map[string]*client  \/\/ \u0417\u0430\u0449\u0438\u0449\u0430\u0435\u043c \u043c\u0430\u043f\u0443 mu sync.Mutex }  \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u0442 \u041a\u043b\u0438\u0435\u043d\u0442\u0430 func (w *Websocket) NewObserver(ctx context.Context, ch interface{}) error {  \/\/ \u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u043c \u043c\u0430\u043f\u0443 clients \/\/ \u0447\u0442\u043e\u0431\u044b \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0441 \u043d\u0435\u0439 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c w.mu.Lock()  \/\/ \u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u043c \u043c\u0430\u043f\u0443 \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u0445\u043e\u0434\u0430 \u0438\u0437 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 defer w.mu.Unlock()  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 sess, err := model.SessionFromContext(ctx) if err != nil { return err }  cid := sess.ClientID sid := sess.Sid  \/\/ \u041d\u0430\u0439\u0434\u0435\u043c, \u0438\u043b\u0438 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0430 cli, ok := w.clients[cid] if !ok {  \/\/ \u041a\u043b\u0438\u0435\u043d\u0442 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d, \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c cli = newClient()  \/\/ \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0432 \u043c\u0430\u043f\u0443 w.clients[cid] = cli }  \/\/ \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0430 err = cli.Add(sid, ch) if err != nil { return err }  \/\/ \u041a\u043b\u0438\u0435\u043d\u0442 \u043e\u0442\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u2013 \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044c go func() { &lt;- ctx.Done()  cli.Delete(sid, ch) }()  return nil }  func New() *Websocket { return &amp;Websocket{ clients: make(map[string]*client), } }<\/code><\/pre>\n<p>\u041c\u044b \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u0430 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439 websocket, \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0435\u0433\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c.<\/p>\n<p>\u041e\u0442\u043a\u0440\u043e\u0435\u043c \u0444\u0430\u0439\u043b \/pkg\/store\/auth.go, \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 \u043a \u043c\u0435\u0442\u043e\u0434\u0443 <code>AuthWebsocket<\/code> \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u044e\u0449\u0435\u043c\u0443 websocket \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f <code>Auth<\/code>:<\/p>\n<pre><code class=\"go\">func (s *Store) AuthWebsocket(ctx context.Context) (&lt;-chan *model.Auth, error) {  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 auth, err := s.Auth(ctx) if err != nil { fmt.Println(err) return nil, gqlerror.Errorf(\"internal error\") }  \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043a\u0430\u043d\u0430\u043b \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f ch := make(chan *model.Auth)  \/\/ \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u043a\u0430\u043d\u0430\u043b \u043a \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u0443 websocket err = s.websocket.NewObserver(ctx, ch) if err != nil { fmt.Println(err) return nil, gqlerror.Errorf(\"internal error\") }  \/\/ \u041d\u0443\u0436\u043d\u043e \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 go func() { ch &lt;- auth }()  \/\/ \u0412\u0435\u0440\u043d\u0435\u043c \u043a\u0430\u043d\u0430\u043b return ch, nil }<\/code><\/pre>\n<p>\u0412\u0441\u0435 \u0433\u043e\u0442\u043e\u0432\u043e, \u0442\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u0448\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u043a ClientID \u0438 SessionID<\/p>\n<p><a href=\"https:\/\/github.com\/zhivulinal\/react-apollo-gqlgen-tutorial\/tree\/0ccfeac61e5e725c1abaa8bb232e6cef5877a1f4\" rel=\"noopener noreferrer nofollow\">\u0418\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0438 \u044d\u0442\u0430\u043f\u0430<\/a><\/p>\n<h2>6. \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043f\u043e websocket<\/h2>\n<p>\u041c\u044b \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439, \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434\u044b \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f\u043c \u043f\u043e \u0438\u0445 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044e. <\/p>\n<p>\u041e\u0442\u043a\u0440\u043e\u0435\u043c \u0444\u0430\u0439\u043b <code>\/pkg\/websocket\/observer.go<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043c\u0435\u0442\u043e\u0434 <code>Send<\/code>:<\/p>\n<pre><code class=\"go\">func (o *observer) Send(ch interface{}) {  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c \u0442\u0438\u043f \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 switch ch.(type) { case *model.Auth:  \/\/ \u0412\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c \u043a\u0430\u043d\u0430\u043b if o.auth == nil { fmt.Println(\"Auth sending error\") return }  \/\/ \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 o.auth &lt;- ch.(*model.Auth) case *model.User:  \/\/ \u0412\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c \u043a\u0430\u043d\u0430\u043b if o.user == nil { fmt.Println(\"User sending error\") return }  \/\/ \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 o.user &lt;- ch.(*model.User) default: fmt.Println(\"unknown message type\") } }<\/code><\/pre>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043c\u0435\u0442\u043e\u0434 \u0432 <code>\/pkg\/websocket\/client.go<\/code>:<\/p>\n<pre><code class=\"go\">func (c *client) Send(ch interface{}) { c.mu.Lock() defer c.mu.Unlock()  \/\/ \u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u043c \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f\u043c \/\/ \u041f\u0440\u043e\u0439\u0434\u0435\u043c\u0441\u044f \u0432 \u0446\u0438\u043a\u043b\u0435 \u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \/\/ \/\/ \u041b\u0443\u0447\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u0433\u043e\u0440\u0443\u0442\u0438\u043d\u0435  \/\/ \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c WaitGroup \/\/ \u041f\u0440\u043e \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u043e \u0432 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435: \/\/ https:\/\/habr.com\/ru\/company\/otus\/blog\/557312\/ wg := sync.WaitGroup{} wg.Add(1) go func() { for _, obs := range c.observers { obs.Send(ch) } wg.Done() }() wg.Wait() }<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u043e\u0431\u0430\u0432\u0438\u043c <code>Send<\/code> \u0432 \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 <code>\/pkg\/websoket\/manager.go<\/code>:<\/p>\n<pre><code class=\"go\">func (w *Websocket) Send(ctx context.Context, ch interface{}) error { w.mu.Lock() defer w.mu.Unlock()  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 sess, err := model.SessionFromContext(ctx) if err != nil { return err }  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c ClientID cid := sess.ClientID  \/\/ \u041d\u0430\u0439\u0434\u0435\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0430 cli, ok := w.clients[cid] if !ok { return fmt.Errorf(\"client not found\") }  \/\/ \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 cli.Send(ch)  return nil }<\/code><\/pre>\n<h3>\u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c Auth \u043f\u043e websocket<\/h3>\n<p>\u041c\u044b \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043b\u0438\u0441\u044c \u043a \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043f\u043e \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442. \u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043c\u0435\u0442\u043e\u0434 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f <code>Auth<\/code> \u041a\u043b\u0438\u0435\u043d\u0442\u0443:<\/p>\n<pre><code class=\"go\">func (s *Store) SendAuth(ctx context.Context) error {  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 auth, err := s.Auth(ctx) if err != nil { return err }  \/\/ Todo: \u0443\u0434\u0430\u043b\u0438\u0442\u044c!!! \/\/ \u0427\u0442\u043e\u0431\u044b \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \/\/ \u041d\u0443\u0436\u043d\u043e \u0447\u0442\u043e \u043d\u0438\u0431\u0443\u0434\u044c \u0440\u0430\u043d\u0434\u043e\u043c\u043d\u043e\u0435 auth.Method = time.Now().String()  if err = s.websocket.Send(ctx, auth); err != nil { return err }  return nil }<\/code><\/pre>\n<h3>\u0422\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435<\/h3>\n<p>\u0414\u043b\u044f \u0442\u0435\u0441\u0442\u0430 \u0438\u0437\u043c\u0435\u043d\u0438\u043c \u043c\u0435\u0442\u043e\u0434 <code>User<\/code> \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u044e\u0449\u0438\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u0432 \u0444\u0430\u0439\u043b\u0435 <code>\/pkg\/store\/user.go<\/code>:<\/p>\n<pre><code class=\"go\">func (s *Store) User(ctx context.Context) (*model.User, error) {  err := s.SendAuth(ctx) fmt.Println(\"\u0417\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c Auth \u0438\u0437 \u043c\u0435\u0442\u043e\u0434\u0430 User\") fmt.Printf(\"\u041e\u0448\u0438\u0431\u043a\u0430: %v\\n\", err)  \/\/ ... return &amp;model.User{ Username: \"LOLO\", }, nil }<\/code><\/pre>\n<p>\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0441\u0435\u0440\u0432\u0435\u0440: <\/p>\n<pre><code class=\"bash\">go run cmd\/main.go<\/code><\/pre>\n<p>\u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c: <a href=\"http:\/\/localhost:2000\/\" rel=\"noopener noreferrer nofollow\">http:\/\/localhost:2000\/<\/a><\/p>\n<p>\u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441:<\/p>\n<pre><code>subscription{   auth{     sessionId,     method   } }<\/code><\/pre>\n<p>\u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043d\u043e\u0432\u0443\u044e \u0432\u043a\u043b\u0430\u0434\u043a\u0443 \u0432 Playground, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d \u043f\u043e\u043c\u0435\u0449\u0430\u0435\u043c \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a:<\/p>\n<pre><code>{   \"Session-ID\": \"TOKEN\" }<\/code><\/pre>\n<p>\u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441:<\/p>\n<pre><code>query{   user{     username   } }<\/code><\/pre>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0432\u043e \u0432\u043a\u043b\u0430\u0434\u043a\u0435 auth:<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" height=\"1316\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/570\/0ef\/0fe\/5700ef0fe2e1b8d6942578099480de9f.png\" data-width=\"1934\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0412\u043a\u043b\u0430\u0434\u043a\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430, \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u044b: \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e.<\/p>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u0443\u044e \u0447\u0430\u0441\u0442\u044c. \u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0447\u0430\u0441\u0442\u0438 \u043c\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u043c\u0435\u0445\u0430\u043d\u0438\u043c \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0438 \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0435\u043c \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u043d\u0430 React + Apollo.<\/p>\n<\/div>\n<\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/599723\/\"> https:\/\/habr.com\/ru\/post\/599723\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\" class=\"article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<blockquote>\n<p>\u0421\u0442\u0430\u0442\u044c\u044f \u0440\u0430\u0437\u0431\u0438\u0442\u0430 \u043d\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0447\u0430\u0441\u0442\u0435\u0439:<br \/>&#8212; <a href=\"https:\/\/habr.com\/ru\/post\/598359\/\" rel=\"noopener noreferrer nofollow\">\u0447\u0430\u0441\u0442\u044c 1<\/a><br \/>&#8212; \u0447\u0430\u0441\u0442\u044c 3 &#8212; \u0432 \u0440\u0430\u0431\u043e\u0442\u0435<br \/>&#8212;\u00a0<a href=\"https:\/\/github.com\/zhivulinal\/react-apollo-gqlgen-tutorial\/tree\/8330d4ad8c6f428e777aada3b047bebd7abf1a2c\" rel=\"noopener noreferrer nofollow\">\u0438\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0438 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438<\/a><\/p>\n<\/blockquote>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>\u0412 \u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043c\u044b \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u043c\u0435\u0442\u043e\u0434\u044b \u0431\u0438\u0437\u043d\u0435\u0441 \u043b\u043e\u0433\u0438\u043a\u0438, \u0437\u0430\u0449\u0438\u0442\u0438\u043b\u0438\u0441\u044c \u043e\u0442 CSRF \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043b\u0438 GraphQL \u0441\u0435\u0440\u0432\u0435\u0440.<\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043d\u0443\u0436\u043d\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u041a\u043b\u0438\u0435\u043d\u0442\u0430 \u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439 \u0433\u043e\u0442\u043e\u0432\u044b\u0445 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0441\u0435\u0441\u0441\u0438\u0438 \u041a\u043b\u0438\u0435\u043d\u0442\u0430. <\/p>\n<h2>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f<\/h2>\n<p>\u0423\u043f\u0440\u043e\u0449\u0435\u043d\u043d\u0430\u044f \u0441\u0445\u0435\u043c\u0430:<\/p>\n<ol>\n<li>\n<p>\u041f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u041a\u043b\u0438\u0435\u043d\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 <code>ClientID<\/code><\/p>\n<\/li>\n<li>\n<p>\u0421\u0435\u0440\u0432\u0435\u0440 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0441\u0435\u0441\u0441\u0438\u044e websocket \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d\u043d\u0443\u044e \u043a <code>ClientID<\/code><\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0432\u0441\u0435\u043c \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f\u043c \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u043c \u043a <code>ClientID<\/code><\/p>\n<\/li>\n<li>\n<p>\u0421\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439 <code>ClientID<\/code> \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043c\u0438\u043d\u0438\u043c\u0443\u043c 2, \u0434\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u0432 \u043d\u043e\u0432\u0443\u044e \u0432\u043a\u043b\u0430\u0434\u043a\u0443<\/p>\n<\/li>\n<\/ol>\n<h3>\u0417\u0430\u0434\u0430\u0447\u0438<\/h3>\n<ol>\n<li>\n<p>\u041c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 \u0441\u0435\u0441\u0441\u0438\u0439<\/p>\n<\/li>\n<li>\n<p>\u041a\u0430\u043a Gqlgen \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 websocket <\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u041a\u043b\u0438\u0435\u043d\u0442\u0430 \u0438 \u0435\u0433\u043e \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0442\u0435\u043b\u0435\u0439<\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u041a\u043b\u0438\u0435\u043d\u0442\u0430 \u0432 JWT-\u0442\u043e\u043a\u0435\u043d <\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439 websocket<\/p>\n<\/li>\n<li>\n<p>\u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043f\u043e websocket<\/p>\n<\/li>\n<\/ol>\n<h3>1. \u041c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 \u0441\u0435\u0441\u0441\u0438\u0439<\/h3>\n<p>\u041c\u044b \u0443\u0436\u0435 \u0438\u043c\u0435\u0435\u043c \u043c\u043e\u0434\u0435\u043b\u044c \u0441\u0435\u0441\u0441\u0438\u0438 <code>\/models\/models_gen.go<\/code> \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0438\u0437 \u0441\u0445\u0435\u043c\u044b <code>session.graphqls<\/code>. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432 \u044d\u0442\u043e\u0439 \u0436\u0435 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0444\u0430\u0439\u043b \u0441 \u043c\u0435\u0442\u043e\u0434\u0430\u043c\u0438 \u0434\u043b\u044f \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0441\u0435\u0441\u0441\u0438\u0435\u0439 <code>\/models\/session.go<\/code><\/p>\n<pre><code class=\"go\">\/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u0442 \u043d\u043e\u0432\u0443\u044e \u0441\u0435\u0441\u0441\u0438\u044e func NewSession() *Session { return &amp;Session{  \/\/ \u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u0442 UUID \/\/ go get github.com\/google\/uuid \/\/ \u0438\u043b\u0438 go mod vendor \u043f\u0440\u0438 \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u0430 Sid: uuid.New().String(), } }  \/\/ \u0421\u043e\u0437\u0434\u0430\u0441\u0442 \u0441\u0435\u0441\u0441\u0438\u044e \u0441 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u043c \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u043c func NewSessionWithSid(sid string) *Session { return &amp;Session{ Sid: sid, } }  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u0435\u0441\u0441\u0438\u0438 func (s *Session) GetSid() (sid string, err error) { if s.Sid == \"\" { return \"\", fmt.Errorf(\"session: not found\") } return s.Sid, nil }  \/\/ \u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u0430 func (s *Session) SetOnline() { s.Online = true return }  \/\/ \u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0441\u0435\u0441\u0441\u0438\u044e \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 func (s *Session) WithContext(ctx context.Context) context.Context { return context.WithValue(ctx, sessionCtxKey{\"session\"}, s) }  \/\/ \u041a\u043b\u044e\u0447 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 type sessionCtxKey struct { name string }<\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u044b\u0439 \u0444\u0430\u0439\u043b <code>session.go<\/code> \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 <code>pkg\/store<\/code><\/p>\n<pre><code class=\"go\">\/\/ \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0441\u0435\u0441\u0441\u0438\u044e \u043a\u043b\u0438\u0435\u043d\u0442\u0430 func (s *Store) SessionHandleClient(w http.ResponseWriter, r *http.Request) *http.Request {  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 ctx := r.Context()  \/\/ \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e var sess *model.Session  \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u0430 c ClientID cookie, err := r.Cookie(\"_sid\") if err != nil {  \/\/ \u041d\u0435\u0442 ClientID, \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e sess = model.NewSession()  } else {  \/\/ \u0422\u0443\u0442 \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u043b\u043e\u0433\u0438\u043a\u0430 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \/\/ \u041d\u043e \u043d\u0430\u043c \u0441\u0435\u0439\u0447\u0430\u0441 \u0443\u0434\u043e\u0431\u043d\u043e \u0432\u0438\u0434\u0435\u0442\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c sess = model.NewSessionWithSid(cookie.Value)  \/\/ \u041a\u043b\u0438\u0435\u043d\u0442 \u0438\u043c\u0435\u0435\u0442 ID     \/\/ \u0412\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c cookie,      \/\/ \u0437\u043d\u0430\u0447\u0438\u0442 \u0435\u0441\u043b\u0438 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442,      \/\/ \u0442\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f websocket      \/\/ \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438 \u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u044b     \/\/      \/\/ \u0412 \u0434\u0430\u043d\u043d\u044b\u0439 \u043a\u0435\u0439\u0441 \u043f\u043e\u043f\u0430\u0434\u0430\u0435\u0442 \u041a\u043b\u0438\u0435\u043d\u0442 \u0441 \u0440\u0430\u043d\u0435\u0435 \u0438\u043c\u0435\u044e\u0449\u0438\u043c\u0441\u044f      \/\/ ClientId, \u0432 \u044d\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435      \/\/ \u043f\u043e websocket \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u044d\u0442\u043e sess.SetOnline() }  \/\/ \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0430 \u2013 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u043d\u043e\u0432\u044b\u0435 cookie if err != nil {  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c ID \u043a\u043b\u0438\u0435\u043d\u0442\u0430 sid, err2 := sess.GetSid() if err2 != nil { fmt.Printf(err.Error()) return r }  \/\/ \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c cookie cookie = &amp;http.Cookie{ Name: \"_sid\", \/\/ \u0421id \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0437\u0430\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0432 \u0442\u043e\u043a\u0435\u043d, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 JWT Value: sid, HttpOnly: true, \/\/Secure: true, }  \/\/ \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c cookie http.SetCookie(w, cookie) }  \/\/ \u0421\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0438 \u0432\u0435\u0440\u043d\u0435\u043c *http.Request return r.WithContext(sess.WithContext(ctx)) }<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u0443\u043c\u0435\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e \u041a\u043b\u0438\u0435\u043d\u0442\u0430.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043c\u0435\u0442\u043e\u0434 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 HTTP \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432 \u0444\u0430\u0439\u043b\u0435 <code>pkg\/store\/auth.go<\/code> \u0438 \u0432 \u043d\u0435\u043c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0442\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434:<\/p>\n<pre><code class=\"go\">\/\/ \u0412\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 AuthMiddleware \/\/ \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 HTTP \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \/\/ \u041f\u0440\u043e\u0432\u043e\u0434\u0438\u0442 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f func (s *Store) HandleAuthHTTP(w http.ResponseWriter, r *http.Request) *http.Request {  \/\/ \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u043a\u043b\u0438\u0435\u043d\u0442\u0430 r = s.SessionHandleClient(w, r)  return r }<\/code><\/pre>\n<p>\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u0442\u044c <code>HandleAuthHTTP<\/code> \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u0433\u043e \u041f\u041e HTTP-\u0440\u043e\u0443\u0442\u0435\u0440\u0430. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b <code>auth.go<\/code> \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 <code>pkg\/middleware<\/code>:<\/p>\n<pre><code class=\"go\">func AuthMiddleware(store *store.Store) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {  \/\/ \u041c\u0435\u0442\u043e\u0434 \u0438\u0437 Store, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043b\u043e\u0433\u0438\u043a\u0443 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 r = store.HandleAuthHTTP(w, r) next.ServeHTTP(w, r) }) } }<\/code><\/pre>\n<p>\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u0435\u0433\u043e \u0432 <code>main.go<\/code>:<\/p>\n<pre><code class=\"go\">func main() {   \/\/ ...   router := mux.NewRouter()      \/\/ \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c Auth middleware \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0434\u0438\u043c store \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 router.Use(middleware.AuthMiddleware(store))      \/\/ ... }<\/code><\/pre>\n<p>\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0441\u0435\u0440\u0432\u0435\u0440, \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0431\u0440\u0430\u0443\u0437\u0435\u0440. \u0422\u0435\u043f\u0435\u0440\u044c <strong>\u041a\u043b\u0438\u0435\u043d\u0442<\/strong> \u0438\u043c\u0435\u0435\u0442 \u0441\u0432\u043e\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0432 \u0432\u0438\u0434\u0435 <code>cookie<\/code> \u0441 \u0438\u043c\u0435\u043d\u0435\u043c <code>_sid<\/code><\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p><a href=\"https:\/\/github.com\/zhivulinal\/react-apollo-gqlgen-tutorial\/tree\/f8ad58fe4921a659125dee250bfc6ff0e4654b59\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u043c\u043c\u0438\u0442 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u044d\u0442\u0430\u043f\u0430<\/a><\/p>\n<h2>2. \u041a\u0430\u043a Gqlgen \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 websocket <\/h2>\n<p>\u041f\u043e \u043c\u0435\u0440\u0435 \u0440\u0430\u0437\u0432\u0438\u0442\u0438\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043c\u044b \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c\u0441\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e. \u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u0449\u0443\u044e \u043b\u043e\u0433\u0438\u043a\u0443 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439 \u043f\u0440\u043e\u0435\u043a\u0442\u0430.<\/p>\n<p><strong>\u0412\u0430\u0436\u043d\u043e \u0443\u0447\u0435\u0441\u0442\u044c:<\/strong><\/p>\n<ol>\n<li>\n<p><code>ClientID<\/code> \u2013 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0440\u0438\u0441\u0432\u043e\u0435\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0443<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0437\u043d\u044b\u0435 \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430, \u0438\u043c\u0435\u044e\u0442 \u0441\u0432\u043e\u0438 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0435 websocket \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u044b, \u043d\u0435 \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u0441 <code>ClientID<\/code><\/p>\n<\/li>\n<\/ol>\n<p>\u041a\u043b\u0438\u0435\u043d\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u044e\u0449\u0438\u0439\u0441\u044f \u043f\u043e websocket \u0434\u043e\u043b\u0436\u0435\u043d \u0438\u043c\u0435\u0442\u044c cookie \u0441 <code>ClientID<\/code>, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043e\u043d \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043f\u043e\u0441\u043b\u0435 GET-\u0437\u0430\u043f\u0440\u043e\u0441\u0430. \u041e\u0442\u0441\u0435\u043a\u0430\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043d\u0435 \u0438\u043c\u0435\u044e\u0449\u0438\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438: \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043c\u043e\u0436\u043d\u043e \u0443\u0437\u043d\u0430\u0442\u044c \u0438\u0437 <code>model.Session<\/code> \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u2013  \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434. <\/p>\n<p>\u0424\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0441\u043e\u0441\u0442\u043e\u043d\u0438\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442, \u0441\u043b\u0443\u0448\u0430\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 <code>subscription.auth<\/code>. \u0414\u043b\u044f \u044d\u0442\u0438\u0445 \u0446\u0435\u043b\u0435\u0439 \u0443 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0435\u0441\u0442\u044c <a href=\"https:\/\/github.com\/zhivulinal\/react-apollo-gqlgen-tutorial\/blob\/d01b3f859d0f932f6e582461b6038e304f04d050\/backoffice\/pkg\/graph\/auth.go#L12\" rel=\"noopener noreferrer nofollow\">\u043c\u0435\u0442\u043e\u0434 Auth<\/a> \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0449\u0438\u0439 <code>subscriptionResolver<\/code>. \u041d\u0430 \u0435\u0433\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u043c <a href=\"https:\/\/github.com\/zhivulinal\/react-apollo-gqlgen-tutorial\/blob\/d01b3f859d0f932f6e582461b6038e304f04d050\/backoffice\/models\/session.go#L10\" rel=\"noopener noreferrer nofollow\">2 \u043c\u0435\u0442\u043e\u0434\u0430<\/a> \u0432 <code>models\/session<\/code>:<\/p>\n<pre><code class=\"go\">\/\/ \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0441\u0435\u0441\u0441\u0438\u044e \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 func SessionFromContext(ctx context.Context) (*Session, error) { if meta := ctx.Value(sessionCtxKey{\"session\"}); meta != nil { return meta.(*Session), nil } return nil, fmt.Errorf(\"meta: not found\") }  \/\/ \u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u0430 func (s *Session) CheckOnline() bool { return s.Online }<\/code><\/pre>\n<p>\u041e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u0441\u0430\u043c \u043c\u0435\u0442\u043e\u0434 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 <code>Auth<\/code>, \u043e\u043d \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u0442 <a href=\"https:\/\/github.com\/zhivulinal\/react-apollo-gqlgen-tutorial\/blob\/d01b3f859d0f932f6e582461b6038e304f04d050\/backoffice\/pkg\/store\/auth.go#L30\" rel=\"noopener noreferrer nofollow\">AuthWebsocket<\/a> \u2013 \u0438\u0437 Store. \u041e\u0442\u043a\u0440\u043e\u0435\u043c \u0444\u0430\u0439\u043b <code>\/pkg\/store\/auth.go<\/code> \u0438 \u043e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u044d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434:<\/p>\n<pre><code class=\"go\">\/\/ \u0410\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0435\u0442 websocket \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u0442 \u0441\u0435\u0441\u0441\u0438\u044e \/\/ \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0438 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043a\u0430\u043d\u0430\u043b \/\/ \/\/ \u041a\u0430\u0436\u0434\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u0432\u044b\u0437\u044b\u0432\u0430\u0432\u0448\u0438\u0439 \u0434\u0430\u043d\u043d\u044b\u0439 \/\/ \u043c\u0435\u0442\u043e\u0434 \u2013 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u043c func (r *Store) AuthWebsocket(ctx context.Context) (&lt;-chan *model.Auth, error) {  \/\/ \u041f\u043e\u043b\u0443\u0447\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 sess, err := model.SessionFromContext(ctx) if err != nil {  \/\/ \u0415\u0441\u043b\u0438 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u0442\u043e \u043d\u0435 \u0441\u0442\u043e\u0438\u0442 \u0437\u0434\u0435\u0441\u044c \/\/ \u0435\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0434\u0430\u043b\u044c\u0448\u0435. \/\/ \/\/ \u0415\u0435 \u043d\u0443\u0436\u043d\u043e \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043d\u0430 \u0444\u0440\u043e\u043d\u0442 \/\/ \u0447\u0442\u043e-\u0442\u043e \u0431\u043e\u043b\u0435\u0435 \u043e\u0431\u043e\u0431\u0449\u0435\u043d\u043d\u043e\u0435 fmt.Printf(\"Auth subscriptionResolver. %v\", err)  return nil, gqlerror.Errorf(\"internal error\") }  \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u0438\u043d\u0438\u0446\u0438\u0430\u0442\u043e\u0440\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430. \/\/ \u0415\u0441\u043b\u0438 \u0437\u0430\u043f\u0440\u043e\u0441 \u043f\u043e\u0441\u0442\u0443\u043f\u0438\u043b \u043f\u043e \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u0438 \u043e\u0442 \u041a\u043b\u0438\u0435\u043d\u0442\u0430 \/\/ \u0440\u0430\u043d\u0435\u0435 \u043d\u0435 \u0438\u043c\u0435\u044e\u0449\u0435\u0433\u043e ClientID \u2013 \u043d\u0435 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0435\u0433\u043e if ok := sess.CheckOnline(); !ok {  \/\/ \u0415\u0441\u043b\u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u043d\u0435 \u0438\u043c\u0435\u0435\u0442 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 return nil, gqlerror.Errorf(\"unauthorized\") }  \/\/ \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0432\u0448\u0438\u0439\u0441\u044f \u043a\u043b\u0438\u0435\u043d\u0442 \u2013 \u0443\u043d\u0438\u043a\u0430\u043b\u0435\u043d \/\/ \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c websocket ID wsid := uuid.New().String()  \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043a\u0430\u043d\u0430\u043b \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f in := make(chan *model.Auth)  \/\/ \u0412\u044b\u0432\u0435\u0434\u0435\u043c \u0432 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043f\u0440\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 fmt.Printf(\"WS connect. ID: %v\\n\", wsid)  \/\/ \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f go func() {  \/\/ \u0427\u0442\u043e\u0431\u044b \u0443\u0437\u043d\u0430\u0442\u044c \u043e\u0431 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 websocket \/\/ \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0441\u043b\u0443\u0448\u0430\u0442\u044c \u0441\u0438\u0433\u043d\u0430\u043b \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 &lt;- ctx.Done() fmt.Printf(\"WS disconnect. ID: %v\\n\", wsid) }()  \/\/ \u0422\u0435\u0441\u0442\u043e\u0432\u0430\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f go func() {  \/\/ \u0421\u0440\u0430\u0437\u0443 \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 in &lt;- &amp;model.Auth{ ClientID: time.Now().String(), }  \/\/ \u041d\u0435\u0431\u043e\u043b\u044c\u0448\u0430\u044f \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435 time.Sleep(time.Second * 2) in &lt;- &amp;model.Auth{ ClientID: time.Now().String(), } }()  \/\/ \u0412\u0435\u0440\u043d\u0435\u043c \u043a\u0430\u043d\u0430\u043b return in, nil }<\/code><\/pre>\n<p>\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u0441\u0435\u0440\u0432\u0435\u0440, \u043e\u0442\u043a\u0440\u043e\u0435\u043c <a href=\"http:\/\/localhost:2000\/\" rel=\"noopener noreferrer nofollow\">http:\/\/localhost:2000\/<\/a> \u0432 \u043e\u0442\u043a\u0440\u044b\u0432\u0448\u0435\u043c\u0441\u044f \u043e\u043a\u043d\u0435 Playground \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u043c \u0437\u0430\u043f\u0440\u043e\u0441:<\/p>\n<pre><code class=\"bash\">subscription{   auth{     client_id   } }<\/code><\/pre>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c 2 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0441 \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u043e\u0439:<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u043e\u0442\u0432\u0435\u0442 \u0435\u0441\u043b\u0438 <strong>\u041a\u043b\u0438\u0435\u043d\u0442<\/strong> \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u043c. \u041f\u0440\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043f\u043e\u043b\u0443\u0447\u0438\u043b cookie \u0441 <code>ClientID<\/code>, \u0437\u0430\u0439\u0434\u0435\u043c \u0432 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430, \u0443\u0434\u0430\u043b\u0438\u043c cookie \u0438 \u0441\u043d\u043e\u0432\u0430 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f:<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>\u041c\u044b \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0438 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u0432 Gqlgen, \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u0442\u044c \u043a \u044d\u0442\u0430\u043f\u0443 \u0441\u0431\u043e\u0440\u043a\u0438 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f.<\/p>\n<p><a href=\"https:\/\/github.com\/zhivulinal\/react-apollo-gqlgen-tutorial\/tree\/d01b3f859d0f932f6e582461b6038e304f04d050\" rel=\"noopener noreferrer nofollow\">\u0418\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0438 \u044d\u0442\u0430\u043f\u0430<\/a><\/p>\n<h2>3. \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u041a\u043b\u0438\u0435\u043d\u0442\u0430 \u0438 \u0435\u0433\u043e \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0442\u0435\u043b\u0435\u0439<\/h2>\n<p>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u041a\u043b\u0438\u0435\u043d\u0442\u0430 \u0438 \u0435\u0433\u043e \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f:<\/p>\n<ol>\n<li>\n<p><code>Client<\/code>: \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0435\u0442 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0435\u0439 \u0435\u0434\u0438\u043d\u044b\u043c <code>ClientID<\/code>. \u0412 \u043d\u0430\u0448\u0435\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 \u2013 \u0431\u0440\u0430\u0443\u0437\u0435\u0440<\/p>\n<\/li>\n<li>\n<p><code>Observer<\/code>: \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u043c \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u2013 \u0440\u0430\u0437\u043d\u044b\u0435 \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430. \u0418\u043c\u0435\u0435\u0442 \u0441\u0432\u043e\u0439 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440<\/p>\n<\/li>\n<\/ol>\n<h3>\u0412\u0430\u0436\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442!<\/h3>\n<p><code>Observer<\/code>-\u043e\u043c, \u043c\u043e\u0436\u0435\u0442 \u044f\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u043e\u0432\u0430\u044f \u0432\u043a\u043b\u0430\u0434\u043a\u0430. \u041d\u043e \u0438 \u043d\u043e\u0432\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0432 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0435. <\/p>\n<h4>\u041d\u0430 \u0441\u0445\u0435\u043c\u0435:<\/h4>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<blockquote>\n<p>Observer \u2013 \u0432\u043a\u043b\u0430\u0434\u043a\u0430 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430<\/p>\n<\/blockquote>\n<p>\u0421\u0438\u043d\u0438\u043c \u043e\u0431\u043e\u0437\u043d\u0430\u0447\u0435\u043d\u044b \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f <code>Client-1<\/code>. \u0412\u0438\u0434\u0438\u043c \u0447\u0442\u043e \u0447\u0435\u0440\u0435\u0437 <code>Observer-1<\/code> \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 2 \u043a\u0430\u043d\u0430\u043b\u0430. \u042d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0438\u0437-\u0437\u0430 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044f \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0430 \u0443 Observer. <\/p>\n<h4>\u041a\u0430\u043a \u0440\u0435\u0448\u0430\u0435\u043c?<\/h4>\n<p>\u041f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043d\u043e\u0432\u043e\u0433\u043e \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f \u2013 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e, \u0441\u043e \u0441\u0432\u043e\u0438\u043c \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u043c <code>SessionID<\/code>. <\/p>\n<ol>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430<\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e, \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f websocket \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439<\/p>\n<\/li>\n<li>\n<p>\u041a\u043b\u0438\u0435\u043d\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 <code>Session-ID<\/code> \u0432 \u043e\u0442\u0432\u0435\u0442\u0435 websocket<\/p>\n<\/li>\n<li>\n<p>\u0421\u0435\u0441\u0441\u0438\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u0430 \u2013 \u043f\u043e\u043a\u0430 \u0430\u043a\u0442\u0438\u0432\u043d\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 <strong>\u041a\u043b\u0438\u0435\u043d\u0442<\/strong> \u0438\u043c\u0435\u0435\u0442 <code>SessionID<\/code>, \u0442\u043e \u043f\u0440\u0438 \u043b\u044e\u0431\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u0445 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0435\u0433\u043e \u0432 HTTP-\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a: <code>Session-ID<\/code><\/p>\n<\/li>\n<\/ol>\n<h4>\u0421\u0445\u0435\u043c\u0430 \u043f\u043e\u0441\u043b\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0430:<\/h4>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<h3>\u041f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u043c \u043a \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438<\/h3>\n<p>\u041e\u0442\u043a\u0440\u043e\u0435\u043c \u0444\u0430\u0439\u043b <code>\/pkg\/store\/session.go<\/code>, \u043d\u0430\u0441 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u0435\u0442 <a href=\"https:\/\/github.com\/zhivulinal\/react-apollo-gqlgen-tutorial\/blob\/d01b3f859d0f932f6e582461b6038e304f04d050\/backoffice\/pkg\/store\/session.go#L19\" rel=\"noopener noreferrer nofollow\">\u043a\u043e\u0434 \u0432 \u043c\u0435\u0442\u043e\u0434\u0435<\/a> <code>SessionHandleClient<\/code>:<\/p>\n<pre><code class=\"go\">func (s *Store) SessionHandleClient(w http.ResponseWriter, r *http.Request) *http.Request {   \/\/ ...      \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u0430 c ClientID cookie, err := r.Cookie(\"_cid\") if err != nil {  \/\/ \u041d\u0435\u0442 ClientID, \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u0435\u0441\u0441\u0438\u044e sess = model.NewSession() } else {          \/\/ ... }      \/\/ ... }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043f\u0440\u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438 \u0441\u0435\u0441\u0441\u0438\u0438 \u041a\u043b\u0438\u0435\u043d\u0442\u0430 \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u0443\u044e \u0441\u0435\u0441\u0441\u0438\u044e, \u0434\u0430\u043b\u0435\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0435\u0435 \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442. \u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u0435\u0441\u043b\u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u0438\u043c\u0435\u0435\u0442 <code>ClientID<\/code>, \u043c\u044b \u0441\u043d\u043e\u0432\u0430 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u0441 <code>ClientID<\/code> \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u043c \u0438\u0437 cookie.<\/p>\n<p>\u0411\u0443\u0434\u0435\u0442 \u0440\u0430\u0437\u0443\u043c\u043d\u0435\u0435 \u0435\u0441\u043b\u0438 \u043f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438 cookie \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c, \u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u0441\u0435\u0441\u0441\u0438\u044e.<\/p>\n<p>\u041d\u0430\u043c \u043d\u0435 \u0432\u044b\u0433\u043e\u0434\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u0430. \u0417\u043d\u0430\u0447\u0438\u0442 \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0435\u0435 \u0443 \u043a\u043b\u0438\u0435\u043d\u0442\u0430. \u0417\u0434\u0435\u0441\u044c \u0431\u0443\u0434\u0443\u0442 \u0440\u0430\u0437\u0443\u043c\u043d\u044b 2 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430:<\/p>\n<ol>\n<li>\n<p>\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e \u0432 cookie \u043a\u0430\u043a JSON<\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e \u0432 JWT-\u0442\u043e\u043a\u0435\u043d<\/p>\n<\/li>\n<\/ol>\n<h2>4. \u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e \u041a\u043b\u0438\u0435\u043d\u0442\u0430 \u0432 JWT-\u0442\u043e\u043a\u0435\u043d <\/h2>\n<p>\u0412 \u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0433\u043b\u0430\u0432\u0435 \u043c\u044b \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u044c \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0441\u0435\u0441\u0441\u0438\u0438 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044f, \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u0438\u0448\u043b\u0438 \u043a \u0432\u044b\u0432\u043e\u0434\u0443 \u0447\u0442\u043e \u0435\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0433\u0434\u0435-\u0442\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c.<\/p>\n<p>\u041d\u0430 \u043f\u0435\u0440\u0432\u044b\u0439 \u0432\u0437\u0433\u043b\u044f\u0434, \u0441\u0430\u043c\u043e\u0435 \u043f\u0440\u043e\u0441\u0442\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435: \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e \u0432 cookie. \u041d\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u0430 \u0441\u0435\u0441\u0441\u0438\u044f \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d\u043d\u0430\u044f \u043a \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0439<\/p>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-326830","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/326830","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=326830"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/326830\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=326830"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=326830"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=326830"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}