@loosername

Как можно реализовать ограничение на одновременное скачивание файлов?

Есть сервис, который отдает пользователям видео файлы, реализован метод для скачивания файла, который отдает файл через метод File
[HttpGet("file/{guid}/download")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        public async Task<IActionResult> DownloadFile([FromRoute] Guid guid)
        {
            var file = await _mediaContentService.DownloadFile(guid);

            return File(file.Stream, file.ContentType, file.FileName);
        }

Можно ли каким-либо образом реализовать ограничение на скачивание файла, чтобы, например, в единицу времени не больше N пользователей качало файл.
Пробовал реализовать через semaphore, но после возврата из метода он освобождается, а файл еще докачивается
[HttpGet("file/{guid}/download")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        public async Task<IActionResult> DownloadFile([FromRoute] Guid guid)
        {
            try
            {
                await _semaphore.WaitAsync();
                var file = await _mediaContentService.DownloadFile(guid);

                return File(file.Stream, file.ContentType, file.FileName);
            }
            finally
            {
                _semaphore.Release();
            }
            
        }

, соответственно, ограничение не работает.
Может быть, кто-то реализовывал такой функционал и поможет советом?
P.S. S3 хранилище использовать не можем
  • Вопрос задан
  • 204 просмотра
Решения вопроса 1
AshBlade
@AshBlade Куратор тега C#
Просто хочу быть счастливым
Проблема с методом File - он возвращает ленивый ответ. Т.е. он не читает весь поток сразу, а ждет пока будет вызван метод от IActionResult, поэтому семафор берется и сразу же отпускается.
Чтобы эту проблему решить надо знать, когда файл точно отправлен.
Предлагаю сделать декоратор специальный. Например:

[ApiController]
[Route("[controller]")]
public class SampleController : ControllerBase
{
    private readonly IRateLimiter _rateLimiter;

    public SampleController(IRateLimiter rateLimiter)
    {
        _rateLimiter = rateLimiter;
    }

    [HttpGet("connection")]
    public async Task<IActionResult> DownLoadFile(Guid file)
    {
        var stream = await GetFileStream(file);
        return new RateLimiterFileActionResult(File(stream, "content/type", "sample.txt"), _rateLimiter);
    }
}

class RateLimiterFileActionResult : IActionResult
{
    private readonly IActionResult _actionResultImplementation;
    private readonly IRateLimiter _rateLimiter;

    public RateLimiterFileActionResult(IActionResult actionResultImplementation, IRateLimiter rateLimiter)
    {
        _actionResultImplementation = actionResultImplementation;
        _rateLimiter = rateLimiter;
    }

    public async Task ExecuteResultAsync(ActionContext context)
    {
        try
        {
            await _rateLimiter.ObtainAsync(context.HttpContext.RequestAborted);
            await _actionResultImplementation.ExecuteResultAsync(context);
        }
        finally
        {
            await _rateLimiter.ReleaseAsync(context.HttpContext.RequestAborted);
        }
    }
}

public interface IRateLimiter
{
    public Task ObtainAsync(CancellationToken token);
    public Task ReleaseAsync(CancellationToken token);
}


Здесь используется не семафор, а специальный интерфейс, хотя разницы особо нет.
Также, стоит заметить, что блокировка берется внутри метода декоратора, а не в методе контроллера - например, пользователь отменит запрос и тогда надо будет думать как семафор отпустить - в этой реализации думать не надо.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы