阿里云主机折上折
  • 微信号
Current Site:Index > Link to file download

Link to file download

Author:Chuan Chen 阅读数:58816人阅读 分类: HTML

Basic Principles of File Download

The core of file downloading lies in the Content-Disposition field in the server's response headers. When the browser receives a response with the attachment parameter, it triggers a download behavior instead of directly rendering the content. For example:

HTTP/1.1 200 OK
Content-Disposition: attachment; filename="example.pdf"
Content-Type: application/pdf

Basic Download Methods in HTML

The simplest implementation is using the download attribute of the <a> tag. This attribute forces the browser to download the target resource instead of navigating to it:

<a href="/files/report.pdf" download="Annual Report.pdf">Download PDF File</a>

Things to note:

  • The download attribute only works for same-origin URLs
  • Cross-origin links ignore this attribute
  • You can specify the filename after download

Dynamic Download with JavaScript

Programmatically create a hidden <a> tag to achieve dynamic downloads:

function downloadFile(url, filename) {
  const a = document.createElement('a');
  a.href = url;
  a.download = filename || 'downloaded-file';
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
}

Blob Object Download

When generating content on the frontend, you can create a Blob object to achieve client-side downloads:

const content = 'Hello, World!';
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);

const a = document.createElement('a');
a.href = url;
a.download = 'example.txt';
a.click();

// Free memory
setTimeout(() => URL.revokeObjectURL(url), 100);

Chunked Download for Large Files

For large files, use streaming downloads:

async function downloadLargeFile(url) {
  const response = await fetch(url);
  const reader = response.body.getReader();
  const contentLength = +response.headers.get('Content-Length');
  let receivedLength = 0;
  const chunks = [];
  
  while(true) {
    const {done, value} = await reader.read();
    if(done) break;
    
    chunks.push(value);
    receivedLength += value.length;
    console.log(`Download progress: ${(receivedLength/contentLength*100).toFixed(1)}%`);
  }
  
  const blob = new Blob(chunks);
  const downloadUrl = URL.createObjectURL(blob);
  // Create an <a> tag to trigger download...
}

Download Progress Display

Monitor download progress using XMLHttpRequest:

const xhr = new XMLHttpRequest();
xhr.open('GET', '/large-file.zip', true);
xhr.responseType = 'blob';

xhr.onprogress = function(e) {
  if(e.lengthComputable) {
    const percent = (e.loaded / e.total) * 100;
    progressBar.style.width = percent + '%';
  }
};

xhr.onload = function() {
  if(this.status === 200) {
    const blob = this.response;
    // Process the downloaded blob
  }
};

xhr.send();

Multi-File Zip Download

Use the JSZip library to achieve multi-file zip downloads:

const zip = new JSZip();
zip.file("hello.txt", "Hello World\n");
zip.file("image.png", imageBlob, {binary: true});

zip.generateAsync({type:"blob"}).then(function(content) {
  const url = URL.createObjectURL(content);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'example.zip';
  a.click();
});

Server-Pushed Downloads

Combine WebSocket to achieve server-pushed downloads:

const socket = new WebSocket('wss://example.com/download');

socket.onmessage = function(event) {
  if(typeof event.data === 'string') {
    // Process metadata
    const meta = JSON.parse(event.data);
    console.log(`Starting download: ${meta.filename}`);
  } else {
    // Process binary data
    chunks.push(event.data);
  }
};

// Server example (Node.js)
ws.on('connection', (client) => {
  const stream = fs.createReadStream('large-file.bin');
  stream.on('data', (chunk) => client.send(chunk));
  stream.on('end', () => client.close());
});

Download Security Considerations

Security issues to consider when implementing file downloads:

  1. Verify user permissions
// Server middleware example
app.get('/download/:file', authenticateUser, (req, res) => {
  if(!userHasPermission(req.user, req.params.file)) {
    return res.status(403).send('Access denied');
  }
  res.download(`/secure-files/${req.params.file}`);
});
  1. Prevent directory traversal attacks
// Secure path handling
const safePath = require('path').join(
  '/secure-dir',
  req.params.file.replace(/\.\.\//g, '')
);
  1. Set download rate limits
# Nginx configuration example
location /downloads/ {
  limit_rate 200k;  # Limit to 200KB/s
}

Browser Compatibility Handling

Compatibility solutions for different browsers:

function forceDownload(blob, filename) {
  if(window.navigator.msSaveOrOpenBlob) {
    // IE10+
    navigator.msSaveBlob(blob, filename);
  } else {
    // Standard browsers
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = filename;
    link.click();
  }
}

Auto-Open After Download

In some scenarios, you may want to automatically open the file after download:

// Only works for same-origin and browser-allowed MIME types
const a = document.createElement('a');
a.href = '/files/document.pdf';
a.target = '_blank';
a.download = 'document.pdf';
a.click();

Resumable Download Implementation

Use the Range header to achieve resumable downloads:

// Client-side
const resumeByte = getStoredDownloadProgress(filename);
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.setRequestHeader('Range', `bytes=${resumeByte}-`);
xhr.responseType = 'blob';

xhr.onprogress = (e) => {
  storeDownloadProgress(filename, resumeByte + e.loaded);
};

Download Timeout Handling

Add timeout control for downloads:

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);

fetch('/large-file', {
  signal: controller.signal
})
.then(response => {
  clearTimeout(timeoutId);
  // Process response
})
.catch(err => {
  if(err.name === 'AbortError') {
    console.log('Download timed out');
  }
});

Mobile Download Optimization

Special handling for mobile devices:

// Detect iOS WebView
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
const isWebView = isIOS && !window.MSStream;

if(isWebView) {
  // iOS WebView requires special handling
  window.location = 'itms-services://?action=download-manifest&url=...';
} else {
  // Standard download process
  normalDownload();
}

Download Logging

Client-side download logging solution:

function logDownload(filename, success) {
  navigator.sendBeacon('/download-log', JSON.stringify({
    file: filename,
    timestamp: Date.now(),
    status: success ? 'completed' : 'failed',
    userAgent: navigator.userAgent
  }));
}

// Usage example
fetch('/file.pdf')
  .then(() => logDownload('file.pdf', true))
  .catch(() => logDownload('file.pdf', false));

Download Button State Management

Optimize user interaction with download button states:

<button id="downloadBtn" data-state="ready">
  <span class="ready">Download File</span>
  <span class="downloading">Downloading... <progress value="0" max="100"></span>
  <span class="completed">Download Complete!</span>
</button>

<style>
  [data-state="ready"] .downloading,
  [data-state="ready"] .completed,
  [data-state="downloading"] .ready,
  [data-state="downloading"] .completed,
  [data-state="completed"] .ready,
  [data-state="completed"] .downloading {
    display: none;
  }
</style>

<script>
  btn.addEventListener('click', () => {
    btn.dataset.state = 'downloading';
    downloadFile().then(() => {
      btn.dataset.state = 'completed';
    });
  });
</script>

Download Speed Calculation

Calculate and display download speed in real-time:

let lastLoaded = 0;
let lastTime = Date.now();
let speeds = [];

xhr.onprogress = function(e) {
  const now = Date.now();
  const duration = (now - lastTime) / 1000; // seconds
  const loadedDiff = e.loaded - lastLoaded;
  
  if(duration > 0) {
    const speed = loadedDiff / duration; // bytes/s
    speeds.push(speed);
    if(speeds.length > 5) speeds.shift();
    
    const avgSpeed = speeds.reduce((a,b) => a+b, 0) / speeds.length;
    speedDisplay.textContent = formatSpeed(avgSpeed);
  }
  
  lastLoaded = e.loaded;
  lastTime = now;
};

function formatSpeed(bytesPerSec) {
  const kb = bytesPerSec / 1024;
  return kb > 1024 
    ? `${(kb/1024).toFixed(1)} MB/s` 
    : `${kb.toFixed(1)} KB/s`;
}

Download Queue System

Implement sequential downloads for multiple files:

class DownloadQueue {
  constructor() {
    this.queue = [];
    this.isDownloading = false;
  }
  
  add(file) {
    this.queue.push(file);
    if(!this.isDownloading) this.next();
  }
  
  next() {
    if(this.queue.length === 0) {
      this.isDownloading = false;
      return;
    }
    
    this.isDownloading = true;
    const file = this.queue.shift();
    this.download(file).finally(() => this.next());
  }
  
  download(file) {
    return new Promise((resolve) => {
      console.log(`Starting download: ${file.name}`);
      // Actual download logic...
    });
  }
}

// Usage example
const queue = new DownloadQueue();
queue.add({name: 'file1.pdf', url: '/files/1.pdf'});
queue.add({name: 'file2.pdf', url: '/files/2.pdf'});

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.