Nodejs中的web服务器

一、web服务器

1.1、介绍

Web服务器一般指的是网站服务器,是指驻留因特网上某一台或N台计算机的程序,可以处理浏览器等Web客户端的请求并返回相应响应,目前最主流的三个Web服务器是Apache、 Nginx 、IIS、Tomcat。
在这里插入图片描述

1.2、服务器相关概念

ip地址:IP地址就是互联网上每台计算机的唯一地址,因此IP地址具有唯一性。在开发期间,自己的电脑既是一台服务器,也是一个客户端,可以在本机浏览器中输入127.0.0.1进行访问。

域名:尽管 IP地址能够唯一地标记网络上的计算机,但IP地址是一长串数字,不直观,而且不便于记忆,于是人们又发明了另一套字符型的地址方案,叫域名地址。IP地址和域名是一一对应的关系,这份对应关系存放在一种叫做域名服务器(DNS)的电脑中。在开发测试期间, 127.0.0.1 对应的域名是 localhost。

网络协议:网络上的计算机之间交换信息,就像我们说话用某种语言一样,在网络上的各台计算机之间也有一种语言,这就是网络协议,不同的计算机之间必须使用相同的网络协议才能进行通信。如:TCP、UDP、HTTP、FTP等等。

端口:务器的端口号就像是现实生活中的门牌号一样。通过门牌号,外卖员就可以准确把外卖送到你的手中。同样的道理,在一台电脑中,可以运行N多个web 服务。每个 web 服务都对应一个唯一的端口号。客户端发送过来的网络请求,通过端口号,可以被准确地交给对应的 web 服务进行处理。端口号总数:(2的16次方) 65535 其中1-1024系统保留

注:服务器上的端口号是不可以重复的,必须是独一无二
http服务默认端口号为 80 https默认端口号443

1.3、创建web服务器

NodeJs是通过官方提供的http模块来创建 web服务器的模块。通过几行简单的代码,就能轻松的手写一个web服务,从而对外提供 web 服务。

//①、导入http模块    
	const http = require('http')
//②、创建web服务对象实例
	const server = http.createServer()
//③、绑定监听客户端请求事件request
	server.on('request', (request, response) => {})
	//request: 接受客户端请求对象,它包含了与客户端相关的数据和属性
			   request.url      //客户端当前请求的uri地址
			   request.method  //客户端请求的方式 get或post
			   req.headers	  //客户端请求头信息
	//response:服务器对客户端的响应对象
			  //设置响应头信息 ,用于响应时有中文时乱码解决处理
			  response.setHeader('content-type', 'text/html;charset=utf-8')
			  //设置状态码
  			  res.statusCod = 200
			  //向客户端发送响应数据,并结束本次请求的处理过程
			  res.end('hello world')
//④、启动服务
	// 在window中查看当前电脑中是否占用此端口命令
	// 如果返回为空,表示此端口没有表占用,有值则为占用
	// netstat -ano | findstr 8080 
	// mac =>  sudo lsof -i tcp:port
	server.listen(8080, '0.0.0.0',() => {
  		console.log('服务已启动')
	})

1.4、静态资源服务器

实现思路:

客户端请求的每个资源uri地址,作为在本机服务器指定目录中的文件。
之后在通过相关模块进行读取文件中数据进行响应给客户端,从而实现静态服务器

在这里插入图片描述
实现步骤:

1、导入模块:
	const http = require('http');
	const fs = require('fs');
	const path = require('path');
	const url = require('url');
2、使用http模块创建web服务器
	const server = http.createServer();
3、将资源的请求uri地址映射为文件的存放路径
	//事件监听
	server.on('request',(req,res) => {
		//得到请求的uri
		let pathname = req.url;
		pathname = pathname === '/' ? '/index.html' : pathname;
		if(pathname !== '/favicon.ico'){
		`	//请求静态地址
			let filepath = path.join(__dirname, 'public', pathname)
		}
	})
4、读取文件内容并相应给客户端
	fs.readFile(filepath,(err, data) => {
		if(err){
			res.statusCode = 500;
			res.end('服务器内部错误');
		}else{
			res.end(data);
		}
	})

一个综合的案例:(完成下面的代码,就可以搭建出一个静态服务器,用Nodejs运行下面代码,在打开页面时在IP后加对应的端口号即可)

const { join, extname } = require('path');	// 导入path 处理路径的模块
const fs = require('fs');					// 导入fs 文件模块
const http = require('http');				// 导入http web模块
const { createGzip } = require('zlib');		// 导入zlib 压缩模块
const mimes = require('./libs/mime');		// 关于扩展名的一个类型对象(第三方也有)

// 网站根目录  url地址中的 / => www目录
const webRoot = join(__dirname, 'www');
// req 是对象也是流 res是对象也是流
http.createServer((req, res) => {
  if (req.url != '/favicon.ico') {
    // 得到客户请求的pathname,并且做了缺省页的处理
    let pathname = req.url === '/' ? '/index.html' : req.url
    // 得到请求的对象服务器中真实的文件路径
    let filepath = join(webRoot, pathname)
    // 判断文件是否存在,如果不存在则返回404
    if (fs.existsSync(filepath)) { // 文件存在

      // 得到请求文件的扩展名
      const ext = extname(filepath).slice(1)
      // 得到扩展名
      const mime = mimes[ext]

      // 设置响应头
      res.setHeader('content-type', mime)
      res.setHeader('Content-Encoding', 'gzip')
      // 读取文件并给响应
      // res.end(fs.readFileSync(filepath))
      fs.createReadStream(filepath).pipe(createGzip()).pipe(res)
    } else {
      res.statusCode = 404
      res.end('not found')
    }
  }

}).listen(3000, '0.0.0.0')

1.5、get数据获取

get数据通过地址栏使用query方式进行传递的数据 例?id=1&name=zhangsan

const http = require('http');
const url = require('url');
http.createServer((req, res) => {
	// 获取地址栏中 query数据
	let { query } = url.parse(req.url, true);
	console.log(query);
}).listen(8080)
const { join, extname } = require('path')
const fs = require('fs')
const http = require('http')
const { createGzip } = require('zlib')
const url = require('url')
const mimes = require('./libs/mime')

// 网站根目录  url地址中的 / => www目录
const webRoot = join(__dirname, 'www')

// get数据获取
http.createServer((req, res) => {
  if (req.url != '/favicon.ico') {
    // query,就是浏览器访问的get参数集合
    let { pathname, query } = url.parse(req.url, true)
    pathname = pathname === '/' ? '/index.html' : pathname
    // 得到请求的对象服务器中真实的文件路径
    let filepath = join(webRoot, pathname)

    if (fs.existsSync(filepath)) {
      // 得到请求文件的扩展名
      const ext = extname(filepath).slice(1)
      // 得到扩展名
      const mime = mimes[ext]
      // 设置响应头
      res.setHeader('content-type', mime)
      // res.setHeader('Content-Encoding', 'gzip')
      let html;
      if ('html' === ext) {
        html = fs.readFileSync(filepath, 'utf-8');
        // html = html.replace('{{id}}', query.id)
        html = html.replace(/\{\{\s*(\w+)\s*\}\}/g, (preg,match) => {
          return query[match]
        })
      } else {
        html = fs.readFileSync(filepath)
      }
      res.end(html)
      // fs.createReadStream(filepath).pipe(createGzip()).pipe(res)
    } else {
      res.statusCode = 404
      res.end('not found')
    }
  }

}).listen(3000, '0.0.0.0')

1.6、post数据获取

表单数据多数为post进行提交到服务器端。

const http = require('http');
const queryString = require('querystring');
http.createServer((req, res) => {
let data = '';
 // 数据接受中
req.on('data', res => {
data += res
 });
// 数据传输结束了  接受到的所有数据
req.on('end', () => {
	// post数据
	let post = queryString.parse(data)
	console.log(post);
});
}).listen(8080)

let postData = []
// 有数据流入
req.on('data', buffer => postData.push(buffer))
// 接受完毕
req.on('end', () => {
	console.log(qs.parse(Buffer.concat(postData).toString()));
	res.end('ok')
})
const { join, extname } = require('path')
const fs = require('fs')
const http = require('http')
const { createGzip } = require('zlib')
const url = require('url')
const qs = require('querystring')
const mimes = require('./libs/mime')

// 网站根目录  url地址中的 / => www目录
const webRoot = join(__dirname, 'www')

// get数据获取
http.createServer((req, res) => {
  let { pathname, query } = url.parse(req.url, true)
  // post处理
  if (req.method === 'POST') {
    // 路由 post登录处理  流
    if (pathname == '/login') {
      // application/x-www-form-urlencoded
      /* let postData = ''
      // 有数据流入
      req.on('data', chunk => postData += chunk)
      // 接受完毕
      req.on('end', () => {
        console.log(qs.parse(postData));
        res.end('ok')
      }) */

      let postData = []
      // 有数据流入
      req.on('data', buffer => postData.push(buffer))
      // 接受完毕
      req.on('end', () => {
        console.log(qs.parse(Buffer.concat(postData).toString()));
        res.end('ok')
      })   
    }
  } else {
    if (req.url != '/favicon.ico') {
      // query,就是浏览器访问的get参数集合
      pathname = pathname === '/' ? '/index.html' : pathname
      // 得到请求的对象服务器中真实的文件路径
      let filepath = join(webRoot, pathname)

      if (fs.existsSync(filepath)) {
        // 得到请求文件的扩展名
        const ext = extname(filepath).slice(1)
        // 得到扩展名
        const mime = mimes[ext]
        // 设置响应头
        res.setHeader('content-type', mime)
        // res.setHeader('Content-Encoding', 'gzip')
        let html;
        if ('html' === ext) {
          html = fs.readFileSync(filepath, 'utf-8');
          // html = html.replace('{{id}}', query.id)
          html = html.replace(/\{\{\s*(\w+)\s*\}\}/g, (preg, match) => {
            return query[match]
          })
        } else {
          html = fs.readFileSync(filepath)
        }
        res.end(html)
        // fs.createReadStream(filepath).pipe(createGzip()).pipe(res)
      } else {
        res.statusCode = 404
        res.end('not found')
      }
    }
  }
}).listen(3000, '0.0.0.0')

1.7、文件上传

异步文件上传,通过文件流方式进行文件上传
html5中提供一个js的api方法 ,得到文件域中的对象 File

const { join, extname } = require('path')
const fs = require('fs')
const http = require('http')
const { createGzip } = require('zlib')
const url = require('url')
const qs = require('querystring')
const mimes = require('./libs/mime')

// 网站根目录  url地址中的 / => www目录
const webRoot = join(__dirname, 'www')

// get数据获取
http.createServer((req, res) => {
  let { pathname, query } = url.parse(req.url, true)
  // post处理
  if (req.method === 'POST') {
    // 路由 post登录处理  流
    if (pathname == '/upload') {
      // 文件名称
      let filename = Date.now() + extname(req.headers.filename);
      // 实现文件上传
      req.pipe(fs.createWriteStream(join(webRoot, 'uploads', filename)))
      res.end(JSON.stringify({ code: 0, url: 'http://localhost:3000/uploads/' + filename }))
    }

  } else {
    if (req.url != '/favicon.ico') {
      // query,就是浏览器访问的get参数集合
      pathname = pathname === '/' ? '/index.html' : pathname
      // 得到请求的对象服务器中真实的文件路径
      let filepath = join(webRoot, pathname)

      if (fs.existsSync(filepath)) {
        // 得到请求文件的扩展名
        const ext = extname(filepath).slice(1)
        // 得到扩展名
        const mime = mimes[ext]
        // 设置响应头
        res.setHeader('content-type', mime)
        // res.setHeader('Content-Encoding', 'gzip')
        let html;
        if ('html' === ext) {
          html = fs.readFileSync(filepath, 'utf-8');
          // html = html.replace('{{id}}', query.id)
          html = html.replace(/\{\{\s*(\w+)\s*\}\}/g, (preg, match) => {
            return query[match]
          })
        } else {
          html = fs.readFileSync(filepath)
        }
        res.end(html)
        // fs.createReadStream(filepath).pipe(createGzip()).pipe(res)
      } else {
        res.statusCode = 404
        res.end('not found')
      }
    }
  }
}).listen(3000, '0.0.0.0')
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div>
    <img src="" alt="" id="pic">
  </div>
  <div>
    <input type="file" id="file">
  </div>


  <script>
    const fileDom = document.querySelector('#file')
    // 监听文件域表单项改变事件,如果有改变则触发,从而得到一个File对象
    fileDom.onchange = function () {
      // 文件对象
      const file = fileDom.files[0];
      // html5提供一个api
      const fileReader = new FileReader()
      // 把得到的File对象转为流  stream可以通过ajax发送
      fileReader.readAsArrayBuffer(file)
      fileReader.onloadend = function () {
        const xhr = new XMLHttpRequest;
        xhr.open('POST', '/upload', true);
        xhr.setRequestHeader('filename', file.name)
        xhr.send(fileReader.result)
        xhr.onreadystatechange = function () {
          if (xhr.readyState === 4 && xhr.status === 200) {
            let json = eval('(' + xhr.responseText + ')')
            pic.src = json.url
          }
        }
      }
    }

  </script>
</body>

</html>

1.8、路由

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

http.createServer( function ( req, res ) {

  switch ( req.url ) {
    case '/home':
      res.write('home')
      res.end()
      break
    case '/mine':
      res.write('mine')
      res.end()
      break
    case '/login': 
      fs.readFile( './static/login.html',function ( error , data ) {
        if ( error ) throw error  
        res.write( data )
        res.end()
      })
      break
    case '/fulian.jpg':
      fs.readFile( './static/fulian.jpg', 'binary', function( error , data ) {
        if( error ) throw error 
        res.write( data, 'binary' )
        res.end()
      })
      break
    default: 
      break
   }

 }).listen( 3000, '0.0.0.0', function () {
   console.log( '服务器运行在: http://localhost:3000' )
 })

1.9、跨域问题解决

 同源策略
是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
如果两个 URL 的 协议、端口和主机地址都相同的话,则这两个 URL 是同源
 为什么浏览器不支持跨域?
跨域的访问会带来许多安全性的问题,比如,cookie一般用于状态控制,常用于存储登录的信息,如果允许跨域访问,那么别的网站只需要一段脚本就可以获取你的cookie,从而冒充你的身份去登录网站,造成非常大的安全问题,因此,现代浏览器均推行同源策略。

1.9.1、jsonp

jsonp不是ajax,它就是一个网络请求,利用 script标签中的src属性可以跨域,然后了务器端返回要执行函数

客户端
<script>
function 函数(){}
</script>
<script src=’http://xxx.com:3000/?cb=函数名’></script>
服务器
返回这个函数

1.9.2、cors

html5提出的解决跨域的方案,通过给响应头设置信息,让浏览器允许跨域。

//服务端
  // 通过设置响应头信息,来允许ajax跨域请求
  // * 表示允许所有域名来跨域请求 
  // * 如果你所写为 * 则cookie将无法跨域
  if (allowDomain.includes(req.headers.origin)) {
    // res.setHeader('Access-Control-Allow-Origin', 'http://localhost:4000')
    res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
    res.setHeader('Access-Control-Allow-Credentials', 'true')
    res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS')
    res.setHeader('Access-Control-Allow-Headers', 'username')
    res.setHeader('Access-Control-Expose-Headers', 'uuuu')
  }
  res.setHeader('content-type', 'application/json;charset=utf-8')
  res.setHeader('uuuu', 'admin888')
  res.end(jsonFn(user))
//客户端
<script>
const xhr = new XMLHttpRequest()
document.cookie = 'name=abc;path=/'
xhr.open('put', 'http://localhost:3000/', true)
xhr.withCredentials = true;
xhr.setRequestHeader('username', 'aaa')
xhr.send()
xhr.onreadystatechange = () => {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log(xhr.getResponseHeader('uuuu'))
    console.log(xhr.responseText)
  }
}
</script>

1.9.3、服务器代理

跨域它是浏览的同源策略导致,但是服务器对服务器通信,是没有跨域限制

中间代理服务器的配置:

// 接口服务器,没有静态资源
const http = require('http')
// 代理
const { createProxyMiddleware: proxy } = require('http-proxy-middleware')

const server = http.createServer((req, res) => {
  // cors
  res.setHeader('Access-Control-Allow-Origin', req.headers.origin)

  let proxyFn = proxy('/api', {
    // 请求的目标地址
    target: 'http://localhost:5000',
    // 取消客户请求过来的前缀
    pathRewrite: {
      '^/api': ''
    },
    // 修改代理请求到的服务器中的请求头中Host字段为本机字段
    changeOrigin: true
  })
  proxyFn(req, res)

  /* let proxyFn2 = proxy('/aaa', {
    // 请求的目标地址
    target: 'http://localhost:6000',
    // 取消客户请求过来的前缀
    pathRewrite: {
      '^/aaa': ''
    }
  })
  proxyFn2(req, res) */

  /* if ('/home' === req.url) {
    res.end(JSON.stringify({ code: 0, msg: '4000接口', data: { title: 'aaaa' } }))
    return;
  } */
}).listen(4000, '0.0.0.0')

1.9.4、postMessage

<!-- a.html  3000-->
<iframe src="http://localhost:4000/" frameborder="0" id="frame" onload="load()"></iframe>
<script>
  function load() {
    let frame = document.getElementById('frame')
    frame.contentWindow.postMessage('aaa', 'http://localhost:4000')
  }
  window.onmessage = function (e) {
    console.log(e.data)
  }

</script>
<!-- b.html  4000-->
<script>
  window.onmessage = function (e) {
    console.log(e.data)
    e.source.postMessage('bbb', e.origin)
  }
</script>

1.10、Node数据获取(爬虫)

纯node获取:

const https = require('https')
// 数据抓取
https.get('https://news.ke.com/bj/baike/0725210.html', res => {
  let html = ''
  res.on('data', chunk => html += chunk)
  res.on('end', () => {
    const preg = /<title>(.*)<\/title>/i
    let arr = html.match(preg)
    console.log(arr[1]);
  })
})

使用第三方插件在nodejs中使用像jquery选择器一样的功能:

安装:
npm i -S cheerio
const https = require('https')
const http = require('http')
const cheerio = require('cheerio')
const path = require('path')
const fs = require('fs')

const dirpath = path.join(__dirname, 'pics')

// 数据抓取
http.get('http://www.jj20.com/bz/nxxz/', res => {
  let html = ''
  res.on('data', chunk => html += chunk)
  res.on('end', () => {
    // 把当前html内容,使用cheerio来完成选择加载s
    const $ = cheerio.load(html)
    const imgs = $('.g-box-1200 > .picbz > li > a > img')
    imgs.each((index, el) => {
      let src = $(el).attr('src')
      if (src) {
        http.get(src, ret => {
          let name = path.basename(src)
          ret.pipe(fs.createWriteStream(dirpath + '/' + name))
        })
      }
    })
  })
})




  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值