Echo 服务器重启时恢复下载

问题描述 投票:0回答:1

我正在尝试制作一个代理来下载具有身份验证层的服务器文件。

我正在使用 Golang (1.21.0)Echo (4.11.1)

问题描述

当用户下载大文件时,如果我终止 Echo 服务器(Ctrl+C),下载只会被标记为“终止”,而不是“取消”或处于“中断错误”状态。

因此,当服务器稍后启动时,用户无法重试并恢复下载(使用范围标头)...

问题

有没有办法在停止服务器时等待每个连接以优雅地中断它而不终止用户下载?

或者,更好的是,有没有办法将下载标记为“失败”而不仅仅是“终止”?

使用的代码

package main

import (
    "net/http"

    "github.com/labstack/echo/v4"
)

func main() {
    e := echo.New()
    e.GET("/file", func(c echo.Context) error {
        return c.Attachment("big_file.zip", "original file name.zip")
    })
    e.Logger.Fatal(e.Start(":1323"))
}
go download interrupt go-echo
1个回答
0
投票

我猜您面临的问题是,当用户下载文件时停止 Echo 服务器时,下载会被标记为“终止”,而不是“取消”或处于“中断错误”状态。这可以防止用户重试下载或稍后使用范围标头恢复下载。

要解决此问题,您可以创建

http.Handler
接口的自定义实现,并在 Echo 中使用它来处理文件下载。此自定义处理程序可以跟踪活动下载并在服务器停止时优雅地中断它们。

以下是如何修改代码以实现此目的的示例:


import (
    "github.com/labstack/echo/v4"
)

type downloadHandler struct {
    activeDownloads map[*http.ResponseWriter]chan struct{}
    mu              sync.Mutex
}

func (h *downloadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    h.mu.Lock()
    defer h.mu.Unlock()

    // Create a channel to track this download
    done := make(chan struct{})
    h.activeDownloads[&w] = done

    // Remove the download channel when the response writer is closed
    wrapper := &responseWriterWrapper{
        ResponseWriter: w,
        onClose: func() {
            h.mu.Lock()
            defer h.mu.Unlock()
            delete(h.activeDownloads, &w)
            close(done)
        },
    }

    // Handle the file download
    err := h.handleDownload(wrapper, r)

    // Handle any errors during the download
    if err != nil {
        if errors.Is(err, os.ErrClosed) {
            // The server was stopped, so the download was interrupted
            wrapper.WriteHeader(http.StatusRequestTimeout)
        } else {
            // Other error occurred, mark the download as failed
            wrapper.WriteHeader(http.StatusInternalServerError)
        }
    }

    // Ensure the response is flushed before returning
    wrapper.Flush()
}

func (h *downloadHandler) handleDownload(w http.ResponseWriter, r *http.Request) error {
    // Implement your file downloading logic here
    // You can use the "http.ServeFile" or any other method

    filePath := "big_file.zip"
    file, err := os.Open(filePath)
    if err != nil {
        return err
    }
    defer file.Close()

    // Set the necessary headers for file download
    w.Header().Set("Content-Disposition", "attachment; filename=original file name.zip")
    w.Header().Set("Content-Type", "application/octet-stream")

    // Copy the file contents to the response writer
    _, err = io.Copy(w, file)
    if err != nil {
        return err
    }

    return nil
}

type responseWriterWrapper struct {
    http.ResponseWriter
    onClose func()
}

func (w *responseWriterWrapper) CloseNotify() <-chan bool {
    return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
}

func (w *responseWriterWrapper) CloseNotifyChan() chan struct{} {
    c := make(chan struct{})
    go func() {
        select {
        case <-w.ResponseWriter.(http.CloseNotifier).CloseNotify():
        case <-c:
        }
        w.onClose()
    }()
    return c
}

func main() {
    // Set up a signal channel to listen for server stop signal
    stop := make(chan os.Signal, 1)
    signal.Notify(stop, os.Interrupt)

    // Initialize the download handler and map to store active downloads
    handler := &downloadHandler{
        activeDownloads: make(map[*http.ResponseWriter]chan struct{}),
    }

    // Create a new Echo server
    e := echo.New()

    // Register the custom download handler
    e.GET("/file", echo.WrapHandler(handler))

    // Start the server in a goroutine
    go func() {
        if err := e.Start(":1323"); err != nil {
            e.Logger.Fatal(err)
        }
    }()

    // Wait for the stop signal
    <-stop

    // Stop the server gracefully
    if err := e.Shutdown(); err != nil {
        e.Logger.Fatal(err)
    }

    // Notify all active downloads about the interruption
    handler.mu.Lock()
    defer handler.mu.Unlock()
    for _, done := range handler.activeDownloads {
        close(done)
    }
}

在此修改后的代码中,实现了自定义

downloadHandler
结构来处理文件下载。它在地图中跟踪活动下载,其中键是指向
http.ResponseWriter
的指针,值是用于跟踪下载状态的通道 (
done
)。

当收到请求时,会创建一个新的

done
通道并将其添加到地图中。然后,请求和响应将传递到
handleDownload
方法,您可以在其中实现文件下载逻辑。

responseWriterWrapper
包装了原始的
http.ResponseWriter
以拦截
CloseNotify
方法并在响应编写器关闭时调用
onClose
回调。这使我们能够从地图上删除下载通道并优雅地中断下载。

当服务器收到停止信号(例如 Ctrl+C)时,它会使用

e.Shutdown()
正常关闭。在退出之前,它会通过关闭各自的
done
通道来通知所有活动下载有关中断的信息。

此实现可确保在服务器停止时正常中断下载,从而允许用户稍后重试或恢复下载。

有点长,抱歉。

© www.soinside.com 2019 - 2024. All rights reserved.