多租户更改

This commit is contained in:
richjohnrambo
2026-08-11 15:59:57 +08:00
parent 6c800de67d
commit c2076721fc
453 changed files with 102349 additions and 47 deletions
+205
View File
@@ -0,0 +1,205 @@
/******************************************************************************
*
* Description :
*
* Handler of long polling clients. See also hdl_websock.go for web sockets and
* hdl_grpc.go for gRPC
*
*****************************************************************************/
package main
import (
"encoding/json"
"errors"
"io"
"net/http"
"time"
"github.com/tinode/chat/server/logs"
)
func (sess *Session) sendMessageLp(wrt http.ResponseWriter, msg any) bool {
if len(sess.send) > sendQueueLimit {
logs.Err.Println("longPoll: outbound queue limit exceeded", sess.sid)
return false
}
statsInc("OutgoingMessagesLongpollTotal", 1)
if err := lpWrite(wrt, msg); err != nil {
logs.Err.Println("longPoll: writeOnce failed", sess.sid, err)
return false
}
return true
}
func (sess *Session) writeOnce(wrt http.ResponseWriter, req *http.Request) {
for {
select {
case msg, ok := <-sess.send:
if !ok {
return
}
switch v := msg.(type) {
case *ServerComMessage: // single unserialized message
w := sess.serializeAndUpdateStats(v)
if !sess.sendMessageLp(wrt, w) {
return
}
default: // serialized message
if !sess.sendMessageLp(wrt, v) {
return
}
}
return
case <-sess.bkgTimer.C:
if sess.background {
sess.background = false
sess.onBackgroundTimer()
}
case msg := <-sess.stop:
// Request to close the session. Make it unavailable.
globals.sessionStore.Delete(sess)
// Don't care if lpWrite fails.
if msg != nil {
lpWrite(wrt, msg)
}
return
case topic := <-sess.detach:
// Request to detach the session from a topic.
sess.delSub(topic)
// No 'return' statement here: continue waiting
case <-time.After(pingPeriod):
// just write an empty packet on timeout
if _, err := wrt.Write([]byte{}); err != nil {
logs.Err.Println("longPoll: writeOnce: timout", sess.sid, err)
}
return
case <-req.Context().Done():
// HTTP request canceled or connection lost.
return
}
}
}
func lpWrite(wrt http.ResponseWriter, msg any) error {
// This will panic if msg is not []byte. This is intentional.
wrt.Write(msg.([]byte))
return nil
}
func (sess *Session) readOnce(wrt http.ResponseWriter, req *http.Request) (int, error) {
if req.ContentLength > globals.maxMessageSize {
return http.StatusExpectationFailed, errors.New("request too large")
}
req.Body = http.MaxBytesReader(wrt, req.Body, globals.maxMessageSize)
raw, err := io.ReadAll(req.Body)
if err == nil {
// Locking-unlocking is needed because the client may issue multiple requests in parallel.
// Should not affect performance
sess.lock.Lock()
statsInc("IncomingMessagesLongpollTotal", 1)
sess.dispatchRaw(raw)
sess.lock.Unlock()
return 0, nil
}
return 0, err
}
// serveLongPoll handles long poll connections when WebSocket is not available
// Connection could be without sid or with sid:
// - if sid is empty, create session, expect a login in the same request, respond and close
// - if sid is not empty and there is an initialized session, payload is optional
// - if no payload, perform long poll
// - if payload exists, process it and close
// - if sid is not empty but there is no session, report an error
func serveLongPoll(wrt http.ResponseWriter, req *http.Request) {
now := time.Now().UTC().Round(time.Millisecond)
// Use the lowest common denominator - this is a legacy handler after all (otherwise would use application/json)
wrt.Header().Set("Content-Type", "text/plain")
if globals.tlsStrictMaxAge != "" {
wrt.Header().Set("Strict-Transport-Security", "max-age"+globals.tlsStrictMaxAge)
}
enc := json.NewEncoder(wrt)
if isValid, _ := checkAPIKey(getAPIKey(req)); !isValid {
wrt.WriteHeader(http.StatusForbidden)
enc.Encode(ErrAPIKeyRequired(now))
return
}
// TODO(gene): should it be configurable?
// Currently any domain is allowed to get data from the chat server
wrt.Header().Set("Access-Control-Allow-Origin", "*")
// Ensure the response is not cached
if req.ProtoAtLeast(1, 1) {
wrt.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") // HTTP 1.1
} else {
wrt.Header().Set("Pragma", "no-cache") // HTTP 1.0
}
wrt.Header().Set("Expires", "0") // Proxies
// TODO(gene): respond differently to valious HTTP methods
// Get session id
sid := req.FormValue("sid")
var sess *Session
if sid == "" {
// New session
var count int
sess, count = globals.sessionStore.NewSession(wrt, "")
sess.remoteAddr = getRemoteAddr(req)
logs.Info.Println("longPoll: session started", sess.sid, sess.remoteAddr, count)
wrt.WriteHeader(http.StatusCreated)
pkt := NoErrCreated(req.FormValue("id"), "", now)
pkt.Ctrl.Params = map[string]string{
"sid": sess.sid,
}
enc.Encode(pkt)
return
}
// Existing session
sess = globals.sessionStore.Get(sid)
if sess == nil {
logs.Warn.Println("longPoll: invalid or expired session id", sid)
wrt.WriteHeader(http.StatusForbidden)
enc.Encode(ErrSessionNotFound(now))
return
}
if addr := getRemoteAddr(req); sess.remoteAddr != addr {
sess.remoteAddr = addr
logs.Warn.Println("longPoll: remote address changed", sid, addr)
}
if req.ContentLength != 0 {
// Read payload and send it for processing.
if code, err := sess.readOnce(wrt, req); err != nil {
logs.Warn.Println("longPoll: readOnce failed", sess.sid, err)
// Failed to read request, report an error, if possible
if code != 0 {
wrt.WriteHeader(code)
} else {
wrt.WriteHeader(http.StatusBadRequest)
}
enc.Encode(ErrMalformed(req.FormValue("id"), "", now))
}
return
}
sess.writeOnce(wrt, req)
}