Skip to content

fangfengxiang/lua-yar

Repository files navigation

Lua-Yar — Yar Lua Framework

English | 简体中文 | Documentation

Lua License LuaRocks OPM Test codecov Release

Lightweight, concurrent Lua RPC framework. A zero-dependency, coroutine-friendly, multi-host compatible lightweight RPC framework — pure Lua implementation of the Yar RPC protocol. See Protocol Specification.

Yar (Yet Another RPC Framework) is a popular lightweight concurrent RPC framework in the PHP ecosystem, enabling cross-language interoperability via binary data streams. Related implementations: Yar-C, lua-resty-yar.

Compared to HTTP+JSON, Yar's binary protocol stream eliminates text encoding/decoding overhead; compared to gRPC/Thrift, it requires no IDL compilation or heavy runtime — ideal for lightweight RPC needs in Lua embedded scenarios.


Features

  • Light & Efficient: Zero-dependency, binary protocol, embeddable, easy to use
  • Multi-transport: TCP / HTTP / Unix Socket
  • Protocol Compatible: Strictly adheres to the YAR binary protocol, interoperable with PHP / C / Lua Yar services
  • Cross-environment: Standard Lua + luasocket out of the box; runs on OpenResty / Skynet / lua-eco / copas and other host environments (see Server Implementation Examples)
  • Coroutine-friendly: Pure functions with no I/O and no yield, callable directly from any coroutine
  • Zero-dependency & Extensible: Built-in pure Lua JSON / Msgpack codecs (no cjson / cmsgpack dependency); supports registering custom packagers, injecting HTTP providers, and Hooks interception

Requirements

  • Lua 5.1 / 5.3 / LuaJIT 2.1 / OpenResty 1.7+
  • Network layer: choose by host environment
    • Standard Lua requires luasocket (luarocks install luasocket)
    • OpenResty has built-in ngx.socket, no extra dependency needed

Installation

LuaRocks (Quick Trial)

# Soft dependency: standard Lua requires luasocket (OpenResty does not)
# If not installed, run first: luarocks install luasocket
luarocks install lua-yar

OPM (OpenResty) (Production Recommended)

opm install lua-yar

Source (Not Recommended)

Add src/yar/ to package.path, no compilation needed:

package.path = package.path .. ";/path/to/lua-yar/src/?.lua;/path/to/lua-yar/src/?/init.lua"
local Yar = require("yar")

Quick Start

New to Yar? Read the Tutorial to understand the YAR protocol from scratch and build your first RPC service.

Client

local Yar = require("yar")

local client = Yar.client.new("http://127.0.0.1:8888/api")
print(client:call("add", {1, 2}))              -- => 3

Server (Native Lua)

local Yar = require("yar")

local server = Yar.server.new({
    add = function(a, b) return a + b end,
})

server:listen("http://0.0.0.0:8888")
server:loop()

Server (OpenResty)

http {
    lua_package_path "/path/to/lua-yar/src/?.lua;/path/to/lua-yar/src/?/init.lua;;";

    server {
        listen 8888;
        location /api {
            content_by_lua_block {
                local Yar = require "yar"
                -- OpenResty uses content_by_lua callback mode: no socket, no HttpServer transport layer
                local server = Yar.server.new({
                    add = function(a, b) return a + b end,
                })
                ngx.req.read_body()
                server:handle({
                    method = ngx.req.get_method(),
                    data = ngx.req.get_body_data() or "",
                    writer = function(status, headers, body)
                        ngx.status = status
                        for k, v in pairs(headers) do ngx.header[k] = v end
                        ngx.print(body)
                    end,
                })
            }
        }
    }
}

On Concurrency: Lua is typically embedded to collaborate with other systems, and native concurrency solutions are limited. lua-yar delegates concurrency to the host environment. See Server Implementation Examples for details. For production, lua-resty-yar (OpenResty binding, ready out of the box) is recommended.

Options Example

Client

local client = Yar.client.new("http://127.0.0.1:8888/api")
client:set_options({
    packager        = Yar.PACKAGER_MSGPACK,   -- Serialization format: JSON (default) or MSGPACK
    timeout         = 3000,                    -- Read/write timeout (ms)
    connect_timeout = 2000,                    -- Connect timeout (ms)
    provider        = "my-app",                -- Request source identifier (written to protocol header, max 32 bytes)
    token           = "secret-token",          -- Auth token (written to protocol header, max 32 bytes)
    headers         = { ["X-Trace"] = "abc" }, -- Custom HTTP headers (HTTP/HTTPS only)
    proxy           = "http://proxy:8080",     -- HTTP proxy address
    resolve         = "host:ip",              -- Custom DNS (curl-style host:port:ip or PHP-style host:ip)
    ssl_verify      = true,                   -- HTTPS certificate verification (default true)
    persistent      = false,                  -- Persistent TCP connection (reused across calls)
    keepalive       = {                       -- cosocket connection pool (OpenResty)
        pool_size    = 64,                     --   pool size
        idle_timeout = 60000,                 --   idle timeout (ms)
    },
    hooks = {                                 -- Request/response interception (pcall-protected, zero overhead)
        on_request  = function(method, params) end,
        on_response = function(method, retval, err) end,
    },
})

local ret, err = client:call("add", {1, 2})
if not ret then
    -- err is a Yar.error object; match error type precisely via err.code
    if err.code == Yar.error.TIMEOUT then
        -- timeout handling
    end
end

Server

-- Process-level packager options (JSON/Msgpack decode depth limits, only effective for built-in pure Lua implementations)
Yar.set_options({
    packager = {
        json_max_depth    = 512,   -- Max JSON nesting depth
        msgpack_max_depth = 512,   -- Max Msgpack nesting depth
    },
})

-- To accelerate decoding with C extensions, register to replace the built-in pure Lua implementations:
Yar.register_packager(Yar.PACKAGER_JSON, require("cjson"))
-- Yar.register_packager(Yar.PACKAGER_MSGPACK, require("cmsgpack"))

local server = Yar.server.new({
    add   = function(a, b) return a + b end,
    greet = function(name) return "hello, " .. name end,
})

server:set_options({
    packager     = Yar.PACKAGER_JSON,    -- GET introspection response encoding (POST response determined by client request header)
    timeout      = 5000,                 -- Per-message timeout (standalone run mode)
    max_body_len = 1048576,              -- Max request body length (bytes)
    hooks = {                            -- Request/response interception
        on_request  = function(method, params) end,
        on_response = function(method, retval, err) end,
    },
})

→ Full options: API Reference

Deployment Guide

lua-yar is a pure protocol library and does not provide concurrency scheduling itself; concurrency relies on the host environment.

Environment Concurrency Approach Example
OpenResty cosocket + content_by_lua server_openresty.lua
lua-eco coroutine scheduling server_luaeco.lua
Skynet Skynet scheduler server_skynet.lua
copas copas coroutine server_copas.lua
Native coroutine coroutine + socket.select server_coroutine.lua

For production, lua-resty-yar (OpenResty binding, ready out of the box) is recommended.

Testing

Type Command Description
BDD Unit/Integration busted --coverage 126 test cases, full module coverage (Lua 5.1 / LuaJIT / 5.3 matrix)
OpenResty E2E resty test/openresty_e2e_test.lua cosocket TCP round-trip, connection pool, concurrency safety, fault injection
OpenResty HTTP E2E bash test/openresty_http_e2e.sh nginx content_by_lua full pipeline
Interoperability bash test/interop.sh Lua ↔ PHP Yar bidirectional end-to-end (JSON + Msgpack)
Concurrent bash test/concurrent_e2e.sh PHP → Lua concurrent (3 native sequential + 50 OpenResty 2-worker), HTTP + TCP, requestId integrity + coroutine log verification
Benchmark lua test/benchmark.lua Codec / protocol / cosocket I/O benchmarks

CI matrix: 4 jobs (test multi-version Lua / no-luasocket soft-dependency degradation / openresty E2E / interop PHP interop).

→ Full test system (type breakdown, coverage dimensions, CI matrix, gap planning): Test Coverage Report

Module Overview

Module Description
Client yar-client, HTTP / HTTPS / TCP / Unix transport, table-driven options, structured Error
Server Unified Server Facade: TCP/HTTP/host mode, Server.new(service, opts) + handle(spec) / listen(addr) + loop()
Packager Built-in JSON / Msgpack (pure Lua), configurable via Yar.set_options and Yar.register_packager
Error Structured error object, 5-level error codes (TRANSPORT / TIMEOUT / PROTOCOL / NOT_FOUND / EXCEPTION)
Log 4-level logging, injectable writer
Hooks Lightweight request/response interception mechanism

→ Full API details: API Reference

Directory Structure

lua-yar/
├── README.md                    # English README (primary)
├── README.zh.md                 # Chinese README
├── LICENSE
├── CHANGELOG.md                 # Changelog (Keep a Changelog format)
├── lua-yar-scm-1.rockspec       # Dev rockspec (luarocks make)
├── lua-yar-0.1.0-1.rockspec     # Release rockspec (luarocks install)
├── dist.ini                     # OPM packaging config (opm build)
├── docs/
│   ├── api.md                   # API reference (full method signatures + examples)
│   ├── protocol.md              # YAR protocol spec (byte-level layout + packet examples)
│   ├── design-rationale.md      # Design rationale
│   ├── design/                  # Design decision records (ADR)
│   └── reports/                 # Review reports
│       ├── architecture-review.md
│       ├── comprehensive-review.md
│       ├── performance-benchmark.md
│       └── test-coverage.md
├── src/
│   └── yar/
│       ├── init.lua             # Main entry (Client, Server, register_packager, get_packager, set_options, Error, VERSION, PACKAGER_*)
│       ├── util.lua             # Byte-order utilities
│       ├── error.lua            # Structured error object (Error.new + code constants)
│       ├── client.lua           # yar-client
│       ├── server/
│       │   ├── init.lua         # Server Facade (unified entry: new/handle/listen/loop)
│       │   ├── constants.lua    # Server constants (max_body_len, max_headers, etc.)
│       │   ├── dispatcher.lua   # Protocol dispatcher (handle_message, register_service)
│       │   ├── daemon.lua       # Accept loop (Daemon.run, sequential)
│       │   ├── http.lua         # HTTP transport (socket mode + callback mode)
│       │   └── tcp.lua          # TCP transport (socket mode)
│       ├── protocol/
│       │   ├── header.lua       # 82-byte protocol header
│       │   ├── protocol.lua     # Message render/parse
│       │   └── framing.lua      # YAR frame unpacking (for TCP transport)
│       ├── message/
│       │   ├── request.lua      # Request {i,m,p}
│       │   └── response.lua     # Response {i,s,r,o,e}
│       ├── packager/
│       │   ├── packager.lua     # Packager factory
│       │   ├── json.lua         # Pure Lua JSON
│       │   └── msgpack.lua      # Pure Lua MessagePack
│       └── transport/
│           ├── transport.lua    # Transport factory / socket provider management
│           ├── constants.lua    # HTTP constants (status, content-type, methods) — leaf module
│           ├── socket.lua       # Socket provider (luasocket adapter / inject cosocket / bind)
│           ├── resolve.lua      # DNS resolution utility (curl/PHP-style host→IP mapping)
│           ├── http.lua         # HTTP transport
│           └── tcp.lua          # TCP transport
├── example/
│   ├── client.lua
│   ├── server_http.lua
│   ├── server_tcp.lua
│   ├── server_copas.lua         # copas coroutine concurrency example
│   ├── server_coroutine.lua     # native coroutine concurrency example (no copas dependency)
│   ├── server_luaeco.lua        # lua-eco coroutine concurrency example
│   ├── server_skynet.lua        # Skynet high-concurrency example
│   ├── server_openresty.lua     # OpenResty stream cosocket example
│   ├── resty_http_provider.lua  # lua-resty-http adapter example
│   ├── hooks.lua                # hook usage example (log observation + timing stats)
│   └── resty_yar_*.lua          # OpenResty OPM package examples
├── spec/                        # BDD unit/integration tests (126 cases, see docs/reports/test-coverage.md)
└── test/                        # E2E / interoperability / performance benchmarks

YAR Protocol

Yar exchanges RPC messages via binary data streams. A complete message consists of three parts:

+-------------------+-------------------+---------------------+
| Packager Name     | Yar Header        | Body                |
| 8 bytes           | 82 bytes          | body_len bytes      |
+-------------------+-------------------+---------------------+
  • Request body: {i, m, p} — transaction ID / method name / parameter array
  • Response body: {i, s, r, o, e} — transaction ID / status / return value / output / error
  • Multi-byte integers use network byte order (big-endian)

→ Full protocol spec (field details, byte-level layout, packet examples, cross-language interop): Protocol Specification

Appendix

  • Tutorial (30-minute guide to building an RPC service from scratch, learn by doing)
  • How-to Guide (Find solutions by task: performance optimization, deployment, debugging, extension, protocol interop)
  • Development Guide (LuaLS type annotation conventions, code style guide, project structure)
  • Design Rationale (network layer selection, comparison with yar-c, options comparison)
  • Benchmark Report
  • Changelog

License

Apache License 2.0

About

Light, concurrent, and pure Lua RPC framework compatible with PHP Yar & yar-c, optimized for OpenResty, Skynet, and standalone Lua.

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages