{"id":90336,"date":"2024-12-29T08:21:41","date_gmt":"2024-12-29T04:51:41","guid":{"rendered":"https:\/\/nabfollower.com\/blog\/troubleshooting-http3-quic-reverse-proxy-for-chunked-uploads-to-s3-pre-signed-urls-10ge\/"},"modified":"2024-12-29T08:21:41","modified_gmt":"2024-12-29T04:51:41","slug":"troubleshooting-http3-quic-reverse-proxy-for-chunked-uploads-to-s3-pre-signed-urls-10ge","status":"publish","type":"post","link":"https:\/\/nabfollower.com\/blog\/troubleshooting-http3-quic-reverse-proxy-for-chunked-uploads-to-s3-pre-signed-urls-10ge\/","title":{"rendered":"\u0639\u06cc\u0628 \u06cc\u0627\u0628\u06cc \u067e\u0631\u0648\u06a9\u0633\u06cc \u0645\u0639\u06a9\u0648\u0633 HTTP\/3 QUIC \u0628\u0631\u0627\u06cc \u0622\u067e\u0644\u0648\u062f\u0647\u0627\u06cc \u062a\u06a9\u0647 \u062a\u06a9\u0647 \u0634\u062f\u0647 \u0628\u0647 URL \u0647\u0627\u06cc \u0627\u0632 \u067e\u06cc\u0634 \u0627\u0645\u0636\u0627 \u0634\u062f\u0647 S3 &#8211; \u0627\u0646\u062c\u0645\u0646 DEV"},"content":{"rendered":"<div data-article-id=\"2180070\" id=\"article-body\">\n<p>\u0633\u0644\u0627\u0645 \u0627\u0646\u062c\u0645\u0646!!<\/p>\n<p>\u0645\u0646 \u0631\u0648\u06cc \u067e\u0631\u0648\u0698\u0647\u200c\u0627\u06cc \u06a9\u0627\u0631 \u0645\u06cc\u200c\u06a9\u0646\u0645 \u06a9\u0647 \u062f\u0631 \u0622\u0646 \u0627\u0632 \u06cc\u06a9 \u067e\u0631\u0648\u06a9\u0633\u06cc \u0645\u0639\u06a9\u0648\u0633 \u0645\u0628\u062a\u0646\u06cc \u0628\u0631 QUIC (\u06a9\u0647 \u0628\u0627 \u06a9\u062a\u0627\u0628\u062e\u0627\u0646\u0647 quic-go \u067e\u06cc\u0627\u062f\u0647\u200c\u0633\u0627\u0632\u06cc \u0634\u062f\u0647 \u0627\u0633\u062a) \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0645\u06cc\u200c\u06a9\u0646\u0645 \u062a\u0627 \u0622\u067e\u0644\u0648\u062f\u0647\u0627\u06cc \u062f\u0627\u062f\u0647\u200c\u0647\u0627\u06cc \u062a\u06a9\u0647\u200c\u0634\u062f\u0647 \u0631\u0627 \u0628\u0647 URL\u0647\u0627\u06cc \u0627\u0632 \u067e\u06cc\u0634 \u0627\u0645\u0636\u0627\u0634\u062f\u0647 AWS S3 \u0627\u0631\u0633\u0627\u0644 \u06a9\u0646\u0645. \u062f\u0631 \u0627\u06cc\u0646\u062c\u0627 \u06cc\u06a9 \u0645\u0631\u0648\u0631 \u06a9\u0644\u06cc \u0627\u0632 \u0631\u0627\u0647 \u0627\u0646\u062f\u0627\u0632\u06cc\u060c \u0627\u0647\u062f\u0627\u0641\u060c \u0648 \u0645\u0634\u06a9\u0644\u0627\u062a\u06cc \u06a9\u0647 \u0628\u0627 \u0622\u0646 \u0631\u0648\u0628\u0631\u0648 \u0647\u0633\u062a\u0645 \u0622\u0648\u0631\u062f\u0647 \u0634\u062f\u0647 \u0627\u0633\u062a:<\/p>\n<p>\u0631\u0627\u0647 \u0627\u0646\u062f\u0627\u0632\u06cc \u0633\u0631\u0648\u0631:<\/p>\n<p>\u06cc\u06a9 \u0633\u0631\u0648\u0631 \u0633\u0641\u0627\u0631\u0634\u06cc HTTP\/3 QUIC \u0628\u0647 \u06cc\u06a9 \u0646\u0642\u0637\u0647 \u067e\u0627\u06cc\u0627\u0646\u06cc \u062e\u0627\u0635 (\u0645\u062b\u0644\u0627\u064b \/post-reverse) \u06af\u0648\u0634 \u0645\u06cc \u062f\u0647\u062f \u062a\u0627 \u062f\u0631\u062e\u0648\u0627\u0633\u062a \u0647\u0627\u06cc PUT \u0631\u0627 \u0628\u0627 \u062f\u0627\u062f\u0647 \u0647\u0627\u06cc \u062a\u06a9\u0647 \u062a\u06a9\u0647 \u0634\u062f\u0647 \u062f\u0631\u06cc\u0627\u0641\u062a \u06a9\u0646\u062f. \u062f\u0631\u062e\u0648\u0627\u0633\u062a \u0634\u0627\u0645\u0644: \u062f\u0627\u062f\u0647\u200c\u0647\u0627\u06cc \u062a\u06a9\u0647\u200c\u0634\u062f\u0647 \u062f\u0631 \u0628\u062f\u0646\u0647 \u0627\u0633\u062a. \u06cc\u06a9 \u0647\u062f\u0631 \u0633\u0641\u0627\u0631\u0634\u06cc (X-Presigned-URL) \u0628\u0627 URL \u0627\u0632 \u067e\u06cc\u0634 \u0627\u0645\u0636\u0627 \u0634\u062f\u0647 S3. \u0628\u0647 \u0645\u062d\u0636 \u062f\u0631\u06cc\u0627\u0641\u062a \u062f\u0631\u062e\u0648\u0627\u0633\u062a: \u0633\u0631\u0648\u0631 \u0622\u062f\u0631\u0633 X-Presigned-URL \u0631\u0627 \u0627\u0632 \u0633\u0631\u0628\u0631\u06af \u0647\u0627 \u0627\u0633\u062a\u062e\u0631\u0627\u062c \u0645\u06cc \u06a9\u0646\u062f. \u0628\u062f\u0646\u0647 \u062f\u0631\u062e\u0648\u0627\u0633\u062a \u0631\u0627 \u0628\u0627 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0627\u0632 \u0645\u06a9\u0627\u0646\u06cc\u0632\u0645 \u067e\u0631\u0648\u06a9\u0633\u06cc \u0645\u0639\u06a9\u0648\u0633 \u0628\u0647 URL \u0627\u0632 \u067e\u06cc\u0634 \u0627\u0645\u0636\u0627 \u0634\u062f\u0647 \u0627\u0631\u0633\u0627\u0644 \u0645\u06cc \u06a9\u0646\u062f. \u067e\u0627\u0633\u062e \u0631\u0627 \u0627\u0632 S3 \u0628\u0647 \u0645\u0634\u062a\u0631\u06cc \u0627\u0631\u0633\u0627\u0644 \u0645\u06cc \u06a9\u0646\u062f.<\/p>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>package main\n\nimport (\n    \"context\"\n    \"errors\"\n    \"fmt\"\n    \"log\/slog\"\n    \"net\/http\"\n    \"net\/http\/httputil\"\n    \"net\/url\"\n    \"os\"\n    \"os\/signal\"\n    \"time\"\n\n    \"github.com\/Private-repo\/go-httperr\"\n    \"github.com\/Private-repo\/go-reqlog\"\n    \"github.com\/quic-go\/quic-go\"\n    \"github.com\/quic-go\/quic-go\/http3\"\n    \"github.com\/quic-go\/quic-go\/qlog\"\n)\n\nconst (\n    Host                     = \"0.0.0.0\"\n    Port                     = 4242\n    webServerShutdownTimeout = 5 * time.Second\n)\n\nfunc main() {\n\n    ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)\n    defer cancel()\n\n    mux := http.NewServeMux()\n\n    \/\/ V1 APIs\n    mux.Handle(\"\/post-reverse\", httperr.HandlerFunc(ReverseProxy))\n\n    hdlr := reqlog.RequestLogger(mux, nil)\n    addr := fmt.Sprintf(\"%s:%d\", Host, Port)\n\n    server := http3.Server{\n        Handler: hdlr,\n        Addr:    addr,\n        QUICConfig: &amp;quic.Config{\n            Tracer:    qlog.DefaultConnectionTracer,\n        },\n    }\n\n    go func() {\n        if err := server.ListenAndServeTLS(\"server.crt\", \"server.key\"); err != nil &amp;&amp; !errors.Is(err, http.ErrServerClosed) {\n            slog.ErrorContext(ctx, \"error starting server\", \"error\", err)\n            os.Exit(1)\n        }\n    }()\n\n    slog.InfoContext(ctx, \"started UDP-Srv\", \"addr\", addr)\n\n    &lt;-ctx.Done()\n\n    \/\/ Shutdown gracefully.\n    ctx, cancel = context.WithTimeout(context.Background(), webServerShutdownTimeout)\n    defer cancel()\n\n    slog.InfoContext(ctx, \"shutting down\")\n    if err := server.Shutdown(ctx) ; err != nil {\n        slog.ErrorContext(ctx, \"http server shutdown error\", \"error\", err)\n    }\n}\n\nfunc ReverseProxy(w http.ResponseWriter, r *http.Request) error {\n    slog.Info(\"ReverseProxy called\", \"method\", r.Method, \"path\", r.URL.Path)\n\n    if r.Method != http.MethodPut {\n        slog.Warn(\"Method not allowed\", \"method\", r.Method)\n        return httperr.Errorf(http.StatusMethodNotAllowed, \"Method not allowed\")\n    }\n\n    \/\/ Extract Pre-Signed URL\n    presignedURL := r.Header.Get(\"X-Presigned-URL\")\n    if presignedURL == \"\" {\n        slog.Warn(\"Missing X-Presigned-URL header\")\n        return httperr.Errorf(http.StatusBadRequest, \"Missing X-Presigned-URL header\")\n    }\n\n    \/\/ Validate Pre-Signed URL\n    url, err := url.Parse(presignedURL)\n    if err != nil {\n        slog.Warn(\"Invalid X-Presigned-URL header\", \"error\", err)\n        return httperr.Errorf(http.StatusBadRequest, \"Invalid X-Presigned-URL header\")\n    }\n    slog.Info(\"Using Pre-Signed URL\", \"url\", presignedURL)\n\n    \/\/ Configure reverse proxy\n    proxy := httputil.NewSingleHostReverseProxy(url)\n    proxy.Director = func(req *http.Request) {\n        req.URL = url\n        req.Host = url.Host\n        req.Method = r.Method\n        req.Header = r.Header.Clone() \/\/ Clone headers\n        req.Header.Del(\"X-Presigned-URL\")\n        req.ContentLength = r.ContentLength\n    }\n\n    \/\/ Handle proxy errors\n    proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) {\n        slog.Error(\"Proxy error\", \"error\", err)\n        http.Error(rw, \"Proxy error: \"+err.Error(), http.StatusBadGateway)\n    }\n\n    \/\/ Serve the proxied request\n    slog.Info(\"Forwarding request to S3\")\n    proxy.ServeHTTP(w, r)\n\n    return nil\n}\n\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p>\u0645\u0634\u062a\u0631\u06cc:<\/p>\n<p>\u06a9\u0644\u0627\u06cc\u0646\u062a \u062f\u0627\u062f\u0647 \u0647\u0627\u06cc \u062a\u06a9\u0647 \u062a\u06a9\u0647 \u0634\u062f\u0647 \u0631\u0627 \u0627\u0632 \u0637\u0631\u06cc\u0642 HTTP\/3 \u0628\u0627 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0627\u0632 \u0645\u0634\u062a\u0631\u06cc quic-go \u0627\u0631\u0633\u0627\u0644 \u0645\u06cc \u06a9\u0646\u062f. \u0647\u0631 \u062f\u0631\u062e\u0648\u0627\u0633\u062a \u062d\u0627\u0648\u06cc \u0647\u062f\u0631 X-Presigned-URL \u0628\u0627 URL S3 \u0648 \u0628\u0627\u0631 \u062a\u06a9\u0647 \u0627\u0633\u062a. \u0627\u06cc\u0646 \u0642\u0637\u0639\u0647 8 \u0645\u06af\u0627\u0628\u0627\u06cc\u062a\u06cc \u0627\u0631\u0633\u0627\u0644 \u0645\u06cc \u06a9\u0646\u062f<\/p>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>func (lu *Uploader) sendChunk(client *http.Client, chunkObject models.ChunkUrl, assetPath string, subtitleRelativeMap *sync.Map) error {\n    var err error\n    var dataReader io.Reader\n    for attempt := 0; attempt &lt; 4; attempt++ {\n        if strings.Contains(assetPath, \".tar\") {\n            lu.logger.Info(\"path\", \"path\", assetPath, \"offset\", chunkObject.Offset, \"size\", chunkObject.Size)\n            path := strings.TrimSuffix(assetPath, \".tar\")\n            lu.logger.Info(\"create.tar\", \"path\", filepath.Base(path))\n            dataReader, err = lu.createTar(path, subtitleRelativeMap)\n            if err != nil {\n                return err\n            }\n            _, err = io.CopyN(io.Discard, dataReader, chunkObject.Offset)\n            if err != nil {\n                return err\n            }\n\n            dataReader = io.LimitReader(dataReader, chunkObject.Size)\n\n        } else {\n            var file *os.File\n            file, err = os.Open(assetPath)\n            if err != nil {\n                return err\n            }\n            defer file.Close()\n\n            dataReader = io.NewSectionReader(file, chunkObject.Offset, chunkObject.Size)\n\n        }\n\n        req, err := http.NewRequest(http.MethodPut, \"https:\/\/localhost:4242\/post-reverse\", dataReader)\n        if err != nil {\n            return err\n        }\n        req.Header.Add(\"Content-Type\", \"application\/octet-stream\")\n        req.Header.Add(\"X-Presigned-URL\", chunkObject.UploadUrl)\n        req.ContentLength = chunkObject.Size\n\n        resp, err := client.Do(req)\n        if err != nil {\n            lu.logger.Error(\"upload.chunk.error\", \"attempt\", attempt, \"err\", err)\n            continue\n        }\n\n        \/\/ handle empty response\n        responseBody, _ := io.ReadAll(resp.Body)\n        defer resp.Body.Close()\n        if resp.StatusCode != http.StatusOK {\n            lu.logger.Error(\"upload.chunk.error\", \"attempt\", attempt, \"response\", string(responseBody))\n            time.Sleep(2 * time.Second)\n            continue\n        }\n        \/\/ SUCCESS\n        lu.logger.Info(\"upload.chunk.success\", \"path\", assetPath, \"offset\", chunkObject.Offset, \"size\", chunkObject.Size)\n        return nil\n    }\n    return err\n}\n\nfunc makeOptimizedClient() *http.Client {\n    tr := &amp;http3.Transport{\n        TLSClientConfig: &amp;tls.Config{InsecureSkipVerify: true},\n        QUICConfig:      &amp;quic.Config{},\n    }\n    defer tr.Close()\n    client := &amp;http.Client{\n        Transport: tr,\n        Timeout:   10 * time.Minute,\n    }\n    return client\n}\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p>\u0627\u0647\u062f\u0627\u0641 \u0645\u0646 \u0647\u0633\u062a\u0646\u062f<\/p>\n<p>\u0628\u0627 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0627\u0632 \u06cc\u06a9 \u067e\u0631\u0627\u06a9\u0633\u06cc \u0645\u0639\u06a9\u0648\u0633 \u06a9\u0647 \u0627\u0632 QUIC \u0628\u0631\u0627\u06cc \u0627\u0646\u062a\u0642\u0627\u0644 \u062f\u0627\u062f\u0647 \u0628\u0627 \u062a\u0623\u062e\u06cc\u0631 \u06a9\u0645 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0645\u06cc \u06a9\u0646\u062f\u060c \u0622\u067e\u0644\u0648\u062f\u0647\u0627\u06cc \u062a\u06a9\u0647 \u0627\u06cc \u06a9\u0627\u0631\u0622\u0645\u062f \u0631\u0627 \u062f\u0631 S3 \u0641\u0639\u0627\u0644 \u06a9\u0646\u06cc\u062f. \u0627\u0632 \u0627\u0631\u0633\u0627\u0644 \u0645\u0648\u0641\u0642\u06cc\u062a \u0622\u0645\u06cc\u0632 \u062f\u0627\u062f\u0647 \u0647\u0627\u06cc \u062a\u06a9\u0647 \u062a\u06a9\u0647 \u0634\u062f\u0647 \u0627\u0632 \u0645\u0634\u062a\u0631\u06cc \u0628\u0647 URL \u0627\u0632 \u067e\u06cc\u0634 \u0627\u0645\u0636\u0627 \u0634\u062f\u0647 S3 \u0627\u0632 \u0637\u0631\u06cc\u0642 \u067e\u0631\u0627\u06a9\u0633\u06cc \u0645\u0639\u06a9\u0648\u0633 \u0627\u0637\u0645\u06cc\u0646\u0627\u0646 \u062d\u0627\u0635\u0644 \u06a9\u0646\u06cc\u062f. \u067e\u0633 \u0627\u0632 \u0622\u067e\u0644\u0648\u062f S3\u060c \u067e\u0627\u0633\u062e\u200c\u0647\u0627\u06cc \u0645\u0646\u0627\u0633\u0628 (\u0645\u0627\u0646\u0646\u062f HTTP 200 \u0628\u0631\u0627\u06cc \u0622\u067e\u0644\u0648\u062f\u0647\u0627\u06cc \u0645\u0648\u0641\u0642 \u06cc\u0627 \u06a9\u062f\u0647\u0627\u06cc \u062e\u0637\u0627 \u0628\u0631\u0627\u06cc \u0645\u0634\u06a9\u0644\u0627\u062a) \u0631\u0627 \u0628\u0647 \u0645\u0634\u062a\u0631\u06cc \u0627\u0631\u0627\u0626\u0647 \u062f\u0647\u06cc\u062f.<\/p>\n<p>\u0645\u0634\u06a9\u0644\u0627\u062a: \u067e\u0631\u0648\u06a9\u0633\u06cc \u0646\u0627\u0645\u0648\u0641\u0642 \u0628\u0647 S3:<\/p>\n<p>\u0647\u0646\u06af\u0627\u0645\u06cc \u06a9\u0647 \u0633\u0631\u0648\u0631 \u062f\u0631\u062e\u0648\u0627\u0633\u062a \u062a\u06a9\u0647 \u062a\u06a9\u0647 \u0634\u062f\u0647 \u0631\u0627 \u0628\u0647 URL \u0627\u0632 \u067e\u06cc\u0634 \u0627\u0645\u0636\u0627 \u0634\u062f\u0647 S3 \u0645\u06cc \u0641\u0631\u0633\u062a\u062f\u060c \u062a\u0646\u0647\u0627 \u0686\u06cc\u0632\u06cc \u06a9\u0647 \u062f\u0631\u06cc\u0627\u0641\u062a \u0645\u06cc \u06a9\u0646\u0645 502 Bad Gateway \u0628\u0627 \u062e\u0637\u0627 \u0627\u0633\u062a: http3: \u062a\u062c\u0632\u06cc\u0647 \u0641\u0631\u06cc\u0645 \u0627\u0646\u062c\u0627\u0645 \u0646\u0634\u062f: \u0645\u0647\u0644\u062a \u0632\u0645\u0627\u0646\u06cc: \u0647\u06cc\u0686 \u0641\u0639\u0627\u0644\u06cc\u062a \u0627\u062e\u06cc\u0631 \u0634\u0628\u06a9\u0647 \u0648\u062c\u0648\u062f \u0646\u062f\u0627\u0631\u062f.<\/p>\n<p>\u0645\u0646 \u0633\u0639\u06cc \u06a9\u0631\u062f\u0645 \u0627\u0632 \u0647\u0645\u0627\u0646 \u06a9\u062f \u0628\u0627 \u06cc\u06a9 \u0633\u0631\u0648\u06cc\u0633 \u06af\u06cc\u0631\u0646\u062f\u0647 \u0648 \u0633\u0631\u0648\u0631 HTTP \u0627\u0635\u0644\u06cc \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u06a9\u0646\u0645 \u0648 \u0628\u0627 \u0627\u062a\u0635\u0627\u0644\u0627\u062a TCP \u062e\u0648\u0628 \u06a9\u0627\u0631 \u0645\u06cc \u06a9\u0646\u062f. \u0628\u0627 \u0627\u06cc\u0646 \u062d\u0627\u0644\u060c \u0648\u0642\u062a\u06cc \u0628\u0647 \u067e\u06cc\u0627\u062f\u0647 \u0633\u0627\u0632\u06cc QUIC \u062a\u063a\u06cc\u06cc\u0631 \u0645\u06cc \u06a9\u0646\u0645\u060c \u0634\u0631\u0648\u0639 \u0628\u0647 \u067e\u0631\u062a\u0627\u0628 \u062e\u0637\u0627 \u0645\u06cc \u06a9\u0646\u062f.<\/p>\n<p>\u0644\u0637\u0641\u0627 \u06a9\u0645\u06a9 \u06a9\u0646\u06cc\u062f\u060c \u0648 \u0627\u06af\u0631 \u0633\u0648\u0627\u0644 \u0645\u0646 \u0646\u0627\u0645\u0634\u062e\u0635 \u0627\u0633\u062a\u060c \u0628\u0631\u0627\u06cc \u062a\u0648\u0636\u06cc\u062d \u0628\u062e\u0648\u0627\u0647\u06cc\u062f.<\/p>\n<\/p><\/div>\n","protected":false},"excerpt":{"rendered":"<p>\u0633\u0644\u0627\u0645 \u0627\u0646\u062c\u0645\u0646!! \u0645\u0646 \u0631\u0648\u06cc \u067e\u0631\u0648\u0698\u0647\u200c\u0627\u06cc \u06a9\u0627\u0631 \u0645\u06cc\u200c\u06a9\u0646\u0645 \u06a9\u0647 \u062f\u0631 \u0622\u0646 \u0627\u0632 \u06cc\u06a9 \u067e\u0631\u0648\u06a9\u0633\u06cc \u0645\u0639\u06a9\u0648\u0633 \u0645\u0628\u062a\u0646\u06cc \u0628\u0631 QUIC (\u06a9\u0647 \u0628\u0627 \u06a9\u062a\u0627\u0628\u062e\u0627\u0646\u0647 quic-go \u067e\u06cc\u0627\u062f\u0647\u200c\u0633\u0627\u0632\u06cc \u0634\u062f\u0647 \u0627\u0633\u062a) \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0645\u06cc\u200c\u06a9\u0646\u0645 \u062a\u0627 \u0622\u067e\u0644\u0648\u062f\u0647\u0627\u06cc \u062f\u0627\u062f\u0647\u200c\u0647\u0627\u06cc \u062a\u06a9\u0647\u200c\u0634\u062f\u0647 \u0631\u0627 \u0628\u0647 URL\u0647\u0627\u06cc \u0627\u0632 \u067e\u06cc\u0634 \u0627\u0645\u0636\u0627\u0634\u062f\u0647 AWS S3 \u0627\u0631\u0633\u0627\u0644 \u06a9\u0646\u0645. \u062f\u0631 \u0627\u06cc\u0646\u062c\u0627 \u06cc\u06a9 \u0645\u0631\u0648\u0631 \u06a9\u0644\u06cc \u0627\u0632 \u0631\u0627\u0647 \u0627\u0646\u062f\u0627\u0632\u06cc\u060c \u0627\u0647\u062f\u0627\u0641\u060c \u0648 \u0645\u0634\u06a9\u0644\u0627\u062a\u06cc \u06a9\u0647 \u0628\u0627 \u0622\u0646 \u0631\u0648\u0628\u0631\u0648 &hellip;<\/p>\n","protected":false},"author":2,"featured_media":90337,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"fifu_image_url":"","fifu_image_alt":"","footnotes":""},"categories":[339],"tags":[],"class_list":["post-90336","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dev"],"_links":{"self":[{"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/posts\/90336","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/comments?post=90336"}],"version-history":[{"count":0,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/posts\/90336\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/media\/90337"}],"wp:attachment":[{"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/media?parent=90336"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/categories?post=90336"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/tags?post=90336"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}