NetCore3.0 文件上傳與大文件上傳的限制

NetCore文件上傳兩種方式

  NetCore官方給出的兩種文件上傳方式分別爲「緩衝」、「流式」。我簡單的說說兩種的區別,web

  1.緩衝:經過模型綁定先把整個文件保存到內存,而後咱們經過IFormFile獲得stream,優勢是效率高,缺點對內存要求大。文件不宜過大。api

  2.流式處理:直接讀取請求體裝載後的Section 對應的stream 直接操做strem便可。無需把整個請求體讀入內存,緩存

如下爲官方微軟說法

緩衝

  整個文件讀入 IFormFile,它是文件的 C# 表示形式,用於處理或保存文件。 文件上傳所用的資源(磁盤、內存)取決於併發文件上傳的數量和大小。 若是應用嘗試緩衝過多上傳,站點就會在內存或磁盤空間不足時崩潰。 若是文件上傳的大小或頻率會消耗應用資源,請使用流式傳輸。服務器

流式處理   

  從多部分請求收到文件,而後應用直接處理或保存它。 流式傳輸沒法顯著提升性能。 流式傳輸可下降上傳文件時對內存或磁盤空間的需求。併發

文件大小限制

  提及大小限制,咱們得從兩方面入手,1應用服務器Kestrel 2.應用程序(咱們的netcore程序),dom

1.應用服務器Kestre設置

  應用服務器Kestrel對咱們的限制主要是對整個請求體大小的限制經過以下配置能夠進行設置(Program -> CreateHostBuilder),超出設置範圍會報 BadHttpRequestException: Request body too large 異常信息async

public static IHostBuilder CreateHostBuilder(string[] args) =>
           Host.CreateDefaultBuilder(args)
               .ConfigureWebHostDefaults(webBuilder =>
               {
                   webBuilder.ConfigureKestrel((context, options) =>
                   {
                       //設置應用服務器Kestrel請求體最大爲50MB
                       options.Limits.MaxRequestBodySize = 52428800;
                   });
                   webBuilder.UseStartup<Startup>();
});

2.應用程序設置

  應用程序設置 (Startup->  ConfigureServices) 超出設置範圍會報InvalidDataException 異常信息ide

services.Configure<FormOptions>(options =>
 {
             options.MultipartBodyLengthLimit = long.MaxValue;
 });

經過設置即重置文件上傳的大小限制。源碼分析

源碼分析

  這裏我主要說一下 MultipartBodyLengthLimit  這個參數他主要限制咱們使用「緩衝」形式上傳文件時每一個的長度。爲何說是緩衝形式中,是由於咱們緩衝形式在讀取上傳文件用的幫助類爲 MultipartReaderStream 類下的 Read 方法,此方法在每讀取一次後會更新下讀入的總byte數量,當超過此數量時會拋出  throw new InvalidDataException($"Multipart body length limit {LengthLimit.GetValueOrDefault()} exceeded.");  主要體如今 UpdatePosition 方法對 _observedLength  的判斷性能

如下爲 MultipartReaderStream 類兩個方法的源代碼,爲方便閱讀,我已精簡掉部分代碼

Read

public override int Read(byte[] buffer, int offset, int count)
 {
          
          var bufferedData = _innerStream.BufferedData;
      int read;
      read = _innerStream.Read(buffer, offset, Math.Min(count, bufferedData.Count));
          return UpdatePosition(read);
}

UpdatePosition

private int UpdatePosition(int read)
        {
            _position += read;
            if (_observedLength < _position)
            {
                _observedLength = _position;
                if (LengthLimit.HasValue && _observedLength > LengthLimit.GetValueOrDefault())
                {
                    throw new InvalidDataException($"Multipart body length limit {LengthLimit.GetValueOrDefault()} exceeded.");
                }
            }
            return read;
}

經過代碼咱們能夠看到 當你作了 MultipartBodyLengthLimit 的限制後,在每次讀取後會累計讀取的總量,當讀取總量超出

 MultipartBodyLengthLimit  設定值會拋出 InvalidDataException 異常,

最終個人文件上傳Controller以下

  須要注意的是咱們建立 MultipartReader 時並未設置 BodyLengthLimit  (這參數會傳給 MultipartReaderStream.LengthLimit )也就是咱們最終的限制,這裏我未設置值也就無限制,能夠經過 UpdatePosition 方法體現出來

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;
using System.IO;
using System.Threading.Tasks;
 
namespace BigFilesUpload.Controllers
{
    [Route("api/[controller]")]
    public class FileController : Controller
    {
        private readonly string _targetFilePath = "C:\\files\\TempDir";
 
        /// <summary>
        /// 流式文件上傳
        /// </summary>
        /// <returns></returns>
        [HttpPost("UploadingStream")]
        public async Task<IActionResult> UploadingStream()
        {
 
            //獲取boundary
            var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value;
            //獲得reader
            var reader = new MultipartReader(boundary, HttpContext.Request.Body);
            //{ BodyLengthLimit = 2000 };//
            var section = await reader.ReadNextSectionAsync();
 
            //讀取section
            while (section != null)
            {
                var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition);
                if (hasContentDispositionHeader)
                {
                    var trustedFileNameForFileStorage = Path.GetRandomFileName();
                    await WriteFileAsync(section.Body, Path.Combine(_targetFilePath, trustedFileNameForFileStorage));
                }
                section = await reader.ReadNextSectionAsync();
            }
            return Created(nameof(FileController), null);
        }
 
        /// <summary>
        /// 緩存式文件上傳
        /// </summary>
        /// <param name=""></param>
        /// <returns></returns>
        [HttpPost("UploadingFormFile")]
        public async Task<IActionResult> UploadingFormFile(IFormFile file)
        {
            using (var stream = file.OpenReadStream())
            {
                var trustedFileNameForFileStorage = Path.GetRandomFileName();
                await WriteFileAsync(stream, Path.Combine(_targetFilePath, trustedFileNameForFileStorage));
            }
            return Created(nameof(FileController), null);
        }
 
 
        /// <summary>
        /// 寫文件導到磁盤
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="path">文件保存路徑</param>
        /// <returns></returns>
        public static async Task<int> WriteFileAsync(System.IO.Stream stream, string path)
        {
            const int FILE_WRITE_SIZE = 84975;//寫出緩衝區大小
            int writeCount = 0;
            using (FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Write, FILE_WRITE_SIZE, true))
            {
                byte[] byteArr = new byte[FILE_WRITE_SIZE];
                int readCount = 0;
                while ((readCount = await stream.ReadAsync(byteArr, 0, byteArr.Length)) > 0)
                {
                    await fileStream.WriteAsync(byteArr, 0, readCount);
                    writeCount += readCount;
                }
            }
            return writeCount;
        }
 
    }
}

 

 總結:

若是你部署 在iis上或者Nginx 等其餘應用服務器 也是須要注意的事情,由於他們自己也有對請求體的限制,還有值得注意的就是咱們在建立文件流對象時 緩衝區的大小盡可能不要超過netcore大對象的限制。這樣在併發高的時候很容易觸發二代GC的回收.

相關文章
相關標籤/搜索