move protocol-related code into internal/protocols (#2572)

This commit is contained in:
Alessandro Ros 2023-10-26 21:46:18 +02:00 committed by GitHub
parent 3ebc585539
commit 99bc327d67
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
126 changed files with 73 additions and 73 deletions

View file

@ -0,0 +1,27 @@
package httpserv
import (
"fmt"
"net/http"
"os"
"runtime"
)
// exit when there's a panic inside the HTTP handler.
// https://github.com/golang/go/issues/16542
type handlerExitOnPanic struct {
http.Handler
}
func (h *handlerExitOnPanic) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer func() {
err := recover()
if err != nil {
buf := make([]byte, 1<<20)
n := runtime.Stack(buf, true)
fmt.Fprintf(os.Stderr, "panic: %v\n\n%s", err, buf[:n])
os.Exit(1)
}
}()
h.Handler.ServeHTTP(w, r)
}

View file

@ -0,0 +1,18 @@
package httpserv
import (
"net/http"
)
// reject requests with empty paths.
type handlerFilterRequests struct {
http.Handler
}
func (h *handlerFilterRequests) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "" || r.URL.Path[0] != '/' {
w.WriteHeader(http.StatusBadRequest)
return
}
h.Handler.ServeHTTP(w, r)
}

View file

@ -0,0 +1,63 @@
package httpserv
import (
"bytes"
"fmt"
"net/http"
"net/http/httputil"
"github.com/bluenviron/mediamtx/internal/logger"
)
type loggerWriter struct {
w http.ResponseWriter
status int
buf bytes.Buffer
}
func (w *loggerWriter) Header() http.Header {
return w.w.Header()
}
func (w *loggerWriter) Write(b []byte) (int, error) {
if w.status == 0 {
w.status = http.StatusOK
}
w.buf.Write(b)
return w.w.Write(b)
}
func (w *loggerWriter) WriteHeader(statusCode int) {
w.status = statusCode
w.w.WriteHeader(statusCode)
}
func (w *loggerWriter) dump() string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "%s %d %s\n", "HTTP/1.1", w.status, http.StatusText(w.status))
w.w.Header().Write(&buf) //nolint:errcheck
buf.Write([]byte("\n"))
if w.buf.Len() > 0 {
fmt.Fprintf(&buf, "(body of %d bytes)", w.buf.Len())
}
return buf.String()
}
// log requests and responses.
type handlerLogger struct {
http.Handler
log logger.Writer
}
func (h *handlerLogger) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.log.Log(logger.Debug, "[conn %v] %s %s", r.RemoteAddr, r.Method, r.URL.Path)
byts, _ := httputil.DumpRequest(r, true)
h.log.Log(logger.Debug, "[conn %v] [c->s] %s", r.RemoteAddr, string(byts))
logw := &loggerWriter{w: w}
h.Handler.ServeHTTP(logw, r)
h.log.Log(logger.Debug, "[conn %v] [s->c] %s", r.RemoteAddr, logw.dump())
}

View file

@ -0,0 +1,15 @@
package httpserv
import (
"net/http"
)
// set the Server header.
type handlerServerHeader struct {
http.Handler
}
func (h *handlerServerHeader) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Server", "mediamtx")
h.Handler.ServeHTTP(w, r)
}

View file

@ -0,0 +1,91 @@
// Package httpserv contains HTTP server utilities.
package httpserv
import (
"context"
"crypto/tls"
"log"
"net"
"net/http"
"time"
"github.com/bluenviron/mediamtx/internal/logger"
)
type nilWriter struct{}
func (nilWriter) Write(p []byte) (int, error) {
return len(p), nil
}
// WrappedServer is a wrapper around http.Server that provides:
// - net.Listener allocation and closure
// - TLS allocation
// - exit on panic
// - logging
// - server header
// - filtering of invalid requests
type WrappedServer struct {
ln net.Listener
inner *http.Server
}
// NewWrappedServer allocates a WrappedServer.
func NewWrappedServer(
network string,
address string,
readTimeout time.Duration,
serverCert string,
serverKey string,
handler http.Handler,
parent logger.Writer,
) (*WrappedServer, error) {
ln, err := net.Listen(network, address)
if err != nil {
return nil, err
}
var tlsConfig *tls.Config
if serverCert != "" {
crt, err := tls.LoadX509KeyPair(serverCert, serverKey)
if err != nil {
ln.Close()
return nil, err
}
tlsConfig = &tls.Config{
Certificates: []tls.Certificate{crt},
}
}
h := handler
h = &handlerFilterRequests{h}
h = &handlerFilterRequests{h}
h = &handlerServerHeader{h}
h = &handlerLogger{h, parent}
h = &handlerExitOnPanic{h}
s := &WrappedServer{
ln: ln,
inner: &http.Server{
Handler: h,
TLSConfig: tlsConfig,
ReadHeaderTimeout: readTimeout,
ErrorLog: log.New(&nilWriter{}, "", 0),
},
}
if tlsConfig != nil {
go s.inner.ServeTLS(s.ln, "", "")
} else {
go s.inner.Serve(s.ln)
}
return s, nil
}
// Close closes all resources and waits for all routines to return.
func (s *WrappedServer) Close() {
s.inner.Shutdown(context.Background())
s.ln.Close() // in case Shutdown() is called before Serve()
}

View file

@ -0,0 +1,44 @@
package httpserv
import (
"io"
"net"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/bluenviron/mediamtx/internal/logger"
)
type testLogger struct{}
func (testLogger) Log(_ logger.Level, _ string, _ ...interface{}) {
}
func TestFilterEmptyPath(t *testing.T) {
s, err := NewWrappedServer(
"tcp",
"localhost:4555",
10*time.Second,
"",
"",
nil,
&testLogger{})
require.NoError(t, err)
defer s.Close()
conn, err := net.Dial("tcp", "localhost:4555")
require.NoError(t, err)
defer conn.Close()
_, err = conn.Write([]byte("OPTIONS http://localhost HTTP/1.1\n" +
"Host: localhost:8889\n" +
"Accept-Encoding: gzip\n" +
"User-Agent: Go-http-client/1.1\n\n"))
require.NoError(t, err)
buf := make([]byte, 20)
_, err = io.ReadFull(conn, buf)
require.NoError(t, err)
}