You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# ASP.NET 不缓冲完整请求体如何读取请求Body前几个字节做验证

首先你之前的自定义拦截流稳定性差的核心原因是:首次ReadAsync返回字节数不足4时会直接标记校验完成,跳过校验逻辑,导致非法文件也可能通过校验。

以下是更稳定的实现方案,全程仅缓冲最多4个字节,完全不需要对整个请求流做磁盘/内存缓冲:

优化后拦截流实现

public class InterceptorStream : Stream
{
    private readonly Stream _innerStream;
    private readonly byte[] _prefixBuffer = new byte[4];
    private int _prefixBytesRead;
    private bool _isValidated;

    public InterceptorStream(Stream innerStream)
    {
        _innerStream = innerStream ?? throw new ArgumentNullException(nameof(innerStream));
    }

    public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
    {
        return await ReadAsync(buffer.AsMemory(offset, count), cancellationToken);
    }

    public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
    {
        // 校验已完成直接透传读取
        if (_isValidated)
        {
            return await _innerStream.ReadAsync(buffer, cancellationToken);
        }

        // 未凑够4字节前缀时持续读取
        if (_prefixBytesRead < _prefixBuffer.Length)
        {
            var remaining = _prefixBuffer.Length - _prefixBytesRead;
            var readCount = await _innerStream.ReadAsync(_prefixBuffer.AsMemory(_prefixBytesRead, remaining), cancellationToken);
            if (readCount == 0)
            {
                _isValidated = true;
                throw new InvalidDataException("非法文件:文件长度不足4字节");
            }
            _prefixBytesRead += readCount;

            // 凑够前缀后执行校验
            if (_prefixBytesRead == _prefixBuffer.Length)
            {
                _isValidated = true;
                if (!_prefixBuffer.SequenceEqual(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }))
                {
                    throw new InvalidDataException("非法文件:魔术字校验不通过");
                }
            }
        }

        // 将已读取的前缀字节复制到返回缓冲区
        var copyCount = Math.Min(_prefixBytesRead, buffer.Length);
        _prefixBuffer.AsSpan(0, copyCount).CopyTo(buffer.Span);
        _prefixBytesRead -= copyCount;
        // 剩余未返回的前缀字节移到缓冲区头部
        if (_prefixBytesRead > 0)
        {
            _prefixBuffer.AsSpan(copyCount, _prefixBytesRead).CopyTo(_prefixBuffer);
        }

        // 缓冲区还有剩余空间的话读取更多字节返回
        if (copyCount < buffer.Length)
        {
            var additionalCount = await _innerStream.ReadAsync(buffer.Slice(copyCount), cancellationToken);
            copyCount += additionalCount;
        }

        return copyCount;
    }

    // 其余Stream方法直接透传给内层流即可
    public override bool CanRead => _innerStream.CanRead;
    public override bool CanSeek => _innerStream.CanSeek;
    public override bool CanWrite => _innerStream.CanWrite;
    public override long Length => _innerStream.Length;
    public override long Position
    {
        get => _innerStream.Position;
        set => _innerStream.Position = value;
    }
    public override void Flush() => _innerStream.Flush();
    public override int Read(byte[] buffer, int offset, int count) => _innerStream.Read(buffer, offset, count);
    public override long Seek(long offset, SeekOrigin origin) => _innerStream.Seek(offset, origin);
    public override void SetLength(long value) => _innerStream.SetLength(value);
    public override void Write(byte[] buffer, int offset, int count) => _innerStream.Write(buffer, offset, count);
}

控制器改造

不需要再调用Request.EnableBuffering(),直接用拦截流包装请求体传入SDK即可:

public async Task<IActionResult> Post()
{
    using var interceptedStream = new InterceptorStream(Request.Body);
    // SDK读取流时会自动触发校验,校验失败会直接抛出异常
    await client.ProcessFileAsync(interceptedStream);
    return StatusCode(201, id);
}

方案优势

  • 全程仅缓冲最多4个字节,不会将整个请求流写入内存或磁盘,内存占用极低
  • 不存在校验跳过的情况:要么凑够4字节完成校验,要么流提前结束直接判定为非法
  • 所有读取到的字节都会完整返回给调用方,不会出现数据丢失
  • 不需要修改外部SDK和原有校验逻辑,侵入性极低

内容的提问来源于stack exchange,提问作者NotFound

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 03:45:04