Máy chủ tệp tĩnh cơ bản trong NodeJS


85

Tôi đang cố gắng tạo một máy chủ tệp tĩnh trong nodejs giống như một bài tập để hiểu về nút hơn là một máy chủ hoàn hảo. Tôi biết rõ về các dự án như Connect và node-static và hoàn toàn có ý định sử dụng các thư viện đó để có thêm mã sẵn sàng cho sản xuất, nhưng tôi cũng muốn hiểu những điều cơ bản về những gì tôi đang làm việc. Với ý nghĩ đó, tôi đã mã hóa một server.js nhỏ:

var http = require('http'),
    url = require('url'),
    path = require('path'),
    fs = require('fs');
var mimeTypes = {
    "html": "text/html",
    "jpeg": "image/jpeg",
    "jpg": "image/jpeg",
    "png": "image/png",
    "js": "text/javascript",
    "css": "text/css"};

http.createServer(function(req, res) {
    var uri = url.parse(req.url).pathname;
    var filename = path.join(process.cwd(), uri);
    path.exists(filename, function(exists) {
        if(!exists) {
            console.log("not exists: " + filename);
            res.writeHead(200, {'Content-Type': 'text/plain'});
            res.write('404 Not Found\n');
            res.end();
        }
        var mimeType = mimeTypes[path.extname(filename).split(".")[1]];
        res.writeHead(200, mimeType);

        var fileStream = fs.createReadStream(filename);
        fileStream.pipe(res);

    }); //end path.exists
}).listen(1337);

Câu hỏi của tôi là gấp đôi

  1. Đây có phải là cách "đúng đắn" để tạo và phát trực tuyến html cơ bản, v.v. trong nút hay có phương pháp tốt hơn / thanh lịch hơn / mạnh mẽ hơn không?

  2. Có phải .pipe () trong nút về cơ bản chỉ thực hiện những việc sau không?

.

var fileStream = fs.createReadStream(filename);
fileStream.on('data', function (data) {
    res.write(data);
});
fileStream.on('end', function() {
    res.end();
});

Cảm ơn mọi người!


2
Tôi đã viết một mô-đun cho phép bạn làm điều đó mà không ảnh hưởng đến tính linh hoạt. Nó cũng tự động lưu trữ tất cả các tài nguyên của bạn. Hãy khám phá: github.com/topcloud/cachemere
Jon

2
Hơi buồn cười là bạn chọn (?) Để trả về '404 Not Found' với mã trạng thái HTTP '200 OK'. Nếu không có tài nguyên nào được tìm thấy tại URL, thì mã thích hợp phải là 404 (và những gì bạn viết trong nội dung tài liệu thường có tầm quan trọng thứ yếu). Nếu không, bạn sẽ nhầm lẫn với rất nhiều tác nhân người dùng (bao gồm trình thu thập dữ liệu web và các bot khác) cung cấp cho họ tài liệu không có giá trị thực (mà họ cũng có thể lưu vào bộ nhớ cache).
Amn

1
Cảm ơn. Vẫn hoạt động tốt trong nhiều năm sau đó.
statosdotcom

1
Cảm ơn! mã này đang hoạt động hoàn hảo. Nhưng bây giờ sử dụng fs.exists()thay vì path.exists()trong mã trên. Chúc mừng! và tuyệt vời! đừng quên return:
Kaushal28

LƯU Ý : 1) fs.exists()bị phản đối . Sử dụng fs.access()hoặc thậm chí tốt hơn đối với trường hợp sử dụng trên , fs.stat(). 2) url.parseNỮA ; sử dụng new URLGiao diện mới hơn thay thế.
rags2riches

Câu trả lời:


44
  • Máy chủ cơ bản của bạn có vẻ tốt, ngoại trừ:

    Có một returntuyên bố bị thiếu.

    res.write('404 Not Found\n');
    res.end();
    return; // <- Don't forget to return here !!
    

    Và:

    res.writeHead(200, mimeType);

    nên là:

    res.writeHead(200, {'Content-Type':mimeType});

  • pipe()Về cơ bản, Yes thực hiện điều đó, nó cũng tạm dừng / tiếp tục luồng nguồn (trong trường hợp máy thu chậm hơn). Đây là mã nguồn của pipe()hàm: https://github.com/joyent/node/blob/master/lib/stream.js


2
điều gì sẽ xảy ra nếu tên tệp giống như blah.blah.css?
ShrekOverflow 14/02/12

2
mimeType sẽ blah trong trường hợp đó xP
ShrekOverflow 14/02/12

5
Đó không phải là sự chà xát mặc dù? nếu bạn tự viết, bạn đang yêu cầu những loại lỗi này. Việc học tốt sẽ phát huy tác dụng nhưng tôi đang học cách trân trọng "kết nối" hơn là tự mình lăn xả. Vấn đề với trang này là mọi người đang tìm cách để tạo một máy chủ tệp đơn giản và lỗi tràn ngăn xếp xuất hiện trước. Câu trả lời này đúng nhưng mọi người không tìm kiếm nó, chỉ là một câu trả lời đơn giản. Tôi đã phải tự mình tìm ra cái đơn giản hơn nên đặt nó ở đây.
Jason Sebring

1
+1 vì không dán liên kết đến giải pháp dưới dạng thư viện nhưng thực sự viết câu trả lời cho câu hỏi.
Shawn Whinnery

57

Càng đơn giản càng đẹp

Chỉ cần truy cập dấu nhắc lệnh đầu tiên trong dự án của bạn và sử dụng

$ npm install express

Sau đó viết mã app.js của bạn như sau:

var express = require('express'),
app = express(),
port = process.env.PORT || 4000;

app.use(express.static(__dirname + '/public'));
app.listen(port);

Sau đó, bạn sẽ tạo một thư mục "công cộng" nơi bạn đặt các tệp của mình. Tôi đã thử nó theo cách khó hơn đầu tiên nhưng bạn phải lo lắng về các loại kịch câm chỉ là phải lập bản đồ các thứ tốn thời gian và sau đó lo lắng về các loại phản hồi, v.v. vv vv ... không cảm ơn bạn.


2
+1 Có rất nhiều điều để nói về việc sử dụng mã đã thử nghiệm thay vì sử dụng mã của riêng bạn.
jcollum

1
Tôi đã thử xem tài liệu nhưng dường như không tìm thấy nhiều, bạn có thể giải thích đoạn mã của mình đang làm gì không? Tôi đã cố gắng sử dụng biến thể cụ thể này và tôi không biết có thể thay thế cái gì bằng cái gì.
onaclov2000,

3
Nếu bạn muốn danh sách thư mục, chỉ cần thêm .use (connect.directory ('public')) ngay sau dòng connect.static, thay thế public, bằng đường dẫn của bạn. Xin lỗi vì hành vi xâm nhập này, nhưng tôi nghĩ nó sẽ giải tỏa mọi thứ cho tôi.
onaclov2000,

1
Bạn cũng có thể 'Sử dụng jQuery'! Đây không phải là câu trả lời cho câu hỏi của OP mà là giải pháp cho một vấn đề thậm chí không tồn tại. OP nói rằng mục đích của thử nghiệm này là để học Node.
Shawn Whinnery

1
@JasonSebring Tại sao require('http')ở dòng thứ hai?
Xiao Peng - ZenUML.com

19

Tôi cũng muốn hiểu những gì đang diễn ra.

Tôi nhận thấy một số điều trong mã của bạn mà bạn có thể muốn xóa:

  • Nó bị treo khi tên tệp trỏ đến một thư mục, vì tồn tại là đúng và nó cố gắng đọc một luồng tệp. Tôi đã sử dụng fs.lstatSync để xác định sự tồn tại của thư mục.

  • Nó không sử dụng đúng mã phản hồi HTTP (200, 404, v.v.)

  • Trong khi MimeType đang được xác định (từ phần mở rộng tệp), nó không được đặt chính xác trong res.writeHead (như người quản lý đã chỉ ra)

  • Để xử lý các ký tự đặc biệt, bạn có thể muốn bỏ bố cục tiểu

  • Nó tuân theo các liên kết tượng trưng một cách mù quáng (có thể là một mối lo ngại về bảo mật)

Do đó, một số tùy chọn apache (FollowSymLinks, ShowIndexes, v.v.) bắt đầu có ý nghĩa hơn. Tôi đã cập nhật mã cho máy chủ tệp đơn giản của bạn như sau:

var http = require('http'),
    url = require('url'),
    path = require('path'),
    fs = require('fs');
var mimeTypes = {
    "html": "text/html",
    "jpeg": "image/jpeg",
    "jpg": "image/jpeg",
    "png": "image/png",
    "js": "text/javascript",
    "css": "text/css"};

http.createServer(function(req, res) {
  var uri = url.parse(req.url).pathname;
  var filename = path.join(process.cwd(), unescape(uri));
  var stats;

  try {
    stats = fs.lstatSync(filename); // throws if path doesn't exist
  } catch (e) {
    res.writeHead(404, {'Content-Type': 'text/plain'});
    res.write('404 Not Found\n');
    res.end();
    return;
  }


  if (stats.isFile()) {
    // path exists, is a file
    var mimeType = mimeTypes[path.extname(filename).split(".").reverse()[0]];
    res.writeHead(200, {'Content-Type': mimeType} );

    var fileStream = fs.createReadStream(filename);
    fileStream.pipe(res);
  } else if (stats.isDirectory()) {
    // path exists, is a directory
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.write('Index of '+uri+'\n');
    res.write('TODO, show index?\n');
    res.end();
  } else {
    // Symbolic link, other?
    // TODO: follow symlinks?  security?
    res.writeHead(500, {'Content-Type': 'text/plain'});
    res.write('500 Internal server error\n');
    res.end();
  }

}).listen(1337);

4
tôi có thể đề xuất "var mimeType = mimeTypes [path.extname (filename) .split (". "). reverse () [0]];" thay thế? một số tên tệp có nhiều hơn một "." ví dụ: "my.cool.video.mp4" hoặc "download.tar.gz"
không đồng bộ hóa

Điều này bằng cách nào đó ngăn ai đó sử dụng url như thư mục /../../../ home / user / jackpot.privatekey? Tôi thấy phép nối để đảm bảo đường dẫn xuống dòng, nhưng tôi đang tự hỏi liệu việc sử dụng ký hiệu ../../../ có phù hợp với điều đó hay không. Có lẽ tôi sẽ tự kiểm tra nó.
Reynard

Nó không hoạt động. Tôi không chắc tại sao, nhưng thật tuyệt khi biết.
Reynard

tốt, một trận đấu RegEx cũng có thể thu thập phần mở rộng; var mimeType = mimeTypes[path.extname(filename).match(/\.([^\.]+)$/)[1]];
John Mutuma

4
var http = require('http')
var fs = require('fs')

var server = http.createServer(function (req, res) {
  res.writeHead(200, { 'content-type': 'text/plain' })

  fs.createReadStream(process.argv[3]).pipe(res)
})

server.listen(Number(process.argv[2]))

4
Có thể muốn giải thích điều này nhiều hơn một chút.
Nathan Tuggy

3

Làm thế nào về mẫu này, tránh kiểm tra riêng rằng tệp tồn tại

        var fileStream = fs.createReadStream(filename);
        fileStream.on('error', function (error) {
            response.writeHead(404, { "Content-Type": "text/plain"});
            response.end("file not found");
        });
        fileStream.on('open', function() {
            var mimeType = mimeTypes[path.extname(filename).split(".")[1]];
            response.writeHead(200, {'Content-Type': mimeType});
        });
        fileStream.on('end', function() {
            console.log('sent file ' + filename);
        });
        fileStream.pipe(response);

1
bạn quên mimetype trong trường hợp thành công. Tôi đang sử dụng thiết kế này, nhưng thay vì tạo đường ống ngay lập tức, tôi tạo đường ống cho chúng trong sự kiện 'mở' của luồng filestream: writeHead cho mimetype, sau đó là đường ống. Phần cuối không cần thiết: readable.pipe .
GeH

Được sửa đổi theo nhận xét của @GeH.
Brett Zamir

Nên làfileStream.on('open', ...
Petah

2

Tôi đã tạo một hàm httpServer với các tính năng bổ sung để sử dụng chung dựa trên câu trả lời của @Jeff Ward

  1. sữa trứng
  2. index.html trả về nếu req === dir

Sử dụng:

httpServer(dir).listen(port);

https://github.com/kenokabe/ConciseStaticHttpServer

Cảm ơn.


0

các mô-đun st làm phục vụ các tập tin tĩnh dễ dàng. Đây là phần trích xuất của README.md:

var mount = st({ path: __dirname + '/static', url: '/static' })
http.createServer(function(req, res) {
  var stHandled = mount(req, res);
  if (stHandled)
    return
  else
    res.end('this is not a static file')
}).listen(1338)

0

Câu trả lời @JasonSebring đã chỉ cho tôi đi đúng hướng, tuy nhiên mã của anh ấy đã lỗi thời. Đây là cách bạn thực hiện với connectphiên bản mới nhất .

var connect = require('connect'),
    serveStatic = require('serve-static'),
    serveIndex = require('serve-index');

var app = connect()
    .use(serveStatic('public'))
    .use(serveIndex('public', {'icons': true, 'view': 'details'}))
    .listen(3000);

Trong connect GitHub Repository có các phần mềm trung gian khác mà bạn có thể sử dụng.


Tôi chỉ sử dụng express thay vì một câu trả lời đơn giản hơn. Phiên bản express mới nhất có tĩnh được đưa vào nhưng không có nhiều thứ khác. Cảm ơn!
Jason Sebring

Nhìn vào connecttài liệu, nó chỉ là một wrappercho middleware. Tất cả những điều thú vị khác middlewarelà từ expresskho lưu trữ, vì vậy về mặt kỹ thuật, bạn có thể sử dụng các API đó bằng cách sử dụng express.use().
ffleandro
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.