refine tenant api

Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
hongming
2019-04-01 02:59:19 +08:00
parent 744bd053e3
commit 93ad572e19
202 changed files with 13517 additions and 7951 deletions

5
vendor/gopkg.in/igm/sockjs-go.v2/sockjs/doc.go generated vendored Normal file
View File

@@ -0,0 +1,5 @@
/*
Package sockjs is a server side implementation of sockjs protocol.
*/
package sockjs

32
vendor/gopkg.in/igm/sockjs-go.v2/sockjs/eventsource.go generated vendored Normal file
View File

@@ -0,0 +1,32 @@
package sockjs
import (
"fmt"
"io"
"net/http"
)
func (h *handler) eventSource(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("content-type", "text/event-stream; charset=UTF-8")
fmt.Fprintf(rw, "\r\n")
rw.(http.Flusher).Flush()
recv := newHTTPReceiver(rw, h.options.ResponseLimit, new(eventSourceFrameWriter))
sess, _ := h.sessionByRequest(req)
if err := sess.attachReceiver(recv); err != nil {
recv.sendFrame(cFrame)
recv.close()
return
}
select {
case <-recv.doneNotify():
case <-recv.interruptedNotify():
}
}
type eventSourceFrameWriter struct{}
func (*eventSourceFrameWriter) write(w io.Writer, frame string) (int, error) {
return fmt.Fprintf(w, "data: %s\r\n\r\n", frame)
}

11
vendor/gopkg.in/igm/sockjs-go.v2/sockjs/frame.go generated vendored Normal file
View File

@@ -0,0 +1,11 @@
package sockjs
import (
"encoding/json"
"fmt"
)
func closeFrame(status uint32, reason string) string {
bytes, _ := json.Marshal([]interface{}{status, reason})
return fmt.Sprintf("c%s", string(bytes))
}

133
vendor/gopkg.in/igm/sockjs-go.v2/sockjs/handler.go generated vendored Normal file
View File

@@ -0,0 +1,133 @@
package sockjs
import (
"errors"
"net/http"
"net/url"
"regexp"
"strings"
"sync"
)
var (
prefixRegexp = make(map[string]*regexp.Regexp)
prefixRegexpMu sync.Mutex // protects prefixRegexp
)
type handler struct {
prefix string
options Options
handlerFunc func(Session)
mappings []*mapping
sessionsMux sync.Mutex
sessions map[string]*session
}
// NewHandler creates new HTTP handler that conforms to the basic net/http.Handler interface.
// It takes path prefix, options and sockjs handler function as parameters
func NewHandler(prefix string, opts Options, handleFunc func(Session)) http.Handler {
return newHandler(prefix, opts, handleFunc)
}
func newHandler(prefix string, opts Options, handlerFunc func(Session)) *handler {
h := &handler{
prefix: prefix,
options: opts,
handlerFunc: handlerFunc,
sessions: make(map[string]*session),
}
sessionPrefix := prefix + "/[^/.]+/[^/.]+"
h.mappings = []*mapping{
newMapping("GET", prefix+"[/]?$", welcomeHandler),
newMapping("OPTIONS", prefix+"/info$", opts.cookie, xhrCors, cacheFor, opts.info),
newMapping("GET", prefix+"/info$", xhrCors, noCache, opts.info),
// XHR
newMapping("POST", sessionPrefix+"/xhr_send$", opts.cookie, xhrCors, noCache, h.xhrSend),
newMapping("OPTIONS", sessionPrefix+"/xhr_send$", opts.cookie, xhrCors, cacheFor, xhrOptions),
newMapping("POST", sessionPrefix+"/xhr$", opts.cookie, xhrCors, noCache, h.xhrPoll),
newMapping("OPTIONS", sessionPrefix+"/xhr$", opts.cookie, xhrCors, cacheFor, xhrOptions),
newMapping("POST", sessionPrefix+"/xhr_streaming$", opts.cookie, xhrCors, noCache, h.xhrStreaming),
newMapping("OPTIONS", sessionPrefix+"/xhr_streaming$", opts.cookie, xhrCors, cacheFor, xhrOptions),
// EventStream
newMapping("GET", sessionPrefix+"/eventsource$", opts.cookie, xhrCors, noCache, h.eventSource),
// Htmlfile
newMapping("GET", sessionPrefix+"/htmlfile$", opts.cookie, xhrCors, noCache, h.htmlFile),
// JsonP
newMapping("GET", sessionPrefix+"/jsonp$", opts.cookie, xhrCors, noCache, h.jsonp),
newMapping("OPTIONS", sessionPrefix+"/jsonp$", opts.cookie, xhrCors, cacheFor, xhrOptions),
newMapping("POST", sessionPrefix+"/jsonp_send$", opts.cookie, xhrCors, noCache, h.jsonpSend),
// IFrame
newMapping("GET", prefix+"/iframe[0-9-.a-z_]*.html$", cacheFor, h.iframe),
}
if opts.Websocket {
h.mappings = append(h.mappings, newMapping("GET", sessionPrefix+"/websocket$", h.sockjsWebsocket))
}
return h
}
func (h *handler) Prefix() string { return h.prefix }
func (h *handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// iterate over mappings
allowedMethods := []string{}
for _, mapping := range h.mappings {
if match, method := mapping.matches(req); match == fullMatch {
for _, hf := range mapping.chain {
hf(rw, req)
}
return
} else if match == pathMatch {
allowedMethods = append(allowedMethods, method)
}
}
if len(allowedMethods) > 0 {
rw.Header().Set("allow", strings.Join(allowedMethods, ", "))
rw.Header().Set("Content-Type", "")
rw.WriteHeader(http.StatusMethodNotAllowed)
return
}
http.NotFound(rw, req)
}
func (h *handler) parseSessionID(url *url.URL) (string, error) {
// cache compiled regexp objects for most used prefixes
prefixRegexpMu.Lock()
session, ok := prefixRegexp[h.prefix]
if !ok {
session = regexp.MustCompile(h.prefix + "/(?P<server>[^/.]+)/(?P<session>[^/.]+)/.*")
prefixRegexp[h.prefix] = session
}
prefixRegexpMu.Unlock()
matches := session.FindStringSubmatch(url.Path)
if len(matches) == 3 {
return matches[2], nil
}
return "", errors.New("unable to parse URL for session")
}
func (h *handler) sessionByRequest(req *http.Request) (*session, error) {
h.sessionsMux.Lock()
defer h.sessionsMux.Unlock()
sessionID, err := h.parseSessionID(req.URL)
if err != nil {
return nil, err
}
sess, exists := h.sessions[sessionID]
if !exists {
sess = newSession(sessionID, h.options.DisconnectDelay, h.options.HeartbeatDelay)
h.sessions[sessionID] = sess
if h.handlerFunc != nil {
go h.handlerFunc(sess)
}
go func() {
<-sess.closedNotify()
h.sessionsMux.Lock()
delete(h.sessions, sessionID)
h.sessionsMux.Unlock()
}()
}
return sess, nil
}

58
vendor/gopkg.in/igm/sockjs-go.v2/sockjs/htmlfile.go generated vendored Normal file
View File

@@ -0,0 +1,58 @@
package sockjs
import (
"fmt"
"io"
"net/http"
"strings"
)
var iframeTemplate = `<!doctype html>
<html><head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head><body><h2>Don't panic!</h2>
<script>
document.domain = document.domain;
var c = parent.%s;
c.start();
function p(d) {c.message(d);};
window.onload = function() {c.stop();};
</script>
`
func init() {
iframeTemplate += strings.Repeat(" ", 1024-len(iframeTemplate)+14)
iframeTemplate += "\r\n\r\n"
}
func (h *handler) htmlFile(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("content-type", "text/html; charset=UTF-8")
req.ParseForm()
callback := req.Form.Get("c")
if callback == "" {
http.Error(rw, `"callback" parameter required`, http.StatusInternalServerError)
return
}
rw.WriteHeader(http.StatusOK)
fmt.Fprintf(rw, iframeTemplate, callback)
rw.(http.Flusher).Flush()
sess, _ := h.sessionByRequest(req)
recv := newHTTPReceiver(rw, h.options.ResponseLimit, new(htmlfileFrameWriter))
if err := sess.attachReceiver(recv); err != nil {
recv.sendFrame(cFrame)
recv.close()
return
}
select {
case <-recv.doneNotify():
case <-recv.interruptedNotify():
}
}
type htmlfileFrameWriter struct{}
func (*htmlfileFrameWriter) write(w io.Writer, frame string) (int, error) {
return fmt.Fprintf(w, "<script>\np(%s);\n</script>\r\n", quote(frame))
}

105
vendor/gopkg.in/igm/sockjs-go.v2/sockjs/httpreceiver.go generated vendored Normal file
View File

@@ -0,0 +1,105 @@
package sockjs
import (
"fmt"
"io"
"net/http"
"strings"
"sync"
)
type frameWriter interface {
write(writer io.Writer, frame string) (int, error)
}
type httpReceiverState int
const (
stateHTTPReceiverActive httpReceiverState = iota
stateHTTPReceiverClosed
)
type httpReceiver struct {
sync.Mutex
state httpReceiverState
frameWriter frameWriter
rw http.ResponseWriter
maxResponseSize uint32
currentResponseSize uint32
doneCh chan struct{}
interruptCh chan struct{}
}
func newHTTPReceiver(rw http.ResponseWriter, maxResponse uint32, frameWriter frameWriter) *httpReceiver {
recv := &httpReceiver{
rw: rw,
frameWriter: frameWriter,
maxResponseSize: maxResponse,
doneCh: make(chan struct{}),
interruptCh: make(chan struct{}),
}
if closeNotifier, ok := rw.(http.CloseNotifier); ok {
// if supported check for close notifications from http.RW
closeNotifyCh := closeNotifier.CloseNotify()
go func() {
select {
case <-closeNotifyCh:
recv.Lock()
defer recv.Unlock()
if recv.state < stateHTTPReceiverClosed {
recv.state = stateHTTPReceiverClosed
close(recv.interruptCh)
}
case <-recv.doneCh:
// ok, no action needed here, receiver closed in correct way
// just finish the routine
}
}()
}
return recv
}
func (recv *httpReceiver) sendBulk(messages ...string) {
if len(messages) > 0 {
recv.sendFrame(fmt.Sprintf("a[%s]",
strings.Join(
transform(messages, quote),
",",
),
))
}
}
func (recv *httpReceiver) sendFrame(value string) {
recv.Lock()
defer recv.Unlock()
if recv.state == stateHTTPReceiverActive {
// TODO(igm) check err, possibly act as if interrupted
n, _ := recv.frameWriter.write(recv.rw, value)
recv.currentResponseSize += uint32(n)
if recv.currentResponseSize >= recv.maxResponseSize {
recv.state = stateHTTPReceiverClosed
close(recv.doneCh)
} else {
recv.rw.(http.Flusher).Flush()
}
}
}
func (recv *httpReceiver) doneNotify() <-chan struct{} { return recv.doneCh }
func (recv *httpReceiver) interruptedNotify() <-chan struct{} { return recv.interruptCh }
func (recv *httpReceiver) close() {
recv.Lock()
defer recv.Unlock()
if recv.state < stateHTTPReceiverClosed {
recv.state = stateHTTPReceiverClosed
close(recv.doneCh)
}
}
func (recv *httpReceiver) canSend() bool {
recv.Lock()
defer recv.Unlock()
return recv.state != stateHTTPReceiverClosed
}

42
vendor/gopkg.in/igm/sockjs-go.v2/sockjs/iframe.go generated vendored Normal file
View File

@@ -0,0 +1,42 @@
package sockjs
import (
"crypto/md5"
"fmt"
"net/http"
"text/template"
)
var tmpl = template.Must(template.New("iframe").Parse(iframeBody))
func (h *handler) iframe(rw http.ResponseWriter, req *http.Request) {
etagReq := req.Header.Get("If-None-Match")
hash := md5.New()
hash.Write([]byte(iframeBody))
etag := fmt.Sprintf("%x", hash.Sum(nil))
if etag == etagReq {
rw.WriteHeader(http.StatusNotModified)
return
}
rw.Header().Set("Content-Type", "text/html; charset=UTF-8")
rw.Header().Add("ETag", etag)
tmpl.Execute(rw, h.options.SockJSURL)
}
var iframeBody = `<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script>
document.domain = document.domain;
_sockjs_onload = function(){SockJS.bootstrap_iframe();};
</script>
<script src="{{.}}"></script>
</head>
<body>
<h2>Don't panic!</h2>
<p>This is a SockJS hidden iframe. It's used for cross domain magic.</p>
</body>
</html>`

77
vendor/gopkg.in/igm/sockjs-go.v2/sockjs/jsonp.go generated vendored Normal file
View File

@@ -0,0 +1,77 @@
package sockjs
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
func (h *handler) jsonp(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("content-type", "application/javascript; charset=UTF-8")
req.ParseForm()
callback := req.Form.Get("c")
if callback == "" {
http.Error(rw, `"callback" parameter required`, http.StatusInternalServerError)
return
}
rw.WriteHeader(http.StatusOK)
rw.(http.Flusher).Flush()
sess, _ := h.sessionByRequest(req)
recv := newHTTPReceiver(rw, 1, &jsonpFrameWriter{callback})
if err := sess.attachReceiver(recv); err != nil {
recv.sendFrame(cFrame)
recv.close()
return
}
select {
case <-recv.doneNotify():
case <-recv.interruptedNotify():
}
}
func (h *handler) jsonpSend(rw http.ResponseWriter, req *http.Request) {
req.ParseForm()
var data io.Reader
data = req.Body
formReader := strings.NewReader(req.PostFormValue("d"))
if formReader.Len() != 0 {
data = formReader
}
if data == nil {
http.Error(rw, "Payload expected.", http.StatusInternalServerError)
return
}
var messages []string
err := json.NewDecoder(data).Decode(&messages)
if err == io.EOF {
http.Error(rw, "Payload expected.", http.StatusInternalServerError)
return
}
if err != nil {
http.Error(rw, "Broken JSON encoding.", http.StatusInternalServerError)
return
}
sessionID, _ := h.parseSessionID(req.URL)
h.sessionsMux.Lock()
defer h.sessionsMux.Unlock()
if sess, ok := h.sessions[sessionID]; !ok {
http.NotFound(rw, req)
} else {
_ = sess.accept(messages...) // TODO(igm) reponse with http.StatusInternalServerError in case of err?
rw.Header().Set("content-type", "text/plain; charset=UTF-8")
rw.Write([]byte("ok"))
}
}
type jsonpFrameWriter struct {
callback string
}
func (j *jsonpFrameWriter) write(w io.Writer, frame string) (int, error) {
return fmt.Fprintf(w, "%s(%s);\r\n", j.callback, quote(frame))
}

36
vendor/gopkg.in/igm/sockjs-go.v2/sockjs/mapping.go generated vendored Normal file
View File

@@ -0,0 +1,36 @@
package sockjs
import (
"net/http"
"regexp"
)
type mapping struct {
method string
path *regexp.Regexp
chain []http.HandlerFunc
}
func newMapping(method string, re string, handlers ...http.HandlerFunc) *mapping {
return &mapping{method, regexp.MustCompile(re), handlers}
}
type matchType uint32
const (
fullMatch matchType = iota
pathMatch
noMatch
)
// matches checks if given req.URL is a match with a mapping. Match can be either full, partial (http method mismatch) or no match.
func (m *mapping) matches(req *http.Request) (match matchType, method string) {
if !m.path.MatchString(req.URL.Path) {
match, method = noMatch, ""
} else if m.method != req.Method {
match, method = pathMatch, m.method
} else {
match, method = fullMatch, m.method
}
return
}

114
vendor/gopkg.in/igm/sockjs-go.v2/sockjs/options.go generated vendored Normal file
View File

@@ -0,0 +1,114 @@
package sockjs
import (
"encoding/json"
"fmt"
"math/rand"
"net/http"
"sync"
"time"
)
var (
entropy *rand.Rand
entropyMutex sync.Mutex
)
func init() {
entropy = rand.New(rand.NewSource(time.Now().UnixNano()))
}
// Options type is used for defining various sockjs options
type Options struct {
// Transports which don't support cross-domain communication natively ('eventsource' to name one) use an iframe trick.
// A simple page is served from the SockJS server (using its foreign domain) and is placed in an invisible iframe.
// Code run from this iframe doesn't need to worry about cross-domain issues, as it's being run from domain local to the SockJS server.
// This iframe also does need to load SockJS javascript client library, and this option lets you specify its url (if you're unsure,
// point it to the latest minified SockJS client release, this is the default). You must explicitly specify this url on the server
// side for security reasons - we don't want the possibility of running any foreign javascript within the SockJS domain (aka cross site scripting attack).
// Also, sockjs javascript library is probably already cached by the browser - it makes sense to reuse the sockjs url you're using in normally.
SockJSURL string
// Most streaming transports save responses on the client side and don't free memory used by delivered messages.
// Such transports need to be garbage-collected once in a while. `response_limit` sets a minimum number of bytes that can be send
// over a single http streaming request before it will be closed. After that client needs to open new request.
// Setting this value to one effectively disables streaming and will make streaming transports to behave like polling transports.
// The default value is 128K.
ResponseLimit uint32
// Some load balancers don't support websockets. This option can be used to disable websockets support by the server. By default websockets are enabled.
Websocket bool
// In order to keep proxies and load balancers from closing long running http requests we need to pretend that the connection is active
// and send a heartbeat packet once in a while. This setting controls how often this is done.
// By default a heartbeat packet is sent every 25 seconds.
HeartbeatDelay time.Duration
// The server closes a session when a client receiving connection have not been seen for a while.
// This delay is configured by this setting.
// By default the session is closed when a receiving connection wasn't seen for 5 seconds.
DisconnectDelay time.Duration
// Some hosting providers enable sticky sessions only to requests that have JSessionID cookie set.
// This setting controls if the server should set this cookie to a dummy value.
// By default setting JSessionID cookie is disabled. More sophisticated behaviour can be achieved by supplying a function.
JSessionID func(http.ResponseWriter, *http.Request)
}
// DefaultOptions is a convenient set of options to be used for sockjs
var DefaultOptions = Options{
Websocket: true,
JSessionID: nil,
SockJSURL: "http://cdn.sockjs.org/sockjs-0.3.min.js",
HeartbeatDelay: 25 * time.Second,
DisconnectDelay: 5 * time.Second,
ResponseLimit: 128 * 1024,
}
type info struct {
Websocket bool `json:"websocket"`
CookieNeeded bool `json:"cookie_needed"`
Origins []string `json:"origins"`
Entropy int32 `json:"entropy"`
}
func (options *Options) info(rw http.ResponseWriter, req *http.Request) {
switch req.Method {
case "GET":
rw.Header().Set("Content-Type", "application/json; charset=UTF-8")
json.NewEncoder(rw).Encode(info{
Websocket: options.Websocket,
CookieNeeded: options.JSessionID != nil,
Origins: []string{"*:*"},
Entropy: generateEntropy(),
})
case "OPTIONS":
rw.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET")
rw.Header().Set("Access-Control-Max-Age", fmt.Sprintf("%d", 365*24*60*60))
rw.WriteHeader(http.StatusNoContent) // 204
default:
http.NotFound(rw, req)
}
}
// DefaultJSessionID is a default behaviour function to be used in options for JSessionID if JSESSIONID is needed
func DefaultJSessionID(rw http.ResponseWriter, req *http.Request) {
cookie, err := req.Cookie("JSESSIONID")
if err == http.ErrNoCookie {
cookie = &http.Cookie{
Name: "JSESSIONID",
Value: "dummy",
}
}
cookie.Path = "/"
header := rw.Header()
header.Add("Set-Cookie", cookie.String())
}
func (options *Options) cookie(rw http.ResponseWriter, req *http.Request) {
if options.JSessionID != nil { // cookie is needed
options.JSessionID(rw, req)
}
}
func generateEntropy() int32 {
entropyMutex.Lock()
entropy := entropy.Int31()
entropyMutex.Unlock()
return entropy
}

219
vendor/gopkg.in/igm/sockjs-go.v2/sockjs/session.go generated vendored Normal file
View File

@@ -0,0 +1,219 @@
package sockjs
import (
"encoding/gob"
"errors"
"io"
"sync"
"time"
)
type sessionState uint32
const (
// brand new session, need to send "h" to receiver
sessionOpening sessionState = iota
// active session
sessionActive
// session being closed, sending "closeFrame" to receivers
sessionClosing
// closed session, no activity at all, should be removed from handler completely and not reused
sessionClosed
)
var (
// ErrSessionNotOpen error is used to denote session not in open state.
// Recv() and Send() operations are not suppored if session is closed.
ErrSessionNotOpen = errors.New("sockjs: session not in open state")
errSessionReceiverAttached = errors.New("sockjs: another receiver already attached")
)
type session struct {
sync.Mutex
id string
state sessionState
// protocol dependent receiver (xhr, eventsource, ...)
recv receiver
// messages to be sent to client
sendBuffer []string
// messages received from client to be consumed by application
// receivedBuffer chan string
msgReader *io.PipeReader
msgWriter *io.PipeWriter
msgEncoder *gob.Encoder
msgDecoder *gob.Decoder
// closeFrame to send after session is closed
closeFrame string
// internal timer used to handle session expiration if no receiver is attached, or heartbeats if recevier is attached
sessionTimeoutInterval time.Duration
heartbeatInterval time.Duration
timer *time.Timer
// once the session timeouts this channel also closes
closeCh chan struct{}
}
type receiver interface {
// sendBulk send multiple data messages in frame frame in format: a["msg 1", "msg 2", ....]
sendBulk(...string)
// sendFrame sends given frame over the wire (with possible chunking depending on receiver)
sendFrame(string)
// close closes the receiver in a "done" way (idempotent)
close()
canSend() bool
// done notification channel gets closed whenever receiver ends
doneNotify() <-chan struct{}
// interrupted channel gets closed whenever receiver is interrupted (i.e. http connection drops,...)
interruptedNotify() <-chan struct{}
}
// Session is a central component that handles receiving and sending frames. It maintains internal state
func newSession(sessionID string, sessionTimeoutInterval, heartbeatInterval time.Duration) *session {
r, w := io.Pipe()
s := &session{
id: sessionID,
msgReader: r,
msgWriter: w,
msgEncoder: gob.NewEncoder(w),
msgDecoder: gob.NewDecoder(r),
sessionTimeoutInterval: sessionTimeoutInterval,
heartbeatInterval: heartbeatInterval,
closeCh: make(chan struct{})}
s.Lock() // "go test -race" complains if ommited, not sure why as no race can happen here
s.timer = time.AfterFunc(sessionTimeoutInterval, s.close)
s.Unlock()
return s
}
func (s *session) sendMessage(msg string) error {
s.Lock()
defer s.Unlock()
if s.state > sessionActive {
return ErrSessionNotOpen
}
s.sendBuffer = append(s.sendBuffer, msg)
if s.recv != nil && s.recv.canSend() {
s.recv.sendBulk(s.sendBuffer...)
s.sendBuffer = nil
}
return nil
}
func (s *session) attachReceiver(recv receiver) error {
s.Lock()
defer s.Unlock()
if s.recv != nil {
return errSessionReceiverAttached
}
s.recv = recv
go func(r receiver) {
select {
case <-r.doneNotify():
s.detachReceiver()
case <-r.interruptedNotify():
s.detachReceiver()
s.close()
}
}(recv)
if s.state == sessionClosing {
s.recv.sendFrame(s.closeFrame)
s.recv.close()
return nil
}
if s.state == sessionOpening {
s.recv.sendFrame("o")
s.state = sessionActive
}
s.recv.sendBulk(s.sendBuffer...)
s.sendBuffer = nil
s.timer.Stop()
if s.heartbeatInterval > 0 {
s.timer = time.AfterFunc(s.heartbeatInterval, s.heartbeat)
}
return nil
}
func (s *session) detachReceiver() {
s.Lock()
defer s.Unlock()
s.timer.Stop()
s.timer = time.AfterFunc(s.sessionTimeoutInterval, s.close)
s.recv = nil
}
func (s *session) heartbeat() {
s.Lock()
defer s.Unlock()
if s.recv != nil { // timer could have fired between Lock and timer.Stop in detachReceiver
s.recv.sendFrame("h")
s.timer = time.AfterFunc(s.heartbeatInterval, s.heartbeat)
}
}
func (s *session) accept(messages ...string) error {
for _, msg := range messages {
if err := s.msgEncoder.Encode(msg); err != nil {
return err
}
}
return nil
}
// idempotent operation
func (s *session) closing() {
s.Lock()
defer s.Unlock()
if s.state < sessionClosing {
s.msgReader.Close()
s.msgWriter.Close()
s.state = sessionClosing
if s.recv != nil {
s.recv.sendFrame(s.closeFrame)
s.recv.close()
}
}
}
// idempotent operation
func (s *session) close() {
s.closing()
s.Lock()
defer s.Unlock()
if s.state < sessionClosed {
s.state = sessionClosed
s.timer.Stop()
close(s.closeCh)
}
}
func (s *session) closedNotify() <-chan struct{} { return s.closeCh }
// Conn interface implementation
func (s *session) Close(status uint32, reason string) error {
s.Lock()
if s.state < sessionClosing {
s.closeFrame = closeFrame(status, reason)
s.Unlock()
s.closing()
return nil
}
s.Unlock()
return ErrSessionNotOpen
}
func (s *session) Recv() (string, error) {
var msg string
err := s.msgDecoder.Decode(&msg)
if err == io.ErrClosedPipe {
err = ErrSessionNotOpen
}
return msg, err
}
func (s *session) Send(msg string) error {
return s.sendMessage(msg)
}
func (s *session) ID() string { return s.id }

13
vendor/gopkg.in/igm/sockjs-go.v2/sockjs/sockjs.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
package sockjs
// Session represents a connection between server and client.
type Session interface {
// Id returns a session id
ID() string
// Recv reads one text frame from session
Recv() (string, error)
// Send sends one text frame to session
Send(string) error
// Close closes the session with provided code and reason.
Close(status uint32, reason string) error
}

16
vendor/gopkg.in/igm/sockjs-go.v2/sockjs/utils.go generated vendored Normal file
View File

@@ -0,0 +1,16 @@
package sockjs
import "encoding/json"
func quote(in string) string {
quoted, _ := json.Marshal(in)
return string(quoted)
}
func transform(values []string, transformFn func(string) string) []string {
ret := make([]string, len(values))
for i, msg := range values {
ret[i] = transformFn(msg)
}
return ret
}

47
vendor/gopkg.in/igm/sockjs-go.v2/sockjs/web.go generated vendored Normal file
View File

@@ -0,0 +1,47 @@
package sockjs
import (
"fmt"
"net/http"
"time"
)
func xhrCors(rw http.ResponseWriter, req *http.Request) {
header := rw.Header()
origin := req.Header.Get("origin")
if origin == "" || origin == "null" {
origin = "*"
}
header.Set("Access-Control-Allow-Origin", origin)
if allowHeaders := req.Header.Get("Access-Control-Request-Headers"); allowHeaders != "" && allowHeaders != "null" {
header.Add("Access-Control-Allow-Headers", allowHeaders)
}
header.Add("Access-Control-Allow-Credentials", "true")
}
func xhrOptions(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Access-Control-Allow-Methods", "OPTIONS, POST")
rw.WriteHeader(http.StatusNoContent) // 204
}
func cacheFor(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", 365*24*60*60))
rw.Header().Set("Expires", time.Now().AddDate(1, 0, 0).Format(time.RFC1123))
rw.Header().Set("Access-Control-Max-Age", fmt.Sprintf("%d", 365*24*60*60))
}
func noCache(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
}
func welcomeHandler(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("content-type", "text/plain;charset=UTF-8")
fmt.Fprintf(rw, "Welcome to SockJS!\n")
}
func httpError(w http.ResponseWriter, error string, code int) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(code)
fmt.Fprintf(w, error)
}

97
vendor/gopkg.in/igm/sockjs-go.v2/sockjs/websocket.go generated vendored Normal file
View File

@@ -0,0 +1,97 @@
package sockjs
import (
"fmt"
"net/http"
"strings"
"github.com/gorilla/websocket"
)
// WebSocketReadBufSize is a parameter that is used for WebSocket Upgrader.
// https://github.com/gorilla/websocket/blob/master/server.go#L230
var WebSocketReadBufSize = 4096
// WebSocketWriteBufSize is a parameter that is used for WebSocket Upgrader
// https://github.com/gorilla/websocket/blob/master/server.go#L230
var WebSocketWriteBufSize = 4096
func (h *handler) sockjsWebsocket(rw http.ResponseWriter, req *http.Request) {
conn, err := websocket.Upgrade(rw, req, nil, WebSocketReadBufSize, WebSocketWriteBufSize)
if _, ok := err.(websocket.HandshakeError); ok {
http.Error(rw, `Can "Upgrade" only to "WebSocket".`, http.StatusBadRequest)
return
} else if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
return
}
sessID, _ := h.parseSessionID(req.URL)
sess := newSession(sessID, h.options.DisconnectDelay, h.options.HeartbeatDelay)
if h.handlerFunc != nil {
go h.handlerFunc(sess)
}
receiver := newWsReceiver(conn)
sess.attachReceiver(receiver)
readCloseCh := make(chan struct{})
go func() {
var d []string
for {
err := conn.ReadJSON(&d)
if err != nil {
close(readCloseCh)
return
}
sess.accept(d...)
}
}()
select {
case <-readCloseCh:
case <-receiver.doneNotify():
}
sess.close()
conn.Close()
}
type wsReceiver struct {
conn *websocket.Conn
closeCh chan struct{}
}
func newWsReceiver(conn *websocket.Conn) *wsReceiver {
return &wsReceiver{
conn: conn,
closeCh: make(chan struct{}),
}
}
func (w *wsReceiver) sendBulk(messages ...string) {
if len(messages) > 0 {
w.sendFrame(fmt.Sprintf("a[%s]", strings.Join(transform(messages, quote), ",")))
}
}
func (w *wsReceiver) sendFrame(frame string) {
if err := w.conn.WriteMessage(websocket.TextMessage, []byte(frame)); err != nil {
w.close()
}
}
func (w *wsReceiver) close() {
select {
case <-w.closeCh: // already closed
default:
close(w.closeCh)
}
}
func (w *wsReceiver) canSend() bool {
select {
case <-w.closeCh: // already closed
return false
default:
return true
}
}
func (w *wsReceiver) doneNotify() <-chan struct{} { return w.closeCh }
func (w *wsReceiver) interruptedNotify() <-chan struct{} { return nil }

88
vendor/gopkg.in/igm/sockjs-go.v2/sockjs/xhr.go generated vendored Normal file
View File

@@ -0,0 +1,88 @@
package sockjs
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
var (
cFrame = closeFrame(2010, "Another connection still open")
xhrStreamingPrelude = strings.Repeat("h", 2048)
)
func (h *handler) xhrSend(rw http.ResponseWriter, req *http.Request) {
if req.Body == nil {
httpError(rw, "Payload expected.", http.StatusInternalServerError)
return
}
var messages []string
err := json.NewDecoder(req.Body).Decode(&messages)
if err == io.EOF {
httpError(rw, "Payload expected.", http.StatusInternalServerError)
return
}
if _, ok := err.(*json.SyntaxError); ok || err == io.ErrUnexpectedEOF {
httpError(rw, "Broken JSON encoding.", http.StatusInternalServerError)
return
}
sessionID, err := h.parseSessionID(req.URL)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
h.sessionsMux.Lock()
defer h.sessionsMux.Unlock()
if sess, ok := h.sessions[sessionID]; !ok {
http.NotFound(rw, req)
} else {
_ = sess.accept(messages...) // TODO(igm) reponse with SISE in case of err?
rw.Header().Set("content-type", "text/plain; charset=UTF-8") // Ignored by net/http (but protocol test complains), see https://code.google.com/p/go/source/detail?r=902dc062bff8
rw.WriteHeader(http.StatusNoContent)
}
}
type xhrFrameWriter struct{}
func (*xhrFrameWriter) write(w io.Writer, frame string) (int, error) {
return fmt.Fprintf(w, "%s\n", frame)
}
func (h *handler) xhrPoll(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("content-type", "application/javascript; charset=UTF-8")
sess, _ := h.sessionByRequest(req) // TODO(igm) add err handling, although err should not happen as handler should not pass req in that case
receiver := newHTTPReceiver(rw, 1, new(xhrFrameWriter))
if err := sess.attachReceiver(receiver); err != nil {
receiver.sendFrame(cFrame)
receiver.close()
return
}
select {
case <-receiver.doneNotify():
case <-receiver.interruptedNotify():
}
}
func (h *handler) xhrStreaming(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("content-type", "application/javascript; charset=UTF-8")
fmt.Fprintf(rw, "%s\n", xhrStreamingPrelude)
rw.(http.Flusher).Flush()
sess, _ := h.sessionByRequest(req)
receiver := newHTTPReceiver(rw, h.options.ResponseLimit, new(xhrFrameWriter))
if err := sess.attachReceiver(receiver); err != nil {
receiver.sendFrame(cFrame)
receiver.close()
return
}
select {
case <-receiver.doneNotify():
case <-receiver.interruptedNotify():
}
}