เซิร์ฟเวอร์ไฟล์แบบคงที่พื้นฐานใน NodeJS


85

ฉันกำลังพยายามสร้างเซิร์ฟเวอร์ไฟล์แบบคงที่ใน nodejs มากขึ้นเพื่อเป็นการออกกำลังกายเพื่อทำความเข้าใจโหนดมากกว่าเซิร์ฟเวอร์ที่สมบูรณ์แบบ ฉันทราบดีเกี่ยวกับโปรเจ็กต์เช่น Connect และ node-static และตั้งใจอย่างเต็มที่ที่จะใช้ไลบรารีเหล่านั้นสำหรับโค้ดที่พร้อมใช้งานจริงมากขึ้น แต่ฉันก็อยากเข้าใจพื้นฐานของสิ่งที่ฉันกำลังทำงานด้วย ด้วยเหตุนี้ฉันจึงเขียนโค้ดเซิร์ฟเวอร์ขนาดเล็ก js:

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);

คำถามของฉันมีสองเท่า

  1. นี่เป็นวิธีที่ "ถูกต้อง" ในการสร้างและสตรีม html และอื่น ๆ ขั้นพื้นฐานในโหนดหรือมีวิธีที่ดีกว่า / หรูหรากว่า / แข็งแกร่งกว่า

  2. .pipe () ในโหนดโดยพื้นฐานแล้วทำสิ่งต่อไปนี้หรือไม่

.

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

ขอบคุณทุกคน!


2
ฉันเขียนโมดูลที่ช่วยให้คุณทำสิ่งนั้นได้โดยไม่ต้องมีความยืดหยุ่น นอกจากนี้ยังแคชทรัพยากรทั้งหมดของคุณโดยอัตโนมัติ ลองดู: github.com/topcloud/cachemere
Jon

2
ตลกนิดหน่อยที่คุณเลือก (?) เพื่อส่งคืน '404 Not Found' พร้อมรหัสสถานะ HTTP '200 OK' หากไม่พบทรัพยากรที่ URL รหัสที่เหมาะสมควรเป็น 404 (และสิ่งที่คุณเขียนในเนื้อหาเอกสารมักมีความสำคัญรองลงมา) มิฉะนั้นคุณจะสับสนกับตัวแทนผู้ใช้จำนวนมาก (รวมถึงโปรแกรมรวบรวมข้อมูลเว็บและบอทอื่น ๆ ) ทำให้เอกสารไม่มีมูลค่าที่แท้จริง (ซึ่งอาจแคชด้วย)
59

1
ขอบคุณ. ยังคงทำงานได้ดีหลายปีให้หลัง
statosdotcom

1
ขอบคุณ! รหัสนี้ทำงานได้อย่างสมบูรณ์ แต่ตอนนี้ใช้fs.exists()แทนpath.exists()ในโค้ดด้านบน ไชโย! และใช่! อย่าลืมreturn:
Kaushal28

หมายเหตุ : 1) fs.exists()มีการเลิกใช้ ใช้หรือดียิ่งขึ้นเช่นเดียวกับกรณีการใช้งานดังกล่าวข้างต้นfs.access() 2)เป็นที่เลิก ; ใช้อินเทอร์เฟซที่ใหม่กว่าแทน fs.stat() url.parsenew URL
rags2riches

คำตอบ:


44
  • เซิร์ฟเวอร์พื้นฐานของคุณดูดียกเว้น:

    มีreturnคำสั่งหายไป

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

    และ:

    res.writeHead(200, mimeType);

    ควรจะเป็น:

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

  • ใช่pipe()โดยพื้นฐานแล้วมันยังหยุด / เล่นต่อสตรีมต้นทาง (ในกรณีที่เครื่องรับช้าลง) นี่คือซอร์สโค้ดของpipe()ฟังก์ชัน: https://github.com/joyent/node/blob/master/lib/stream.js


2
จะเกิดอะไรขึ้นถ้าชื่อไฟล์เหมือน blah.blah.css?
ShrekOverflow

2
mimeType จะเป็น blah ในกรณีนั้น xP
ShrekOverflow

5
ไม่ใช่ว่าถู? หากคุณเขียนเองแสดงว่าคุณกำลังขอข้อบกพร่องประเภทนี้ เรียนเก่ง แต่ฉันเรียนรู้ที่จะชื่นชม "เชื่อมต่อ" มากกว่าที่จะหมุนตัวเอง ปัญหาของหน้านี้คือผู้คนกำลังมองหาวิธีการสร้างไฟล์เซิร์ฟเวอร์อย่างง่ายและสแต็กล้นขึ้นมาก่อน คำตอบนี้ถูกต้อง แต่ผู้คนไม่ได้มองหามันเป็นเพียงคำตอบง่ายๆ ฉันต้องหาวิธีที่ง่ายกว่านั้นด้วยตัวเองจึงนำมาไว้ที่นี่
Jason Sebring

1
+1 สำหรับการไม่วางลิงก์ไปยังโซลูชันในรูปแบบของไลบรารี แต่เขียนคำตอบสำหรับคำถามนั้น ๆ
Shawn Whinnery

56

น้อยมาก

เพียงไปที่พรอมต์คำสั่งก่อนในโครงการของคุณและใช้

$ npm install express

จากนั้นเขียนโค้ด app.js ของคุณดังนี้:

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

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

จากนั้นคุณจะสร้างโฟลเดอร์ "สาธารณะ" ที่คุณวางไฟล์ของคุณ ฉันลองวิธีที่ยากกว่าก่อน แต่คุณต้องกังวลเกี่ยวกับประเภทละครใบ้ซึ่งเพียงแค่ต้องแมปสิ่งที่ใช้เวลานานแล้วกังวลเกี่ยวกับประเภทการตอบสนอง ฯลฯ ฯลฯ ฯลฯ .... ไม่ขอบคุณ


2
+1 มีหลายสิ่งที่ต้องพูดถึงสำหรับการใช้โค้ดที่ผ่านการทดสอบแทนที่จะใช้โค้ดของคุณเอง
jcollum

1
ฉันลองดูเอกสารแล้ว แต่ดูเหมือนจะหาอะไรไม่ได้มากคุณช่วยอธิบายได้ไหมว่าตัวอย่างข้อมูลของคุณกำลังทำอะไรอยู่ ฉันพยายามใช้รูปแบบเฉพาะนี้และไม่รู้ว่าอะไรสามารถแทนที่ด้วยอะไรได้
onaclov2000

3
หากคุณต้องการรายชื่อไดเร็กทอรีเพียงเพิ่ม. use (connect.directory ('public')) หลังบรรทัด connect.static แทนที่ public ด้วยพา ธ ของคุณ ขออภัยสำหรับการหักหลัง แต่ฉันคิดว่ามันช่วยให้ฉันกระจ่างขึ้น
onaclov2000

1
คุณอาจ 'ใช้ jQuery' เช่นกัน! นี่ไม่ใช่การตอบคำถามของ OP แต่เป็นการแก้ปัญหาที่ไม่มีอยู่จริง OP ระบุว่าประเด็นของการทดลองนี้คือการเรียนรู้ Node
Shawn Whinnery

1
@JasonSebring ทำไมrequire('http')ที่บรรทัดที่สอง?
Peng ที่ ZenUML.com

20

ฉันชอบทำความเข้าใจกับสิ่งที่เกิดขึ้นภายใต้ประทุนเช่นกัน

ฉันสังเกตเห็นบางสิ่งในโค้ดของคุณที่คุณอาจต้องการทำความสะอาด:

  • มันล้มเหลวเมื่อชื่อไฟล์ชี้ไปที่ไดเร็กทอรีเนื่องจากมีอยู่จริงและพยายามอ่านสตรีมไฟล์ ฉันใช้ fs.lstatSync เพื่อตรวจสอบการมีอยู่ของไดเร็กทอรี

  • ไม่ได้ใช้รหัสตอบกลับ HTTP อย่างถูกต้อง (200, 404 ฯลฯ )

  • ในขณะที่กำลังกำหนด MimeType (จากนามสกุลไฟล์) จะไม่ได้รับการตั้งค่าอย่างถูกต้องใน res.writeHead (ดังที่สตูว์ชี้ให้เห็น)

  • ในการจัดการกับอักขระพิเศษคุณอาจต้องการยกเลิกการถ่ายภาพ uri

  • มันติดตาม symlink แบบสุ่มสี่สุ่มห้า (อาจเป็นปัญหาด้านความปลอดภัย)

ด้วยเหตุนี้ตัวเลือก apache บางตัว (FollowSymLinks, ShowIndexes และอื่น ๆ ) จึงเริ่มมีความหมายมากขึ้น ฉันได้อัปเดตรหัสสำหรับเซิร์ฟเวอร์ไฟล์ธรรมดาของคุณดังนี้:

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
ฉันสามารถแนะนำ "var mimeType = mimeTypes [path.extname (ชื่อไฟล์) .split (". "). reverse () [0]];" แทน? ชื่อไฟล์บางชื่อมีมากกว่าหนึ่ง "." เช่น "my.cool.video.mp4" หรือ "download.tar.gz"
หมู่

วิธีนี้จะหยุดไม่ให้ใครบางคนใช้ url เช่นโฟลเดอร์ /../../../ home / user / jackpot.privatekey หรือไม่ ฉันเห็นการเข้าร่วมเพื่อให้แน่ใจว่าเส้นทางนั้นอยู่ด้านล่าง แต่ฉันสงสัยว่าการใช้สัญกรณ์ ../../../ จะได้ผลประมาณนั้นหรือไม่ บางทีฉันจะทดสอบด้วยตัวเอง
Reynard

มันไม่ทำงาน. ฉันไม่แน่ใจว่าทำไม แต่ก็ยินดีที่ได้รู้
Reynard

ดีการจับคู่ RegEx สามารถรวบรวมส่วนขยายได้ 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
อาจต้องการอธิบายเรื่องนี้อีกเล็กน้อย
Nathan Tuggy

3

วิธีการเกี่ยวกับรูปแบบนี้ซึ่งหลีกเลี่ยงการตรวจสอบแยกต่างหากว่ามีไฟล์อยู่

        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
คุณลืม mimetype ในกรณีที่ประสบความสำเร็จ ฉันใช้การออกแบบนี้ แต่แทนที่จะวางท่อสตรีมทันทีฉันจะวางท่อในเหตุการณ์ 'เปิด' ของ filestream: writeHead สำหรับ mimetype แล้วไปป์ ท้ายที่สุดไม่จำเป็นต้อง: readable.pipe
GeH

แก้ไขตามความคิดเห็นของ @GeH
Brett Zamir

ควรจะเป็นfileStream.on('open', ...
Petah

2

ฉันสร้างฟังก์ชัน httpServer พร้อมคุณสมบัติพิเศษสำหรับการใช้งานทั่วไปตามคำตอบของ @Jeff Ward

  1. ผบ
  2. index.html ส่งกลับหาก req === ผบ

การใช้งาน:

httpServer(dir).listen(port);

https://github.com/kenokabe/ConciseStaticHttpServer

ขอบคุณ.


URL น่าจะเป็นgithub.com/uplusplus/ConciseStaticHttpServerตอนนี้
NetOperator Wibby

0

โมดูลเซนต์ทำให้การให้บริการไฟล์คงง่าย นี่คือสารสกัดจาก 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

คำตอบของ @JasonSebring ชี้ให้ฉันไปในทิศทางที่ถูกต้อง แต่รหัสของเขาล้าสมัย นี่คือวิธีที่คุณทำกับconnectเวอร์ชันใหม่ล่าสุด

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);

ในconnect GitHub Repositoryมีมิดเดิลแวร์อื่น ๆ ที่คุณสามารถใช้ได้


ฉันเพิ่งใช้ Express แทนคำตอบที่ง่ายกว่า Express รุ่นใหม่ล่าสุดมีการอบแบบคงที่ แต่ไม่มากนัก ขอบคุณ!
Jason Sebring

ดูconnectเอกสารเป็นเพียงwrapperสำหรับmiddleware. สิ่งที่น่าสนใจอื่น ๆmiddlewareทั้งหมดมาจากที่expressเก็บดังนั้นในทางเทคนิคคุณสามารถใช้ API เหล่านั้นโดยใช้ไฟล์express.use().
ffleandro
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.