一、简单的静态服务器

1、代码解析

var http = require('http')
// http是nodejs里面的一个模块,这个对象能够提供实现底层的方法。我们通过require去加载这个模块

var server = http.createServer(function(req, res){
  // 函数内部创建一个服务器,创建好之后,通过浏览器访问这个服务器的时候,会把请求封装成一个对象
  // 这个对象就是这个回调函数的第一个参数req。用户请求的信息都在这个对象内,可以获取用户的信息,如ip,请求信息等。
  // 第二个参数res是服务器返回给用户的信息
    console.log('jiengu')
    res.setHeader("Content-Type","text/html; charset=utf-8")
  //设置响应头的content-type内容,text/html是把响应体当成html解析,
    res.write('<h1> 饥人谷</h1>')
  //在res写入服务器返回给浏览器的内容
    res.end()
})
server.listen(9000)
// 通过listen方法来启动他,服务器监听9000端口

2、执行步骤

打开gitbash,切换到js文件当前的文件夹,然后输入node index.js(index.js是我的js文件名,反正你们取什么名就输入啥名)

clipboard.png
打开浏览器,输入http://127.0.0.1:9000/,或者http://localhost:9000/
注意哈9000是代码里面写的9000端口,如果下次改成了8080等其他的端口,那就改成对应的端口就好
clipboard.png

3、响应头和响应体

响应头查看路径:network-name-headers
clipboard.png

响应体:
响应体是response的数据,有点类似于打开网页的查看源代码
clipboard.png

每次修改了js文件的内容之后,要断掉git的服务器,重新连接。不然即使刷新网页没有办法显示修改的内容

4、设置响应头

4.1response.setHeader
格式:response.setHeader(name, value)
为一个隐式的响应头设置值。 如果该响应头已存在,则值会被覆盖。 如果要发送多个名称相同的响应头,则使用字符串数组。 非字符串的值会保留原样,所以 response.getHeader() 会返回非字符串的值。 非字符串的值在网络传输时会转换为字符串。
举例:

response.setHeader('Content-Type', 'text/plain'); //当成字符串解析
response.setHeader('Content-Type','text/html; charset=utf-8')//当成html解析,如果是css就设置为text/css

执行结果
clipboard.png

setHeader引申的链接,是nodejs中文网的规范

4.2 response.writeHead()
writeHead文档规范
格式:response.writeHead(statusCode, statusMessage)
参数1 statusCode(状态码)是一个三位数的 HTTP 状态码,如 404。
参数2是 statusMessage 是可选的状态描述,是一个string。
参数3 headers 是响应头,是个对象。其实我们可以理解为这个对象放的是response headers全部内容。我们设置的writehead的内容处理status码是放在general,其他的内容都是封装成一个对象放在响应头内容response headers。

clipboard.png

 response.writeHead(404, 'Not Found')
res.writeHead(200,'hhh', { 'Content-Type':'text/plain;charset=utf-8','X-Foo':'bar2222'});

4.3两者的不同

  • response.writeHead() 在消息中只能被调用一次,且必须在 response.end() 被调用之前调用。调用两次就会报错。 setheader可以多次调用
  • headers.setheader()只允许您设置单一标题。
    writehead()允许您设置关于响应头的几乎所有内容,包括状态代码、内容和多个标题。

4.4遇到的坑
坑1:res.setHeader("Content-Type","text/html; charset=gbk")才是对的,charset=gbk必须放在Content-Type内部,展示的时候也是在一起。(我猜想charset应该是Content-Type的一部分)

clipboard.png

如果分开写成下面的格式,不会报错,但charset就变成了响应头的单独子项展示,而且charset=utf-8不会生效(下图utf-8没有生效就按照gbk去解码,就出现了乱码)。

res.setHeader('Content-Type', 'text/html');
res.setHeader("charset","utf-8")

clipboard.png
所以一定注意写法

坑2:writeHead只能写一次,所有响应头要设置的内容都要按照对象的格式,放在参数三headers里面。以下缩写是正确的,要记住啊

 res.writeHead(200,'hhh', { 'Content-Type':'text/plain;charset=utf-8','X-Foo':'bar2222'});

坑3:response.setHeader() 设置的响应头会与 response.writeHead() 设置的响应头合并,但是如果设置的内容重复,以response.writeHead() 的优先为准。

var server = http.createServer(function(req, res){
    res.setHeader("Content-Type","text/html; charset=utf-8")
    res.setHeader('X-Foo', 'bar');
    res.writeHead(200,'hhh', { 'Content-Type':'text/plain;charset=utf-8','X-Foo':'bar2222'});

    res.write('<h1> 饥人谷2</h1>')
    res.end()
})
server.listen(9000)

执行结果是:很明显的看到setHeader和writeHead重复设置的内容,都是以writeHead为准的

clipboard.png

4.5设置status的异常

 res.writeHead(404,'hhh');

当我设置status为404,发现即使是请求成功回送之后,也会出现红色。这是因为大家约定404就是一个错误的状态,所以status的值要按照约定来设置
clipboard.png

二、一个可用的静态服务器

搭建一个有图片,css,js的资源的服务器,github代码链接

1、步骤

  1. 我在step1文件夹下放置了server.js文件,static文件夹。static文件夹对应放了css,png,js,html等文档,并在html文档内引用了图片,css,js资源。
  2. 打开gitbash,切换step1文件夹,执行node server.jsclipboard.png
  3. 打开浏览器输入localhost:8080index.html,查看结果

clipboard.png

输出内容
clipboard.png

clipboard.png

2、js代码解析

var http = require('http')
var path = require('path')
// path模块处理url,不同系统(mac/lincx/window)下对url的写法可能不一致的。(一个写成c:/project/code/a.png
// 另外一个可能写成/user/local/project/a.png)。path模块会对这种情况自动处理url类型
var fs = require('fs')
// fs模块用来读取文件数据,也可以往文件里面写数据。
var url = require('url')
// url模块可以自动解析url,得到一个对象,可以获得对应的信息。




function staticRoot(staticPath, req, res){
  console.log(staticPath) 
  //输出static文件的绝对路径,/user/documents/code/node-server/step1/static
  console.log(req.url) 
  //请求的url地址,第一次调用html时,为/index.html,第二次调用css时,就是css/a.css
  var pathObj = url.parse(req.url, true)
  // 解析url,得到url对象(包含protocal/hostname/port/pathname/query等等),即pathobj对象就是url的对象。本次要用的是pathname
  console.log(pathObj)
  
  
  if(pathObj.pathname === '/'){
    pathObj.pathname += 'index.html'
  }
  //如果pathname没有输入(浏览器输入的值只是localhost:8080,没有后缀的话),服务器默认选择去读取和发送index.html文件

  

  var filePath = path.join(staticPath, pathObj.pathname)
  // staticPath=static文件夹的绝对路径, pathObj.pathname=调用文件的后缀地址。
  // 两个加起来得到filePath(用户输入的url想要访问文件的绝对路径),举例本文是/user/documents/code/node-server/step1/static/index.html

  // var fileContent = fs.readFileSync(filePath,'binary')
  // res.write(fileContent, 'binary')
  // // 采用同步的方式读取filePath的文档,把读取的数据写入res对象内
  // res.end()
  
  
  fs.readFile(filePath, 'binary', function(err, fileContent){
  // 异步的方式来读取filePath的文档。binary指以二进制的方式来读取数据,因为服务器不仅仅要读取普通的数据,需要兼容图片和文件等数据。
    if(err){
      console.log('404')
      res.writeHead(404, 'not found')
      res.end('<h1>404 Not Found</h1>')
  // 在页面展示404 Not Found。在res.end('数据')等于执行res.write('数据')加上res.end()
    }else{
      console.log('ok')
      res.writeHead(200, 'OK')
      res.write(fileContent, 'binary')
      // 通过二进制的方式发送数据
      res.end()      
    }
  })
  

}

console.log(path.join(__dirname, 'static'))

// 在浏览器输入localhost:8080/index.html地址,浏览器向服务器发起请求。
// 服务器收到请求后,执行相关函数,解析req对象信息,得到了index.html的地址。
// 服务器根据解析的地址在本地static文件夹下找到对应的index.html文件,读取html里面数据,并把数据放在res内,当成字符串发给服务器。

var server = http.createServer(function(req, res){
  staticRoot(path.join(__dirname, 'static'), req, res)  //写一个staticRoot函数,来处理请求。
 /* 参数1:把哪个路径当成静态文件路径,传递路径名。__dirname是nodejs里面的一个变量,代表当前的server.js执行的这个文件。
  path.join(__dirname, 'static')可以使用一个或多个字符串值参数,该参数返回将这些字符串值参数结合而成的路径。
var joinPath = path.join(__dirname, 'a', 'b', 'c');
console.log(joinPath);      //   D:\nodePro\fileTest\a\b\c,
__dirname对应的step1文件夹的路径,加上static文件夹得路径,就等于static的绝对路径。、
  这样的好处是每次绝对路径发生变化的时候,不用重新去修改绝对路径。*/
})

server.listen(8080)  //创建一个服务器,监听8080端口
console.log('visit http://localhost:8080' )

3、代码难点解析

3.1 path node.js文档中的标准解释
path 模块用于处理文件与目录的路径。不同系统(mac/lincx/window)下对url的写法可能不一致的。(一个写成c:/project/code/a.png
// 另外一个可能写成/user/local/project/a.png)。path模块会对这种情况自动处理url类型

3.2 path.join([...paths])
参数...paths <string> :路径片段的序列,返回: <string>
使用平台特定的分隔符把所有 path 片段连接到一起,并规范化生成的路径

path.join('C:\Users\jz\documents\code\node-server\step1'
, 'static')
//C:\Users\jz\documents\code\node-server\step1\static

3.3 fs 文件系统node.js文档中的标准解释
fs 模块用于以一种类似标准 POSIX 函数的方式与文件系统进行交互。
所有的文件系统操作都有同步和异步两种形式。
异步形式的最后一个参数是完成时的回调函数。 传给回调函数的参数取决于具体方法,但第一个参数会保留给异常。 如果操作成功完成,则第一个参数会是 null 或 undefined。

3.4 fs.readFile(path[, options], callback)异步地读取文件的内容
path 文件名或文件路径
options 如果 options 是一个字符串,则指定字符编码,默认为 null
callback 是一个回调函数,有两个参数 (err, data),其中 data 是要读取文件的内容

fs.readFile(filePath, 'binary', function(err, fileContent){
  // 异步的方式来读取filePath的文档。binary指以二进制的方式来读取数据,因为服务器不仅仅要读取普通的数据,需要兼容图片和文件等数据。
    if(err){
      console.log('404')
      res.writeHead(404, 'not found')
      res.end('<h1>404 Not Found</h1>')
  // 在页面展示404 Not Found。在res.end('数据')等于执行res.write('数据')加上res.end()
    }else{
      console.log('ok')
      res.writeHead(200, 'OK')
      res.write(fileContent, 'binary')
      // 通过二进制的方式发送数据
      res.end()      
    }
  })

3.5 fs.readFileSync(path[, options])
同步的读取文件内容,两个参数和异步的一样的用法

// var fileContent = fs.readFileSync(filePath,'binary')
  // res.write(fileContent, 'binary')
  // // 采用同步的方式读取filePath的文档,把读取的数据写入res对象内
  // res.end()

3.6 url模块node.js文档中的标准解释
url 模块提供了一些实用函数,用于 URL 处理与解析。 URL 字符串可以被解析为一个 URL 对象,其属性对应于字符串的各组成部分。

clipboard.png

3.7url.parse(urlString[, parseQueryString[, slashesDenoteHost]])
url.parse() 方法会解析一个 URL 字符串并返回一个 URL 对象。
urlString <string>
要解析的 URL 字符串。
parseQueryString <boolean>
如果为 true,则 query 属性总会通过 querystring 模块的 parse() 方法生成一个对象。 如果为 false,则返回的 URL 对象上的 query 属性会是一个未解析、未解码的字符串。 默认为 false。
slashesDenoteHost <boolean>
如果为 true,则 // 之后至下一个 / 之前的字符串会被解析作为 host。 例如,//foo/bar 会被解析为 {host: 'foo', pathname: '/bar'} 而不是 {pathname: '//foo/bar'}。 默认为 false。
举个例子

var pathObj = url.parse(req.url, true)// 解析req.url,得到url对象pathobj

clipboard.png
3.8__dirname
当前模块的文件夹名称。等同于 __filename 的 path.dirname() 的值
__filename 当前模块的文件名称---解析后的绝对路径
例如:
在 /Users/mjr 目录下执行 node example.js

console.log(__filename);
// Prints: /Users/mjr/example.js
console.log(__dirname);
// Prints: /Users/mjr

4、坑

有一个问题,为什么我们要用req.url解析成url对象pathobj,再通过staticPath文件地址和pathobj.pastname结合成filepath,为啥我们不直接把req.url和staticPath结合在一起生成filepath呢?这样还少了一步呢

答案:如果requrl是常规的index.html或者css.css这种,两种方式都不会报错。但是如果url比较复杂,像是index.html?query=111#111这种,直接把req.url和staticPath结合在一起是会报错的,所以需要转成url对象再把pashname挑出来。

三、实现一个简单的node.js服务器路由

实现更复杂的服务器,url不仅仅是定位一个静态文件,可以mock任何数据和前端交互。

1、核心原理:

根据浏览器请求的不同路由,导致服务器执行不同的操作。

2、文档结构:

clipboard.png

3、服务器实现3条路由:

  • /getWeather,结合b.js文件实现一个ajax来mock天气数据
  • /user/123 ,结合user.tpl文件实现用户页面
  • /index.html,结合index.html实现index.html的页面。在html引用css文件,b.js,和图片

4、对应的文件内容

可以查看GitHub上面的代码,我这里截图说明

html
clipboard.png
css

clipboard.png
js,实现ajax的代码

clipboard.png

user.tpl
clipboard.png
最重要的server-simple.js服务器代码
本次演示的url是localhost:8080/user/123,localhost:8080之后的内容是路由。所有请求到8080这个服务器内,根据不同的路由给浏览器发送不同的数据

var http = require('http')
var fs = require('fs')
var url = require('url')



http.createServer(function(req, res){

  var pathObj = url.parse(req.url, true)
  console.log(pathObj)

  switch (pathObj.pathname){
    case '/getWeather':    //根据req.url来执行不同的函数
      var ret
      if(pathObj.query.city == 'beijing'){
        ret = {
          city: 'beijing',
          weather: '晴天'
        }
      }else{
        ret = {
          city: pathObj.query.city,
          weather: '不知道'
        }
      }
      res.setHeader('content-Type','text/plain;charset=utf-8')
      res.end(JSON.stringify(ret)) //给浏览器输入是一个json格式的对象,根据JSON.stringify转换成字符串
      break;
    case '/user/123':

      res.end( fs.readFileSync(__dirname + '/static/user.tpl' ))
      //如果路由是/user/123,读取user.tpl的内容,并返回给浏览器
      break;
    default:
      res.end( fs.readFileSync(__dirname + '/static' + pathObj.pathname) )
  }
}).listen(8080)

5、执行结果

index.html
clipboard.png

/getWeather
clipboard.png

/user/123
clipboard.png


bamboo
75 声望9 粉丝

bamboo前端学习笔记


« 上一篇
mock数据
下一篇 »
跨域