Skip to content

Commit dd5863f

Browse files
committed
Add hostname-based Brotli support for brotli.swimburger.net
The same static deployment now serves both gzip.swimburger.net and brotli.swimburger.net. On a brotli hostname, App.razor switches to BrotliStream for compress/decompress and uses .br file extensions; an inline script rewrites the page title, meta tags, heading, and intro paragraph before Blazor boots so crawlers see correct content. - App.razor: inject NavigationManager, detect hostname in OnInitialized, dispatch to BrotliStream or GZipStream via helper methods - wwwroot/index.html: inline script at end of body updates all user-visible and SEO text for the brotli domain - wwwroot/staticwebapp.config.json: SPA navigation fallback so direct URL hits return index.html instead of 404 - e2e/tests/app.spec.js: brotli describe block covering title, heading, and intro paragraph (Blazor component behaviour requires a real brotli.* hostname so is covered by manual testing)
1 parent 503b256 commit dd5863f

4 files changed

Lines changed: 76 additions & 7 deletions

File tree

App.razor

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@using System.IO
22
@using System.IO.Compression
33
@inject BlazorDownloadFile.IBlazorDownloadFileService BlazorDownloadFileService
4+
@inject NavigationManager NavManager
45

56
<div class="mb-3">
67
<div class="form-check form-check-inline">
@@ -52,11 +53,11 @@
5253
<div class="mb-3">
5354
@if (compressionMode == CompressionMode.Compress)
5455
{
55-
<label class="form-label">Choose files to compress to gzip</label>
56+
<label class="form-label">Choose files to compress to @(_format == CompressionFormat.Brotli ? "brotli" : "gzip")</label>
5657
}
5758
else
5859
{
59-
<label class="form-label">Choose gzip files to decompress</label>
60+
<label class="form-label">Choose @(_format == CompressionFormat.Brotli ? "brotli" : "gzip") files to decompress</label>
6061
}
6162
<InputFile OnChange="OnFilesChange" multiple class="form-control" />
6263
</div>
@@ -102,11 +103,32 @@
102103
</ul>
103104

104105
@code {
106+
private enum CompressionFormat { GZip, Brotli }
107+
105108
private CompressionMode compressionMode = CompressionMode.Compress;
106109
private CompressionLevel compressionLevel = CompressionLevel.Optimal;
107110
private List<Models.File> files = new List<Models.File>();
108111
private bool anyFiles => files.Any();
109112

113+
private CompressionFormat _format = CompressionFormat.GZip;
114+
private string Extension => _format == CompressionFormat.Brotli ? ".br" : ".gz";
115+
116+
protected override void OnInitialized()
117+
{
118+
if (new Uri(NavManager.Uri).Host.Contains("brotli"))
119+
_format = CompressionFormat.Brotli;
120+
}
121+
122+
private Stream CreateCompressStream(Stream output, CompressionLevel level) =>
123+
_format == CompressionFormat.Brotli
124+
? new BrotliStream(output, level)
125+
: new GZipStream(output, level);
126+
127+
private Stream CreateDecompressStream(Stream input) =>
128+
_format == CompressionFormat.Brotli
129+
? new BrotliStream(input, CompressionMode.Decompress)
130+
: new GZipStream(input, CompressionMode.Decompress);
131+
110132
private void OnFilesChange(InputFileChangeEventArgs e)
111133
{
112134
files.Clear();
@@ -126,7 +148,7 @@
126148
var task = Task.Run(async () =>
127149
{
128150
file.Status = "Compressing";
129-
file.NewName = $"{file.OriginalName}.gz";
151+
file.NewName = $"{file.OriginalName}{Extension}";
130152
StateHasChanged();
131153

132154
try
@@ -136,7 +158,7 @@
136158
await browserFile.OpenReadStream(long.MaxValue).ReadAsync(buffer);
137159
using (var outputStream = new MemoryStream())
138160
{
139-
using (var compressionStream = new GZipStream(outputStream, compressionLevel))
161+
using (var compressionStream = CreateCompressStream(outputStream, compressionLevel))
140162
{
141163
await compressionStream.WriteAsync(buffer, 0, buffer.Length);
142164
}
@@ -165,7 +187,7 @@
165187
var task = Task.Run(async () =>
166188
{
167189
file.Status = "Decompressing";
168-
file.NewName = file.OriginalName.Replace(".gz", "");
190+
file.NewName = file.OriginalName.Replace(Extension, "");
169191
StateHasChanged();
170192

171193
try
@@ -176,7 +198,7 @@
176198
using (var inputStream = new MemoryStream(buffer))
177199
using (var outputStream = new MemoryStream())
178200
{
179-
using (var compressionStream = new GZipStream(inputStream, CompressionMode.Decompress))
201+
using (var compressionStream = CreateDecompressStream(inputStream))
180202
{
181203
await compressionStream.CopyToAsync(outputStream);
182204
}

e2e/tests/app.spec.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,24 @@ test('decompress a file end-to-end', async ({ page }) => {
8080
fs.unlinkSync(srcFile);
8181
fs.unlinkSync(gzFile);
8282
});
83+
84+
// Brotli static content (heading, title, meta) is driven by the inline JS in index.html,
85+
// which reads window.compressionFormat. We spoof it here via addInitScript.
86+
// Blazor component behaviour (labels, file extension) uses NavigationManager and requires
87+
// a real "brotli.*" hostname, so those paths are covered by manual / deployment testing.
88+
test.describe('brotli mode', () => {
89+
test.beforeEach(async ({ page }) => {
90+
await page.addInitScript(() => { window.compressionFormat = 'brotli'; });
91+
await page.goto('/');
92+
await page.waitForSelector('.spinner-border', { state: 'hidden', timeout: 30000 });
93+
});
94+
95+
test('page title and heading reflect brotli', async ({ page }) => {
96+
await expect(page).toHaveTitle(/Brotli/i);
97+
await expect(page.getByRole('heading', { name: /Online Brotli de\/compressor/i })).toBeVisible();
98+
});
99+
100+
test('intro paragraph mentions brotli', async ({ page }) => {
101+
await expect(page.locator('#intro-paragraph')).toContainText(/Brotli/i);
102+
});
103+
});

wwwroot/index.html

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@
9393
<main class="flex-shrink-0">
9494
<div class="container">
9595
<h1 class="display-4">Online GZIP de/compressor</h1>
96-
<p style="font-size: 1.1rem;">
96+
<p id="intro-paragraph" style="font-size: 1.1rem;">
9797
This tool can compress and decompress files using the GZIP algorithm.
9898
The files are compressed/decompress right inside of your browser without transmitting the files to a
9999
server.
@@ -141,6 +141,26 @@ <h1 class="display-4">Online GZIP de/compressor</h1>
141141
<a href="https://privacypolicies.com/privacy/view/95b2095f4609044d9618c21b06f5df75">Privacy Policy</a>
142142
</div>
143143
</footer>
144+
<script>
145+
(function () {
146+
const isBrotli = window.compressionFormat === 'brotli' || window.location.hostname.includes('brotli');
147+
if (!window.compressionFormat) window.compressionFormat = isBrotli ? 'brotli' : 'gzip';
148+
if (!isBrotli) return;
149+
150+
const set = (sel, attr, val) => { const el = document.querySelector(sel); if (el) el[attr] = val; };
151+
const title = 'Brotli decompress/compress files from your browser';
152+
const desc = 'Use this online Brotli tool to compress and decompress files right in the browser';
153+
154+
document.title = title;
155+
set('meta[name="description"]', 'content', desc);
156+
set('meta[property="og:title"]', 'content', title);
157+
set('meta[property="og:description"]', 'content', desc);
158+
set('meta[name="twitter:title"]', 'content', title);
159+
set('meta[name="twitter:description"]','content', desc);
160+
set('h1.display-4', 'textContent', 'Online Brotli de/compressor');
161+
set('#intro-paragraph','textContent', 'This tool can compress and decompress files using the Brotli algorithm. The files are compressed/decompressed right inside of your browser without transmitting the files to a server.');
162+
})();
163+
</script>
144164
<script src="_framework/blazor.webassembly.js"></script>
145165
<script>navigator.serviceWorker.register('service-worker.js');</script>
146166
</body>

wwwroot/staticwebapp.config.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"navigationFallback": {
3+
"rewrite": "/index.html",
4+
"exclude": ["/_framework/*", "/assets/*"]
5+
}
6+
}

0 commit comments

Comments
 (0)