每天一个nodejs模块-http

735 阅读4分钟

这是我参与更文挑战的第3天,活动详情查看: 更文挑战

前言

今天,我们主要说nodejs中的Http模块,这个模块可以说是每个前端开发接触nodejs的第一个模块了,那我们接下来具体讲讲,Http模块主要做了什么,废话不多说,我们直接上代码:

const http = require('http');
http.createServer((request, response) => {
    response.end('Hello Nodejs');
}).listen(3000, () => {
    console.log('server start up at 3000')
})

如上所写,这样我们就创建了一个最简单的nodejs服务,是不是很简单,这大概就是他受欢迎的原因吧。但是作为一个test服务,这样完全可以了,我们可以借助这种方式做到一些事情,但是当我们想做的事情变多(可能不用与生产),但这样一个服务,很明显是无法支撑我们进行我们前端页面的调试,那一个最基本可用的服务需要包括那些部分呢?具体如下

简单可用服务需要包括那些东西

  1. 需要可以处理getpost请求
  2. post参数的处理
  3. 需要可以处理静态文件
  4. 需要可以处理跨域请求 今天,我们暂时先说以上四点吧,当然涉及到的地方肯定还有很多,比如FormData参数的处理,文件参数的处理,中间件的处理等等,如果对这些感兴趣的话,建议大家先简单看一下express或者koa的api设计,在不查看源代码的情况下,对某一方向处理进行开发,然后对比源码,大概是对nodejs学习最有效的方式了,废话不多说,我们一个一个点看:

处理get post请求

处理get post请求,其实很简单,if else 一把梭就可以,具体如下:

const http = require('http');
http.createServer((request, response) => {
    const {url, method} = request;
    if(url === '/' && method.toLowerCase() === 'get') {
        response.setHeader('Content-Type', 'text/plain;charset=utf-8')
        response.end("你访问的是首页!");
    } else {
        response.statusCode = 404;
        response.end();
    }
    
}).listen(3000, () => {
    console.log('server start up at 3000')
})

像这种方式,再处理一些简单逻辑时当然是可以的,但是当我们涉及到的业务越来越多,势必要对接口进行规范化处理,而不是像现在这样,那我们是否可以这样做呢?编写一个js,用来获取某个固定文件夹下的js文件进行接口的处理,具体如下:

// loader.js 负责读取routes的文件
const { readdir } = require("fs/promises")
module.exports = {
    load: (path, callback) => {
        readdir(path)
        .then(files => {
            files.forEach(filename => {
                callback(filename);
            })
        })
    }
}
// 编写route文件
// index
module.exports = {
    'get /': (request, response) => {
        response.setHeader('Content-Type', 'text/plain;charset=utf-8');
        response.end('这是首页的内容');
    },
    'get /detail': (request, response) => {
        response.setHeader('Content-Type', 'text/plain;charset=utf-8');
        response.end('这是首页详情页')
    }
}
// home
module.exports = {
    'get /': (request, response) => {
        response.setHeader('Content-Type', 'text/plain;charset=utf-8');
        response.end('这是home的内容');
    },
    'get /detail': (request, response) => {
        response.setHeader('Content-Type', 'text/plain;charset=utf-8');
        response.end('这是home详情页')
    }
}
// httpService对文件进行加载并执行函数
const http = require('http');
const { url } = require('inspector');
const { resolve } = require('path');
const { load } = require('./loader');
http.createServer((request, response) => {
    let {url, method} = request;
    method = method.toLowerCase();
    load(resolve(__dirname, 'routes'), filename => {
        const prefix = filename === 'index.js' ? '' : '/' + filename.split('.')[0];
        const module = require(`./routes/${filename}`);
        Object.keys(module).forEach(str => {
            let [currMethod, currUrl] = str.split(' ');
            currUrl = prefix + currUrl;
            if(url === currUrl && currMethod === method) {
                module[str](request, response)
            }
        })
    })
}).listen(3000, () => {
    console.log('server start up at 3000')
})

如上所述,我们解决了http服务的if else嵌套问题,在每次访问时进行文件的读写操作,根据读出的配置进行函数的调用,那这个时候我们可以发现,我们无法拿到post的body数据,那我们应该如何做呢?请继续往下看:

post参数基本处理

众所周知,http的createServer提供的request参数和response都是基于流的,request类型为post时,参数会通过流的方式进行传递,那我们就需要监听某些方法,从而让我们可以拿到HttpRequest的完整参数,具体代码如下:

const http = require('http');
const { resolve } = require('path');
const { load } = require('./loader');
http.createServer((request, response) => {
    let {url, method} = request;
    method = method.toLowerCase();
    // post body的处理
    const data = [];
    request.on('data', (result) => data.push(result));
    request.on('end', () => {
        request.data = Buffer.concat(data).toString()
        // 读取文件进行url和method的比对
        load(resolve(__dirname, 'routes'), filename => {
            const prefix = filename === 'index.js' ? '' : '/' + filename.split('.')[0];
            const module = require(`./routes/${filename}`);
            Object.keys(module).forEach(str => {
                let [currMethod, currUrl] = str.split(' ');
                currUrl = prefix + currUrl;
                if(url === currUrl && currMethod === method) {
                    module[str](request, response)
                }
            })
        })
    })
    
}).listen(3000, () => {
    console.log('server start up at 3000')
})

这个时候,我们的请求实际上已经可用了,但是因为浏览器跨域限制的原因,ajax post方法并不能正确返回参数,那具体应该怎么做呢?我们接着往下看

解决跨域问题

解决跨域问题,现在最常用的有以下两种(当然还有其他,有时间的话会总结),CROS设置和设置代理,具体设置代理的方式我们可以在下次分享时着重讲一下,今天先说CROS,CROS是几个常用的http请求头的设置,包括一下几个:

  1. Access-Control-Allow-Origin 此项添加的是当前请求允许访问的域名
  2. Access-Control-Allow-Headers 此项添加的是允许发送的请求头信息(涉及复杂请求)
  3. Access-Control-Allow-Credentials 允许携带cooke 具体代码如下:
module.exports = {
    'post /': (request, response) => {
        // 正常环境不应该设置*,为了测试设置*
        response.setHeader('Content-Type', 'text/plain;charset=utf-8');
        response.setHeader('Access-Control-Allow-Origin', '*');
        response.setHeader('Access-Control-Allow-Headers', '*');
        response.setHeader('Access-Control-Allow-Credentials', true);
        response.end('这是home的内容' + request.data);
    },
    'get /detail': (request, response) => {
        response.setHeader('Content-Type', 'text/plain;charset=utf-8');
        response.end('这是home详情页')
    }
}

解决静态文件代理问题

关于静态文件的代理问题,首先我们要有一个静态文件目录,在每次请求进入时,根据请求路径和静态文件目录一起进行文件的检索,如果存在,则返回文件,不存在则返回404,值得注意的是,正常文件处理中包括html/css/js/image/font/等文件,我们可能没法覆盖到全部文件,大家如果感兴趣,可以查看express.static的源码看看框架层面如何处理,今天我们的例子就拿最简单的进行处理:

const filename = resolve(__dirname, temp, url.slice(1));
stat(filename).then(
    result => {
        if(result.isFile()) {
            readFile(filename)
            .then(result => {
                response.end(result);
            })
        }
    },
    err => {
        response.statusCode = 404;
        response.end();
    }
)

结语

以上就是今天的全部内容,大家有想要看的模块可以在评论区提出,具体代码地址,大家感兴趣的也可以下载express或者koa的代码对他们的结构功能进行分析,一定会有不小的收获!