node搭建的一個應用在前端專案中的可切換介面的代理伺服器

Emine發表於2018-09-21

web專案的編寫過程當中,常常會出現前後端進度不一致的情況,就像談戀愛的兩個人如果步調不一致,那麼肯定會很累,更有甚者因節奏的不一樣導致分手的下場,所以為了避免前後端走到“分手”的這一步,我們需要把前後端解耦開來,可是對於前端來說,你讓我自己寫假資料行啊,可是模擬不到網路請求這一步,那這個mock的假資料真是“食之無味,棄之可惜”,於是,

Node: "對面的女孩看過來,看過來,看過來~這裡的哥哥有點帥"

node挺身而出,幫我們用三兩句話就搭建起了一個代理伺服器,於是乎,前端:“麵包(資料)我有了,陽光(網路請求)我有了,你(後端)最後來給我愛情就好~”,啊,說了一堆廢話,還是來看看我們的可切換源資料的代理node伺服器是長啥樣吧~~

/**
 * @author luojun <https://github.com/Luooojunnn>
 * @desc 本中間伺服器可以切換資料來源,如需二次改造,請github上提問
*/
const http = require('http')
const url = require('url')
const path = require('path')
const fs = require('fs')
const colors = require('colors')

/**
 * 環境判斷
*/
const ENV = process.argv[2] && process.argv[2].split('=')[1]

http.createServer((req, res) => {
  try {
    /**
     * 將讀取api檔案寫在服務程式內,實現動態更新
     * 遇到一個坑,就是node跑在gw根目錄導致這裡讀取檔案採用相對路徑就會讀不到東西
    */
    let apiFile = JSON.parse(fs.readFileSync(`${path.join(__dirname, '../../data/apiManage.json')}`))
    if (url.parse(req.url).pathname !== '/favicon.ico') {
      // 判斷請求方式,請求引數
      let reqMethod = req.method === 'GET' ? 'GET' : (req.method === 'POST' ? 'POST' : req.method)

      // 定址回值
      if (apiFile[url.parse(req.url, true).pathname.substring(1)]) {
        res.setHeader('Access-Control-Allow-Origin', '*')
        res.setHeader('Allow', '*')
        res.setHeader('Access-Control-Allow-Headers', '*')

        if (reqMethod === 'OPTIONS') {
          res.end('')
        }
        // 根據環境變數選擇介面
        let apiAdress = apiFile[url.parse(req.url, true).pathname.substring(1)][ENV]
        // TODO 根據環境判斷是夠需要一個代理轉發
        if (ENV === 'dev') {
          console.log('在dev環境'.red)
          // 儲存請求引數,用於轉發
          let paramsData = ''
          if (reqMethod === 'GET') {
            paramsData = url.parse(req.url, true).search
          } else if (reqMethod === 'POST') {
            let res = ''
            req.on('data', (reqData) => {
              res += reqData
            })
            req.on('end', (reqData) => {
              paramsData = res
              console.log(paramsData)
            })
          }
          console.log(`介面名 ${url.parse(req.url, true).pathname},採用 ${reqMethod} 請求方式,傳遞的引數是 ${paramsData}`)

          let finalAddress = path.join(__dirname, '../../data/', apiAdress)
          // dev環境 - 讀取介面,輸出json
          let apiData = fs.readFileSync(finalAddress)
          res.end(apiData.toString('utf8'))
        } else {
          console.log('在測試環境'.red)
          // online環境 - 代理轉發 PS: hostname 不含協議
          let hn = url.parse(apiAdress).hostname
          let pt = url.parse(apiAdress).path
          req.headers.host = hn //url.parse(apiAdress).host
          let sRes = res
          if (reqMethod === 'GET') {
            if (url.parse(req.url, true).search) {
              pt = '' + pt + url.parse(req.url, true).search
            }
            console.log(`介面名 ${url.parse(req.url, true).pathname},採用 ${reqMethod} 請求方式,傳遞的引數是 ${url.parse(req.url, true).search}`)
            HCLIENTFC({
              hostname: hn + '',
              path: pt + '',
              method: reqMethod + '',
              headers: req.headers
            }, (data, statusCode, header) => {
              sRes.writeHead(Number(statusCode), header)
              sRes.end(data)
            }, '')
          } else if (reqMethod === 'POST') {
            let res = ''
            req.on('data', (reqData) => {
              res += reqData
            })
            req.on('end', (reqData) => {
              console.log(`介面名 ${url.parse(req.url, true).pathname},採用 ${reqMethod} 請求方式,傳遞的引數是 ${res}`)
              /**
               * 將 host 頭部資訊改成online的域名資訊,否則host會指向程式碼裡的請求的localhost:9000
               * !!! 為什麼一定要用 header ,因為可以在做爬蟲的時候,冒充瀏覽器端,越過有些程式碼的機器人識別
               * */
              HCLIENTFC({
                hostname: hn + '',
                path: pt + '',
                method: reqMethod + '',
                headers: req.headers
              }, (data, statusCode, header) => {
                sRes.writeHead(Number(statusCode), header)
                sRes.end(data)
              }, res)
            })
          }
        }
      } else {
        res.writeHead(404)
        res.end('')
      }
    }
  } catch (e) {
    res.end('介面解析出現了一些錯誤...')
    throw new Error(e)
  }
}).listen(9000, 'localhost', () => {
  console.log('介面服務啟動在localhost:9000')
})

/**
 * online伺服器
 * @param {Object} obj - 需要參入的配置引數
 * @param {string} obj.hostname - 目標地址ip或域名
 * @param {string} obj.path - 請求路徑 (這裡忘寫導致出問題)
 * @param {string} obj.method - 請求方法
 * @param {Object} obj.headers - 請求頭
 * @param {function} callback - 回撥
 * @param {string} params - 轉發引數
*/
function HCLIENTFC ({ hostname, path = '/', method = 'GET', headers }, callback = () => { }, v = '') {
  let resData = ''
  console.log('介面的資訊引數:========1'.blue)
  console.log('hostname:' + hostname)
  console.log('path:' + path)
  console.log('method:' + method)
  console.log(headers)
  console.log('post傳送的資料v:' + v)
  console.log('介面的資訊引數:--------2'.blue)
  /**
   * 由於傳送了header,其accept接受的資料型別可能和返回的資料型別不一致,所以可能導致介面超時,必要時,可去掉髮送header
  */
  const HCLIENT = http.request({
    hostname,
    path,
    method,
    headers
  }, (res) => {
    res.setEncoding('utf8')
    res.on('data', (chunk) => {
      resData += chunk
    })
    res.on('end', () => {
      console.log('介面返回結果是:', resData.toString())
      console.log('測試環境返回狀態碼:', res.statusCode)
      console.log('測試環境返回響應頭:', res.headers)
      callback(resData, res.statusCode, res.headers)
    })
  })
  HCLIENT.on('error', (e) => {
    console.log('出現錯誤,錯誤資訊為:' + e)
    return resData
  })
  HCLIENT.write(v)
  HCLIENT.end()
}


複製程式碼

node:"陽光大熊貨前來報到!!"

有啥不清楚的可以留言大家一起討論,共同學習進步!!sir!

git地址:一些好玩的Node

PS: 之前關於get、post請求引數的獲取判斷有誤,線已改正(180927)

相關文章