Node.js超详细零基础教程(1)—处理GET、POST请求

封面.jpg

基于nodejs的服务端开发已经有express、koa2等成熟框架。熟练使用这些框架并不难,但背后的原理是怎样的,很多同学并没有做到知其所以然。

本nodejs系列教程先抛开框架,原生搭建nodejs后服务,在掌握原理后,再去学习express、koa2框架。

通过开发一个ToDoList小项目,掌握常用的nodejs开发。大致分为以下几个阶段:

【第一阶段】不借助框架开发nodejs后端服务,包括数据的接收、处理、返回,路由,MySQL、Redis对接,登录验证,简单的安全防范等(分较多期进行讲解)。

【第二阶段】使用express重构项目

【第三阶段】使用koa2重构项目

第一阶段将花费大量篇幅讲解,当我们深入了解原理后再去学习express、koa2就会理解得更加透彻。

重要的事情说三遍:懂原理!懂原理!懂原理!

1 初始化项目

1.1 创建项目目录

找个喜欢的地方,新建并初始化项目,执行以下命令:

mkdir node-server
cd node-server
npm init -y

1.2 编写服务脚本

在项目根目录下创建bin/www.js

+  |- /bin
+     |- www.js
   |- package.json

启动web服务需要使用nodejs的http模块,打开 bin/www.js 编写代码:

const http = require('http')

// 设置服务器端口
const PORT = 8000

// 创建服务器
const server = http.createServer((req, res) => {
   // 返回的内容
   res.end('hello nodejs')
});

// 设置服务器端口
server.listen(PORT)

console.log('node-server started at port http://localhost:' + PORT)

配置入口文件,修改package.json

    "name": "node-server",
      "version": "1.0.0",
      "description": "",
M     "main": "./bin/www.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
+       "dev": "node ./bin/www.js"
      },

【关于main字段】

官方说明的原文是这样的:

The main field is a module ID that is the primary entry point to your program. That is, if your package is named foo, and a user installs it, and then does require("foo"), then your main module’s exports object will be returned.

This should be a module ID relative to the root of your package folder.

For most modules, it makes the most sense to have a main script and often not much else.

main字段是一个模块ID,是指向你程序的主入口。也就是说,如果你的package名叫foo,那么一个用户安装了你这个package,并且执行require("foo"),你的main模块的exports对象将会被返回。

这个模块ID应该相对于你的package根目录。

对于大多数的模块来说,设置main还是非常有意义的,除此之外也没有其他鸟用了。

【关于script字段】

官方说明的原文是这样的:

The “scripts” property is a dictionary containing script commands that are run at various times in the lifecycle of your package. The key is the lifecycle event, and the value is the command to run at that point.

scripts是一个包含了脚本命令的dictionary,可以在package不同的生命周期中被执行。key是生命周期事件,value是要运行的命令。

英语比较好的同学可以参看官方原文:https://docs.npmjs.com/files/package.json

回到我们的项目来,项目的主入口文件是./bin/www.js

现在,在项目根目录下执行npm run dev,就等同于执行node ./bin/www.js
执行后,在控制台可以看到输出,说明服务已经正常启动:

node-server started at port http://localhost:8000

浏览器打开http://localhost:8000,出现“hello nodejs”。

2 设置服务自动热启动

每次修改代码都要重启服务器才能生效很麻烦,使用nodemon来实现自动监测代码变化并重启。

另外,安装cross-env可以方便的跨平台设置环境变量(例如,windows用%ENV_VAR%,其他系统可能使用$ENV_VAR,不统一)

npm install nodemon cross-env --save-dev

修改package.json:

    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
M       "dev": "cross-env NODE_ENV=dev nodemon ./bin/www.js"
    },

再次执行npm run dev的时候,如果代码有改动,web服务会自动重启,这样就方便多啦。

3 处理GET请求

浏览器访问http://localhost:8000?id=1&name=demo,相当于我们发送了一个GET请求,并且传递了两个变量和值。

接下来我们实现:接收GET请求,并把GET请求传递的数据再返回给浏览器。

修改bin/www.js:

    const http = require('http')
+   const querystring = require('querystring')
   
+   // 设置服务器端口
    const PORT = 8000

    // 创建服务器
    const server = http.createServer((req, res) => {
+       // 获取请求的url
+       const url = req.url
+       // 获取请求的method
+       const method = req.method
+       // 解析URL,把url中?后面的参数转换为对象
+       const query = querystring.parse(url.split('?')[1])
       
+       // 设置返回数据的Content-type为JSON
+       res.setHeader('Content-type', 'application/json')
    
+       if (method === 'GET') {
+           // 返回的数据
+           let resData = {
+               error: 0,
+               message: 'GET返回成功',
+               data: {
+                   query: query
+               }
+           }
+           // 将对象转换为json字符串
+           res.end(JSON.stringify(resData));
+           return
+       }
+       // 如果没有匹配,则返回404页面
+       res.writeHead(200, {'content-type': 'text/plain'});
+       res.write('404 Not Found\n')
+       res.end()

-       // 返回的内容            <--删除
-       res.end('hello nodejs')  <--删除
    });

    // 设置服务器端口
    server.listen(PORT)

    console.log('node-server started at port http://localhost:' + PORT)

浏览器访问http://localhost:8000?id=1&name=demo,可以看到返回的数据了。

3 GET请求的数据返回.jpg

【原理讲解】

接收GET请求数据还是很简单的,关键点就是使用const query = querystring.parse(url.split('?')[1])把url中?后面的参数直接解析好了。

4 下载安装postman

因为POST请求不能像GET请求一样通过浏览器的URL直接发起请求,所以在讲解处理POST请求之前,我们先postman这个工具软件。

postman是一款可以模拟各种请求的工具,方便我们进行POST调试,否则我们还要去做一个静态页。通过ajax等方式去请求。

官方建议下载桌面端APP,https://www.getpostman.com/downloads/

5 处理POST请求

启动postman,我们把请求方式改为POST,输入请求地址http://localhost:8000/

点击下方的Body标签,可以看到发起POST请求的content-type有好多种。

5-打开postman.jpg

这里我们以application/jsonform-data为例,分别讲解如何接收POST数据。

5.1 接收application/json数据

首先我们要知道的是,POST数据是以二进制流的方式进行传输,所以要不断的接收数据,直到数据流结束。

修改bin/www.js:

    ...(略)
    const server = http.createServer((req, res) => {
        const url = req.url
        const method = req.method
        const query = querystring.parse(url.split('?')[1])
+       const contentType = req.headers['content-type']
       
        // 设置返回数据的Content-type为JSON
        res.setHeader('Content-type', 'application/json')

        if (method === 'GET') {...}

+       if (method === 'POST') {
+           if (contentType === 'application/json') {
+               let postData = '';
+               req.on('data', chunk => {
+                   // chunk是原始二进制数据,需要转化成字符串
+                   postData += chunk.toString()
+               })
+               req.on('end', () => {
+                   res.end(postData)
+               })
+               return
+           }
+       }
       ...(略)

【原理讲解】

  1. 通过req.headers['content-type']获取请求的数据格式,如果是applicatin/json则进入下面的逻辑。
  2. 创建postData变量,用来存储post数据。
  3. 在接收数据流的时候,会不断触发request的data事件,postData持续累积数据。
  4. 当数据流接收完毕,会触发request的end事件,返回给客户端最终结果。

按照下图设置postman:

5.1-postman设置json数据.jpg

点击Send后,可在下方看到返回的JSON数据。

5.1-postman接收返回数据.jpg

切换到Header标签,可以看到返回数据的content-type为application/json。

5.1-postman接收返回数据的格式.jpg

5.2 接收form-data数据

在项目开发时,特别是jQuery项目,我们经常使用formData提交数据,例如:

var formData = new FormData();
formData.append('username', 'admin')
formData.append('password', 123456)
$.ajax({
    type: 'POST',
    url: 'api/',
    data: formData, 
    dataType: 'json',
    processData: false,
    contentType: false,
    success: function(obj) {
        console.log(obj)
    }
})

如果用5.1章节的方式,接收到的数据是这样的:

------WebKitFormBoundaryQrEkLmfuhHhIu5vy 
Content-Disposition: form-data; name="username"

admin
------WebKitFormBoundaryQrEkLmfuhHhIu5vy       
Content-Disposition: form-data; name="password"

123456
------WebKitFormBoundaryQrEkLmfuhHhIu5vy--

需要我们自行解析,比较麻烦。这里推荐安装multiparty插件。

npm install multiparty --save

修改bin/www.js:

    const http = require('http')
    const querystring = require('querystring')
+   const multiparty = require('multiparty')
    
    const PORT = 8000

    const server = http.createServer((req, res) => {
        ...(略)
        if (method === 'GET') {...}

        if (method === 'POST') { 
            if (contentType === 'application/json') { ... }
+           else if (contentType.indexOf('multipart/form-data') !== -1) {
+               let form = new multiparty.Form()
+               form.parse(req, function(err, fields, files) {
+                   res.end(JSON.stringify({fields: fields, files: files}));
+               });
+               return
            }
        }
        ...(略)

【原理讲解】

1.为何使用contentType.indexOf('multipart/form-data') !== -1来判断是不是multipart/form-data类型?

因为通过form-data提交的数据content-type的值是类似

multipart/form-data;boundary=----WebKitFormBoundaryMxVY9JCuXKMvmRuL

的形式,因此使用indexOf()。

2.let form = new multiparty.Form()可方便的解析form-data数据。fields里可以获取提交的数据变量及值,files里获取提交的文件数据。

3.使用JSON.stringify()将对象转化为JSON字符串返回给客户端。

点击Send之后,可以看到数据已经正常返回了。

5.2-处理form-data数据.jpg

以上就是本期分享,对GET和POST请求的处理方式进行了讲解。这是基于Node.js开发后端服务最最基本的功能了。

下期我们将讲解如何更加科学合理的设置目录结构以及拆分代码文件,更侧重于架构方面,敬请期待~

欢迎关注我的个人微信公众号,随时获取最新文章。

微信关注.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Node.js Node.js 就是运行在服务端的 JavaScript。 Node.js 是一个基于Chrome...
    Gukson666阅读 690评论 0 1
  • 概要 64学时 3.5学分 章节安排 电子商务网站概况 HTML5+CSS3 JavaScript Node 电子...
    阿啊阿吖丁阅读 9,072评论 0 3
  • 个人入门学习用笔记、不过多作为参考依据。如有错误欢迎斧正 目录 简书好像不支持锚点、复制搜索(反正也是写给我自己看...
    kirito_song阅读 2,448评论 1 37
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,082评论 1 32
  • Chapter01.简介 NodeJS是让JavaScript脱离浏览器运行在服务器的一个平台,不是语言。 Nod...
    JunChow520阅读 940评论 0 9