使用noode.js創建一個服務器

一、簡單的靜態服務器

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章