1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
|
package sockjs
import (
"net/http"
"net/url"
"regexp"
"strings"
"sync"
)
type Handler struct {
prefix string
options Options
handlerFunc func(Session)
mappings []*mapping
sessionsMux sync.Mutex
sessions map[string]*session
}
const sessionPrefix = "^/([^/.]+)/([^/.]+)"
var sessionRegExp = regexp.MustCompile(sessionPrefix)
// 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, handlerFunc func(Session)) *Handler {
if handlerFunc == nil {
handlerFunc = func(s Session) {}
}
h := &Handler{
prefix: prefix,
options: opts,
handlerFunc: handlerFunc,
sessions: make(map[string]*session),
}
xhrCors := xhrCorsFactory(opts)
h.mappings = []*mapping{
newMapping("GET", "^[/]?$", welcomeHandler),
newMapping("OPTIONS", "^/info$", opts.cookie, xhrCors, cacheFor, opts.info),
newMapping("GET", "^/info$", opts.cookie, 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", "^/iframe[0-9-.a-z_]*.html$", cacheFor, h.iframe),
}
if opts.Websocket {
h.mappings = append(h.mappings, newMapping("GET", sessionPrefix+"/websocket$", h.sockjsWebsocket))
}
if opts.RawWebsocket {
h.mappings = append(h.mappings, newMapping("GET", "^/websocket$", h.rawWebsocket))
}
return h
}
func (h *Handler) Prefix() string { return h.prefix }
func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// iterate over mappings
http.StripPrefix(h.prefix, http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
var 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)
})).ServeHTTP(rw, req)
}
func (h *Handler) parseSessionID(url *url.URL) (string, error) {
matches := sessionRegExp.FindStringSubmatch(url.Path)
if len(matches) == 3 {
return matches[2], nil
}
return "", errSessionParse
}
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(req, sessionID, h.options.DisconnectDelay, h.options.HeartbeatDelay)
h.sessions[sessionID] = sess
go func() {
<-sess.closeCh
h.sessionsMux.Lock()
delete(h.sessions, sessionID)
h.sessionsMux.Unlock()
}()
}
sess.setCurrentRequest(req)
return sess, nil
}
|