19Oct

fantastic-wooden-pier-frisco-night
В этой статье мы рассмотрим, как при помощи Node.js создать веб-сервер, который будет возвращать файл пользователю из директории public. Может возникнуть вопрос: зачем здесь Node.js? почему бы не сделать это на другом сервере? Вопрос совершенно уместен. Да, для отдачи файлов, как правило, другие сервера будут более эффективны. С другой стороны, Node.js, во-первых, тоже работает весьма неплохо, а во-вторых, перед отдачей файла может совершить какие-то интеллектуальные действия, например, обратиться к базе данных, проверить, имеет ли пользователь право на доступ к данному файлу, и только если имеет, тогда уже отдавать.

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

npm install mime

Так выглядит наш server.js:

var http = require('http');
var fs = require('fs');
var url = require('url');
var path = require('path');

var ROOT = __dirname + "/public";

http.createServer(function(req, res) {

    if (!checkAccess(req)) {
        res.statusCode = 403;
        res.end("Tell me the secret to access!");
        return;
    }

    sendFileSafe(url.parse(req.url).pathname, res);

}).listen(3000);

function checkAccess(req) {
    return url.parse(req.url, true).query.secret == 'o_O';
}

function sendFileSafe(filePath, res) {

    try {
        filePath = decodeURIComponent(filePath);
    } catch(e) {
        res.statusCode = 400;
        res.end("Bad Request");
        return;
    }

    if (~filePath.indexOf('\0')) {
        res.statusCode = 400;
        res.end("Bad Request");
        return;
    }
    
    filePath = path.normalize(path.join(ROOT, filePath));

    if (filePath.indexOf(path.normalize(ROOT)) != 0) {
        res.statusCode = 404;
        res.end("File not found");
        return;
    }

    fs.stat(filePath, function(err, stats) {
        if (err || !stats.isFile()) {
            res.statusCode = 404;
            res.end("File not found");
            return;
        }

        sendFile(filePath, res);
    });
}

function sendFile(filePath, res) {

    fs.readFile(filePath, function(err, content) {
        if (err) throw err;

        var mime = require('mime').lookup(filePath); // npm install mime
        res.setHeader('Content-Type', mime + "; charset=utf-8");
        res.end(content);
    });

}

http.create здесь очень прост.

http.createServer(function(req, res) {

    if (!checkAccess(req)) {
        res.statusCode = 403;
        res.end("Tell me the secret to access!");
        return;
    }

    sendFileSafe(url.parse(req.url).pathname, res);

}).listen(3000);

Он будет проверять, есть ли доступ к данному файлу, и если есть, то уже отдавать. Для проверки доступа мы будем использовать следующую функцию (которая является по своей сути является Fake function), которая будет выполнять url parse , и если есть параметр secret, который будет равен ‘o_O’, то считается, что доступ есть:

function checkAccess(req) {  
  return url.parse(req.url, true).query.secret == 'o_O';  
} 

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

sendFileSafe(url.parse(req.url).pathname, res);

Именно эта функция должна, получив путь от user, отослать соответствующий файл с директории public, учитывая путь директории. Важнейший аспект, который в ней должен быть заложен, это безопасность. Какой бы путь не передал пользователь, он ни в коем случае не должен получить файл вне директории public. Например, обращение:

http://localhost:3000/index.html?secret=o_O

должно возвращать файл index.html,  картинка здесь взята из директории

<img src ="deep/nodejs.jpg?secret=o_O">

А если бы мы не указали secret:

http://localhost:3000/index.html

то оно должно было бы выдать ошибку с кодом 403.

А если бы я попробовали бы указать вот так:

http://localhost:3000/server.js

то тоже была бы ошибка. И так для любых попыток выйти за пределы этой директории.

Итак, смотрим функцию sendFileSafe, чтобы получить пример безопасной работы с путем от посетителя. Эта функция состоит из нескольких шагов.

function sendFileSafe(filePath, res) {

    try {
        filePath = decodeURIComponent(filePath);
    } catch(e) {
        res.statusCode = 400;
        res.end("Bad Request");
        return;
    }

На первом шаге мы пропускаем путь через decodeURIComponent, ведь по стандарту “http” многие символы кодируются. Получив такой “url”, мы обязаны его декодировать обратно, при помощи вот такого вызова:

decodeURIComponent

При этом, если “url” закодирован заведомо неверно, то возникнет ошибка, которую необходимо поймать и обработать. Код 400 как раз означает, что “url” некорректен и запрос неверен. Можно, конечно, вернуть и 404.
Когда мы раскодировали запрос, время его проверить. Есть специальный нулевой байт, который в строке “url” присутствовать не должен:

if (~filePath.indexOf('\0')) {  
  res.statusCode = 400;  
  res.end("Bad Request");  
  return;  
}  

Если он есть, то это означает, что кто-то его злонамеренно передал, потому что некоторые встроенные функции Node.js будут работать таким байтом некорректно. Соответственно, если такой байт есть, то мы тоже возвращаем «Bad Request», запрос некорректен.

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

filePath = path.normalize(path.join(ROOT, filePath));

Этот встроенный модуль содержит набор самых разных функций для работы с путями. Например, join объединяет пути, normalize – удаляет из пути всякие странные вещи типа “.”, “:”, “//” и  т.д., то есть, делает путь более корректным. Если “url”, который передал пользователь, выглядел так:

//  /deep/nodejs.jpg

то после join с ROOT, который представляет собой вот эту директорию:

var ROOT = __dirname + "/public";

он будет выглядеть уже по-другому, например:

//  /deep/nodejs.jpg ->  /Users/learn/node/path/public/deep/nodejs.jpg  

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

Users/learn/node/path/public

то есть, что путь начинается с ROOT. Проверяем. Если это не так, то “File not Found”:

filePath = path.normalize(path.join(ROOT, filePath));

    if (filePath.indexOf(path.normalize(ROOT)) != 0) {
        res.statusCode = 404;
        res.end("File not found");
        return;
    }

Если путь разрешен, то проверим, что по нему находиться. Если ничего нет, то fs.stat вернет ошибку err:

fs.stat(filePath, function(err, stats) {  
  if (err || !stats.isFile()) {  
    res.statusCode = 404;  
    res.end("File not found");  
    return;  
  }  

Если даже ошибки нет, то нужно проверить, файл ли это. В том случае, если это не файл, то это ошибка. Ну а если файл, то все проверено, и надо его отослать. Это делает вложенный вызов sendFile:

sendFile(filePath, res);  
  });  
}  

sendFile – функция которая есть в этом же файле, но немного ниже:

function sendFile(filePath, res) {

    fs.readFile(filePath, function(err, content) {
        if (err) throw err;

        var mime = require('mime').lookup(filePath); // npm install mime
        res.setHeader('Content-Type', mime + "; charset=utf-8");
        res.end(content);
    });

}

Она для чтения файла использует вызов fs.readFile, и когда он будет прочитан, то выводит его через res.end:

res.end(content);

Обращаем ваше внимание вот на что:  ошибка в этом callback очень маловероятна

if (err) throw err;

хотя бы потому, что мы уже проверили, файл есть, это действительно файл и его можно отдать. Но, тем не менее, мало ли что. Например, может возникнуть ошибка при чтении с диска. Так или иначе как-то обработать ошибку надо. Далее, мало просто считать содержимое файла и отправить его, ведь различные файлы должны снабжаться различными заголовками “Content-Tyрe”.

Например, HTML  файл должен иметь тип text/html, файл с картинкой JPEG – image/jpeg и т.д. Нужный тип файла определяется по расширению с использованием модуля ‘mime’.

 var mime = require('mime').lookup(filePath); // npm install

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

function sendFile(filePath, res) {

    fs.readFile(filePath, function(err, content) {
        if (err) throw err;

        var mime = require('mime').lookup(filePath); // npm install mime
        res.setHeader('Content-Type', mime + "; charset=utf-8");
        res.end(content);
    });

}

потому что readFile полностью просчитывает файл, потом отсылает его в content. А представьте, что будет, если файл очень большой. А если он превышает количество свободной памяти? Тогда вообще все упадет! Поэтому, для того чтобы отсылать файл, нужно либо дать команду специализированному серверу, либо использовать потоки, которые мы рассмотрим в следующей статье.

tumblr_nvdx0jpq7F1rf2fjgo1_1280
Материалы для статьи взяты из следующего скринкаста.

We are looking forward to meeting you on our website soshace.com

18. Уроки Node.js. Работа с Файлами, Модуль fs

Всем привет! Цель этого занятия – научиться работать с бинарными данными и с файловой системой. В Node.js для работы с файлами существует модуль fs, и в нем есть множество функций для самых различных операций с файлами и директориями.

Уроки React . Урок 10.

Теперь начнем разбираться в том как, мы работаем с данными. Пока, для отображения статей мы использовали денормализированную структуру. Ее видно в файле fixtures.js. В каждой статье есть вся информация о ней, она напоминает древовидную структуру. Подобная структура удобна для чтения, но она превратит вашу жизнь в ад если вы начнете как-то изменять эти данные. Если у вас будет более или менее сложная структура, где эти зависимости будут пересекаться, например, у статьи есть автор у комментария есть автор, у автора есть своя страница. Если хранить это в том виде как это есть сейчас, то когда вы захотите поменять имя этого автора, вам придется просмотреть все места где даже чисто теоретически он может использоваться и заменить эти данные, и скорее всего Вы что-то пропустите. Поэтому, перед тем как сохранять данные в stores их нормализируют.

Уроки React . Урок 9

Всем привет! Сегодня начнем наш урок с домашнего задания, сделав его вместе. Главной целью этих уроков является помочь читателю научиться думать категориями данных – это является основой функционального программирования как такового. когда вы любую систему, например UI, вы описываете набором данных. Пока в наших уроках мы делали все самыми простыми способами. У нас были статьи которые содержат в себе все необходимое, счетчик который содержит в себе число и т.д. Мы пока не делали ничего сложнее, не объединяли все эти элементы, мы не думали какие данные стоит хранить в store а какие в state компонентов. Поэтому давайте все это обсудим. Во-первых, какие данные и где следует держать?

Leave a Reply