Node.js 學(xué)習(xí)(三): 構(gòu)建 Web 應(yīng)用(服務(wù)器端)

1. 構(gòu)建 Web 應(yīng)用(服務(wù)器端)

1.1. 基礎(chǔ)功能

對象 http.Server 的 'request' 事件發(fā)生于網(wǎng)絡(luò)連接建立,客戶端向服務(wù)器端發(fā)送報(bào)文鸯檬,服務(wù)器段解析報(bào)文颁井,發(fā)現(xiàn) HTTP 請求報(bào)文的報(bào)文頭時(shí)物咳。在已出發(fā) 'request' 事件前顺又,http 模塊已準(zhǔn)備好 IncomingMessage 和 ServerResponse 對象以對應(yīng)請求和響應(yīng)報(bào)文的操作萧福。

const http = require('http');
http.createServer( function(req, res) {    // 'request' 事件的偵聽器
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end();
}).listen(1337, '127.0.0.1');

對于一個(gè) Web 應(yīng)用除了上面的業(yè)務(wù)還有如下的需求:

  • 請求方法的判斷
  • URL 的路徑解析
  • URL 中的查詢字符串的解析
  • Cookie 和 Session 的解析
  • Basic 認(rèn)證
  • 表單數(shù)據(jù)的解析
  • 任意格式文件的上傳處理

Web 應(yīng)用可以看成是將上述需求進(jìn)行線性組合一屋,最終生成 'request' 事件的偵聽器涨薪,通過高階函數(shù)將它傳遞給 http.createServer() 方法骑素。

const app = express();
// TODO
http.createServer(app).listen(1337);

1.1.1. HTTP Parser

Node 底層使用 HTTP_Parser 這個(gè) C 語言模塊來解析 HTTP 協(xié)議數(shù)據(jù), 它解析的主要信息有:

  • 頭部字段和對應(yīng)值(Header)
  • Content-Length
  • 請求方法(Method)
  • 響應(yīng)狀態(tài)碼(Status Code)
  • 傳輸編碼
  • HTTP 版本
  • 請求 URL
  • 報(bào)文主體

1.1.2. 請求方法

HTTP_Parser 在解析請求報(bào)文時(shí)刚夺,將報(bào)文頭抽取出來并將請求方式抽象為 req.method 屬性献丑。

1.1.3. 路徑解析

url 模塊提供了 URL 的解析。URL 是由多個(gè)具有意義的字段組成的字符串光督,具體描述如下:

HTTP_Parser 將請求報(bào)文頭的路徑字段解析成名為 req.url 的 URL 字符串, 它可通過 url.parse() 方法解析成 URL 對象阳距,對象中的 urlObject.pathname 屬性反映了 URL 字符串的 path 字段中的 pathname 部分。

1.1.4. 查詢字符串

pathname 部分后就是查詢字符串结借,這部分內(nèi)容經(jīng)常需要為業(yè)務(wù)邏輯所用筐摘, Node 提供了 qureystring 模塊來處理這部分?jǐn)?shù)據(jù)。注意船老,業(yè)務(wù)的判斷一定要檢查值是數(shù)組還是字符串咖熟。

1.1.5. Cookie

HTTP 是一個(gè)無狀態(tài)協(xié)議,無法區(qū)分用戶之間的身份柳畔。如何標(biāo)識(shí)和認(rèn)證一個(gè)用戶馍管,最早的方案就是 Cookie 。

Cookie 的處理分為如下幾步:

  • 服務(wù)器向客戶端發(fā)送 Cookie
  • 瀏覽器將 Cookie 保存
  • 之后每次瀏覽器都會(huì)將 Cookie 發(fā)送給服務(wù)器端

1.1.5.1. 服務(wù)器端解析 Cookie

HTTP_Parser 會(huì)將請求報(bào)文頭的所有字段解析到 req.headers 上薪韩,Cookie 就是 req.headers.cookie 确沸。 Cookie 值的格式是鍵值對捌锭,Express 的中間件 cookie-parser 將其掛載在 req 對象上,讓業(yè)務(wù)代碼可以直接訪問罗捎。

function cookieParser (options) {
  return function cookieParser (req, res, next) {
    if (req.cookies) {
      return next()
    }
    var cookies = req.headers.cookie
    req.cookies = Object.create(null)
    // no cookies
    if (!cookies) {
      return next()
    }
    req.cookies = cookie.parse(cookies, options) // 這里調(diào)用了 cookie 模塊 (https://github.com/jshttp/cookie)
    next()
  }
}

1.1.5.2. 客戶端初始 Cookie

客戶端的 Cookie 最初來自服務(wù)器端观谦,服務(wù)器端告知客戶端的方式是通過響應(yīng)報(bào)文實(shí)現(xiàn)的,響應(yīng)的 Cookie 值在 Set-Cookie 字段中設(shè)置桨菜。具體格式如下所示:

Set-Cookie: name=value; Path=/; Expires=Sun, 23-Apr-23 09:01:35 GMT; Domain=.domain.com;
  • path 標(biāo)識(shí)這個(gè) Cookie 影響到的路徑豁状。
  • ExpiresMax-Age 告知瀏覽器這個(gè) Cookie 何時(shí)過期。
  • Secure 該屬性為 true 時(shí)倒得,表示 Cookie 只能通過 HTTPS 協(xié)議傳遞泻红。

Express 中間件 express-session 處理 Set-Cookie :

function setcookie(res, name, val, secret, options) {
  var signed = 's:' + signature.sign(val, secret);
  var data = cookie.serialize(name, signed, options);
  var prev = res.getHeader('set-cookie') || [];
  var header = Array.isArray(prev) ? prev.concat(data) : [prev, data];
  res.setHeader('set-cookie', header)
}

1.1.6. Session

Cookie 的缺點(diǎn)是無法保護(hù)敏感數(shù)據(jù),因此 Session 應(yīng)運(yùn)而生霞掺。 Session 的數(shù)據(jù)只保留在服務(wù)器端谊路,客戶端是無法更改的。服務(wù)器端是如何將每個(gè)用戶和 Session 數(shù)據(jù)對應(yīng)起來的根悼?通常是基于 Cookie 來實(shí)現(xiàn)映射關(guān)系的凶异,具體步驟如下:

  • 服務(wù)器端生成 Session 和 sessionID(口令)
  • 將 Session 數(shù)據(jù) 和 sessionID 映射存儲(chǔ)在 Store 中(redis、mongodb挤巡、memory)
  • 通過 Set-Cookie 將 sessionID 作為 Cookie 的鍵值對發(fā)送給客戶端。
  • 對于客戶端的請求酷麦,服務(wù)器端每次檢查 Cookie 中的 sessionID矿卑,并對應(yīng)保留在服務(wù)器端 Store 的 Session 數(shù)據(jù)。
  • [ 服務(wù)器端更新存儲(chǔ) Session 數(shù)據(jù) ]

Express 的中間件 express-session 將 Session 數(shù)據(jù)掛載在 req.session沃饶,方便業(yè)務(wù)邏輯使用母廷。同時(shí) express-session 還提供了多種 Store 。

1.1.7. Basic 認(rèn)證

Basic 認(rèn)證是一個(gè)通過用戶名和密碼實(shí)現(xiàn)的身份認(rèn)證方式糊肤。如果用戶首次訪問網(wǎng)頁琴昆, URL 地址中沒有攜帶認(rèn)證內(nèi)容,那么瀏覽器會(huì)到得一個(gè) 401 未授權(quán)的響應(yīng)馆揉。

var http = require('http')
var auth = require('basic-auth')

// Create server
var server = http.createServer(function (req, res) {
  var credentials = auth(req)

  if (!credentials || credentials.name !== 'john' || credentials.pass !== 'secret') {
    res.statusCode = 401
    res.setHeader('WWW-Authenticate', 'Basic realm="example"')
    res.end('Access denied')
  } else {
    res.end('Access granted')
  }
})

// Listen
server.listen(3000)

響應(yīng)頭中的 WWW-Authenticate 字段告知瀏覽器采用什么樣的認(rèn)證和加密方式业舍。

瀏覽器在后續(xù)請求中都攜帶上 Authorization 信息,服務(wù)器會(huì)檢查請求報(bào)文頭中的 Authorization 字段的內(nèi)容升酣,該字段有認(rèn)證方式和加密值構(gòu)成舷暮。

function auth (req) {
  // get header
  var header = req.headers.authorization
  // parse header
  var match = CREDENTIALS_REGEXP.exec(string) // CREDENTIALS_REGEXP = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/
  if (!match) {
    return undefined
  }
  // decode user pass
  var userPass = USER_PASS_REGEXP.exec(decodeBase64(match[1]))  // USER_PASS_REGEXP = /^([^:]*):(.*)$/
  if (!userPass) {
    return undefined
  }
  // return credentials object
  return new Credentials(userPass[1], userPass[2])
}

function decodeBase64 (str) {
  return new Buffer(str, 'base64').toString()
}

function Credentials (name, pass) {
  this.name = name
  this.pass = pass
}

1.2. 數(shù)據(jù)上傳

報(bào)文頭部中的內(nèi)容已經(jīng)能夠讓服務(wù)器端進(jìn)行大多數(shù)業(yè)務(wù)邏輯操作了,但是單純的報(bào)文頭部無法攜帶大量的數(shù)據(jù)噩茄,請求報(bào)文中還有攜帶內(nèi)容的報(bào)文體下面,這部分需要用戶自行接收和解析。通過報(bào)文頭部的 Transfer-EncodingContent-Length 字段即可判斷請求中是否帶有報(bào)文體绩聘。

function hasbody (req) {
  return req.headers['transfer-encoding'] !== undefined ||
    !isNaN(req.headers['content-length'])
}

HTTP_Parser 模塊通過觸發(fā) 'data' 事件獲取 req.rawBody 沥割,然后針對不同類型的報(bào)文體進(jìn)行相應(yīng)的解析耗啦。 Express 中間件 body-parser 針對 JSON 的解析如下:

  function parse (body) {
    if (body.length === 0) {
      // special-case empty json body, as it's a common client-side mistake
      // TODO: maybe make this configurable or part of "strict" option
      return {}
    }
    if (strict) {
      var first = firstchar(body)   // FIRST_CHAR_REGEXP = /^[\x20\x09\x0a\x0d]*(.)/ // eslint-disable-line no-control-regex
      if (first !== '{' && first !== '[') {
        throw new SyntaxError('Unexpected token ' + first)
      }
    }
    return JSON.parse(body)
  }

1.2.1. 表單數(shù)據(jù)

在表單提交的請求頭中 Content-Type 字段值為 application/x-www-form-urlencoded ,也就是其內(nèi)容通過 urlencoded 的方式編碼內(nèi)容形成報(bào)文體机杜,node-formidable 模塊解析表單提交大概如下:

// 判斷報(bào)文頭
if (this.headers['content-type'].match(/urlencoded/i)) {
  this._initUrlencoded();
  return;
}
// 事件發(fā)布
IncomingForm.prototype._initUrlencoded = function() {
  this.type = 'urlencoded';
  var parser = new QuerystringParser(this.maxFields);
  parser.onField = function(key, val) {
    self.emit('field', key, val);
  };
};
// 事件訂閱
IncomingForm.prototype.parse = function(req, cb) {
  if (cb) {
    this
      .on('field', function(name, value) {
        fields[name] = value;
      })
  }
}

1.2.2. 附件上傳

一種特殊的表單需要提交文件芹彬,該表單中可以含有 file 類型的控件,以及需要指定表單屬性 enctypemultipart/form-data 叉庐。因?yàn)楸韱沃泻卸喾N控件舒帮,所有使用名為 boundary 的分隔符進(jìn)行分割。

模塊 node-formidable 將解析上傳文件和處理普通表單數(shù)據(jù)進(jìn)行了統(tǒng)一化處理陡叠,以下是文件上傳的實(shí)例:

var formidable = require('formidable'),
    http = require('http'),
    util = require('util');

http.createServer(function(req, res) {
  if (req.url == '/upload' && req.method.toLowerCase() == 'post') {
    // parse a file upload
    var form = new formidable.IncomingForm();

    form.parse(req, function(err, fields, files) {
      res.writeHead(200, {'content-type': 'text/plain'});
      res.write('received upload:\n\n');
      res.end(util.inspect({fields: fields, files: files}));
    });
    return;
  }

  // show a file upload form
  res.writeHead(200, {'content-type': 'text/html'});
  res.end(
    '<form action="/upload" enctype="multipart/form-data" method="post">'+
    '<input type="text" name="title"><br>'+
    '<input type="file" name="upload" multiple="multiple"><br>'+
    '<input type="submit" value="Upload">'+
    '</form>'
  );
}).listen(8080);

Express 的中間件 Multer 也提供了類似的功能玩郊,但是它只能處理特殊的表單也就是表單屬性含有 multipart/form-data

var express = require('express')
var multer  = require('multer')
var upload = multer({ dest: 'uploads/' })

var app = express()

app.post('/profile', upload.single('avatar'), function (req, res, next) {
  // req.file is the `avatar` file
  // req.body will hold the text fields, if there were any
})

app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) {
  // req.files is array of `photos` files
  // req.body will contain the text fields, if there were any
})

var cpUpload = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }])
app.post('/cool-profile', cpUpload, function (req, res, next) {
  // req.files is an object (String -> Array) where fieldname is the key, and the value is array of files
  //
  // e.g.
  //  req.files['avatar'][0] -> File
  //  req.files['gallery'] -> Array
  //
  // req.body will contain the text fields, if there were any
})

1.2.3. 跨站請求偽造 ( CSRF )

通常解決 CSRF 的方法是在表單中添加隨機(jī)值枉阵。 首先服務(wù)器端生成一個(gè)隨機(jī)值译红,然后將隨機(jī)值內(nèi)嵌到前端表單,前端表單的請求中攜帶該隨機(jī)值兴溜,服務(wù)器端收到并解析后對比判定是否一致侦厚。

Express 中間件 csurf 默認(rèn)情況下會(huì)自動(dòng)生成隨機(jī)值,并且會(huì)將該隨機(jī)值掛載到 req.session.csrfSecret 上拙徽。

1.3. 路由解析

1.3.1. 文件路徑型

這種路由的處理方式刨沦,就是將請求路徑中的文件發(fā)送給客戶端即可,而請求 URL 中的文件路徑與文件所在的具體路徑相對應(yīng)膘怕。

1.3.2. MVC

MVC 模型將業(yè)務(wù)邏輯按職責(zé)分離:

  • 模型 (Model) 數(shù)據(jù)相關(guān)的操作和封裝
  • 控制器(Controller) 行為的集合
  • 視圖 (View) 頁面的渲染

它的工作模式:

  • 路由解析想诅,根據(jù) URL 查找到對應(yīng)的控制器及其所定義的行為
  • 行為調(diào)用相關(guān)的模型,進(jìn)行數(shù)據(jù)操作
  • 將操作后的數(shù)據(jù)結(jié)合相應(yīng)的視圖進(jìn)行頁面渲染岛心,并將頁面返回給客戶端

在 MVC 模型中来破,路由也是非常重要的概念,它主要實(shí)現(xiàn)了 URL 和控制器的映射忘古,具體實(shí)現(xiàn)的方式有:

  • 手工映射
    • 靜態(tài)映射
    • 正則匹配
    • 參數(shù)解析
  • 自然映射

1.3.3. RESTful

REST 的中文含義為表現(xiàn)層狀態(tài)轉(zhuǎn)化徘禁, 符合 REST 規(guī)范的設(shè)計(jì)成為 RESTful 設(shè)計(jì)。 它的設(shè)計(jì)哲學(xué)是將服務(wù)器端提供的內(nèi)容實(shí)體看作為一個(gè)資源髓堪,并表現(xiàn)在 URL 上送朱。其中 URL 中的 Method 代表了對這個(gè)資源的操作方法。

POST /user/jacksontian  // 創(chuàng)建新用戶
DELETE /user/jacksontian  // 刪除用戶
PUT /user/jacksontian    // 更改用戶
GET /user/jacksontian   // 查詢用戶

在 RESTful 設(shè)計(jì)中旦袋,客戶端能夠接受資源的具體格式由請求報(bào)文頭中的 Accept 字段給出:

Accept: application/json,application/xml

而服務(wù)器端在響應(yīng)報(bào)文中骤菠,通過 Content-Type 字段告知客戶端是什么格式:

Content-Type: application/json 

所以 RESTful 的設(shè)計(jì)就是, 通過 URL 設(shè)計(jì)資源疤孕、請求方法定義資源的操作和通過 Accept 決定資源的具體格式商乎。

1.4. 中間件

上述工作有太多的繁瑣細(xì)節(jié)要完成,為了簡化和隔離這些基礎(chǔ)功能祭阀,讓開發(fā)者關(guān)注業(yè)務(wù)邏輯的實(shí)現(xiàn)鹉戚,引入了中間件這個(gè)定義鲜戒。中間件組件是一個(gè)函數(shù),它攔截 HTTP 服務(wù)器提供的請求和響應(yīng)對象抹凳,執(zhí)行邏輯遏餐,然后或者結(jié)束響應(yīng),或者傳遞給下一個(gè)中間件赢底。

Node 的 http 模塊提供了應(yīng)用層協(xié)議的封裝失都,但是對具體業(yè)務(wù)沒有支持(小而靈活),因此必須有開發(fā)框架對業(yè)務(wù)提供支持幸冻。 通過中間件的形式搭建開發(fā)框架粹庞,完成各種基礎(chǔ)功能,最終匯成強(qiáng)大的基礎(chǔ)框架洽损。每一種基礎(chǔ)框架對中間件的組織形式不盡相同庞溜,下圖是基礎(chǔ)框架 Express 的實(shí)現(xiàn)機(jī)制。

Middleware
Middleware

1.4.1. 普通中間件

在 Express 中碑定,中間件按慣例會(huì)接受三個(gè)參數(shù):一個(gè)請求對象流码,一個(gè)響應(yīng)對象,還有一個(gè)通常命名為 next 的參數(shù)延刘,它是一個(gè)回調(diào)函數(shù)漫试,表明該組件已經(jīng)完成了工作,可以執(zhí)行下一個(gè)中間件組件了访娶。中間件的分派主要依賴于 next 這個(gè)回調(diào)函數(shù)的尾觸發(fā)商虐,這樣前一個(gè)中間件組件完成后才能進(jìn)入下一個(gè)中間件組件。

在 Web 應(yīng)用中崖疤,路由是個(gè)至關(guān)重要的概念,它會(huì)把請求 URL 映射到實(shí)現(xiàn)業(yè)務(wù)邏輯的函數(shù)上典勇。通過中間件和業(yè)務(wù)邏輯的結(jié)合可以完成對路由的執(zhí)行劫哼。首先使用 app.use() 等方法將所有的中間件和業(yè)務(wù)邏輯以及相應(yīng)的掛載點(diǎn)有序的放入路由數(shù)組,然后通過請求路徑與掛載點(diǎn)的對比割笙,將匹配的數(shù)據(jù)元素重組為新的數(shù)組权烧,最后通過分發(fā)執(zhí)行中間件,中間件執(zhí)行完畢后通過 next() 函數(shù)將結(jié)果轉(zhuǎn)入到下一個(gè)匹配的數(shù)組元素伤溉。

var handle = function (req, res, stack) {
  var next = function () {
    // 從stack數(shù)組中取出中間件并執(zhí)行
    var middleware = stack.shift();
    if (middleware) {
      // 傳入next()函數(shù)自身般码,使中間件能夠執(zhí)行結(jié)束后遞歸
      middleware(req, res, next);
    }
  };

  // 啟動(dòng)執(zhí)行
  next();
};
dispatch

1.4.2. 異常處理

為了捕獲中間件拋出的同步異常,保證 Web 應(yīng)用的穩(wěn)定和健壯乱顾,我們?yōu)?next() 方法添加 err 參數(shù)板祝。這主要是因?yàn)楫惒降漠惓2荒苤苯硬东@,中間件的異常需要自己傳遞出來走净。

使用中間件的思路將異常的處理交給中間件券时,同時(shí)為了區(qū)分異常處理中間件和普通中間件的區(qū)別孤里,在其參數(shù)中加入 err 參數(shù):

const middleware = function(options) {
  return function(err, req, res, next) {
    // TODO
    next();
  };
};

每個(gè)異常處理中間件可通過 next(err) 方法將異常傳遞給下一個(gè)異常處理中間件,其思路與普通中間件完全一致橘洞。

如果異常處理中間件沒有設(shè)置 next(err) 方法捌袜,那它后面的異常處理中間件都不會(huì)起作用。

1.5. 頁面渲染

執(zhí)行完中間件及業(yè)務(wù)邏輯后炸枣,服務(wù)器端該如何響應(yīng)客戶端虏等?一般有兩種方式:

  • 內(nèi)容響應(yīng)
  • 視圖渲染

1.5.1. 內(nèi)容響應(yīng)

因?yàn)榉?wù)器端響應(yīng)的報(bào)文,最終都會(huì)被客戶端處理适肠,具體終端有可能是命令行霍衫,也有可能是瀏覽器。這就使得響應(yīng)報(bào)文頭中的 content-* 字段顯得十分重要迂猴。

1.5.1.1. MIME

報(bào)文頭中的 Content-Type 字段的值決定采用不同的渲染方式慕淡,而這個(gè)值就是 MIME值。不同的文件類型具有不同的 MIME 值:

  • JSON 文件: application/json
  • XML 文件: application/xml
  • PDF 文件: application/pdf

1.5.1.2. 附件下載

報(bào)文頭中的 Content-Disposition 字段影響的行為是客戶端會(huì)根據(jù)它的值判斷是應(yīng)該將報(bào)文數(shù)據(jù)當(dāng)做及時(shí)瀏覽的內(nèi)容(inline)沸毁,還是可以下載的附件(attachment)峰髓。

1.5.1.3. 響應(yīng) JSON

為了快捷的響應(yīng) JSON 數(shù)據(jù), Express 封裝了響應(yīng)對象的 res.json() 方法:

res.json = function json(obj) {
  var val = obj;
  var body = JSON.stringify(val);

  // content-type
  if (!this.get('Content-Type')) {
    this.set('Content-Type', 'application/json');
  }
  return this.send(body);
};

1.5.1.4. 響應(yīng)跳轉(zhuǎn)

當(dāng)前 URL 因?yàn)槟承┰虿荒芴幚恚?需要將用戶跳轉(zhuǎn)到別的 URL 時(shí)息尺,Express 同樣封裝了一個(gè)快捷方式 res.redirect():

res.redirect = function redirect(url) {
  var address = url;
  var body;
  var status = 302;

  // Set location header
  address = this.location(address).get('Location');

  // Support text/{plain,html} by default
  this.format({
    text: function(){
      body = statuses[status] + '. Redirecting to ' + address
    },

    html: function(){
      var u = escapeHtml(address);
      body = '<p>' + statuses[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>'
    },

    default: function(){
      body = '';
    }
  });

  // Respond
  this.statusCode = status;
  this.set('Content-Length', Buffer.byteLength(body));

  if (this.req.method === 'HEAD') {
    this.end();
  } else {
    this.end(body);
  }
};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末携兵,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子搂誉,更是在濱河造成了極大的恐慌徐紧,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炭懊,死亡現(xiàn)場離奇詭異并级,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)侮腹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進(jìn)店門嘲碧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人父阻,你說我怎么就攤上這事愈涩。” “怎么了加矛?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵履婉,是天一觀的道長。 經(jīng)常有香客問我斟览,道長毁腿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮狸棍,結(jié)果婚禮上身害,老公的妹妹穿的比我還像新娘。我一直安慰自己草戈,他們只是感情好塌鸯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著唐片,像睡著了一般丙猬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上费韭,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天茧球,我揣著相機(jī)與錄音,去河邊找鬼星持。 笑死抢埋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的督暂。 我是一名探鬼主播揪垄,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼逻翁!你這毒婦竟也來了饥努?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤八回,失蹤者是張志新(化名)和其女友劉穎酷愧,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缠诅,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡溶浴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了管引。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片戳葵。...
    茶點(diǎn)故事閱讀 38,673評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖汉匙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情生蚁,我是刑警寧澤噩翠,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站邦投,受9級特大地震影響伤锚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜志衣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一屯援、第九天 我趴在偏房一處隱蔽的房頂上張望猛们。 院中可真熱鬧,春花似錦狞洋、人聲如沸弯淘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽庐橙。三九已至,卻和暖如春借嗽,著一層夾襖步出監(jiān)牢的瞬間态鳖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工恶导, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留浆竭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓惨寿,卻偏偏與公主長得像邦泄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子缤沦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評論 2 349

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理虎韵,服務(wù)發(fā)現(xiàn),斷路器缸废,智...
    卡卡羅2017閱讀 134,633評論 18 139
  • 1. 網(wǎng)絡(luò)基礎(chǔ)TCP/IP HTTP基于TCP/IP協(xié)議族包蓝,HTTP屬于它內(nèi)部的一個(gè)子集。 把互聯(lián)網(wǎng)相關(guān)聯(lián)的協(xié)議集...
    yozosann閱讀 3,440評論 0 20
  • 本篇文章篇幅比較長企量,先來個(gè)思維導(dǎo)圖預(yù)覽一下测萎。 一、概述 1.計(jì)算機(jī)網(wǎng)絡(luò)體系結(jié)構(gòu)分層 2.TCP/IP 通信傳輸流 ...
    滌生_Woo閱讀 54,976評論 24 557
  • 從三月份找實(shí)習(xí)到現(xiàn)在届巩,面了一些公司硅瞧,掛了不少,但最終還是拿到小米恕汇、百度腕唧、阿里、京東瘾英、新浪枣接、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,213評論 11 349
  • 基礎(chǔ)功能 之前我們通過http模塊創(chuàng)建了一個(gè)簡單的服務(wù)器缺谴,但是對于一個(gè)網(wǎng)絡(luò)應(yīng)用來說肯定是遠(yuǎn)遠(yuǎn)不夠的但惶,在聚義的業(yè)務(wù)中...
    exialym閱讀 875評論 1 22