├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── benchmark └── http_benchmark.lua ├── deps └── .gitignore ├── doc ├── fs.md ├── http.md ├── loop.md ├── parallel.md ├── process.md ├── system.md ├── tcp.md ├── timer.md └── url.md ├── src ├── uv.lua └── uv │ ├── ctypes │ ├── init.lua │ ├── uv_buf_t.lua │ ├── uv_check_t.lua │ ├── uv_connect_t.lua │ ├── uv_fs_t.lua │ ├── uv_getaddrinfo_t.lua │ ├── uv_handle_t.lua │ ├── uv_idle_t.lua │ ├── uv_loop_t.lua │ ├── uv_prepare_t.lua │ ├── uv_process_options_t.lua │ ├── uv_process_t.lua │ ├── uv_signal_t.lua │ ├── uv_stream_t.lua │ ├── uv_tcp_t.lua │ ├── uv_timer_t.lua │ └── uv_write_t.lua │ ├── fs.lua │ ├── http.lua │ ├── init.lua │ ├── lib │ └── .gitignore │ ├── libc.lua │ ├── libhttp_parser.lua │ ├── libuv.lua │ ├── libuv2.c │ ├── libuv2.h │ ├── libuv2.lua │ ├── loop.lua │ ├── parallel.lua │ ├── process.lua │ ├── system.lua │ ├── tcp.lua │ ├── timer.lua │ ├── url.lua │ └── util │ ├── async.lua │ ├── class.lua │ ├── ctype.lua │ ├── errno.lua │ ├── expect.lua │ ├── join.lua │ ├── strict.lua │ └── verify.lua └── test ├── fs_test.lua ├── http_test.lua ├── loop_test.lua ├── parallel_test.lua ├── process_test.lua ├── system_test.lua ├── tcp_test.lua ├── timer_test.lua └── url_test.lua /.gitignore: -------------------------------------------------------------------------------- 1 | /deps 2 | /luajit 3 | /src/uv/lib 4 | *.swp 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | 3 | install: 4 | - make 5 | 6 | script: 7 | - make test 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Preston Guillory 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LUA = ./luajit 2 | LUA_DIR=/usr/local 3 | LUA_LIBDIR=$(LUA_DIR)/lib/lua/5.1 4 | LUA_SHAREDIR=$(LUA_DIR)/share/lua/5.1 5 | 6 | EXT ?= so 7 | ifeq ($(shell uname -s), Darwin) 8 | EXT = dylib 9 | endif 10 | 11 | FILES=src/uv/lib/libuv.$(EXT) \ 12 | src/uv/lib/libuv.min.h \ 13 | src/uv/lib/libuv2.$(EXT) \ 14 | src/uv/lib/libuv2.min.h \ 15 | src/uv/lib/libhttp_parser.$(EXT) \ 16 | src/uv/lib/libhttp_parser.min.h 17 | 18 | all: $(FILES) 19 | 20 | ################################################################################ 21 | # libuv 22 | ################################################################################ 23 | 24 | deps/libuv-v0.11.28.zip: 25 | wget https://github.com/joyent/libuv/archive/v0.11.28.zip -O $@ 26 | 27 | deps/libuv-0.11.28: deps/libuv-v0.11.28.zip 28 | rm -rf $@ 29 | unzip $< -d deps 30 | touch $@ 31 | 32 | deps/libuv: deps/libuv-0.11.28 33 | cd deps && ln -fs libuv-0.11.28 libuv 34 | 35 | deps/libuv/include/uv.h: deps/libuv 36 | deps/libuv/autogen.sh: deps/libuv 37 | 38 | deps/libuv/configure: deps/libuv/autogen.sh 39 | cd deps/libuv && sh autogen.sh 40 | 41 | deps/libuv/Makefile: deps/libuv/configure 42 | cd deps/libuv && ./configure 43 | 44 | deps/libuv/.libs/libuv.a: deps/libuv/Makefile 45 | cd deps/libuv && make 46 | 47 | deps/libuv/.libs/libuv.$(EXT): deps/libuv/.libs/libuv.a 48 | 49 | src/uv/lib/libuv.$(EXT): deps/libuv/.libs/libuv.$(EXT) 50 | cp $+ $@ 51 | 52 | src/uv/lib/libuv.min.h: deps/libuv/include/uv.h 53 | gcc -E $+ | grep -v '^ *#' > $@ 54 | 55 | src/uv/lib/libuv2.dylib: deps/libuv/.libs/libuv.a src/uv/libuv2.c 56 | gcc -dynamiclib $+ -o $@ 57 | 58 | src/uv/lib/libuv2.so: src/uv/libuv2.c deps/libuv/.libs/libuv.so 59 | gcc -g -fPIC -shared $+ -o $@ 60 | 61 | src/uv/lib/libuv2.min.h: src/uv/libuv2.h 62 | gcc -E $+ | grep -v '^ *#' > $@ 63 | 64 | 65 | ################################################################################ 66 | # http-parser 67 | ################################################################################ 68 | 69 | deps/http-parser-v2.3.zip: 70 | wget https://github.com/joyent/http-parser/archive/v2.3.zip -O $@ 71 | 72 | deps/http-parser-2.3: deps/http-parser-v2.3.zip 73 | rm -rf $@ 74 | unzip $< -d deps 75 | touch $@ 76 | 77 | deps/http-parser: deps/http-parser-2.3 78 | cd deps && ln -fs http-parser-2.3 http-parser 79 | 80 | deps/http-parser/http_parser.h: deps/http-parser 81 | 82 | deps/http-parser/libhttp_parser.so.2.3: deps/http-parser 83 | cd deps/http-parser && make library 84 | 85 | src/uv/lib/libhttp_parser.$(EXT): deps/http-parser/libhttp_parser.so.2.3 86 | cp $+ $@ 87 | 88 | src/uv/lib/libhttp_parser.min.h: deps/http-parser/http_parser.h 89 | gcc -E $+ | grep -v '^ *#' > $@ 90 | 91 | ################################################################################ 92 | # luajit 93 | ################################################################################ 94 | 95 | deps/LuaJIT-2.0.3.tar.gz: 96 | cd deps && wget http://luajit.org/download/LuaJIT-2.0.3.tar.gz 97 | 98 | deps/LuaJIT-2.0.3: deps/LuaJIT-2.0.3.tar.gz 99 | rm -rf $@ 100 | cd deps && tar zxf LuaJIT-2.0.3.tar.gz 101 | touch $@ 102 | 103 | deps/LuaJIT-2.0.3/Makefile: deps/LuaJIT-2.0.3 104 | 105 | deps/LuaJIT-2.0.3/src/luajit: deps/LuaJIT-2.0.3/Makefile 106 | cd deps/LuaJIT-2.0.3 && make 107 | 108 | luajit: deps/LuaJIT-2.0.3/src/luajit 109 | cp $+ $@ 110 | 111 | ################################################################################ 112 | # etc... 113 | ################################################################################ 114 | 115 | install: all uninstall 116 | cp -R src/uv ${LUA_SHAREDIR}/ 117 | 118 | uninstall: 119 | rm -rf ${LUA_SHAREDIR}/uv 120 | 121 | clean: 122 | rm -rf deps/* src/uv/lib/* 123 | 124 | test: run-tests 125 | run-tests: $(LUA) 126 | LUA_PATH="src/?.lua;;" find test -name "*_test.lua" -exec luajit "{}" ";" 127 | @echo All tests passing 128 | 129 | benchmark: run-benchmarks 130 | run-benchmarks: $(LUA) 131 | LUA_PATH="src/?.lua;;" find benchmark -name "*_benchmark.lua" -exec luajit "{}" ";" 132 | @echo All tests passing 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | luajit-libuv [![build status](https://travis-ci.org/pguillory/luajit-libuv.svg)](https://travis-ci.org/pguillory/luajit-libuv) 2 | ============ 3 | 4 | This project provides a [LuaJIT FFI] binding to [libuv], the async I/O library 5 | powering [Node.js]. It uses Lua coroutines to provide non-blocking I/O with 6 | synchronous syntax. 7 | 8 | For example, you can build a web server that performs I/O (like reading a file 9 | or talking to a database) while generating each response, and it will process 10 | multiple requests simultaneously. 11 | 12 | ```lua 13 | local http = require 'uv.http' 14 | local fs = require 'uv.fs' 15 | 16 | http.listen('127.0.0.1', 8080, function(request) 17 | return { status = 200, body = fs.readfile('README.md') } 18 | end) 19 | ``` 20 | 21 | Or you can perform multiple HTTP requests simultaneously. 22 | 23 | ```lua 24 | local http = require 'uv.http' 25 | local parallel = require 'uv.parallel' 26 | 27 | local requests = { 28 | { url = 'http://www.google.com/' }, 29 | { url = 'http://www.bing.com/' }, 30 | { url = 'http://www.amazon.com/' }, 31 | } 32 | 33 | local responses = parallel.map(requests, http.request) 34 | ``` 35 | 36 | Status 37 | ------ 38 | 39 | Not production ready. Under active development. The API is unstable. 40 | 41 | Requirements 42 | ------------ 43 | 44 | - [LuaJIT]. Regular Lua won't run it. That said, you probably want LuaJIT 45 | anyway. 46 | 47 | - Standard build tools. 48 | 49 | - [libuv] and [http-parser] are bundled and do not need to be installed 50 | separately. 51 | 52 | Installation 53 | ------------ 54 | 55 | ```bash 56 | git clone https://github.com/pguillory/luajit-libuv.git 57 | cd luajit-libuv 58 | make 59 | make install 60 | ``` 61 | 62 | API Reference 63 | ------------- 64 | 65 | Functions are divided into submodules. Each submodule can either be required directly or accessed indirectly through the `uv` module: 66 | 67 | ```lua 68 | local fs = require 'uv.fs' 69 | 70 | local uv = require 'uv' 71 | local fs = uv.fs 72 | ``` 73 | 74 | * [uv.fs](doc/fs.md) - File system 75 | * [uv.http](doc/http.md) - HTTP client and server 76 | * [uv.loop](doc/loop.md) - Event loop control 77 | * [uv.parallel](doc/parallel.md) - Parallel processing 78 | * [uv.process](doc/process.md) - Process management 79 | * [uv.system](doc/system.md) - System utility functions 80 | * [uv.timer](doc/timer.md) - Timers 81 | * [uv.url](doc/url.md) - URL parsing and encoding 82 | 83 | Contributing 84 | ------------ 85 | 86 | Your contributions are welcome! Please verify that `make test` succeeds and 87 | submit your changes as a pull request. 88 | 89 | See Also 90 | -------- 91 | 92 | Other people have done things like this. 93 | 94 | - [luvit](https://github.com/luvit/luvit) 95 | - [LuaNode](https://github.com/ignacio/LuaNode) 96 | - [lev](https://github.com/connectFree/lev) 97 | - [luv](https://github.com/luvit/luv) 98 | - [luauv](https://github.com/grrrwaaa/luauv) 99 | - [uv](https://github.com/steveyen/uv) 100 | - [lua-uv](https://github.com/bnoordhuis/lua-uv/) 101 | - [Ray](https://github.com/richardhundt/luv/tree/ray) 102 | 103 | [Luajit FFI]: http://luajit.org/ext_ffi.html 104 | [libuv]: https://github.com/joyent/libuv 105 | [Node.js]: http://nodejs.org/ 106 | [luv]: https://github.com/creationix/luv 107 | [http-parser]: https://github.com/joyent/http-parser 108 | [LuaJIT]: http://luajit.org/ 109 | [FIFO]: http://en.wikipedia.org/wiki/Named_pipe 110 | -------------------------------------------------------------------------------- /benchmark/http_benchmark.lua: -------------------------------------------------------------------------------- 1 | local loop = require 'uv.loop' 2 | local http = require 'uv.http' 3 | local system = require 'uv.system' 4 | 5 | loop.run(function() 6 | local server = http.listen('127.0.0.1', 7000, function(request) 7 | return 200, {}, 'ok' 8 | end) 9 | local count = 1000 10 | local time1 = system.hrtime() 11 | for i = 1, count do 12 | http.request { url = 'http://127.0.0.1:7000/' } 13 | end 14 | local time2 = system.hrtime() 15 | print(count .. ' requests took ' .. (time2 - time1) .. ' seconds') 16 | server:close() 17 | end) 18 | -------------------------------------------------------------------------------- /deps/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pguillory/luajit-libuv/dc0ec6e9de02011783d01ff99788be27b444c53e/deps/.gitignore -------------------------------------------------------------------------------- /doc/fs.md: -------------------------------------------------------------------------------- 1 | API Reference - uv.fs 2 | ===================== 3 | 4 | The `fs` module provides access to the file system. 5 | 6 | ### fs.open(path, flags, mode) 7 | 8 | Open a file. `flags` can have the following values: 9 | 10 | - `r`: reading 11 | - `w`: writing, truncate it if it exists, otherwise create it 12 | - `a`: appending if it exists, otherwise create it 13 | - `r+`: reading and writing 14 | - `w+`: reading and writing, truncate it if it exists, otherwise create it 15 | - `a+`: reading and appending if it exists, otherwise create it 16 | 17 | `mode` sets access permissions on the file if it is created. It can be a 18 | number or an octal string. '700' means a file is only accessible by you. '777' 19 | means it is accessible by anyone. 20 | 21 | Returns a file object. 22 | 23 | ```lua 24 | local file = fs.open('/path/to/file.txt', 'w', '755') 25 | file:write('hello world') 26 | file:close() 27 | ``` 28 | 29 | ### fs.unlink(path) 30 | 31 | Delete a file. 32 | 33 | ### fs.mkdir(path, mode) 34 | 35 | Create a directory. See `fs.open` for documentation on `mode`. 36 | 37 | ### fs.rmdir(path) 38 | 39 | Delete a directory. It must be empty. 40 | 41 | ### fs.chmod(path, mode) 42 | 43 | Change the permissions of a file/directory. See `fs.open` for documentation on 44 | `mode`. 45 | 46 | ### fs.chown(path, uid, gid) 47 | 48 | Change the owner of a file/directory. `uid` and `gid` should be numbers. 49 | 50 | ### fs.stat(path) 51 | 52 | Retrieve information about a file/directory. Returns a table with the following keys: 53 | 54 | - `uid`: User who owns the file. 55 | - `gid`: Group that owns the file. 56 | - `size`: File size in bytes. 57 | - `mode`: Access permissions. See `fs.open`. 58 | - `is_dir`: `true` if it is a directory. 59 | - `is_fifo`: `true` if it is a [FIFO]. 60 | - `atime`: Time the file was last accessed (second precision). 61 | - `atimensec`: Time the file was last accessed (nanosecond precision). 62 | - `mtime`: Time the file was last accessed (second precision). 63 | - `mtimensec`: Time the file was last accessed (nanosecond precision). 64 | - `ctime`: Time the file was last changed (second precision). 65 | - `ctimensec`: Time the file was last changed (nanosecond precision). 66 | - `birthtime`: Time the file was created (second precision). 67 | - `birthtimensec`: Time the file was created (nanosecond precision). 68 | 69 | ### fs.lstat(path) 70 | 71 | Same as `fs.stat`, but doesn't follow symlinks. 72 | 73 | ### fs.rename(path, new_path) 74 | 75 | Rename a file. 76 | 77 | ### fs.link(path, new_path) 78 | 79 | Create a hard link. 80 | 81 | ### fs.symlink(path, new_path) 82 | 83 | Create a symlink. 84 | 85 | ### fs.readlink(path) 86 | 87 | Read the value of a symlink. 88 | 89 | ### fs.readfile(path) 90 | 91 | Get the full contents of a file as a string. 92 | 93 | ### fs.writefile(path, body) 94 | 95 | Write a string to a file. Truncates the file if it already exists. 96 | 97 | ### fs.tmpname() 98 | 99 | Get a temporary filename. 100 | 101 | ### fs.cwd() 102 | 103 | Get the current working directory. 104 | 105 | ### fs.chdir(dir) 106 | 107 | Change the current working directory. 108 | 109 | ### fs.readdir(path) 110 | 111 | Get a list of files/subdirectories in a directory. 112 | 113 | ### fs.readdir_r(path) 114 | 115 | Get a list of all files under a directory and its descendent subdirectories. 116 | Returns a flat list of filenames. 117 | 118 | ```lua 119 | fs.with_tempdir(function(dir) 120 | fs.chdir(dir) 121 | fs.writefile('a', '') 122 | fs.writefile('b', '') 123 | fs.mkdir('c') 124 | fs.writefile('c/d', '') 125 | local filenames = fs.readdir_r('.') 126 | # filenames == { 'a', 'b', 'c/d' } 127 | end) 128 | ``` 129 | 130 | ### fs.rm_rf(path) 131 | 132 | Delete a file or directory. If it is a directory, its contents are deleted as well. *Be careful with this one!* 133 | 134 | ### fs.dirname(filename) 135 | 136 | ### fs.basename(filename) 137 | 138 | ### fs.extname(filename) 139 | 140 | Extract the directory, basename, and extension from a filename, respectively. 141 | 142 | ```lua 143 | assert(fs.dirname ('/path/to/file.txt') == '/path/to/') 144 | assert(fs.basename('/path/to/file.txt') == 'file') 145 | assert(fs.extname ('/path/to/file.txt') == '.txt') 146 | ``` 147 | 148 | ### fs.with_tempdir(callback) 149 | 150 | Create a directory, pass it to a callback, and delete the directory when the 151 | callback returns. 152 | 153 | ```lua 154 | fs.with_tempdir(function(dir) 155 | # dir will be something like '/tmp/lua_bVjBeR' 156 | end) 157 | ``` 158 | 159 | -------------------------------------------------------------------------------- /doc/http.md: -------------------------------------------------------------------------------- 1 | API Reference - uv.http 2 | ======================= 3 | 4 | The `http` module provides both a client and a server. 5 | 6 | ### http.request(request) 7 | 8 | Send an HTTP request and return the response. `request` should be a table with 9 | the following fields: 10 | 11 | - `method`: An HTTP method. Defaults to 'GET'. 12 | 13 | - `url`: The full URL to request. Any parts that are present will override the 14 | corresponding options below. 15 | 16 | - `scheme`: Defaults to 'http'. 17 | 18 | - `host`: Either an IP or DNS name is acceptable. 19 | 20 | - `port`: Defaults to 80. 21 | 22 | - `path`: Defaults to '/'. 23 | 24 | - `query`: Query string. Optional. 25 | 26 | - `body`: Request body, for POST requests. 27 | 28 | - `ip`: The IP address of the connected client. 29 | 30 | The value returned will be a table containing the following fields: 31 | 32 | - `status`: The HTTP status. 200 on success, or 400+ on error. 33 | 34 | - `headers`: A table of HTTP response headers. 35 | 36 | - `body`: The response body, as a string. 37 | 38 | ```lua 39 | local response = http.request { url = 'http://example.com/page1' } 40 | 41 | -- or 42 | 43 | local response = http.request { host = 'example.com', path = '/page1' } 44 | ``` 45 | 46 | ### http.listen(host, port, callback) 47 | 48 | Listen for requests at the given host and port. Use a `host` value of 49 | "0.0.0.0" to listen on all network interfaces, or "127.0.0.1" to only listen 50 | for requests from your own computer. 51 | 52 | Each request will be passed to `callback` in a distinct coroutine. The 53 | callback should return a table containing the response status, headers, and 54 | body. Only the status is required. 55 | 56 | ```lua 57 | http.listen('127.0.0.1', 80, function(request) 58 | return { 59 | status = 200, 60 | headers = { ['Content-Type'] = 'text/html' }, 61 | body = '

Hello world!

' 62 | } 63 | end) 64 | ``` 65 | 66 | Requests are tables containing the following keys: 67 | 68 | - `method`: An HTTP method, like 'GET' or 'POST'. 69 | - `url`: The URL from the raw HTTP request. 70 | - `path`: The path portion of the URL. 71 | - `query`: The query portion of the URL, as a string. 72 | - `headers`: The request headers, as a table. 73 | - `body`: The request body. Only present for POST requests. 74 | 75 | Note that the query string is not parsed. See the [url](url.md) module. 76 | 77 | ### http.format_date(time) 78 | 79 | Format a date according to RFC 1123, which is the preferred date format in 80 | HTTP. `time` is a number representing a [Unix time]. It defaults to the current time, given by `os.time()`. 81 | 82 | ### http.parse_date(date_string) 83 | 84 | Parse a date string in any of the acceptable [HTTP date formats]. 85 | 86 | [HTTP date formats]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3 87 | 88 | [Unix time]: http://en.wikipedia.org/wiki/Unix_time 89 | -------------------------------------------------------------------------------- /doc/loop.md: -------------------------------------------------------------------------------- 1 | API Reference - uv.loop 2 | ======================= 3 | 4 | The `loop` module provides direct control over the libuv event loop. 5 | 6 | ### loop.run() 7 | 8 | Run the libuv event loop. This is only necessary if an I/O request was created 9 | in a coroutine without the event loop already running. Requests made outside a 10 | coroutine are performed synchronously. It returns when the last I/O request is 11 | finished. 12 | 13 | ```lua 14 | print(uv.fs.readfile('README.md')) 15 | ``` 16 | 17 | In this example, we're not in a coroutine, so `fs.readfile` ran the event loop 18 | implicitly. There is no need to call `loop.run()`. 19 | 20 | ```lua 21 | coroutine.resume(coroutine.create(function() 22 | print(uv.fs.readfile('README.md')) 23 | end)) 24 | loop.run() 25 | ``` 26 | 27 | Here, we manually created a coroutine that called `fs.readfile`, which yielded 28 | while awaiting the result. The event loop is not running, so unless we called 29 | `loop.run()`, the program would exit without performing the I/O request and the 30 | coroutine would never resume. 31 | 32 | ### loop.alive() 33 | 34 | Check whether the libuv event loop is running. 35 | 36 | ### loop.stop() 37 | 38 | Stop the libuv event loop. 39 | 40 | ### loop.idle(callback) 41 | 42 | Call `callback` continuously while the event loop has nothing else to do. 43 | 44 | ### loop.yield(callback) 45 | 46 | Call `callback` each time Lua yields control to the event loop. 47 | 48 | ### loop.resume(callback) 49 | 50 | Call `callback` each time Lua resumes control from libuv. 51 | -------------------------------------------------------------------------------- /doc/parallel.md: -------------------------------------------------------------------------------- 1 | API Reference - uv.parallel 2 | =========================== 3 | 4 | The `parallel` module contains functions for performing computation in 5 | parallel across multiple coroutines. Note that Lua coroutines are not 6 | preemptive. Only one coroutine can run at a time, but they yield to each other 7 | while waiting for I/O requests from `libuv`. 8 | 9 | ### parallel.map(inputs, callback) 10 | 11 | Map an array of inputs to an array of outputs. Each input is passed to 12 | `callback` in its own coroutine, so that I/O operations are performed in 13 | parallel. 14 | 15 | ```lua 16 | local requests = { 17 | { url = 'http://example.com/page1' }, 18 | { url = 'http://example.com/page2' }, 19 | } 20 | local responses = parallel.map(requests, http.request) 21 | ``` 22 | 23 | ### parallel.range(n, callback) 24 | 25 | Call `callback` `n` times, each in its own coroutine. Like a parallel version 26 | of the `for` loop. 27 | -------------------------------------------------------------------------------- /doc/process.md: -------------------------------------------------------------------------------- 1 | API Reference - uv.process 2 | ========================== 3 | 4 | The `process` module provides functions for managing processes. 5 | 6 | ### process.spawn(args) 7 | 8 | Spawn a child process. `args` should be a table containing the executable path 9 | at index 1, arguments at indexes 2+, and any of the following options: 10 | 11 | - `env`: A table of environment variables as key/value pairs. If `env` is 12 | omitted, the child process will inherit the parent's environment. 13 | 14 | - `cwd`: Current working directory of the child process. 15 | 16 | - `stdin`: File descriptor to inherit as stdin. 0 causes it to inherit the 17 | parent's stdin. 18 | 19 | - `stdout`: File descriptor to inherit as stdout. 1 causes it to inherit the 20 | parent's stdout. 21 | 22 | - `stderr`: File descriptor to inherit as stderr. 2 causes it to inherit the 23 | parent's stderr. 24 | 25 | - `uid`: User ID under which to run. 26 | 27 | - `gid`: Group ID under which to run. 28 | 29 | Returns the signal that terminated the child process, or 0 on successful exit. 30 | 31 | ```lua 32 | local signal = process.spawn { '/bin/echo', 'Hello', 'world' } 33 | 34 | local file = fs.open('out.txt', 'w') 35 | process.spawn { '/bin/ls', cwd = fs.cwd(), stdout = file.descriptor } 36 | ``` 37 | 38 | ### process.pid() 39 | 40 | Returns the current process's PID. 41 | 42 | ### process.path() 43 | 44 | Returns the path to the executable for the current process. 45 | 46 | ### process.kill(pid, signal) 47 | 48 | Send a signal to a process. `signal` defaults to "SIGKILL" and can have any of 49 | the following values: 50 | 51 | - "SIGKILL": The process should exit. 52 | - "SIGINT": The user pressed Control+C. 53 | - "SIGHUP": The user closed the console window. 54 | - "SIGWINCH": The user resized the console window. 55 | 56 | ### process.on(signal, callback) 57 | 58 | Call `callback` when a given signal is received. `signal` can only be "SIGINT", "SIGHUP", or "SIGWINCH". 59 | 60 | ```lua 61 | process.on('SIGINT', function() 62 | print('Shutting down...') 63 | os.exit() 64 | end) 65 | ``` 66 | 67 | ### process.usage() 68 | 69 | Returns a table describing the current process's resource usage with the 70 | following keys: 71 | 72 | - `utime`: User CPU time used, in microseconds. 73 | - `stime`: System CPU time used, in microseconds. 74 | - `maxrss`: Maximum resident set size. 75 | - `ixrss`: Integral shared memory size. 76 | - `idrss`: Integral unshared data size. 77 | - `isrss`: Integral unshared stack size. 78 | - `minflt`: Page reclaims (soft page faults). 79 | - `majflt`: Page faults (hard page faults). 80 | - `nswap`: Swaps. 81 | - `inblock`: Block input operations. 82 | - `oublock`: Block output operations. 83 | - `msgsnd`: IPC messages sent. 84 | - `msgrcv`: IPC messages received. 85 | - `nsignals`: Signals received. 86 | - `nvcsw`: Voluntary context switches. 87 | - `nivcsw`: Involuntary context switches. 88 | 89 | ### process.title(value) 90 | 91 | Change the current process's title to `value`, if present. Returns the 92 | existing title. I think this only works on Windows. 93 | -------------------------------------------------------------------------------- /doc/system.md: -------------------------------------------------------------------------------- 1 | API Reference - uv.system 2 | ========================= 3 | 4 | The `system` module provides functionality related to the system as a whole, 5 | not just this process. 6 | 7 | ### system.free_memory() 8 | 9 | Returns the amount of free memory available to the system, in bytes. 10 | 11 | ### system.total_memory() 12 | 13 | Returns the total amount of memory in the system, in bytes. 14 | 15 | ### system.hrtime() 16 | 17 | Returns a high-resolution time in seconds. It is useful for measuring 18 | intervals but not for determining the current clock time. 19 | 20 | ### system.loadavg() 21 | 22 | Returns the system load average over 1, 5, and 15 minutes. The load average is 23 | the average number of jobs in the run queue. 24 | 25 | ### system.uptime() 26 | 27 | Returns the number of seconds since the system booted. 28 | -------------------------------------------------------------------------------- /doc/tcp.md: -------------------------------------------------------------------------------- 1 | API Reference - uv.tcp 2 | ======================= 3 | 4 | The `tcp` module provides both a client and a server. 5 | 6 | ### tcp.listen(host, port) 7 | 8 | Start listening for TCP connections. Returns a server object. Call 9 | `server:accept()` to accept a new connection and `server:close()` to stop 10 | listening. 11 | 12 | ```lua 13 | local server = tcp.listen('127.0.0.1', 7000) 14 | while true do 15 | local socket = server:accept() 16 | while true do 17 | local data = socket:read() 18 | if data:find('quit') then 19 | break 20 | end 21 | socket:write(data) 22 | end 23 | socket:close() 24 | end 25 | ``` 26 | 27 | ### tcp.connect(host, port) 28 | 29 | Connect to a TCP server. Returns a connection with `read`, `write`, and 30 | `close` methods. 31 | 32 | ```lua 33 | local socket = tcp.connect('127.0.0.1', 7000) 34 | socket:write('ping') 35 | local data = socket:read() 36 | socket:write('quit') 37 | socket:close() 38 | ``` 39 | -------------------------------------------------------------------------------- /doc/timer.md: -------------------------------------------------------------------------------- 1 | API Reference - uv.timer 2 | ======================== 3 | 4 | ### timer.set(timeout, callback) 5 | 6 | Schedule a function to be called once in the future. Returns immediately. 7 | 8 | ```lua 9 | timer.set(5000, function() 10 | print('Ding!') 11 | end) 12 | print('Waiting 5 seconds...') 13 | loop.run() 14 | print('The timer dinged.') 15 | ``` 16 | 17 | ### timer.every(timeout, callback) 18 | 19 | Schedule a function to be called every `timeout` milliseconds. Returns 20 | immediately. 21 | 22 | ```lua 23 | timer.every(1000, function(t) 24 | print('Tick...') 25 | if we_are_done then 26 | t:stop() 27 | end 28 | end) 29 | loop.run() 30 | ``` 31 | 32 | ### timer.sleep(timeout, callback) 33 | 34 | Yield the current coroutine for `timeout` milliseconds. 35 | 36 | ```lua 37 | print('Going to sleep...') 38 | timer.sleep(5000) 39 | print('Woke up') 40 | ``` 41 | -------------------------------------------------------------------------------- /doc/url.md: -------------------------------------------------------------------------------- 1 | API Reference - uv.url 2 | ====================== 3 | 4 | The `url` module provides functions for working with URLs. 5 | 6 | ### url.split(str) 7 | 8 | Return a URL's components as a table. The table may contain any of the 9 | following keys, depending on which are present in the URL: 10 | 11 | - `scheme` 12 | - `userinfo` 13 | - `host` 14 | - `port` 15 | - `path` 16 | - `query` 17 | - `fragment` 18 | 19 | ```lua 20 | local parts = url.split 'http://myname:12345@host.com:80/path?a=1&b=2#section 21 | 22 | -- parts.scheme == 'http' 23 | -- parts.userinfo == 'myname:12345' 24 | -- parts.host == 'host.com' 25 | -- parts.port == '80' 26 | -- parts.path == '/path' 27 | -- parts.query == 'a=1&b=2' 28 | -- parts.fragment == 'section' 29 | ``` 30 | 31 | ### url.join(parts) 32 | 33 | The inverse of `url.split`. It takes a table of URL components and returns the 34 | assembled URL as a string. 35 | 36 | ```lua 37 | url.join { path = '/path', query = 'a=1&b=2' } 38 | -- '/path?a=1&b=2' 39 | ``` 40 | 41 | ### url.encode(value) 42 | 43 | Encode a value as a URI component. If `value` is a table, `url.encode` will return a full query string. 44 | 45 | ```lua 46 | local query = url.encode { name = 'Isaac Newton' } 47 | -- query == 'name=Isaac%20Newton' 48 | ``` 49 | 50 | ### url.relative(base, relative) 51 | 52 | Evaluate a relative URL in the context of a base URL. It mirrors the logic 53 | applied by browsers when evaluating a link in a web page. 54 | 55 | ```lua 56 | url.relative('http://host.com/path/to/page', 'other/page') 57 | -- 'http://host.com/path/to/other/page' 58 | ``` 59 | -------------------------------------------------------------------------------- /src/uv.lua: -------------------------------------------------------------------------------- 1 | return require 'uv/init' 2 | -------------------------------------------------------------------------------- /src/uv/ctypes/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | uv_buf_t = require 'uv/ctypes/uv_buf_t', 3 | uv_check_t = require 'uv/ctypes/uv_check_t', 4 | uv_connect_t = require 'uv/ctypes/uv_connect_t', 5 | uv_fs_t = require 'uv/ctypes/uv_fs_t', 6 | uv_getaddrinfo_t = require 'uv/ctypes/uv_getaddrinfo_t', 7 | uv_handle_t = require 'uv/ctypes/uv_handle_t', 8 | uv_idle_t = require 'uv/ctypes/uv_idle_t', 9 | uv_loop_t = require 'uv/ctypes/uv_loop_t', 10 | uv_prepare_t = require 'uv/ctypes/uv_prepare_t', 11 | uv_process_t = require 'uv/ctypes/uv_process_t', 12 | uv_process_options_t = require 'uv/ctypes/uv_process_options_t', 13 | uv_signal_t = require 'uv/ctypes/uv_signal_t', 14 | uv_stream_t = require 'uv/ctypes/uv_stream_t', 15 | uv_tcp_t = require 'uv/ctypes/uv_tcp_t', 16 | uv_timer_t = require 'uv/ctypes/uv_timer_t', 17 | uv_write_t = require 'uv/ctypes/uv_write_t', 18 | } 19 | -------------------------------------------------------------------------------- /src/uv/ctypes/uv_buf_t.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local ctype = require 'uv/util/ctype' 3 | local libuv = require 'uv/libuv' 4 | local libuv2 = require 'uv/libuv2' 5 | local libc = require 'uv/libc' 6 | 7 | -------------------------------------------------------------------------------- 8 | -- uv_buf_t 9 | -------------------------------------------------------------------------------- 10 | 11 | local uv_buf_t = ctype('uv_buf_t', function(base, len) 12 | local self = ffi.cast('uv_buf_t*', libc.malloc(ffi.sizeof('uv_buf_t'))) 13 | self.len = len or 65536 14 | self.base = libc.malloc(self.len) 15 | if base then 16 | assert(#base <= self.len) 17 | ffi.copy(self.base, base, #base) 18 | end 19 | return self 20 | end) 21 | 22 | function uv_buf_t:free() 23 | libc.free(self.base) 24 | libc.free(self) 25 | end 26 | 27 | return uv_buf_t 28 | -------------------------------------------------------------------------------- /src/uv/ctypes/uv_check_t.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local async = require 'uv/util/async' 3 | local ctype = require 'uv/util/ctype' 4 | local libuv = require 'uv/libuv' 5 | local libuv2 = require 'uv/libuv2' 6 | local libc = require 'uv/libc' 7 | local verify = require 'uv/util/verify' 8 | 9 | local uv_check_t = ctype('uv_check_t', function(loop) 10 | loop = loop or libuv.uv_default_loop() 11 | local self = ffi.cast('uv_check_t*', libc.malloc(ffi.sizeof('uv_check_t'))) 12 | verify(libuv.uv_check_init(loop, self)) 13 | return self 14 | end) 15 | 16 | function uv_check_t:start(callback) 17 | verify(libuv.uv_check_start(self, async.uv_check_cb)) 18 | while true do 19 | async.yield(self) 20 | callback() 21 | end 22 | end 23 | 24 | return uv_check_t 25 | -------------------------------------------------------------------------------- /src/uv/ctypes/uv_connect_t.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local ctype = require 'uv/util/ctype' 3 | local libuv = require 'uv/libuv' 4 | local libuv2 = require 'uv/libuv2' 5 | local libc = require 'uv/libc' 6 | 7 | -------------------------------------------------------------------------------- 8 | -- uv_connect_t 9 | -------------------------------------------------------------------------------- 10 | 11 | local uv_connect_t = ctype('uv_connect_t', function() 12 | local self = ffi.cast('uv_connect_t*', libc.malloc(ffi.sizeof('uv_connect_t'))) 13 | return self 14 | end) 15 | 16 | function uv_connect_t:free() 17 | libc.free(self) 18 | end 19 | 20 | return uv_connect_t 21 | -------------------------------------------------------------------------------- /src/uv/ctypes/uv_fs_t.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local async = require 'uv/util/async' 3 | local ctype = require 'uv/util/ctype' 4 | local libuv = require 'uv/libuv' 5 | local libuv2 = require 'uv/libuv2' 6 | local libc = require 'uv/libc' 7 | local uv_buf_t = require 'uv/ctypes/uv_buf_t' 8 | local uv_loop_t = require 'uv/ctypes/uv_loop_t' 9 | local errno = require 'uv/util/errno' 10 | local verify = require 'uv/util/verify' 11 | 12 | -------------------------------------------------------------------------------- 13 | -- uv_fs_t 14 | -------------------------------------------------------------------------------- 15 | 16 | local uv_fs_t = ctype('uv_fs_t', function(loop) 17 | local self = ffi.cast('uv_fs_t*', libc.malloc(ffi.sizeof('uv_fs_t'))) 18 | self.loop = loop or libuv.uv_default_loop() 19 | return self 20 | end) 21 | 22 | function uv_fs_t:open(path, flags, mode) 23 | verify(libuv2.uv2_fs_open(self.loop, self, path, flags, mode, async.uv_fs_cb)) 24 | async.yield(self) 25 | local descriptor = tonumber(self.result) 26 | if descriptor < 0 then 27 | error(errno[tonumber(self.result)]) 28 | end 29 | libuv.uv_fs_req_cleanup(self) 30 | return descriptor 31 | end 32 | 33 | function uv_fs_t:read(file) 34 | local buf = uv_buf_t() 35 | verify(libuv.uv_fs_read(self.loop, self, file, buf, 1, -1, async.uv_fs_cb)) 36 | async.yield(self) 37 | local nread = tonumber(self.result) 38 | if nread < 0 then 39 | error(errno[tonumber(self.result)]) 40 | end 41 | local chunk = ffi.string(buf.base, nread) 42 | buf:free() 43 | libuv.uv_fs_req_cleanup(self) 44 | return chunk 45 | end 46 | 47 | function uv_fs_t:close(file) 48 | verify(libuv.uv_fs_close(self.loop, self, file, async.uv_fs_cb)) 49 | async.yield(self) 50 | local status = tonumber(self.result) 51 | if status < 0 then 52 | error(errno[tonumber(self.result)]) 53 | end 54 | libuv.uv_fs_req_cleanup(self) 55 | end 56 | 57 | function uv_fs_t:unlink(path) 58 | verify(libuv.uv_fs_unlink(self.loop, self, path, async.uv_fs_cb)) 59 | async.yield(self) 60 | local status = tonumber(self.result) 61 | if status < 0 then 62 | error(errno[tonumber(self.result)]) 63 | end 64 | libuv.uv_fs_req_cleanup(self) 65 | end 66 | 67 | function uv_fs_t:write(file, buffer) 68 | local buf = uv_buf_t(buffer, #buffer) 69 | verify(libuv.uv_fs_write(self.loop, self, file, buf, 1, -1, async.uv_fs_cb)) 70 | async.yield(self) 71 | buf:free() 72 | local status = tonumber(self.result) 73 | if status < 0 then 74 | error(errno[tonumber(self.result)]) 75 | end 76 | libuv.uv_fs_req_cleanup(self) 77 | end 78 | 79 | function uv_fs_t:mkdir(path, mode) 80 | verify(libuv.uv_fs_mkdir(self.loop, self, path, mode, async.uv_fs_cb)) 81 | async.yield(self) 82 | local status = tonumber(self.result) 83 | if status < 0 then 84 | error(errno[tonumber(self.result)]) 85 | end 86 | libuv.uv_fs_req_cleanup(self) 87 | end 88 | 89 | function uv_fs_t:rmdir(path) 90 | verify(libuv.uv_fs_rmdir(self.loop, self, path, async.uv_fs_cb)) 91 | async.yield(self) 92 | local status = tonumber(self.result) 93 | if status < 0 then 94 | error(errno[tonumber(self.result)]) 95 | end 96 | libuv.uv_fs_req_cleanup(self) 97 | end 98 | 99 | function uv_fs_t:chmod(path, mode) 100 | verify(libuv.uv_fs_chmod(self.loop, self, path, mode, async.uv_fs_cb)) 101 | async.yield(self) 102 | local status = tonumber(self.result) 103 | if status < 0 then 104 | error(errno[tonumber(self.result)]) 105 | end 106 | libuv.uv_fs_req_cleanup(self) 107 | end 108 | 109 | function uv_fs_t:fchmod(file, mode) 110 | verify(libuv.uv_fs_fchmod(self.loop, self, file, mode, async.uv_fs_cb)) 111 | async.yield(self) 112 | local status = tonumber(self.result) 113 | if status < 0 then 114 | error(errno[tonumber(self.result)]) 115 | end 116 | libuv.uv_fs_req_cleanup(self) 117 | end 118 | 119 | function uv_fs_t:chown(path, uid, gid) 120 | verify(libuv.uv_fs_chown(self.loop, self, path, uid, gid, async.uv_fs_cb)) 121 | async.yield(self) 122 | local status = tonumber(self.result) 123 | if status < 0 then 124 | error(errno[tonumber(self.result)]) 125 | end 126 | libuv.uv_fs_req_cleanup(self) 127 | end 128 | 129 | function uv_fs_t:fchown(file, uid, gid) 130 | verify(libuv.uv_fs_fchown(self.loop, self, file, uid, gid, async.uv_fs_cb)) 131 | async.yield(self) 132 | local status = tonumber(self.result) 133 | if status < 0 then 134 | error(errno[tonumber(self.result)]) 135 | end 136 | libuv.uv_fs_req_cleanup(self) 137 | end 138 | 139 | function uv_fs_t:stat(path) 140 | verify(libuv.uv_fs_stat(self.loop, self, path, async.uv_fs_cb)) 141 | async.yield(self) 142 | local status = tonumber(self.result) 143 | if status < 0 then 144 | error(errno[tonumber(self.result)]) 145 | end 146 | local stat = ffi.cast('uv_stat_t*', self.ptr) 147 | libuv.uv_fs_req_cleanup(self) 148 | return stat 149 | end 150 | 151 | function uv_fs_t:fstat(path) 152 | verify(libuv.uv_fs_fstat(self.loop, self, path, async.uv_fs_cb)) 153 | async.yield(self) 154 | local status = tonumber(self.result) 155 | if status < 0 then 156 | error(errno[tonumber(self.result)]) 157 | end 158 | local stat = ffi.cast('uv_stat_t*', self.ptr) 159 | libuv.uv_fs_req_cleanup(self) 160 | return stat 161 | end 162 | 163 | function uv_fs_t:lstat(path) 164 | verify(libuv.uv_fs_lstat(self.loop, self, path, async.uv_fs_cb)) 165 | async.yield(self) 166 | local status = tonumber(self.result) 167 | if status < 0 then 168 | error(errno[tonumber(self.result)]) 169 | end 170 | local stat = ffi.cast('uv_stat_t*', self.ptr) 171 | libuv.uv_fs_req_cleanup(self) 172 | return stat 173 | end 174 | 175 | function uv_fs_t:rename(path, new_path) 176 | verify(libuv.uv_fs_rename(self.loop, self, path, new_path, async.uv_fs_cb)) 177 | async.yield(self) 178 | local status = tonumber(self.result) 179 | if status < 0 then 180 | error(errno[tonumber(self.result)]) 181 | end 182 | libuv.uv_fs_req_cleanup(self) 183 | end 184 | 185 | function uv_fs_t:link(path, new_path) 186 | verify(libuv.uv_fs_link(self.loop, self, path, new_path, async.uv_fs_cb)) 187 | async.yield(self) 188 | local status = tonumber(self.result) 189 | if status < 0 then 190 | error(errno[tonumber(self.result)]) 191 | end 192 | libuv.uv_fs_req_cleanup(self) 193 | end 194 | 195 | function uv_fs_t:symlink(path, new_path, flags) 196 | local flags = flags or 0 197 | verify(libuv.uv_fs_symlink(self.loop, self, path, new_path, flags, async.uv_fs_cb)) 198 | async.yield(self) 199 | local status = tonumber(self.result) 200 | if status < 0 then 201 | error(errno[tonumber(self.result)]) 202 | end 203 | libuv.uv_fs_req_cleanup(self) 204 | end 205 | 206 | function uv_fs_t:readlink(path) 207 | verify(libuv.uv_fs_readlink(self.loop, self, path, async.uv_fs_cb)) 208 | async.yield(self) 209 | local status = tonumber(self.result) 210 | if status < 0 then 211 | error(errno[tonumber(self.result)]) 212 | end 213 | local path = ffi.string(self.ptr) 214 | libuv.uv_fs_req_cleanup(self) 215 | return path 216 | end 217 | 218 | function uv_fs_t:fsync(file) 219 | verify(libuv.uv_fs_fsync(self.loop, self, file, async.uv_fs_cb)) 220 | async.yield(self) 221 | local status = tonumber(self.result) 222 | if status < 0 then 223 | error(errno[tonumber(self.result)]) 224 | end 225 | libuv.uv_fs_req_cleanup(self) 226 | end 227 | 228 | local function cstrings(cstrings, count) 229 | cstrings = ffi.cast('char*', cstrings) 230 | local t = {} 231 | for i = 1, count do 232 | local s = ffi.string(cstrings) 233 | cstrings = cstrings + #s + 1 234 | t[i] = s 235 | end 236 | return t 237 | end 238 | 239 | function uv_fs_t:readdir(path, flags) 240 | verify(libuv.uv_fs_readdir(self.loop, self, path, flags, async.uv_fs_cb)) 241 | async.yield(self) 242 | local status = tonumber(self.result) 243 | if status < 0 then 244 | error(errno[tonumber(self.result)]) 245 | end 246 | local filenames = cstrings(self.ptr, status) 247 | libuv.uv_fs_req_cleanup(self) 248 | return filenames 249 | end 250 | 251 | return uv_fs_t 252 | -------------------------------------------------------------------------------- /src/uv/ctypes/uv_getaddrinfo_t.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local async = require 'uv/util/async' 3 | local ctype = require 'uv/util/ctype' 4 | local libuv = require 'uv/libuv' 5 | local libc = require 'uv/libc' 6 | local uv_loop_t = require 'uv/ctypes/uv_loop_t' 7 | local verify = require 'uv/util/verify' 8 | 9 | local AF_INET = 2 10 | local AF_INET6 = 28 11 | 12 | -------------------------------------------------------------------------------- 13 | -- sockaddr_in 14 | -------------------------------------------------------------------------------- 15 | 16 | local sockaddr_in = ctype('struct sockaddr_in') 17 | 18 | function sockaddr_in:ip() 19 | local buf = ffi.new('char[?]', 16) 20 | libuv.uv_ip4_name(self, buf, 16) 21 | return ffi.string(buf) 22 | end 23 | 24 | function sockaddr_in:port() 25 | return libc.ntohs(self.sin_port) 26 | end 27 | 28 | -------------------------------------------------------------------------------- 29 | -- uv_getaddrinfo_t 30 | -------------------------------------------------------------------------------- 31 | 32 | local uv_getaddrinfo_t = ctype('uv_getaddrinfo_t', function(loop) 33 | local self = ffi.cast('uv_getaddrinfo_t*', libc.malloc(ffi.sizeof('uv_getaddrinfo_t'))) 34 | self.loop = loop or libuv.uv_default_loop() 35 | return self 36 | end) 37 | 38 | function uv_getaddrinfo_t:free() 39 | libc.free(self) 40 | end 41 | 42 | function uv_getaddrinfo_t:getaddrinfo(node, service) 43 | local hints = ffi.new('struct addrinfo') 44 | -- hints.ai_family = AF_INET 45 | verify(libuv.uv_getaddrinfo(self.loop, self, async.uv_getaddrinfo_cb, node, service, hints)) 46 | local status, addrinfo = async.yield(self) 47 | verify(status) 48 | local addrs = {} 49 | local ai = addrinfo 50 | while ai ~= ffi.NULL do 51 | if ai.ai_addr.sa_family == AF_INET then 52 | local addr = ffi.new('struct sockaddr_in') 53 | ffi.copy(addr, ai.ai_addr, ai.ai_addrlen) 54 | -- print('addr: ', addr.sin_p) 55 | table.insert(addrs, addr) 56 | end 57 | ai = ai.ai_next 58 | end 59 | libuv.uv_freeaddrinfo(addrinfo) 60 | return addrs 61 | end 62 | 63 | return uv_getaddrinfo_t 64 | -------------------------------------------------------------------------------- /src/uv/ctypes/uv_handle_t.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local async = require 'uv/util/async' 3 | local ctype = require 'uv/util/ctype' 4 | local join = require 'uv/util/join' 5 | local libuv = require 'uv/libuv' 6 | local libuv2 = require 'uv/libuv2' 7 | local libc = require 'uv/libc' 8 | local verify = require 'uv/util/verify' 9 | 10 | local uv_handle_t = ctype('uv_handle_t') 11 | 12 | function uv_handle_t:close() 13 | -- libuv.uv_unref(self) 14 | libuv.uv_close(self, async.uv_close_cb) 15 | async.yield(self) 16 | end 17 | 18 | local type_itoa = { 19 | [libuv.UV_UNKNOWN_HANDLE] = 'unknown_handle', 20 | [libuv.UV_ASYNC] = 'async', 21 | [libuv.UV_CHECK] = 'check', 22 | [libuv.UV_FS_EVENT] = 'fs_event', 23 | [libuv.UV_FS_POLL] = 'fs_poll', 24 | [libuv.UV_HANDLE] = 'handle', 25 | [libuv.UV_IDLE] = 'idle', 26 | [libuv.UV_NAMED_PIPE] = 'named_pipe', 27 | [libuv.UV_POLL] = 'poll', 28 | [libuv.UV_PREPARE] = 'prepare', 29 | [libuv.UV_PROCESS] = 'process', 30 | [libuv.UV_STREAM] = 'stream', 31 | [libuv.UV_TCP] = 'tcp', 32 | [libuv.UV_TIMER] = 'timer', 33 | [libuv.UV_TTY] = 'tty', 34 | [libuv.UV_UDP] = 'udp', 35 | [libuv.UV_SIGNAL] = 'signal', 36 | [libuv.UV_FILE] = 'file', 37 | } 38 | 39 | function uv_handle_t:typestring() 40 | return type_itoa[tonumber(self.type)] or 'unknown' 41 | end 42 | 43 | function uv_handle_t:is_active() 44 | return 0 ~= libuv.uv_is_active(self) 45 | end 46 | 47 | return uv_handle_t 48 | -------------------------------------------------------------------------------- /src/uv/ctypes/uv_idle_t.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local async = require 'uv/util/async' 3 | local ctype = require 'uv/util/ctype' 4 | local libuv = require 'uv/libuv' 5 | local libuv2 = require 'uv/libuv2' 6 | local libc = require 'uv/libc' 7 | local verify = require 'uv/util/verify' 8 | 9 | local uv_idle_t = ctype('uv_idle_t', function(loop) 10 | local self = ffi.cast('uv_idle_t*', libc.malloc(ffi.sizeof('uv_idle_t'))) 11 | verify(libuv.uv_idle_init(loop or libuv.uv_default_loop(), self)) 12 | return self 13 | end) 14 | 15 | function uv_idle_t:start(callback) 16 | verify(libuv.uv_idle_start(self, async.uv_idle_cb)) 17 | while true do 18 | async.yield(self) 19 | callback() 20 | end 21 | end 22 | 23 | return uv_idle_t 24 | -------------------------------------------------------------------------------- /src/uv/ctypes/uv_loop_t.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local ctype = require 'uv/util/ctype' 3 | local join = require 'uv/util/join' 4 | local libuv = require 'uv/libuv' 5 | local verify = require 'uv/util/verify' 6 | 7 | -------------------------------------------------------------------------------- 8 | -- uv_loop_t 9 | -------------------------------------------------------------------------------- 10 | 11 | local uv_loop_t = ctype('uv_loop_t') 12 | 13 | function uv_loop_t:run() 14 | verify(libuv.uv_run(self, libuv.UV_RUN_DEFAULT)) 15 | end 16 | 17 | function uv_loop_t:stop() 18 | libuv.uv_stop(self) 19 | end 20 | 21 | function uv_loop_t:close() 22 | libuv.uv_loop_close(self) 23 | end 24 | 25 | function uv_loop_t:walk(callback) 26 | local callback = ffi.cast('uv_walk_cb', callback) 27 | libuv.uv_walk(self, callback, nil) 28 | callback:free() 29 | end 30 | 31 | function uv_loop_t:now() 32 | return libuv.uv_now(self) 33 | end 34 | 35 | return uv_loop_t 36 | -------------------------------------------------------------------------------- /src/uv/ctypes/uv_prepare_t.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local async = require 'uv/util/async' 3 | local ctype = require 'uv/util/ctype' 4 | local libuv = require 'uv/libuv' 5 | local libuv2 = require 'uv/libuv2' 6 | local libc = require 'uv/libc' 7 | local verify = require 'uv/util/verify' 8 | 9 | local uv_prepare_t = ctype('uv_prepare_t', function(loop) 10 | local self = ffi.cast('uv_prepare_t*', libc.malloc(ffi.sizeof('uv_prepare_t'))) 11 | verify(libuv.uv_prepare_init(loop or libuv.uv_default_loop(), self)) 12 | return self 13 | end) 14 | 15 | function uv_prepare_t:start(callback) 16 | verify(libuv.uv_prepare_start(self, async.uv_prepare_cb)) 17 | while true do 18 | async.yield(self) 19 | callback() 20 | end 21 | end 22 | 23 | return uv_prepare_t 24 | -------------------------------------------------------------------------------- /src/uv/ctypes/uv_process_options_t.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local async = require 'uv/util/async' 3 | local ctype = require 'uv/util/ctype' 4 | local libuv = require 'uv/libuv' 5 | local libuv2 = require 'uv/libuv2' 6 | local libc = require 'uv/libc' 7 | 8 | local uv_process_options_t = ctype('uv_process_options_t', function(loop) 9 | local self = ffi.cast('uv_process_options_t*', libc.malloc(ffi.sizeof('uv_process_options_t'))) 10 | ffi.fill(self, ffi.sizeof('uv_process_options_t'), 0) 11 | return self 12 | end) 13 | 14 | function uv_process_options_t:free() 15 | libc.free(self) 16 | end 17 | 18 | return uv_process_options_t 19 | -------------------------------------------------------------------------------- /src/uv/ctypes/uv_process_t.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local async = require 'uv/util/async' 3 | local ctype = require 'uv/util/ctype' 4 | local libuv = require 'uv/libuv' 5 | local libuv2 = require 'uv/libuv2' 6 | local libc = require 'uv/libc' 7 | local verify = require 'uv/util/verify' 8 | 9 | local uv_process_t = ctype('uv_process_t', function(loop) 10 | local self = ffi.cast('uv_process_t*', libc.malloc(ffi.sizeof('uv_process_t'))) 11 | self.loop = loop or libuv.uv_default_loop() 12 | return self 13 | end) 14 | 15 | function uv_process_t:spawn(options) 16 | verify(libuv.uv_spawn(self.loop, self, options)) 17 | local exit_status, term_signal = async.yield(self) 18 | verify(exit_status) 19 | return term_signal 20 | end 21 | 22 | function uv_process_t:kill(signum) 23 | verify(libuv.uv_process_kill(self, signum)) 24 | end 25 | 26 | return uv_process_t 27 | -------------------------------------------------------------------------------- /src/uv/ctypes/uv_signal_t.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local async = require 'uv/util/async' 3 | local ctype = require 'uv/util/ctype' 4 | local libuv = require 'uv/libuv' 5 | local libuv2 = require 'uv/libuv2' 6 | local libc = require 'uv/libc' 7 | local verify = require 'uv/util/verify' 8 | 9 | -------------------------------------------------------------------------------- 10 | -- uv_signal_t 11 | -------------------------------------------------------------------------------- 12 | 13 | local uv_signal_t = ctype('uv_signal_t', function(loop) 14 | local self = ffi.cast('uv_signal_t*', libc.malloc(ffi.sizeof('uv_signal_t'))) 15 | verify(libuv.uv_signal_init(loop or libuv.uv_default_loop(), self)) 16 | return self 17 | end) 18 | 19 | function uv_signal_t:start(signum, callback) 20 | verify(libuv.uv_signal_start(self, async.uv_signal_cb, signum)) 21 | while true do 22 | local signum = async.yield(self) 23 | callback() 24 | end 25 | end 26 | 27 | function uv_signal_t:stop() 28 | verify(libuv.uv_signal_stop(self)) 29 | end 30 | 31 | function uv_signal_t:free() 32 | libc.free(self) 33 | end 34 | 35 | return uv_signal_t 36 | -------------------------------------------------------------------------------- /src/uv/ctypes/uv_stream_t.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local async = require 'uv/util/async' 3 | local ctype = require 'uv/util/ctype' 4 | local libuv = require 'uv/libuv' 5 | local libuv2 = require 'uv/libuv2' 6 | local libc = require 'uv/libc' 7 | local uv_buf_t = require 'uv/ctypes/uv_buf_t' 8 | local uv_write_t = require 'uv/ctypes/uv_write_t' 9 | local uv_loop_t = require 'uv/ctypes/uv_loop_t' 10 | local verify = require 'uv/util/verify' 11 | 12 | -------------------------------------------------------------------------------- 13 | -- uv_stream_t 14 | -------------------------------------------------------------------------------- 15 | 16 | local uv_stream_t = ctype('uv_stream_t') 17 | 18 | function uv_stream_t:read() 19 | libuv.uv_read_start(self, libuv2.uv2_alloc_cb, async.uv_read_cb) 20 | local nread, buf = async.yield(self) 21 | libuv.uv_read_stop(self) 22 | verify(nread) 23 | local chunk = (nread < 0) and '' or ffi.string(buf.base, nread) 24 | libc.free(buf.base) 25 | return chunk, nread 26 | end 27 | 28 | function uv_stream_t:write(content) 29 | local req = uv_write_t() 30 | local buf = uv_buf_t(content, #content) 31 | verify(libuv.uv_write(req, self, buf, 1, async.uv_write_cb)) 32 | verify(async.yield(req)) 33 | req:free() 34 | buf:free() 35 | end 36 | 37 | function uv_stream_t:close() 38 | libuv2.uv2_stream_close(self, async.uv_close_cb) 39 | async.yield(self) 40 | end 41 | 42 | return uv_stream_t 43 | -------------------------------------------------------------------------------- /src/uv/ctypes/uv_tcp_t.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local async = require 'uv/util/async' 3 | local ctype = require 'uv/util/ctype' 4 | local join = require 'uv/util/join' 5 | local libuv = require 'uv/libuv' 6 | local libuv2 = require 'uv/libuv2' 7 | local libc = require 'uv/libc' 8 | local uv_buf_t = require 'uv/ctypes/uv_buf_t' 9 | local uv_connect_t = require 'uv/ctypes/uv_connect_t' 10 | local uv_getaddrinfo_t = require 'uv/ctypes/uv_getaddrinfo_t' 11 | local uv_loop_t = require 'uv/ctypes/uv_loop_t' 12 | local verify = require 'uv/util/verify' 13 | 14 | -------------------------------------------------------------------------------- 15 | -- uv_tcp_t 16 | -------------------------------------------------------------------------------- 17 | 18 | local uv_tcp_t = ctype('uv_tcp_t', function(loop) 19 | local self = ffi.cast('uv_tcp_t*', libc.malloc(ffi.sizeof('uv_tcp_t'))) 20 | libuv.uv_tcp_init(loop or libuv.uv_default_loop(), self) 21 | return self 22 | end) 23 | 24 | function uv_tcp_t:bind(host, port) 25 | local addr = ffi.new('struct sockaddr_in') 26 | libuv.uv_ip4_addr(host, port, addr) 27 | addr = ffi.cast('struct sockaddr*', addr) 28 | libuv.uv_tcp_bind(self, addr, 0) 29 | end 30 | 31 | function uv_tcp_t:connect(host, port) 32 | local socket = uv_tcp_t(self.loop) 33 | local connect = uv_connect_t() 34 | local addr = ffi.new('struct sockaddr_in') 35 | if libuv.uv_ip4_addr(host, port, addr) ~= 0 then 36 | local gai = uv_getaddrinfo_t(self.loop) 37 | addr = gai:getaddrinfo(host, tostring(port))[1] 38 | gai:free() 39 | end 40 | addr = ffi.cast('struct sockaddr*', addr) 41 | 42 | verify(libuv.uv_tcp_connect(connect, socket, addr, async.uv_connect_cb)) 43 | local status = async.yield(connect) 44 | if status < 0 then 45 | verify(status) 46 | end 47 | local handle = connect.handle 48 | connect:free() 49 | return handle 50 | end 51 | 52 | function uv_tcp_t:read() 53 | libuv2.uv2_tcp_read_start(self, libuv2.uv2_alloc_cb, async.uv_read_cb) 54 | local nread, buf = async.yield(self) 55 | libuv2.uv2_tcp_read_stop(self) 56 | verify(nread) 57 | local chunk = (nread < 0) and '' or ffi.string(buf.base, nread) 58 | libc.free(buf.base) 59 | return chunk, nread 60 | end 61 | 62 | function uv_tcp_t:write(content) 63 | local req = ffi.new('uv_write_t') 64 | local buf = uv_buf_t(content, #content) 65 | verify(libuv2.uv2_tcp_write(req, self, buf, 1, async.uv_write_cb)) 66 | local status = async.yield(req) 67 | buf:free() 68 | return 0 == status 69 | end 70 | 71 | function uv_tcp_t:listen() 72 | verify(libuv2.uv2_tcp_listen(self, 128, async.uv_connection_cb)) 73 | end 74 | 75 | function uv_tcp_t:accept() 76 | local status = async.yield(self) 77 | if status >= 0 then 78 | local socket = uv_tcp_t() 79 | if 0 == tonumber(libuv2.uv2_tcp_accept(self, socket)) then 80 | return socket 81 | end 82 | end 83 | end 84 | 85 | function uv_tcp_t:close() 86 | libuv2.uv2_tcp_close(self, async.uv_close_cb) 87 | async.yield(self) 88 | libc.free(self) 89 | end 90 | 91 | function uv_tcp_t:getsockname() 92 | local addr = ffi.new('struct sockaddr') 93 | local len = ffi.new('int[1]') 94 | len[0] = ffi.sizeof(addr) 95 | local buf = libc.malloc(4096) 96 | verify(libuv.uv_tcp_getsockname(self, addr, len)) 97 | addr = ffi.cast('struct sockaddr_in*', addr) 98 | verify(libuv.uv_ip4_name(addr, buf, 4096)) 99 | local peername = ffi.string(buf) 100 | libc.free(buf) 101 | return peername 102 | end 103 | 104 | function uv_tcp_t:getpeername() 105 | local addr = ffi.new('struct sockaddr') 106 | local len = ffi.new('int[1]') 107 | len[0] = ffi.sizeof(addr) 108 | local buf = libc.malloc(4096) 109 | verify(libuv.uv_tcp_getpeername(self, addr, len)) 110 | addr = ffi.cast('struct sockaddr_in*', addr) 111 | verify(libuv.uv_ip4_name(addr, buf, 4096)) 112 | local peername = ffi.string(buf) 113 | libc.free(buf) 114 | return peername 115 | end 116 | 117 | return uv_tcp_t 118 | -------------------------------------------------------------------------------- /src/uv/ctypes/uv_timer_t.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local async = require 'uv/util/async' 3 | local ctype = require 'uv/util/ctype' 4 | local libuv = require 'uv/libuv' 5 | local libc = require 'uv/libc' 6 | local uv_loop_t = require 'uv/ctypes/uv_loop_t' 7 | local verify = require 'uv/util/verify' 8 | 9 | -------------------------------------------------------------------------------- 10 | -- uv_timer_t 11 | -------------------------------------------------------------------------------- 12 | 13 | local uv_timer_t = ctype('uv_timer_t', function(loop) 14 | local self = ffi.cast('uv_timer_t*', libc.malloc(ffi.sizeof('uv_timer_t'))) 15 | libuv.uv_timer_init(loop or libuv.uv_default_loop(), self) 16 | return self 17 | end) 18 | 19 | function uv_timer_t:free() 20 | libc.free(self) 21 | end 22 | 23 | function uv_timer_t:every(timeout, callback) 24 | verify(libuv.uv_timer_start(self, async.uv_timer_cb, timeout, timeout)) 25 | while true do 26 | callback(self, async.yield(self)) 27 | end 28 | end 29 | 30 | function uv_timer_t:sleep(timeout) 31 | verify(libuv.uv_timer_start(self, async.uv_timer_cb, timeout, 0)) 32 | async.yield(self) 33 | end 34 | 35 | function uv_timer_t:stop() 36 | verify(libuv.uv_timer_stop(self)) 37 | end 38 | 39 | function uv_timer_t:again() 40 | verify(libuv.uv_timer_again(self)) 41 | end 42 | 43 | function uv_timer_t:set_repeat(repeat_time) 44 | libuv.uv_timer_set_repeat(self, repeat_time) 45 | end 46 | 47 | function uv_timer_t:get_repeat() 48 | return libuv.uv_timer_get_repeat(self) 49 | end 50 | 51 | return uv_timer_t 52 | -------------------------------------------------------------------------------- /src/uv/ctypes/uv_write_t.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local ctype = require 'uv/util/ctype' 3 | local libuv = require 'uv/libuv' 4 | local libuv2 = require 'uv/libuv2' 5 | local libc = require 'uv/libc' 6 | 7 | -------------------------------------------------------------------------------- 8 | -- uv_write_t 9 | -------------------------------------------------------------------------------- 10 | 11 | local uv_write_t = ctype('uv_write_t', function() 12 | local self = ffi.cast('uv_write_t*', libc.malloc(ffi.sizeof('uv_write_t'))) 13 | return self 14 | end) 15 | 16 | function uv_write_t:free() 17 | libc.free(self) 18 | end 19 | 20 | return uv_write_t 21 | -------------------------------------------------------------------------------- /src/uv/fs.lua: -------------------------------------------------------------------------------- 1 | require 'uv/ctypes/init' 2 | local class = require 'uv/util/class' 3 | local ffi = require 'ffi' 4 | local libuv = require 'uv/libuv' 5 | local libuv2 = require 'uv/libuv2' 6 | local libc = require 'uv/libc' 7 | local uv_fs_t = require 'uv/ctypes/uv_fs_t' 8 | local uv_buf_t = require 'uv/ctypes/uv_buf_t' 9 | local errno = require 'uv/util/errno' 10 | 11 | -- libc.umask(0) 12 | 13 | 14 | -------------------------------------------------------------------------------- 15 | -- octal 16 | -------------------------------------------------------------------------------- 17 | 18 | local function octal(s) 19 | local i = 0 20 | for c in s:gmatch('.') do 21 | i = i * 8 + tonumber(c) 22 | end 23 | return i 24 | end 25 | 26 | do 27 | assert(octal('0') == 0) 28 | assert(octal('1') == 1) 29 | assert(octal('10') == 8) 30 | assert(octal('11') == 9) 31 | assert(octal('100') == 64) 32 | assert(octal('101') == 65) 33 | end 34 | 35 | -------------------------------------------------------------------------------- 36 | -- flags_atoi 37 | -------------------------------------------------------------------------------- 38 | 39 | local flags_atoi = setmetatable({}, { __index = function(self, s) 40 | assert(type(s) == 'number', 'file flags should be "r", "w", "a", "r+", "w+", "a+", or a number') 41 | self[s] = s 42 | return s 43 | end}) 44 | 45 | do 46 | local O_RDONLY = 0x0000 -- open for reading only 47 | local O_WRONLY = 0x0001 -- open for writing only 48 | local O_RDWR = 0x0002 -- open for reading and writing 49 | local O_NONBLOCK = 0x0004 -- no delay 50 | local O_APPEND = 0x0008 -- set append mode 51 | local O_SHLOCK = 0x0010 -- open with shared file lock 52 | local O_EXLOCK = 0x0020 -- open with exclusive file lock 53 | local O_ASYNC = 0x0040 -- signal pgrp when data ready 54 | local O_NOFOLLOW = 0x0100 -- don't follow symlinks 55 | local O_CREAT = 0x0200 -- create if nonexistant 56 | local O_TRUNC = 0x0400 -- truncate to zero length 57 | local O_EXCL = 0x0800 -- error if already exists 58 | 59 | flags_atoi['r'] = O_RDONLY 60 | flags_atoi['w'] = O_WRONLY + O_CREAT + O_TRUNC 61 | flags_atoi['a'] = O_WRONLY + O_CREAT + O_APPEND 62 | flags_atoi['r+'] = O_RDWR 63 | flags_atoi['w+'] = O_RDWR + O_CREAT + O_TRUNC 64 | flags_atoi['a+'] = O_RDWR + O_CREAT + O_APPEND 65 | end 66 | 67 | -------------------------------------------------------------------------------- 68 | -- mode_atoi 69 | -------------------------------------------------------------------------------- 70 | 71 | local mode_atoi = setmetatable({}, { __index = function(self, s) 72 | local i 73 | if type(s) == 'string' then 74 | if #s == 3 then 75 | i = octal(s) 76 | elseif #s == 9 then 77 | i = 0 78 | local function match_char(index, expected_char, n) 79 | local char = s:sub(index, index) 80 | if char == expected_char then 81 | i = i + n 82 | elseif char ~= '-' then 83 | error('file modes look like: "755" or "rwxr-xr-x"') 84 | end 85 | end 86 | match_char(1, 'r', 256) 87 | match_char(2, 'w', 128) 88 | match_char(3, 'x', 64) 89 | match_char(4, 'r', 32) 90 | match_char(5, 'w', 16) 91 | match_char(6, 'x', 8) 92 | match_char(7, 'r', 4) 93 | match_char(8, 'w', 2) 94 | match_char(9, 'x', 1) 95 | else 96 | error('file modes look like: "755" or "rwxr-xr-x"') 97 | end 98 | elseif type(s) == 'number' then 99 | i = s 100 | else 101 | error('unexpected mode type: ' .. type(s)) 102 | end 103 | self[s] = i 104 | return i 105 | end}) 106 | 107 | do 108 | assert(mode_atoi['001'] == 1) 109 | assert(mode_atoi['007'] == 7) 110 | assert(mode_atoi['070'] == 7 * 8) 111 | assert(mode_atoi['700'] == 7 * 64) 112 | assert(mode_atoi['777'] == 511) 113 | 114 | assert(mode_atoi['--------x'] == 1) 115 | assert(mode_atoi['-------w-'] == 2) 116 | assert(mode_atoi['------r--'] == 4) 117 | assert(mode_atoi['-----x---'] == 8) 118 | assert(mode_atoi['----w----'] == 16) 119 | assert(mode_atoi['---r-----'] == 32) 120 | assert(mode_atoi['--x------'] == 64) 121 | assert(mode_atoi['-w-------'] == 128) 122 | assert(mode_atoi['r--------'] == 256) 123 | assert(mode_atoi['rwxrwxrwx'] == 511) 124 | 125 | assert(mode_atoi[1] == 1) 126 | assert(mode_atoi[511] == 511) 127 | 128 | do 129 | local ok, err = pcall(function() return mode_atoi[true] end) 130 | assert(not ok) 131 | assert(err:find('unexpected mode type: boolean')) 132 | end 133 | end 134 | 135 | -------------------------------------------------------------------------------- 136 | -- Stat 137 | -------------------------------------------------------------------------------- 138 | 139 | local S_IFMT = octal('0170000') -- type of file mask 140 | local S_IFIFO = octal('0010000') -- named pipe (fifo) 141 | local S_IFCHR = octal('0020000') -- character special 142 | local S_IFDIR = octal('0040000') -- directory 143 | local S_IFBLK = octal('0060000') -- block special 144 | local S_IFREG = octal('0100000') -- regular 145 | local S_IFLNK = octal('0120000') -- symbolic link 146 | local S_IFSOCK = octal('0140000') -- socket 147 | 148 | local Stat = function(stat) 149 | return { 150 | uid = stat.st_uid, 151 | gid = stat.st_gid, 152 | size = stat.st_size, 153 | mode = bit.band(tonumber(stat.st_mode), bit.bnot(S_IFMT)), 154 | is_dir = bit.band(tonumber(stat.st_mode), S_IFDIR) > 0, 155 | is_fifo = bit.band(tonumber(stat.st_mode), S_IFIFO) > 0, 156 | atime = stat.st_atim.tv_sec, 157 | atimensec = stat.st_atim.tv_nsec, 158 | mtime = stat.st_mtim.tv_sec, 159 | mtimensec = stat.st_mtim.tv_nsec, 160 | ctime = stat.st_ctim.tv_sec, 161 | ctimensec = stat.st_ctim.tv_nsec, 162 | birthtime = stat.st_birthtim.tv_sec, 163 | birthtimensec = stat.st_birthtim.tv_nsec, 164 | } 165 | end 166 | 167 | -------------------------------------------------------------------------------- 168 | -- File 169 | -------------------------------------------------------------------------------- 170 | 171 | local File = class(function(descriptor) 172 | return { descriptor = descriptor } 173 | end) 174 | 175 | function File:read() 176 | return uv_fs_t():read(self.descriptor) 177 | end 178 | 179 | function File:close() 180 | return uv_fs_t():close(self.descriptor) 181 | end 182 | 183 | function File:write(buffer) 184 | return uv_fs_t():write(self.descriptor, buffer) 185 | end 186 | 187 | function File:chmod(mode) 188 | local mode = mode_atoi[mode or '700'] 189 | return uv_fs_t():fchmod(self.descriptor, mode) 190 | end 191 | 192 | function File:chown(uid, gid) 193 | return uv_fs_t():fchown(self.descriptor, uid, gid) 194 | end 195 | 196 | function File:sync() 197 | return uv_fs_t():fsync(self.descriptor) 198 | end 199 | 200 | function File.stat() 201 | return Stat(uv_fs_t():fstat(self.descriptor)) 202 | end 203 | 204 | -------------------------------------------------------------------------------- 205 | -- fs 206 | -------------------------------------------------------------------------------- 207 | 208 | local fs = {} 209 | 210 | function fs.open(path, flags, mode) 211 | local flags = flags_atoi[flags or 'r'] 212 | local mode = mode_atoi[mode or '700'] 213 | local mask = libc.umask(0) 214 | local descriptor = uv_fs_t():open(path, flags, mode) 215 | libc.umask(mask) 216 | return File(descriptor) 217 | end 218 | 219 | function fs.unlink(path) 220 | return uv_fs_t():unlink(path) 221 | end 222 | 223 | function fs.mkdir(path, mode) 224 | local mode = mode_atoi[mode or '700'] 225 | return uv_fs_t():mkdir(path, mode) 226 | end 227 | 228 | function fs.rmdir(path) 229 | return uv_fs_t():rmdir(path) 230 | end 231 | 232 | function fs.chmod(path, mode) 233 | local mode = mode_atoi[mode or '700'] 234 | return uv_fs_t():chmod(path, mode) 235 | end 236 | 237 | function fs.chown(path, uid, gid) 238 | return uv_fs_t():chown(path, uid, gid) 239 | end 240 | 241 | function fs.stat(path) 242 | return Stat(uv_fs_t():stat(path)) 243 | end 244 | 245 | function fs.lstat(path) 246 | return uv_fs_t():lstat(path) 247 | end 248 | 249 | function fs.rename(path, new_path) 250 | return uv_fs_t():rename(path, new_path) 251 | end 252 | 253 | function fs.link(path, new_path) 254 | return uv_fs_t():link(path, new_path) 255 | end 256 | 257 | function fs.symlink(path, new_path) 258 | return uv_fs_t():symlink(path, new_path, 0) 259 | end 260 | 261 | function fs.readlink(path) 262 | return uv_fs_t():readlink(path) 263 | end 264 | 265 | function fs.readfile(path) 266 | local file = fs.open(path) 267 | local buffer = {} 268 | repeat 269 | local chunk = file:read() 270 | table.insert(buffer, chunk) 271 | until chunk == '' 272 | file:close() 273 | return table.concat(buffer) 274 | end 275 | 276 | function fs.writefile(path, body) 277 | local file = fs.open(path, 'w') 278 | file:write(body) 279 | file:close() 280 | end 281 | 282 | function fs.tmpname() 283 | return os.tmpname() 284 | end 285 | 286 | function fs.cwd() 287 | local buf = uv_buf_t() 288 | local status = libuv2.uv2_cwd(buf) 289 | if status ~= 0 then 290 | buf:free() 291 | error(errno(status)) 292 | end 293 | local cwd = ffi.string(buf.base, buf.len) 294 | buf:free() 295 | return cwd 296 | end 297 | 298 | function fs.chdir(dir) 299 | local status = libuv.uv_chdir(dir) 300 | if 0 ~= status then 301 | error(errno(status)) 302 | end 303 | end 304 | 305 | function fs.readdir(path) 306 | local filenames = uv_fs_t():readdir(path, 0) 307 | table.sort(filenames) 308 | return filenames 309 | end 310 | 311 | function fs.readdir_r(path) 312 | local filenames = {} 313 | local function scan(prefix) 314 | if fs.stat(path .. '/' .. prefix).is_dir then 315 | for _, f in ipairs(fs.readdir(path .. '/' .. prefix)) do 316 | scan(prefix .. '/' .. f) 317 | end 318 | else 319 | table.insert(filenames, prefix) 320 | end 321 | end 322 | for _, prefix in ipairs(fs.readdir(path)) do 323 | scan(prefix) 324 | end 325 | table.sort(filenames) 326 | return filenames 327 | end 328 | 329 | function fs.rm_rf(path) 330 | if fs.stat(path).is_dir then 331 | for _, filename in ipairs(fs.readdir(path)) do 332 | fs.rm_rf(path .. '/' .. filename) 333 | end 334 | fs.rmdir(path) 335 | else 336 | fs.unlink(path) 337 | end 338 | end 339 | 340 | function fs.extname(filename) 341 | return filename:match('%.[^./]+$') or '' 342 | end 343 | 344 | function fs.basename(filename) 345 | return filename:match('[^/]+$'):sub(1, -#fs.extname(filename) - 1) 346 | end 347 | 348 | function fs.dirname(filename) 349 | return filename:match('.*/') or '' 350 | end 351 | 352 | do 353 | assert(fs.dirname('/a/b') == '/a/') 354 | assert(fs.dirname('/a/') == '/a/') 355 | assert(fs.dirname('/a') == '/') 356 | assert(fs.dirname('/') == '/') 357 | assert(fs.dirname('a') == '') 358 | assert(fs.dirname('a/') == 'a/') 359 | assert(fs.dirname('a/b') == 'a/') 360 | end 361 | 362 | local function finally(try, always) 363 | local ok, err = xpcall(try, function(err) 364 | return debug.traceback(err, 2) 365 | end) 366 | always() 367 | if not ok then 368 | error(err, 0) 369 | end 370 | end 371 | 372 | function fs.with_tempdir(callback) 373 | local name = fs.tmpname() 374 | fs.unlink(name) 375 | fs.mkdir(name) 376 | finally(function() 377 | callback(name) 378 | end, function() 379 | fs.rm_rf(name) 380 | end) 381 | end 382 | 383 | return fs 384 | -------------------------------------------------------------------------------- /src/uv/http.lua: -------------------------------------------------------------------------------- 1 | require 'uv/ctypes/init' 2 | local class = require 'uv/util/class' 3 | local ffi = require 'ffi' 4 | local libuv = require 'uv/libuv' 5 | local libuv2 = require 'uv/libuv2' 6 | local libhttp_parser = require 'uv/libhttp_parser' 7 | local uv_tcp_t = require 'uv/ctypes/uv_tcp_t' 8 | local url = require 'uv.url' 9 | local join = require 'uv/util/join' 10 | 11 | -------------------------------------------------------------------------------- 12 | -- status_codes 13 | -------------------------------------------------------------------------------- 14 | 15 | local status_codes = {} 16 | for i = 100, 199 do status_codes[i] = 'Informational' end 17 | for i = 200, 299 do status_codes[i] = 'Successful' end 18 | for i = 300, 399 do status_codes[i] = 'Redirection' end 19 | for i = 400, 499 do status_codes[i] = 'Client Error' end 20 | for i = 500, 599 do status_codes[i] = 'Successful' end 21 | status_codes[100] = 'Continue' 22 | status_codes[101] = 'Switching Protocols' 23 | status_codes[200] = 'OK' 24 | status_codes[201] = 'Created' 25 | status_codes[202] = 'Accepted' 26 | status_codes[203] = 'Non-Authoritative Information' 27 | status_codes[204] = 'No Content' 28 | status_codes[205] = 'Reset Content' 29 | status_codes[206] = 'Partial Content' 30 | status_codes[300] = 'Multiple Choices' 31 | status_codes[301] = 'Moved Permanently' 32 | status_codes[302] = 'Found' 33 | status_codes[303] = 'See Other' 34 | status_codes[304] = 'Not Modified' 35 | status_codes[305] = 'Use Proxy' 36 | status_codes[306] = '(Unused)' 37 | status_codes[307] = 'Temporary Redirect' 38 | status_codes[400] = 'Bad Request' 39 | status_codes[401] = 'Unauthorized' 40 | status_codes[402] = 'Payment Required' 41 | status_codes[403] = 'Forbidden' 42 | status_codes[404] = 'Not Found' 43 | status_codes[405] = 'Method Not Allowed' 44 | status_codes[406] = 'Not Acceptable' 45 | status_codes[407] = 'Proxy Authentication Required' 46 | status_codes[408] = 'Request Timeout' 47 | status_codes[409] = 'Conflict' 48 | status_codes[410] = 'Gone' 49 | status_codes[411] = 'Length Required' 50 | status_codes[412] = 'Precondition Failed' 51 | status_codes[413] = 'Request Entity Too Large' 52 | status_codes[414] = 'Request-URI Too Long' 53 | status_codes[415] = 'Unsupported Media Type' 54 | status_codes[416] = 'Requested Range Not Satisfiable' 55 | status_codes[417] = 'Expectation Failed' 56 | status_codes[500] = 'Internal Server Error' 57 | status_codes[501] = 'Not Implemented' 58 | status_codes[502] = 'Bad Gateway' 59 | status_codes[503] = 'Service Unavailable' 60 | status_codes[504] = 'Gateway Timeout' 61 | status_codes[505] = 'HTTP Version Not Supported' 62 | 63 | -------------------------------------------------------------------------------- 64 | -- parse_http 65 | -------------------------------------------------------------------------------- 66 | 67 | local function parse_http(stream, mode) 68 | local header_last, header_field, header_value, message_complete 69 | local headers, body, url = {}, '' 70 | 71 | local on = {} 72 | 73 | function on:url(buf, length) 74 | url = (url or '') .. ffi.string(buf, length) 75 | return 0 76 | end 77 | 78 | function on:header_field(buf, length) 79 | if header_last == 'field' then 80 | header_field = header_field .. ffi.string(buf, length) 81 | elseif header_last == 'value' then 82 | headers[header_field] = header_value 83 | header_field = ffi.string(buf, length) 84 | else 85 | header_field = ffi.string(buf, length) 86 | end 87 | header_last = 'field' 88 | return 0 89 | end 90 | 91 | function on:header_value(buf, length) 92 | if header_last == 'field' then 93 | header_value = ffi.string(buf, length) 94 | elseif header_last == 'value' then 95 | header_value = header_value .. ffi.string(buf, length) 96 | else 97 | error('header value before field') 98 | end 99 | header_last = 'value' 100 | return 0 101 | end 102 | 103 | function on:headers_complete() 104 | if header_last == 'value' then 105 | headers[header_field] = header_value 106 | end 107 | return 0 108 | end 109 | 110 | function on:body(buf, length) 111 | body = body .. ffi.string(buf, length) 112 | return 0 113 | end 114 | 115 | function on:message_complete() 116 | message_complete = true 117 | return 0 118 | end 119 | 120 | local settings = ffi.new('http_parser_settings') 121 | 122 | settings.on_url = ffi.cast('http_data_cb', on.url) 123 | settings.on_header_field = ffi.cast('http_data_cb', on.header_field) 124 | settings.on_header_value = ffi.cast('http_data_cb', on.header_value) 125 | settings.on_headers_complete = ffi.cast('http_cb', on.headers_complete) 126 | settings.on_body = ffi.cast('http_data_cb', on.body) 127 | settings.on_message_complete = ffi.cast('http_cb', on.message_complete) 128 | 129 | local parser = ffi.new('http_parser') 130 | libhttp_parser.http_parser_init(parser, mode) 131 | 132 | repeat 133 | libhttp_parser.http_parser_execute(parser, settings, stream:read()) 134 | until message_complete 135 | 136 | settings.on_url:free() 137 | settings.on_header_field:free() 138 | settings.on_header_value:free() 139 | settings.on_headers_complete:free() 140 | settings.on_body:free() 141 | settings.on_message_complete:free() 142 | 143 | local message = { url = url, headers = headers, body = body } 144 | 145 | if mode == libhttp_parser.HTTP_REQUEST then 146 | message.method = ffi.string(libhttp_parser.http_method_str(parser.method)) 147 | else 148 | message.status = parser.status_code 149 | end 150 | 151 | return message 152 | end 153 | 154 | do 155 | local function test_stream(s) 156 | local r = {} 157 | function r:read() 158 | function r:read() return '', 0 end 159 | return s, #s 160 | end 161 | return r 162 | end 163 | 164 | local request = parse_http(test_stream('GET / HTTP/1.1\n\n'), libhttp_parser.HTTP_REQUEST) 165 | assert(request.method == 'GET') 166 | assert(request.status == nil) 167 | assert(request.url == '/') 168 | assert(request.body == '') 169 | 170 | local request = parse_http(test_stream('POST / HTTP/1.1\n\n'), libhttp_parser.HTTP_REQUEST) 171 | assert(request.method == 'POST') 172 | end 173 | 174 | -------------------------------------------------------------------------------- 175 | -- Connection 176 | -------------------------------------------------------------------------------- 177 | 178 | local Connection = class(function(socket) 179 | return { socket = socket } 180 | end) 181 | 182 | function Connection:receive() 183 | local request = parse_http(self.socket, libhttp_parser.HTTP_REQUEST) 184 | url.split(request.url, request) 185 | request.ip = ffi.cast('uv_tcp_t*', self.socket):getpeername() 186 | return request 187 | end 188 | 189 | function Connection:respond(response) 190 | self.socket:write('HTTP/1.1 ' .. response.status .. ' ' .. status_codes[response.status] .. '\n') 191 | 192 | response.headers = response.headers or {} 193 | 194 | if not response.headers['Server'] then 195 | response.headers['Server'] = 'luajit-libuv' 196 | end 197 | 198 | response.headers['Content-Length'] = response.body and #response.body or 0 199 | 200 | for field, value in pairs(response.headers) do 201 | self.socket:write(field .. ': ' .. value .. '\n') 202 | end 203 | 204 | self.socket:write('\n') 205 | 206 | if response.body then 207 | self.socket:write(response.body) 208 | end 209 | end 210 | 211 | function Connection:close() 212 | return self.socket:close() 213 | end 214 | 215 | -------------------------------------------------------------------------------- 216 | -- Server 217 | -------------------------------------------------------------------------------- 218 | 219 | local Server = class(function(server) 220 | return { server = server } 221 | end) 222 | 223 | function Server:accept() 224 | local socket = self.server:accept() 225 | return Connection(socket) 226 | end 227 | 228 | function Server:close() 229 | return self.server:close() 230 | end 231 | 232 | -------------------------------------------------------------------------------- 233 | -- http 234 | -------------------------------------------------------------------------------- 235 | 236 | local http = {} 237 | 238 | function http.listen(host, port, on_request, on_error) 239 | if on_request then 240 | local server = http.listen(host, port) 241 | join(coroutine.create(function() 242 | while true do 243 | local connection = server:accept() 244 | join(coroutine.create(function() 245 | local request = connection:receive() 246 | local ok, response = xpcall(function() 247 | return on_request(request) 248 | end, on_error or error) 249 | if ok then 250 | connection:respond(response) 251 | end 252 | connection:close() 253 | end)) 254 | end 255 | end)) 256 | if coroutine.running() then 257 | return server 258 | else 259 | libuv.uv_default_loop():run() 260 | return 261 | end 262 | end 263 | 264 | local server = uv_tcp_t() 265 | server:bind(host, port) 266 | server:listen() 267 | return Server(server) 268 | end 269 | 270 | function http.request(request) 271 | if request.url then 272 | url.split(request.url, request) 273 | end 274 | 275 | local method = request.method or 'GET' 276 | local host = request.host or error('host required', 2) 277 | local port = request.port or 80 278 | local path = request.path or '/' 279 | local query = request.query or '' 280 | local headers = request.headers or {} 281 | local body = request.body or '' 282 | 283 | if not headers['Host'] then 284 | headers['Host'] = host 285 | end 286 | if not headers['User-Agent'] then 287 | headers['User-Agent'] = 'luajit-libuv' 288 | end 289 | 290 | local tcp = uv_tcp_t() 291 | local client = tcp:connect(host, tonumber(port)) 292 | client:write(method:upper() .. ' ' .. path .. '?' .. query .. ' HTTP/1.1\r\n') 293 | for header, value in pairs(headers) do 294 | client:write(header .. ': ' .. value .. '\r\n') 295 | end 296 | client:write('Content-Length: ' .. #body .. '\r\n\r\n') 297 | client:write(body) 298 | 299 | local response = parse_http(client, libhttp_parser.HTTP_RESPONSE) 300 | 301 | client:close() 302 | 303 | return response 304 | end 305 | 306 | function http.format_date(time) 307 | return os.date("!%a, %d %b %Y %H:%M:%S GMT", tonumber(time)) 308 | end 309 | 310 | local month_atoi = { 311 | Jan = 1, 312 | Feb = 2, 313 | Mar = 3, 314 | Apr = 4, 315 | May = 5, 316 | Jun = 6, 317 | Jul = 7, 318 | Aug = 8, 319 | Sep = 9, 320 | Oct = 10, 321 | Nov = 11, 322 | Dec = 12, 323 | } 324 | 325 | local function get_timezone_offset(ts) 326 | local utcdate = os.date("!*t", ts) 327 | local localdate = os.date("*t", ts) 328 | localdate.isdst = false -- this is the trick 329 | return os.difftime(os.time(localdate), os.time(utcdate)) 330 | end 331 | 332 | function http.parse_date(s) 333 | local rfc1123 = '%w+, (%d+) (%w+) (%d+) (%d+):(%d+):(%d+) GMT' -- Sun, 06 Nov 1994 08:49:37 GMT 334 | local rfc1036 = '%w+, (%d+)-(%w+)-(%d+) (%d+):(%d+):(%d+) GMT' -- Sunday, 06-Nov-94 08:49:37 GMT 335 | local asctime = '%w+ (%w+) +(%d+) (%d+):(%d+):(%d+) (%d+)' -- Sun Nov 6 08:49:37 1994 336 | 337 | local day, month, year, hour, min, sec 338 | 339 | day, month, year, hour, min, sec = s:match(rfc1123) 340 | if day then 341 | -- print('RFC 1123: ' .. s) 342 | else 343 | day, month, year, hour, min, sec = s:match(rfc1036) 344 | if day then 345 | -- print('RFC 1036: ' .. s) 346 | if tonumber(year) >= 0 then 347 | year = '19' .. year 348 | else 349 | year = '20' .. year 350 | end 351 | else 352 | month, day, hour, min, sec, year = s:match(asctime) 353 | if day then 354 | -- print('asctime: ' .. s) 355 | else 356 | return 357 | end 358 | end 359 | end 360 | 361 | local time = os.time { 362 | year = tonumber(year), 363 | month = assert(month_atoi[month], 'invalid month'), 364 | day = tonumber(day), 365 | hour = tonumber(hour), 366 | min = tonumber(min), 367 | sec = tonumber(sec), 368 | isdst = false, 369 | } 370 | return time + get_timezone_offset(time) 371 | end 372 | 373 | return http 374 | -------------------------------------------------------------------------------- /src/uv/init.lua: -------------------------------------------------------------------------------- 1 | local uv = {} 2 | 3 | uv.fs = require 'uv.fs' 4 | uv.http = require 'uv.http' 5 | uv.loop = require 'uv.loop' 6 | uv.parallel = require 'uv.parallel' 7 | uv.process = require 'uv.process' 8 | uv.system = require 'uv.system' 9 | uv.timer = require 'uv.timer' 10 | uv.url = require 'uv.url' 11 | 12 | return uv 13 | -------------------------------------------------------------------------------- /src/uv/lib/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pguillory/luajit-libuv/dc0ec6e9de02011783d01ff99788be27b444c53e/src/uv/lib/.gitignore -------------------------------------------------------------------------------- /src/uv/libc.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | 3 | ffi.cdef [[ 4 | void *malloc(size_t size); 5 | void free(void *ptr); 6 | uint16_t ntohs(uint16_t netshort); 7 | mode_t umask(mode_t mask); 8 | uid_t getuid(void); 9 | gid_t getgid(void); 10 | pid_t getpid(void); 11 | ]] 12 | 13 | return ffi.C 14 | -------------------------------------------------------------------------------- /src/uv/libhttp_parser.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | 3 | local dir = debug.getinfo(1).source:match('@(.*/)') or '.' 4 | 5 | do 6 | local file = io.open(dir .. 'lib/libhttp_parser.min.h') 7 | if not file then 8 | error('libhttp_parser.min.h not found') 9 | end 10 | local header = file:read('*a') 11 | ffi.cdef(header:match('typedef struct http_parser.*')) 12 | file:close() 13 | end 14 | 15 | local ok, lib = pcall(function() return ffi.load(dir .. 'lib/libhttp_parser.dylib') end) 16 | if ok then return lib end 17 | 18 | local ok, lib = pcall(function() return ffi.load(dir .. 'lib/libhttp_parser.so') end) 19 | if ok then return lib end 20 | 21 | assert('libhttp_parser not found') 22 | -------------------------------------------------------------------------------- /src/uv/libuv.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | 3 | local dir = debug.getinfo(1).source:match('@(.*/)') or '.' 4 | 5 | do 6 | local file = io.open(dir .. '/lib/libuv.min.h') 7 | if not file then 8 | error('libuv.min.h not found') 9 | end 10 | local header = file:read('*a') 11 | ffi.cdef(header) 12 | file:close() 13 | end 14 | 15 | local ok, lib = pcall(function() return ffi.load(dir .. 'lib/libuv.dylib') end) 16 | if ok and lib then return lib end 17 | 18 | local ok, lib = pcall(function() return ffi.load(dir .. 'lib/libuv.so') end) 19 | if ok and lib then return lib end 20 | 21 | error('libuv not found') 22 | 23 | -- return require 'uv/libuv2' 24 | -------------------------------------------------------------------------------- /src/uv/libuv2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../../deps/libuv/include/uv.h" 3 | #include "libuv2.h" 4 | 5 | void uv2_alloc_cb(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { 6 | buf->base = malloc(suggested_size); 7 | buf->len = suggested_size; 8 | } 9 | 10 | void uv2_stream_close(uv_stream_t* stream, uv_close_cb close_cb) { 11 | return uv_close((uv_handle_t*) stream, close_cb); 12 | } 13 | 14 | void uv2_tcp_close(uv_tcp_t* tcp, uv_close_cb close_cb) { 15 | return uv_close((uv_handle_t*) tcp, close_cb); 16 | } 17 | 18 | int uv2_tcp_accept(uv_tcp_t* server, uv_tcp_t* client) { 19 | return uv_accept((uv_stream_t*) server, (uv_stream_t*) client); 20 | } 21 | 22 | int uv2_tcp_read_start(uv_tcp_t* tcp, uv_alloc_cb alloc_cb, uv_read_cb read_cb) { 23 | return uv_read_start((uv_stream_t*) tcp, alloc_cb, read_cb); 24 | } 25 | 26 | int uv2_tcp_read_stop(uv_tcp_t* tcp) { 27 | return uv_read_stop((uv_stream_t*) tcp); 28 | } 29 | 30 | int uv2_tcp_write(uv_write_t* req, uv_tcp_t* tcp, const uv_buf_t bufs[], unsigned int nbufs, uv_write_cb cb) { 31 | return uv_write(req, (uv_stream_t*) tcp, bufs, nbufs, cb); 32 | } 33 | 34 | int uv2_tcp_listen(uv_tcp_t* stream, int backlog, uv_connection_cb cb) { 35 | return uv_listen((uv_stream_t*) stream, backlog, cb); 36 | } 37 | 38 | int uv2_cwd(uv_buf_t* buf) { 39 | return uv_cwd(buf->base, &buf->len); 40 | } 41 | 42 | int uv2_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, int mode, uv_fs_cb cb) { 43 | int flags2 = 0; 44 | 45 | if (flags & 0x0000) flags2 += O_RDONLY; 46 | if (flags & 0x0001) flags2 += O_WRONLY; 47 | if (flags & 0x0002) flags2 += O_RDWR; 48 | //if (flags & 0x0004) flags2 += O_NONBLOCK; 49 | if (flags & 0x0008) flags2 += O_APPEND; 50 | //if (flags & 0x0010) flags2 += O_SHLOCK; 51 | //if (flags & 0x0020) flags2 += O_EXLOCK; 52 | //if (flags & 0x0040) flags2 += O_ASYNC; 53 | //if (flags & 0x0100) flags2 += O_NOFOLLOW; 54 | if (flags & 0x0200) flags2 += O_CREAT; 55 | if (flags & 0x0400) flags2 += O_TRUNC; 56 | //if (flags & 0x0800) flags2 += O_EXCL; 57 | 58 | return uv_fs_open(loop, req, path, flags2, mode, cb); 59 | } 60 | 61 | int uv2_exepath(uv_buf_t* buffer) { 62 | return uv_exepath(buffer->base, &buffer->len); 63 | } 64 | 65 | int uv2_sigkill() { 66 | return SIGKILL; 67 | } 68 | 69 | int uv2_sighup() { 70 | return SIGHUP; 71 | } 72 | 73 | int uv2_sigint() { 74 | return SIGINT; 75 | } 76 | 77 | int uv2_sigwinch() { 78 | return SIGWINCH; 79 | } 80 | -------------------------------------------------------------------------------- /src/uv/libuv2.h: -------------------------------------------------------------------------------- 1 | 2 | void uv2_alloc_cb(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf); 3 | void uv2_stream_close(uv_stream_t* stream, uv_close_cb close_cb); 4 | void uv2_tcp_close(uv_tcp_t* stream, uv_close_cb close_cb); 5 | int uv2_tcp_accept(uv_tcp_t* server, uv_tcp_t* client); 6 | int uv2_tcp_read_start(uv_tcp_t*, uv_alloc_cb alloc_cb, uv_read_cb read_cb); 7 | int uv2_tcp_read_stop(uv_tcp_t*); 8 | int uv2_tcp_write(uv_write_t* req, uv_tcp_t* tcp, const uv_buf_t bufs[], unsigned int nbufs, uv_write_cb cb); 9 | int uv2_tcp_listen(uv_tcp_t* stream, int backlog, uv_connection_cb cb); 10 | int uv2_cwd(uv_buf_t* buf); 11 | int uv2_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, int mode, uv_fs_cb cb); 12 | int uv2_exepath(uv_buf_t* buffer); 13 | int uv2_sigkill(); 14 | int uv2_sighup(); 15 | int uv2_sigint(); 16 | int uv2_sigwinch(); 17 | -------------------------------------------------------------------------------- /src/uv/libuv2.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | 3 | local dir = debug.getinfo(1).source:match('@(.*/)') or '.' 4 | 5 | do 6 | local file = io.open(dir .. 'libuv2.h') 7 | if not file then 8 | error('libuv2.h not found') 9 | end 10 | local header = file:read('*a') 11 | ffi.cdef(header) 12 | file:close() 13 | end 14 | 15 | local ok, lib = pcall(function() return ffi.load(dir .. 'lib/libuv2.dylib') end) 16 | if ok and lib then return lib end 17 | 18 | local ok, lib = pcall(function() return ffi.load(dir .. 'lib/libuv2.so') end) 19 | if ok and lib then return lib end 20 | 21 | error('libuv2 not found') 22 | -------------------------------------------------------------------------------- /src/uv/loop.lua: -------------------------------------------------------------------------------- 1 | require 'uv/ctypes/init' 2 | local ffi = require 'ffi' 3 | local timer = require 'uv.timer' 4 | local libuv = require 'uv/libuv' 5 | local libuv2 = require 'uv/libuv2' 6 | local uv_idle_t = require 'uv/ctypes/uv_idle_t' 7 | local uv_prepare_t = require 'uv/ctypes/uv_prepare_t' 8 | local uv_check_t = require 'uv/ctypes/uv_check_t' 9 | local join = require 'uv/util/join' 10 | 11 | local loop = {} 12 | 13 | function loop.run(callback) 14 | if callback then 15 | timer.set(0, callback) 16 | end 17 | return libuv.uv_default_loop():run() 18 | end 19 | 20 | function loop.alive() 21 | return libuv.uv_loop_alive(libuv.uv_default_loop()) ~= 0 22 | end 23 | 24 | function loop.stop() 25 | libuv.uv_default_loop():stop() 26 | end 27 | 28 | function loop.idle(callback) 29 | join(coroutine.create(function() 30 | uv_idle_t():start(callback) 31 | end)) 32 | end 33 | 34 | function loop.yield(callback) 35 | join(coroutine.create(function() 36 | uv_prepare_t():start(callback) 37 | end)) 38 | end 39 | 40 | function loop.resume(callback) 41 | join(coroutine.create(function() 42 | uv_check_t():start(callback) 43 | end)) 44 | end 45 | 46 | return loop 47 | -------------------------------------------------------------------------------- /src/uv/parallel.lua: -------------------------------------------------------------------------------- 1 | require 'uv/ctypes/init' 2 | local join = require 'uv/util/join' 3 | local timer = require 'uv.timer' 4 | local libuv = require 'uv/libuv' 5 | 6 | local parallel = {} 7 | 8 | function parallel.map(inputs, callback) 9 | local outputs = {} 10 | 11 | parallel.range(#inputs, function(i) 12 | outputs[i] = callback(inputs[i]) 13 | end) 14 | 15 | return outputs 16 | end 17 | 18 | function parallel.range(n, callback) 19 | local thread = coroutine.running() 20 | local busy = n 21 | 22 | for i = 1, n do 23 | timer.set(0, function() 24 | callback(i) 25 | busy = busy - 1 26 | if busy == 0 then 27 | join(thread) 28 | end 29 | end) 30 | end 31 | 32 | if thread then 33 | coroutine.yield() 34 | else 35 | busy = 0 36 | libuv.uv_default_loop():run() 37 | end 38 | end 39 | 40 | return parallel 41 | -------------------------------------------------------------------------------- /src/uv/process.lua: -------------------------------------------------------------------------------- 1 | require 'uv/ctypes/init' 2 | local ffi = require 'ffi' 3 | local async = require 'uv/util/async' 4 | local ctype = require 'uv/util/ctype' 5 | local libuv = require 'uv/libuv' 6 | local libuv2 = require 'uv/libuv2' 7 | local libc = require 'uv/libc' 8 | local uv_buf_t = require 'uv/ctypes/uv_buf_t' 9 | local uv_signal_t = require 'uv/ctypes/uv_signal_t' 10 | local uv_process_t = require 'uv/ctypes/uv_process_t' 11 | local uv_process_options_t = require 'uv/ctypes/uv_process_options_t' 12 | local join = require 'uv/util/join' 13 | local verify = require 'uv/util/verify' 14 | 15 | local signals = { 16 | kill = libuv2.uv2_sigkill(), 17 | int = libuv2.uv2_sigint(), 18 | hup = libuv2.uv2_sighup(), 19 | winch = libuv2.uv2_sigwinch(), 20 | } 21 | 22 | local signum_atoi = {} 23 | for k, v in pairs(signals) do 24 | signum_atoi[k] = v 25 | signum_atoi['SIG' .. k:upper()] = v 26 | signum_atoi[v] = v 27 | end 28 | 29 | local process = {} 30 | 31 | function process.pid() 32 | return libc.getpid(); 33 | end 34 | 35 | function process.on(signum, callback) 36 | local signum = signum_atoi[signum] 37 | local sig = uv_signal_t() 38 | join(coroutine.create(function() 39 | sig:start(signum, callback) 40 | end)) 41 | return sig 42 | end 43 | 44 | function process.kill(pid, signum) 45 | local signum = signum_atoi[signum or 'kill'] 46 | verify(libuv.uv_kill(pid, signum)) 47 | end 48 | 49 | function process.path() 50 | local buf = uv_buf_t() 51 | local status = libuv2.uv2_exepath(buf) 52 | assert(status == 0) 53 | local result = ffi.string(buf.base, buf.len) 54 | buf:free() 55 | return result 56 | end 57 | 58 | function process.usage() 59 | local usage = ffi.new('uv_rusage_t') 60 | verify(libuv.uv_getrusage(usage)) 61 | local result = { 62 | utime = usage.ru_utime.tv_usec, 63 | stime = usage.ru_stime.tv_usec, 64 | maxrss = usage.ru_maxrss, 65 | ixrss = usage.ru_ixrss, 66 | idrss = usage.ru_idrss, 67 | isrss = usage.ru_isrss, 68 | minflt = usage.ru_minflt, 69 | majflt = usage.ru_majflt, 70 | nswap = usage.ru_nswap, 71 | inblock = usage.ru_inblock, 72 | oublock = usage.ru_oublock, 73 | msgsnd = usage.ru_msgsnd, 74 | msgrcv = usage.ru_msgrcv, 75 | nsignals = usage.ru_nsignals, 76 | nvcsw = usage.ru_nvcsw, 77 | nivcsw = usage.ru_nivcsw, 78 | } 79 | return result 80 | end 81 | 82 | function process.title(value) 83 | local buf = uv_buf_t() 84 | verify(libuv.uv_get_process_title(buf.base, buf.len)) 85 | local title = ffi.string(buf.base) 86 | buf:free() 87 | if value then 88 | verify(uv_set_process_title(value)) 89 | end 90 | return title 91 | end 92 | 93 | function process.spawn(args) 94 | local options = uv_process_options_t() 95 | 96 | options.exit_cb = async.uv_exit_cb 97 | 98 | assert(#args >= 1, 'path to executable required') 99 | options.file = args[1] 100 | 101 | options.args = ffi.new('char*[?]', #args + 1) 102 | for i, arg in ipairs(args) do 103 | options.args[i - 1] = ffi.cast('char*', arg) 104 | end 105 | 106 | options.stdio_count = 3 107 | local stdio = ffi.new('uv_stdio_container_t[?]', 3) 108 | options.stdio = stdio 109 | 110 | if type(args.stdin) == 'number' then 111 | options.stdio[0].flags = libuv.UV_INHERIT_FD 112 | options.stdio[0].data.fd = args.stdin 113 | end 114 | 115 | if type(args.stdout) == 'number' then 116 | options.stdio[1].flags = libuv.UV_INHERIT_FD 117 | options.stdio[1].data.fd = args.stdout 118 | end 119 | 120 | if type(args.stderr) == 'number' then 121 | options.stdio[2].flags = libuv.UV_INHERIT_FD 122 | options.stdio[2].data.fd = args.stderr 123 | end 124 | 125 | if args.uid then 126 | options.uid = args.uid 127 | options.flags = bit.bor(options.flags, libuv.UV_PROCESS_SETUID) 128 | end 129 | 130 | if args.gid then 131 | options.gid = args.gid 132 | options.flags = bit.bor(options.flags, libuv.UV_PROCESS_SETGID) 133 | end 134 | 135 | local req = uv_process_t() 136 | local term_signal = req:spawn(options) 137 | options:free() 138 | return term_signal 139 | end 140 | 141 | return process 142 | -------------------------------------------------------------------------------- /src/uv/system.lua: -------------------------------------------------------------------------------- 1 | require 'uv/ctypes/init' 2 | local ffi = require 'ffi' 3 | local libuv = require 'uv/libuv' 4 | local libuv2 = require 'uv/libuv2' 5 | local verify = require 'uv/util/verify' 6 | 7 | local system = {} 8 | 9 | function system.free_memory() 10 | return libuv.uv_get_free_memory() 11 | end 12 | 13 | function system.total_memory() 14 | return libuv.uv_get_total_memory() 15 | end 16 | 17 | function system.hrtime() 18 | return tonumber(libuv.uv_hrtime()) / 1000000000 19 | end 20 | 21 | function system.loadavg() 22 | local avg = ffi.new('double[?]', 3) 23 | libuv.uv_loadavg(avg) 24 | return avg[0], avg[1], avg[2] 25 | end 26 | 27 | function system.uptime() 28 | local time = ffi.new('double[1]') 29 | verify(libuv.uv_uptime(time)) 30 | return time[0] 31 | end 32 | 33 | return system 34 | -------------------------------------------------------------------------------- /src/uv/tcp.lua: -------------------------------------------------------------------------------- 1 | require 'uv/ctypes/init' 2 | local class = require 'uv/util/class' 3 | local ffi = require 'ffi' 4 | local libuv = require 'uv/libuv' 5 | local libuv2 = require 'uv/libuv2' 6 | local uv_tcp_t = require 'uv/ctypes/uv_tcp_t' 7 | local join = require 'uv/util/join' 8 | 9 | local tcp = {} 10 | 11 | function tcp.listen(host, port, callback) 12 | local server = uv_tcp_t() 13 | server:bind(host, port) 14 | server:listen() 15 | return server 16 | end 17 | 18 | function tcp.connect(host, port, callback) 19 | local client = uv_tcp_t() 20 | return client:connect(host, tonumber(port)) 21 | end 22 | 23 | return tcp 24 | -------------------------------------------------------------------------------- /src/uv/timer.lua: -------------------------------------------------------------------------------- 1 | require 'uv/ctypes/init' 2 | local join = require 'uv/util/join' 3 | local uv_timer_t = require 'uv/ctypes/uv_timer_t' 4 | 5 | local timer = {} 6 | 7 | function timer.set(timeout, callback) 8 | join(coroutine.create(function() 9 | local timer = uv_timer_t() 10 | timer:sleep(timeout) 11 | timer:free() 12 | callback() 13 | end)) 14 | end 15 | 16 | function timer.every(timeout, callback) 17 | join(coroutine.create(function() 18 | local timer = uv_timer_t() 19 | timer:every(timeout, callback) 20 | timer:free() 21 | end)) 22 | end 23 | 24 | function timer.sleep(timeout) 25 | local timer = uv_timer_t() 26 | timer:sleep(timeout) 27 | timer:free() 28 | end 29 | 30 | return timer 31 | -------------------------------------------------------------------------------- /src/uv/url.lua: -------------------------------------------------------------------------------- 1 | require 'uv/ctypes/init' 2 | local class = require 'uv/util/class' 3 | local ffi = require 'ffi' 4 | local libhttp_parser = require 'uv/libhttp_parser' 5 | 6 | local url = {} 7 | 8 | function url.split(s, parts) 9 | if s:sub(1, 2) == '//' then 10 | local parts = url.split('http:' .. s, parts) 11 | parts.schema = nil 12 | return parts 13 | end 14 | 15 | parts = parts or {} 16 | 17 | local struct = ffi.new('struct http_parser_url') 18 | local status = libhttp_parser.http_parser_parse_url(s, #s, 0, struct) 19 | if status ~= 0 then 20 | if s:sub(1, 1) ~= '/' then 21 | local parts = url.split('/' .. s, parts) 22 | parts.path = parts.path:sub(2) 23 | return parts 24 | end 25 | error('error parsing url') 26 | end 27 | 28 | local function segment(name, id) 29 | if bit.band(struct.field_set, bit.lshift(1, id)) > 0 then 30 | local field = struct.field_data[id] 31 | parts[name] = s:sub(field.off + 1, field.off + field.len) 32 | end 33 | end 34 | 35 | segment('schema', libhttp_parser.UF_SCHEMA) 36 | segment('host', libhttp_parser.UF_HOST) 37 | segment('port', libhttp_parser.UF_PORT) 38 | segment('path', libhttp_parser.UF_PATH) 39 | segment('query', libhttp_parser.UF_QUERY) 40 | segment('fragment', libhttp_parser.UF_FRAGMENT) 41 | segment('userinfo', libhttp_parser.UF_USERINFO) 42 | 43 | return parts 44 | end 45 | 46 | function url.join(parts) 47 | return (parts.schema and (parts.schema .. '://') or (parts.host and '//' or '')) 48 | .. (parts.userinfo and (parts.userinfo .. '@') or '') 49 | .. (parts.host or '') 50 | .. (parts.port and (':' .. parts.port) or '') 51 | .. (parts.path or '') 52 | .. (parts.query and ('?' .. parts.query) or '') 53 | .. (parts.fragment and ('#' .. parts.fragment) or '') 54 | end 55 | 56 | function url.encode(value) 57 | if type(value) == 'table' then 58 | local names = {} 59 | for name in pairs(value) do 60 | table.insert(names, name) 61 | end 62 | table.sort(names) 63 | 64 | local buffer = {} 65 | for _, name in ipairs(names) do 66 | table.insert(buffer, url.encode(name) .. '=' .. url.encode(value[name])) 67 | end 68 | return table.concat(buffer, '&') 69 | elseif type(value) == 'string' then 70 | return (value:gsub('[^%w]', function(c) 71 | return string.format('%%%02X', string.byte(c)) 72 | end)) 73 | elseif type(value) == 'nil' then 74 | return '' 75 | else 76 | return url.encode(tostring(value)) 77 | end 78 | end 79 | 80 | function url.relative(base, relative) 81 | local base = url.split(base) 82 | local relative = url.split(relative) 83 | 84 | local result = { 85 | schema = relative.schema or base.schema, 86 | userinfo = relative.userinfo or base.userinfo, 87 | host = relative.host or base.host, 88 | port = relative.port or base.port, 89 | path = relative.path or base.path, 90 | query = relative.query or base.query, 91 | fragment = relative.fragment or base.fragment, 92 | } 93 | 94 | if relative.path == '' or relative.path == nil then 95 | result.path = base.path 96 | elseif relative.path:match('^/') then 97 | result.path = relative.path 98 | else 99 | result.path = base.path:gsub('[^/]*$', relative.path, 1) 100 | end 101 | 102 | return url.join(result) 103 | end 104 | 105 | return url 106 | -------------------------------------------------------------------------------- /src/uv/util/async.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local join = require 'uv/util/join' 3 | local libuv = require 'uv/libuv' 4 | 5 | local async, threads, id = {}, {}, 0 6 | setmetatable(async, async) 7 | 8 | local red_on_black = '\27[31;40m' 9 | local green_on_black = '\27[32;40m' 10 | local yellow_on_black = '\27[33;40m' 11 | local white_on_black = '\27[37;40m' 12 | 13 | local function log(color, method, id, req, info) 14 | local source = info.short_src .. ':' .. info.currentline 15 | print(string.format('%s%-8s %-5i %-42s %-10s %s%s', color, method, id, req, info.name, source, white_on_black)) 16 | io.flush() 17 | end 18 | 19 | function async.yield(req) 20 | local thread = coroutine.running() 21 | 22 | if not thread then 23 | local retval 24 | local thread = coroutine.create(function() 25 | retval = { async.yield(req) } 26 | end) 27 | local ok = coroutine.resume(thread) 28 | libuv.uv_default_loop():run() 29 | return unpack(retval) 30 | end 31 | 32 | while threads[id] do 33 | id = bit.band(id + 1, 0xFFFFFFFFFFFF) -- lower 48 bits 34 | end 35 | -- log(yellow_on_black, 'yield', id, req, debug.getinfo(2)) 36 | req.data = ffi.cast('void*', id) 37 | threads[id] = thread 38 | return coroutine.yield() 39 | end 40 | 41 | function async.resume(req, ...) 42 | local id = tonumber(ffi.cast('int', req.data)) 43 | local thread = threads[id] 44 | -- log(green_on_black, 'resume', id, req, debug.getinfo(thread, 1)) 45 | threads[id] = nil 46 | return join(thread, ...) 47 | end 48 | 49 | function async:__index(ctype) 50 | self[ctype] = ffi.cast(ctype, self.resume) 51 | return self[ctype] 52 | end 53 | 54 | return async 55 | -------------------------------------------------------------------------------- /src/uv/util/class.lua: -------------------------------------------------------------------------------- 1 | local function class(constructor, parent) 2 | local class, mt = {}, {} 3 | class.__index = class 4 | if constructor then 5 | function mt:__call(...) 6 | return setmetatable(constructor(...), self) 7 | end 8 | else 9 | function mt:__call() 10 | return setmetatable({}, self) 11 | end 12 | end 13 | if parent then 14 | mt.__index = parent 15 | end 16 | return setmetatable(class, mt) 17 | end 18 | 19 | return class 20 | -------------------------------------------------------------------------------- /src/uv/util/ctype.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | 3 | local function ctype(name, constructor, destructor) 4 | local metatype = {} 5 | local metatable = {} 6 | metatype.__index = metatype 7 | local ctype = ffi.metatype(name, metatype) 8 | local sizeof = ffi.sizeof(ctype) 9 | assert(sizeof > 0) 10 | if constructor then 11 | function metatable:__call(...) 12 | return constructor(...) 13 | end 14 | else 15 | function metatable:__call(...) 16 | return ctype(...) 17 | end 18 | end 19 | return setmetatable(metatype, metatable) 20 | end 21 | 22 | return ctype 23 | -------------------------------------------------------------------------------- /src/uv/util/errno.lua: -------------------------------------------------------------------------------- 1 | local ffi = require 'ffi' 2 | local libuv = require 'uv/libuv' 3 | 4 | local errno_mt = {} 5 | 6 | local errno = setmetatable({}, { 7 | __call = function(self, n) 8 | return ffi.string(libuv.uv_err_name(n)) .. ': ' .. ffi.string(libuv.uv_strerror(n)) 9 | end, 10 | __index = function(self, n) 11 | self[n] = self(n) 12 | return self[n] 13 | end, 14 | }) 15 | 16 | assert(errno[-1] == 'EPERM: operation not permitted') 17 | assert(errno[-2] == 'ENOENT: no such file or directory') 18 | 19 | return errno 20 | -------------------------------------------------------------------------------- /src/uv/util/expect.lua: -------------------------------------------------------------------------------- 1 | 2 | local expect = {} 3 | 4 | local tosource = function(value) 5 | if type(value) == 'string' then 6 | return string.format('%q', value) 7 | else 8 | return tostring(value) 9 | end 10 | end 11 | 12 | function expect.equal(a, b) 13 | if a ~= b then 14 | local err = string.format('values should be equal:\n- %s\n- %s', tosource(a), tosource(b)) 15 | -- print(err) 16 | error(err, 2) 17 | end 18 | end 19 | 20 | function expect.error(expected, callback) 21 | local ok, actual = pcall(callback) 22 | if ok then 23 | local err = string.format('expected an error but got none:\n- %q', expected) 24 | -- print(err) 25 | error(err, 2) 26 | end 27 | if not actual:find(expected, 1, true) then 28 | local err = string.format('expected a different error:\n- (expect): %q\n- (actual): %q', expected, actual) 29 | -- print(err) 30 | error(err, 2) 31 | end 32 | end 33 | 34 | function expect.ok(callback) 35 | local ok, actual = pcall(callback) 36 | if not ok then 37 | local err = string.format('expected no errors but got one:\n- %q', actual) 38 | -- print(err) 39 | error(err, 2) 40 | end 41 | end 42 | 43 | do 44 | expect.error('values should be equal', function() 45 | expect.equal('asdf', 'zxcv') 46 | end) 47 | 48 | expect.error('expected an error but got none', function() 49 | expect.error('something', function() 50 | end) 51 | end) 52 | 53 | expect.error('expected a different error', function() 54 | expect.error('something', function() 55 | error('anything else') 56 | end) 57 | end) 58 | 59 | expect.ok(function() 60 | expect.ok(function() 61 | end) 62 | end) 63 | 64 | expect.error('expected no errors but got one', function() 65 | expect.ok(function() 66 | error('something') 67 | end) 68 | end) 69 | end 70 | 71 | return expect 72 | -------------------------------------------------------------------------------- /src/uv/util/join.lua: -------------------------------------------------------------------------------- 1 | local function join(thread, ...) 2 | local ok, err = coroutine.resume(thread, ...) 3 | if not ok then 4 | return error(debug.traceback(thread, err), 0) 5 | end 6 | end 7 | 8 | return join 9 | -------------------------------------------------------------------------------- /src/uv/util/strict.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- strict.lua 3 | -- checks uses of undeclared global variables 4 | -- All global variables must be 'declared' through a regular assignment 5 | -- (even assigning nil will do) in a main chunk before being used 6 | -- anywhere or assigned to inside a function. 7 | -- 8 | 9 | local mt = getmetatable(_G) 10 | if mt == nil then 11 | mt = {} 12 | setmetatable(_G, mt) 13 | end 14 | 15 | __STRICT = true 16 | mt.__declared = {} 17 | 18 | mt.__newindex = function (t, n, v) 19 | if __STRICT and not mt.__declared[n] then 20 | local w = debug.getinfo(2, "S").what 21 | if w ~= "main" and w ~= "C" then 22 | error("assign to undeclared variable '"..n.."'", 2) 23 | end 24 | mt.__declared[n] = true 25 | end 26 | rawset(t, n, v) 27 | end 28 | 29 | mt.__index = function (t, n) 30 | if not mt.__declared[n] and debug.getinfo(2, "S").what ~= "C" then 31 | error("variable '"..n.."' is not declared", 2) 32 | end 33 | return rawget(t, n) 34 | end 35 | 36 | function global(...) 37 | for _, v in ipairs{...} do mt.__declared[v] = true end 38 | end 39 | -------------------------------------------------------------------------------- /src/uv/util/verify.lua: -------------------------------------------------------------------------------- 1 | local errno = require 'uv/util/errno' 2 | 3 | local function verify(status) 4 | if tonumber(status) < 0 then 5 | error(errno[status], 2) 6 | end 7 | return tonumber(status) 8 | end 9 | 10 | return verify 11 | -------------------------------------------------------------------------------- /test/fs_test.lua: -------------------------------------------------------------------------------- 1 | require 'uv/util/strict' 2 | local loop = require 'uv.loop' 3 | local fs = require 'uv.fs' 4 | local libc = require 'uv/libc' 5 | local expect = require 'uv/util/expect' 6 | 7 | local ffi = require 'ffi' 8 | local uid = libc.getuid() 9 | local gid = libc.getgid() 10 | 11 | do 12 | fs.with_tempdir(function(dir) 13 | expect.equal(fs.cwd():find(dir, 1, true), nil) 14 | fs.chdir(dir) 15 | assert(fs.cwd():find(dir, 1, true)) 16 | 17 | -- writing 18 | local file = fs.open('file.txt', 'w', '777') 19 | file:write('hello!') 20 | file:close() 21 | 22 | -- reading 23 | local file = fs.open('file.txt') 24 | expect.equal(file:read(), 'hello!') 25 | file:sync() 26 | file:close() 27 | 28 | fs.writefile('file2.txt', 'hello!') 29 | expect.equal(fs.readfile('file2.txt'), 'hello!') 30 | fs.unlink('file2.txt') 31 | 32 | -- hard links 33 | fs.link('file.txt', 'link.txt') 34 | local file = fs.open('link.txt') 35 | expect.equal(file:read(), 'hello!') 36 | file:close() 37 | fs.unlink('link.txt') 38 | 39 | -- symlinks 40 | fs.symlink('file.txt', 'symlink.txt') 41 | expect.equal(fs.readlink('symlink.txt'), 'file.txt') 42 | fs.unlink('symlink.txt') 43 | 44 | local stat = fs.stat('file.txt') 45 | expect.equal(stat.uid, uid) 46 | -- This doesn't work! stat.gid is returning 0 for some reason. 47 | -- expect.equal(stat.gid, gid) 48 | expect.equal(stat.mode, 511) -- octal('777') 49 | expect.equal(stat.size, 6) 50 | expect.equal(stat.is_dir, false) 51 | expect.equal(stat.is_fifo, false) 52 | assert(math.abs(os.time() - tonumber(stat.atime)) < 10) 53 | 54 | -- renaming 55 | fs.rename('file.txt', 'new-file.txt') 56 | local file = fs.open('new-file.txt') 57 | expect.equal(file:read(), 'hello!') 58 | file:close() 59 | fs.unlink('new-file.txt') 60 | 61 | fs.open('a', 'w'):close() 62 | fs.open('b', 'w'):close() 63 | fs.mkdir('c') 64 | fs.open('c/d', 'w'):close() 65 | fs.open('c/e', 'w'):close() 66 | fs.mkdir('c/f') 67 | fs.open('c/f/g', 'w'):close() 68 | fs.open('c/f/h', 'w'):close() 69 | 70 | local filenames = fs.readdir('.') 71 | expect.equal(#filenames, 3) 72 | expect.equal(filenames[1], 'a') 73 | expect.equal(filenames[2], 'b') 74 | expect.equal(filenames[3], 'c') 75 | 76 | local filenames = fs.readdir_r('.') 77 | expect.equal(#filenames, 6) 78 | expect.equal(filenames[1], 'a') 79 | expect.equal(filenames[2], 'b') 80 | expect.equal(filenames[3], 'c/d') 81 | expect.equal(filenames[4], 'c/e') 82 | expect.equal(filenames[5], 'c/f/g') 83 | expect.equal(filenames[6], 'c/f/h') 84 | 85 | local filenames = fs.readdir_r('c') 86 | expect.equal(#filenames, 4) 87 | expect.equal(filenames[1], 'd') 88 | expect.equal(filenames[2], 'e') 89 | expect.equal(filenames[3], 'f/g') 90 | expect.equal(filenames[4], 'f/h') 91 | 92 | -- errors 93 | local ok, err = pcall(function() 94 | fs.open('nonexistent') 95 | end) 96 | assert(not ok) 97 | assert(err:find('ENOENT: no such file or directory'), err) 98 | 99 | local ok, err = pcall(function() 100 | fs.chdir('nonexistent') 101 | end) 102 | assert(not ok) 103 | assert(err:find('ENOENT: no such file or directory'), err) 104 | end) 105 | end 106 | -------------------------------------------------------------------------------- /test/http_test.lua: -------------------------------------------------------------------------------- 1 | require 'uv/util/strict' 2 | local loop = require 'uv.loop' 3 | local http = require 'uv.http' 4 | local join = require 'uv/util/join' 5 | local expect = require 'uv/util/expect' 6 | 7 | -------------------------------------------------------------------------------- 8 | -- basic server 9 | -------------------------------------------------------------------------------- 10 | 11 | loop.run(function() 12 | local server = http.listen('127.0.0.1', 7000, function(request) 13 | expect.equal(request.ip, '127.0.0.1') 14 | expect.equal(request.method, 'GET') 15 | expect.equal(request.path, '/path/to/route') 16 | expect.equal(request.query, 'a=1&b=2') 17 | expect.equal(request.headers['User-Agent'], 'test') 18 | 19 | return { status = 200, headers = { Expires = '-1' }, body = 'hello world' } 20 | end) 21 | 22 | local response = http.request { 23 | url = 'http://127.0.0.1:7000/path/to/route?a=1&b=2', 24 | headers = { ['User-Agent'] = 'test' }, 25 | } 26 | 27 | expect.equal(response.status, 200) 28 | expect.equal(response.headers['Expires'], '-1') 29 | expect.equal(response.headers['Content-Length'], '11') 30 | expect.equal(response.body, 'hello world') 31 | 32 | local response = http.request{ 33 | host = '127.0.0.1', port = 7000, path = '/path/to/route', query = 'a=1&b=2', 34 | headers = { ['User-Agent'] = 'test' }, 35 | } 36 | 37 | expect.equal(response.status, 200) 38 | expect.equal(response.headers['Expires'], '-1') 39 | expect.equal(response.headers['Content-Length'], '11') 40 | expect.equal(response.body, 'hello world') 41 | 42 | server:close() 43 | end) 44 | 45 | loop.run(function() 46 | local server = http.listen('127.0.0.1', 7000, function(request) 47 | expect.equal(request.method, 'POST') 48 | return { status = 200 } 49 | end) 50 | 51 | for i = 1, 10 do 52 | local response = http.request { 53 | url = 'http://127.0.0.1:7000/?a=1&b=2', 54 | method = 'post', body = 'b=3&c=4', 55 | } 56 | collectgarbage() 57 | end 58 | collectgarbage() 59 | 60 | server:close() 61 | end) 62 | 63 | -- loop.run(function() 64 | -- local response = http.request{ 65 | -- url = 'http://pygments.appspot.com/', 66 | -- method = 'post', body = 'lang=lua&code=print', 67 | -- } 68 | -- end) 69 | 70 | -------------------------------------------------------------------------------- 71 | -- manually invoked event loop 72 | -------------------------------------------------------------------------------- 73 | 74 | do 75 | local server, response 76 | 77 | join(coroutine.create(function() 78 | server = http.listen('127.0.0.1', 7000, function(request) 79 | return { status = 200, body = 'ok' } 80 | end) 81 | end)) 82 | 83 | join(coroutine.create(function() 84 | response = http.request { url = 'http://127.0.0.1:7000/' } 85 | server:close() 86 | end)) 87 | 88 | loop.run() 89 | 90 | expect.equal(response.body, 'ok') 91 | end 92 | 93 | -------------------------------------------------------------------------------- 94 | -- dates 95 | -------------------------------------------------------------------------------- 96 | 97 | do 98 | expect.equal(http.format_date(1408986974LL), 'Mon, 25 Aug 2014 17:16:14 GMT') 99 | -- expect.equal(http.parse_date('Mon, 25 Aug 2014 17:16:14 GMT'), 1408986974LL) 100 | 101 | expect.equal(http.format_date(784111777), 'Sun, 06 Nov 1994 08:49:37 GMT') 102 | expect.equal(http.parse_date('Sun, 06 Nov 1994 08:49:37 GMT'), 784111777) 103 | expect.equal(http.parse_date('Sunday, 06-Nov-94 08:49:37 GMT'), 784111777) 104 | expect.equal(http.parse_date('Sun Nov 6 08:49:37 1994'), 784111777) 105 | expect.equal(http.parse_date('asdf'), nil) 106 | end 107 | -------------------------------------------------------------------------------- /test/loop_test.lua: -------------------------------------------------------------------------------- 1 | require 'uv/util/strict' 2 | local loop = require 'uv.loop' 3 | local timer = require 'uv.timer' 4 | local expect = require 'uv/util/expect' 5 | 6 | for i = 1, 1000 do 7 | loop.run(function() 8 | end) 9 | end 10 | 11 | assert(loop.alive() == false) 12 | loop.run(function() 13 | assert(loop.alive() == false) 14 | timer.set(0, function() 15 | assert(loop.alive() == false) 16 | end) 17 | assert(loop.alive() == true) 18 | end) 19 | assert(loop.alive() == false) 20 | 21 | do 22 | local buffer = {} 23 | table.insert(buffer, '1') 24 | loop.run(function() 25 | table.insert(buffer, '2') 26 | timer.set(0, function() 27 | table.insert(buffer, '4') 28 | end) 29 | table.insert(buffer, '3') 30 | end) 31 | table.insert(buffer, '5') 32 | expect.equal(table.concat(buffer), '12345') 33 | end 34 | 35 | loop.run(function() 36 | local buffer = {} 37 | loop.yield(function() 38 | table.insert(buffer, 'yield') 39 | end) 40 | loop.resume(function() 41 | table.insert(buffer, 'resume') 42 | end) 43 | table.insert(buffer, 'before') 44 | timer.sleep(1) 45 | table.insert(buffer, 'after') 46 | expect.equal(table.concat(buffer, ' '), 'before yield resume after') 47 | loop.stop() 48 | end) 49 | 50 | loop.run(function() 51 | local buffer = {} 52 | local count = 0 53 | loop.idle(function() 54 | count = count + 1 55 | if not buffer[2] then 56 | buffer[2] = 'idle' 57 | end 58 | end) 59 | table.insert(buffer, 'before') 60 | timer.sleep(2) 61 | table.insert(buffer, 'after') 62 | expect.equal(table.concat(buffer, ' '), 'before idle after') 63 | assert(count > 0) 64 | loop.stop() 65 | end) 66 | -------------------------------------------------------------------------------- /test/parallel_test.lua: -------------------------------------------------------------------------------- 1 | require 'uv/util/strict' 2 | local loop = require 'uv.loop' 3 | local parallel = require 'uv.parallel' 4 | local timer = require 'uv.timer' 5 | 6 | do 7 | local log = {} 8 | loop.run(function() 9 | parallel.range(3, function(i) 10 | table.insert(log, i) 11 | timer.sleep(1) 12 | table.insert(log, i) 13 | end) 14 | end) 15 | assert(table.concat(log) == '123123') 16 | end 17 | 18 | do 19 | local log = {} 20 | parallel.range(3, function(i) 21 | table.insert(log, i) 22 | timer.sleep(1) 23 | table.insert(log, i) 24 | end) 25 | assert(table.concat(log) == '123123') 26 | end 27 | 28 | do 29 | local inputs = { 'a', 'b', 'c' } 30 | local log = {} 31 | local outputs = {} 32 | 33 | loop.run(function() 34 | outputs = parallel.map(inputs, function(input) 35 | table.insert(log, input) 36 | timer.sleep(1) 37 | local output = string.upper(input) 38 | table.insert(log, output) 39 | return output 40 | end) 41 | assert(table.concat(outputs) == 'ABC') 42 | end) 43 | 44 | assert(table.concat(log) == 'abcABC') 45 | end 46 | 47 | do 48 | local inputs = { 'a', 'b', 'c' } 49 | local log = {} 50 | local outputs = {} 51 | 52 | outputs = parallel.map(inputs, function(input) 53 | table.insert(log, input) 54 | timer.sleep(1) 55 | local output = string.upper(input) 56 | table.insert(log, output) 57 | return output 58 | end) 59 | 60 | assert(table.concat(outputs) == 'ABC') 61 | assert(table.concat(log) == 'abcABC') 62 | end 63 | -------------------------------------------------------------------------------- /test/process_test.lua: -------------------------------------------------------------------------------- 1 | require 'uv/util/strict' 2 | local loop = require 'uv.loop' 3 | local process = require 'uv.process' 4 | local timer = require 'uv.timer' 5 | local expect = require 'uv/util/expect' 6 | 7 | do 8 | local signal = process.spawn { './luajit', '-v' } 9 | expect.equal(signal, 0) 10 | 11 | local signal = process.spawn { '/bin/echo', 'Hello', 'world' } 12 | expect.equal(signal, 0) 13 | end 14 | 15 | loop.run(function() 16 | local count = 0 17 | process.on('hup', function() 18 | count = count + 1 19 | end) 20 | process.kill(process.pid(), 'hup') 21 | process.kill(process.pid(), 'hup') 22 | process.kill(process.pid(), 'hup') 23 | timer.sleep(1) 24 | expect.equal(count, 3) 25 | loop.stop() 26 | end) 27 | 28 | do 29 | assert(process.path():find('luajit')) 30 | end 31 | -------------------------------------------------------------------------------- /test/system_test.lua: -------------------------------------------------------------------------------- 1 | require 'uv/util/strict' 2 | local system = require 'uv.system' 3 | 4 | do 5 | local free, total = system.free_memory(), system.total_memory() 6 | assert(free > 0) 7 | assert(total > 0) 8 | assert(free <= total) 9 | end 10 | 11 | do 12 | local hrtime = system.hrtime() 13 | assert(hrtime > 0) 14 | end 15 | 16 | do 17 | local x, y, z = system.loadavg() 18 | assert(type(x) == 'number') 19 | assert(type(y) == 'number') 20 | assert(type(z) == 'number') 21 | end 22 | 23 | do 24 | local uptime = system.uptime() 25 | assert(uptime > 0) 26 | end 27 | -------------------------------------------------------------------------------- /test/tcp_test.lua: -------------------------------------------------------------------------------- 1 | require 'uv/util/strict' 2 | local loop = require 'uv.loop' 3 | local tcp = require 'uv.tcp' 4 | local join = require 'uv/util/join' 5 | local expect = require 'uv/util/expect' 6 | 7 | loop.run(function() 8 | local server = tcp.listen('127.0.0.1', 7000) 9 | 10 | join(coroutine.create(function() 11 | while true do 12 | local socket = server:accept() 13 | while true do 14 | local data = socket:read() 15 | if data:find('quit') then 16 | break 17 | end 18 | socket:write(data:upper()) 19 | end 20 | socket:close() 21 | end 22 | end)) 23 | 24 | local socket = tcp.connect('127.0.0.1', 7000) 25 | socket:write('ping') 26 | expect.equal(socket:read(), 'PING') 27 | socket:write('quit') 28 | socket:close() 29 | 30 | server:close() 31 | end) 32 | 33 | -- loop.run(function() 34 | -- local server = uv_tcp_t() 35 | -- server:bind('127.0.0.1', 7000) 36 | -- server:listen(function(stream) 37 | -- expect.equal(stream:read(), 'foo') 38 | -- stream:write('bar') 39 | -- end) 40 | -- 41 | -- local client = uv_tcp_t() 42 | -- local stream = client:connect('127.0.0.1', 7000) 43 | -- stream:write('foo') 44 | -- expect.equal(stream:read(), 'bar') 45 | -- stream:close() 46 | -- 47 | -- server:close() 48 | -- end) 49 | -- 50 | -- loop.run(function() 51 | -- local getaddrinfo = uv_getaddrinfo_t() 52 | -- 53 | -- local addrs = getaddrinfo:getaddrinfo('123.123.123.123', 'https') 54 | -- assert(#addrs > 0) 55 | -- for _, addr in ipairs(addrs) do 56 | -- expect.equal(addr:ip(), '123.123.123.123') 57 | -- expect.equal(addr:port(), 443) 58 | -- end 59 | -- 60 | -- -- local addrs = getaddrinfo:getaddrinfo('google.com', 'http') 61 | -- -- for _, addr in ipairs(addrs) do 62 | -- -- assert(addr:ip():match('^74%.')) 63 | -- -- assert(addr:port() == 80) 64 | -- -- end 65 | -- end) 66 | -------------------------------------------------------------------------------- /test/timer_test.lua: -------------------------------------------------------------------------------- 1 | require 'uv/util/strict' 2 | local loop = require 'uv.loop' 3 | local timer = require 'uv.timer' 4 | 5 | do 6 | local s = '' 7 | timer.set(5, function() s = s .. 'e' end) 8 | timer.set(3, function() s = s .. 'c' end) 9 | timer.set(1, function() s = s .. 'a' end) 10 | timer.set(2, function() s = s .. 'b' end) 11 | timer.set(4, function() s = s .. 'd' end) 12 | timer.set(6, function() s = s .. 'f' end) 13 | loop.run() 14 | assert(s == 'abcdef') 15 | end 16 | 17 | local s = '' 18 | loop.run(function() 19 | timer.set(5, function() s = s .. 'e' end) 20 | timer.set(3, function() s = s .. 'c' end) 21 | timer.set(1, function() s = s .. 'a' end) 22 | timer.set(2, function() s = s .. 'b' end) 23 | timer.set(4, function() s = s .. 'd' end) 24 | timer.set(6, function() s = s .. 'f' end) 25 | end) 26 | assert(s == 'abcdef') 27 | 28 | do 29 | local s = '' 30 | timer.every(1, function(self) 31 | s = s .. 'a' 32 | if #s >= 5 then self:stop() end 33 | end) 34 | loop.run() 35 | assert(s == 'aaaaa') 36 | end 37 | 38 | local s = '' 39 | loop.run(function() 40 | timer.every(1, function(self) 41 | s = s .. 'a' 42 | if #s >= 5 then self:stop() end 43 | end) 44 | end) 45 | assert(s == 'aaaaa') 46 | -------------------------------------------------------------------------------- /test/url_test.lua: -------------------------------------------------------------------------------- 1 | require 'uv/util/strict' 2 | local expect = require 'uv/util/expect' 3 | local url = require 'uv.url' 4 | 5 | do 6 | local function test(input, schema, userinfo, host, port, path, query, fragment) 7 | local parts = url.split(input) 8 | expect.equal(parts.schema, schema) 9 | expect.equal(parts.userinfo, userinfo) 10 | expect.equal(parts.host, host) 11 | expect.equal(parts.port, port) 12 | expect.equal(parts.path, path) 13 | expect.equal(parts.query, query) 14 | expect.equal(parts.fragment, fragment) 15 | expect.equal(url.join(parts), input) 16 | end 17 | 18 | test('http://user:pass@host:1234/path?a=1#section', 'http', 'user:pass', 'host', '1234', '/path', 'a=1', 'section') 19 | test( '//user:pass@host:1234/path?a=1#section', nil, 'user:pass', 'host', '1234', '/path', 'a=1', 'section') 20 | test('http://' .. 'host:1234/path?a=1#section', 'http', nil, 'host', '1234', '/path', 'a=1', 'section') 21 | test('http://user:pass@host'.. '/path?a=1#section', 'http', 'user:pass', 'host', nil, '/path', 'a=1', 'section') 22 | test( '/path?a=1#section', nil, nil, nil, nil, '/path', 'a=1', 'section') 23 | test( 'path?a=1#section', nil, nil, nil, nil, 'path', 'a=1', 'section') 24 | test('http://user:pass@host:1234'.. '?a=1#section', 'http', 'user:pass', 'host', '1234', nil, 'a=1', 'section') 25 | test('http://user:pass@host:1234/path'..'#section', 'http', 'user:pass', 'host', '1234', '/path', nil, 'section') 26 | test('http://user:pass@host:1234/path?a=1', 'http', 'user:pass', 'host', '1234', '/path', 'a=1', nil) 27 | test('http://user:pass@host:1234', 'http', 'user:pass', 'host', '1234', nil, nil, nil) 28 | end 29 | 30 | do 31 | local function test(input, expected) 32 | local actual = url.encode(input) 33 | expect.equal(actual, expected) 34 | end 35 | 36 | test('asdf', 'asdf') 37 | test(' ', '%20') 38 | test(1, '1') 39 | test(nil, '') 40 | test(false, 'false') 41 | test({}, '') 42 | test({ a = 1 }, 'a=1') 43 | test({ ['a b'] = 'c d' }, 'a%20b=c%20d') 44 | end 45 | 46 | do 47 | local function test(base, relative, expected) 48 | local actual = url.relative(base, relative) 49 | expect.equal(actual, expected) 50 | end 51 | 52 | test('/a/b/c', '/d/e/f', '/d/e/f') 53 | test('/a/b/c', 'd/e/f', '/a/b/d/e/f') 54 | test('/a/b/', 'd/e/f', '/a/b/d/e/f') 55 | test('/a/b/c', '', '/a/b/c') 56 | test('/a/b/c?x=1', '', '/a/b/c?x=1') 57 | test('/a/b/c', '?y=2', '/a/b/c?y=2') 58 | test('/a/b/c?x=1', '?y=2', '/a/b/c?y=2') 59 | end 60 | --------------------------------------------------------------------------------