web-dev-qa-db-fra.com

Nginx PHP Échec avec les téléchargements de fichiers volumineux (plus de 6 Go)

J'ai un problème très étrange lors du téléchargement de fichiers volumineux de plus de 6 Go. Mon processus fonctionne comme ceci:

  1. Les fichiers sont téléchargés via Ajax vers un script php.
  2. Le script de téléchargement PHP prend le $ _FILE et le copie en morceaux, comme dans cette réponse vers un emplacement tmp.
  3. L'emplacement du fichier est stocké dans la base de données
  4. Un script cron téléchargera le fichier vers s3 ultérieurement, en utilisant à nouveau les fonctions fopen et la mise en mémoire tampon pour limiter l'utilisation de la mémoire

Ma configuration PHP (HHVM) et NGINX ont toutes deux leur configuration définie pour autoriser jusqu'à 16 Go de fichier, mon fichier de test ne fait que 8 Go.

Voici la partie étrange, l'ajax va [~ # ~] toujours [~ # ~] expirer. Mais le fichier est correctement téléchargé, il est copié vers l'emplacement tmp, l'emplacement stocké dans le db, s3, etc. Mais le AJAX fonctionne pendant une heure, même APRÈS que toute l'exécution soit terminée (ce qui prend 10-15 minutes) et ne se termine que lorsque le temps est écoulé.

Qu'est-ce qui peut empêcher le serveur d'envoyer une réponse uniquement pour les fichiers volumineux?

Les journaux d'erreurs côté serveur sont également vides.

13
Devin Dixon

Un téléchargement de fichiers volumineux est une opération coûteuse et sujette aux erreurs. Nginx et le backend devraient avoir un timeout correct configuré pour gérer le disque lent IO si cela se produit. En théorie, il est simple de gérer le téléchargement de fichiers en utilisant le codage multipart/form-data RFC 1867.

Selon developer.mozilla.org dans un corps multipart/form-data, l'en-tête général HTTP Content-Disposition est un en-tête qui peut être utilisé sur la sous-partie d'un corps multipart pour donner des informations sur le champ il s'applique à. La sous-partie est délimitée par la limite définie dans l'en-tête Content-Type. Utilisé sur le corps lui-même, Content-Disposition n'a aucun effet.

Voyons ce qui se passe lors du téléchargement du fichier:

1) le client envoie une requête HTTP avec le contenu du fichier au serveur Web

2) le serveur Web accepte la demande et lance le transfert de données (ou renvoie l'erreur 413 si la taille du fichier dépasse la limite)

3) le serveur Web commence à remplir les tampons (dépend de la taille du fichier et des tampons)

4) Le serveur Web envoie le contenu du fichier via le fichier/socket réseau au backend

5) le backend authentifie la demande initiale

6) le backend lit le fichier et coupe les en-têtes (Content-Disposition, Content-Type)

7) Les sauvegardes dorsales ont généré le fichier sur le disque

8) toute procédure de suivi comme les modifications de la base de données

client_body_in_file_only off;

Lors du téléchargement de fichiers volumineux, plusieurs problèmes se produisent:

  • la requête de corps HTTP est sauvegardée sur le disque et transmise au backend qui traite et copie le fichier
  • impossible d'authentifier la demande avant le téléchargement du contenu de la demande HTTP sur le serveur
  • tandis que le téléchargement de fichiers volumineux nécessite rarement un contenu de fichier lui-même immédiatement

Commençons par Nginx configuré avec un nouvel emplacement http: // backend/upload pour recevoir le téléchargement de fichiers volumineux, interaction back-end est minimisé (Content-Legth: 0), le fichier est stocké uniquement sur le disque. À l'aide de tampons, Nginx vide le fichier sur le disque (un fichier stocké dans le répertoire temporaire avec un nom aléatoire, il ne peut pas être modifié) suivi de POST demande de backend à l'emplacement http: // backend/fichier avec le nom du fichier dans X-File-Name entête.

Pour conserver des informations supplémentaires, vous pouvez utiliser des en-têtes avec la demande initiale POST. Par exemple, avoir X-Original-File-Name les en-têtes des demandes initiales vous aident à faire correspondre le fichier et à stocker les informations de mappage nécessaires dans la base de données.

client_body_in_file_only on;

Voyons comment y arriver:

1) configurer Nginx pour vider le contenu du corps HTTP dans un fichier et le conserver stocké client_body_in_file_only on;

2) créer un nouveau point de terminaison backend http: // backend/fichier pour gérer le mappage entre le nom du fichier temporaire et l'en-tête X-File-Name

4) instrument AJAX requête avec en-tête X-File-Name Nginx utilisera pour envoyer une demande de post-téléchargement avec

Configuration:

location /upload {
  client_body_temp_path      /tmp/;
  client_body_in_file_only   on;
  client_body_buffer_size    1M;
  client_max_body_size       7G;

  proxy_pass_request_headers on;
  proxy_set_header           X-File-Name $request_body_file; 
  proxy_set_body             off;
  proxy_redirect             off;
  proxy_pass                 http://backend/file;
}

L'option de configuration Nginx client_body_in_file_only est incompatible avec le téléchargement de données en plusieurs parties, mais vous pouvez l'utiliser avec AJAX i.e. XMLHttpRequest2 (données binaires).

Si vous avez besoin d'une authentification principale, la seule façon de gérer est d'utiliser auth_request , par exemple:

location = /upload {
  auth_request               /upload/authenticate;
  ...
}

location = /upload/authenticate {
  internal;
  proxy_set_body             off;
  proxy_pass                 http://backend;
}

client_body_in_file_only on; auth_request on;

La logique d'authentification préalable au téléchargement protège des demandes non authentifiées quelle que soit la taille initiale POST Content-Length).

46
Anatoly