feat: added feature of http/2 chunk load

This commit is contained in:
Pavel-Savely Savianok 2025-07-08 14:41:46 +03:00
parent a2555a7ce5
commit 4665c11e25
8 changed files with 143 additions and 2 deletions

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace HTTP2FileStreams;
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var formValueProviderFactory = context.ValueProviderFactories
.OfType<FormValueProviderFactory>()
.FirstOrDefault();
if (formValueProviderFactory != null)
{
context.ValueProviderFactories.Remove(formValueProviderFactory);
}
}
public void OnResourceExecuted(ResourceExecutedContext context) { }
}

View File

@ -6,4 +6,14 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<Content Update="wwwroot\index.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Folder Include="Uploads\" />
</ItemGroup>
</Project>

View File

@ -1,3 +1,5 @@
using Microsoft.AspNetCore.Server.Kestrel.Core;
namespace HTTP2FileStreams;
public class Program
@ -5,9 +7,19 @@ public class Program
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
builder.WebHost.ConfigureKestrel(options =>
{
options.ConfigureEndpointDefaults(endpointOptions =>
{
endpointOptions.Protocols = HttpProtocols.Http2;
});
});
builder.Services.AddControllers();
app.MapGet("/", () => "Hello World!");
var app = builder.Build();
app.MapControllers();
app.UseDefaultFiles();
app.UseStaticFiles();
app.Run();
}

33
UploadController.cs Normal file
View File

@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Mvc;
namespace HTTP2FileStreams;
[ApiController]
[Route("api/[controller]")]
public class UploadController : ControllerBase
{
[HttpPost]
[DisableFormValueModelBinding] // Отключаем стандартный парсинг формы
public async Task<IActionResult> Upload()
{
// Получаем имя файла из заголовка
var fileName = Request.Headers["X-File-Name"].ToString();
if (string.IsNullOrEmpty(fileName))
return BadRequest("File name header is missing");
fileName = Uri.UnescapeDataString(fileName);
// Потоковая запись файла
if (!Directory.Exists("Uploads"))
Directory.CreateDirectory("Uploads");
var filePath = Path.Combine("Uploads", fileName);
await using var fileStream = System.IO.File.Create(filePath);
await Request.Body.CopyToAsync(fileStream);
return Ok(new {
fileName,
size = fileStream.Length
});
}
}

1
Uploads/Blockbench.exe Normal file
View File

@ -0,0 +1 @@
[object ReadableStream]

51
wwwroot/index.html Normal file
View File

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>HTTP2FileStreams test</title>
</head>
<input type="file" id="input"/>
<button onclick="onFileSelected()">Загрузить</button>
<script>
function onFileSelected(){
const inputElement = document.getElementById("input").files[0];
uploadFile(inputElement, console.log);
}
async function uploadFile(file, onProgress) {
const endpoint = '/api/upload';
// Создаем TransformStream для отслеживания прогресса
let uploadedBytes = 0;
const progressStream = new TransformStream({
transform(chunk, controller) {
controller.enqueue(chunk);
uploadedBytes += chunk.length;
const percent = Math.round((uploadedBytes / file.size) * 100);
onProgress(percent);
}
});
try {
// Создаем поток для чтения файла
const fileStream = file.stream();
// Соединяем потоки
const streamWithProgress = fileStream.pipeThrough(progressStream);
// Отправляем запрос
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/octet-stream',
'X-File-Name': encodeURIComponent(file.name),
},
body: streamWithProgress,
duplex: 'half' // Обязательно для потоковой отправки
});
return await response.json();
} catch (error) {
throw new Error(`Upload failed: ${error.message}`);
}
}
</script>
</html>