Cập nhật năm 2016
Ví dụ với Express và không có Express thực sự hoạt động
Câu hỏi này đã hơn 5 tuổi nhưng mỗi câu trả lời đều có một số vấn đề .
TL; DR
Cuộn xuống để lấy ví dụ để phục vụ một hình ảnh với:
express.static
express
connect
http
net
Tất cả các ví dụ cũng có trên GitHub: https://github.com/rsp/node-static-http-servers
Kết quả kiểm tra có sẵn trên Travis: https://travis-ci.org/rsp/node-static-http-servers
Giới thiệu
Sau hơn 5 năm kể từ khi câu hỏi này được hỏi, chỉ có một câu trả lời đúng bởi Generalhenry nhưng mặc dù câu trả lời đó không có vấn đề gì với mã, nó dường như có một số vấn đề với việc tiếp nhận . Nó đã được nhận xét rằng nó "không giải thích nhiều ngoài việc làm thế nào để dựa vào người khác để hoàn thành công việc" và thực tế có bao nhiêu người đã bỏ phiếu nhận xét này cho thấy rõ ràng rất nhiều điều cần làm rõ.
Trước hết, một câu trả lời hay cho "Cách phục vụ hình ảnh bằng Node.js" không phải là triển khai một máy chủ tệp tĩnh từ đầu và làm điều đó không tốt. Một câu trả lời tốt là sử dụng một mô-đun như Express thực hiện công việc một cách chính xác .
Trả lời các bình luận nói rằng sử dụng Express "không giải thích gì nhiều ngoài cách dựa vào người khác để hoàn thành công việc" cần lưu ý rằng việc sử dụng httpmô-đun đã dựa vào người khác để hoàn thành công việc. Nếu ai đó không muốn dựa vào bất kỳ ai để hoàn thành công việc thì ít nhất nên sử dụng ổ cắm TCP thô - điều mà tôi làm trong một ví dụ dưới đây.
Một vấn đề nghiêm trọng hơn là tất cả các câu trả lời ở đây sử dụng httpmô-đun đều bị hỏng . Họ đưa ra các điều kiện chủng tộc , độ phân giải đường dẫn không an toàn sẽ dẫn đến lỗ hổng truyền tải đường dẫn , chặn I / O sẽ hoàn toàn không phục vụ bất kỳ yêu cầu đồng thời nào và các vấn đề tế nhị khác - chúng hoàn toàn bị phá vỡ như những ví dụ về câu hỏi và Tuy nhiên, họ đã sử dụng tính trừu tượng được cung cấp bởi httpmô-đun thay vì sử dụng ổ cắm TCP để họ thậm chí không làm mọi thứ từ đầu như họ yêu cầu.
Nếu câu hỏi là "Làm thế nào để triển khai máy chủ tệp tĩnh từ đầu, như một bài tập học tập" thì bằng mọi cách, câu trả lời làm thế nào để làm điều đó nên được đăng - nhưng ngay cả khi đó chúng ta nên mong đợi chúng ít nhất là chính xác . Ngoài ra, không phải là không có lý khi cho rằng ai đó muốn phục vụ hình ảnh có thể muốn phục vụ nhiều hình ảnh hơn trong tương lai nên người ta có thể lập luận rằng việc viết một máy chủ tệp tĩnh tùy chỉnh cụ thể chỉ có thể phục vụ một tệp duy nhất với đường dẫn được mã hóa cứng là hơi thiển cận. Khó có thể tưởng tượng rằng bất kỳ ai tìm kiếm câu trả lời về cách phục vụ hình ảnh sẽ hài lòng với một giải pháp chỉ phục vụ một hình ảnh thay vì một giải pháp chung để phục vụ bất kỳ hình ảnh nào.
Nói tóm lại, câu hỏi là làm thế nào để phục vụ một hình ảnh và một câu trả lời cho điều đó là sử dụng một mô-đun thích hợp để thực hiện điều đó một cách an toàn, phù hợp và đáng tin cậy , có thể đọc được, có thể duy trì và trong tương lai trong khi sử dụng Node chuyên nghiệp tốt nhất phát triển. Nhưng tôi đồng ý rằng một bổ sung tuyệt vời cho câu trả lời như vậy sẽ chỉ ra một cách để thực hiện chức năng tương tự bằng tay nhưng thật đáng buồn là mọi nỗ lực để làm điều đó đã thất bại cho đến nay. Và đó là lý do tại sao tôi viết một số ví dụ mới.
Sau phần giới thiệu ngắn này, đây là năm ví dụ của tôi thực hiện công việc trên 5 mức độ trừu tượng khác nhau.
Chức năng tối thiểu
Mỗi ví dụ phục vụ các tệp từ publicthư mục và hỗ trợ chức năng tối thiểu của:
- Các loại MIME cho hầu hết các tệp phổ biến
- phục vụ HTML, JS, CSS, văn bản và hình ảnh đơn giản
- phục vụ
index.htmlnhư một chỉ mục thư mục mặc định
- phản hồi với mã lỗi cho các tập tin bị thiếu
- không có lỗ hổng truyền tải
- không có điều kiện cuộc đua trong khi đọc các tập tin
Tôi đã thử nghiệm mọi phiên bản trên Node phiên bản 4, 5, 6 và 7.
express.static
Phiên bản này sử dụng express.staticphần mềm trung gian tích hợp của expressmô-đun.
Ví dụ này có nhiều chức năng nhất và số lượng mã ít nhất.
var path = require('path');
var express = require('express');
var app = express();
var dir = path.join(__dirname, 'public');
app.use(express.static(dir));
app.listen(3000, function () {
console.log('Listening on http://localhost:3000/');
});
express
Phiên bản này sử dụng expressmô-đun nhưng không có express.staticphần mềm trung gian. Phục vụ các tệp tĩnh được triển khai như một trình xử lý tuyến đường duy nhất bằng cách sử dụng các luồng.
Ví dụ này có các biện pháp đối phó đường dẫn đơn giản và hỗ trợ một tập hợp hạn chế các loại MIME phổ biến nhất.
var path = require('path');
var express = require('express');
var app = express();
var fs = require('fs');
var dir = path.join(__dirname, 'public');
var mime = {
html: 'text/html',
txt: 'text/plain',
css: 'text/css',
gif: 'image/gif',
jpg: 'image/jpeg',
png: 'image/png',
svg: 'image/svg+xml',
js: 'application/javascript'
};
app.get('*', function (req, res) {
var file = path.join(dir, req.path.replace(/\/$/, '/index.html'));
if (file.indexOf(dir + path.sep) !== 0) {
return res.status(403).end('Forbidden');
}
var type = mime[path.extname(file).slice(1)] || 'text/plain';
var s = fs.createReadStream(file);
s.on('open', function () {
res.set('Content-Type', type);
s.pipe(res);
});
s.on('error', function () {
res.set('Content-Type', 'text/plain');
res.status(404).end('Not found');
});
});
app.listen(3000, function () {
console.log('Listening on http://localhost:3000/');
});
connect
Phiên bản này sử dụng connectmô-đun một mức độ trừu tượng thấp hơn express.
Ví dụ này có chức năng tương tự như expressphiên bản nhưng sử dụng API đòn bẩy thấp hơn một chút.
var path = require('path');
var connect = require('connect');
var app = connect();
var fs = require('fs');
var dir = path.join(__dirname, 'public');
var mime = {
html: 'text/html',
txt: 'text/plain',
css: 'text/css',
gif: 'image/gif',
jpg: 'image/jpeg',
png: 'image/png',
svg: 'image/svg+xml',
js: 'application/javascript'
};
app.use(function (req, res) {
var reqpath = req.url.toString().split('?')[0];
if (req.method !== 'GET') {
res.statusCode = 501;
res.setHeader('Content-Type', 'text/plain');
return res.end('Method not implemented');
}
var file = path.join(dir, reqpath.replace(/\/$/, '/index.html'));
if (file.indexOf(dir + path.sep) !== 0) {
res.statusCode = 403;
res.setHeader('Content-Type', 'text/plain');
return res.end('Forbidden');
}
var type = mime[path.extname(file).slice(1)] || 'text/plain';
var s = fs.createReadStream(file);
s.on('open', function () {
res.setHeader('Content-Type', type);
s.pipe(res);
});
s.on('error', function () {
res.setHeader('Content-Type', 'text/plain');
res.statusCode = 404;
res.end('Not found');
});
});
app.listen(3000, function () {
console.log('Listening on http://localhost:3000/');
});
http
Phiên bản này sử dụng httpmô-đun là API cấp thấp nhất cho HTTP trong Nút.
Ví dụ này có chức năng tương tự như connectphiên bản nhưng sử dụng các API cấp thấp hơn.
var path = require('path');
var http = require('http');
var fs = require('fs');
var dir = path.join(__dirname, 'public');
var mime = {
html: 'text/html',
txt: 'text/plain',
css: 'text/css',
gif: 'image/gif',
jpg: 'image/jpeg',
png: 'image/png',
svg: 'image/svg+xml',
js: 'application/javascript'
};
var server = http.createServer(function (req, res) {
var reqpath = req.url.toString().split('?')[0];
if (req.method !== 'GET') {
res.statusCode = 501;
res.setHeader('Content-Type', 'text/plain');
return res.end('Method not implemented');
}
var file = path.join(dir, reqpath.replace(/\/$/, '/index.html'));
if (file.indexOf(dir + path.sep) !== 0) {
res.statusCode = 403;
res.setHeader('Content-Type', 'text/plain');
return res.end('Forbidden');
}
var type = mime[path.extname(file).slice(1)] || 'text/plain';
var s = fs.createReadStream(file);
s.on('open', function () {
res.setHeader('Content-Type', type);
s.pipe(res);
});
s.on('error', function () {
res.setHeader('Content-Type', 'text/plain');
res.statusCode = 404;
res.end('Not found');
});
});
server.listen(3000, function () {
console.log('Listening on http://localhost:3000/');
});
net
Phiên bản này sử dụng netmô-đun là API cấp thấp nhất cho các socket TCP trong Node.
Ví dụ này có một số chức năng của httpphiên bản nhưng giao thức HTTP tối thiểu và không đầy đủ đã được triển khai từ đầu. Vì nó không hỗ trợ mã hóa khối, nó tải các tệp vào bộ nhớ trước khi phục vụ chúng để biết kích thước trước khi gửi phản hồi vì việc lưu trữ các tệp và sau đó tải sẽ đưa ra một điều kiện cuộc đua.
var path = require('path');
var net = require('net');
var fs = require('fs');
var dir = path.join(__dirname, 'public');
var mime = {
html: 'text/html',
txt: 'text/plain',
css: 'text/css',
gif: 'image/gif',
jpg: 'image/jpeg',
png: 'image/png',
svg: 'image/svg+xml',
js: 'application/javascript'
};
var server = net.createServer(function (con) {
var input = '';
con.on('data', function (data) {
input += data;
if (input.match(/\n\r?\n\r?/)) {
var line = input.split(/\n/)[0].split(' ');
var method = line[0], url = line[1], pro = line[2];
var reqpath = url.toString().split('?')[0];
if (method !== 'GET') {
var body = 'Method not implemented';
con.write('HTTP/1.1 501 Not Implemented\n');
con.write('Content-Type: text/plain\n');
con.write('Content-Length: '+body.length+'\n\n');
con.write(body);
con.destroy();
return;
}
var file = path.join(dir, reqpath.replace(/\/$/, '/index.html'));
if (file.indexOf(dir + path.sep) !== 0) {
var body = 'Forbidden';
con.write('HTTP/1.1 403 Forbidden\n');
con.write('Content-Type: text/plain\n');
con.write('Content-Length: '+body.length+'\n\n');
con.write(body);
con.destroy();
return;
}
var type = mime[path.extname(file).slice(1)] || 'text/plain';
var s = fs.readFile(file, function (err, data) {
if (err) {
var body = 'Not Found';
con.write('HTTP/1.1 404 Not Found\n');
con.write('Content-Type: text/plain\n');
con.write('Content-Length: '+body.length+'\n\n');
con.write(body);
con.destroy();
} else {
con.write('HTTP/1.1 200 OK\n');
con.write('Content-Type: '+type+'\n');
con.write('Content-Length: '+data.byteLength+'\n\n');
con.write(data);
con.destroy();
}
});
}
});
});
server.listen(3000, function () {
console.log('Listening on http://localhost:3000/');
});
Tải ví dụ
Tôi đã đăng tất cả các ví dụ trên GitHub với nhiều lời giải thích hơn.
Ví dụ với express.static, express, connect, httpvà net:
Dự án khác chỉ sử dụng express.static:
Xét nghiệm
Kết quả kiểm tra có sẵn trên Travis:
Mọi thứ đều được thử nghiệm trên các phiên bản Node 4, 5, 6 và 7.
Xem thêm
Các câu trả lời liên quan khác: