Выбрать главу

methods.GET = function(path, respond) {

  fs.stat(path, function(error, stats) {

    if (error && error.code == "ENOENT")

      respond(404, "File not found");

    else if (error)

      respond(500, error.toString());

    else if (stats.isDirectory())

      fs.readdir(path, function(error, files) {

        if (error)

          respond(500, error.toString());

        else

          respond(200, files.join("\n"));

      });

    else

      respond(200, fs.createReadStream(path),

              require("mime").lookup(path));

  });

};

Поскольку запросы к диску занимают время, fs.stat работает асинхронно. Когда файла не существует, fs.stat передаст объект error с кодовым свойством "ENOENT" в функцию обратного вызова. Было бы здорово, если бы Node определил разные типы ошибок для разных ошибок, но такого нет. Вместо этого он выдаёт запутанные коды в стиле Unix.

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

Объект stats возвращаемый fs.stat, рассказывает нам о файле всё. Например, size – размер файла, mtime – дата модификации. Здесь нам нужно узнать, директория это или обычный файл – это нам сообщит метод isDirectory.

Для чтения списка файлов в директории мы используем fs.readdir, и через ещё один обратный вызов, возвращаем его пользователю. Для обычных файлов мы создаём читаемый поток через fs.createReadStream и передаём его в ответ, вместе с типом содержимого, который модуль "mime" выдал для этого файла.

Код обработки DELETE будет проще:

methods.DELETE = function(path, respond) {

  fs.stat(path, function(error, stats) {

    if (error && error.code == "ENOENT")

      respond(204);

    else if (error)

      respond(500, error.toString());

    else if (stats.isDirectory())

      fs.rmdir(path, respondErrorOrNothing(respond));

    else

      fs.unlink(path, respondErrorOrNothing(respond));

  });

};

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

function respondErrorOrNothing(respond) {

  return function(error) {

    if (error)

      respond(500, error.toString());

    else

      respond(204);

  };

}

Когда ответ HTTP не содержит данных, можно использовать код статуса 204 (“no content”). Так как нам нужно обеспечить функции обратного вызова, которые либо сообщают об ошибки, или возвращают ответ 204 в разных ситуациях, я написал специальную функцию respondErrorOrNothing, которая создаёт такой обратный вызов.

Вот обработчик запросов PUT:

methods.PUT = function(path, respond, request) {

  var outStream = fs.createWriteStream(path);

  outStream.on("error", function(error) {

    respond(500, error.toString());

  });

  outStream.on("finish", function() {

    respond(204);

  });

  request.pipe(outStream);

};

Здесь нам не нужно проверять существование файла – если он есть, мы его просто перезапишем. Опять мы используем pipe для передачи данных из читаемого потока в записываемый, в нашем случае – из запроса в файл. Если создать поток не удаётся, создаётся событие “error”, о чём мы сообщаем в ответе. Когда данные переданы успешно, pipe закроет оба потока, что приведёт к запуску события “finish”. А после этого мы можем отчитаться об успехе с кодом 204.

Полный скрипт сервера лежит тут: eloquentjavascript.net/code/file_server.js. Его можно скачать и запустить через Node. Конечно, его можно менять и дополнять для решения упражнений или экспериментов.

Утилита командной строки curl, общедоступная на unix-системах, может использоваться для создания HTTP запросов. Следующий фрагмент тестирует наш сервер. –X используется для задания метода запроса, а –d для включения тела запроса.

$ curl http://localhost:8000/file.txt

File not found

$ curl -X PUT -d hello http://localhost:8000/file.txt