Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Building Real-Time Voting Mechanisms Using Short and Long Polling

Tech May 19 1

Real-Time Data Monitoring Requirements

Users interacting with the voting interface require immediate visibility into voting statistics without manual page refreshes. To address this, we implement two strategies: standard polling and long polling. The latter is preferred as it significantly reduces the number of network requests while maintaining high data freshness.

1. Implementation via Short Polling

The fundamental concept involves setting up a client-side interval timer. This timer triggers an AJAX request to the server every few seconds (e.g., 3 seconds) to retrieve the current state of the votes.

from flask import Flask, render_template, request, ify

app = Flask(__name__)

# Sample data store
candidates = {
    '101': {'name': 'Alice', 'votes': 1},
    '102': {'name': 'Bob', 'votes': 0},
    '103': {'name': 'Charlie', 'votes': 0},
}

@app.route('/')
def index():
    return render_template('vote.html', data=candidates)

@app.route('/cast_vote', methods=['POST'])
def cast_vote():
    cid = request.form.get('id')
    if cid in candidates:
        candidates[cid]['votes'] += 1
    return ify({'status': 'success'})

@app.route('/get_results', methods=['GET'])
def get_results():
    return ify(candidates)

if __name__ == '__main__':
    app.run(threaded=True)
<!DOCTYPE html>
<html>
<head>
    <title>Live Voting</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <style>
        li { cursor: pointer; padding: 5px; border-bottom: 1px solid #eee; }
    </style>
</head>
<body>
    <ul id="list">
        {% for key, val in data.items() %}
            <li uid="{{key}}">{{ val.name }}: {{ val.votes }}</li>
        {% endfor %}
    </ul>

    <script>
        function updateList(items) {
            $('#list').empty();
            $.each(items, function(k, v) {
                const li = $('<li>').attr('uid', k).text(`${v.name}: ${v.votes}`);
                $('#list').append(li);
            });
        }

        function pollServer() {
            $.get('/get_results', function(resp) {
                updateList(resp);
            });
        }

        // Poll every 3 seconds
        setInterval(pollServer, 3000);

        // Handle voting
        $('#list').on('click', 'li', function() {
            const uid = $(this).attr('uid');
            $.post('/cast_vote', {id: uid}, function() {
                pollServer(); // Instant update on action
            });
        });
    </script>
</body>
</html>

Limitation: This approach may result in high server load due to frequent requests, and data freshness is limited by the polling interval.

2. Implementation via Long Polling

Long polling optimizes resource usage. The server holds the client's request open until new data is available or a timeout occurs.

Core Logic:

  • Client: Sends a request. Upon receiving a response, it updates the DOM and immediately initiates the next request.
  • Server: If no new votes exist, the server blocks the request (using a queue mechanism) for a specific duration (e.g., 10 seconds). If a vote occurs during this wait, the server immediately pushes the updated data to all waiting clients. If the timeout is reached, it returns an empty response.

The server-side implementation relies on the queue module to block execution:

import queue
import time

# Create a blocking queue
msg_queue = queue.Queue()

try:
    # This blocks execution for 10 seconds unless data is available
    data = msg_queue.get(timeout=10)
    print(f"Received: {data}")
except queue.Empty:
    print("Timeout reached, no data available")

Backend Implementation (app.py):

from flask import Flask, render_template, request, ify, session
import uuid
import queue

app = Flask(__name__)
app.secret_key = 'super_secret_key'

candidates = {
    '101': {'name': 'Alice', 'votes': 1},
    '102': {'name': 'Bob', 'votes': 0},
    '103': {'name': 'Charlie', 'votes': 0},
}

# Dictionary to store user-specific queues
client_queues = {}

@app.route('/')
def index():
    # Generate unique ID and queue for the session
    sess_id = str(uuid.uuid4())
    client_queues[sess_id] = queue.Queue()
    session['user_id'] = sess_id
    return render_template('long_poll_vote.html', data=candidates)

@app.route('/cast_vote', methods=['POST'])
def cast_vote():
    cid = request.form.get('id')
    if cid in candidates:
        candidates[cid]['votes'] += 1
        
        # Push update to all connected clients
        for q in client_queues.values():
            try:
                q.put(candidates)
            except:
                pass
                
    return ify({'status': 'success'})

@app.route('/listen', methods=['GET'])
def listen():
    sess_id = session.get('user_id')
    q = client_queues.get(sess_id)
    
    response = {'update': False, 'data': None}
    
    if q:
        try:
            # Wait for data or timeout
            data = q.get(timeout=10)
            response['update'] = True
            response['data'] = data
        except queue.Empty:
            # Timeout: return empty response to trigger reconnect
            pass
            
    return ify(response)

if __name__ == '__main__':
    app.run(threaded=True)

Frontend Implementation (long_poll_vote.html):

<!DOCTYPE html>
<html>
<head>
    <title>Live Voting - Long Polling</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <style>
        li { cursor: pointer; padding: 5px; border-bottom: 1px solid #eee; }
    </style>
</head>
<body>
    <ul id="list">
        {% for key, val in data.items() %}
            <li uid="{{key}}">{{ val.name }}: {{ val.votes }}</li>
        {% endfor %}
    </ul>

    <script>
        function updateList(items) {
            $('#list').empty();
            $.each(items, function(k, v) {
                const li = $('<li>').attr('uid', k).text(`${v.name}: ${v.votes}`);
                $('#list').append(li);
            });
        }

        function longPoll() {
            $.get('/listen', function(resp) {
                if (resp.update) {
                    updateList(resp.data);
                }
                // Recursive call immediately after response
                longPoll();
            });
        }

        // Start the long polling loop
        $(document).ready(function() {
            longPoll();
        });

        // Handle voting
        $('#list').on('click', 'li', function() {
            const uid = $(this).attr('uid');
            $.post('/cast_vote', {id: uid});
        });
    </script>
</body>
</html>

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.