Express как принять файл

Обновлено: 06.07.2024

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

Это пример HTML-формы, которая позволяет пользователю загружать файл:

Не забудьте добавить enctype="multipart/form-data" в форму, иначе файлы не будут загружены

Когда пользователь нажимает кнопку отправки, браузер автоматически создает POST запрос в /submit-form URL в том же источнике, что и страница. Браузер отправляет содержащиеся данные, а не закодированные, как в обычной форме. application/x-www-form-urlencoded , но, как multipart/form-data .

На стороне сервера обработка составных данных может быть сложной и подверженной ошибкам, поэтому мы собираемся использовать служебную библиотеку под названиемгрозный.Вот репо на GitHub, он имеет более 4000 звезд и содержится в хорошем состоянии.

Вы можете установить его, используя:

Затем включите его в свой файл Node.js:

Теперь в POST конечная точка на /submit-form route, мы создаем новую форму Formidable, используя formidable.IncomingForm() :

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

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

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

  • file.size , размер файла в байтах
  • file.path , путь к файлу
  • file.name , имя файла
  • file.type , MIME-тип файла

По умолчанию путь к временной папке и может быть изменен, если вы прислушиваетесь к fileBegin мероприятие:

В этой главе мы покажем вам как работать с HTML формами в Express, используя Pug, и в частности как написать формы для создания, обновления и удаления документов из базы данных.

Предварительные знания: Завершите изучение предыдущих тем учебника, включая Учебник Express Часть 5: Отображение данных библиотеки
Цель: Понять, как писать формы для получения данных от пользователей и обновлять базу данных с этими данными.

Обзор

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

В этом уроке мы покажем вам, как вышеуказанные операции могут быть выполнены в Express. По пути мы расширим веб-сайт LocalLibrary, чтобы пользователи могли создавать, редактировать и удалять элементы из библиотеки.

Примечание: Мы не рассматривали, как ограничить определённые маршруты аутентифицированными или авторизованными пользователями, поэтому на данный момент любой пользователь сможет вносить изменения в базу данных.

HTML Forms

Первый краткий обзор HTML Forms. Рассмотрим простую HTML-форму с одним текстовым полем для ввода имени некоторой "команды" и связанной с ней меткой:

Simple name field example in HTML form

Определённые в HTML формы собираются внутри тэга <form>. </form> , содержащего хотя ы один элемент input с type="submit" .

Хотя здесь мы включили только одно (текстовое) поле для ввода имени команды, форма может содержать любое количество других элементов ввода и связанных с ними меток. Атрибут type определяет какой из виджетов будет выбран для отображения поля. Атрибуты name и id идентифицируют поле в JavaScript/CSS/HTML, а value определяет его первоначальное значение. Связанная с полем метка, задаётся с помощью тега label (располагается строкой выше и содержит в себе подпись "Enter name"). Связь метки и поля ввода устанавливается при помощи атрибута for , в котором указывается значение идентификатора поля ( input id ).

Процесс обработки формы

Обработка форм использует все те же методы, которые мы изучили для отображения информации о наших моделях: маршрут отправляет запрос в функцию контроллера, которая выполняет все необходимые действия с базой данных, включая чтение данных из моделей, а затем генерирует и возвращает HTML-страницу. Что усложняет ситуацию, так это то, что сервер также должен иметь возможность обрабатывать данные, предоставленные пользователем, и повторно отображать форму с информацией об ошибках, если есть какие-либо проблемы.

Блок-схема процесса обработки запросов формы показана ниже, начиная с запроса страницы, содержащей форму (показана зелёным цветом):


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

Часто код обработки формы реализуется с помощью GET route для начального отображения формы и POST route к тому же пути для обработки проверки и обработки данных формы. Это подход, который будет использоваться в этом уроке!

Сам Express не предоставляет какой-либо конкретной поддержки для операций обработки форм, но он может использовать промежуточное программное обеспечение для обработки POST и GET параметров из формы, а также для проверки/очистки их значений.

Валидация и обработка

Перед сохранением данных формы их необходимо проверить и очистить:

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

В этом уроке мы будем использовать популярный модуль express-validator для проверки и очистки данных формы.

Установка

Установите модуль, выполнив следующую команду в корне проекта

Использование express-validator

Note: express-validator руководство на Github предоставляет хороший обзор API. Мы рекомендуем вам прочитать это, чтобы получить представление о всех его возможностях (включая создание пользовательских валидаторов). Ниже мы рассмотрим только подмножество, которое полезно для LocalLibrary.

Для того, чтобы использовать валидатор в наших контроллерах, мы должны требовать функции, которые мы хотим использовать из модулей 'express-validator/check' и 'express-validator/filter', как показано ниже:

Есть много доступных функций, позволяющих проверять и очищать данные из параметров запроса, тела, заголовков, файлов cookie и т. д., или все сразу. Для этого урока мы будем использовать body , sanitizeBody , and validationResult (как "требуется" выше).

Функции определяются следующим образом:

Note: Вы также можете добавить встроенные средства очистки, такие как trim() , как показано выше. Однако средства очистки, применяемые здесь, применяются только к шагу проверки. Если требуется очистить конечный результат, необходимо использовать отдельный метод очистки, как показано ниже.

Цепочки проверки и очистки являются промежуточными запросами, которые должны быть переданы обработчику Express -маршрута (мы делаем это косвенно, через контроллер). При запуске промежуточного по каждый валидатор / средства очистки выполняется в указанном порядке..

Мы рассмотрим некоторые реальные примеры, когда мы реализуем LocalLibrary формы ниже.

Дизайн формы

Многие модели в библиотеке связаны / зависимы—например, книга требует автора, а также может иметь один или несколько жанров. Это поднимает вопрос о том, как мы должны обрабатывать случай, когда пользователь хочет:

  • Создайте объект, если связанные с ним объекты ещё не существуют (например, книга, в которой не определён объект автора).
  • Удаление объекта, который все ещё используется другим объектом (например, удаление жанра, который все ещё используется книгой).

Для этого проекта мы упростили реализацию, объявив, что форма может быть только:

  • Создайте объект, используя объекты, которые уже существуют (таким образом, пользователи должны будут создать все необходимые экземпляры автора и жанра, прежде чем пытаться создать любые объекты книги).
  • Удалите объект, если на него не ссылаются другие объекты (например, вы не сможете удалить книгу, пока не будут удалены все связанные объекты BookInstance).

Note: Более" надёжная " реализация может позволить создавать зависимые объекты при создании нового объекта и удалять любой объект в любое время (например, путём удаления зависимых объектов или путём удаления ссылок на удалённый объект из базы данных).

Маршруты

Чтобы реализовать наш код обработки форм, нам понадобятся два маршрута с одинаковым шаблоном URL. Первый ( GET ) маршрут используется для отображения новой пустой формы создания объекта. Второй маршрут ( POST ) используется для проверки введённых пользователем данных, а затем сохранения информации и перенаправления на страницу сведений (если данные верны) или повторного отображения формы с ошибками (если данные неверны).

Мы уже создали маршруты для всех страниц создания нашей модели в /routes/catalog.js (in a previous tutorial). Например, жанровые маршруты показаны ниже:

Express формы — подразделы

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

    — Определение нашей страницы для создания объектов Genre . — Определение страницы для создания объектов Author . — Определение страницы/формы для создания объектов Book . — Определение страницы/формы для создания объектов BookInstance . — Определение страницы для удаления объектов Author . — Определение страницы для обновления объектов Book .

Challenge yourself

Implement the delete pages for the Book , BookInstance , and Genre models, linking them from the associated detail pages in the same way as our Author delete page. The pages should follow the same design approach:

  • If there are references to the object from other objects, then these other objects should be displayed along with a note that this record can't be deleted until the listed objects have been deleted.
  • If there are no other references to the object then the view should prompt to delete it. If the user presses the Delete button, the record should then be deleted.
  • Deleting a Genre is just like deleting an Author as both objects are dependencies of Book (so in both cases you can delete the object only when the associated books are deleted).
  • Deleting a Book is also similar, but you need to check that there are no associated BookInstances .
  • Deleting a BookInstance is the easiest of all, because there are no dependent objects. In this case you can just find the associated record and delete it.

Implement the update pages for the BookInstance , Author , and Genre models, linking them from the associated detail pages in the same way as our Book update page.

  • The Book update page we just implemented is the hardest! The same patterns can be used for the update pages for the other objects.
  • The Author date of death and date of birth fields, and the BookInstance due_date field are the wrong format to input into the date input field on the form (it requires data in form "YYYY-MM-DD"). The easiest way to get around this is to define a new virtual property for the dates that formats the dates appropriately, and then use this field in the associated view templates.
  • If you get stuck, there are examples of the update pages in the example here.

Summary

Express, node, and third party packages on NPM provide everything you need to add forms to your website. In this article you've learned how to create forms using Pug, validate and sanitize input using express-validator, and add, delete, and modify records in the database.

You should now understand how to add basic forms and form-handling code to your own node websites!

Для работы с файлами в Node.js используется встроенный модуль fs , который выполняет все синхронные и асинхронные операции ввода/вывода применительно к файлам. Чтение и запись файла могут осуществляться одним из двумя способов:

  • с использованием Buffer ;
  • через создание соответствующего потока.

Чтение файлов и директорий¶

Для чтения файла в асинхронном режиме используется метод Node.js readFile() , который принимает три параметра:

  • путь к файлу;
  • кодировка;
  • callback-функция, вызываемая после получения содержимого файла.

Callback-функции передается два аргумента: ошибка и полученные данные в строковом формате. Если операция прошла успешна, то в качестве ошибки передается null .

Если в readFile() не указать кодировку, то данные файла будут возвращены в формате Buffer .

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

Node.js readFileSync() возвращает результат чтения файла и принимает два параметра:

Обработка и перехват ошибок при использовании readFileSync() осуществляется с помощью конструкции try<. >catch() <. >.

Чтобы инициировать ошибку, укажите неправильный путь к файлу.

Методы readFile() и readFileSync() для работы с файлами используют Buffer . Но есть и другой способ считать содержимое файла: создать поток с помощью Node.js fs.createReadStream() . Любой поток в Node.js является экземпляром класса EventEmitter , который позволяет обрабатывать возникающие в потоке события.

Параметры, принимаемые fs.createReadStream() :

  • путь к файлу;
  • объект со следующими настройками:
  • encoding - кодировка (по умолчанию utf8 );
  • mode - режим доступа (по умолчанию 0o666 );
  • autoClose - если true , то при событиях error и finish поток закроется автоматически (по умолчанию true ).

Вместо объекта настроек можно передать строку, которая будет задавать кодировку.

Использование потока имеет ряд преимуществ перед Buffer :

Для чтения директорий используются методы readdir() и readdirSync() , для асинхронного и синхронного режимов соответственно.

Node.js readdir() работает асинхронно и принимает три аргумента:

  • путь к директории;
  • кодировку;
  • callback-функцию, которая принимает аргументами ошибку и массив файлов директории (при успешном выполнении операции ошибка передается как null ).

Node.js readdirSync() работает синхронно, возвращает массив найденных файлов и принимает два параметра:

Создание и запись файлов и директорий¶

В Node.js файлы могут быть записаны также синхронно и асинхронно. Для асинхронной записи имеется метод writeFile() , принимающий следующие аргументы:

  • путь к файлу;
  • данные для записи;
  • параметры записи:
  • кодировка (по умолчанию utf8 );
  • права доступа (по умолчанию 0o666 );
  • callback-функция, которая вызывается по завершению операции и единственным аргументом принимает ошибку (в случае успешной записи передается null ).

Если нет необходимости указывать параметры записи, то третьим параметром Node.js writeFile() можно сразу передать callback-функцию.

Для синхронной записи Node.js файла используйте writeFileSync() . Метод принимает все те же аргументы, что и writeFile() за исключением callback-функции. В качестве значения возвращает undefined .

Как и в случае с readFileSync() обработка ошибок происходит с помощью try<. >catch() <. >.

Методы writeFile() и writeFileSync() перезаписывают уже имеющуюся в файле информацию новыми данными. Если вам нужно внести новые данные без удаления старых, используйте методы appendFIle() и appendFileAsync() , которые имеют идентичные параметры.

Для записи файла через потока ввода имеется метод fs.createWriteStream() , который возвращает поток ввода и принимает два параметра:

  • путь к файлу;
  • объект со следующими настройками:
  • encoding - кодировка (по умолчанию utf8 );
  • mode - режим доступа (по умолчанию 0o666 );
  • autoClose - если true , то при событиях error и finish поток закроется автоматически (по умолчанию true ).

Чтобы создать директорию, используйте методы mkdir() и mkdirSync() .

Node.js mkdir() работает асинхронно и принимает в качестве параметров:

  • путь к директории;
  • объект со следующими настройками:
  • recursive - если true , создает директорию и все ее родительские директории согласно указанному пути, если они еще не существуют (по умолчанию false , т. е. все родительские директории уже должны быть созданы, иначе будет сгенерирована ошибка);
  • mode - режим доступа, параметр не поддерживается на ОС Windows (по умолчанию 0o777 );
  • callback-функцию, которая единственным аргументом принимает ошибку, при успешном создании директории передается null .

Вторым параметром можно сразу передать callback-функцию.

Node.js mkdirSync() создает директорию синхронно и возвращает undefined . Обработка ошибок осуществляется через try<. >catch() <. >. Метод mkdirSync() принимает те же параметры, что и mkdir() , за исключением callback-функции.

Удаление файлов и директорий¶

Чтобы удалить в Node.js файлы используйте методы unlink() и unlinkSync() .

Метод unlink() асинхронный и принимает имя файла, который нужно удалить, и callback-функцию с ошибкой в качестве параметра ( null , если удаление прошло успешно).

Для синхронного удаления файла используйте unlinkSync() , которому единственным аргументом передается имя файла.

Для удаления директорий имеются методы rmdir() и rmdirSync() соответственно. Они полностью идентичны unlink() и unlinkSync() , только вместо имени файла принимают имя директории.

От автора: если вы создаете веб-приложение, то в первые дни, вероятно, сталкиваетесь с необходимостью создавать HTML-формы. Они являются большой частью веб experience, и могут быть довольно сложными. И здесь может прийти на помощь Node js Express.

Обычно процесс обработки формы включает в себя:

отображение пустой HTML-формы в ответ на исходный GET запрос

пользователь, отправляющий форму с данными в POST запросе

проверка как в клиенте, так и на сервере


JavaScript. Быстрый старт

Изучите основы JavaScript на практическом примере по созданию веб-приложения

Действия с очищенными данными на сервере, если они действительны

Обработка данных формы также связана с дополнительными соображениями безопасности.

Формы, загрузки файлов и безопасность с помощью Node.js и Express

Настройка

Убедитесь, что у вас установлена последняя версия Node.js; node –v должен быть версии 8.9.0 или выше. Загрузите исходный код с git:

Тут не слишком большой код. Это просто экспресс-настройка с использованием EJS-шаблонов и обработчиков ошибок:

Корневой URL /просто отображает index.ejs представление.

Отображение формы

Когда пользователи создают GET запрос в /contact, мы хотим отобразить новое представление contact.ejs:

< textarea class = "input" id = "message" name = "message" rows = "4" autofocus > < / textarea > < input class = "input" id = "email" name = "email" type = "email" value = "" / >

Посмотрите, как это выглядит на //localhost:3000/contact.

Представление формы

Чтобы получать значения POST в Express, необходимо сначала включить промежуточное программное обеспечение body-parser, которое предоставляет отображаемые значения формы req.body в обработчиках маршрутов. Добавьте его в конец middlewares массива:

Это обычное соглашение форм для обратных POST-данных к тому же URL-адресу, который использовался в первоначальном запросе GET. Давайте сделаем его и сразу обработаем POST /contact для обработки ввода пользователя.

Если в проверке есть ошибки, мы делаем следующее:

отображаем ошибки в верхней части формы

устанавливаем входные значения на то, что было отправлено на сервер

отображаем встроенные ошибки ниже входных значений

добавляем form-field-invalid класс в поля с ошибками.

< h2 class = "errors-heading" > Oops , please correct the following : < / h2 > < div class = "form-field <%= errors . message ? 'form-field-invalid' : '' %> " > < textarea class = "input" id = "message" name = "message" rows = "4" autofocus > <%= data . message %> < / textarea > < div class = "form-field <%= errors . email ? 'form-field-invalid' : '' %> " > < input class = "input" id = "email" name = "email" type = "email" value = " <%= data . email %> " / >

Отправьте форму, //localhost:3000/contact, чтобы увидеть её в действии. Это все, что нам нужно от представления.

Валидация и защита

Существует удобное промежуточное программное обеспечение express-validator для валидации и дезактивации данных с использованием библиотеки validator.js, давайте включим его в наш middlewares массив:

Валидация

Защита

Эти средства могут быть просто привязаны к концу валидаторов:

Функция matchedData возвращает выходные данные после чистки входных данных.

Действительная форма

Для этого нужно включить три промежуточных устройства:


JavaScript. Быстрый старт

Изучите основы JavaScript на практическом примере по созданию веб-приложения

Средство express-flash промежуточного уровня добавляет req.flash(type, message, которые мы можем использовать в наших обработчиках маршрутов:

// Homework: send sanitized data in an email or persist in a db req . flash ( 'success' , 'Thanks for the message! I‘ll be in touch' )

Средство express-flash промежуточного ПО добавляет messages к req.locals, которому доступны все представления:

< div class = "flash flash-success" > <%= messages . success %> < / div >

Вопросы безопасности

Всегда используйте шифрование TLS над //при работе с формами, так как представленные данные шифруются при пересылке через Интернет. Если вы отправляете данные формы //, они отправляются в виде обычного текста и могут быть видны любому, кто заглядывает в эти пакеты во время путешествия по Интернету.

Носите шлем

Подделка кросс-сайт запросов/Cross-site Request Forgery (CSRF)

Вы можете защитить себя от подделки запроса, создав уникальный знак, тогда пользователю предоставляется форма, а затем перед обработкой данных POST проверяется этот знак. Здесь также есть промежуточное ПО, которое помогает с этим:

В запросе GET мы создаем знак:

А также в ответе проверки на ошибки:

Теперь нужно просто включить знак в скрытый ввод:

Вот и всё, что от нас требуется.

Нам не нужно изменять наш обработчик запросов POST, поскольку все запросы POST теперь потребуют действительный знак csurf промежуточного программного обеспечения. Если действительный знак CSRF не указан, возникает ошибка ForbiddenError, которую может обработать обработчик ошибок, определенный в конце server.js.

Вы можете проверить это самостоятельно, отредактировав или удалив знак из формы с помощью инструментов разработчика браузера и отправки.

Межсайтовый скриптинг/Cross-site Scripting (XSS)

Вы должны проявлять осторожность при отображении представленных пользователем данных в виде HTML, так как это может открыть вас для межсайтового скриптинга (XSS) . Все языки шаблонов предоставляют различные методы вывода значений. EJS <%= value %> выводит значение экранированного HTML, чтобы защитить вас от XSS, тогда как <%- value %> выводит необработанную строку.

Всегда используйте экранированный вывод <%= value %> при работе с представленными пользователем значениями. Используйте только исходные данные, если вы уверены, что это безопасно.

Загрузка файлов

Для обработки многостраничных загрузок вам потребуется дополнительное промежуточное программное обеспечение. Там есть экспресс пакет multer который мы будем использовать здесь:


Node.js с Express — это популярный дуэт, используемый многими приложениями во всем мире. Данный урок познакомит вас с функциональностью этих инструментов на примере сборки простого веб-сервера.

Создаваемый сервер будет обслуживать HTML-страницу, доступную другим людям. К концу статьи вы усвоите базовые знания о:

  • Node.js;
  • Express;
  • npm;
  • создании маршрутов Express;
  • обслуживании HTML;
  • настройке статических ресурсов Express.

Совет: не копируйте код урока, а напишите его сами. Это позволит вам лучше его понять и усвоить.

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

После создания проекта нужно его инициализировать:

Эта команда соз д ает файл package.json и инициализирует его с предустановленными значениями. Если вы захотите сами заполнить его поля, удалите флаг -y и следуйте инструкциям.

После инициализации проекта Node.js мы переходим к следующему шагу — добавлению Express. Установка пакетов в Node.js выполняется командой npm install packageName .

Для добавления последней стабильной версии Express выполните:

После установки Express файл package.json будет выглядеть так:

Express перечислен среди dependencies , значит, он установился успешно. Переходим к третьему шагу — созданию сервера.

Прежде чем продолжать, нужно создать для сервера JS-файл. Выполните в терминале следующую команду:

Теперь откройте этот файл и пропишите в нем:

Что эти строки делают?

  1. Первая импортирует Express в проект, чтобы его можно было использовать. При каждом добавлении в проект пакета необходимо импортировать его туда, где он будет использоваться.
  2. Вторая строка вызывает функцию express , которая создает новое приложение, после чего присваивает результат константе app .

Создание маршрутов и прослушивание порта

Пропишите в файле следующий код:

Разберем его согласно приведенной ранее структуре:

Следовательно, когда пользователь выполняет запрос GET к домашней странице, т.е. localhost:3333 , вызывается стрелочная функция и отображается фраза “Hello WWW!”

Последним шагом подготовки сервера к работе будет настройка слушателя. Понадобится указать для приложения конкретный порт. Напишите нижеприведенный код в конец JS-файла.

Чтобы иметь возможность запускать сервер, вам нужно будет вызывать метод listen . При этом вы также можете изменить номер порта (3333) на любой другой.

Доступ к приложению в браузере

Для запуска приложения выполните в терминале node index.js . Имейте в виду, что index.js — это произвольное имя, которое я выбрал для данного урока, так что можете назвать его app.js или как-угодно иначе.


Вы отлично справились с настройкой веб-сервера Node.js + Express. В следующем разделе мы настроим статическое содержимое, а именно JavaScript, CSS, HTML, изображения и т.д.

Наше приложение пока что выглядит не очень. Почему бы не добавить ему структуру и стилизацию? Но куда все это нужно добавлять?

В этом разделе вы узнаете, как настраивать и передавать статические ресурсы, такие как HTML, JavaScript, CSS и изображения.

Импорт модуля path

Первым делом нужно импортировать в приложение модуль path . Устанавливать ничего не нужно, потому что path предустановлен в Node изначально.

Пропишите в начале файла эту строку:

Зачем вообще этот модуль? Он позволяет генерировать абсолютные пути, которые необходимы для передачи статических файлов. Добавьте следующую строку в приложение перед определением маршрутов:

path.join получает два аргумента:

  • Текущую рабочую директорию (cwd).
  • Вторую директорию, которую нужно объединить с cwd.

На данный момент сервер должен выглядеть так:

Создание каталога public и добавление ресурсов

Создайте каталог и перейдите в него с помощью следующих команд:

Теперь создадим пустые файлы, куда затем добавим HTML, CSS и JavaScript. Выполните в терминале следующие команды:

Читайте также: