Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example crashed with file size > 50kb #49

Open
EricNgo1972 opened this issue Nov 20, 2023 · 4 comments
Open

Example crashed with file size > 50kb #49

EricNgo1972 opened this issue Nov 20, 2023 · 4 comments

Comments

@EricNgo1972
Copy link

EricNgo1972 commented Nov 20, 2023

I cloned your repository and ran it locally. On the /ViewZipFile page, if I select a zip file with a size above 50 KB, it crashes at:

     var buffer = await file.ArrayBufferAsync(); // <<----------crash happen at this line.
     using var stream = new MemoryStream(buffer);
     using var archive = new ZipArchive(stream, ZipArchiveMode.Read, true);

with the exception:

blazor.server.js:1 [2023-11-20T04:37:04.932Z] Error: Connection disconnected with error 'Error: Server returned an error on close: Connection closed with an error.'.

However your app at https://kristofferstrube.github.io/Blazor.FileSystemAccess/ViewZipFile works fine with any file size.

Is there any size limitation we need to set?

@KristofferStrube
Copy link
Owner

You may be using it in a Blazor Server App or a Blazor Web App project from the exception I see you get. Is that correct?

As I note in the project README under the Usage Section:

The package can be used in Blazor WebAssembly and Blazor Server projects. (Note that streaming of big files is not supported in Blazor Server due to bandwidth problems.)

This is still a valid issue though, as that is a very valid use case. I just haven't prioritized this previously. Actually, this is more related to my Blazor.Streams project, so I might end up creating an issue there. What I have done previously, for my own customers, has been to get a stream from the file using the StreamAsync method and then reading small chunks of the file into an intermediate buffer while it is streamed like I have done here in my ReadableStream implementation but limiting the buffer size and putting in some Task.Yields or Delays between reading each chunk so that the Blazor Server circuit isn't blocked by this work in too long consecutive periods. This likewise opens up the possibility to update the user with upload progress while it happens i.e "Uploading progress 23% ..."

A more straightforward way to get around this limitation is to increase the message size limit for the SignalR connection to however big files you want to send which could look like this when configuring Server Side SignalR Hub connection settings in a Blazor Server project:

builder.Services.AddServerSideBlazor().AddHubOptions(o =>
{
    o.MaximumReceiveMessageSize = 1024 * 1024; // This is 1 Megabyte
});

@EricNgo1972
Copy link
Author

Adding MaximumReceivemessageSize option indeed resolved the limitation. Thanks.

@KristofferStrube
Copy link
Owner

I'll keep this issue open for bit longer so that I that I can keep track of the use case and either add more documentation for this case or resolve it in the Blazor.Streams project.

@georg-jung
Copy link

georg-jung commented May 21, 2024

I think I found a rather easy and quite flexible streaming solution. Not sure if it has any downsides I'm currently overlooking, but it seems to work well for me without changing the SignalR message size. I tested it with files of around ~25 MB, which worked on localhost with a minor delay (roughly >1s <5s on my machine). It uses the same mechanisms as Blazor's InputFile component:

internal static class BlazorFileSystemAccessExtensions
{
    public static async Task<IJSStreamReference> GetFileStream(this FileSystemFileHandle fileHandle, CancellationToken cancellationToken = default)
    {
        return await fileHandle.JSReference.InvokeAsync<IJSStreamReference>("getFile", cancellationToken);
    }

    public static async Task<byte[]> ReadAllBytes(this IJSStreamReference jsStream, long maxAllowedSize = 512000, CancellationToken cancellationToken = default)
    {
        await using var s = await jsStream.OpenReadStreamAsync(maxAllowedSize, cancellationToken);
        var buffer = new byte[s.Length];
        using var ms = new MemoryStream(buffer);
        await s.CopyToAsync(ms, cancellationToken);
        return buffer;
    }

    public static async Task<byte[]> ReadAllBytes(this FileSystemFileHandle fileHandle, long maxAllowedSize = 512000, CancellationToken cancellationToken = default)
    {
        await using var fs = await fileHandle.GetFileStream(cancellationToken);
        return await fs.ReadAllBytes(maxAllowedSize, cancellationToken);
    }
}

Note that Microsoft recommends to avoid the ReadAllBytes pattern:

Avoid reading the incoming file stream directly into memory all at once. For example, don't copy all of the file's bytes into a MemoryStream or read the entire stream into a byte array all at once. These approaches can result in performance and security problems, especially for server-side components.

It could lead to DoS attacks / high memory pressure if the maxAllowedSize is chosen too large. My approach shares this downsides though with the corresponding implementations in Blazor.FileSystemAccess. Only using GetFileStream is not against the advice.

This works because the File object returned by FileSystemHandle.getFile() is a Blob and Blazor JS Interop can return IJSStreamReference for these.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants