node.js怎么用?

263 阅读15分钟

用http 模块写一个简单的web服务器

http模块在线文档:nodejs.org/dist/latest…

目标

用http模块写一个简单的服务器

要点

  • 引入http核心模块

  • 使用createServer来创建服务

  • 使用listen来启动服务

    操作

第一步:新建一个文件,名为 d:/src/http.js( 文件名及路径名可以自行设置,建议均不使用中文字符), 内容如下

// 创建我们第一个web服务器
// 1.0 导入http模块
const http = require('http');
​
// 2.0 创建一个服务
let server = http.createServer((req, res) => {
    console.log('接收到了客户端请求');
    res.end('OK');
})
​
// 3.0 监听8001端口并启动web服务器等待客户端请求
server.listen(8001, () => {
    console.log('web服务器准备就绪:127.0.0.1:8001可以访问');
})

第二步:运行js代码。

在小黑窗中进入到d盘根目录,键入命令 node http.js,此时会弹出一个小黑窗,不要关。

第三步:本地验收

打开一个浏览器页面,输入'http://localhost:8081',观察效果:

- 浏览器中的效果
- 小黑窗中的效果

第四步:共享地址

把localhost改成你自己电脑的ip地址,再把这个路径发你的同学(同一个局域网)来访问。

第五步:停止服务

ctrl + c

如果不能访问,有可能你需要手动关闭你自己计算机的防火墙。

工作原理

使用http模块在本机上创建一个虚拟服务器,它来接收浏览器的请求,并给出响应。

image.png

注意:

  • 小黑窗不要关,它就是服务器,它不会有主动行为(看起来没有任何变化),它在时刻等待客户端的访问。

代码解析

  • 引入核心模块,得到的http是一个对象。

  • http.createServer方法创建一个http服务。参数是一个回调函数:当有http请求进来时,它会自动被调用。请求一次,它就被调用一次

    • 第一个参数:客户端的请求

    • 第二个参数:设置对本次请求的响应

      • res.end() :设置响应体,结束请求。
  • server.listen() 用来监听端口。

    • 格式:server.listen(端口号,[回调函数]) 。回调是可选的。

    • 说明:

      • 如果监听成功,则回调函数会执行一次。
      • 如果不成功(例如端口被占用),会报错。

修改代码后要重启

更改res.end()的内容,重启后,再次观察。

  • 停止服务: 在小黑窗中按下ctrl+c 停止服务。
  • 重启服务:就是重新运行程序(按下向上的箭头,再回车)。

修改了服务器的代码要重启。

理解请求和响应

请求

当web服务器就绪之后,如果没有客户端来访问它,它也是不会有任何效果的。也就是说回调函数不会执行。

而每一次的请求,都会导致回调函数要执行一次。

服务器的响应内容格式

res.end()的格式只能是buffer或者是String

练习

写一个服务器,让你的同学们来访问一下。

随机返回内容

开启多台服务器

复制一份代码,并启动,更改端口号,模拟多台服务器。

不同URL返回不同的内容-认识URL

全称

Uniform Resource Locator,统一资源定位符。

作用

定位资源(css,html,js,png, avi,接口......)。

格式

协议://主机地址[:端口]/路径?查询字符串#锚点
  • 协议: http 或者是 https

  • 主机地址: IP地址 或者 域名

  • 端口号

    • http请求,默认端口80(可以省略)
    • https请求,默认端口443(可以省略)
    • MySQL默认端口3306
  • 路径

    • 服务器文件夹上的资源。(.html/.css/.images/.js/接口)
  • 参数(查询字符串)

    • ? 后面的部分,是键值对的形式
  • 锚点

    • 网页内部的锚点链接

例如:itcast.cn:80/schools/stu…

经典用法:访问文件时传递参数。

// index.html
<a href='detail.html?id=1'>新闻1</a>
<a href='detail.html?id=2'>新闻2</a>

上面的两个地址在指向detail.html时,分别传入了不同的参数

// detail.html
<script>
// 1. 获取id
// 2. 根据id值去获取这个新闻的详情 
</script>

不同的URL返回不同的内容-问题分析

思路:在服务器端收到客户端发的请求之后,分析url是什么,然后分别对应处理

image.png

不同的URL返回不同的内容-req.url

目标

通过 req.url来获取当前请求的url地址 ,并做出相应处理

req.url属性

首先就需要知道浏览器请求的url是什么。

涉及到和请求相关的信息,都是通过请求响应处理函数的第一个参数完成的。

image.png

代码示例

const http = require('http');
​
// 创建服务
const server = http.createServer(function(req, res) {
  console.log(req.url)
  res.end(req.url)
});
// 启动服务
server.listen(8081, function() {
  console.log('success');
});

req.url用来获取本次请求的资源地址。在请求行中可以看到这一点。

小结

序号浏览器中的url服务器中的req.url
1http://localhost:8080/
2http://localhost:8080/a.html/a.html
3http://localhost:8080/js/jquery.js/js/jquery.js
4http://localhost:8080/1.jpg/1.jpg
5http://localhost:8080/api?a=1&b=2/api?a=1&b=2

req.url一定是以/开头的。

在现代浏览器中,它们会自动去请求服务器上的favicon.ico

不同的URL返回不同的内容-读取文件内容并返回

目标

用户在访问服务器上不同的url时,能返回不同的内容

目录结构

|-index.html
|-style.css
|-01.png
|-js/jquery.js
|-server.js

请求与响应的对应的关系

用户的请求地址服务器的动作
http://localhost:8000读出index.html的内容并返回
http://localhost:8000/index.html读出index.html的内容并返回
http://localhost:8000/style.css读出style.css的内容并返回
http://localhost:8000/01.png读出01.png的内容并返回
http://localhost:8000/js/jquery.js读出jquery.js的内容并返回
http://localhost:8000/xxxx 或者不是上面的地址返回404

示例代码

// 1.0 导包
const http = require('http');
const fs = require('fs');
const path = require('path');
​
// 2.0 创建服务器对象并且设置好回调函数
let server = http.createServer((req, res) => {
​
    // 1.0 根据不同路径响应不同的数据内容
    // 如果浏览器请求的是 /index.html 这个路径,那么就读取这个index.html中的内容通过res.end()响应即可
    // 如果浏览器请求的是 /style.css 这个路径,那么久读取style.css通过res.end()响应即可
​
    // 获取路径
    let urlPath = req.url;
    console.log('urlPath==>', urlPath);
    if (urlPath === '/index.html') {
        // 读取index.html文件的内容响应回去
        let htmlString = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8');
        // 防止低版本浏览器不能自动解析内容,则手动设置
        res.setHeader('content-type', 'text/html;charset:utf8');
        // res.end() 响应
        res.end(htmlString)
    } else if (urlPath === '/style.css') {
        // 读取到了当前目录下的style.css的内容
        let styleString = fs.readFileSync(path.join(__dirname, 'style.css'), 'utf8');
        // 防止低版本浏览器不能自动解析内容,则手动设置
        res.setHeader('content-type', 'text/css;charset:utf8');
​
        // 响应
        res.end(styleString);
    } else {
        // 资源找不到,状态码应该设置为404,同时响应一段文本告诉用户资源找不到
        res.statusCode = 404;
        res.setHeader('content-type', 'text/html;charset=utf8')
        res.end('您要的资源去了火星,找不到咯');
    }
​
})
​
// 3.0 启动服务器
server.listen(8000, () => {
    console.log('服务器已经在8000端口准备就绪,等待客户端访问');
})

综合使用fs, path, __dirname

注意

  • url地址与服务器上的文件地址并不是一一对应的。url的作用是确定用户要访问的资源的位置,在地址栏中输入回车之后,这个请求会到web服务器中来,然后由web服务器来决定此时返回什么数据给用户。但是,我们能够根据url来推测服务器会返回什么信息吗?

    url:http://nodejs.cn/api/querystring.html
    请求一个页面,名是querystring.html
    url:http://oa.itcast.cn/seeyon/main.do?method=main
    url:https://mail.qq.com/cgi-bin/frame_html?sid=aLqnlljMxF54DgtW&r=d281ced83329f34caae9786fcb5d4934
    

    显然,不能,你能从服务器上获得什么,完全是由服务器决定的。

  • 如果用户请求的是静态资源(静态资源指的是html文件中链接的外部资源,如.html, css、js、image文件等等),服务器的处理方法就是:读出文件内容,返回给用户。

不同的URL返回不同的内容-设置content-type

目标

  • 理解content-type的作用
  • 会根据不同的文件类型来设置不同的content-type

content-type的作用

在http协议中,content-type用来告诉对方本次传输的数据的类型是什么。

  • 在请求头中设置content-type来告诉服务器,本次请求携带的数据是什么类型的
  • 在响应头中设置content-type来告诉服务器,本次返回的数据是什么类型的

通过使用res对象中的setHeader方法,我们可以设置content-type这个响应头。这个响应头的作用是告诉浏览器,本次响应的内容是什么格式的内容,以方便浏览器进行处理。

常见的几种文件类型及content-type

  • .html:res.setHeader('content-type', 'text/html;charset=utf8')
  • .css:res.setHeader('content-type', 'text/css;charset=utf8')
  • .js:res.setHeader('content-type', 'application/javascript')
  • .png:res.setHeader('content-type', 'image/png')
  • json数据:res.setHeader('content-type', 'application/json;charset=utf-8')

其它类型,参考这里:developer.mozilla.org/en-US/docs/…

如果读出来的是.html的文件,但是content-type设置为了css。则浏览器将不会当作是html页面来渲染了。

示例代码

不同的URL返回不同的内容-设置statusCode

目标:了解statusCode的作用

会正确使用statusCode

res.statusCode = 301;
// res.setHeader('location','http://www.qq.com')
res.statusCode = 404
res.statusCode = 500
res.end()

处理.html文件中的二次请求

二次请求

从服务器获取html文件之后,如果这个html文件中还引用了其它的外部资源(图片,样式文件等),则浏览器会重新再发请求。

假设在index.html中还引入了 style.css 1.png 或者 .js文件,则:浏览器请求localhost:8000/index.html之后,得到的从服务器反馈的内容,解析的过程中还发现有外部的资源,所以浏览器会再次发出第二次请求,再去请求相应的资源。

思路

一个最朴素的想法是枚举不同的请求来返回不同的文件。

代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        h1{
            text-align: center;
        }
    </style>
+    <link rel="stylesheet" href="./style.css">
</head>
<body>
    <h1>index.html</h1>
+    <img src="./01.png" alt="">
</body>
</html>

上面的+号只是表示这里的代码需要修改。

const http = require('http');
const fs = require('fs');
const path = require('path');
​
//创建服务器
const app = http.createServer((req, res) => {
​
  if (req.url === '/index.html') {
    let htmlString = fs.readFileSync(path.join(__dirname, 'index.html'));
    res.end(htmlString);
  }
  else if (req.url === '/style.css') {
    let cssString = fs.readFileSync(path.join(__dirname, 'style.css'));
    res.setHeader('content-type', 'text/css');
    res.end(cssString);
  } else if (req.url === '/1.png') {
    let pngString = fs.readFileSync(path.join(__dirname, '/1.png'));
    res.end(pngString);
  } else {
    res.setHeader('content-type', 'text/html;charset=utf-8');
    res.statusCode = 404;
    res.end('<h2>可惜了, 找不到你要的资源' + req.url + '</h2>');
  }
}); 
//启动服务器,监听8082端口
app.listen(8082, () => {
  console.log('8082端口启动');
});

作业

const http = require('http');
const fs = require('fs');
const path = require('path');
​
let server = http.createServer((req, res) => {
    let urlPath = req.url;
    if (urlPath === '/index.html') {
        let htmlString = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8');
        // 防止低版本浏览器不能自动解析内容,则手动设置
        res.setHeader('content-type', 'text/html;charset=utf8');
        // res.end() 响应
        res.end(htmlString)
    } else if (urlPath === '/style.css') {  // 读取css文件
        // 读取到了当前目录下的style.css的内容
        let styleString = fs.readFileSync(path.join(__dirname, 'style.css'), 'utf8');
        // 防止低版本浏览器不能自动解析内容,则手动设置
        res.setHeader('content-type', 'text/css;charset=utf8');
​
        // 响应
        res.end(styleString);
​
    } else if (urlPath === '/images/bg.jpg') {
        // 读取图片内容 ,img 格式:
        let img = fs.readFileSync(path.join(__dirname, '/images/bg.jpg'));
        res.setHeader('content-type', 'image/jpg');
        // 将img响应回去
        res.end(img);
    }
    else if (urlPath === '/images/fm.jpg') {
        // 读取图片内容 ,img 格式:
        let img = fs.readFileSync(path.join(__dirname, '/images/fm.jpg'));
        res.setHeader('content-type', 'image/jpg');
        // 将img响应回去
        res.end(img);
    }
    else {
        res.statusCode = 404;
        res.setHeader('content-type', 'text/html;charset=utf8')
        res.end('您要的资源去了火星,找不到咯');
    }
​
})
​
server.listen(8002, () => {
    console.log('启动');
})

批量处理请求

问题分析

由于我们无法事先得知一个.html文件中会引用多少个静态资源(.png, .css, .js....),所以,我们不能像处理某个页面一样去处理它们。

我们的解决办法是:

  • 把所有的静态资源(.html,.png,.css,.js)全放在一个指定的目录里;

  • 收到用户的请求之后,去指定的目录下去找对应的文件

    • 找到,把内容读出来返回给用户。
    • 找不到,报404。

目录如下:

|-public
|-public/index.html
|-public/stye.css
|-public/01.png
|-server.js

在上面的目录结构中,我们把所有的静态资源全放在public下面,然后使用server.js来启动web服务器。

参考代码

// 当收到用户的请求之后
// 在public下去读这个文件
//  读到:返回
//  读不到:返回404const http = require('http')
const path = require('path')
const fs = require('fs')
​
// 放一个对象,其中保存所有的 后缀名与content-type的一一对应关系
const mapExtToContentType = {
  '.html': 'text/html;charset=utf8',
  '.jpg': 'image/jpg',
  '.css': 'text/css;charset=utf8',
  '.js': 'application/javascript',
  '.ico': 'image/ico'
}
​
const server = http.createServer((req, res) => {
  const { url } = req
  // 获取后缀名
  const ext = path.extname(url)
  console.log(url, ext)
  // 1. 拼接地址
  const filePath = path.join(__dirname, 'public', url)
  // 2. 读资源文件
  try{
    const content = fs.readFileSync(filePath)
    console.log(content)
​
    // 根据不同的后缀名,补充不同的content-type
    // .html ---> text/html;charset=utf8
    // .jpg ---> image/jpg
    
    if(mapExtToContentType[ext]) {
      res.setHeader('content-type', mapExtToContentType[ext])
    }
​
    // mapExtToContentType[ext] && res.setHeader('content-type', mapExtToContentType[ext])
    
    res.end(content)
  } catch (err) {
    console.log(err)
    res.statusCode = 404
    res.end('404')
  }
})
​
server.listen(8001, () => {
  console.log('你的服务器在8001就绪');
})

理解静态资源与接口的区别

服务器上有很多的资源,每个资源都有自己的url。客户端浏览器想要访问某个资源就要向服务器发起对应的请求。

资源的分类

  • 静态资源

    • 它们一般表现为一个一个的文件。例如index.html, style.css, index.js, mp4, .png....。
    • 处理请求静态资源时,服务器一般就直接读出资源的内容,再返回给客户端浏览器
  • 动态资源:接口

    • 它们不是以某个具体的文件存在的,而是服务器上的一段代码,访问接口时,服务器会执行这段代码,然后把代码的执行结果返回给客户端浏览器。

image.png

发送请求的途径

  • 在地址栏中直接访问这个url
  • 通过某个a标签进行进行跳转
  • 通过表单进行提交
  • 通过ajax技术访问这个url

发送请求的类型

  • get:在地址栏中直接访问这个url就是get方式。对于静态资源,我们通过的处理方式就是get请求。
  • post: 通过表单提交,可以设置form的method为post
  • delete
  • put
  • patch
  • options
  • ......

写一个不带任何参数的get类型接口

目录结构

|-db
|---data.json # 数据
|-server.js # 你的代码

目标

在server.js中写代码,提供一个名为getList的接口(http://localhost:8083/getList),它以json字符串格式返回db/data.json的内容。并通过使用postman软件进行测试。

参考代码

// server.js
const http = require('http');
const app = http.createServer((req, res) => {
  if (req.url === '/getmsg' && req.method=== 'GET') {
    const filePath = path.join(__dirname, 'db', 'data.json')
    let content = fs.readFileSync(filePath, 'utf8')
    res.end(content);
  } else {
    res.end('error');
  }
});
app.listen(8083, () => {
  console.log(8083);
});

说明:

  • 注意:类型
  • req.method 可以判断请求的类型
  • res.end()的参数只能是字符串(或者是buffer),而不能是对象

postman的使用说明

postman的作用

前端人员一定要会用它!它用来去测试接口

下载地址+汉化:gitee.com/hlmd/Postma…

image.png

带参数的get请求-获取查询字符串中的数据

目标

提供一个名为getList的接口(http://localhost:8083/getList?name=xxxx),它以json字符串格式返回db/data.json中name为xxxx的数据

使用postman软件进行测试。

目录结构

|-db
|---data.json # 数据
|-server.js # 你的代码

参考代码

// server.js
const http = require('http');
const app = http.createServer((req, res) => {
  const [url, queryStr] = req.url.split('?')
  if (url === '/getmsg' && req.method=== 'GET') {
    // 从queryStr中拆解出来name
    // ....
  } else {
    res.end('error');
  }
});
app.listen(8083, () => {
  console.log(8083);
});

思路: 1、首先测试的请求的地址是: http://127.0.0.1:8004/getHeroSkin?name=后裔

为了实现上面的测试地址能得到正确结果,需要编写

  1. 三个步骤开启nodejs服务器

  2. 在第二步的回调函数中编写逻辑

    1. 解析出url中的查询参数字符串 let [pathname,query] = req.url.split('?')
    2. 利用 let urlsearch = new URLSearchParams(query) 实例化参数对象
    3. 利用 urlsearch.get() 方法获取参数值
  3. 读取data.json文件内容,使用JSON.parse() 转换成js数组

  4. 使用filter方法结合2.3中获取到的参数过滤出需要的数据通过res.end()响应回去

nodejs中的querystring模块

用来对url中的查询字符串这部分进行处理。nodejs中提供了querystring这个核心模块来帮助我们处理这个需求。

地址

示例

const qs= require('querystring');
let obj = qs.parse('id=18&name=zs');
console.log(obj) // {id:18, name:"zs"}

URLSearchParams 解析参数

URLSearchParams可以将查询字符串解析成一个对象,通过get方法获取到指定参数值

参考网址:developer.mozilla.org/zh-CN/docs/…

示例

let query = 'name=后裔'
// 通过urlSearchParams解析参数值
let urlSearch = new URLSearchParams(query);
// 通过get方法获取到查询字符串name参数的值
let namevalue = urlSearch.get('name');

post接口

目录结构

|-db
  |---data.json # 数据 [{"name": "小李"},{"name": "小张"}]
|-server.js   # 你的代码

目标

在server.js中写代码,提供一个名为add的接口(http://localhost:8083/add),它以post的方式请求接口,并传入name值,把数据保存到db/data.json中去。

使用postman软件进行测试。

image.png

预备知识

post类型与get类型的接口区别较大,主要在两个方面:

  1. 类型不同: 可以通过 req.method 来获取(GET,POST)

  2. 传参不同

    • get请求参数在请求行中(附加在url后面),内容比较少。
    • post请求参数在请求体中。内容比较大(上传图片,上传文件....)。

对于获取post参数就相对复杂一些。它的特点是:

  1. 参数在请求体中发给后端

  2. 后端是一段一段接收数据的,并不像get在请求行中传递的数据:直接写在url中的查询字符串内,可以立即通过req.url来解析出来。

  3. 在接收参数的过程中,会涉及req对象的两个事件data,end

    • data事件,每次收到一部分参数数据就会触发一次这个事件。
    • end事件,全部的参数数据接收完成之后会执行一次。

基本流程

基本流程是:

  1. 定义一个空容器result来装参数。
  2. 在req对象上监听data事件。这个事件触发一次,就把当次收到的数据向result中填充一些。
  3. 在req对象上监听end事件。这个事件触发时,就表示整个参数数据接收完成,此时取出容器result中的内容,进行解析,以取出参数。