天天看點

Node之HTTP子產品(request對象解析、response對象解析、圖檔上傳)一、什麼是Web伺服器?二、request對象解析三、response 對象解析四、發送網絡請求五、使用原生http子產品實作檔案上傳六、進階express

文章目錄

  • 一、什麼是Web伺服器?
    • 1.1 web伺服器初體驗
    • 1.2 另外一種建立方式
    • 1.3 listen方法的參數詳解
  • 二、request對象解析
    • 2.1 基本使用
    • 2.2 進階使用
    • 2.3 method 應用
    • 2.4 headers 屬性
      • 2.4.1 content-type
      • 2.4.2 content-length
      • 2.4.3 keep-alive
      • 2.4.4 accept-encoding
      • 2.4.5 accept
      • 2.4.6 user-agent
  • 三、response 對象解析
    • 3.1 設定響應體
    • 3.2 設定響應狀态碼
    • 3.3 設定響應頭
  • 四、發送網絡請求
    • 4.1 http發送GET請求
    • 4.2 http發送POST請求
  • 五、使用原生http子產品實作檔案上傳
  • 六、進階express
    • 6.1 解析query 和 params
    • 6.2 十分好用的工具中間件推薦

一、什麼是Web伺服器?

當應用程式(用戶端)需要某一個資源時,可以向一台伺服器,通過Http請求擷取到這個資源;提供資源的這個伺服器,就是一個Web伺服器。
Node之HTTP子產品(request對象解析、response對象解析、圖檔上傳)一、什麼是Web伺服器?二、request對象解析三、response 對象解析四、發送網絡請求五、使用原生http子產品實作檔案上傳六、進階express

目前有很多開源的Web伺服器:Nginx、Apache(靜态)、Apache Tomcat(靜态、動态)、Node.js

1.1 web伺服器初體驗

const http = require('http')

// 1.建立一個web伺服器
const server = http.createServer((req, res) => {
  // res.write('heelo world)
  // res.close()  下面方法是上述兩個方法的簡寫
  res.end('hello world')

})

// 2.啟動伺服器 并指定端口号和主機
// port:0-65535,
// hostname:主機 ['localhost','127.0.0.1','0.0.0.0']
server.listen(8080,'0.0.0.0',()=>{
  console.log('server start');
})
           

1.2 另外一種建立方式

const http = require('http')

const server2 = new http.Server((req, res) => {
  res.end('server 2')
})
server2.listen(8002,()=>{
  console.log('server2啟動成功');
})
           

本質上是同一種,源碼層面都是去

new

Server

這個類。

Node源碼:

Node之HTTP子產品(request對象解析、response對象解析、圖檔上傳)一、什麼是Web伺服器?二、request對象解析三、response 對象解析四、發送網絡請求五、使用原生http子產品實作檔案上傳六、進階express

1.3 listen方法的參數詳解

官網直達:http://nodejs.cn/api/net.html#net_server_listen_port_host_backlog_callback

listen函數有三個參數:

  • 端口port: 可以不傳, 系統會預設配置設定端, 後續項目中我們會寫入到環境變量中;
    • 如果 port 省略或是 0,系統會随意配置設定
  • 主機host: 通常可以傳入

    localhost

    、ip位址

    127.0.0.1

    、或者ip位址

    0.0.0.0

    ,預設是0.0.0.0;
    • localhost:本質上是一個域名,通常情況下會被解析成127.0.0.1;
    • 127.0.0.1:回環位址(Loop Back Address),表達的意思其實是我們主機自己發出去的包,直接被自己接收;
      • 正常的資料庫包經常 應用層 - 傳輸層 - 網絡層 - 資料鍊路層 - 實體層 ;
      • 而回環位址,是在網絡層直接就被擷取到了,是不會經常資料鍊路層和實體層的;
      • 比如我們監聽 127.0.0.1時,在同一個網段下的主機中,通過ip位址是不能通路的;
    • 0.0.0.0:
      • 監聽IPV4上所有的位址,再根據端口找到不同的應用程式;
      • 比如我們監聽 0.0.0.0時,在同一個網段下的主機中,通過ip位址是可以通路的;
  • 回調函數:伺服器啟動成功時的回調函數
const http = require('http')

const server = new http.Server((req, res) => {
  res.end('hello world!')
})

// port:不寫動态生成,const result = server.address().port 可以拿到
// address:不寫預設是 0.0.0.0
server.listen(8000,() => {
  console.log('伺服器啟動成功');

  // 拿到伺服器資訊
  const result = server.address()
  console.log('result: ', result);
})
           

二、request對象解析

2.1 基本使用

request 對象中常用的有三個屬性,分别是:

req.url

req.method

req.headers

const http = require('http')

const server = http.Server((req,res)=>{
  // request對象中封裝了用戶端給我們伺服器傳遞過來的所有資訊
  console.log('req.url: ', req.url);
  console.log('req.method: ', req.method);
  console.log('req.headers: ', req.headers);
  
  res.end('hello world!')
})

server.listen(8000,()=>{
  console.log('server start');
})
           

output:

server start
req.url:  /login?username=sir&password=123
req.method:  POST
req.headers:  {
  'user-agent': 'PostmanRuntime/7.26.10',
  accept: '*/*',
  host: 'localhost:8000',
  'accept-encoding': 'gzip, deflate, br',
  connection: 'keep-alive',
  cookie: 'NMTID=00OtVDuIBEAhUMivkXAgeFb15wM0PUAAAF4kDtcOQ',
  'content-length': '0'
}

           

2.2 進階使用

可以發現上述使用方法在處理我們的請求參數時不是很友善,node為我們提供了一些内置庫,幫助我們提取出url和query資訊。
const http = require('http')
const url = require('url')
const qs = require('querystring')

const server = http.Server((req, res) => {

  // 1.基本使用
  // if(req.url === '/login'){
  //   res.end('歡飲回來')
  // }else if(req.url === '/users'){
  //   res.end('使用者清單~')
  // }else{
  //   res.end('錯誤請求,請檢查')
  // }

  console.log(req.url);

  // 2.進階方式使用
  // 通過内置的庫 url 解析出我們需要的 包含在url裡的資訊
  const ret = url.parse(req.url)
  console.log('ret: ', ret);

  const { pathname, query } = url.parse(req.url)
  if (pathname === '/login') {
    console.log('query: ', query);

    // 通過内置庫 querystring 解析出query裡我們需要的資料
    // query => username=sir&password=123
    const { username, password } = qs.parse(query)
    console.log('username: ', username);
    console.log('password: ', password);
  }

  res.end('登入成功!')
})

server.listen(8000, () => {
  console.log('server start');
})
           

output:

server start
/login?username=sir&password=123
ret:  Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?username=sir&password=123',    
  query: 'username=sir&password=123',      
  pathname: '/login',
  path: '/login?username=sir&password=123',
  href: '/login?username=sir&password=123' 
}
query:  username=sir&password=123
username:  sir
password:  123
           

2.3 method 應用

const http = require('http')
const url = require('url')

const server = http.Server((req, res) => {

  const { pathname } = url.parse(req.url)

  if (pathname === '/login') {
    // 拿到body中的資料

    // 1. 拿到的是字元串(手動設定編碼)
    req.setEncoding('utf-8')

    req.on('data', (data) => {
      console.log('data: ', data);
      console.log('typeof data: ', typeof data); // string
      // 2. 拿到的是 Buffer 調用 toString解碼
      // console.log('data: ', data.toString());

      const { username, password } = JSON.parse(data)
      console.log('username: ', username);
      console.log('password: ', password);
    })

    console.log('req.method: ', req.method);
    res.end('hello server')
  }
})

server.listen(8000, () => {
  console.log('server start');
})
           

output:

server start
req.method:  POST
data:  {
    "username":"foo",
    "password":"8888"
}
typeof data:  string
username:  foo
password:  8888
           

2.4 headers 屬性

在request對象的header中也包含很多有用的資訊,用戶端會預設傳遞過來一些資訊:

Node之HTTP子產品(request對象解析、response對象解析、圖檔上傳)一、什麼是Web伺服器?二、request對象解析三、response 對象解析四、發送網絡請求五、使用原生http子產品實作檔案上傳六、進階express
詞彙:accept 接受,encoding 編碼,content 内容,keep-alive 持續服務

2.4.1 content-type

content-type是這次請求攜帶的資料的類型:

  • application/json表示是一個json類型;
  • text/plain表示是文本類型;
  • application/xml表示是xml類型;
  • multipart/form-data表示是上傳檔案;
  • text/html 表示伺服器傳回的是 html文檔。

2.4.2 content-length

content-length:檔案的大小和長度

2.4.3 keep-alive

keep-alive:

  • http是基于TCP協定的,但是通常在進行一次請求和響應結束後會立刻中斷;
  • 在http1.0中,如果想要繼續保持連接配接:
    • 浏覽器需要在請求頭中添加 connection: keep-alive;
    • 伺服器需要在響應頭中添加 connection:keey-alive;
    • 當用戶端再次放請求時,就會使用同一個連接配接,直至一方中斷連接配接;
  • 在http1.1中,所有連接配接預設是 connection: keep-alive的;
    • 不同的Web伺服器會有不同的保持 keep-alive的時間;
    • Node中預設是5s

2.4.4 accept-encoding

accept-encoding:告知伺服器,用戶端支援的檔案壓縮格式,比如js檔案可以使用gzip編碼,對應 .gz檔案;

2.4.5 accept

accept:告知伺服器,用戶端可接受檔案的格式類型;

2.4.6 user-agent

user-agent:用戶端相關的資訊;

Node之HTTP子產品(request對象解析、response對象解析、圖檔上傳)一、什麼是Web伺服器?二、request對象解析三、response 對象解析四、發送網絡請求五、使用原生http子產品實作檔案上傳六、進階express

三、response 對象解析

3.1 設定響應體

如果我們希望給用戶端響應結果資料,可以通過兩種方式:

  • write方法:這種方式是直接寫出資料,但是并沒有關閉流;
  • end方法:這種方式是寫出最後的資料,并且寫出後會關閉流;
    Node之HTTP子產品(request對象解析、response對象解析、圖檔上傳)一、什麼是Web伺服器?二、request對象解析三、response 對象解析四、發送網絡請求五、使用原生http子產品實作檔案上傳六、進階express

    如果我們沒有調用 end,用戶端将會一直等待結果:

    是以用戶端在發送網絡請求時,都會設定逾時時間。

3.2 設定響應狀态碼

設定狀态碼常見的有兩種方式:

res.statusCode = 200

res.writeHead(200)
           
Node之HTTP子產品(request對象解析、response對象解析、圖檔上傳)一、什麼是Web伺服器?二、request對象解析三、response 對象解析四、發送網絡請求五、使用原生http子產品實作檔案上傳六、進階express

3.3 設定響應頭

  • res.setHeader("key","value")

  • res.writeHead({"key":"value"})

示例:

const http = require('http')

// 1.建立一個web伺服器
const server = http.createServer((req, res) => {
  // 設定響應頭
  // 1.setHeader
  // res.setHeader('Content-Type','text/plain;charset=utf8')
  // res.setHeader('Content-Type','text/html;charset=utf8')

  // 2.writeHead
  res.writeHead(200, {
    // "Content-Type": "application/json;charset=utf8",
    "Content-Type": "text/html;charset=utf8",
  })
  res.end('<h1>Hello,world!</h1>')
})

// 2.啟動伺服器 并指定端口号和主機
server.listen(8000, '0.0.0.0', () => {
  console.log('server start');
})
           

四、發送網絡請求

4.1 http發送GET請求

const http = require('http')

// http發送get請求
http.get('http://localhost:8000', (res) => {
  res.setEncoding('utf-8')
  res.on('data', (data) => {
    // 預設是 Buffer
    // console.log(data.toString());
    console.log(data);
  })

  res.on('end', () => {
    console.log('傳輸完成');
  })
})
           

4.2 http發送POST請求

const http = require('http')

// http發送post請求
const request = http.request({
  method: 'POST',
  hostname: 'localhost',
  port: '8000'
}, res => {
  res.setEncoding('utf-8')
  res.on('data', (data) => {
    // 預設是 Buffer
    // console.log(data.toString());
    console.log(data);
  })
  res.on('end', () => {
    console.log('傳輸完成');
  })
})
// 需要調用該方法表示 POST請求已經配置完成,可以發送了
request.end()
           

五、使用原生http子產品實作檔案上傳

準備:

  1. 準備

    postman

    發送一個post請求,請求體是一個

    form-data

    類型的表單資料,檔案類型選擇file,上傳一張圖檔。

注意: 不能直接把拿到的資料,寫入到檔案中,因為拿到的資料包含請求頭,檔案名稱大小等資料,不是純粹的圖檔二進制資料, 我需要我們人工拆解出真實的二進制圖檔資料。

下圖中圈出來的才是圖檔的真實 二進制資料。

Node之HTTP子產品(request對象解析、response對象解析、圖檔上傳)一、什麼是Web伺服器?二、request對象解析三、response 對象解析四、發送網絡請求五、使用原生http子產品實作檔案上傳六、進階express

server.js

const http = require('http')
const url = require('url')
const fs = require('fs')
const qs = require('querystring')

const server = http.createServer((req, res) => {
  // 解析該url,解構出我們需要的資料
  const { pathname } = url.parse(req.url)

  if (pathname === '/upload') {
    if (req.method === 'POST') {
      // 預設是utf8編碼,必須要先設定編碼  圖檔資料是二進制編碼
      req.setEncoding('binary')

      let body = ''
      const boundary = req.headers['content-type'].split(';')[1].replace(' boundary=', '')
      console.log('boundary: ', boundary);

      req.on('data', (data) => {
        // data是Buffer 相加的時候會自動轉化成字元串再相加
        body += data
        // console.log(data);
      })

      req.on('end', () => {
        // console.log('body: ', body);

        // 處理資料
        // 1.擷取image/png的位置
        const payload = qs.parse(body, '\r\n', ': ')
        console.log(payload['Content-Type']);
        const type = payload['Content-Type']

        // 2.開始再 image/png的位置進行截取
        const typeIndex = body.indexOf(type)
        const typeLength = type.length
        let imageData = body.substring(typeIndex + typeLength)

        // 3.将中間的兩個空格去掉
        imageData = imageData.replace(/^\s\s*/, '')

        // 4.将最後的 boundary去除掉
        imageData = imageData.substring(0, imageData.indexOf(`--${boundary}--`))

        // 5.寫入到檔案
        fs.writeFile('./bar.png', imageData, { encoding: 'binary' }, (err) => {
          console.log(err);
          console.log('檔案上傳成功!');
          res.end('檔案上傳成功!')
        })
      })
    } else {
      res.statusCode = '403'
      res.end('請求方法不正确')
    }
  } else {
    res.end('ok')
  }
})

server.listen(8000, () => {
  console.log('server start');
})
           

六、進階express

6.1 解析query 和 params

假如現在我們将接收如下兩個請求,該怎麼拿到附帶在請求中的資料呢?

// params方式
http://localhost:3000/products/123/why

// query方式 
http://localhost:3000/login?username=why&pasword=123
           

其實在

express

中非常簡單,

req.params

可以拿到第一種方式的請求資料,

req.query

可以拿到第二種。

示例:

const express = require('express')
const app = express()

// params方式
// 請求:http://localhost:3000/products/123/why
app.get('/products/:id/:name', (req, res, next) => {
  console.log('req.params: ', req.params);
  // req.params:  { id: '123', name: 'why' }
  res.send('商品的詳情資料!')
})

// query方式 
// 請求 http://localhost:3000/login?username=why&pasword=123
app.get('/login', (req, res, next) => {
  console.log('req.query: ', req.query);
  // req.query:  { username: 'why', pasword: '123' }

  console.log("登入成功!");
  res.end('登入成功!')
})
app.listen(3000, () => console.log(`Example app listening on 3000 port!`))
           

6.2 十分好用的工具中間件推薦

本章主要講解以下内容:自動解析

application/json

,自動解析

x-www-form-urlencoded

,自動解析

form-data

假如現在前端給我們發送了一個

application/json

類型的資料,如下:

{
  "username":"test",
  "password":"12346"
}
           

express

中我們如何拿到該資料呢?

開始測試:

Postman:

http://localhost:8080/login

Node之HTTP子產品(request對象解析、response對象解析、圖檔上傳)一、什麼是Web伺服器?二、request對象解析三、response 對象解析四、發送網絡請求五、使用原生http子產品實作檔案上傳六、進階express

普通做法:

const express = require('express')

const app = express()

app.post('/login', (req, res, next) => {
  req.on('data', data => {
    const result = JSON.parse(data.toString())
    console.log('result: ', result);
    // result:  { username: 'test', password: '12346' }
  })
  req.on('end', () => {
    res.end('welcome back')
  })
})

app.listen(8080, () => {
  console.log('server start');
})
           

進階做法:

const express = require('express')

const app = express()

// 隻解析 application/josn 成對象形式資料,儲存到 req.body 中
app.use((req, res, next) => {
  if (req.headers["content-type"] === 'application/json') {
    req.on('data', data => {
      const info = JSON.parse(data.toString())
      req.body = info
    })
    req.on('end', () => {
      next()
    })
  } else {
    next()
  }
})

app.post('/login', (req, res, next) => {
  console.log('req.body: ', req.body);
  // req.body:  { username: 'test', password: '12346' }
  res.end('welcome back')
})

app.listen(8080, () => {
  console.log('server start');
})
           

可以發現在進階做法中,我們注冊了一個全局的中間件,并且它實作了隻監測我們的

application/json

資料的處理,解析出來的資料放到了

req.body

中。

這種方式十分的巧妙且靈活。

但是每次新項目都需要寫這樣寫一短代碼是不是有點麻煩呢?于是誕生了

body-parser

,它就是專門幹這個的。

用法示例:

const express = require('express')

const app = express()

// 1. 公用中間件 用于處了解析出我們需要的資料  
// 解析 application/josn 成對象形式資料
// app.use((req, res, next) => {
//   if (req.headers["content-type"] === 'application/json') {
//     req.on('data', data => {
//       const info = JSON.parse(data.toString())
//       req.body = info
//     })

//     req.on('end', () => {
//       next()
//     })
//   } else {
//     next()
//   }
// })

// 有一個庫專門幹這個:body-parser
// body-parser:express 3.x 内置于express架構
// body-parser:express 4.x 被分離出去
// body-parser類似功能:express 4.16.x 内置成函數

// 2. 目前express版本 4.17.1,直接使用内置的 body-parser 解析我們需要的資料
// 解析 application/json
app.use(express.json())
// 解析 x-www-form-urlencoded
// extended:true:那麼對urlencoded進行解析時,它使用的是第三方庫,qs
// extended:false:那麼對urlencoded進行解析時,使用的是Node内置的querystring
app.use(express.urlencoded({ extended: true })) 
//不加{ extended: true }抛出警告:不推薦使用body解析器


app.post('/login', (req, res, next) => {
  console.log('req.body: ', req.body);
  res.end('welcome back')
})
app.post('/products', (req, res, next) => {
  console.log('req.body: ', req.body);
  res.end('upload products info success')
})

app.listen(8080, () => {
  console.log('server start');
})
           

此外,還有一個庫

multer

,專門解析

form-data

用法示例:解析普通資料(非file)

const express = require('express')
const multer = require('multer')

const app = express()
const upload = multer()

// 解析 application/json
app.use(express.json())
// 解析 x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }))
// 解析 form-data

// upload.any() 會傳回一個函數,直接把他注冊為中間件,他會幫我們完成解析
app.post('/login', upload.any(),(req, res, next) => {
  console.log("使用者登入成功");
  res.end("使用者登入成功")
  console.log('req.body: ', req.body);
})

app.listen(8080, () => {
  console.log('form-data解析伺服器啟動成功~');
})
           

解析上傳的檔案:

const path = require('path')

const express = require('express')
const multer = require('multer')

const app = express()

// 解析 application/json
app.use(express.json())
// 解析 x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }))

// 解析 form-data
// 構造檔案上傳的配置對象
const storage = multer.diskStorage({
  // destination 目的地
  destination: (req, file, cb) => {
    cb(null, './uploads/')
  },
  filename: (req, file, cb) => {
    // file.originalname 能拿到原始檔案的名稱
    cb(null, Date.now() + path.extname(file.originalname))
  }
})

const upload = multer({
  // storage 表示自定義上傳的檔案資訊,如檔案名稱、字尾名等
  storage
})

// upload.any() 會傳回一個函數,直接把他注冊為中間件,他會幫我們解析
app.post('/login', upload.any(), (req, res, next) => {
  console.log("使用者登入成功");
  res.end("使用者登入成功")
  console.log('req.body: ', req.body);
})

// form-data 上傳檔案的中間價
// 該中間件:擷取這次上傳的檔案,并且進行儲存
// 單個檔案upload.single('key') 多個:upload.array
app.post('/upload', upload.array('file'), (req, res, next) => {
  console.log('req.body: ', req.body);

  // 拿到儲存的files
  console.log('req.files: ', req.files);
  res.end('檔案上傳成功!')
})

app.listen(8080, () => {
  console.log('form-data解析伺服器啟動成功~');
})
           

繼續閱讀