برنامه نویسی

عیب یابی پروکسی معکوس HTTP/3 QUIC برای آپلودهای تکه تکه شده به URL های از پیش امضا شده S3 – انجمن DEV

سلام انجمن!!

من روی پروژه‌ای کار می‌کنم که در آن از یک پروکسی معکوس مبتنی بر QUIC (که با کتابخانه quic-go پیاده‌سازی شده است) استفاده می‌کنم تا آپلودهای داده‌های تکه‌شده را به URLهای از پیش امضاشده AWS S3 ارسال کنم. در اینجا یک مرور کلی از راه اندازی، اهداف، و مشکلاتی که با آن روبرو هستم آورده شده است:

راه اندازی سرور:

یک سرور سفارشی HTTP/3 QUIC به یک نقطه پایانی خاص (مثلاً /post-reverse) گوش می دهد تا درخواست های PUT را با داده های تکه تکه شده دریافت کند. درخواست شامل: داده‌های تکه‌شده در بدنه است. یک هدر سفارشی (X-Presigned-URL) با URL از پیش امضا شده S3. به محض دریافت درخواست: سرور آدرس X-Presigned-URL را از سربرگ ها استخراج می کند. بدنه درخواست را با استفاده از مکانیزم پروکسی معکوس به URL از پیش امضا شده ارسال می کند. پاسخ را از S3 به مشتری ارسال می کند.

package main

import (
    "context"
    "errors"
    "fmt"
    "log/slog"
    "net/http"
    "net/http/httputil"
    "net/url"
    "os"
    "os/signal"
    "time"

    "github.com/Private-repo/go-httperr"
    "github.com/Private-repo/go-reqlog"
    "github.com/quic-go/quic-go"
    "github.com/quic-go/quic-go/http3"
    "github.com/quic-go/quic-go/qlog"
)

const (
    Host                     = "0.0.0.0"
    Port                     = 4242
    webServerShutdownTimeout = 5 * time.Second
)

func main() {

    ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
    defer cancel()

    mux := http.NewServeMux()

    // V1 APIs
    mux.Handle("/post-reverse", httperr.HandlerFunc(ReverseProxy))

    hdlr := reqlog.RequestLogger(mux, nil)
    addr := fmt.Sprintf("%s:%d", Host, Port)

    server := http3.Server{
        Handler: hdlr,
        Addr:    addr,
        QUICConfig: &quic.Config{
            Tracer:    qlog.DefaultConnectionTracer,
        },
    }

    go func() {
        if err := server.ListenAndServeTLS("server.crt", "server.key"); err != nil && !errors.Is(err, http.ErrServerClosed) {
            slog.ErrorContext(ctx, "error starting server", "error", err)
            os.Exit(1)
        }
    }()

    slog.InfoContext(ctx, "started UDP-Srv", "addr", addr)

    <-ctx.Done()

    // Shutdown gracefully.
    ctx, cancel = context.WithTimeout(context.Background(), webServerShutdownTimeout)
    defer cancel()

    slog.InfoContext(ctx, "shutting down")
    if err := server.Shutdown(ctx) ; err != nil {
        slog.ErrorContext(ctx, "http server shutdown error", "error", err)
    }
}

func ReverseProxy(w http.ResponseWriter, r *http.Request) error {
    slog.Info("ReverseProxy called", "method", r.Method, "path", r.URL.Path)

    if r.Method != http.MethodPut {
        slog.Warn("Method not allowed", "method", r.Method)
        return httperr.Errorf(http.StatusMethodNotAllowed, "Method not allowed")
    }

    // Extract Pre-Signed URL
    presignedURL := r.Header.Get("X-Presigned-URL")
    if presignedURL == "" {
        slog.Warn("Missing X-Presigned-URL header")
        return httperr.Errorf(http.StatusBadRequest, "Missing X-Presigned-URL header")
    }

    // Validate Pre-Signed URL
    url, err := url.Parse(presignedURL)
    if err != nil {
        slog.Warn("Invalid X-Presigned-URL header", "error", err)
        return httperr.Errorf(http.StatusBadRequest, "Invalid X-Presigned-URL header")
    }
    slog.Info("Using Pre-Signed URL", "url", presignedURL)

    // Configure reverse proxy
    proxy := httputil.NewSingleHostReverseProxy(url)
    proxy.Director = func(req *http.Request) {
        req.URL = url
        req.Host = url.Host
        req.Method = r.Method
        req.Header = r.Header.Clone() // Clone headers
        req.Header.Del("X-Presigned-URL")
        req.ContentLength = r.ContentLength
    }

    // Handle proxy errors
    proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) {
        slog.Error("Proxy error", "error", err)
        http.Error(rw, "Proxy error: "+err.Error(), http.StatusBadGateway)
    }

    // Serve the proxied request
    slog.Info("Forwarding request to S3")
    proxy.ServeHTTP(w, r)

    return nil
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مشتری:

کلاینت داده های تکه تکه شده را از طریق HTTP/3 با استفاده از مشتری quic-go ارسال می کند. هر درخواست حاوی هدر X-Presigned-URL با URL S3 و بار تکه است. این قطعه 8 مگابایتی ارسال می کند

func (lu *Uploader) sendChunk(client *http.Client, chunkObject models.ChunkUrl, assetPath string, subtitleRelativeMap *sync.Map) error {
    var err error
    var dataReader io.Reader
    for attempt := 0; attempt < 4; attempt++ {
        if strings.Contains(assetPath, ".tar") {
            lu.logger.Info("path", "path", assetPath, "offset", chunkObject.Offset, "size", chunkObject.Size)
            path := strings.TrimSuffix(assetPath, ".tar")
            lu.logger.Info("create.tar", "path", filepath.Base(path))
            dataReader, err = lu.createTar(path, subtitleRelativeMap)
            if err != nil {
                return err
            }
            _, err = io.CopyN(io.Discard, dataReader, chunkObject.Offset)
            if err != nil {
                return err
            }

            dataReader = io.LimitReader(dataReader, chunkObject.Size)

        } else {
            var file *os.File
            file, err = os.Open(assetPath)
            if err != nil {
                return err
            }
            defer file.Close()

            dataReader = io.NewSectionReader(file, chunkObject.Offset, chunkObject.Size)

        }

        req, err := http.NewRequest(http.MethodPut, "https://localhost:4242/post-reverse", dataReader)
        if err != nil {
            return err
        }
        req.Header.Add("Content-Type", "application/octet-stream")
        req.Header.Add("X-Presigned-URL", chunkObject.UploadUrl)
        req.ContentLength = chunkObject.Size

        resp, err := client.Do(req)
        if err != nil {
            lu.logger.Error("upload.chunk.error", "attempt", attempt, "err", err)
            continue
        }

        // handle empty response
        responseBody, _ := io.ReadAll(resp.Body)
        defer resp.Body.Close()
        if resp.StatusCode != http.StatusOK {
            lu.logger.Error("upload.chunk.error", "attempt", attempt, "response", string(responseBody))
            time.Sleep(2 * time.Second)
            continue
        }
        // SUCCESS
        lu.logger.Info("upload.chunk.success", "path", assetPath, "offset", chunkObject.Offset, "size", chunkObject.Size)
        return nil
    }
    return err
}

func makeOptimizedClient() *http.Client {
    tr := &http3.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
        QUICConfig:      &quic.Config{},
    }
    defer tr.Close()
    client := &http.Client{
        Transport: tr,
        Timeout:   10 * time.Minute,
    }
    return client
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اهداف من هستند

با استفاده از یک پراکسی معکوس که از QUIC برای انتقال داده با تأخیر کم استفاده می کند، آپلودهای تکه ای کارآمد را در S3 فعال کنید. از ارسال موفقیت آمیز داده های تکه تکه شده از مشتری به URL از پیش امضا شده S3 از طریق پراکسی معکوس اطمینان حاصل کنید. پس از آپلود S3، پاسخ‌های مناسب (مانند HTTP 200 برای آپلودهای موفق یا کدهای خطا برای مشکلات) را به مشتری ارائه دهید.

مشکلات: پروکسی ناموفق به S3:

هنگامی که سرور درخواست تکه تکه شده را به URL از پیش امضا شده S3 می فرستد، تنها چیزی که دریافت می کنم 502 Bad Gateway با خطا است: http3: تجزیه فریم انجام نشد: مهلت زمانی: هیچ فعالیت اخیر شبکه وجود ندارد.

من سعی کردم از همان کد با یک سرویس گیرنده و سرور HTTP اصلی استفاده کنم و با اتصالات TCP خوب کار می کند. با این حال، وقتی به پیاده سازی QUIC تغییر می کنم، شروع به پرتاب خطا می کند.

لطفا کمک کنید، و اگر سوال من نامشخص است، برای توضیح بخواهید.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا