Back to Blog
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:

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.