Introdução ao Lua
API7.ai
September 23, 2022
Após uma compreensão geral dos conceitos básicos do NGINX, vamos aprender mais sobre Lua. É a linguagem de programação usada no OpenResty, e é necessário dominar sua sintaxe básica.
Lua é uma linguagem de script pequena e sutil, nascida em um laboratório universitário no Brasil, cujo nome significa "lua bonita" em português. O NGINX nasceu na Rússia, Lua no Brasil, e o OpenResty na China, país de origem do autor. Curiosamente, essas três tecnologias open source igualmente inteligentes vieram de países BRICS, não da Europa ou dos Estados Unidos.
Lua foi projetada para se posicionar como uma linguagem de cola simples, leve e embutível, que não segue o caminho grandioso e ousado. Embora você possa não escrever código Lua diretamente no seu trabalho diário, Lua é amplamente utilizada. Muitos jogos online, como World of Warcraft, usam Lua para escrever plugins; Redis, o banco de dados chave-valor, tem Lua embutido para controlar a lógica.
Por outro lado, embora a biblioteca do Lua seja relativamente simples, ela pode facilmente chamar bibliotecas da linguagem de programação C, e muitos códigos maduros da linguagem C podem ser usados para ela. Por exemplo, no OpenResty, você frequentemente precisará chamar funções da linguagem C do NGINX e do OpenSSL, graças à capacidade do Lua e do LuaJIT de acessar facilmente bibliotecas C.
Aqui, vou te guiar por uma rápida familiarização com os tipos de dados e a sintaxe do Lua para que você possa aprender OpenResty mais suavemente mais tarde.
Ambiente e hello world
Não precisamos instalar especificamente um ambiente Lua 5.1 padrão porque o OpenResty não suporta mais o Lua padrão, apenas o LuaJIT. Observe que a sintaxe do Lua que apresento aqui também é compatível com o LuaJIT e não é baseada no Lua 5.3 mais recente.
Você pode encontrar o diretório e o executável do LuaJIT no diretório de instalação do OpenResty. Estou em um ambiente Mac e usei o brew para instalar o OpenResty, então seu caminho local provavelmente será diferente do seguinte.
$ ll /usr/local/Cellar/openresty/1.13.6.2/luajit/bin/luajit
lrwxr-xr-x 1 ming admin 18B 4 2 14:54 /usr/local/Cellar/openresty/1.13.6.2/luajit/bin/luajit -> luajit-2.1.0-beta3
Você também pode encontrá-lo no diretório de arquivos executáveis do sistema.
$ which luajit
/usr/local/bin/luajit
Verifique a versão do LuaJIT.
$ luajit -v
LuaJIT 2.1.0-beta2 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/
Após verificar essas informações, você pode criar um novo arquivo 1.lua
e usar o LuaJIT para executar o código hello world
.
$ cat 1.lua
print("hello world")
$ luajit 1.lua
hello world
Claro, você também pode usar resty
para executá-lo diretamente, sabendo que ele é finalmente executado com o LuaJIT.
$ resty -e 'print("hello world")'
hello world
Ambas as formas de executar hello world
são possíveis. Eu prefiro a abordagem resty
porque muito código do OpenResty também é executado pelo resty
mais tarde.
Tipos de Dados
Não há muitos tipos de dados em Lua, e você pode retornar o tipo de um valor com a função type
, como no exemplo a seguir.
$ resty -e 'print(type("hello world"))
print(type(print))
print(type(true))
print(type(360.0))
print(type({}))
print(type(nil))
'
As seguintes informações serão impressas.
string
function
boolean
number
table
nil
Esses são os tipos de dados básicos em Lua. Vamos apresentá-los brevemente.
String
Em Lua, a string é um valor imutável. Se você quiser modificar uma string, deve criar uma nova. Essa abordagem tem vantagens e desvantagens: a vantagem é que, mesmo que a mesma string apareça muitas vezes, há apenas uma cópia na memória, mas a desvantagem também é aparente: se você quiser modificar e concatenar strings, cria muitas strings extras desnecessárias.
Vamos usar um exemplo para ilustrar essa desvantagem. Em Lua, usamos dois pontos para indicar a adição de strings. O código a seguir concatena os números de 1 a 10 como strings.
$ resty -e 'local s = ""
for i = 1, 10 do
s = s .. tostring(i)
end
print(s)'
Aqui, fazemos um loop dez vezes, e apenas o último resultado é o que precisamos; as nove novas strings intermediárias são inúteis. Elas não apenas ocupam espaço extra, mas também consomem operações de CPU desnecessárias.
Claro, teremos uma solução para isso mais tarde na seção de otimização de desempenho.
Além disso, em Lua, você tem três maneiras de expressar uma string: aspas simples, aspas duplas e colchetes longos ([[]]
). Os dois primeiros são relativamente fáceis de entender e são geralmente usados em outras linguagens, então para que servem os colchetes longos?
Vamos ver um exemplo concreto.
$ resty -e 'print([[string has \n and \r]])'
string has \n and \r
Você pode ver que as strings nos colchetes longos não são escapadas de forma alguma.
Você pode perguntar: E se a string acima incluir os colchetes longos? A resposta é simples: adicione um ou mais símbolos =
no meio dos colchetes longos.
$ resty -e 'print([=[ string has a [[]]. ]=])'
string has a [[]].
Booleano
Este é simples, true
e false
. Em Lua, apenas nil
e false
são falsos; todo o resto é verdadeiro, incluindo 0 e a string vazia. Podemos verificar isso com o seguinte código.
$ resty -e 'local a = 0
if a then
print("true")
end
a = ""
if a then
print("true")
end'
Esse tipo de julgamento é inconsistente com muitas linguagens de desenvolvimento comuns, então, para evitar erros em tais questões, você pode escrever explicitamente o objeto de comparação, como no exemplo a seguir.
$ resty -e 'local a = 0
if a == false then
print("true")
end
'
Número
O tipo número em Lua é implementado como um número de ponto flutuante de precisão dupla. Vale a pena mencionar que o LuaJIT suporta o modo dual-number
, o que significa que o LuaJIT armazena inteiros como inteiros e números de ponto flutuante como números de ponto flutuante de precisão dupla, dependendo do contexto.
Além disso, o LuaJIT suporta long-long integers
para inteiros grandes, como no exemplo a seguir.
$ resty -e 'print(9223372036854775807LL - 1)'
9223372036854775806LL
Função
Funções são cidadãos de primeira classe em Lua, e você pode armazenar uma função em uma variável ou usá-la como referência de entrada e saída para outra função.
Por exemplo, as duas declarações de função a seguir são exatamente equivalentes.
function foo()
end
e
foo = function ()
end
Tabela
A Tabela é a única estrutura de dados em Lua e é naturalmente muito importante, então dedicarei uma seção especial a ela mais tarde. Podemos começar vendo um exemplo simples de código.
$ resty -e 'local color = {first = "red"}
print(color["first"])'
red
Valor Nulo
Em Lua, um valor nulo é nil
. Se você definir uma variável, mas não atribuir um valor, seu valor padrão será nil
.
$ resty -e 'local a
print(type(a))'
nil
Quando você entrar no sistema OpenResty, encontrará muitos valores nulos, como ngx.null
, e assim por diante. Falaremos mais sobre isso mais tarde.
Os tipos de dados do Lua, vou apresentar principalmente isso, primeiro para te dar uma base. Continuaremos a aprender o que você precisa dominar mais tarde no artigo. Aprender através da prática e do uso é sempre a maneira mais conveniente de absorver novos conhecimentos.
Bibliotecas Padrão Comuns
Muitas vezes, aprender uma linguagem é realmente aprender suas bibliotecas padrão.
Lua é relativamente pequeno e não tem muitas bibliotecas padrão embutidas. Além disso, no ambiente OpenResty, a biblioteca padrão do Lua tem uma prioridade muito baixa. Para a mesma funcionalidade, recomendo usar a API do OpenResty primeiro, as funções da biblioteca do LuaJIT e, por último, as funções normais do Lua.
API do OpenResty > funções da biblioteca do LuaJIT > funções padrão do Lua
é uma prioridade que será mencionada repetidamente em termos de usabilidade e desempenho.
No entanto, apesar disso, inevitavelmente usaremos algumas bibliotecas do Lua em nossos projetos reais. Aqui, selecionei algumas das bibliotecas padrão mais comumente usadas para apresentá-las, e se você quiser saber mais, pode consultar a documentação oficial do Lua.
Biblioteca de Strings
A manipulação de strings é o que frequentemente usamos e onde estão as maiores armadilhas.
Uma regra simples é que, se expressões regulares estiverem envolvidas, use ngx.re.*
fornecido pelo OpenResty para resolvê-las, e não o string.*
do Lua. Isso porque o regex do Lua é único e não está em conformidade com a especificação PCRE, e acredito que a maioria dos engenheiros não conseguirá lidar com isso.
Uma das funções mais comumente usadas da biblioteca de strings é string.byte(s [, i [, j ]])
, que retorna o código ASCII correspondente aos caracteres s[i], s[i + 1], s[i + 2], ------, s[j]
. O valor padrão de i
é 1, o primeiro byte, e o valor padrão de j
é i
.
Vamos ver um exemplo de código.
$ resty -e 'print(string.byte("abc", 1, 3))
print(string.byte("abc", 3)) -- Falta o terceiro parâmetro, o terceiro parâmetro é o mesmo que o segundo por padrão, que é 3
print(string.byte("abc")) -- Faltam o segundo e o terceiro parâmetros, ambos padrão para 1
'
Sua saída é:
979899
99
97
Biblioteca de Tabelas
No contexto do OpenResty, não recomendo usar a maioria das bibliotecas de tabelas que vêm com o Lua, exceto por algumas funções como table.concat
e table.sort
. Quanto aos detalhes delas, deixaremos para o capítulo do LuaJIT.
Aqui, mencionarei brevemente o table.concat
. table.concat
é geralmente usado em cenários de concatenação de strings, como no exemplo abaixo. Ele pode evitar a geração de muitas strings inúteis.
$ resty -e 'local a = {"A", "b", "C"}
print(table.concat(a))'
Biblioteca Matemática
A biblioteca matemática do Lua consiste em um conjunto padrão de funções matemáticas. A introdução da biblioteca matemática enriquece a linguagem de programação Lua e facilita a escrita de programas.
Em projetos OpenResty, raramente usamos Lua para fazer operações matemáticas. Ainda assim, duas funções relacionadas a números aleatórios, math.random()
e math.randomseed()
, são comumente usadas, como no código a seguir, que pode gerar dois números aleatórios em um intervalo especificado.
$ resty -e 'math.randomseed (os.time())
print(math.random())
print(math.random(100))'
Variáveis Dummy
Após entender essas bibliotecas padrão compartilhadas, vamos aprender um novo conceito - variáveis dummy.
Imagine um cenário onde uma função retorna vários valores, alguns dos quais não precisamos, então como devemos receber esses valores?
Não sei como você se sente sobre isso, mas para mim, pelo menos, é torturante tentar dar nomes significativos a essas variáveis não utilizadas.
Felizmente, Lua tem uma solução perfeita para isso, fornecendo o conceito de uma variável dummy, convencionalmente nomeada com um sublinhado, para descartar valores não necessários e servir como um espaço reservado.
Vamos usar a função da biblioteca padrão string.find
como exemplo para ver o uso de variáveis dummy. Essa função da biblioteca padrão retorna dois valores representando os subscritos de início e fim, respectivamente.
Se precisarmos apenas obter o subscrito de início, é simples declarar uma variável para receber o valor de retorno de string.find
como segue.
$ resty -e 'local start = string.find("hello", "he")
print(start)'
1
Mas se você quiser apenas obter o subscrito de fim, então você tem que usar a variável dummy.
$ resty -e 'local _, end_pos = string.find("hello", "he")
print(end_pos)'
2
Além de ser usada em valores de retorno, variáveis dummy são frequentemente usadas em loops, como no exemplo a seguir.
$ resty -e 'for _, v in ipairs({4,5,6}) do
print(v)
end'
4
5
6
E quando há vários valores de retorno para ignorar, você pode reutilizar a mesma variável dummy. Não vou dar um exemplo aqui. Você pode tentar escrever um código de exemplo como esse? Sinta-se à vontade para postar o código na seção de comentários para compartilhar e trocar comigo.
Resumo
Hoje, demos uma olhada rápida nas estruturas de dados e na sintaxe do Lua padrão, e tenho certeza de que você teve um primeiro contato com essa linguagem simples e compacta. Na próxima lição, vou te guiar pela relação entre Lua e LuaJIT, com o LuaJIT sendo o foco principal do OpenResty e valendo a pena explorar.
Por fim, quero deixar mais uma questão para reflexão.
Lembra do código que você aprendeu neste post quando falamos sobre a biblioteca matemática? Ele gera dois números aleatórios em um intervalo especificado.
$ resty -e 'math.randomseed (os.time())
print(math.random())
print(math.random(100))'
No entanto, você pode ter notado que o código é semeado com o timestamp atual. Há algum problema com essa abordagem? E como devemos gerar boas sementes? Muitas vezes, os números aleatórios que desenvolvemos não são aleatórios e têm grandes riscos de segurança.
Sinta-se à vontade para compartilhar suas opiniões conosco e também para encaminhar este post para seus colegas e amigos. Vamos nos comunicar e melhorar juntos.