Aktualizacja 2016
Przykłady z Express i bez Express, które faktycznie działają
To pytanie ma ponad 5 lat, ale każda odpowiedź ma jakieś problemy .
TL; DR
Przewiń w dół, aby zobaczyć przykłady, z którymi można wyświetlać obraz z:
express.static
express
connect
http
net
Wszystkie przykłady są również na GitHub: https://github.com/rsp/node-static-http-servers
Wyniki testów są dostępne na Travis: https://travis-ci.org/rsp/node-static-http-servers
Wprowadzenie
Po ponad 5 latach od zadania tego pytania jest tylko jedna prawidłowa odpowiedź od generalhenry, ale chociaż ta odpowiedź nie ma problemów z kodem, wydaje się, że ma pewne problemy z odbiorem . Stwierdzono, że to „niewiele wyjaśnia poza tym, jak polegać na kimś innym, aby wykonał swoją pracę”, a fakt, jak wiele osób głosowało na ten komentarz, wyraźnie pokazuje, że wiele rzeczy wymaga wyjaśnienia.
Po pierwsze, dobrą odpowiedzią na pytanie „Jak obsługiwać obrazy przy użyciu Node.js” nie jest implementowanie statycznego serwera plików od zera i robienie tego źle. Dobrą odpowiedzią jest użycie modułu takiego jak Express, który wykonuje zadanie poprawnie .
Odpowiadając na komentarze, które mówią, że używanie Express "nie wyjaśnia wiele poza tym, jak polegać na kimś innym, aby wykonał zadanie" , należy zauważyć, że korzystanie z httpmodułu już zależy od kogoś innego do wykonania zadania. Jeśli ktoś nie chce polegać na nikim, aby wykonał zadanie, zamiast tego należy użyć przynajmniej surowych gniazd TCP - co robię w jednym z poniższych przykładów.
Poważniejszym problemem jest to, że wszystkie odpowiedzi, które używają httpmodułu, są zepsute . Wprowadzają warunki wyścigu , niezabezpieczone rozwiązywanie ścieżek , które doprowadzą do podatności na przemierzanie ścieżki , blokowanie we / wy , które w ogóle nie będą obsługiwać żadnych równoczesnych żądań i inne subtelne problemy - są całkowicie zepsute jako przykłady tego, o co chodzi w pytaniu, oraz jednak używają już abstrakcji dostarczanej przez httpmoduł zamiast używać gniazd TCP, więc nie robią nawet wszystkiego od zera, jak twierdzą.
Jeśli pytanie brzmiało: „Jak zaimplementować statyczny serwer plików od podstaw, jako ćwiczenie szkoleniowe”, to z całą pewnością należy opublikować odpowiedzi, jak to zrobić - ale nawet wtedy powinniśmy oczekiwać, że przynajmniej będą poprawne . Nie jest też nierozsądne założenie, że ktoś, kto chce wyświetlać obraz, może chcieć udostępniać więcej obrazów w przyszłości, więc można argumentować, że napisanie określonego niestandardowego serwera plików statycznych, który może obsługiwać tylko jeden plik ze ścieżką zakodowaną na stałe, jest nieco krótkowzroczny. Trudno sobie wyobrazić, że każdy, kto szuka odpowiedzi na temat sposobu wyświetlania obrazu, byłby zadowolony z rozwiązania, które obsługuje tylko jeden obraz, zamiast ogólnego rozwiązania obsługującego dowolny obraz.
Krótko mówiąc, pytanie brzmi, jak obsłużyć obraz, a odpowiedzią na to jest użycie odpowiedniego modułu, aby zrobić to w bezpieczny, skuteczny i niezawodny sposób, który jest czytelny, łatwy w utrzymaniu i przyszłościowy przy wykorzystaniu najlepszych praktyk profesjonalnych Node rozwój. Ale zgadzam się, że świetnym dodatkiem do takiej odpowiedzi byłoby pokazanie sposobu ręcznego zaimplementowania tej samej funkcjonalności, ale niestety każda próba wykonania tego zawiodła. Dlatego napisałem kilka nowych przykładów.
Po tym krótkim wprowadzeniu, oto pięć moich przykładów wykonywania pracy na 5 różnych poziomach abstrakcji.
Minimalna funkcjonalność
Każdy przykład obsługuje pliki z publickatalogu i obsługuje minimalną funkcjonalność:
- Typy MIME dla większości popularnych plików
- obsługuje HTML, JS, CSS, zwykły tekst i obrazy
- służy
index.htmljako domyślny indeks katalogu
- odpowiada kodami błędów dla brakujących plików
- brak luk w przechodzeniu przez ścieżkę
- brak warunków wyścigu podczas odczytu plików
Testowałem każdą wersję na Node w wersjach 4, 5, 6 i 7.
express.static
Ta wersja korzysta z express.staticwbudowanego oprogramowania pośredniego expressmodułu.
Ten przykład ma największą funkcjonalność i najmniejszą ilość kodu.
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
Ta wersja używa expressmodułu, ale bez express.staticoprogramowania pośredniego. Obsługa plików statycznych jest implementowana jako procedura obsługi pojedynczej trasy przy użyciu strumieni.
Ten przykład zawiera proste środki zaradcze przy przechodzeniu po ścieżce i obsługuje ograniczony zestaw najpopularniejszych typów MIME.
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
Ta wersja używa connectmodułu, który jest o jeden poziom abstrakcji niższy niż express.
Ten przykład ma podobną funkcjonalność do expresswersji, ale używa nieco mniejszych dźwigni API.
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
Ta wersja używa httpmodułu, który jest interfejsem API najniższego poziomu dla HTTP w Node.
Ten przykład ma podobną funkcjonalność do connectwersji, ale wykorzystuje jeszcze więcej interfejsów API niższego poziomu.
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
Ta wersja używa netmodułu, który jest interfejsem API najniższego poziomu dla gniazd TCP w Node.
Ten przykład zawiera niektóre funkcje httpwersji, ale minimalny i niekompletny protokół HTTP został zaimplementowany od podstaw. Ponieważ nie obsługuje kodowania fragmentarycznego, ładuje pliki do pamięci przed udostępnieniem im informacji o rozmiarze przed wysłaniem odpowiedzi, ponieważ statystyka plików, a następnie ładowanie wprowadziłoby stan wyścigu.
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/');
});
Pobierz przykłady
Opublikowałem wszystkie przykłady na GitHub z dokładniejszym wyjaśnieniem.
Przykłady z express.static, express, connect, httpi net:
Inny projekt wykorzystujący tylko express.static:
Testy
Wyniki testów są dostępne w Travisie:
Wszystko jest testowane na Node w wersjach 4, 5, 6 i 7.
Zobacz też
Inne powiązane odpowiedzi: