13 changed files with 200 additions and 105 deletions
@ -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) |
||||||
|
} |
||||||
@ -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) |
||||||
|
} |
||||||
@ -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) |
||||||
|
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()) |
||||||
|
} |
||||||
@ -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) |
||||||
|
} |
||||||
@ -1,55 +0,0 @@ |
|||||||
package httpserv |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"fmt" |
|
||||||
"net/http" |
|
||||||
"net/http/httputil" |
|
||||||
|
|
||||||
"github.com/gin-gonic/gin" |
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/logger" |
|
||||||
) |
|
||||||
|
|
||||||
type loggerWriter struct { |
|
||||||
gin.ResponseWriter |
|
||||||
buf bytes.Buffer |
|
||||||
} |
|
||||||
|
|
||||||
func (w *loggerWriter) Write(b []byte) (int, error) { |
|
||||||
w.buf.Write(b) |
|
||||||
return w.ResponseWriter.Write(b) |
|
||||||
} |
|
||||||
|
|
||||||
func (w *loggerWriter) WriteString(s string) (int, error) { |
|
||||||
w.buf.WriteString(s) |
|
||||||
return w.ResponseWriter.WriteString(s) |
|
||||||
} |
|
||||||
|
|
||||||
func (w *loggerWriter) dump() string { |
|
||||||
var buf bytes.Buffer |
|
||||||
fmt.Fprintf(&buf, "%s %d %s\n", "HTTP/1.1", w.ResponseWriter.Status(), http.StatusText(w.ResponseWriter.Status())) |
|
||||||
w.ResponseWriter.Header().Write(&buf) |
|
||||||
buf.Write([]byte("\n")) |
|
||||||
if w.buf.Len() > 0 { |
|
||||||
fmt.Fprintf(&buf, "(body of %d bytes)", w.buf.Len()) |
|
||||||
} |
|
||||||
return buf.String() |
|
||||||
} |
|
||||||
|
|
||||||
// MiddlewareLogger is a middleware that logs requests and responses.
|
|
||||||
func MiddlewareLogger(p logger.Writer) func(*gin.Context) { |
|
||||||
return func(ctx *gin.Context) { |
|
||||||
p.Log(logger.Debug, "[conn %v] %s %s", ctx.Request.RemoteAddr, ctx.Request.Method, ctx.Request.URL.Path) |
|
||||||
|
|
||||||
byts, _ := httputil.DumpRequest(ctx.Request, true) |
|
||||||
p.Log(logger.Debug, "[conn %v] [c->s] %s", ctx.Request.RemoteAddr, string(byts)) |
|
||||||
|
|
||||||
logw := &loggerWriter{ResponseWriter: ctx.Writer} |
|
||||||
ctx.Writer = logw |
|
||||||
|
|
||||||
ctx.Next() |
|
||||||
|
|
||||||
p.Log(logger.Debug, "[conn %v] [s->c] %s", ctx.Request.RemoteAddr, logw.dump()) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,11 +0,0 @@ |
|||||||
package httpserv |
|
||||||
|
|
||||||
import ( |
|
||||||
"github.com/gin-gonic/gin" |
|
||||||
) |
|
||||||
|
|
||||||
// MiddlewareServerHeader is a middleware that sets the Server header.
|
|
||||||
func MiddlewareServerHeader(ctx *gin.Context) { |
|
||||||
ctx.Writer.Header().Set("Server", "mediamtx") |
|
||||||
ctx.Next() |
|
||||||
} |
|
||||||
@ -0,0 +1,45 @@ |
|||||||
|
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{}) { |
||||||
|
// fmt.Printf(format, args...)
|
||||||
|
} |
||||||
|
|
||||||
|
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) |
||||||
|
} |
||||||
Loading…
Reference in new issue