Обработка загружаемых файлов

Задача
Вы хотите разрешить загрузку файлов на ваш веб-сервер и хранение их в базе данных.

Решение
Предложите пользователю веб-форму, включающую поле для файла. Когда пользователь отправит форму, извлеките файл и сохраните в MySQL.

Обсуждение
Особым видом ввода через Web является загружаемый файл. Файл отправляется в составе запроса POST, но обрабатывается не так, как остальные параметры POST, поскольку файл представлен несколькими элементами информации: содержимым, MIME-типом, исходным именем файла у клиента и именем временного хранения на хосте веб-сервера.

Для обработки загружаемого файла необходимо отправить пользователю специальную форму. Это верно для любого API, в котором создается форма.

Однако после отправки формы пользователем все операции проверки и обработки загружаемого файла являются специфичными для каждого API.Чтобы создать форму, позволяющую загружать файлы, необходимо указать метод POST в открывающем теге
, кроме того у него должен быть атрибут enctype (тип кодирования) со значением multipart/form-data:


Если вы не укажете такое кодирование, форма будет отправлена с типом кодирования по умолчанию (application/x-www-form-urlencoded), и файл будет передан некорректно.
Для включения загружаемого файла в форму используйте элемент типа file.


Например, элемент, представляющий 60-символьное поле файла upload_file, должен быть таким:



Броузер выводит его как текстовое поле ввода, в которое пользователь может вручную вводить имя. Он также предлагает кнопку Browse для выбора файла в стандартном системном диалоге поиска файла. Когда пользователь выбирает файл и отправляет форму, броузер кодирует содержимое файла для включения в результирующий запрос POST. В этот момент веб-сервер получает запрос и вызывает ваш сценарий для его обработки. Детали загрузки файла определяются конкретным используемым вами API, но в целом процедура всегда такова:

• Файл уже загружен и сохранен во временном каталоге ко времени начала работы сценария обработки. Все сценарии должны прочитать его. Временный файл будет доступен сценарию через дескриптор открытого файла, через временное имя файла или и так, и так. Размер файла можно получить при помощи файлового дескриптора. API может обеспечить доступ и к другой информации о файле, такой как его MIME-тип. (Только имейте в виду, что некоторые броузеры не отправляют значение MIME.)

• Загружаемые файлы автоматически удаляются веб-сервером при завершении сценария.


Если вы хотите, чтобы содержимое файла сохранилось и после окончания работы сценария, сохраните его явно (например, в базе данных или каком-то другом каталоге файловой системы). При сохранении в файловой системе необходимо выбрать каталог, доступный веб-серверу.

• API может позволить вам управлять положением каталога временных файлов или максимальным размером загружаемых файлов. Переход к каталогу, доступному только веб-серверу, может несколько улучшить защиту от пользователей, имеющих учетные записи на хосте сервера.

В этом разделе описано, как создавать формы, включающие поле загрузки файла. Показано, как обрабатывать такие файлы, на примере сценария на Perl post_image.pl. Этот сценарий напоминает сценарий store_image.pl, написанный для загрузки изображений из командной строки.

Сценарий post_image.pl отличается тем, что позволяет сохранить изображения, полученные через Web; кроме того, он хранит изображения только в MySQL, а store_image.pl – в MySQL и файловой системе.Также в этом разделе рассказано о получении информации из загружаемого файла при помощи PHP и Python. Я не буду приводить весь код отправки изображения, как это сделано для Perl, но в дистрибутиве recipes вы найдете полный текст реализаций post_image.pl для PHP и Python.

Perl
Используя модуль CGI.pm, вы можете задать сложное (multipart) кодирование формы несколькими способами.


Все приведенные ниже предложения эквиваленты:

print start_form (-action => url (), -enctype => "multipart/form-data");
print start_form (-action => url (), -enctype => MULTIPART ());
print start_multipart_form (-action => url ());

Первое предложение задает тип кодирования буквально. Второе использует функцию the CGI.pm MULTIPART(), применить которую проще, чем вспоминать значение. Самым простым является третье предложение, так как функция start_multipart_form() сама добавляет параметр enctype. (Как и функция start_form(), start_multipart_form() использует по умолчанию метод запроса POST, поэтому нет необходимости в аргументе method.)

Рассмотрим простую форму, содержащую текстовое поле для присвоения изображению имени, файловое поле для выбора файла изображения и кнопку отправки:

print start_multipart_form (-action => url ()),
"Image name:", br (),
textfield (-name =>"image_name", -size => 60),
br (), "Image file:", br (),
filefield (-name =>"upload_file", -size => 60),
br (), br (),
submit (-name => "choice", -value => "Submit"),
end_form ();

Когда пользователь отправит загруженный файл, приступаем к его обработке, извлекая значение параметра для поля файла:

$file = param ("upload_file");

Значение параметра загрузки файла является особенным, так как его можно использовать двумя способами. Можно интерпретировать его как дескриптор открытого файла для чтения содержимого файла или передать его в uploadInfo() для получения ссылки на хеш, предоставляющий сведения о файле, например, его MIME-тип. Рассмотрим сценарий post_image.pl, который создает форму и обрабатывает ее после отправки пользователем. При первом вызове post_image.pl генерирует форму с полем загрузки. На момент первого вызова никакой файл еще не загружен, так что сценарию больше делать нечего. Если пользователь передает файл изображения, сценарий принимает имя файла, читает его содержимое, определяет MIME-тип и сохраняет новую запись в таблице image. Для наглядности post_image.pl также выводитвсю информацию о загруженном файле, доступную посредством функции uploadInfo().

#! /usr/bin/perl -w
# post_image.pl - позволяет пользователю загрузить файлы изображений
# при помощи запросов POST
use strict;
use lib qw(/usr/local/apache/lib/perl);
use CGI qw(:standard escapeHTML);
use Cookbook;
print header (), start_html (-title => "Post Image", -bgcolor => "white");
# Использовать multipart-кодирование, так как форма содержит поле загрузки файла.
print start_multipart_form (-action => url ()),
"Image name:", br (),
textfield (-name =>"image_name", -size => 60),
br (), "Image file:", br (),
filefield (-name =>"upload_file", -size => 60),
br (), br (),
submit (-name => "choice", -value => "Submit"),
end_form ();
# Получить дескриптор файла изображения и имя, которое должно
# быть присвоено изображению.
my $image_file = param ("upload_file");
my $image_name = param ("image_name");
# Должно быть указано или ни одного параметра (если сценарий вызван впервые)
# или два (то есть форма должна быть заполнена). Если указан лишь один параметр,
# форма заполнена не полностью.
my $param_count = 0;
++$param_count if defined ($image_file) && $image_file ne "";
++$param_count if defined ($image_name) && $image_name ne "";
if ($param_count == 0) # первый вызов
{
print p ("No file was uploaded.");
}
elsif ($param_count == 1) # незаполненная форма
{
print p ("Please fill in BOTH fields and resubmit the form.");
}
else # файл загружен
{
my ($size, $data);
# Если файл изображения загружен, вывести какую-нибудь информацию о нем
# и сохранить его в базе данных.
# Получить ссылку на хеш с данными о файле и вывести информацию
# в формате "key=x, value=y".my $info_ref = uploadInfo ($image_file);
print p ("Information about uploaded file:");
foreach my $key (sort (keys (%{$info_ref})))
{
printf p ("key="
. escapeHTML ($key)
. ", value="
. escapeHTML ($info_ref->{$key}));
}
$size = (stat ($image_file))[7]; # получить размер файла
# из файлового дескриптора
print p ("File size: " . $size);
binmode ($image_file); # удобно для двоичных данных
if (sysread ($image_file, $data, $size) != $size)
{
print p ("File contents could not be read.");
}
else
{
print p ("File contents were read without error.");
# Получить MIME-тип, использовать общее умолчание, если не указан.
my $mime_type = $info_ref->{'Content-Type'};
$mime_type = "application/octet-stream" unless defined ($mime_type);
# Сохранить изображение в таблице базы данных. (Использовать REPLACE
# для замены старых изображений с таким же именем.)
my $dbh = Cookbook::connect ();
$dbh->do ("REPLACE INTO image (name,type,data) VALUES(?,?,?)",
undef,
$image_name, $mime_type, $data);
$dbh->disconnect ();
}
}
print end_html ();
exit (0);

PHP
Для создания формы загрузки в PHP включите в нее поле файла. При желании можно также добавить перед полем файла скрытое поле с именем MAX_FILE_SIZE и значением, определяющим желаемый максимальный допустимый размер загружаемого файла:

action="">
Image name:



Image file:








Знайте, что MAX_FILE_SIZE носит только рекомендательный характер, и такое ограничение без труда можно обойти. Для того чтобы указать значение, которое нельзя превышать, используйте параметр конфигурации upload_max_filesize в инициализационном файле PHP. Кроме того, есть параметр file_uploads, определяющий принципиальную возможность загрузки файлов.

После отправки формы пользователем информацию о загруженном файле можно получить так:

• Начиная с версии PHP 4.1 данные загруженного файла из запроса POST помещаются в специальный массив $_FILES, который включает в себя запись для каждого загруженного файла. Каждая запись представляет собой массив из четырех элементов. Например, если форма содержит поле файла с именем upload_file и пользователь отправляет файл, информация о нем доступна в следующих переменных:

$_FILES["upload_file]["name"] исходное имя файла на клиентском хосте
$_FILES["upload_file]["tmp_name"] временное имя файла на хосте сервера
$_FILES["upload_file]["size"] размер файла в байтах
$_FILES["upload_file]["type"] MIME-тип файла

Будьте внимательны, поскольку запись для поля загрузки может существовать, даже если пользователь не отправил файл. Тогда значение tmp_name будет пустой строкой или строкой none.

• В более ранних версиях PHP 4 информация о загружаемых файлах хранилась в массиве $HTTP_POST_FILES, записи которого были устроены так же, как и в $_FILES. Информация о файле, отправленном из поля upload_file, доступна в таких переменных:

$HTTP_POST_FILES["upload_file]["name"] исходное имя файла на клиентском хосте
$HTTP_POST_FILES["upload_file]["tmp_name"] временное имя файла на хосте сервера
$HTTP_POST_FILES["upload_file]["size"] размер файла в байтах
$HTTP_POST_FILES["upload_file]["type"] MIME-тип файла

• До версии PHP 4 обращение к информации о загружаемых файлах для поля upload_file осуществлялось через четыре переменные $HTTP_POST_VARS:

$HTTP_POST_VARS["upload_file_name"] исходное имя файла на клиентском хосте
$HTTP_POST_VARS["upload_file"] временное имя файла на хосте сервера
$HTTP_POST_VARS["upload_file_size"] размер файла в байтах
$HTTP_POST_VARS["upload_file_type"]

MIME-тип файла$_FILES – это суперглобальный массив (глобальный в любой области видимости). $HTTP_POST_FILES и $HTTP_POST_VARS должны объявляться с ключевым словом global при использовании не в глобальной области видимости, например, внутри функции.

Чтобы не терять время попусту, пытаясь определить, какой из массивов содержит информацию о загружаемых файлах, разумно написать функцию, которая делала бы всю работу. Функция get_upload_info() принимает аргумент, соответствующий имени поля загружаемого файла. Затем она просматривает массивы $_FILES, $HTTP_POST_FILES и $HTTP_POST_VARS и возвращает ассоциативный массив информации о файле или неустановленное значение, если информация недоступна. При успешном вызове ключами элементов массива будут "tmp_name", "name", "size" и "type" (то есть ключи совпадают с ключами записей в массивах $_FILES или $HTTP_POST_FILES).

function get_upload_info ($name)
{
global $HTTP_POST_FILES, $HTTP_POST_VARS;
unset ($unset);
# Сначала ищем информацию в массиве PHP 4.1 $_FILES.
# Проверяем элемент tmp_name, чтобы убедиться в том, что файл существует.
# (Запись в $_FILES может присутствовать, даже если файл не был загружен.)
if (isset ($_FILES))
{
if (isset ($_FILES[$name])
&& $_FILES[$name]["tmp_name"] != ""
&& $_FILES[$name]["tmp_name"] != "none")
return ($_FILES[$name]);
return (@$unset);
}
# Затем ищем информацию в массиве PHP 4 $HTTP_POST_FILES.
if (isset ($HTTP_POST_FILES))
{
if (isset ($HTTP_POST_FILES[$name])
&& $HTTP_POST_FILES[$name]["tmp_name"] != ""
&& $HTTP_POST_FILES[$name]["tmp_name"] != "none")
return ($HTTP_POST_FILES[$name]);
return (@$unset);
}
# Ищем переменные загрузки PHP 3. Проверяем элемент_name,
# так как на самом деле $HTTP_POST_VARS[$name] может не быть полем файла.
if (isset ($HTTP_POST_VARS[$name])
&& isset ($HTTP_POST_VARS[$name . "_name"]))
{
# Сопоставляем элементам PHP 3 имена элементов PHP 4.
$info = array ();
$info["name"] = $HTTP_POST_VARS[$name . "_name"];
$info["tmp_name"] = $HTTP_POST_VARS[$name];
$info["size"] = $HTTP_POST_VARS[$name . "_size"];
$info["type"] = $HTTP_POST_VARS[$name . "_type"];return ($info);
}
return (@$unset);
}

Сценарий post_image.php использует эту функцию для получения информации об изображении и сохранения ее в MySQL.

Параметр конфигурации PHP upload_tmp_dir указывает место сохранения загруженных файлов. По умолчанию во многих системах это /tmp, но при желании вы можете изменить настройки PHP, задав сохранение в каталоге, принадлежащем пользователю с идентификатором веб-сервера, соответственно, ограничив доступ к нему.

Python
Простую форму загрузки на Python можно написать так:

print "
" \
% (os.environ["SCRIPT_NAME"])
print "Image name:
"
print ""
print "
"
print "Image file:
"
print ""
print "

"
print ""
print "
"

Когда пользователь отправляет форму, ее содержимое можно получить при помощи метода FieldStorage() модуля cgi. Результирующий объект содержит элемент для каждого входного параметра. Информацию для загруженного из поля файла можно получить так:

form = cgi.FieldStorage ()
if form.has_key ("upload_file") and form["upload_file"].filename != "":
image_file = form["upload_file"]
else:
image_file = None

В большей части прочитанных мною документов сказано, что атрибут file объекта, соответствующего полю файла, установлен в значение «истина», если файл был загружен. К сожалению, атрибут file установлен в значение «истина», даже если пользователь отправляет форму, оставив поле файла пустым. Может случиться и так, что атрибут type установлен, при том что файл не был загружен (например, в application/octet-stream). Мой опыт подсказывает, что наиболее надежный способ определить, был ли файл загружен, – проверить атрибут filename:

form = cgi.FieldStorage ()
if form.has_key ("upload_file") and form["upload_file"].filename:
print "

A file was uploaded

"else:
print "

A file was not uploaded

"

Считая, что файл был загружен, обращаемся к атрибуту параметра value для чтения файла и получения его содержимого:

data = form["upload_file"].value

Те, кому интересны подробности использования этой функции для получения информации об изображении и сохранения ее в MySQL, могут обратиться к сценарию post_image.py.

Оцените статью: (0 голосов)
0 5 0

Статьи из раздела MySQL на эту тему:
Ведение журнала Apache с помощью MySQL
Выполнение поиска и получение результатов
Журнал доступа к веб-странице
Загрузка в форму записи базы данных
Использование ввода через Web для формирования запросов