2 min read
Long Polling: Real-Time Patterns for Legacy Systems
Long polling is a technique where clients hold HTTP connections open until the server has data to send, providing near-real-time updates without WebSocket support.
Server Implementation
[ApiController]
[Route("api/[controller]")]
public class UpdatesController : ControllerBase
{
private readonly IUpdateService _updateService;
[HttpGet("poll")]
public async Task<IActionResult> LongPoll(
[FromQuery] string lastEventId,
CancellationToken cancellationToken)
{
var timeout = TimeSpan.FromSeconds(30);
var deadline = DateTime.UtcNow + timeout;
while (DateTime.UtcNow < deadline)
{
cancellationToken.ThrowIfCancellationRequested();
var updates = await _updateService.GetUpdatesSinceAsync(lastEventId);
if (updates.Any())
{
return Ok(new
{
updates,
lastEventId = updates.Last().Id
});
}
await Task.Delay(TimeSpan.FromMilliseconds(500), cancellationToken);
}
// Timeout - return empty to trigger reconnect
return Ok(new { updates = Array.Empty<object>(), lastEventId });
}
}
Client Implementation
class LongPollingClient {
private lastEventId: string | null = null;
private running = false;
async start(): Promise<void> {
this.running = true;
await this.poll();
}
stop(): void {
this.running = false;
}
private async poll(): Promise<void> {
while (this.running) {
try {
const response = await fetch(
`/api/updates/poll?lastEventId=${this.lastEventId || ''}`
);
if (response.ok) {
const data = await response.json();
this.lastEventId = data.lastEventId;
for (const update of data.updates) {
this.handleUpdate(update);
}
}
} catch (error) {
console.error('Polling error:', error);
await this.delay(5000); // Backoff on error
}
}
}
private handleUpdate(update: any): void {
console.log('Update received:', update);
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
Summary
Long polling provides real-time capabilities for environments where WebSocket is unavailable, with simpler infrastructure requirements.
References: