NodeJS의 기본 정적 파일 서버


85

완벽한 서버보다는 노드를 이해하기위한 연습으로 nodejs에서 정적 파일 서버를 만들려고합니다. 저는 Connect 및 node-static과 같은 프로젝트에 대해 잘 알고 있으며 더 많은 프로덕션 준비 코드에 해당 라이브러리를 완전히 사용할 계획이지만 작업중인 작업의 기본 사항도 이해하고 싶습니다. 이를 염두에두고 작은 server.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
HTTP 상태 코드 '200 OK'와 함께 '404 Not Found'를 반환하도록 선택 (?)하는 것이 약간 재미 있습니다. URL에서 리소스를 찾을 수없는 경우 적절한 코드는 404 여야합니다 (그리고 문서 본문에 작성하는 내용은 일반적으로 이차적으로 중요합니다). 그렇지 않으면 많은 사용자 에이전트 (웹 크롤러 및 기타 봇 포함)가 실제 가치가없는 문서 (캐시 할 수도 있음)를 제공하는 데 혼동을 줄 것입니다.
amn

1
감사. 몇 년 후에도 여전히 잘 작동합니다.
statosdotcom

1
감사! 이 코드는 완벽하게 작동합니다. 그러나 이제는 위의 코드 fs.exists()대신 사용하십시오 path.exists(). 건배! 그리고 그래! 잊지 마세요 return:
Kaushal28

참고 : 1) fs.exists()더 이상 사용되지 않습니다 . fs.access()위의 사용 사례와 같이 사용 하거나 더 좋게,fs.stat() . 2) url.parse사용되지 않는 ; new 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 2014-07-28

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

그런 다음 파일을 저장하는 "공용"폴더를 만듭니다. 먼저 더 어려운 방법으로 시도했지만 시간이 많이 걸리는 항목을 매핑하고 응답 유형 등에 대해 걱정해야하는 MIME 유형에 대해 걱정해야합니다. 감사합니다.


2
+1 자신의 코드를 롤링하는 대신 테스트 된 코드를 사용하는 것에 대해 할 말이 많습니다.
jcollum

1
문서를 살펴 보았지만 많이 찾지 못하는 것 같습니다. 스 니펫이 무엇을하는지 설명해 주시겠습니까? 이 특별한 변형을 사용하려고했지만 무엇으로 대체 할 수 있는지 모르겠습니다.
onaclov2000

3
디렉토리 목록을 보려면 connect.static 줄 바로 뒤에 .use (connect.directory ( 'public'))를 추가하고 public을 경로로 바꾸면됩니다. 납치해서 미안하지만, 제 생각에는 문제가 해결되었다고 생각합니다.
onaclov2000

1
'JQuery를 사용'하는 것이 좋습니다! 이것은 OP의 질문에 대한 대답이 아니라 존재하지 않는 문제에 대한 해결책입니다. OP는이 실험의 요점은 Node.js를 배우는 것이라고 말했습니다.
Shawn Whinnery 2014-07-28

1
@JasonSebring 왜 require('http')두 번째 줄에서?
Xiao Peng-ZenUML.com 2014

20

나는 내부에서 무슨 일이 일어나고 있는지 이해하는 것도 좋아합니다.

코드에서 정리하고 싶은 몇 가지 사항을 발견했습니다.

  • 파일 이름이 디렉토리를 가리킬 때 충돌이 발생합니다. 존재가 참이고 파일 스트림을 읽으려고하기 때문입니다. fs.lstatSync를 사용하여 디렉터리 존재를 확인했습니다.

  • HTTP 응답 코드를 올바르게 사용하지 않습니다 (200, 404 등).

  • MimeType이 (파일 확장자에서) 결정되는 동안 res.writeHead에서 올바르게 설정되지 않습니다 (스튜가 지적한대로)

  • 특수 문자를 처리하려면 uri를 이스케이프 해제해야합니다.

  • 맹목적으로 심볼릭 링크를 따릅니다 (보안 문제가 될 수 있음)

이를 감안할 때 일부 아파치 옵션 (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 (filename) .split (". "). reverse () [0]];" 대신? 일부 파일 이름에는 "."가 두 개 이상 있습니다. 예 : "my.cool.video.mp4"또는 "download.tar.gz"
동기화되지

이로 인해 누군가가 folder /../../../ home / user / jackpot.privatekey와 같은 URL을 사용하지 못합니까? 경로가 다운 스트림인지 확인하기 위해 조인이 표시되지만 ../../../ 유형의 표기법을 사용할지 여부가 궁금합니다. 아마도 내가 직접 테스트 할 것입니다.
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을 잊어 버렸습니다. 이 디자인을 사용하고 있지만 스트림을 즉시 파이핑하는 대신 파일 스트림의 'open'이벤트에서 파이핑합니다. mimetype에 대한 writeHead, 다음 파이프. 끝은 필요하지 않습니다 : readable.pipe .
GeH 2014

@GeH의 의견에 따라 수정되었습니다.
Brett Zamir

해야합니다fileStream.on('open', ...
Petah

2

@Jeff Ward 답변을 기반으로 일반적인 사용을 위해 추가 기능이있는 httpServer 기능을 만들었습니다.

  1. custtom dir
  2. index.html은 req === dir 인 경우 반환합니다.

용법:

httpServer(dir).listen(port);

https://github.com/kenokabe/ConciseStaticHttpServer

감사.


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의 저장소 당신이 사용할 수있는 다른 미들웨어가있다.


나는 단순한 대답 대신에 express를 사용했습니다. 최신 익스프레스 버전은 정적으로 구워졌지만 그 외에는 많지 않습니다. 감사!
Jason Sebring

보면 connect문서, 그것은 단지입니다 wrapper에 대한 middleware. 다른 모든 흥미로운 middleware것은 express저장소 에서 가져온 것이므로 기술적으로는 express.use().
ffleandro
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.