74 lines
2.2 KiB
Go
74 lines
2.2 KiB
Go
package qq_obv11
|
|
|
|
// This file implements the server role of the OneBot v11 WebSocket channel:
|
|
// the adapter listens on a TCP port and accepts inbound OneBot (反向) client
|
|
// connections, validates any configured access token, and upgrades each HTTP
|
|
// request to a WebSocket session.
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// connectServer starts the HTTP listener in server mode. It returns once the
|
|
// listener is bound; the session becomes live when a OneBot client connects.
|
|
func (a *Adapter) connectServer(ctx context.Context) error {
|
|
if a.cfg.ListenAddr == "" {
|
|
return errors.New("qq-obv11: server mode requires listenAddr")
|
|
}
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/", a.wsHandler)
|
|
|
|
ln, err := net.Listen("tcp", a.cfg.ListenAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("qq-obv11: listen %s: %w", a.cfg.ListenAddr, err)
|
|
}
|
|
|
|
a.mu.Lock()
|
|
a.ln = ln
|
|
a.srv = &http.Server{Handler: mux}
|
|
a.mu.Unlock()
|
|
|
|
go func() {
|
|
if serveErr := a.srv.Serve(ln); serveErr != nil && serveErr != http.ErrServerClosed {
|
|
a.log.Warn("websocket server stopped", "err", serveErr)
|
|
}
|
|
}()
|
|
a.log.Info("websocket server listening (server mode)", "addr", ln.Addr().String())
|
|
return nil
|
|
}
|
|
|
|
// wsHandler is the HTTP handler that upgrades one inbound OneBot client
|
|
// connection to a WebSocket session.
|
|
func (a *Adapter) wsHandler(w http.ResponseWriter, r *http.Request) {
|
|
if a.cfg.AccessToken != "" && !a.checkAuth(r) {
|
|
http.Error(w, "Forbidden: invalid access_token", http.StatusForbidden)
|
|
return
|
|
}
|
|
conn, err := upgradeWS(w, r)
|
|
if err != nil {
|
|
a.log.Warn("websocket upgrade failed", "err", err)
|
|
return
|
|
}
|
|
a.setConn(conn)
|
|
a.log.Info("onebot client connected (server mode)", "remote", r.RemoteAddr)
|
|
}
|
|
|
|
// checkAuth validates the access token carried by an upgrade request. OneBot
|
|
// implementations pass it either as an access_token query parameter or as an
|
|
// Authorization: Bearer header.
|
|
func (a *Adapter) checkAuth(r *http.Request) bool {
|
|
if got := r.URL.Query().Get("access_token"); got != "" {
|
|
return got == a.cfg.AccessToken
|
|
}
|
|
auth := r.Header.Get("Authorization")
|
|
if strings.HasPrefix(auth, "Bearer ") {
|
|
return strings.TrimPrefix(auth, "Bearer ") == a.cfg.AccessToken
|
|
}
|
|
return false
|
|
}
|