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

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

@@ -0,0 +1,26 @@
Copyright (c) 2012-2014, sockjs-go authors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

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():
}
}