SSE (OpenAPI)

Document a Server-Sent Events (SSE) endpoint (text/event-stream) using OpenAPI.

Full source

File: pwsh/tutorial/examples/10.20-OpenAPI-Sse.ps1

<#
    Create an SSE demo server with Kestrun in PowerShell (with OpenAPI).
    FileName: 10.20-OpenAPI-Sse.ps1

    SSE helpers used by the streaming route:
    - Start-KrSseResponse
    - Write-KrSseEvent
#>
param(
    [int]$Port = 5000,
    [IPAddress]$IPAddress = [IPAddress]::Loopback
)

if (-not (Get-Module Kestrun)) { Import-Module Kestrun }

# Initialize Kestrun root directory
Initialize-KrRoot -Path $PSScriptRoot

## 1. Logging
New-KrLogger |
    Set-KrLoggerLevel -Value Debug |
    Add-KrSinkConsole | Register-KrLogger -Name 'SseDemoOpenApi' -SetAsDefault

## 2. Create Server
New-KrServer -Name 'Kestrun SSE Demo with OpenAPI'

## 3. Configure Listener
Add-KrEndpoint -Port $Port -IPAddress $IPAddress

# =========================================================
#                 TOP-LEVEL OPENAPI
# =========================================================

Add-KrOpenApiInfo -Title 'Kestrun SSE Demo API' `
    -Version '1.0.0' `
    -Description 'Demonstrates documenting an SSE (text/event-stream) endpoint with OpenAPI.'

# =========================================================
#           OPENAPI COMPONENT DEFINITIONS
# =========================================================

[OpenApiParameterComponent(In = 'Query', Description = 'Number of SSE events to send before closing the connection', Example = 30, Minimum = 1)]
[ValidateRange(1, 1000)]
[int]$count = 30

[OpenApiParameterComponent(In = 'Query', Description = 'Delay between events in milliseconds', Example = 1000, Minimum = 100)]
[ValidateRange(100, 60000)]
[int]$intervalMs = 1000

## 4. Enable Configuration
Enable-KrConfiguration

# Swagger / Redoc UI routes
Add-KrApiDocumentationRoute -DocumentType Swagger -OpenApiEndpoint '/openapi/v3.1/openapi.json'
Add-KrApiDocumentationRoute -DocumentType Redoc -OpenApiEndpoint '/openapi/v3.1/openapi.json'

## 5. Non-OpenAPI routes

# Simple home page with an EventSource client
Add-KrHtmlTemplateRoute -Pattern '/' -HtmlTemplatePath 'Assets/wwwroot/sse.html'

## 6. OpenAPI-annotated SSE route

<#
.SYNOPSIS
    Streams a Server-Sent Events (SSE) response.
.DESCRIPTION
    Sets Content-Type to text/event-stream and writes a series of SSE events ("connected" and "tick").
.PARAMETER count
    Number of events to send.
.PARAMETER intervalMs
    Delay between events.
#>
function GetSseStream {
    [OpenApiPath(HttpVerb = 'get', Pattern = '/sse', Tags = 'SSE')]
    [OpenApiResponse(StatusCode = '200', Description = 'SSE stream (text/event-stream)', SchemaItem = [string], ContentType = 'text/event-stream')]
    param(
        [OpenApiParameterRef(ReferenceId = 'count')]
        [int]$count,

        [OpenApiParameterRef(ReferenceId = 'intervalMs')]
        [int]$intervalMs
    )

    if ($count -le 0) { $count = 30 }
    if ($intervalMs -le 0) { $intervalMs = 1000 }

    Start-KrSseResponse

    $connected = @{
        message = 'Connected to Kestrun SSE stream'
        serverTime = (Get-Date)
        count = $count
        intervalMs = $intervalMs
    } | ConvertTo-Json -Compress

    Write-KrSseEvent -Event 'connected' -Data $connected -retryMs 2000

    for ($i = 1; $i -le $count; $i++) {
        $payload = @{
            i = $i
            total = $count
            timestamp = (Get-Date)
        } | ConvertTo-Json -Compress

        try {
            Write-KrSseEvent -Event 'tick' -Data $payload -id "$i"
        } catch {
            Write-KrLog -Level Debug -Message 'SSE write failed (client disconnected?): {Error}' -Values $_
            break
        }

        Start-Sleep -Milliseconds $intervalMs
    }

    $complete = @{
        message = 'Stream complete'
        total = $count
        serverTime = (Get-Date)
    } | ConvertTo-Json -Compress

    try {
        Write-KrSseEvent -Event 'complete' -Data $complete
    } catch {
        write-KrLog -Level Debug -Message 'SSE write failed on completion (client disconnected?): {Error}' -Values $_ -exception $_.Exception
        # Client may have disconnected; ignore.
    }
}

# =========================================================
#                OPENAPI DOC ROUTE / BUILD
# =========================================================

Add-KrOpenApiRoute  # Default pattern '/openapi/{version}/openapi.{format}'

Build-KrOpenApiDocument
# Test and log OpenAPI document validation result
if (Test-KrOpenApiDocument) {
    Write-KrLog -Level Information -Message 'OpenAPI document built and validated successfully.'
} else {
    Write-KrLog -Level Error -Message 'OpenAPI document validation failed.'
}

## 7. Start Server

Write-Host '🟢 Kestrun SSE Demo Server Started' -ForegroundColor Green
Write-Host "📍 Navigate to http://localhost:$Port to see the demo" -ForegroundColor Cyan
Write-Host "📡 SSE stream endpoint: http://localhost:$Port/sse" -ForegroundColor Cyan
Write-Host "📄 OpenAPI JSON: http://localhost:$Port/openapi/v3.1/openapi.json" -ForegroundColor Cyan
Write-Host "🧭 Swagger UI: http://localhost:$Port/swagger" -ForegroundColor Cyan

Start-KrServer -CloseLogsOnExit

Step-by-step

  1. Logging: Register a console logger as default.
  2. Server: Create a server and listener (IP + port).
  3. OpenAPI: Add document metadata with Add-KrOpenApiInfo.
  4. Parameters: Define reusable query parameter components (count, intervalMs).
  5. Configuration: Enable configuration and add Swagger/ReDoc routes.
  6. UI: Map / with a browser EventSource client for /sse.
  7. SSE route: Implement GetSseStream with OpenAPI attributes and SSE helpers.
  8. Spec: Add the OpenAPI route, build + validate the document, then start the server.

Try it

Start the server:

pwsh .\docs\_includes\examples\pwsh\10.20-OpenAPI-Sse.ps1

Stream a short SSE response (closes after count events):

curl -N "http://127.0.0.1:5000/sse?count=3&intervalMs=100"

Fetch the OpenAPI document and open Swagger:

curl http://127.0.0.1:5000/openapi/v3.1/openapi.json
Start-Process http://127.0.0.1:5000/swagger

Troubleshooting

Symptom Cause Fix
/sse missing from OpenAPI Route not discovered Ensure Enable-KrConfiguration is called and the route has OpenAPI attributes ([OpenApiPath], [OpenApiResponse]).
Swagger loads but no operations appear OpenAPI route not mapped Add Add-KrOpenApiRoute before starting the server.
Browser reconnects repeatedly Stream closes (expected) This sample intentionally ends the stream after count events; handle onerror by closing the EventSource.

References


Previous / Next

Previous: Product Search with HTTP QUERY Next: SSE Broadcast (OpenAPI)