-
Notifications
You must be signed in to change notification settings - Fork 3
/
handler.go
133 lines (121 loc) · 4.15 KB
/
handler.go
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
124
125
126
127
128
129
130
131
132
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(req, 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
}