mirror of
https://github.com/ossrs/srs.git
synced 2025-02-15 04:42:04 +00:00
01. Support GB config as StreamCaster. 02. Support disable GB by --gb28181=off. 03. Add utests for SIP examples. 04. Wireshark plugin to decode TCP/9000 as rtp.rfc4571 05. Support MPEGPS program stream codec. 06. Add utest for PS stream codec. 07. Decode MPEGPS packet stream. 08. Carry RTP and PS packet as helper in PS message. 09. Support recover from error mode. 10. Support process by a pack of PS/TS messages. 11. Add statistic for recovered and msgs dropped. 12. Recover from err position fastly. 13. Define state machine for GB session. 14. Bind context to GB session. 15. Re-invite when media disconnected. 16. Update GitHub actions with GB28181. 17. Support parse CANDIDATE by env or pip. 18. Support mux GB28181 to RTMP. 19. Support regression test by srs-bench.
504 lines
13 KiB
Go
504 lines
13 KiB
Go
package ws
|
||
|
||
import (
|
||
"bufio"
|
||
"bytes"
|
||
"io"
|
||
"net/http"
|
||
"net/textproto"
|
||
"net/url"
|
||
"strconv"
|
||
|
||
"github.com/gobwas/httphead"
|
||
)
|
||
|
||
const (
|
||
crlf = "\r\n"
|
||
colonAndSpace = ": "
|
||
commaAndSpace = ", "
|
||
)
|
||
|
||
const (
|
||
textHeadUpgrade = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n"
|
||
)
|
||
|
||
var (
|
||
textHeadBadRequest = statusText(http.StatusBadRequest)
|
||
textHeadInternalServerError = statusText(http.StatusInternalServerError)
|
||
textHeadUpgradeRequired = statusText(http.StatusUpgradeRequired)
|
||
|
||
textTailErrHandshakeBadProtocol = errorText(ErrHandshakeBadProtocol)
|
||
textTailErrHandshakeBadMethod = errorText(ErrHandshakeBadMethod)
|
||
textTailErrHandshakeBadHost = errorText(ErrHandshakeBadHost)
|
||
textTailErrHandshakeBadUpgrade = errorText(ErrHandshakeBadUpgrade)
|
||
textTailErrHandshakeBadConnection = errorText(ErrHandshakeBadConnection)
|
||
textTailErrHandshakeBadSecAccept = errorText(ErrHandshakeBadSecAccept)
|
||
textTailErrHandshakeBadSecKey = errorText(ErrHandshakeBadSecKey)
|
||
textTailErrHandshakeBadSecVersion = errorText(ErrHandshakeBadSecVersion)
|
||
textTailErrUpgradeRequired = errorText(ErrHandshakeUpgradeRequired)
|
||
)
|
||
|
||
var (
|
||
headerHost = "Host"
|
||
headerUpgrade = "Upgrade"
|
||
headerConnection = "Connection"
|
||
headerSecVersion = "Sec-WebSocket-Version"
|
||
headerSecProtocol = "Sec-WebSocket-Protocol"
|
||
headerSecExtensions = "Sec-WebSocket-Extensions"
|
||
headerSecKey = "Sec-WebSocket-Key"
|
||
headerSecAccept = "Sec-WebSocket-Accept"
|
||
|
||
headerHostCanonical = textproto.CanonicalMIMEHeaderKey(headerHost)
|
||
headerUpgradeCanonical = textproto.CanonicalMIMEHeaderKey(headerUpgrade)
|
||
headerConnectionCanonical = textproto.CanonicalMIMEHeaderKey(headerConnection)
|
||
headerSecVersionCanonical = textproto.CanonicalMIMEHeaderKey(headerSecVersion)
|
||
headerSecProtocolCanonical = textproto.CanonicalMIMEHeaderKey(headerSecProtocol)
|
||
headerSecExtensionsCanonical = textproto.CanonicalMIMEHeaderKey(headerSecExtensions)
|
||
headerSecKeyCanonical = textproto.CanonicalMIMEHeaderKey(headerSecKey)
|
||
headerSecAcceptCanonical = textproto.CanonicalMIMEHeaderKey(headerSecAccept)
|
||
)
|
||
|
||
var (
|
||
specHeaderValueUpgrade = []byte("websocket")
|
||
specHeaderValueConnection = []byte("Upgrade")
|
||
specHeaderValueConnectionLower = []byte("upgrade")
|
||
specHeaderValueSecVersion = []byte("13")
|
||
)
|
||
|
||
var (
|
||
httpVersion1_0 = []byte("HTTP/1.0")
|
||
httpVersion1_1 = []byte("HTTP/1.1")
|
||
httpVersionPrefix = []byte("HTTP/")
|
||
)
|
||
|
||
type httpRequestLine struct {
|
||
method, uri []byte
|
||
major, minor int
|
||
}
|
||
|
||
type httpResponseLine struct {
|
||
major, minor int
|
||
status int
|
||
reason []byte
|
||
}
|
||
|
||
// httpParseRequestLine parses http request line like "GET / HTTP/1.0".
|
||
func httpParseRequestLine(line []byte) (req httpRequestLine, err error) {
|
||
var proto []byte
|
||
req.method, req.uri, proto = bsplit3(line, ' ')
|
||
|
||
var ok bool
|
||
req.major, req.minor, ok = httpParseVersion(proto)
|
||
if !ok {
|
||
err = ErrMalformedRequest
|
||
return
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func httpParseResponseLine(line []byte) (resp httpResponseLine, err error) {
|
||
var (
|
||
proto []byte
|
||
status []byte
|
||
)
|
||
proto, status, resp.reason = bsplit3(line, ' ')
|
||
|
||
var ok bool
|
||
resp.major, resp.minor, ok = httpParseVersion(proto)
|
||
if !ok {
|
||
return resp, ErrMalformedResponse
|
||
}
|
||
|
||
var convErr error
|
||
resp.status, convErr = asciiToInt(status)
|
||
if convErr != nil {
|
||
return resp, ErrMalformedResponse
|
||
}
|
||
|
||
return resp, nil
|
||
}
|
||
|
||
// httpParseVersion parses major and minor version of HTTP protocol. It returns
|
||
// parsed values and true if parse is ok.
|
||
func httpParseVersion(bts []byte) (major, minor int, ok bool) {
|
||
switch {
|
||
case bytes.Equal(bts, httpVersion1_0):
|
||
return 1, 0, true
|
||
case bytes.Equal(bts, httpVersion1_1):
|
||
return 1, 1, true
|
||
case len(bts) < 8:
|
||
return
|
||
case !bytes.Equal(bts[:5], httpVersionPrefix):
|
||
return
|
||
}
|
||
|
||
bts = bts[5:]
|
||
|
||
dot := bytes.IndexByte(bts, '.')
|
||
if dot == -1 {
|
||
return
|
||
}
|
||
var err error
|
||
major, err = asciiToInt(bts[:dot])
|
||
if err != nil {
|
||
return
|
||
}
|
||
minor, err = asciiToInt(bts[dot+1:])
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
return major, minor, true
|
||
}
|
||
|
||
// httpParseHeaderLine parses HTTP header as key-value pair. It returns parsed
|
||
// values and true if parse is ok.
|
||
func httpParseHeaderLine(line []byte) (k, v []byte, ok bool) {
|
||
colon := bytes.IndexByte(line, ':')
|
||
if colon == -1 {
|
||
return
|
||
}
|
||
|
||
k = btrim(line[:colon])
|
||
// TODO(gobwas): maybe use just lower here?
|
||
canonicalizeHeaderKey(k)
|
||
|
||
v = btrim(line[colon+1:])
|
||
|
||
return k, v, true
|
||
}
|
||
|
||
// httpGetHeader is the same as textproto.MIMEHeader.Get, except the thing,
|
||
// that key is already canonical. This helps to increase performance.
|
||
func httpGetHeader(h http.Header, key string) string {
|
||
if h == nil {
|
||
return ""
|
||
}
|
||
v := h[key]
|
||
if len(v) == 0 {
|
||
return ""
|
||
}
|
||
return v[0]
|
||
}
|
||
|
||
// The request MAY include a header field with the name
|
||
// |Sec-WebSocket-Protocol|. If present, this value indicates one or more
|
||
// comma-separated subprotocol the client wishes to speak, ordered by
|
||
// preference. The elements that comprise this value MUST be non-empty strings
|
||
// with characters in the range U+0021 to U+007E not including separator
|
||
// characters as defined in [RFC2616] and MUST all be unique strings. The ABNF
|
||
// for the value of this header field is 1#token, where the definitions of
|
||
// constructs and rules are as given in [RFC2616].
|
||
func strSelectProtocol(h string, check func(string) bool) (ret string, ok bool) {
|
||
ok = httphead.ScanTokens(strToBytes(h), func(v []byte) bool {
|
||
if check(btsToString(v)) {
|
||
ret = string(v)
|
||
return false
|
||
}
|
||
return true
|
||
})
|
||
return
|
||
}
|
||
func btsSelectProtocol(h []byte, check func([]byte) bool) (ret string, ok bool) {
|
||
var selected []byte
|
||
ok = httphead.ScanTokens(h, func(v []byte) bool {
|
||
if check(v) {
|
||
selected = v
|
||
return false
|
||
}
|
||
return true
|
||
})
|
||
if ok && selected != nil {
|
||
return string(selected), true
|
||
}
|
||
return
|
||
}
|
||
|
||
func btsSelectExtensions(h []byte, selected []httphead.Option, check func(httphead.Option) bool) ([]httphead.Option, bool) {
|
||
s := httphead.OptionSelector{
|
||
Flags: httphead.SelectCopy,
|
||
Check: check,
|
||
}
|
||
return s.Select(h, selected)
|
||
}
|
||
|
||
func negotiateMaybe(in httphead.Option, dest []httphead.Option, f func(httphead.Option) (httphead.Option, error)) ([]httphead.Option, error) {
|
||
if in.Size() == 0 {
|
||
return dest, nil
|
||
}
|
||
opt, err := f(in)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if opt.Size() > 0 {
|
||
dest = append(dest, opt)
|
||
}
|
||
return dest, nil
|
||
}
|
||
|
||
func negotiateExtensions(
|
||
h []byte, dest []httphead.Option,
|
||
f func(httphead.Option) (httphead.Option, error),
|
||
) (_ []httphead.Option, err error) {
|
||
index := -1
|
||
var current httphead.Option
|
||
ok := httphead.ScanOptions(h, func(i int, name, attr, val []byte) httphead.Control {
|
||
if i != index {
|
||
dest, err = negotiateMaybe(current, dest, f)
|
||
if err != nil {
|
||
return httphead.ControlBreak
|
||
}
|
||
index = i
|
||
current = httphead.Option{Name: name}
|
||
}
|
||
if attr != nil {
|
||
current.Parameters.Set(attr, val)
|
||
}
|
||
return httphead.ControlContinue
|
||
})
|
||
if !ok {
|
||
return nil, ErrMalformedRequest
|
||
}
|
||
return negotiateMaybe(current, dest, f)
|
||
}
|
||
|
||
func httpWriteHeader(bw *bufio.Writer, key, value string) {
|
||
httpWriteHeaderKey(bw, key)
|
||
bw.WriteString(value)
|
||
bw.WriteString(crlf)
|
||
}
|
||
|
||
func httpWriteHeaderBts(bw *bufio.Writer, key string, value []byte) {
|
||
httpWriteHeaderKey(bw, key)
|
||
bw.Write(value)
|
||
bw.WriteString(crlf)
|
||
}
|
||
|
||
func httpWriteHeaderKey(bw *bufio.Writer, key string) {
|
||
bw.WriteString(key)
|
||
bw.WriteString(colonAndSpace)
|
||
}
|
||
|
||
func httpWriteUpgradeRequest(
|
||
bw *bufio.Writer,
|
||
u *url.URL,
|
||
nonce []byte,
|
||
protocols []string,
|
||
extensions []httphead.Option,
|
||
header HandshakeHeader,
|
||
) {
|
||
bw.WriteString("GET ")
|
||
bw.WriteString(u.RequestURI())
|
||
bw.WriteString(" HTTP/1.1\r\n")
|
||
|
||
httpWriteHeader(bw, headerHost, u.Host)
|
||
|
||
httpWriteHeaderBts(bw, headerUpgrade, specHeaderValueUpgrade)
|
||
httpWriteHeaderBts(bw, headerConnection, specHeaderValueConnection)
|
||
httpWriteHeaderBts(bw, headerSecVersion, specHeaderValueSecVersion)
|
||
|
||
// NOTE: write nonce bytes as a string to prevent heap allocation –
|
||
// WriteString() copy given string into its inner buffer, unlike Write()
|
||
// which may write p directly to the underlying io.Writer – which in turn
|
||
// will lead to p escape.
|
||
httpWriteHeader(bw, headerSecKey, btsToString(nonce))
|
||
|
||
if len(protocols) > 0 {
|
||
httpWriteHeaderKey(bw, headerSecProtocol)
|
||
for i, p := range protocols {
|
||
if i > 0 {
|
||
bw.WriteString(commaAndSpace)
|
||
}
|
||
bw.WriteString(p)
|
||
}
|
||
bw.WriteString(crlf)
|
||
}
|
||
|
||
if len(extensions) > 0 {
|
||
httpWriteHeaderKey(bw, headerSecExtensions)
|
||
httphead.WriteOptions(bw, extensions)
|
||
bw.WriteString(crlf)
|
||
}
|
||
|
||
if header != nil {
|
||
header.WriteTo(bw)
|
||
}
|
||
|
||
bw.WriteString(crlf)
|
||
}
|
||
|
||
func httpWriteResponseUpgrade(bw *bufio.Writer, nonce []byte, hs Handshake, header HandshakeHeaderFunc) {
|
||
bw.WriteString(textHeadUpgrade)
|
||
|
||
httpWriteHeaderKey(bw, headerSecAccept)
|
||
writeAccept(bw, nonce)
|
||
bw.WriteString(crlf)
|
||
|
||
if hs.Protocol != "" {
|
||
httpWriteHeader(bw, headerSecProtocol, hs.Protocol)
|
||
}
|
||
if len(hs.Extensions) > 0 {
|
||
httpWriteHeaderKey(bw, headerSecExtensions)
|
||
httphead.WriteOptions(bw, hs.Extensions)
|
||
bw.WriteString(crlf)
|
||
}
|
||
if header != nil {
|
||
header(bw)
|
||
}
|
||
|
||
bw.WriteString(crlf)
|
||
}
|
||
|
||
func httpWriteResponseError(bw *bufio.Writer, err error, code int, header HandshakeHeaderFunc) {
|
||
switch code {
|
||
case http.StatusBadRequest:
|
||
bw.WriteString(textHeadBadRequest)
|
||
case http.StatusInternalServerError:
|
||
bw.WriteString(textHeadInternalServerError)
|
||
case http.StatusUpgradeRequired:
|
||
bw.WriteString(textHeadUpgradeRequired)
|
||
default:
|
||
writeStatusText(bw, code)
|
||
}
|
||
|
||
// Write custom headers.
|
||
if header != nil {
|
||
header(bw)
|
||
}
|
||
|
||
switch err {
|
||
case ErrHandshakeBadProtocol:
|
||
bw.WriteString(textTailErrHandshakeBadProtocol)
|
||
case ErrHandshakeBadMethod:
|
||
bw.WriteString(textTailErrHandshakeBadMethod)
|
||
case ErrHandshakeBadHost:
|
||
bw.WriteString(textTailErrHandshakeBadHost)
|
||
case ErrHandshakeBadUpgrade:
|
||
bw.WriteString(textTailErrHandshakeBadUpgrade)
|
||
case ErrHandshakeBadConnection:
|
||
bw.WriteString(textTailErrHandshakeBadConnection)
|
||
case ErrHandshakeBadSecAccept:
|
||
bw.WriteString(textTailErrHandshakeBadSecAccept)
|
||
case ErrHandshakeBadSecKey:
|
||
bw.WriteString(textTailErrHandshakeBadSecKey)
|
||
case ErrHandshakeBadSecVersion:
|
||
bw.WriteString(textTailErrHandshakeBadSecVersion)
|
||
case ErrHandshakeUpgradeRequired:
|
||
bw.WriteString(textTailErrUpgradeRequired)
|
||
case nil:
|
||
bw.WriteString(crlf)
|
||
default:
|
||
writeErrorText(bw, err)
|
||
}
|
||
}
|
||
|
||
func writeStatusText(bw *bufio.Writer, code int) {
|
||
bw.WriteString("HTTP/1.1 ")
|
||
bw.WriteString(strconv.Itoa(code))
|
||
bw.WriteByte(' ')
|
||
bw.WriteString(http.StatusText(code))
|
||
bw.WriteString(crlf)
|
||
bw.WriteString("Content-Type: text/plain; charset=utf-8")
|
||
bw.WriteString(crlf)
|
||
}
|
||
|
||
func writeErrorText(bw *bufio.Writer, err error) {
|
||
body := err.Error()
|
||
bw.WriteString("Content-Length: ")
|
||
bw.WriteString(strconv.Itoa(len(body)))
|
||
bw.WriteString(crlf)
|
||
bw.WriteString(crlf)
|
||
bw.WriteString(body)
|
||
}
|
||
|
||
// httpError is like the http.Error with WebSocket context exception.
|
||
func httpError(w http.ResponseWriter, body string, code int) {
|
||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
|
||
w.WriteHeader(code)
|
||
w.Write([]byte(body))
|
||
}
|
||
|
||
// statusText is a non-performant status text generator.
|
||
// NOTE: Used only to generate constants.
|
||
func statusText(code int) string {
|
||
var buf bytes.Buffer
|
||
bw := bufio.NewWriter(&buf)
|
||
writeStatusText(bw, code)
|
||
bw.Flush()
|
||
return buf.String()
|
||
}
|
||
|
||
// errorText is a non-performant error text generator.
|
||
// NOTE: Used only to generate constants.
|
||
func errorText(err error) string {
|
||
var buf bytes.Buffer
|
||
bw := bufio.NewWriter(&buf)
|
||
writeErrorText(bw, err)
|
||
bw.Flush()
|
||
return buf.String()
|
||
}
|
||
|
||
// HandshakeHeader is the interface that writes both upgrade request or
|
||
// response headers into a given io.Writer.
|
||
type HandshakeHeader interface {
|
||
io.WriterTo
|
||
}
|
||
|
||
// HandshakeHeaderString is an adapter to allow the use of headers represented
|
||
// by ordinary string as HandshakeHeader.
|
||
type HandshakeHeaderString string
|
||
|
||
// WriteTo implements HandshakeHeader (and io.WriterTo) interface.
|
||
func (s HandshakeHeaderString) WriteTo(w io.Writer) (int64, error) {
|
||
n, err := io.WriteString(w, string(s))
|
||
return int64(n), err
|
||
}
|
||
|
||
// HandshakeHeaderBytes is an adapter to allow the use of headers represented
|
||
// by ordinary slice of bytes as HandshakeHeader.
|
||
type HandshakeHeaderBytes []byte
|
||
|
||
// WriteTo implements HandshakeHeader (and io.WriterTo) interface.
|
||
func (b HandshakeHeaderBytes) WriteTo(w io.Writer) (int64, error) {
|
||
n, err := w.Write(b)
|
||
return int64(n), err
|
||
}
|
||
|
||
// HandshakeHeaderFunc is an adapter to allow the use of headers represented by
|
||
// ordinary function as HandshakeHeader.
|
||
type HandshakeHeaderFunc func(io.Writer) (int64, error)
|
||
|
||
// WriteTo implements HandshakeHeader (and io.WriterTo) interface.
|
||
func (f HandshakeHeaderFunc) WriteTo(w io.Writer) (int64, error) {
|
||
return f(w)
|
||
}
|
||
|
||
// HandshakeHeaderHTTP is an adapter to allow the use of http.Header as
|
||
// HandshakeHeader.
|
||
type HandshakeHeaderHTTP http.Header
|
||
|
||
// WriteTo implements HandshakeHeader (and io.WriterTo) interface.
|
||
func (h HandshakeHeaderHTTP) WriteTo(w io.Writer) (int64, error) {
|
||
wr := writer{w: w}
|
||
err := http.Header(h).Write(&wr)
|
||
return wr.n, err
|
||
}
|
||
|
||
type writer struct {
|
||
n int64
|
||
w io.Writer
|
||
}
|
||
|
||
func (w *writer) WriteString(s string) (int, error) {
|
||
n, err := io.WriteString(w.w, s)
|
||
w.n += int64(n)
|
||
return n, err
|
||
}
|
||
|
||
func (w *writer) Write(p []byte) (int, error) {
|
||
n, err := w.w.Write(p)
|
||
w.n += int64(n)
|
||
return n, err
|
||
}
|