Node 수동 정적 리소스 서버

7581 단어

배경


서버 지식을 배우려면 서버에 파일을 마운트해야 상응하는 파일에 접근할 수 있습니다.로컬 개발을 할 때, 우리는 같은 랜에서 같은 서버 주소를 통해 같은 파일에 접근할 수 있도록 자주 파일을 서버에 저장합니다. 예를 들어 우리는 xampp를 사용하고sulime의 플러그인sublime-server를 사용합니다.이 글은 node를 통해 정적 자원 서버를 손으로 써서 모든 폴더를 루트 디렉터리로 정의하고 해당하는 파일에 접근하여anywhere is yourstatic-server에 도달할 수 있도록 합니다.

주요 구현 기능

  • 정적 파일을 읽습니다
  • 정적 자원 캐시
  • 자원 압축
  • MIME 유형 지원
  • 단점 속전
  • 명령을 실행할 수 있고 백그라운드에서 실행할 수 있으며 npm install-g를 통해 설치할 수 있습니다

  • Useage

    //install
    $ npm i st-server -g
    //forhelp
    $ st-server -h
    //start
    $ st-server
    // or with port
    $ st-server -p 8800
    // or with hostname
    $ st-server -o localhost -p 8888
    // or with folder
    $ st-server -d / 
    // full parameters
    $ st-server -d / -p 9900 -o localhost
    

    그 중에서 세 개의 매개 변수를 설정할 수 있습니다. - d는 당신이 방문할 루트 디렉터리를 대표하고, - p는 포트 번호를 대표합니다. (현재는 같은 포트 번호를 여러 번 열 수 없습니다. 이전의 프로세스를 수동으로 죽여야 합니다.) - o는hostname을 대표합니다.모든 원본 코드가github에 업로드되었습니다.

    원본 분석

  • 모든 코드는 하나의 Static Server 클래스를 바탕으로 이루어진다. 구조 함수에 모든 설정을 먼저 도입하고argv는 명령줄을 통해 들어오는 매개 변수를 두드린 다음에 컴파일해야 하는 템플릿을 가져온다. 이 템플릿은 하나의 폴더 아래 모든 파일의 목록을 간단하게 표시한다.handlebars 기반 구현.그리고 서비스를 켜고, 요청을 감청하고,this.request () 처리
  • class StaticServer{
        constructor(argv){
            this.config = Object.assign({},config,argv);
            this.compileTpl = compileTpl();
        }
        startServer(){
            let server = http.createServer();
            server.on('request',this.request.bind(this));
            server.listen(this.config.port,()=>{
                let serverUrl = `http://${this.config.host}:${this.config.port}`;
                debug(` , ${chalk.green(serverUrl)}`);
            })
        }
    }
    
  • 메인 라인은 정적 서비스를 구축하고자 하는 주소를 읽는 것입니다. 폴더라면 이 폴더 아래에 index가 있는지 찾습니다.html 파일, 있으면 표시하고 없으면 모든 파일을 열거합니다.파일이면 해당 파일의 내용을 직접 표시합니다.대전제는 구체적인 파일을 표시하기 전에 캐시가 있는지 없는지를 판단하고 캐시를 직접 가져오며 없으면 서버를 요청해야 한다..
  •  async request(req,res){
            let {pathname} = url.parse(req.url);
            if(pathname == '/favicon.ico'){
                return this.sendError('NOT FOUND',req,res);
            }
            // 
            let filePath = path.join(this.config.root,pathname);
            let statObj = await fsStat(filePath);
            if(statObj.isDirectory()){//   
                let files = await readDir(filePath);
                let isHasIndexHtml = false;
                files = files.map(file=>{
                    if(file.indexOf('index.html')>-1){
                        isHasIndexHtml = true;
                    }
                    return {
                        name:file,
                        url:path.join(pathname,file)
                    }
                })
                if(isHasIndexHtml){
                    let statObjN = await fsStat(filePath+'/index.html');
                    return this.sendFile(req,res,filePath+'/index.html',statObjN);
                }
                let resHtml = this.compileTpl({
                    title:filePath,
                    files
                })
                res.setHeader('Content-Type','text/html');
                res.end(resHtml);
            }else{
                this.sendFile(req,res,filePath,statObj);
            }
            
        }
        sendFile(req,res,filePath,statObj){
            // 
            if (this.getFileFromCache(req, res, statObj)) return; // , 
            res.setHeader('Content-Type',mime.getType(filePath)+';charset=utf-8');
            let encoding = this.getEncoding(req,res);
            // 
            let rs = this.getPartStream(req,res,filePath,statObj);
            if(encoding){
                rs.pipe(encoding).pipe(res);
            }else{
                rs.pipe(res);
            }
        }
    

    sendFile 방법은 브라우저에 내용을 출력하는 방법입니다. 주로 다음과 같은 몇 가지 중요한 점을 포함합니다.
  • 캐시 처리
  •  getFileFromCache(req,res,statObj){
            let ifModifiedSince = req.headers['if-modified-since'];
            let isNoneMatch = req.headers['if-none-match'];
            res.setHeader('Cache-Control','private,max-age=60');
            res.setHeader('Expires',new Date(Date.now() + 60*1000).toUTCString());
            let etag = crypto.createHash('sha1').update(statObj.ctime.toUTCString() + statObj.size).digest('hex');
            let lastModified = statObj.ctime.toGMTString();
            res.setHeader('ETag', etag);
            res.setHeader('Last-Modified', lastModified);
            if (isNoneMatch && isNoneMatch != etag) {
                return false;
            }
            if (ifModifiedSince && ifModifiedSince != lastModified) {
                return false;
            }
            if (isNoneMatch || ifModifiedSince) {
                res.statusCode = 304;
                res.end('');
                return true;
            } else {
                return false;
            }
        }
    

    여기에서 우리는 Last-Modified, ETag를 통해 협상 캐시를 실현하고, Cache-Control, Expires를 통해 강제 캐시를 실현하며, 모든 캐시 조건이 성립될 때만 효력이 발생한다.Last-Modified 원리는 파일의 수정 시간을 통해 파일이 수정되었는지, ETag는 파일 내용의 암호화를 통해 수정되었는지 판단하는 것이다.Cache-Control, Expire는 시간을 통해 완만합니다.
  • 파일을 압축하면 파일을 압축한 후에 부피를 줄이고 전송 속도를 가속화하며 대역폭을 절약할 수 있다. 여기는 gzip과 deflate 두 가지 방식을 지원하고 node 자체의 모듈 zlib로 처리한다
  •   getEncoding(req,res){
            let acceptEncoding = req.headers['accept-encoding'];
            if(acceptEncoding.match(/\bgzip\b/)){
                res.setHeader('Content-Encoding','gzip');
                return zlib.createGzip();
            }else if(acceptEncoding.match(/\bdeflate\b/)){
                res.setHeader('Conetnt-Encoding','deflate');
                return zlib.createDeflate();
            }else{
                return null;
            }
        }
    
  • range를 통해 단점 속전 처리를 진행한다
  •  getPartStream(req,res,filePath,statObj){
            let start = 0;
            let end = statObj.size -1;
            let range = req.headers['range'];
            if(range){
                res.setHeader('Accept-Range','bytes');
                res.statusCode = 206;
                let result = range.match(/bytes=(\d*)-(\d*)/);
                if(result){
                    start = isNaN(result[1]) ? start : parseInt(result[1]);
                    end = isNaN(result[2]) ? end : parseInt(result[2]) - 1;
                }
            }
            return fs.createReadStream(filePath,{
                start,end
            })
        }
    
  • 명령행 도구를 생성하고 npm로yargs 패키지를 설치하여 조작하고 패키지.json에 "bin"추가: {"st-Server": "bin/www"} 명령을 실행해야 하는 파일을 가리키고 www에서 대응하는 명령을 설정하고 하위 프로세스를 열어 주 코드를 조작합니다. 명령을 실행한 후 명령줄이 계속 끊기는 상태를 해결하기 위해서입니다.서브프로세스를 켜도 node 원본 모듈child_프로세스 지원..
  • #! /usr/bin/env node
    
    let yargs = require('yargs');
    let argv = yargs.option('d', {
        alias: 'root',
        demand: 'false',
        type: 'string',
        default: process.cwd(),
        description: ' '
    }).option('o', {
        alias: 'host',
        demand: 'false',
        default: 'localhost',
        type: 'string',
        description: ' '
    }).option('p', {
        alias: 'port',
        demand: 'false',
        type: 'number',
        default: 8800,
        description: ' '
    })
        .usage('st-server [options]')
        .example(
        'st-server -d / -p 9900 -o localhost', ' 9900 '
        ).help('h').argv;
    
    let path = require('path');
    let {
     spawn
    } = require('child_process');
    
    let p1 = spawn('node', ['www.js', JSON.stringify(argv)], {
     cwd: __dirname
    });
    p1.unref();
    process.exit(0);
    

    참고


    anywhere

    좋은 웹페이지 즐겨찾기