NaviServer - programmable web server
4.99  5.0

[ Main Table Of Contents | Table Of Contents | Keyword Index ]

ns_upload_stats(n) 5.0.0a naviserver "NaviServer Built-in Commands"

Name

ns_upload_stats - Return Real-Time Statistics for an Ongoing File Upload

Table Of Contents

Synopsis

Description

This command provides real-time statistics for large file uploads, enabling user interfaces to display progress information about uploads in progress.

Internally, an upload request is identified by a unique key, which is used to track statistics such as the current uploaded size and the total size. These statistics are dynamically updated throughout the upload process. Once the upload completes, the statistics are automatically discarded.

The upload key can be provided in several ways:

To enable upload progress statistics, the configuration parameter progressminsize must be set. This ensures statistics are maintained only for files exceeding a specified minimum size, reducing unnecessary overhead for small files.

COMMANDS

ns_upload_stats key

The key parameter is typically the value provided via the X-Progress-ID request header, although query parameters or URLs can also be used. The convention of using the X-Progress-ID header is widely supported by other web servers, including Nginx, Lighttpd, and Apache (with mod_passenger).

This command returns a list with two elements:

  1. The number of bytes uploaded so far.

  2. The total size of the file being uploaded.

CONFIGURATION

To minimize unnecessary overhead for small uploads, progress statistics are only maintained for files larger than a configurable size. Set the progressminsize parameter to specify the minimum file size (e.g., 2kB).

 ns_section ns/parameters {
   ns_param progressminsize 2kB
 }

EXAMPLES

JavaScript file for uploading a file, supplying for the upload a unique X-Progress-ID.

 <!DOCTYPE html>
 <html lang="en">
 <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>File Upload with Progress</title>
 </head>
 <body>
    <h1>File Upload with X-Progress-ID</h1>
    <form id="uploadForm" enctype="multipart/form-data">
        <input type="file" name="file" id="fileInput" />
        <button type="button" id="uploadButton">Upload</button>
    </form>
    <p id="progressText">Progress: 0%</p>
    <div id="progressBar" style="width: 0%; height: 20px; background-color: green;"></div>
    <script>
        document.getElementById('uploadButton').addEventListener('click', function () {
            const fileInput = document.getElementById('fileInput');
            if (fileInput.files.length === 0) {
                alert('Please select a file to upload.');
                return;
            }
            const progressId = generateUUID();
            const formData = new FormData();
            formData.append('file', fileInput.files[0]);
            // Start the file upload with X-Progress-ID header
            const xhr = new XMLHttpRequest();
            xhr.open('POST', '/upload', true);
            xhr.setRequestHeader('X-Progress-ID', progressId);
            xhr.upload.addEventListener('loadstart', () => {
                document.getElementById('progressText').innerText = 'Upload started...';
            });
            xhr.upload.addEventListener('error', () => {
                document.getElementById('progressText').innerText = 'Error during upload.';
            });
            xhr.addEventListener('load', () => {
                document.getElementById('progressText').innerText = 'Upload completed.';
            });
            xhr.send(formData);
            // Query upload progress
            const progressInterval = setInterval(() => {
                fetch(`/progress?X-Progress-ID=${progressId}`)
                    .then(response => {
                        if (response.ok) {
                            return response.json();
                        } else if (response.status === 429) {
                            // request block told us to slow down, could be handled differently
                            return {status: "continue"}
                        } else {
                            console.warn(`Non-200 status code: ${response.status}`);
                            return null;
                        }
                    })
                    .then(data => {
                        //console.log(data);
                        if (data && data.total) {
                           const percent = Math.round((data.current / data.total) * 100);
                           document.getElementById('progressText').innerText = `Progress: ${percent}%`;
                           document.getElementById('progressBar').style.width = `${percent}%`;
                           if (percent === 100) {
                               clearInterval(progressInterval);
                            }
                         }
                    })
                    .catch(err => {
                        console.error('Error querying progress:', err);
                        clearInterval(progressInterval);
                    });
            },250);
        });
        function generateUUID() {
            // Simple UUID generator for X-Progress-ID
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                const r = Math.random() * 16 | 0;
                const v = c === 'x' ? r : (r & 0x3 | 0x8);
                return v.toString(16);
            });
        }
    </script>
 </body>
 </html>

Server-side support:

 # Upload handler
 ns_register_proc POST /upload {
   ns_return 200 text/plain "Upload successful"
 }
 
 # Progress handler
 ns_register_proc GET /progress {
   set progress_id [ns_queryget X-Progress-ID]
   if {$progress_id ne ""} {
     set stats [ns_upload_stats $progress_id]
     if {[llength $stats] == 2} {
       lassign $stats current total
       ns_return 200 application/json [subst {{"total":$total,"current":$current}}]
     } else {
       ns_return 404 application/json {{"error": "No progress data available."}}
     }
   } else {
     ns_return 400 application/json {{"error": "Missing X-Progress-ID."}}
   }
 }

See Also

nsd

Keywords

NaviServer, configuration, progress, server built-in, upload