第二章 OpenResty(Nginx+Lua)開發入門

Nginx入門

本文目的是學習Nginx+Lua開發,對于Nginx基本知識可以參考如下文章:

nginx啟動、關閉、重啟

http://www.cnblogs.com/derekchen/archive/2011/02/17/1957209.html

agentzh 的 Nginx 教程

http://openresty.org/download/agentzh-nginx-tutorials-zhcn.html

Nginx+Lua入門

http://17173ops.com/2013/11/01/17173-ngx-lua-manual.shtml

nginx 配置指令的執行順序

http://zhongfox.github.io/blog/server/2013/05/15/nginx-exec-order/

nginx與lua的執行順序和步驟說明

http://www.mrhaoting.com/?p=157

Nginx配置文件nginx.conf中文詳解

http://www.ha97.com/5194.html

Tengine的Nginx開發從入門到精通

http://tengine.taobao.org/book/

官方文檔

http://wiki.nginx.org/Configuration


Lua入門

本文目的是學習Nginx+Lua開發,對于Lua基本知識可以參考如下文章:

Lua簡明教程

http://coolshell.cn/articles/10739.html

lua在線lua學習教程

http://book.luaer.cn/

Lua 5.1 參考手冊

http://www.codingnow.com/2000/download/lua_manual.html

Lua5.3 參考手冊

http://cloudwu.github.io/lua53doc/

Nginx Lua API

和一般的Web Server類似,我們需要接收請求、處理并輸出響應。而對于請求我們需要獲取如請求參數、請求頭、Body體等信息;而對于處理就是調用相應的Lua代碼即可;輸出響應需要進行響應狀態碼、響應頭和響應內容體的輸出。因此我們從如上幾個點出發即可。


接收請求

1、example.conf配置文件?

Java代碼

location?~?/lua_request/(\d+)/(\d+)?{??

????#設置nginx變量??

set?$a?$1;???

????set?$b?$host;??

default_type"text/html";??

????#nginx內容處理??

????content_by_lua_file?/usr/example/lua/test_request.lua;??

????#內容體處理完成后調用??

echo_after_body"ngx.var.b?$b";??

}??

2、test_request.lua?

Java代碼

--nginx變量??

local?var?=?ngx.var??

ngx.say("ngx.var.a?:?",?var.a,?"
")??

ngx.say("ngx.var.b?:?",?var.b,?"
")??

ngx.say("ngx.var[2]?:?",?var[2],?"
")??

ngx.var.b?=2;??


ngx.say("
")??


--請求頭??

local?headers?=?ngx.req.get_headers()??

ngx.say("headers?begin",?"
")??

ngx.say("Host?:?",?headers["Host"],?"
")??

ngx.say("user-agent?:?",?headers["user-agent"],?"
")??

ngx.say("user-agent?:?",?headers.user_agent,?"
")??

for?k,v?in?pairs(headers)?do??

if?type(v)?==?"table"?then??

ngx.say(k,"?:?",?table.concat(v,?","),?"
")??

else??

ngx.say(k,"?:?",?v,?"
")??

????end??

end??

ngx.say("headers?end",?"
")??

ngx.say("
")??


--get請求uri參數??

ngx.say("uri?args?begin",?"
")??

local?uri_args?=?ngx.req.get_uri_args()??

for?k,?v?in?pairs(uri_args)?do??

if?type(v)?==?"table"?then??

ngx.say(k,"?:?",?table.concat(v,?",?"),?"
")??

else??

ngx.say(k,":?",?v,?"
")??

????end??

end??

ngx.say("uri?args?end",?"
")??

ngx.say("
")??


--post請求參數??

ngx.req.read_body()??

ngx.say("post?args?begin",?"
")??

local?post_args?=?ngx.req.get_post_args()??

for?k,?v?in?pairs(post_args)?do??

if?type(v)?==?"table"?then??

ngx.say(k,"?:?",?table.concat(v,?",?"),?"
")??

else??

ngx.say(k,":?",?v,?"
")??

????end??

end??

ngx.say("post?args?end",?"
")??

ngx.say("
")??


--請求的http協議版本??

ngx.say("ngx.req.http_version?:?",?ngx.req.http_version(),?"
")??

--請求方法??

ngx.say("ngx.req.get_method?:?",?ngx.req.get_method(),?"
")??

--原始的請求頭內容??

ngx.say("ngx.req.raw_header?:?",??ngx.req.raw_header(),?"
")??

--請求的body內容體??

ngx.say("ngx.req.get_body_data()?:?",?ngx.req.get_body_data(),?"
")??

ngx.say("
")??

ngx.var?: nginx變量,如果要賦值如ngx.var.b = 2,此變量必須提前聲明;另外對于nginx location中使用正則捕獲的捕獲組可以使用ngx.var[捕獲組數字]獲取;

ngx.req.get_headers:獲取請求頭,默認只獲取前100,如果想要獲取所以可以調用ngx.req.get_headers(0);獲取帶中劃線的請求頭時請使用如headers.user_agent這種方式;如果一個請求頭有多個值,則返回的是table;

ngx.req.get_uri_args:獲取url請求參數,其用法和get_headers類似;

ngx.req.get_post_args:獲取post請求內容體,其用法和get_headers類似,但是必須提前調用ngx.req.read_body()來讀取body體(也可以選擇在nginx配置文件使用lua_need_request_body on;開啟讀取body體,但是官方不推薦);

ngx.req.raw_header:未解析的請求頭字符串;

ngx.req.get_body_data:為解析的請求body體內容字符串。


如上方法處理一般的請求基本夠用了。另外在讀取post內容體時根據實際情況設置client_body_buffer_sizeclient_max_body_size來保證內容在內存而不是在文件中。


使用如下腳本測試

Java代碼

wget?--post-data?'a=1&b=2'?'http://127.0.0.1/lua_request/1/2?a=3&b=4'?-O?-???


輸出響應

1.1、example.conf配置文件

Java代碼

location?/lua_response_1?{??

default_type"text/html";??

????content_by_lua_file?/usr/example/lua/test_response_1.lua;??

}??

1.2、test_response_1.lua?

Java代碼

--寫響應頭??

ngx.header.a?="1"??

--多個響應頭可以使用table??

ngx.header.b?=?{"2",?"3"}??

--輸出響應??

ngx.say("a",?"b",?"
")??

ngx.print("c",?"d",?"
")??

--200狀態碼退出??

return?ngx.exit(200)??

ngx.header:輸出響應頭;

ngx.print:輸出響應內容體;

ngx.say:通ngx.print,但是會最后輸出一個換行符;

ngx.exit:指定狀態碼退出。


2.1、example.conf配置文件

Java代碼

location?/lua_response_2?{??

default_type"text/html";??

????content_by_lua_file?/usr/example/lua/test_response_2.lua;??

}??


2.2、test_response_2.lua

Java代碼

ngx.redirect("http://jd.com",?302)??

ngx.redirect:重定向;


ngx.status=狀態碼,設置響應的狀態碼;ngx.resp.get_headers()獲取設置的響應狀態碼;ngx.send_headers()發送響應狀態碼,當調用ngx.say/ngx.print時自動發送響應狀態碼;可以通過ngx.headers_sent=true判斷是否發送了響應狀態碼。


其他API

1、example.conf配置文件

Java代碼

location?/lua_other?{??

default_type"text/html";??

????content_by_lua_file?/usr/example/lua/test_other.lua;??

}??


2、test_other.lua

Java代碼

--未經解碼的請求uri??

local?request_uri?=?ngx.var.request_uri;??

ngx.say("request_uri?:?",?request_uri,?"
");??

--解碼??

ngx.say("decode?request_uri?:?",?ngx.unescape_uri(request_uri),?"
");??

--MD5??

ngx.say("ngx.md5?:?",?ngx.md5("123"),?"
")??

--http?time??

ngx.say("ngx.http_time?:?",?ngx.http_time(ngx.time()),?"
")??


ngx.escape_uri/ngx.unescape_uri?: uri編碼解碼;

ngx.encode_args/ngx.decode_args:參數編碼解碼;

ngx.encode_base64/ngx.decode_base64:BASE64編碼解碼;

ngx.re.match:nginx正則表達式匹配;


更多Nginx Lua API請參考http://wiki.nginx.org/HttpLuaModule#Nginx_API_for_Lua


Nginx全局內存

使用過如Java的朋友可能知道如Ehcache等這種進程內本地緩存,Nginx是一個Master進程多個Worker進程的工作方式,因此我們可能需要在多個Worker進程中共享數據,那么此時就可以使用ngx.shared.DICT來實現全局內存共享。


1、首先在nginx.conf的http部分分配內存大小

Java代碼

#共享全局變量,在所有worker間共享??

lua_shared_dict?shared_data?1m;??


2、example.conf配置文件

Java代碼

location?/lua_shared_dict?{??

default_type"text/html";??

????content_by_lua_file?/usr/example/lua/test_lua_shared_dict.lua;??

}??

3、?test_lua_shared_dict.lua

Java代碼

--1、獲取全局共享內存變量??

local?shared_data?=?ngx.shared.shared_data??


--2、獲取字典值??

local?i?=?shared_data:get("i")??

if?not?i?then??

i?=1??

--3、惰性賦值??

shared_data:set("i",?i)??

ngx.say("lazy?set?i?",?i,?"
")??

end??

--遞增??

i?=?shared_data:incr("i",?1)??

ngx.say("i=",?i,?"
")??

更多API請參考http://wiki.nginx.org/HttpLuaModule#ngx.shared.DICT



到此基本的Nginx Lua API就學完了,對于請求處理和輸出響應如上介紹的API完全夠用了,更多API請參考官方文檔。


Nginx Lua模塊指令

Nginx共11個處理階段,而相應的處理階段是可以做插入式處理,即可插拔式架構;另外指令可以在http、server、server if、location、location if幾個范圍進行配置:

指令所處處理階段使用范圍解釋

init_by_lua

init_by_lua_file

loading-confighttpnginx Master進程加載配置時執行;

通常用于初始化全局配置/預加載Lua模塊

init_worker_by_lua

init_worker_by_lua_file

starting-workerhttp每個Nginx Worker進程啟動時調用的計時器,如果Master進程不允許則只會在init_by_lua之后調用;

通常用于定時拉取配置/數據,或者后端服務的健康檢查

set_by_lua

set_by_lua_file

rewriteserver,server if,location,location if設置nginx變量,可以實現復雜的賦值邏輯;此處是阻塞的,Lua代碼要做到非常快;

rewrite_by_lua

rewrite_by_lua_file

rewrite tailhttp,server,location,location ifrrewrite階段處理,可以實現復雜的轉發/重定向邏輯;

access_by_lua

access_by_lua_file

access tailhttp,server,location,location if請求訪問階段處理,用于訪問控制

content_by_lua

content_by_lua_file

contentlocation,location if內容處理器,接收請求處理并輸出響應

header_filter_by_lua

header_filter_by_lua_file

output-header-filterhttp,server,location,location if設置header和cookie

body_filter_by_lua

body_filter_by_lua_file

output-body-filterhttp,server,location,location if對響應數據進行過濾,比如截斷、替換。

log_by_lua

log_by_lua_file

loghttp,server,location,location iflog階段處理,比如記錄訪問量/統計平均響應時間


更詳細的解釋請參考http://wiki.nginx.org/HttpLuaModule#Directives。如上指令很多并不常用,因此我們只拿其中的一部分做演示。


init_by_lua

每次Nginx重新加載配置時執行,可以用它來完成一些耗時模塊的加載,或者初始化一些全局配置;在Master進程創建Worker進程時,此指令中加載的全局變量會進行Copy-OnWrite,即會復制到所有全局變量到Worker進程。


1、nginx.conf配置文件中的http部分添加如下代碼

Java代碼

#共享全局變量,在所有worker間共享??

lua_shared_dict?shared_data?1m;??


init_by_lua_file?/usr/example/lua/init.lua;??


2、init.lua

Java代碼

--初始化耗時的模塊??

local?redis?=?require'resty.redis'??

local?cjson?=?require'cjson'??


--全局變量,不推薦??

count?=1??


--共享全局內存??

local?shared_data?=?ngx.shared.shared_data??

shared_data:set("count",?1)??


3、test.lua

Java代碼

count?=?count?+?1??

ngx.say("global?variable?:?",?count)??

local?shared_data?=?ngx.shared.shared_data??

ngx.say(",?shared?memory?:?",?shared_data:get("count"))??

shared_data:incr("count",?1)??

ngx.say("hello?world")??


4、訪問如http://192.168.1.2/lua 會發現全局變量一直不變,而共享內存一直遞增

global variable : 2 , shared memory : 8 hello world?


另外注意一定在生產環境開啟lua_code_cache,否則每個請求都會創建Lua VM實例。


init_worker_by_lua

用于啟動一些定時任務,比如心跳檢查,定時拉取服務器配置等等;此處的任務是跟Worker進程數量有關系的,比如有2個Worker進程那么就會啟動兩個完全一樣的定時任務。


1、nginx.conf配置文件中的http部分添加如下代碼

Java代碼

init_worker_by_lua_file?/usr/example/lua/init_worker.lua;??


2、init_worker.lua

Java代碼

local?count?=?0??

local?delayInSeconds?=3??

local?heartbeatCheck?=?nil??


heartbeatCheck?=?function(args)??

count?=?count?+1??

ngx.log(ngx.ERR,"do?check?",?count)??


???local?ok,?err?=?ngx.timer.at(delayInSeconds,?heartbeatCheck)??


if?not?ok?then??

ngx.log(ngx.ERR,"failed?to?startup?heartbeart?worker...",?err)??

???end??

end??


heartbeatCheck()??

ngx.timer.at:延時調用相應的回調方法;ngx.timer.at(秒單位延時,回調函數,回調函數的參數列表);可以將延時設置為0即得到一個立即執行的任務,任務不會在當前請求中執行不會阻塞當前請求,而是在一個輕量級線程中執行。


另外根據實際情況設置如下指令

lua_max_pending_timers 1024; ?#最大等待任務數

lua_max_running_timers 256; ? ?#最大同時運行任務數



set_by_lua?

設置nginx變量,我們用的set指令即使配合if指令也很難實現負責的賦值邏輯;


1.1、example.conf配置文件

Java代碼

location?/lua_set_1?{??

default_type"text/html";??

????set_by_lua_file?$num?/usr/example/lua/test_set_1.lua;??

????echo?$num;??

}??

set_by_lua_file:語法set_by_lua_file $var lua_file arg1 arg2...; 在lua代碼中可以實現所有復雜的邏輯,但是要執行速度很快,不要阻塞;


1.2、test_set_1.lua

Java代碼

local?uri_args?=?ngx.req.get_uri_args()??

local?i?=?uri_args["i"]?or?0??

local?j?=?uri_args["j"]?or?0??


return?i?+?j??

得到請求參數進行相加然后返回。


訪問如http://192.168.1.2/lua_set_1?i=1&j=10進行測試。 如果我們用純set指令是無法實現的。


再舉個實際例子,我們實際工作時經常涉及到網站改版,有時候需要新老并存,或者切一部分流量到新版


2.1、首先在example.conf中使用map指令來映射host到指定nginx變量,方便我們測試

Java代碼

############?測試時使用的動態請求??

map?$host?$item_dynamic?{??

default?????????????????????"0";??

item2014.jd.com"1";??

}??

如綁定hosts

192.168.1.2 item.jd.com;

192.168.1.2 item2014.jd.com;


此時我們想訪問item2014.jd.com時訪問新版,那么我們可以簡單的使用如

Java代碼

if?($item_dynamic?=?"1")?{??

proxy_pass?http://new;??

}??

proxy_pass?http://old;??


但是我們想把商品編號為為8位(比如品類為圖書的)沒有改版完成,需要按照相應規則跳轉到老版,但是其他的到新版;雖然使用if指令能實現,但是比較麻煩,基本需要這樣

Java代碼

set?jump?"0";??

if($item_dynamic?=?"1")?{??

set?$jump"1";??

}??

if(uri?~?"^/6[0-9]{7}.html")?{??

set?$jump"${jump}2";??

}??

#非強制訪問新版,且訪問指定范圍的商品??

if?(jump?==?"02")?{??

proxy_pass?http://old;??

}??

proxy_pass?http://new;??

以上規則還是比較簡單的,如果涉及到更復雜的多重if/else或嵌套if/else實現起來就更痛苦了,可能需要到后端去做了;此時我們就可以借助lua了:

Java代碼

set_by_lua?$to_book?'??

?????local?ngx_match?=?ngx.re.match??

?????local?var?=?ngx.var??

?????local?skuId?=?var.skuId??

local?r?=?var.item_dynamic?~="1"?and?ngx.re.match(skuId,?"^[0-9]{8}$")??

if?r?then?return?"1"?else?return?"0"?end;??

';??

set_by_lua?$to_mvd?'??

?????local?ngx_match?=?ngx.re.match??

?????local?var?=?ngx.var??

?????local?skuId?=?var.skuId??

local?r?=?var.item_dynamic?~="1"?and?ngx.re.match(skuId,?"^[0-9]{9}$")??

if?r?then?return?"1"?else?return?"0"?end;??

';??

#自營圖書??

if?($to_book)?{??

proxy_pass?http://127.0.0.1/old_book/$skuId.html;??

}??

#自營音像??

if?($to_mvd)?{??

proxy_pass?http://127.0.0.1/old_mvd/$skuId.html;??

}??

#默認??

proxy_pass?http://127.0.0.1/proxy/$skuId.html;??


rewrite_by_lua?

執行內部URL重寫或者外部重定向,典型的如偽靜態化的URL重寫。其默認執行在rewrite處理階段的最后。


1.1、example.conf配置文件

Java代碼

location?/lua_rewrite_1?{??

default_type"text/html";??

????rewrite_by_lua_file?/usr/example/lua/test_rewrite_1.lua;??

echo"no?rewrite";??

}??


1.2、test_rewrite_1.lua

Java代碼

if?ngx.req.get_uri_args()["jump"]?==?"1"?then??

return?ngx.redirect("http://www.jd.com?jump=1",?302)??

end??

當我們請求http://192.168.1.2/lua_rewrite_1時發現沒有跳轉,而請求http://192.168.1.2/lua_rewrite_1?jump=1時發現跳轉到京東首頁了。 此處需要301/302跳轉根據自己需求定義。


2.1、example.conf配置文件

Java代碼

location?/lua_rewrite_2?{??

default_type"text/html";??

????rewrite_by_lua_file?/usr/example/lua/test_rewrite_2.lua;??

echo"rewrite2?uri?:?$uri,?a?:?$arg_a";??

}??


2.2、test_rewrite_2.lua

Java代碼

if?ngx.req.get_uri_args()["jump"]?==?"1"?then??

ngx.req.set_uri("/lua_rewrite_3",?false);??

ngx.req.set_uri("/lua_rewrite_4",?false);??

ngx.req.set_uri_args({a?=1,?b?=?2});??

end???

ngx.req.set_uri(uri, false):可以內部重寫uri(可以帶參數),等價于 rewrite ^ /lua_rewrite_3;通過配合if/else可以實現?rewrite ^ /lua_rewrite_3 break;這種功能;此處兩者都是location內部url重寫,不會重新發起新的location匹配;

ngx.req.set_uri_args:重寫請求參數,可以是字符串(a=1&b=2)也可以是table;


訪問如http://192.168.1.2/lua_rewrite_2?jump=0時得到響應

rewrite2 uri : /lua_rewrite_2, a :


訪問如http://192.168.1.2/lua_rewrite_2?jump=1時得到響應

rewrite2 uri : /lua_rewrite_4, a : 1


3.1、example.conf配置文件

Java代碼

location?/lua_rewrite_3?{??

default_type"text/html";??

????rewrite_by_lua_file?/usr/example/lua/test_rewrite_3.lua;??

echo"rewrite3?uri?:?$uri";??

}??


3.2、test_rewrite_3.lua

Java代碼

if?ngx.req.get_uri_args()["jump"]?==?"1"?then??

ngx.req.set_uri("/lua_rewrite_4",?true);??

ngx.log(ngx.ERR,"=========")??

ngx.req.set_uri_args({a?=1,?b?=?2});??

end??

ngx.req.set_uri(uri, true):可以內部重寫uri,即會發起新的匹配location請求,等價于 rewrite ^ /lua_rewrite_4 last;此處看error log是看不到我們記錄的log。


所以請求如http://192.168.1.2/lua_rewrite_3?jump=1會到新的location中得到響應,此處沒有/lua_rewrite_4,所以匹配到/lua請求,得到類似如下的響應

global variable : 2 , shared memory : 1 hello world


rewrite ^ /lua_rewrite_3; ? ? ? ? ? ? ? ? 等價于 ?ngx.req.set_uri("/lua_rewrite_3", false);

rewrite ^ /lua_rewrite_3 break; ? ? ? 等價于 ?ngx.req.set_uri("/lua_rewrite_3", false); 加 if/else判斷/break/return

rewrite ^ /lua_rewrite_4 last; ? ? ? ? ? 等價于 ?ngx.req.set_uri("/lua_rewrite_4", true);


注意,在使用rewrite_by_lua時,開啟rewrite_log on;后也看不到相應的rewrite log。


access_by_lua?

用于訪問控制,比如我們只允許內網ip訪問,可以使用如下形式

Java代碼

allow?????127.0.0.1;??

allow10.0.0.0/8;??

allow192.168.0.0/16;??

allow172.16.0.0/12;??

deny??????all;??


1.1、example.conf配置文件

Java代碼

location?/lua_access?{??

default_type"text/html";??

????access_by_lua_file?/usr/example/lua/test_access.lua;??

echo"access";??

}??


?1.2、test_access.lua

Java代碼

if?ngx.req.get_uri_args()["token"]?~=?"123"?then??

return?ngx.exit(403)??

end??

即如果訪問如http://192.168.1.2/lua_access?token=234將得到403 Forbidden的響應。這樣我們可以根據如cookie/用戶token來決定是否有訪問權限。



content_by_lua?

此指令之前已經用過了,此處就不講解了。


另外在使用PCRE進行正則匹配時需要注意正則的寫法,具體規則請參考http://wiki.nginx.org/HttpLuaModule中的Special PCRE Sequences部分。還有其他的注意事項也請閱讀官方文檔。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內容