An open API service indexing awesome lists of open source software.

https://github.com/arkenidar/c_lua_http_client

luajit ffi : c and lua http client with libCurl
https://github.com/arkenidar/c_lua_http_client

c client ffi http libcurl lua luajit

Last synced: about 2 months ago
JSON representation

luajit ffi : c and lua http client with libCurl

Awesome Lists containing this project

README

          

# LuaJIT HTTP Client with libcurl

A lightweight, efficient HTTP client for LuaJIT using FFI bindings to libcurl. This library provides a simple and elegant API for making HTTP requests without external dependencies (except libcurl itself).

## Features

- Pure LuaJIT implementation using FFI (no C compilation needed)
- Support for all common HTTP methods (GET, POST, PUT, DELETE, PATCH, HEAD)
- Custom headers support
- Request/response body handling
- Timeout configuration
- SSL/TLS support
- Redirect following
- JSON support (with optional lua-cjson)
- Streaming response handling
- Thread-safe and high-performance

## Requirements

- LuaJIT 2.0+
- libcurl (shared library)
- Windows: `libcurl.dll` or `libcurl-4.dll`
- Linux: `libcurl.so`
- macOS: `libcurl.dylib`
- Optional: `lua-cjson` for JSON encoding/decoding

### Installing libcurl

**Windows (MSYS2/MinGW):**
```bash
pacman -S mingw-w64-x86_64-curl
```

**Linux (Debian/Ubuntu):**
```bash
sudo apt-get install libcurl4-openssl-dev
```

**Linux (Fedora/RHEL):**
```bash
sudo dnf install libcurl-devel
```

**macOS:**
```bash
brew install curl
```

## Installation

Simply copy the files to your project or add them to your LUA_PATH:

```bash
# Copy to your project
cp curl_ffi.lua http_client.lua /path/to/your/project/

# Or set LUA_PATH
export LUA_PATH="$LUA_PATH;/path/to/c_lua_http_client/?.lua"
```

## Quick Start

```lua
local http = require("http_client")

-- Simple GET request
local response, err = http.get("https://api.example.com/users")

if response then
print("Status: " .. response.status)
print("Body: " .. response.body)
print("Content-Type: " .. response.content_type)
else
print("Error: " .. err)
end
```

## API Reference

### Module Functions

#### `http.get(url, options)`
Perform a GET request.

```lua
local response, err = http.get("https://example.com", {
headers = {
["Authorization"] = "Bearer token123"
},
timeout = 10
})
```

#### `http.post(url, options)`
Perform a POST request.

```lua
local response, err = http.post("https://example.com/api", {
body = "key=value",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
})
```

#### `http.put(url, options)`
Perform a PUT request.

#### `http.delete(url, options)`
Perform a DELETE request.

#### `http.patch(url, options)`
Perform a PATCH request.

#### `http.head(url, options)`
Perform a HEAD request.

#### `http.json(method, url, data, options)`
Perform a request with JSON encoding/decoding (requires lua-cjson).

```lua
local response, err = http.json("POST", "https://api.example.com/users", {
name = "John Doe",
email = "john@example.com"
})

if response and response.json then
print("User ID: " .. response.json.id)
end
```

### Client Instance

Create a custom client instance with default options:

```lua
local client = http.new({
timeout = 30,
connect_timeout = 10,
follow_redirects = true,
max_redirects = 10,
user_agent = "MyApp/1.0",
verify_ssl = true,
verbose = false
})

local response, err = client:get("https://example.com")
```

### Request Options

All request methods accept an options table:

```lua
{
-- Request body (string)
body = "request data",

-- Custom headers (table)
headers = {
["Content-Type"] = "application/json",
["Authorization"] = "Bearer token"
},

-- Timeout in seconds
timeout = 30,

-- Connection timeout in seconds
connect_timeout = 10,

-- Follow redirects
follow_redirects = true,

-- Maximum number of redirects
max_redirects = 10,

-- Custom User-Agent
user_agent = "MyApp/1.0",

-- Verify SSL certificates
verify_ssl = true,

-- Verbose output (for debugging)
verbose = false
}
```

### Response Object

Successful requests return a response table:

```lua
{
status = 200, -- HTTP status code
headers = { -- Response headers (lowercase keys)
["content-type"] = "application/json",
["content-length"] = "1234"
},
body = "response body", -- Response body as string
content_type = "application/json", -- Content-Type header value
url = "https://final.url", -- Final URL (after redirects)
json = { ... } -- Parsed JSON (only with http.json())
}
```

Failed requests return `nil, error_message, partial_response`.

## Examples

### Basic GET Request

```lua
local http = require("http_client")

local response, err = http.get("https://httpbin.org/get")
if response then
print("Status:", response.status)
print("Body:", response.body)
end
```

### POST with Form Data

```lua
local response, err = http.post("https://httpbin.org/post", {
body = "name=John&email=john@example.com",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
})
```

### POST with JSON

```lua
local response, err = http.json("POST", "https://httpbin.org/post", {
name = "Alice",
age = 30
})

if response and response.json then
-- Access parsed JSON response
print(response.json.data)
end
```

### Custom Headers

```lua
local response, err = http.get("https://api.example.com/data", {
headers = {
["Authorization"] = "Bearer your-token",
["Accept"] = "application/json",
["X-Custom-Header"] = "value"
}
})
```

### Timeout Handling

```lua
local response, err = http.get("https://slow-server.com", {
timeout = 5, -- 5 second timeout
connect_timeout = 2
})

if not response then
print("Request failed:", err)
end
```

### Custom Client Instance

```lua
local client = http.new({
timeout = 60,
user_agent = "MyBot/2.0",
verify_ssl = false -- Disable SSL verification (not recommended)
})

local response = client:get("https://self-signed-cert.com")
```

### Following Redirects

```lua
local response, err = http.get("https://httpbin.org/redirect/5", {
follow_redirects = true,
max_redirects = 10
})

if response then
print("Final URL:", response.url)
print("Status:", response.status)
end
```

## Error Handling

Always check for errors:

```lua
local response, err, partial = http.get("https://invalid-url")

if not response then
print("Error:", err)
if partial then
print("Partial response available")
print("Status:", partial.status)
end
else
print("Success:", response.status)
end
```

## Low-Level API

For advanced usage, you can use the low-level FFI bindings directly:

```lua
local curl_ffi = require("curl_ffi")

local handle = curl_ffi.init()
curl_ffi.setopt(handle, ffi.C.CURLOPT_URL, "https://example.com")
-- ... more options
local result = curl_ffi.perform(handle)
curl_ffi.cleanup(handle)
```

## File Structure

- [curl_ffi.lua](curl_ffi.lua) - Low-level FFI bindings to libcurl
- [http_client.lua](http_client.lua) - High-level HTTP client wrapper
- [example.lua](example.lua) - Usage examples and test cases

## Performance

This library uses LuaJIT's FFI which provides near-native performance for libcurl operations. The overhead is minimal compared to pure C implementations.

## Thread Safety

Each request creates its own CURL handle, making it safe to use from multiple coroutines. However, the global libcurl initialization is done once at module load time.

## Limitations

- Binary file uploads require manual setup (use low-level API)
- No built-in progress callbacks (can be added using low-level API)
- Multipart form data requires manual construction

## License

This project is provided as-is for educational and commercial use.

## Contributing

Contributions are welcome. Please ensure code follows the existing style and includes examples.

## Troubleshooting

### "Could not load libcurl" error

Make sure libcurl is installed and in your system's library path:

**Windows:** Add the directory containing `libcurl.dll` to your PATH
**Linux/macOS:** Use `ldconfig` or set `LD_LIBRARY_PATH`

### SSL Certificate Errors

If you encounter SSL certificate verification errors:

```lua
-- Disable verification (not recommended for production)
local response = http.get(url, { verify_ssl = false })
```

### Timeout Issues

Adjust timeout values based on your network conditions:

```lua
local response = http.get(url, {
timeout = 60, -- Total request timeout
connect_timeout = 10 -- Connection timeout
})
```

## Version

libcurl version can be checked with:

```lua
local http = require("http_client")
print(http.version())
```