mirror of
https://github.com/Gouryella/drip.git
synced 2026-02-24 05:10:43 +00:00
- Add Bearer Token authentication, supporting tunnel access control via the --auth-bearer parameter - Refactor large modules into smaller, more focused components to improve code maintainability - Update dependency versions, including golang.org/x/crypto, golang.org/x/net, etc. - Add SilenceUsage and SilenceErrors configuration for all CLI commands - Modify connector configuration structure to support the new authentication method - Update recent change log in README with new feature descriptions BREAKING CHANGE: Authentication via Bearer Token is now supported, requiring the new --auth-bearer parameter
252 lines
6.2 KiB
Go
252 lines
6.2 KiB
Go
package tcp
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// bufioWriterPool reuses bufio.Writer instances to reduce GC pressure
|
|
var bufioWriterPool = sync.Pool{
|
|
New: func() interface{} {
|
|
return bufio.NewWriterSize(nil, 4096)
|
|
},
|
|
}
|
|
|
|
// HTTPRequestHandler handles HTTP requests on TCP connections.
|
|
type HTTPRequestHandler struct {
|
|
conn net.Conn
|
|
reader *bufio.Reader
|
|
httpHandler http.Handler
|
|
httpListener *connQueueListener
|
|
ctx interface{ Done() <-chan struct{} }
|
|
logger *zap.Logger
|
|
mu *sync.RWMutex
|
|
handedOff *bool
|
|
}
|
|
|
|
// NewHTTPRequestHandler creates a new HTTP request handler.
|
|
func NewHTTPRequestHandler(
|
|
conn net.Conn,
|
|
reader *bufio.Reader,
|
|
httpHandler http.Handler,
|
|
httpListener *connQueueListener,
|
|
ctx interface{ Done() <-chan struct{} },
|
|
logger *zap.Logger,
|
|
mu *sync.RWMutex,
|
|
handedOff *bool,
|
|
) *HTTPRequestHandler {
|
|
return &HTTPRequestHandler{
|
|
conn: conn,
|
|
reader: reader,
|
|
httpHandler: httpHandler,
|
|
httpListener: httpListener,
|
|
ctx: ctx,
|
|
logger: logger,
|
|
mu: mu,
|
|
handedOff: handedOff,
|
|
}
|
|
}
|
|
|
|
// Handle processes the HTTP request.
|
|
func (h *HTTPRequestHandler) Handle() error {
|
|
if h.httpListener == nil {
|
|
return h.handleLegacy()
|
|
}
|
|
|
|
h.conn.SetReadDeadline(time.Time{})
|
|
|
|
wrappedConn := &bufferedConn{
|
|
Conn: h.conn,
|
|
reader: h.reader,
|
|
}
|
|
|
|
if !h.httpListener.Enqueue(wrappedConn) {
|
|
h.logger.Warn("HTTP listener queue full, rejecting connection")
|
|
response := "HTTP/1.1 503 Service Unavailable\r\n" +
|
|
"Content-Type: text/plain\r\n" +
|
|
"Content-Length: 32\r\n" +
|
|
"Connection: close\r\n" +
|
|
"\r\n" +
|
|
"Server busy, please retry later\r\n"
|
|
h.conn.Write([]byte(response))
|
|
return fmt.Errorf("http listener queue full")
|
|
}
|
|
|
|
h.mu.Lock()
|
|
*h.handedOff = true
|
|
h.mu.Unlock()
|
|
|
|
return nil
|
|
}
|
|
|
|
// handleLegacy processes HTTP requests using the legacy handler.
|
|
func (h *HTTPRequestHandler) handleLegacy() error {
|
|
if h.httpHandler == nil {
|
|
h.logger.Warn("HTTP request received but no HTTP handler configured")
|
|
response := "HTTP/1.1 503 Service Unavailable\r\n" +
|
|
"Content-Type: text/plain\r\n" +
|
|
"Content-Length: 47\r\n" +
|
|
"\r\n" +
|
|
"HTTP handler not configured for this TCP port\r\n"
|
|
h.conn.Write([]byte(response))
|
|
return fmt.Errorf("HTTP handler not configured")
|
|
}
|
|
|
|
h.conn.SetReadDeadline(time.Time{})
|
|
|
|
for {
|
|
h.conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
|
|
|
req, err := http.ReadRequest(h.reader)
|
|
if err != nil {
|
|
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
|
h.logger.Debug("Client closed HTTP connection")
|
|
return nil
|
|
}
|
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
|
h.logger.Debug("HTTP keep-alive timeout")
|
|
return nil
|
|
}
|
|
errStr := err.Error()
|
|
if errors.Is(err, net.ErrClosed) || strings.Contains(errStr, "use of closed network connection") {
|
|
h.logger.Debug("HTTP connection closed during read", zap.Error(err))
|
|
return nil
|
|
}
|
|
if strings.Contains(errStr, "connection reset by peer") ||
|
|
strings.Contains(errStr, "broken pipe") ||
|
|
strings.Contains(errStr, "connection refused") {
|
|
h.logger.Debug("Client disconnected abruptly", zap.Error(err))
|
|
return nil
|
|
}
|
|
if strings.Contains(errStr, "malformed HTTP") {
|
|
h.logger.Warn("Received malformed HTTP request",
|
|
zap.Error(err),
|
|
zap.String("error_snippet", errStr[:min(len(errStr), 100)]),
|
|
)
|
|
return nil
|
|
}
|
|
h.logger.Error("Failed to parse HTTP request", zap.Error(err))
|
|
return fmt.Errorf("failed to parse HTTP request: %w", err)
|
|
}
|
|
|
|
if h.ctx != nil {
|
|
if ctxWithContext, ok := h.ctx.(interface{ Done() <-chan struct{} }); ok {
|
|
req = req.WithContext(ctxWithContext.(interface {
|
|
Done() <-chan struct{}
|
|
Deadline() (deadline time.Time, ok bool)
|
|
Err() error
|
|
Value(key interface{}) interface{}
|
|
}))
|
|
}
|
|
}
|
|
|
|
h.logger.Info("Processing HTTP request on TCP port",
|
|
zap.String("method", req.Method),
|
|
zap.String("url", req.URL.String()),
|
|
zap.String("host", req.Host),
|
|
)
|
|
|
|
// Get writer from pool to reduce GC pressure
|
|
pooledWriter := bufioWriterPool.Get().(*bufio.Writer)
|
|
pooledWriter.Reset(h.conn)
|
|
|
|
respWriter := &httpResponseWriter{
|
|
conn: h.conn,
|
|
writer: pooledWriter,
|
|
header: make(http.Header),
|
|
}
|
|
|
|
h.httpHandler.ServeHTTP(respWriter, req)
|
|
|
|
if err := respWriter.writer.Flush(); err != nil {
|
|
h.logger.Debug("Failed to flush HTTP response", zap.Error(err))
|
|
}
|
|
|
|
// Return writer to pool
|
|
pooledWriter.Reset(nil) // Clear reference to connection
|
|
bufioWriterPool.Put(pooledWriter)
|
|
|
|
h.logger.Debug("HTTP request processing completed",
|
|
zap.String("method", req.Method),
|
|
zap.String("url", req.URL.String()),
|
|
)
|
|
|
|
shouldClose := false
|
|
if req.Close {
|
|
shouldClose = true
|
|
} else if req.ProtoMajor == 1 && req.ProtoMinor == 0 {
|
|
if req.Header.Get("Connection") != "keep-alive" {
|
|
shouldClose = true
|
|
}
|
|
}
|
|
|
|
if respWriter.headerWritten && respWriter.header.Get("Connection") == "close" {
|
|
shouldClose = true
|
|
}
|
|
|
|
if shouldClose {
|
|
h.logger.Debug("Closing connection as requested by client or server")
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// httpResponseWriter implements http.ResponseWriter for raw TCP connections.
|
|
type httpResponseWriter struct {
|
|
conn net.Conn
|
|
writer *bufio.Writer
|
|
header http.Header
|
|
statusCode int
|
|
headerWritten bool
|
|
}
|
|
|
|
func (w *httpResponseWriter) Header() http.Header {
|
|
return w.header
|
|
}
|
|
|
|
func (w *httpResponseWriter) WriteHeader(statusCode int) {
|
|
if w.headerWritten {
|
|
return
|
|
}
|
|
w.statusCode = statusCode
|
|
w.headerWritten = true
|
|
|
|
statusText := http.StatusText(statusCode)
|
|
if statusText == "" {
|
|
statusText = "Unknown"
|
|
}
|
|
|
|
w.writer.WriteString("HTTP/1.1 ")
|
|
w.writer.WriteString(fmt.Sprintf("%d", statusCode))
|
|
w.writer.WriteByte(' ')
|
|
w.writer.WriteString(statusText)
|
|
w.writer.WriteString("\r\n")
|
|
|
|
for key, values := range w.header {
|
|
for _, value := range values {
|
|
w.writer.WriteString(key)
|
|
w.writer.WriteString(": ")
|
|
w.writer.WriteString(value)
|
|
w.writer.WriteString("\r\n")
|
|
}
|
|
}
|
|
|
|
w.writer.WriteString("\r\n")
|
|
}
|
|
|
|
func (w *httpResponseWriter) Write(data []byte) (int, error) {
|
|
if !w.headerWritten {
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
return w.writer.Write(data)
|
|
}
|