Версия:

Руководство пользователя / Сервер приложений / Примеры и рекомендации по разработке
Руководство пользователя / Сервер приложений / Примеры и рекомендации по разработке

Примеры и рекомендации по разработке

Примеры и рекомендации по разработке

Ниже представлены дополнения в виде Lua-программ для часто встречающихся или сложных случаев.

Любую из этих программ можно выполнить, скопировав код в .lua-файл, а затем выполнив в командной строке chmod +x ./program-name.lua и :samp :./{program-name}.lua.

Первая строка – это шебанг:

#!/usr/bin/env tarantool

Он запускает сервер приложений Tarantool’а на языке Lua, который должен быть в пути выполнения.

This section contains the following recipes:

Можно использовать свободно.

hello_world.lua

Стандартный пример простой программы.

#!/usr/bin/env tarantool

        print('Hello, World!')

console_start.lua

Для инициализации базы данных (создания спейсов) используйте box.once(), если сервер запускается впервые. Затем используйте console.start(), чтобы запустить интерактивный режим.

#!/usr/bin/env tarantool

        -- Настроить базу данных
        box.cfg {
            listen = 3313
        }

        box.once("bootstrap", function()
            box.schema.space.create('tweedledum')
            box.space.tweedledum:create_index('primary',
                { type = 'TREE', parts = {1, 'unsigned'}})
        end)

        require('console').start()

fio_read.lua

Используйте Модуль fio, чтобы открыть, прочитать и закрыть файл.

#!/usr/bin/env tarantool

        local fio = require('fio')
        local errno = require('errno')
        local f = fio.open('/tmp/xxxx.txt', {'O_RDONLY' })
        if not f then
            error("Failed to open file: "..errno.strerror())
        end
        local data = f:read(4096)
        f:close()
        print(data)

fio_write.lua

Используйте Модуль fio, чтобы открыть, записать данные и закрыть файл.

#!/usr/bin/env tarantool

        local fio = require('fio')
        local errno = require('errno')
        local f = fio.open('/tmp/xxxx.txt', {'O_CREAT', 'O_WRONLY', 'O_APPEND'},
            tonumber('0666', 8))
        if not f then
            error("Failed to open file: "..errno.strerror())
        end
        f:write("Hello\n");
        f:close()

ffi_printf.lua

Используйте Библиотеку LuaJIT FFI, чтобы вызвать встроенную в C функцию: printf(). (Чтобы лучше понимать FFI, см. Учебное пособие по FFI.)

#!/usr/bin/env tarantool

        local ffi = require('ffi')
        ffi.cdef[[
            int printf(const char *format, ...);
        ]]

        ffi.C.printf("Hello, %s\n", os.getenv("USER"));

ffi_gettimeofday.lua

Используйте Библиотеку LuaJIT FFI, чтобы вызвать встроенную в C функцию: gettimeofday(). Она позволяет получить значение времени с точностью в миллисекундах, в отличие от функции времени в Tarantool’е Модуль clock.

#!/usr/bin/env tarantool

        local ffi = require('ffi')
        ffi.cdef[[
            typedef long time_t;
            typedef struct timeval {
            time_t tv_sec;
            time_t tv_usec;
        } timeval;
            int gettimeofday(struct timeval *t, void *tzp);
        ]]

        local timeval_buf = ffi.new("timeval")
        local now = function()
            ffi.C.gettimeofday(timeval_buf, nil)
            return tonumber(timeval_buf.tv_sec * 1000 + (timeval_buf.tv_usec / 1000))
        end

ffi_zlib.lua

Используйте Библиотеку LuaJIT FFI, чтобы вызвать библиотечную функцию в C. (Чтобы лучше понимать FFI, см. Учебное пособие по FFI.)

#!/usr/bin/env tarantool

        local ffi = require("ffi")
        ffi.cdef[[
            unsigned long compressBound(unsigned long sourceLen);
            int compress2(uint8_t *dest, unsigned long *destLen,
            const uint8_t *source, unsigned long sourceLen, int level);
            int uncompress(uint8_t *dest, unsigned long *destLen,
            const uint8_t *source, unsigned long sourceLen);
        ]]
        local zlib = ffi.load(ffi.os == "Windows" and "zlib1" or "z")

        -- Надстройка Lua для функции compress2()
        local function compress(txt)
            local n = zlib.compressBound(#txt)
            local buf = ffi.new("uint8_t[?]", n)
            local buflen = ffi.new("unsigned long[1]", n)
            local res = zlib.compress2(buf, buflen, txt, #txt, 9)
            assert(res == 0)
            return ffi.string(buf, buflen[0])
        end

        -- Надстройка Lua для функции uncompress
        local function uncompress(comp, n)
            local buf = ffi.new("uint8_t[?]", n)
            local buflen = ffi.new("unsigned long[1]", n)
            local res = zlib.uncompress(buf, buflen, comp, #comp)
            assert(res == 0)
            return ffi.string(buf, buflen[0])
        end

        — Простой код тестов.
        local txt = string.rep("abcd", 1000)
        print("Uncompressed size: ", #txt)
        local c = compress(txt)
        print("Compressed size: ", #c)
        local txt2 = uncompress(c, #txt)
        assert(txt2 == txt)

ffi_meta.lua

Используйте Библиотеку LuaJIT FFI, чтобы получить доступ к объекту в C с помощью метаметода (метод, который определен метатаблицей).

#!/usr/bin/env tarantool

        local ffi = require("ffi")
        ffi.cdef[[
        typedef struct { double x, y; } point_t;
        ]]

        local point
        local mt = {
          __add = function(a, b) return point(a.x+b.x, a.y+b.y) end,
          __len = function(a) return math.sqrt(a.x*a.x + a.y*a.y) end,
          __index = {
            area = function(a) return a.x*a.x + a.y*a.y end,
          },
        }
        point = ffi.metatype("point_t", mt)

        local a = point(3, 4)
        print(a.x, a.y)  --> 3  4
        print(#a)        --> 5
        print(a:area())  --> 25
        local b = a + point(0.5, 8)
        print(#b)        --> 12.5

count_array.lua

Используйте оператор „#“, чтобы получить количество элементов в Lua-таблице типа массива. У этой операции сложность O(log(N)).

#!/usr/bin/env tarantool

        array = { 1, 2, 3}
        print(#array)

count_array_with_nils.lua

Отсутствующие элементы в массивах, которые Lua рассматривает как nil, заставляют простой оператор „#“ выдавать неправильные результаты. Команда «print(#t)» выведет «4», команда «print(counter)» выведет «3», а команда «print(max)» – «10». Другие табличные функции, такие как table.sort(), также сработают неправильно при наличии значений nils.

#!/usr/bin/env tarantool

        local t = {}
        t[1] = 1
        t[4] = 4
        t[10] = 10
        print(#t)
        local counter = 0
        for k,v in pairs(t) do counter = counter + 1 end
        print(counter)
        local max = 0
        for k,v in pairs(t) do if k > max then max = k end end
        print(max)

count_array_with_nulls.lua

Используйте явные значения``NULL``, чтобы избежать проблем, вызванных nil в Lua == поведение с пропущенными значениями. Хотя json.NULL == nil является true, все команды вывода в данной программе выведут правильное значение: 10.

#!/usr/bin/env tarantool

        local json = require('json')
        local t = {}
        t[1] = 1; t[2] = json.NULL; t[3]= json.NULL;
        t[4] = 4; t[5] = json.NULL; t[6]= json.NULL;
        t[6] = 4; t[7] = json.NULL; t[8]= json.NULL;
        t[9] = json.NULL
        t[10] = 10
        print(#t)
        local counter = 0
        for k,v in pairs(t) do counter = counter + 1 end
        print(counter)
        local max = 0
        for k,v in pairs(t) do if k > max then max = k end end
        print(max)

count_map.lua

Программа используется для получения количества элементов в таблице типа ассоциативного массива.

#!/usr/bin/env tarantool

        local map = { a = 10, b = 15, c = 20 }
        local size = 0
        for _ in pairs(map) do size = size + 1; end
        print(size)

swap.lua

Программа использует особенность Lua менять местами две переменные без необходимости использования третьей переменной.

#!/usr/bin/env tarantool

        local x = 1
        local y = 2
        x, y = y, x
        print(x, y)

class.lua

Используется для создания класса, метатаблицы для класса, экземпляра класса. Другой пример можно найти в http://lua-users.org/wiki/LuaClassesWithMetatable.

#!/usr/bin/env tarantool

        -- определить объекты класса
        local myclass_somemethod = function(self)
            print('test 1', self.data)
        end

        local myclass_someothermethod = function(self)
            print('test 2', self.data)
        end

        local myclass_tostring = function(self)
            return 'MyClass <'..self.data..'>'
        end

        local myclass_mt = {
            __tostring = myclass_tostring;
            __index = {
                somemethod = myclass_somemethod;
                someothermethod = myclass_someothermethod;
            }
        }

        -- создать новый объект своего класса myclass
        local object = setmetatable({ data = 'data'}, myclass_mt)
        print(object:somemethod())
        print(object.data)

garbage.lua

Activate the Lua garbage collector with the collectgarbage function.

#!/usr/bin/env tarantool

        collectgarbage('collect')

fiber_producer_and_consumer.lua

Запустите один файбер для производителя и один файбер для потребителя. Используйте fiber.channel() для обмена данных и синхронизации. Можно настроить ширину канала (ch_size в программном коде) для управления количеством одновременных задач к обработке.

#!/usr/bin/env tarantool

        local fiber = require('fiber')
        local function consumer_loop(ch, i)
            -- инициализировать потребитель синхронно или выдать ошибку()
            fiber.sleep(0) -- позволить fiber.create() продолжать
            while true do
                local data = ch:get()
                if data == nil then
                    break
                end
                print('consumed', i, data)
                fiber.sleep(math.random()) -- моделировать работу
            end
        end

        local function producer_loop(ch, i)
            -- инициализировать потребитель синхронно или выдать ошибку()
            fiber.sleep(0) -- позволить fiber.create() продолжать
            while true do
                local data = math.random()
                ch:put(data)
                print('produced', i, data)
            end
        end

        local function start()
            local consumer_n = 5
            local producer_n = 3

            — создать канал
            local ch_size = math.max(consumer_n, producer_n)
            local ch = fiber.channel(ch_size)

            -- запустить потребители
            for i=1, consumer_n,1 do
                fiber.create(consumer_loop, ch, i)
            end

            -- запустить производители
            for i=1, producer_n,1 do
                fiber.create(producer_loop, ch, i)
            end
        end

        start()
        print('started')

socket_tcpconnect.lua

Используйте socket.tcp_connect() для подключения к удаленному серверу по TCP. Можно отобразить информацию о подключении и результат запроса GET.

#!/usr/bin/env tarantool

        local s = require('socket').tcp_connect('google.com', 80)
        print(s:peer().host)
        print(s:peer().family)
        print(s:peer().type)
        print(s:peer().protocol)
        print(s:peer().port)
        print(s:write("GET / HTTP/1.0\r\n\r\n"))
        print(s:read('\r\n'))
        print(s:read('\r\n'))

socket_tcp_echo.lua

Используйте socket.tcp_connect() для настройки простого TCP-сервера путем создания функции, которая обрабатывает запросы и отражает их, а затем передачи функции на socket.tcp_server(). Данная программа была протестирована на 100 000 клиентов, каждый из которых получил отдельный файбер.

#!/usr/bin/env tarantool

        local function handler(s, peer)
            s:write("Welcome to test server, " .. peer.host .."\n")
            while true do
                local line = s:read('\n')
                if line == nil then
                    break -- ошибка или eof
                end
                if not s:write("pong: "..line) then
                    break -- ошибка или eof
                end
            end
        end

        local server, addr = require('socket').tcp_server('localhost', 3311, handler)

getaddrinfo.lua

Используйте socket.getaddrinfo(), чтобы провести неблокирующее разрешение имен DNS, получая как AF_INET6, так и AF_INET информацию для „google.com“. Данная техника не всегда необходима для TCP-соединений, поскольку socket.tcp_connect() выполняет socket.getaddrinfo с точки зрения внутреннего устройства до попытки соединения с первым доступным адресом.

#!/usr/bin/env tarantool

        local s = require('socket').getaddrinfo('google.com', 'http', { type = 'SOCK_STREAM' })
        print('host=',s[1].host)
        print('family=',s[1].family)
        print('type=',s[1].type)
        print('protocol=',s[1].protocol)
        print('port=',s[1].port)
        print('host=',s[2].host)
        print('family=',s[2].family)
        print('type=',s[2].type)
        print('protocol=',s[2].protocol)
        print('port=',s[2].port)

socket_udp_echo.lua

В данный момент в Tarantool нет функции udp_server, поэтому socket_udp_echo.lua - более сложная программа, чем socket_tcp_echo.lua. Ее можно имплементировать с сокетами и файберами.

#!/usr/bin/env tarantool

        local socket = require('socket')
        local errno = require('errno')
        local fiber = require('fiber')

        local function udp_server_loop(s, handler)
            fiber.name("udp_server")
            while true do
                -- попытка прочитать сначала датаграмму
                local msg, peer = s:recvfrom()
                if msg == "" then
                    -- сокет был закрыт с помощью s:close()
                    break
                elseif msg ~= nil then
                    -- получена новая датаграмма
                    handler(s, peer, msg)
                else
                    if s:errno() == errno.EAGAIN or s:errno() == errno.EINTR then
                        -- сокет не готов
                        s:readable() -- возврат, epoll сообщит, когда будут новые данные
                    else
                        -- ошибка сокета
                        local msg = s:error()
                        s:close() -- сохранить ресурсы и не ждать сборку мусора
                        error("Socket error: " .. msg)
                    end
                end
            end
        end

        local function udp_server(host, port, handler)
            local s = socket('AF_INET', 'SOCK_DGRAM', 0)
            if not s then
                return nil -- проверить номер ошибки errno:strerror()
            end
            if not s:bind(host, port) then
                local e = s:errno() -- сохранить номер ошибки errno
                s:close()
                errno(e) -- восстановить номер ошибки errno
                return nil -- проверить номер ошибки errno:strerror()
            end

            fiber.create(udp_server_loop, s, handler) -- запустить новый файбер в фоновом режиме
            return s
        end

Функция для клиента, который подключается к этому серверу, может выглядеть следующим образом:

local function handler(s, peer, msg)
            -- Необязательно ждать, пока сокет будет готов отправлять UDP
            -- s:writable()
            s:sendto(peer.host, peer.port, "Pong: " .. msg)
        end

        local server = udp_server('127.0.0.1', 3548, handler)
        if not server then
            error('Failed to bind: ' .. errno.strerror())
        end

        print('Started')

        require('console').start()

http_get.lua

Используйте Модуль HTTP для получения данных по HTTP.

#!/usr/bin/env tarantool

        local http_client = require('http.client')
        local json = require('json')
        local r = http_client.get('http://api.openweathermap.org/data/2.5/weather?q=Oakland,us')
        if r.status ~= 200 then
            print('Failed to get weather forecast ', r.reason)
            return
        end
        local data = json.decode(r.body)
        print('Oakland wind speed: ', data.wind.speed)

http_send.lua

Используйте Модуль HTTP для отправки данных по HTTP.

#!/usr/bin/env tarantool

        local http_client = require('http.client')
        local json = require('json')
        local data = json.encode({ Key = 'Value'})
        local headers = { Token = 'xxxx', ['X-Secret-Value'] = 42 }
        local r = http_client.post('http://localhost:8081', data, { headers = headers})
        if r.status == 200 then
            print 'Success'
        end

http_server.lua

Используйте сторонний модуль http (который необходимо предварительно установить), чтобы превратить Tarantool в веб-сервер.

#!/usr/bin/env tarantool

        local function handler(self)
            return self:render{ json = { ['Your-IP-Is'] = self.peer.host } }
        end

        local server = require('http.server').new(nil, 8080) -- анализировать связь с *:8080
        server:route({ path = '/' }, handler)
        server:start()
        -- подключиться к localhost:8080 и читать JSON

http_generate_html.lua

Используйте сторонний модуль http (который необходимо предварительно установить) для создания HTML-страниц из шаблонов. В `модуле http`_ достаточно простой движок шаблонов, который позволяет выполнять регулярный код на Lua в текстовых блоках (как в PHP). Таким образом, нет необходимости в изучении новых языков, чтобы написать шаблоны.

#!/usr/bin/env tarantool

        local function handler(self)
        local fruits = { 'Apple', 'Orange', 'Grapefruit', 'Banana'}
            return self:render{ fruits = fruits }
        end

        local server = require('http.server').new(nil, 8080) -- nil означает '*'
        server:route({ path = '/', file = 'index.html.lua' }, handler)
        server:start()

HTML-файл для этого сервера, включая Lua, может выглядеть следующим образом (он выведет «1 Apple | 2 Orange | 3 Grapefruit | 4 Banana»).

<html>
        <body>
            <table border="1">
                % for i,v in pairs(fruits) do
                <tr>
                    <td><%= i %></td>
                    <td><%= v %></td>
                </tr>
                % end
            </table>
        </body>
        </html>