Nginx

【錯誤處理】【Nginx】client intended to send too large body

查了一下資料發現造成的原因為 web server 接收 request body 的大小設定

根據以下步驟更改一下 nginx 設定,就沒問題了

http {
    ...
 
    # set request body size unlimit
    client_max_body_size 0;
    ...
}
nginx -s reload

【Lua】【型別】 字串- 數字

Number( 數字)

字串轉數字

tonumber("1.0")

數字轉字串

tostring(1.0)

自動轉型

-- Lua會自動轉型,如果一個字串與數字相加,會嘗試將字串轉換為數字
"1.0" + 2 -- => 3.0

-- 如果一個數字和字串使用串接,會嘗試將數字轉換成字串:
"1.0"..2 -- => 1.02
-- 數字轉字串
1.0 .. ""

-- 字串轉數字
1.0 + 0 

string (字串)

使用單引號或雙引號包起來:

-- 單行
s1 = 'string'
s2 = "string"

-- 多行
s3 = [[
multiple
string
]]

字串長度

-- #
print(#"abc") -- => 3

-- string.len()
rint(string.len("abc")) -- => 3

-- 文字會有些不同
-- note: with UTF-8
print(string.len("我")) -- equal print(#"我") => 3

local utf8 = require "utf8"
print(utf8.len("你好,世界!")) -- => 6

 文字串接

-- 使用兩個點 .
print("Hello, " .. "World")  -- Hello, World

-- 多文字串接建議使用table.concat

local strs = {"Hello"," ","World"}
print(table.concat(strs,""))  -- Hello, World
-- format
local string = require "string"
print(string.format("%s !! %s","Hello","World")) -- > Hello !! World

 

file的資料型態--userdata

do
  local f <close> = io.open("text.txt",'w')
  print(type(f)) -- Output: userdata
end

【Lua】【型別】 函數function

函數宣告

函數可以使用function來做宣告,並以end結束

function hello()
  print("Hello, World")
end

-- 等同以下
hello = function()
  print("Hello, World")
end
function genFun()
  return function()
    print("Hello, World")
  end
end


function callFun(f)
  f()
end


local _f = genFun() -- # get a function
callFun(_f) -- print("Hello, World")

函數傳遞

function plus(a, b)
    print(a,b, a + b)
    return a+b ,a ,b
end

local anser, A, B = plus(1,2)
print(A.."+"..B.."="..anser)

-- 1	2	3
-- 1+2=3

回傳值規則

若返回值個數大於接收變量的個數,多餘的返回值會被忽略掉; 若返回值個數小 於參數個數,從左向右,沒有被返回值初始化的變量會被初始化為 nil。

function init() --init 函数 返回两个值 1 和 "lua"
return 1, "lua"
end
x = init()
print(x)
x, y, z = init()
print(x, y, z)

--output
1
1 lua nil

当一个函数有一个以上返回值,且函数调用不是一个列表表达式的最后一个元素,那么函数 调用只会产生一个返回值, 也就是第一个返回值。

local function init() -- init 函数 返回两个值 1 和 "lua"
return 1, "lua"
end
local x, y, z = init(), 2 -- init 函数的位置不在最后,此时只返回 1
print(x, y, z) -->output 1 2 nil
local a, b, c = 2, init() -- init 函数的位置在最后,此时返回 1 和 "lua"
print(a, b, c) -->output 2 1 

 

local function init() -- init 函数 返回两个值 1 和 "lua"
return 1, "lua"
end
local x, y, z = init(), 2 -- init 函数的位置不在最后,此时只返回 1
print(x, y, z) -->output 1 2 nil
local a, b, c = 2, init() -- init 函数的位置在最后,此时返回 1 和 "lua"
print(a, b, c) -->output 2 1 

不定長參數

function hello(...)
  local members = {...}
  print("有多少成員: ", #members)
  for _,m in pairs(members) do
    print("Hello, "..m)
  end
end

-- hello("Bob", "Lua", "Luna", "Selene")
-- 有多少成員: 	4
-- Hello, Bob
-- Hello, Lua
-- Hello, Luna
-- Hello, Selene
function mylog(...)
	local arr = {...}
	table.insert(arr,1,"\n=====[mylog_begin]=====")
	table.insert(arr,"=====[mylog_end]=====\n")
	local print_str = table.concat(arr,"\n")
	-- for key,value in pairs(arr) do
	-- 	-- ngx.say(key..":"..value)
	-- 	ngx.say(value)
	-- end	
	ngx.log(ngx.ERR,print_str)
end


mylog("開始",1,2,3,"結束")
-- =====[mylog_begin]=====
-- 開始
-- 1
-- 2
-- 3
-- 結束
-- =====[mylog_end]=====

立即呼叫函式表達式(IIFE) 與 匿名函式

(function ()
  print("Hello, World")
end)()

多值返回

function get(dict, key, default)
  return (dict[key] or default), dict[key] ~= nil
end

_d = {}
_v, found = get(_d, "key", "value")
print(_v) -- => Output: value
print(found) -- => Output: false

function Person(args)
  local person = args
  person.name = args.name or "Bob"
  person.age = args.age or 20
  person.address = args.address or ""
  return person
end

p1 = Person {
  "位置參數1",
  age = 13,
  address = "secret",
  "位置參數2",
}

print("Name:    ", p1.name)
print("Age:     ", p1.age)
print("Address: ", p1.address)
print("-------------")
print("位置參數:", p1[1], p1[2])
Name: Bob
Age: 13
Address: secret

位置參數: 位置參數1 位置參數2

常用的內建函數

基本

輸入/輸出

估計本系列不怎麼會提到,先放這XD。

數學

作業系統

Lua 5.1前一個和os.getenv很像的函數setfenv其實也蠻常用的,後來被移除,改用其他方式。
當前還有很多是使用Lua 5.1,這雖然是需要知道的事情,不過其實其與作業系統無關。

字串

【Lua】【型別】 nil - 布林

nil

nil是Lua裡的一個特殊值,代表什麼也沒有。其型別也是nil

type(nil) -- => nil

布林

布林值只有truefalse

只要不是nil或是false都為真, 包含0、空表、空字串 。

if 0 then
  print("0 is true")
end

if {} then
  print("{} is true")
end

if "" then
  print [["" is true]]
end

~= 不等於

weeks = {"週一","週二","週三","週四","週五","週六","週日", "Opps"}

if #weeks ~= 7 then
  error("Weeks length must is 7.")
end

【Lua】【流程控制】for

 

-- for i = {起始值}, {結束值}, {step] do
for i = 1, 10, 2 do
  print(i)
end


-- 1
-- 3
-- 5
-- 7
-- 9

迭代

array = {"one", "two", "three"}

for i, v in ipairs(array) do
  print(i, v)
end

-- 1	one
-- 2	two
-- 3	three
array = {"one", "two", "three"}
 
for i in ipairs(array) do
  print(i, array[i])
end

-- 1	one
-- 2	two
-- 3	three
tb = {
  ["key1"] = "one",
  ["key2"] = "two",
  ["key3"] = "three",
}

for k, v in pairs(tb) do
  print(k, v)
end

-- key3	three
-- key1	one
-- key2	two

【Lua】【流程控制】if

 

if true then
  print("if block")
elseif true then
  print("elseif block")
else
  print("else")
end
if not true then
  print("not true")
else
  print("else")
end

elseif 中間沒有空白要注意

if true then
  print("if block")
else if true then
  print("elseif block")
else
  print("else")
end
end

-- 其實等於

if true then
    print("if block")
else 
    if true then
        print("elseif block")
    else
        print("else")
    end
end

【Lua】【流程控制】while

destination = 5  -- 終點位置
current_pos = 0  -- 目前位置

print([[馬拉松賽跑
============]])

print("終點位於:"..destination)
print("目前在:"..current_pos)

while current_pos < destination do
  current_pos = current_pos + 1
  print("往前跑1㎞,目前位於"..current_pos)
end

print("抵達終點,目前位於"..current_pos)
馬拉松賽跑
============
終點位於:5
目前在:0
往前跑1㎞,目前位於1
往前跑1㎞,目前位於2
往前跑1㎞,目前位於3
往前跑1㎞,目前位於4
往前跑1㎞,目前位於5
抵達終點,目前位於5

【Lua】【優化】請求返回後繼續執行

ngx.eof 關閉連線,data 返還user,後續代碼繼續進行

local response, user_stat = logic_func.get_response(request)
ngx.say(response)
ngx.eof()
if user_stat then
local ret = db_redis.update_user_data(user_stat)
end

【Lua】安裝 luarocks

https://github.com/luarocks/luarocks/wiki/Download

https://github.com/luarocks/luarocks/wiki/Installation-instructions-for-Unix

 

apt-get update
apt install build-essential libreadline-dev unzip wget zlib1g-dev -y

#安裝 luarocks
wget https://luarocks.org/releases/luarocks-3.8.0.tar.gz && tar zxpf luarocks-3.8.0.tar.gz && cd luarocks-3.8.0 
./configure 
make && make install

#安裝 lua-resty-http
luarocks install lua-resty-http
#安裝 lua-ffi-zlib
luarocks install lua-ffi-zlib

 

install lua-zlib

https://blog.51cto.com/u_5650011/5394910
cd /usr/local
wget https://github.com/brimworks/lua-zlib/archive/master.zip
unzip lua-zlib-master.zip
cd /usr/local/lua-zlib-master
cmake -DLUA_INCLUDE_DIR=/usr/local/openresty/luajit/include/luajit-2.1 -DLUA_LIBRARIES=/usr/local/openresty/luajit/lib -DUSE_LUAJIT=ON -DUSE_LUA=OFF
make
cp zlib.so /usr/local/openresty/lualib/zlib.so

【Lua】註解

單行註解

 --  這是註解

多行註解

-- 使用 --[[ 內文 ]]-- 包起來

--[[
 function log_header_body()
        -- 取出列表編號
        local t = cjson.decode(ngx.req.get_body_data())
        local body =  cjson.encode(t)
        ngx.log(ngx.ERR, body)
        local headers = cjson.encode(ngx.req.get_headers())
        ngx.log(ngx.ERR, headers)
    
end
]]--

特數多行註解

可在--[ ${這中間插入任意數量=}[  ..... ] ${這中間插入任意數量=}]--

--[===[
Some Comments
==============
特別插入--]]  但實際註釋還未結束
--]===] print("這段不是註釋") -- end comment

【Lua】load

與 javascipt eval 相同-動態產生並執行程式碼

-- load不回直接執行,其實其返回一個包裝函式:

f = load[[g2 = 2]]
print(type(f)) -- => function

print(g2) -- => Output: nil
f()
print(g2) -- => Output: 2

 

【Lua】ngx_lua_module

 

image-1676368742762.png

 

 

指令 說明
set_by_lua* 流程分支處理判斷變量初始化
rewrite_by_lua* 轉發、重定向、緩存等功能
access_by_lua* IP 准入、身份驗證、接口權限、解密
content_by_lua* 內容生成
header_filter_by_lua* 響應頭部過濾處理,可以添加響應頭
body_filter_by_lua* 響應體過濾處理,例如轉換響應體
log_by_lua* 異步完成日誌記錄,日誌可以記錄在本地,還可以同步到其他機器

儘管僅使用單個階段的指令content_by_lua*就可以完成以上職責,但是把邏輯劃分在不同階段,更加容易維護。

https://blog.gmem.cc/openresty-study-note

https://github.com/openresty/lua-nginx-module

 

【Lua】Nginx 變量

使用Ng變量

要在OpenResty中引用Nginx變量,可以使用 ngx var VARIABLE,要將變量從字符串轉換為數字,可以使用 tonumber函數。

-- 黑名单
local black_ips = {["127.0.0.1"]=true}
 
-- 当前客户端IP
local ip = ngx.var.remote_addr
if true == black_ips[ip] then
    -- 返回相应的HTTP状态码
    ngx.exit(ngx.HTTP_FORBIDDEN)
end

經常用到的Ng變量如下表:

變量 說明
arg_name 請求中的name參數
args 請求中的參數
binary_remote_addr 遠程地址的二進製表示
body_bytes_sent 已發送的消息體字節數
content_length HTTP請求信息裡的"Content-Length"
content_type 請求信息裡的"Content-Type"
document_root 針對當前請求的根路徑設置值
document_uri 與$uri相同; 比如/test2/test.php
host 請求信息中的"Host",如果請求中沒有Host行,則等於設置的服務器名
hostname 機器名使用gethostname系統調用的值
http_cookie Cookie信息
http_referer 引用地址
http_user_agent 客戶端代理信息
http_via 最後一個訪問服務器的Ip地址。
http_x_forwarded_for 相當於網絡訪問路徑
is_args 如果請求行帶有參數,返回“?”,否則返回空字符串
limit_rate

對連接速率的限制。此變量支持寫入:

Lua
1
2
-- 设置当前请求的响应传递速率限制
ngx.var.limit_rate = 1000 
nginx_version 當前運行的nginx版本號
pid Worker進程的PID
query_string 與$args相同
realpath_root 按root指令或alias指令算出的當前請求的絕對路徑。其中的符號鏈接都會解析成真是文件路徑
remote_addr 客戶端IP地址
remote_port 客戶端端口號
remote_user 客戶端用戶名,認證用
request 用戶請求
request_body 這個變量(0.7.58+)包含請求的主要信息。在使用proxy_pass或fastcgi_pass指令的location中比較有意義
request_body_file 客戶端請求主體信息的臨時文件名
request_completion 如果請求成功,設為"OK";如果請求未完成或者不是一系列請求中最後一部分則設為空
request_filename 當前請求的文件路徑名,比如/opt/nginx/www/test.php
request_method 請求的方法,比如"GET"、"POST"等
request_uri 請求的URI,帶參數
scheme 所用的協議,比如http或者是https
server_addr 服務器地址,如果沒有用listen指明服務器地址,使用這個變量將發起一次系統調用以取得地址(造成資源浪費)
server_name 請求到達的服務器名
server_port 請求到達的服務器端口號
server_protocol 請求的協議版本,"HTTP/1.0"或"HTTP/1.1"
uri 請求的URI,可能和最初的值有不同,比如經過重定向之類的

【Lua】ngx.say 與 ngx.print 差異

差異在 ngx.say 會加入一個換行符號

【Lua】SSL相關指令執行順序

dyJfouYkGTnWwOup-drawing-3-1676368425.png

【Lua】table

模擬陣列用法

注意 lua 索引 從 1 開始

-- 宣告 table
lang = {
  "C",
  "C#",
  "C++",
  "Java",
  "Swift",
  "Python",
  "Haskell",
}

-- 使用 ipairs 迭代 lang
for i in ipairs(lang) do
    print(i, #lang[i], lang[i])
end

-- 以下寫法相同
for i,v in ipairs(lang) do
    print(i, #v, v)
end


-- 1	1	C
-- 2	2	C#
-- 3	3	C++
-- 4	4	Java
-- 5	5	Swift
-- 6	6	Python
-- 7	7	Haskell

 新增資料 => table.insert

lang = {
  "C",
  "C#",
  "C++",
  "Java",
  "Swift",
  "Python",
  "Haskell",
}

-- 使用table.insert
table.insert(lang, "lua")
-- 使用索引
lang[9] = "Javascript"

-- 1	1	C
-- 2	2	C#
-- 3	3	C++
-- 4	4	Java
-- 5	5	Swift
-- 6	6	Python
-- 7	7	Haskell
-- 8	3	lua
-- 9	10	Javascript

刪除資料 => 設為 nil

lang = {
  "C",
  "C#",
  "C++",
  "Java",
  "Swift",
  "Python",
  "Haskell",
}

-- 刪除總是山最後一筆,不然會有意外錯誤
lang[8] = nil

for i,v in ipairs(lang) do
    print(i, #v, v)
end
-- 1	1	C
-- 2	2	C#
-- 3	3	C++
-- 4	4	Java
-- 5	5	Swift
-- 6	6	Python
-- 7	7	Haskell

類map(key,vlaue)宣告

key 不要文字,數字混和宣告

person = {
  ["name"] = "Bob",
  ["age"] = 25,
}

-- key不為數字-以上相同
person = {
  name = "Bob",
  age = 25,
}

-- 陣列用法
arr = {
  [1] = 1,
  [2] = 2,
  [3] = 3,
  [4] = 4,
}

Key 值範圍

key可以是除了nilNaN(Not a Number)以外的任何型別。

obj = {} -- 建立一個空表
obj[1] = 1 -- 整數是合法的key值
obj[1.0] = 2 -- 浮點數是合法的key值
obj["string"] = 1 -- 字串是合法的key值
obj[math.huge] = 1 -- inf是合法的key值

--[[ 
要注意的是 obj[1] 和 obj[1.0]相同
其obj[1]和obj[1.0]最終值為2
--]]

----------------

print(obj[nil]) --> nil
obj[nil] = 1 --> Error: nil不是合法的key值,儘管取值不會出錯

print(obj[0/0]) --> nil
print(0/0) --> -nan
obj[0/0] = 1 --> Error: NaN不是合法的key值

 

【Lua】vscode 外掛

https://marketplace.visualstudio.com/items?itemName=sumneko.lua

image-1677036811172.png

【Nginx】config 詳解

基本配置區塊

#config 區塊 基本配置
...                   # 全域性區塊
 
event{                # events 區塊
        ...
}
http{                 # http 區塊
    server{           # server 區塊
        location{     # location 區塊
            ...
        }
    }
}

server 區塊的配置

server{
      listen  80;
      listen [::]80;
      server_name  example.com  example2.com; 
    location /{
        root /usr/share/nginx/html;
    	index index.html index.htm;
    }
}

#listen 80; 代表監聽所有 ipv4 的位址
#listen [::]80; 代表監聽所有 ipv6 的位址
#server_name 是你的 Domain 名稱。 可以使用空白listen 多個domain
#location 中則是指定對不同路徑要怎麼處理
# 如果沒有下deault_server; 設定,第一個server 區塊會變成預設所以有位被匹配到的設定
# 避免除錯上的困難,可設定一個預設區塊,在未匹配到server 時,顯示403錯誤
server {
  listen 80 default_server;
  server_name _;

  set $cache_status -;
  set $parameter -;
  set $response_body_size 0;
  location / {
  default_type 'text/html';
  echo "default server" ;
  return 403; # 403 forbidden
  }
}
	

【反向代理】reverse proxy

server {
 
    listen 80;
    server_name www.example.com;
 
    location \ {  
           proxy_pass http://192.168.1.1:8080;  
           
           proxy_set_header Host 				$host;
           proxy_set_header X-Real-IP			$remote_addr;
           proxy_set_header X-Forwarded-For		$proxy_add_x_forwarded_for;
           proxy_set_header X-Forwarded-Proto	$scheme;
        }
}

 【快取】實作圖片快取機制

server {
 
    listen 80;
    server_name www.example.com;
 
    location ~* \.(jpg|jpeg|png|css|js)$ {  
            expires 365d;  # 快取時間
            proxy_pass http://loaclhost:8080;  
        }
}

【virtural host】實作 virtural host

# .web1
server {
    listen      80;
    server_name web1.example.com ;
    root /usr/loacl/nginx/web1;
    index index.html;
}
 
# .web2
server {
    listen      80;
    server_name web2.example.com ;
    root /usr/loacl/nginx/web2;
    index index.html;
}
 

【負載平衡】 load balance

#Nginx 提供了以下三種 load balancing 方法:

#round-robin:預設值,會將請留輪流平均分配到每台伺服器上
#lest-connected:會將請求分配到目前連接數最少的伺服器上
#ip-hash:利用 hash-function 來決定使用者要被分配到的伺服器,此方法可以達到同一個使用者 (IP address) 每次連結的伺服器都是相同的
#如果要從默認值 round-robin 方法改成 lest-connected 或 ip-hash 的方法,只需要在第一行加上lest_conn; 或 ip_hash; 即可
upstream myapp {
    ip_hash;
    server srv1.example.com;
    server srv2.example.com;
    server srv3.example.com;
}
server {
  listen 80;
  location / {
    proxy_pass   http://myapp;
  }
}
# weight 默認值為 1,
# 以下的配置代表如果有 5 次新的請求,則會有 3 次被分配到 srv1 和分配各 1 次到 srv2 srv3 上
upstream myapp1 {
    server srv1.example.com weight=3;
    server srv2.example.com;
    server srv3.example.com;
    server bak.example.com; #backup 代表,所有伺服器都掛掉之後,此伺服器才會生效
}
server {
  listen 80;
  location / {
    proxy_pass   http://myapp1;
  }
}

限制來源

location / {
    deny  192.168.1.1;
    allow 192.168.1.0/24;
    allow 10.1.1.0/16;
    allow 2001:0db8::/32;
    deny  all;
}

nginx.conf 詳細配置

#定义Nginx运行的用户和用户组
user www www;


#nginx進程數,建議設置為等於CPU總核心數。
worker_processes 8;


#全局錯誤日誌定義類型,[ debug | info | notice | warn | error | crit ]
error_log /var/log/nginx/error.log info;


#進程文件
pid /var/run/nginx.pid;


#一個nginx進程打開的最多文件描述符數目,理論值應該是最多打開文件數(系統的值ulimit -n)與nginx進程數相除,但是nginx分配請求並不均勻,所以建議與ulimit -n的值保持一致。
worker_rlimit_nofile 65535;


#工作模式與連接數上限
events
{
#參考事件模型,use [ kqueue | rtsig | epoll | /dev/poll | select | poll ]; epoll模型是Linux 2.6以上版本內核中的高性能網絡I/O模型,如果跑在FreeBSD上面,就用kqueue模型。
use epoll;
#單個進程最大連接數(最大連接數=連接數*進程數)
worker_connections 65535;
}

#設定http服務器
http
{
include mime.types; #文件擴展名與文件類型映射表
default_type application/octet-stream; #默認文件類型
#charset utf-8; #默認編碼
server_names_hash_bucket_size 128; #服務器名字的hash表大小
large_client_header_buffers 4 64k; #設定請求緩
autoindex on; #開啟目錄列表訪問,合適下載服務器,默認關閉。

#此指令設置 Nginx 使用的 DNS 解析器的配置。在這個例子中,使用了兩個 DNS 解析器的 IP 地址:8.8.8.8 和 168.95.1.1。
#ipv6=off 表示禁用 IPv6 解析。valid=300s 表示 DNS 解析的結果將在 300 秒(5 分鐘)內被緩存。
resolver 8.8.8.8 168.95.1.1 ipv6=off valid=300s;
  	
#此指令設置 DNS 解析的超時時間。如果在指定的時間內無法解析域名,Nginx 將中斷解析並返回錯誤。
resolver_timeout 3s;
  
client_body_buffer_size 256K; 	#此指令指定客戶端請求主體的緩衝區大小,這是用於存儲客戶端發送的請求主體數據的內存區域。
client_body_timeout 3s; 		#此指令設置接收客戶端請求主體的超時時間。如果在指定的時間內沒有接收到完整的請求主體,Nginx 將結束該連接。
client_header_buffer_size 64k;	#此指令指定用於存儲客戶端請求標頭的緩衝區大小。(?上傳文件大小限制?)
client_header_timeout 3s;		#此指令設置接收客戶端請求標頭的超時時間。如果在指定的時間內沒有接收到完整的請求標頭,Nginx 將結束該連接。
client_max_body_size 1m;		#此指令設置允許客戶端發送的最大請求主體大小。如果客戶端嘗試發送超過此大小的請求主體,Nginx 將返回 413 Request Entity Too Large 錯誤。
large_client_header_buffers 8 64k; #此指令設置用於存儲大型客戶端請求標頭的緩衝區大小。如果客戶端請求的標頭超過指定的大小,Nginx 將使用這些緩衝區來處理它。
keepalive_timeout 5s;			#此指令設置持久連接的超時時間。如果在指定的時間內沒有新的請求到達,Nginx 將關閉持久連接。
open_file_cache max=5000 inactive=20s; #此指令啟用了文件打開時的緩存機制,以加快文件的讀取速度。max 參數指定緩存的最大數量,inactive 參數指定文件在緩存中保持不活躍的時間。
open_file_cache_valid 30s;		#此指令設置緩存中的文件信息的有效期限。在此期限內,Nginx 將重用緩存的文件信息而不需要再次訪問磁盤。
open_file_cache_min_uses 8;		#此指令設置文件緩存機制中的最小使用次數。只有當某個文件被訪問的次數超過此設定值時,才會將該文件的信息緩存起來。
open_file_cache_errors on;		#此指令設置是否緩存文件打開期間的錯誤。當設置為 "on" 時,如果在打開文件時發生錯誤,Nginx 會將該錯誤信息緩存起來,以避免重複出現相同的錯誤。
send_timeout 10;				#此指令設置向客戶端發送數據的超時時間。如果在指定的時間內沒有將數據發送給客戶端,Nginx 將結束該連接。

#開啟高效文件傳輸模式,sendfile指令指定nginx是否調用sendfile函數來輸出文件,對於普通應用設為on,用於高效地將文件內容發送給客戶端,而無需將文件數據從磁盤讀入到用戶空間。
#如果用來進行下載等應用磁盤IO重負載應用,可設置為off,以平衡磁盤與網絡I/O處理速度,降低系統的負載。注意:如果圖片顯示不正常把這個改成off。
sendfile on;
  
sendfile_max_chunk 512k;		#此指令設置單次 sendfile 操作的最大數據塊大小。如果需要發送的文件數據大於此值,Nginx 將分段進行 sendfile 操作。
server_tokens off;				#此指令關閉 Nginx 的版本號顯示。在生產環境中,關閉版本號的顯示可以增加安全性,防止攻擊者利用已知漏洞進行攻擊。
tcp_nodelay on;					#此指令啟用 TCP 即時發送功能。它禁用了 Nagle 算法,從而降低了數據傳輸的延遲,但可能會增加網絡流量。
tcp_nopush  on;					#此指令啟用 TCP 壓縮機制。當服務器將數據發送給客戶端時,它將盡可能地將多個小的數據包合並為一個較大的數據包發送,從而減少了網絡傳輸的開銷。
types_hash_max_size 2048;		#此指令設置 Nginx 在處理 MIME 類型時使用的哈希表的大小。增大此值可以提高性能

#FastCGI相關參數是為了改善網站的性能:減少資源佔用,提高訪問速度。下面參數看字面意思都能理解。
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 64k;
fastcgi_buffers 4 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 128k;


#gzip模塊設置
gzip on; #開啟gzip壓縮輸出
gzip_min_length 1k; #最小壓縮文件大小
gzip_buffers 4 16k; #壓縮緩衝區
gzip_http_version 1.0; #壓縮版本(默認1.1,前端如果是squid2.5請使用1.0)
gzip_comp_level 2; #壓縮等級
gzip_types text/plain application/x-javascript text/css application/xml;
#壓縮類型,默認就已經包含text/html,所以下面就不用再寫了,寫上去也不會有問題,但是會有一個warn。
gzip_vary on;
#limit_zone crawler $binary_remote_addr 10m; #開啟限制IP連接數的時候需要使用


upstream blog.ha97.com {
#upstream的負載均衡,weight是權重,可以根據機器配置定義權重。weigth參數表示權值,權值越高被分配到的機率越大。
server 192.168.80.121:80 weight=3;
server 192.168.80.122:80 weight=2;
server 192.168.80.123:80 weight=3;
}


#虛擬主機的配置
server
{
#監聽端口
listen 80;
#域名可以有多個,用空格隔開
server_name www.ha97.com ha97.com;
index index.html index.htm index.php;
root /data/www/ha97;
location ~ .*\.(php|php5)?$
{
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi.conf;
}
#圖片緩存時間設置
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 10d;
}
#JS和CSS緩存時間設置
location ~ .*\.(js|css)?$
{
expires 1h;
}
#日誌格式設定
log_format access '$remote_addr – $remote_user [$time_local] “$request” '
'$status $body_bytes_sent “$http_referer” '
'”$http_user_agent” $http_x_forwarded_for';
#定義本虛擬主機的訪問日誌
access_log /var/log/nginx/ha97access.log access;


#對“/” 啟用反向代理
location / {
proxy_pass http://127.0.0.1:88;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
#後端的Web服務器可以通過X-Forwarded-For獲取用戶真實IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#以下是一些反向代理的配置,可選。
proxy_set_header Host $host;
client_max_body_size 10m; #允許客戶端請求的最大單文件字節數
client_body_buffer_size 128k; #緩衝區代理緩衝用戶端請求的最大字節數,
proxy_connect_timeout 90; #nginx跟後端服務器連接超時時間(代理連接超時)
proxy_send_timeout 90; #後端服務器數據回傳時間(代理髮送超時)
proxy_read_timeout 90; #連接成功後,後端服務器響應時間(代理接收超時)
proxy_buffer_size 4k; #設置代理服務器(nginx)保存用戶頭信息的緩衝區大小
proxy_buffers 4 32k; #proxy_buffers緩衝區,網頁平均在32k以下的設置
proxy_busy_buffers_size 64k; #高負荷下緩衝大小(proxy_buffers*2)
proxy_temp_file_write_size 64k;
#設定緩存文件夾大小,大於這個值,將從upstream服務器傳
}


#設定查看Nginx狀態的地址
location /NginxStatus {
stub_status on;
access_log on;
auth_basic “NginxStatus”;
auth_basic_user_file conf/htpasswd;
#htpasswd文件的內容可以用apache提供的htpasswd工具來產生。
}



#本地动静分离反向代理配置
#所有jsp的页面均交由tomcat或resin处理
location ~ .(jsp|jspx|do)?$ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8080;
}
#所有静态文件由nginx直接读取不经过tomcat或resin
location ~ .*.(htm|html|gif|jpg|jpeg|png|bmp|swf|ioc|rar|zip|txt|flv|mid|doc|ppt|pdf|xls|mp3|wma)$
{ expires 15d; }
location ~ .*.(js|css)?$
{ expires 1h; }
}
}

 

    

 

 

【Nginx】location 匹配規則

一般匹配

location [=|~|~*|^~] /uri/ { ... }

模式 含义
location = /uri = 表示精确匹配,只有完全匹配上才能生效
location ^~ /uri ^~ 开头对URL路径进行前缀匹配,并且在正则之前。
location ~ pattern 开头表示区分大小写的正则匹配
location ~* pattern 开头表示不区分大小写的正则匹配
location /uri 不带任何修饰符,也表示前缀匹配,但是在正则匹配之后
location / 通用匹配,任何未匹配到其它location的请求都会匹配到,相当于switch中的default

實際使用規則至少有三個匹配定義

# 直接匹配网站根,通过域名访问网站首页比较频繁,使用这个会加速处理,官网如是说。 # 这里是直接转发给后端应用服务器了,也可以是一个静态首页
# 第一个必选规则
location = / {
    proxy_pass http://tomcat:8080/index
}
# 第二个必选规则是处理静态文件请求,这是 nginx 作为 http 服务器的强项
# 有两种配置模式,目录匹配或后缀匹配,任选其一或搭配使用 
location ^~ /static/ {
    root /webroot/static/;
}
location ~* \.(gif|jpg|jpeg|png|css|js|ico)$ {
    root /webroot/res/;
}
# 第三个规则就是通用规则,用来转发动态请求到后端应用服务器
# 非静态文件请求就默认是动态请求,自己根据实际把握
# 毕竟目前的一些框架的流行,带.php、.jsp后缀的情况很少了 location / {
    proxy_pass http://tomcat:8080/
}

依據檔案類型設置過期時間

location ~* \.(js|css|jpg|jpeg|gif|png|swf)$ {
    if (-f $request_filename) {
expires 1h;
break; }
}

禁止訪問某目錄

location ~* \.(txt|doc)${
    root /data/www/wwwroot/linuxtone/test;
    deny all;
}

內部命名位置

命名位置的用途

如何使用命名位置

命名位置常用於內部重定向,當某些條件滿足時會跳轉到這些位置。通常用在 try_fileserror_page 等指令中。

命名位置的典型應用

使用 try_files 與命名位置

server {
    listen 80;
    server_name example.com;

    # 主位置塊
    location / {
        # 嘗試匹配靜態文件,如果找不到則重定向到 @backend
        try_files $uri $uri/ @backend;
    }

    # 定義 @backend 內部重定向位置
    location @backend {
        # 添加自定義標頭
        add_header X-Cache-Status "MISS" always;
        # 設定代理地址
        proxy_pass http://backend_server;
    }
}

在這個例子中:

使用 error_page 與命名位置

假設你希望在遇到 404 錯誤時顯示自定義的錯誤頁面,並且如果特定條件下(例如 URL 中包含 /api/),將請求代理到其他服務器。可以這樣配置:

server {
    listen 80;
    server_name example.com;

    # 主位置塊
    location / {
        # 通常的處理邏輯
        try_files $uri $uri/ =404;
    }

    # 錯誤頁面處理
    error_page 404 /404.html;
    location = /404.html {
        # 內部重定向到 @notfound 位置
        try_files @notfound;
    }

    # 定義 @notfound 內部重定向位置
    location @notfound {
        # 檢查 URL 中是否包含 /api/
        if ($request_uri ~* /api/) {
            # 如果是,代理到 API 服務器
            proxy_pass http://api_server;
        }

        # 否則返回自定義的 404 頁面
        root /usr/share/nginx/html;
        internal; # 表示這個位置只能內部訪問
    }
}

 

【OpenResty】相關資源

 

跟我学OpenResty(Nginx+Lua)开发目录贴

https://jinnianshilongnian.iteye.com/category/333854

Docker下的OpenResty三部曲

 

【Openresty】Lua 的變數範圍

只有在 init_by_lua* 和 init_worker_by_lua* 階段才能定義真正的 全域變數

 

【Openresty】Nginx Lua的 執行階段

nginx 執行步驟

1、post-read
读取请求内容阶段,nginx读取并解析完请求头之后就立即开始运行;例如模块 ngx_realip 就在 post-read 阶段注册了处理程序,它的功能是迫使 Nginx 认为当前请求的来源地址是指定的某一个请求头的值。

2、server-rewrite
server请求地址重写阶段;当ngx_rewrite模块的set配置指令直接书写在server配置块中时,基本上都是运行在server-rewrite 阶段

3、find-config
配置查找阶段,这个阶段并不支持Nginx模块注册处理程序,而是由Nginx核心来完成当前请求与location配置块之间的配对工作。


4、rewrite
location请求地址重写阶段,当ngx_rewrite指令用于location中,就是再这个阶段运行的;另外ngx_set_misc(设置md5、encode_base64等)模块的指令,还有ngx_lua模块的set_by_lua指令和rewrite_by_lua指令也在此阶段。

5、post-rewrite
请求地址重写提交阶段,当nginx完成rewrite阶段所要求的内部跳转动作,如果rewrite阶段有这个要求的话;

6、preaccess
访问权限检查准备阶段,ngx_limit_req和ngx_limit_zone在这个阶段运行,ngx_limit_req可以控制请求的访问频率,ngx_limit_zone可以控制访问的并发度;

7、access
访问权限检查阶段,标准模块ngx_access、第三方模块ngx_auth_request以及第三方模块ngx_lua的access_by_lua指令就运行在这个阶段。配置指令多是执行访问控制相关的任务,如检查用户的访问权限,检查用户的来源IP是否合法;

8、post-access
访问权限检查提交阶段;主要用于配合access阶段实现标准ngx_http_core模块提供的配置指令satisfy的功能。satisfy all(与关系),satisfy any(或关系)

9、try-files
配置项try_files处理阶段;专门用于实现标准配置指令try_files的功能,如果前 N-1 个参数所对应的文件系统对象都不存在,try-files 阶段就会立即发起“内部跳转”到最后一个参数(即第 N 个参数)所指定的URI.

10、content
内容产生阶段,是所有请求处理阶段中最为重要的阶段,因为这个阶段的指令通常是用来生成HTTP响应内容并输出 HTTP 响应的使命;

11、log
日志模块处理阶段;记录日志
#lua 模組使用 nginx 區塊
init_by_lua            http
set_by_lua             server, server if, location, location if
rewrite_by_lua         http, server, location, location if
access_by_lua          http, server, location, location if
content_by_lua         location, location if
header_filter_by_lua   http, server, location, location if
body_filter_by_lua     http, server, location, location if
log_by_lua             http, server, location, location if
timer
#config 區塊 基本配置
...                   # 全域性區塊
 
event{                # events 區塊
        ...
}
http{                 # http 區塊
  
    init_by_lua_block{
    }
    
    init_by_lua_file /usr/local/nginx/conf/lua/init.lua
    
    #常見的功能是執行定時任務 or health_check
    #此階段一般用來進行權限檢查和黑白名單配置
    init_worker_by_lua_block{
  
  	}
  
    #配置環境:**http,server,location,location if
  	access_by_lua_block{}
  
  	#配置環境**http,server,location,location if#
    #常用於header進行添加、刪除等操作
    header_filter_by_lua_block{}
    
    server{           # server 區塊
        location /aaa {     # location 區塊
        	set $b '';
      		#  設定變量
            set_by_lua_block $a {
                local t = 'test'
                ngx.var.b = 'test_b'
                return t
            }
            return 200 $a,$b; # test,test_b
            }
    
        location /bbb {
          set $b  '1';
      	  #rewrite_by_lua_block始終都在rewrite階段的後面執行
          rewrite_by_lua_block { ngx.var.b = '2'}
          set $b  '3';
            echo $b; #2
          }
        }
  		
  		location /ccc{
    		#**配置環境:**location,location if
    		#content_by_lua_block指令不可以和其他內容處理階段的模塊如echo、return、proxy_pass等放在一起
    		content_by_lua_block{}
  		}
  
        location /ddd{
            #content_by_lua_file可以直接获取URL中参数file_name的值
            content_by_lua_file conf/lua/$arg_file_name;
        }
  
        location /eee{
    		#将响应体全部转换为大写
    		#**配置環境:**http,server,location,location if
    		body_filter_by_lua_block { ngx.arg[1] = string.upper(ngx. arg[1]) }
        }
    }
}

https://blog.51cto.com/xikder/2331649

Nginx 反向代理

server {
    listen       80;
    listen  [::]:80;
    server_name  www.srou.com;

    access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        # proxy_pass http://10.2.99.14:8081;
        index  index.html index.htm;
    }

    location /dev/ {
        proxy_pass http://10.2.99.14:8081;    
    }


    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

server {
    listen       80;
    listen  [::]:80;
    server_name  wsf.srou.com;

    location / {
        # root   /usr/share/nginx/html;
        proxy_pass http://10.2.99.14:8081;
        index  index.html index.htm;
    }

    location /uat/ {
        proxy_pass http://10.2.99.14:8082;    
    }

}

server {
    listen       80;
    listen  [::]:80;
    server_name  wsm.srou.com;

    location / {
        # root   /usr/share/nginx/html;
        proxy_pass http://10.2.99.14:8082;
        index  index.html index.htm;
    }
}


使用 IP 來替代域名的配置方法

  1. 查找域名對應的 IP 地址:確保你知道 a.aaa.com 對應的 IP 地址。

  2. 修改 Nginx 配置:將原本指向域名的 proxy_pass 修改為指向 IP 地址。

具體操作步驟:

假設 aaa.aaa.com 對應的 IP 地址是 192.168.1.1,這樣配置:

location / {
    proxy_pass https://192.168.1.1;
    proxy_ssl_name a.aaa.com;  # 設置 SSL 握手時使用的域名
    proxy_ssl_server_name on;    # 開啟 SNI 支持
}

配置解釋:

  1. proxy_pass 使用 IP 地址:直接使用 IP 地址(如 https://192.168.1.1)來替代域名,這樣就不需要 DNS 解析。

  2. proxy_ssl_name 指令:即使使用了 IP 地址,你仍然需要指示 Nginx 在 SSL 握手過程中使用哪個主機名。這是因為 SSL/TLS 握手過程通常依賴於域名,特別是在有 SNI(Server Name Indication)需求的情況下。

  3. proxy_ssl_server_name on 指令:這個指令開啟 SNI 支持,以便在 SSL 握手時能夠發送正確的域名信息給後端服務器。

不使用 DNS 的注意事項

這種方法有效避免了 Nginx 對 DNS 解析的依賴,但需要確保 IP 地址和 SSL 配置的正確性。

 

TWCA 憑證放入 Nginx

憑證安裝


TWCA發回來憑證內容若要裝在nginx 上處理方式
解開cert.zip後會有下列檔案

主機憑證:root.cer
網域憑證:server.cer
中繼憑證1:uca_1.cer
中繼憑證2:uca_2.cer

檔案內容由上而下的順序uca_2在上面,再來是uca_1,透過下列指令産生檔案
cp uca_2.cer  uca.crt ; cat  uca_1.cer >> uca.cer
這個個設定可以適用於apache,apache的設定如下:
       SSLEngine On
       SSLCertificateFile /etc/ssl/server.cer
       SSLCertificateKeyFile /etc/ssl/server.key

       SSLCertificateChainFile /etc/ssl/uca.cer

若你要放在nginx上的處理方式有稍梢不同
nginx 上的設檔如下:
        ssl on;
        ssl_certificate /etc/ssl/server.pem;
        ssl_certificate_key /etc/ssl/server.key;
        ssl_session_timeout 5m;
        ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
        ssl_prefer_server_ciphers on;

server{
    listen       80;
    listen       443 ssl;
    server_name  demo.server.com.tw;

    
    ssl_certificate /etc/nginx/ssl/server.pem;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    ssl_session_timeout 5m;
    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
    ssl_prefer_server_ciphers on;
    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        # root   /usr/share/nginx/html;
        # index  index.html index.htm;    
        proxy_http_version 1.1;
    
    	# 以下是reverse proxy 才需要
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection "upgrade";
		proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
		proxy_set_header Host $host;
		proxy_read_timeout 60s;        
        proxy_pass   http://192.168.1.1:20599/;


    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

}

其中/etc/ssl/server.pem和原來的産生的規則有些不同,他的順序是 server.cer + uca_2.cer + uca_1.cer
産生方法如下
cp server.cer server.pem;cat uca_2.cer >> server.pem ; cat uca_1.cer >> server.pem
https://davidelin.blogspot.com/2014/12/nginx-twca.html

憑證需要密碼解決方案

今天設定客戶的域名遇到 SSL_CTX_use_PrivateKey_fileEVP_DecryptFinal_ex:bad decrypt error 的錯誤訊息 指出是私鑰的問題。

May 07 16:43:18 terrylin nginx[5307]: nginx: [emerg] SSL_CTX_use_PrivateKey_file("/etc/nginx/ssl/terryl.in/2020/server.key") failed (SSL: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt error:0May 07 16:43:18 terrylin nginx[5307]: nginx: configuration file /etc/nginx/nginx.conf test failed
May 07 16:43:18 terrylin systemd[1]: nginx.service: Control process exited, code=exited status=1
May 07 16:43:18 terrylin systemd[1]: nginx.service: Failed with result 'exit-code'.
May 07 16:43:18 terrylin systemd[1]: Failed to start A high performance web server and a reverse proxy server.
-- Subject: Unit nginx.service has failed

打開私鑰檔案,發現在以往的長的不一樣,開頭多了一些編碼資訊。

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,D5F9600F17CDAB82

這是被密碼保護的私鑰。所以解決方法有兩種方式,一是補上設定在 nginx 的設定檔中。舉例,我們建立一個文字檔,命名為 ssl_password.txt 並將密碼存放在這個檔案。

在 Nginx 的設定中補上這一行。

ssl_password_file /etc/nginx/ssl/terryl.in/2020/ssl_password.txt;

重啟 Nginx,就可以了。

另外一種方法則是把有密碼保護的私鑰檔案轉成不需要密碼的私鑰。利用 OpenSSL 的指令如下:

openssl rsa -in server.key -out new_server.key

會提示需要您輸入密碼。

Enter pass phrase for server.key:

密碼正確輸入的話,則會匯出 new_server.key,這是不需要密碼保護的私鑰檔案。當然 Nginx 的設定就照之前的設定就可以了。

【Lua】錯誤處理

 

local status, err = pcall(function()
    -- 可能会出现错误的代码
    local result = some_function()
    ngx.print("Result: ", result)
end)

if not status then
    -- 出现错误时的处理
    ngx.log(ngx.ERR, "Error occurred: ", err)
    ngx.status = 500
    ngx.print("An error occurred. Please try again later.")
end

【 Lua】動態參數

function mylog(...)
	local arr = {...}
  	-- table.inset(table,index,value)
	table.insert(arr,1,"start")
	table.insert(arr,"end")
	for key,value in pairs(arr) do
		ngx.say(key..":"..value)
		-- ngx.say(value)
	end	
end

mylog("A","B","C","D")
-- 1:start
-- 2:A
-- 3:B
-- 4:C
-- 5:D
-- 6:end

【Lua】使用map 做全域變數

http {
    map $server_name $redis_server {
        default 10.10.10.1;
        example.com 10.10.10.2;  # 每個 server_name 可以有自己的 redis 伺服器
        example.org 10.10.10.3;
        # 添加更多的 server_name 和對應的 redis 伺服器
    }

    server {
    	listen 80 backlog=16384;
        server_name  example.com;
        charset UTF-8;
    
    	# $redis_server 10.10.10.2 
        set $REDIS_SERVER $redis_server;
        lua_need_request_body on;
        add_header X-Content-Length $response_body_size always;

        # 其他設定...
    }

     server {
    	listen 80 backlog=16384;
        server_name  example.org;
        charset UTF-8;
    
    	# $redis_server 10.10.10.3
        set $REDIS_SERVER $redis_server;
        lua_need_request_body on;
        add_header X-Content-Length $response_body_size always;

        # 其他設定...
    }
  
    server {
    	listen 80 backlog=16384;
        server_name  test.com;
        charset UTF-8;
    
    	# $redis_server 走預設 10.10.10.1
        set $REDIS_SERVER $redis_server;
        lua_need_request_body on;
        add_header X-Content-Length $response_body_size always;

        # 其他設定...
    }
    # 其他 server 區塊...
}

a

 

【Lua】Learn Lua in 15 Minutes

Learn Lua in 15 Minutes
-- Two dashes start a one-line comment.

--[[
     Adding two ['s and ]'s makes it a
     multi-line comment.
--]]

----------------------------------------------------
-- 1. Variables and flow control.
----------------------------------------------------

num = 42  -- All numbers are doubles.
-- Don't freak out, 64-bit doubles have 52 bits for
-- storing exact int values; machine precision is
-- not a problem for ints that need < 52 bits.

s = 'walternate'  -- Immutable strings like Python.
t = "double-quotes are also fine"
u = [[ Double brackets
       start and end
       multi-line strings.]]
t = nil  -- Undefines t; Lua has garbage collection.

-- Blocks are denoted with keywords like do/end:
while num < 50 do
  num = num + 1  -- No ++ or += type operators.
end

-- If clauses:
if num > 40 then
  print('over 40')
elseif s ~= 'walternate' then  -- ~= is not equals.
  -- Equality check is == like Python; ok for strs.
  io.write('not over 40\n')  -- Defaults to stdout.
else
  -- Variables are global by default.
  thisIsGlobal = 5  -- Camel case is common.

  -- How to make a variable local:
  local line = io.read()  -- Reads next stdin line.

  -- String concatenation uses the .. operator:
  print('Winter is coming, ' .. line)
end

-- Undefined variables return nil.
-- This is not an error:
foo = anUnknownVariable  -- Now foo = nil.

aBoolValue = false

-- Only nil and false are falsy; 0 and '' are true!
if not aBoolValue then print('twas false') end

-- 'or' and 'and' are short-circuited.
-- This is similar to the a?b:c operator in C/js:
ans = aBoolValue and 'yes' or 'no'  --> 'no'

karlSum = 0
for i = 1, 100 do  -- The range includes both ends.
  karlSum = karlSum + i
end

-- Use "100, 1, -1" as the range to count down:
fredSum = 0
for j = 100, 1, -1 do fredSum = fredSum + j end

-- In general, the range is begin, end[, step].

-- Another loop construct:
repeat
  print('the way of the future')
  num = num - 1
until num == 0


----------------------------------------------------
-- 2. Functions.
----------------------------------------------------

function fib(n)
  if n < 2 then return 1 end
  return fib(n - 2) + fib(n - 1)
end

-- Closures and anonymous functions are ok:
function adder(x)
  -- The returned function is created when adder is
  -- called, and remembers the value of x:
  return function (y) return x + y end
end
a1 = adder(9)
a2 = adder(36)
print(a1(16))  --> 25
print(a2(64))  --> 100

-- Returns, func calls, and assignments all work
-- with lists that may be mismatched in length.
-- Unmatched receivers are nil;
-- unmatched senders are discarded.

x, y, z = 1, 2, 3, 4
-- Now x = 1, y = 2, z = 3, and 4 is thrown away.

function bar(a, b, c)
  print(a, b, c)
  return 4, 8, 15, 16, 23, 42
end

x, y = bar('zaphod')  --> prints "zaphod  nil nil"
-- Now x = 4, y = 8, values 15..42 are discarded.

-- Functions are first-class, may be local/global.
-- These are the same:
function f(x) return x * x end
f = function (x) return x * x end

-- And so are these:
local function g(x) return math.sin(x) end
local g; g  = function (x) return math.sin(x) end
-- the 'local g' decl makes g-self-references ok.

-- Trig funcs work in radians, by the way.

-- Calls with one string param don't need parens:
print 'hello'  -- Works fine.


----------------------------------------------------
-- 3. Tables.
----------------------------------------------------

-- Tables = Lua's only compound data structure;
--          they are associative arrays.
-- Similar to php arrays or js objects, they are
-- hash-lookup dicts that can also be used as lists.

-- Using tables as dictionaries / maps:

-- Dict literals have string keys by default:
t = {key1 = 'value1', key2 = false}

-- String keys can use js-like dot notation:
print(t.key1)  -- Prints 'value1'.
t.newKey = {}  -- Adds a new key/value pair.
t.key2 = nil   -- Removes key2 from the table.

-- Literal notation for any (non-nil) value as key:
u = {['@!#'] = 'qbert', [{}] = 1729, [6.28] = 'tau'}
print(u[6.28])  -- prints "tau"

-- Key matching is basically by value for numbers
-- and strings, but by identity for tables.
a = u['@!#']  -- Now a = 'qbert'.
b = u[{}]     -- We might expect 1729, but it's nil:
-- b = nil since the lookup fails. It fails
-- because the key we used is not the same object
-- as the one used to store the original value. So
-- strings & numbers are more portable keys.

-- A one-table-param function call needs no parens:
function h(x) print(x.key1) end
h{key1 = 'Sonmi~451'}  -- Prints 'Sonmi~451'.

for key, val in pairs(u) do  -- Table iteration.
  print(key, val)
end

-- _G is a special table of all globals.
print(_G['_G'] == _G)  -- Prints 'true'.

-- Using tables as lists / arrays:

-- List literals implicitly set up int keys:
v = {'value1', 'value2', 1.21, 'gigawatts'}
for i = 1, #v do  -- #v is the size of v for lists.
  print(v[i])  -- Indices start at 1 !! SO CRAZY!
end
-- A 'list' is not a real type. v is just a table
-- with consecutive integer keys, treated as a list.

----------------------------------------------------
-- 3.1 Metatables and metamethods.
----------------------------------------------------

-- A table can have a metatable that gives the table
-- operator-overloadish behavior. Later we'll see
-- how metatables support js-prototypey behavior.

f1 = {a = 1, b = 2}  -- Represents the fraction a/b.
f2 = {a = 2, b = 3}

-- This would fail:
-- s = f1 + f2

metafraction = {}
function metafraction.__add(f1, f2)
  sum = {}
  sum.b = f1.b * f2.b
  sum.a = f1.a * f2.b + f2.a * f1.b
  return sum
end

setmetatable(f1, metafraction)
setmetatable(f2, metafraction)

s = f1 + f2  -- call __add(f1, f2) on f1's metatable

-- f1, f2 have no key for their metatable, unlike
-- prototypes in js, so you must retrieve it as in
-- getmetatable(f1). The metatable is a normal table
-- with keys that Lua knows about, like __add.

-- But the next line fails since s has no metatable:
-- t = s + s
-- Class-like patterns given below would fix this.

-- An __index on a metatable overloads dot lookups:
defaultFavs = {animal = 'gru', food = 'donuts'}
myFavs = {food = 'pizza'}
setmetatable(myFavs, {__index = defaultFavs})
eatenBy = myFavs.animal  -- works! thanks, metatable

-- Direct table lookups that fail will retry using
-- the metatable's __index value, and this recurses.

-- An __index value can also be a function(tbl, key)
-- for more customized lookups.

-- Values of __index,add, .. are called metamethods.
-- Full list. Here a is a table with the metamethod.

-- __add(a, b)                     for a + b
-- __sub(a, b)                     for a - b
-- __mul(a, b)                     for a * b
-- __div(a, b)                     for a / b
-- __mod(a, b)                     for a % b
-- __pow(a, b)                     for a ^ b
-- __unm(a)                        for -a
-- __concat(a, b)                  for a .. b
-- __len(a)                        for #a
-- __eq(a, b)                      for a == b
-- __lt(a, b)                      for a < b
-- __le(a, b)                      for a <= b
-- __index(a, b)  <fn or a table>  for a.b
-- __newindex(a, b, c)             for a.b = c
-- __call(a, ...)                  for a(...)

----------------------------------------------------
-- 3.2 Class-like tables and inheritance.
----------------------------------------------------

-- Classes aren't built in; there are different ways
-- to make them using tables and metatables.

-- Explanation for this example is below it.

Dog = {}                                   -- 1.

function Dog:new()                         -- 2.
  newObj = {sound = 'woof'}                -- 3.
  self.__index = self                      -- 4.
  return setmetatable(newObj, self)        -- 5.
end

function Dog:makeSound()                   -- 6.
  print('I say ' .. self.sound)
end

mrDog = Dog:new()                          -- 7.
mrDog:makeSound()  -- 'I say woof'         -- 8.

-- 1. Dog acts like a class; it's really a table.
-- 2. function tablename:fn(...) is the same as
--    function tablename.fn(self, ...)
--    The : just adds a first arg called self.
--    Read 7 & 8 below for how self gets its value.
-- 3. newObj will be an instance of class Dog.
-- 4. self = the class being instantiated. Often
--    self = Dog, but inheritance can change it.
--    newObj gets self's functions when we set both
--    newObj's metatable and self's __index to self.
-- 5. Reminder: setmetatable returns its first arg.
-- 6. The : works as in 2, but this time we expect
--    self to be an instance instead of a class.
-- 7. Same as Dog.new(Dog), so self = Dog in new().
-- 8. Same as mrDog.makeSound(mrDog); self = mrDog.

----------------------------------------------------

-- Inheritance example:

LoudDog = Dog:new()                           -- 1.

function LoudDog:makeSound()
  s = self.sound .. ' '                       -- 2.
  print(s .. s .. s)
end

seymour = LoudDog:new()                       -- 3.
seymour:makeSound()  -- 'woof woof woof'      -- 4.

-- 1. LoudDog gets Dog's methods and variables.
-- 2. self has a 'sound' key from new(), see 3.
-- 3. Same as LoudDog.new(LoudDog), and converted to
--    Dog.new(LoudDog) as LoudDog has no 'new' key,
--    but does have __index = Dog on its metatable.
--    Result: seymour's metatable is LoudDog, and
--    LoudDog.__index = LoudDog. So seymour.key will
--    = seymour.key, LoudDog.key, Dog.key, whichever
--    table is the first with the given key.
-- 4. The 'makeSound' key is found in LoudDog; this
--    is the same as LoudDog.makeSound(seymour).

-- If needed, a subclass's new() is like the base's:
function LoudDog:new()
  newObj = {}
  -- set up newObj
  self.__index = self
  return setmetatable(newObj, self)
end

----------------------------------------------------
-- 4. Modules.
----------------------------------------------------


--[[ I'm commenting out this section so the rest of
--   this script remains runnable.
-- Suppose the file mod.lua looks like this:
local M = {}

local function sayMyName()
  print('Hrunkner')
end

function M.sayHello()
  print('Why hello there')
  sayMyName()
end

return M

-- Another file can use mod.lua's functionality:
local mod = require('mod')  -- Run the file mod.lua.

-- require is the standard way to include modules.
-- require acts like:     (if not cached; see below)
local mod = (function ()
  <contents of mod.lua>
end)()
-- It's like mod.lua is a function body, so that
-- locals inside mod.lua are invisible outside it.

-- This works because mod here = M in mod.lua:
mod.sayHello()  -- Says hello to Hrunkner.

-- This is wrong; sayMyName only exists in mod.lua:
mod.sayMyName()  -- error

-- require's return values are cached so a file is
-- run at most once, even when require'd many times.

-- Suppose mod2.lua contains "print('Hi!')".
local a = require('mod2')  -- Prints Hi!
local b = require('mod2')  -- Doesn't print; a=b.

-- dofile is like require without caching:
dofile('mod2.lua')  --> Hi!
dofile('mod2.lua')  --> Hi! (runs it again)

-- loadfile loads a lua file but doesn't run it yet.
f = loadfile('mod2.lua')  -- Call f() to run it.

-- loadstring is loadfile for strings.
g = loadstring('print(343)')  -- Returns a function.
g()  -- Prints out 343; nothing printed before now.

--]]

----------------------------------------------------
-- 5. References.
----------------------------------------------------

--[[

I was excited to learn Lua so I could make games
with the Löve 2D game engine. That's the why.

I started with BlackBulletIV's Lua for programmers.
Next I read the official Programming in Lua book.
That's the how.

It might be helpful to check out the Lua short
reference on lua-users.org.

The main topics not covered are standard libraries:
 * string library
 * table library
 * math library
 * io library
 * os library

By the way, this entire file is valid Lua; save it
as learn.lua and run it with "lua learn.lua" !

This was first written for tylerneylon.com. It's
also available as a github gist. Tutorials for other
languages, in the same style as this one, are here:

https://learnxinyminutes.com/

Have fun with Lua!

--]]

Tyler Neylon
336.2013