Request-level compression

Enable ASP.NET Core RequestDecompression middleware for gzip request bodies.

Full source

File: pwsh/tutorial/examples/22.6-Request-Compressed.ps1

<#!
    22.6 Upload with request-level compression (RequestDecompression middleware)

    Client example (PowerShell):
        $boundary = 'req-boundary'
        $body = @(
            "--$boundary",
            "Content-Disposition: form-data; name=note",
            "",
            "compressed",
            "--$boundary",
            "Content-Disposition: form-data; name=file; filename=hello.txt",
            "Content-Type: text/plain",
            "",
            "hello",
            "--$boundary--",
            ""
        ) -join "`r`n"
        $bytes = [System.Text.Encoding]::UTF8.GetBytes($body)
        $ms = [System.IO.MemoryStream]::new()
        $gzip = [System.IO.Compression.GZipStream]::new($ms, [System.IO.Compression.CompressionMode]::Compress, $true)
        $gzip.Write($bytes, 0, $bytes.Length)
        $gzip.Dispose()
        $compressed = $ms.ToArray()
        Invoke-WebRequest -Method Post -Uri "http://127.0.0.1:$Port/upload" -ContentType "multipart/form-data; boundary=$boundary" -Headers @{ 'Content-Encoding'='gzip' } -Body $compressed

    Cleanup:
        Remove-Item -Recurse -Force (Join-Path ([System.IO.Path]::GetTempPath()) 'kestrun-uploads-22.6-request-compressed')
#>
param(
    [int]$Port = 5000,
    [IPAddress]$IPAddress = [IPAddress]::Loopback
)

New-KrLogger |
    Set-KrLoggerLevel -Value Debug |
    Add-KrSinkConsole |
    Register-KrLogger -Name 'console' -SetAsDefault

New-KrServer -Name 'Forms 22.6'

Add-KrEndpoint -Port $Port -IPAddress $IPAddress | Out-Null

# Enable Request Decompression Middleware
Add-KrRequestDecompressionMiddleware -AllowedEncoding gzip | Out-Null

# Upload directory
$scriptName = [System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath)
$uploadRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "kestrun-uploads-$scriptName"

# Add Rules
New-KrFormPartRule -Name 'file' -Required -AllowOnlyOne -AllowedContentTypes 'text/plain' |
    New-KrFormPartRule -Name 'note' -Required |
    Add-KrFormOption -DefaultUploadPath $uploadRoot -ComputeSha256 |
    # Form Route
    Add-KrFormRoute -Pattern '/upload' -ScriptBlock {
        $files = $FormPayload.Files['file']
        Write-KrJsonResponse -InputObject @{ count = $files.Count; files = $files } -StatusCode 200
    }

Enable-KrConfiguration

# Start the server asynchronously
Start-KrServer

Step-by-step

  1. Logger: Enable console logging.
  2. Server: Create the host and bind a listener.
  3. Middleware: Add Add-KrRequestDecompressionMiddleware for gzip.
  4. Options: Configure upload storage and hashing.
  5. Route: Add /upload with Add-KrFormRoute.
  6. Response: Return stored file metadata.

Try it

$boundary = 'req-boundary'
$body = @(
    "--$boundary",
    "Content-Disposition: form-data; name=note",
    "",
    "compressed",
    "--$boundary",
    "Content-Disposition: form-data; name=file; filename=hello.txt",
    "Content-Type: text/plain",
    "",
    "hello",
    "--$boundary--",
    ""
) -join "`r`n"
$bytes = [System.Text.Encoding]::UTF8.GetBytes($body)

$ms = [System.IO.MemoryStream]::new()
try {
  $gzip = [System.IO.Compression.GZipStream]::new($ms, [System.IO.Compression.CompressionMode]::Compress, $true)
  try {
    $gzip.Write($bytes, 0, $bytes.Length)
  } finally {
    $gzip.Dispose()
  }

  # Ensure we send a raw byte[] body (not an enumerated Object[] of bytes)
  [byte[]]$compressed = $ms.ToArray()
} finally {
  $ms.Dispose()
}

Invoke-WebRequest -Method Post -Uri 'http://127.0.0.1:5000/upload' -ContentType "multipart/form-data; boundary=$boundary" -Headers @{ 'Content-Encoding'='gzip' } -Body $compressed
cat > payload.txt <<'EOF'
--req-boundary
Content-Disposition: form-data; name=note

compressed
--req-boundary
Content-Disposition: form-data; name=file; filename=hello.txt
Content-Type: text/plain

hello
--req-boundary--
EOF

gzip -c payload.txt | curl -X POST \
  -H "Content-Encoding: gzip" \
  -H "Content-Type: multipart/form-data; boundary=req-boundary" \
  --data-binary @- \
  http://127.0.0.1:5000/upload

Expected output

{
  "count": 1,
  "files": [
    { "name": "file", "fileName": "hello.txt", "length": 5, "sha256": "..." }
  ]
}

Notes

  • Middleware: Request decompression must be enabled before parsing compressed bodies.
  • Limits: MaxRequestBodyBytes still applies to decompressed size at the server level.
  • Logging: Request-level compression status is logged via host.Logger.
  • PowerShell: If you build a helper function that returns a byte[], ensure it returns a single byte[] object (avoid enumeration into Object[]).

Troubleshooting

  • 415 / Unsupported Content-Type: Ensure the request Content-Type is multipart/form-data; boundary=....
  • 500 / Decompression errors: Ensure Add-KrRequestDecompressionMiddleware -AllowedEncoding gzip is enabled and you send valid gzip bytes with Content-Encoding: gzip.

References


Previous / Next

Previous: Nested multipart/mixed Next: Part-level compression