├── docs ├── copas.png ├── license.html └── doc.css ├── .luacov ├── tests ├── certs │ ├── _readme.md │ ├── all.sh │ ├── rootA.sh │ ├── rootB.sh │ ├── rootA.bat │ ├── rootB.bat │ ├── all.bat │ ├── serverA.bat │ ├── serverB.bat │ ├── clientA.bat │ ├── clientB.bat │ ├── serverA.sh │ ├── serverB.sh │ ├── clientA.sh │ ├── clientB.sh │ ├── rootA.cnf │ ├── rootB.cnf │ ├── serverA.cnf │ ├── serverB.cnf │ └── clientA.cnf ├── loop_starter.lua ├── pause.lua ├── connecttwice.lua ├── no_luasocket.lua ├── timeout_errors.lua ├── removeserver.lua ├── exit.lua ├── removethread.lua ├── request.lua ├── close.lua ├── starve.lua ├── udptimeout.lua ├── tls-sni.lua ├── lock.lua ├── httpredirect.lua ├── exittest.lua ├── largetransfer.lua ├── timer.lua ├── errhandlers.lua ├── semaphore.lua ├── tcptimeout.lua └── queue.lua ├── .gitignore ├── .editorconfig ├── .luacheckrc ├── Makefile.win ├── misc ├── echoserver.lua ├── testasyncspeed.lua └── cosocket.lua ├── .github └── workflows │ ├── luacheck.yml │ └── unix_build.yml ├── rockspec ├── copas-1.1.4-1.rockspec ├── copas-1.1.5-1.rockspec ├── copas-1.1.6-1.rockspec ├── copas-1.1.2-1.rockspec ├── copas-1.2.0-1.rockspec ├── copas-1.2.1-1.rockspec ├── copas-1.1.3-1.rockspec ├── copas-2.0.0-1.rockspec ├── copas-2.0.0-2.rockspec ├── copas-2.0.1-1.rockspec ├── copas-2.0.2-1.rockspec ├── copas-3.0.0-1.rockspec ├── copas-3.0.0-2.rockspec ├── copas-3.0.0-3.rockspec ├── copas-4.0.0-1.rockspec ├── copas-4.1.0-1.rockspec ├── copas-4.2.0-1.rockspec ├── copas-4.3.0-1.rockspec ├── copas-4.3.1-1.rockspec ├── copas-4.3.2-1.rockspec ├── copas-4.4.0-1.rockspec ├── copas-4.5.0-1.rockspec ├── copas-4.6.0-1.rockspec ├── copas-4.7.0-1.rockspec ├── copas-4.7.1-1.rockspec └── copas-4.8.0-1.rockspec ├── src └── copas │ ├── smtp.lua │ ├── ftp.lua │ ├── timer.lua │ ├── lock.lua │ ├── semaphore.lua │ └── queue.lua ├── LICENSE ├── copas-cvs-6.rockspec ├── README.md ├── bin └── copas.lua └── Makefile /docs/copas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunarmodules/copas/HEAD/docs/copas.png -------------------------------------------------------------------------------- /.luacov: -------------------------------------------------------------------------------- 1 | modules = { 2 | ["copas"] = "src/copas.lua", 3 | ["copas.*"] = "src" 4 | } 5 | -------------------------------------------------------------------------------- /tests/certs/_readme.md: -------------------------------------------------------------------------------- 1 | The certificate generation scripts here are copied from LuaSec 2 | 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | **/*.srl 3 | **/*.pem 4 | *.rock 5 | luacov.report.out 6 | luacov.stats.out 7 | -------------------------------------------------------------------------------- /tests/certs/all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CWD=$(PWD) 4 | cd $( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 5 | 6 | ./rootA.sh 7 | ./rootB.sh 8 | ./serverA.sh 9 | ./serverB.sh 10 | ./clientA.sh 11 | ./clientB.sh 12 | 13 | cd $CWD 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | 9 | [*.lua] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [Makefile] 14 | indent_style = tab 15 | -------------------------------------------------------------------------------- /tests/certs/rootA.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | openssl req -newkey rsa:2048 -sha256 -keyout rootAkey.pem -out rootAreq.pem -nodes -config ./rootA.cnf -days 365 -batch 3 | 4 | openssl x509 -req -in rootAreq.pem -sha256 -extfile ./rootA.cnf -extensions v3_ca -signkey rootAkey.pem -out rootA.pem -days 365 5 | 6 | openssl x509 -subject -issuer -noout -in rootA.pem 7 | -------------------------------------------------------------------------------- /tests/certs/rootB.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | openssl req -newkey rsa:2048 -sha256 -keyout rootBkey.pem -out rootBreq.pem -nodes -config ./rootB.cnf -days 365 -batch 4 | 5 | openssl x509 -req -in rootBreq.pem -sha256 -extfile ./rootB.cnf -extensions v3_ca -signkey rootBkey.pem -out rootB.pem -days 365 6 | 7 | openssl x509 -subject -issuer -noout -in rootB.pem 8 | -------------------------------------------------------------------------------- /tests/certs/rootA.bat: -------------------------------------------------------------------------------- 1 | REM #!/bin/sh 2 | 3 | openssl req -newkey rsa:2048 -sha256 -keyout rootAkey.pem -out rootAreq.pem -nodes -config ./rootA.cnf -days 365 -batch 4 | 5 | openssl x509 -req -in rootAreq.pem -sha256 -extfile ./rootA.cnf -extensions v3_ca -signkey rootAkey.pem -out rootA.pem -days 365 6 | 7 | openssl x509 -subject -issuer -noout -in rootA.pem 8 | -------------------------------------------------------------------------------- /tests/certs/rootB.bat: -------------------------------------------------------------------------------- 1 | rem #!/bin/sh 2 | 3 | openssl req -newkey rsa:2048 -sha256 -keyout rootBkey.pem -out rootBreq.pem -nodes -config ./rootB.cnf -days 365 -batch 4 | 5 | openssl x509 -req -in rootBreq.pem -sha256 -extfile ./rootB.cnf -extensions v3_ca -signkey rootBkey.pem -out rootB.pem -days 365 6 | 7 | openssl x509 -subject -issuer -noout -in rootB.pem 8 | -------------------------------------------------------------------------------- /tests/loop_starter.lua: -------------------------------------------------------------------------------- 1 | -- make sure we are pointing to the local copas first 2 | package.path = string.format("../src/?.lua;%s", package.path) 3 | local copas = require "copas" 4 | 5 | local x 6 | 7 | copas.loop(function() 8 | -- Copas initialization function 9 | x = true 10 | end) 11 | 12 | assert(x, "expected 'x' to be truthy") 13 | print "test success!" 14 | -------------------------------------------------------------------------------- /tests/certs/all.bat: -------------------------------------------------------------------------------- 1 | REM make sure the 'openssl.exe' commandline tool is in your path before starting! 2 | REM set the path below; 3 | set opensslpath=c:\program files (x86)\openssl-win32\bin 4 | 5 | 6 | 7 | setlocal 8 | set path=%opensslpath%;%path% 9 | call roota.bat 10 | call rootb.bat 11 | call servera.bat 12 | call serverb.bat 13 | call clienta.bat 14 | call clientb.bat 15 | -------------------------------------------------------------------------------- /tests/certs/serverA.bat: -------------------------------------------------------------------------------- 1 | rem #!/bin/sh 2 | 3 | openssl req -newkey rsa:2048 -keyout serverAkey.pem -out serverAreq.pem -config ./serverA.cnf -nodes -days 365 -batch 4 | 5 | openssl x509 -req -in serverAreq.pem -sha256 -extfile ./serverA.cnf -extensions usr_cert -CA rootA.pem -CAkey rootAkey.pem -CAcreateserial -out serverAcert.pem -days 365 6 | 7 | copy serverAcert.pem + rootA.pem serverA.pem 8 | 9 | openssl x509 -subject -issuer -noout -in serverA.pem 10 | -------------------------------------------------------------------------------- /tests/certs/serverB.bat: -------------------------------------------------------------------------------- 1 | rem #!/bin/sh 2 | 3 | openssl req -newkey rsa:2048 -keyout serverBkey.pem -out serverBreq.pem -config ./serverB.cnf -nodes -days 365 -batch 4 | 5 | openssl x509 -req -in serverBreq.pem -sha256 -extfile ./serverB.cnf -extensions usr_cert -CA rootB.pem -CAkey rootBkey.pem -CAcreateserial -out serverBcert.pem -days 365 6 | 7 | copy serverBcert.pem + rootB.pem serverB.pem 8 | 9 | openssl x509 -subject -issuer -noout -in serverB.pem 10 | -------------------------------------------------------------------------------- /tests/certs/clientA.bat: -------------------------------------------------------------------------------- 1 | rem #!/bin/sh 2 | 3 | openssl req -newkey rsa:2048 -sha256 -keyout clientAkey.pem -out clientAreq.pem -nodes -config ./clientA.cnf -days 365 -batch 4 | 5 | openssl x509 -req -in clientAreq.pem -sha256 -extfile ./clientA.cnf -extensions usr_cert -CA rootA.pem -CAkey rootAkey.pem -CAcreateserial -out clientAcert.pem -days 365 6 | 7 | copy clientAcert.pem + rootA.pem clientA.pem 8 | 9 | openssl x509 -subject -issuer -noout -in clientA.pem 10 | -------------------------------------------------------------------------------- /tests/certs/clientB.bat: -------------------------------------------------------------------------------- 1 | rem #!/bin/sh 2 | 3 | openssl req -newkey rsa:2048 -sha256 -keyout clientBkey.pem -out clientBreq.pem -nodes -config ./clientB.cnf -days 365 -batch 4 | 5 | openssl x509 -req -in clientBreq.pem -sha256 -extfile ./clientB.cnf -extensions usr_cert -CA rootB.pem -CAkey rootBkey.pem -CAcreateserial -out clientBcert.pem -days 365 6 | 7 | copy clientBcert.pem + rootB.pem clientB.pem 8 | 9 | openssl x509 -subject -issuer -noout -in clientB.pem 10 | -------------------------------------------------------------------------------- /tests/certs/serverA.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | openssl req -newkey rsa:2048 -keyout serverAkey.pem -out serverAreq.pem \ 4 | -config ./serverA.cnf -nodes -days 365 -batch 5 | 6 | openssl x509 -req -in serverAreq.pem -sha256 -extfile ./serverA.cnf \ 7 | -extensions usr_cert -CA rootA.pem -CAkey rootAkey.pem -CAcreateserial \ 8 | -out serverAcert.pem -days 365 9 | 10 | cat serverAcert.pem rootA.pem > serverA.pem 11 | 12 | openssl x509 -subject -issuer -noout -in serverA.pem 13 | -------------------------------------------------------------------------------- /tests/certs/serverB.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | openssl req -newkey rsa:2048 -keyout serverBkey.pem -out serverBreq.pem \ 4 | -config ./serverB.cnf -nodes -days 365 -batch 5 | 6 | openssl x509 -req -in serverBreq.pem -sha256 -extfile ./serverB.cnf \ 7 | -extensions usr_cert -CA rootB.pem -CAkey rootBkey.pem -CAcreateserial \ 8 | -out serverBcert.pem -days 365 9 | 10 | cat serverBcert.pem rootB.pem > serverB.pem 11 | 12 | openssl x509 -subject -issuer -noout -in serverB.pem 13 | -------------------------------------------------------------------------------- /tests/certs/clientA.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | openssl req -newkey rsa:2048 -sha256 -keyout clientAkey.pem -out clientAreq.pem \ 4 | -nodes -config ./clientA.cnf -days 365 -batch 5 | 6 | openssl x509 -req -in clientAreq.pem -sha256 -extfile ./clientA.cnf \ 7 | -extensions usr_cert -CA rootA.pem -CAkey rootAkey.pem -CAcreateserial \ 8 | -out clientAcert.pem -days 365 9 | 10 | cat clientAcert.pem rootA.pem > clientA.pem 11 | 12 | openssl x509 -subject -issuer -noout -in clientA.pem 13 | -------------------------------------------------------------------------------- /tests/certs/clientB.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | openssl req -newkey rsa:2048 -sha256 -keyout clientBkey.pem -out clientBreq.pem \ 4 | -nodes -config ./clientB.cnf -days 365 -batch 5 | 6 | openssl x509 -req -in clientBreq.pem -sha256 -extfile ./clientB.cnf \ 7 | -extensions usr_cert -CA rootB.pem -CAkey rootBkey.pem -CAcreateserial \ 8 | -out clientBcert.pem -days 365 9 | 10 | cat clientBcert.pem rootB.pem > clientB.pem 11 | 12 | openssl x509 -subject -issuer -noout -in clientB.pem 13 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | --std = "ngx_lua+busted" 2 | unused_args = false 3 | redefined = false 4 | max_line_length = false 5 | 6 | 7 | globals = { 8 | --"_KONG", 9 | --"kong", 10 | --"ngx.IS_CLI", 11 | } 12 | 13 | 14 | not_globals = { 15 | "string.len", 16 | "table.getn", 17 | } 18 | 19 | 20 | ignore = { 21 | --"6.", -- ignore whitespace warnings 22 | } 23 | 24 | 25 | exclude_files = { 26 | ".install/**", 27 | ".luarocks/**", 28 | --"spec/fixtures/invalid-module.lua", 29 | --"spec-old-api/fixtures/invalid-module.lua", 30 | } 31 | 32 | -------------------------------------------------------------------------------- /Makefile.win: -------------------------------------------------------------------------------- 1 | # $Id: Makefile.win,v 1.5 2008/01/16 18:07:17 mascarenhas Exp $ 2 | 3 | LUA_DIR= c:\lua5.1\lua 4 | 5 | build clean: 6 | 7 | install: 8 | mkdir "$(LUA_DIR)\copas" 9 | copy src\copas.lua "$(LUA_DIR)\copas.lua" 10 | copy src\copas\ftp.lua "$(LUA_DIR)\copas\ftp.lua" 11 | copy src\copas\http.lua "$(LUA_DIR)\copas\http.lua" 12 | copy src\copas\lock.lua "$(LUA_DIR)\copas\lock.lua" 13 | copy src\copas\queue.lua "$(LUA_DIR)\copas\queue.lua" 14 | copy src\copas\semaphore.lua "$(LUA_DIR)\copas\semaphore.lua" 15 | copy src\copas\smtp.lua "$(LUA_DIR)\copas\smtp.lua" 16 | copy src\copas\timer.lua "$(LUA_DIR)\copas\timer.lua" 17 | -------------------------------------------------------------------------------- /misc/echoserver.lua: -------------------------------------------------------------------------------- 1 | -- Tests Copas with a simple Echo server 2 | -- 3 | -- Run the test file and the connect to the server using telnet on the used port. 4 | -- The server should be able to echo any input, to stop the test just send the command "quit" 5 | 6 | local copas = require("copas") 7 | local socket = require("socket") 8 | 9 | local function echoHandler(skt) 10 | skt = copas.wrap(skt) 11 | while true do 12 | local data = skt:receive() 13 | if not data or data == "quit" then 14 | break 15 | end 16 | skt:send(data) 17 | end 18 | end 19 | 20 | local server = socket.bind("localhost", 20000) 21 | 22 | copas.addserver(server, echoHandler) 23 | 24 | copas.loop() 25 | -------------------------------------------------------------------------------- /tests/pause.lua: -------------------------------------------------------------------------------- 1 | -- check no memory leaks when sleeping 2 | 3 | -- make sure we are pointing to the local copas first 4 | package.path = string.format("../src/?.lua;%s", package.path) 5 | 6 | local copas = require("copas") 7 | 8 | local t1 = copas.addthread( 9 | function() 10 | copas.pauseforever() -- sleep until woken up 11 | end 12 | ) 13 | 14 | 15 | -- prepare GC test 16 | local validate_gc = setmetatable({ 17 | [t1] = true, 18 | },{ __mode = "k" }) 19 | 20 | -- start test 21 | copas.loop() 22 | 23 | t1 = nil -- luacheck: ignore 24 | collectgarbage() 25 | collectgarbage() 26 | 27 | --check GC 28 | assert(next(validate_gc) == nil, "the 'validate_gc' table should have been empty!") 29 | 30 | print "test success!" 31 | -------------------------------------------------------------------------------- /tests/connecttwice.lua: -------------------------------------------------------------------------------- 1 | -- test reconnecting a socket, should return an error "already connected" 2 | -- test based on Windows behaviour, see comments in `copas.connect()` function 3 | local copas = require("copas") 4 | local socket = require("socket") 5 | 6 | local skt = copas.wrap(socket.tcp()) 7 | local done = false 8 | 9 | copas.addthread(function() 10 | print("First try... (should succeed)") 11 | local ok, err = skt:connect("google.com", 80) 12 | if ok then 13 | print("Success") 14 | else 15 | print("Failed: "..err) 16 | os.exit(1) 17 | end 18 | 19 | print("\nSecond try... (should error as already connected)") 20 | ok, err = skt:connect("thijsschreijer.nl", 80) 21 | if ok then 22 | print("Unexpected success") 23 | os.exit(1) 24 | else 25 | print("Failed: "..err) 26 | end 27 | 28 | done = true 29 | end) 30 | 31 | copas.loop() 32 | 33 | if not done then 34 | print("Loop completed with test not finished") 35 | os.exit(1) 36 | end 37 | -------------------------------------------------------------------------------- /.github/workflows/luacheck.yml: -------------------------------------------------------------------------------- 1 | name: Luacheck 2 | 3 | concurrency: 4 | # for PR's cancel the running task, if another commit is pushed 5 | group: ${{ github.workflow }} ${{ github.ref }} 6 | cancel-in-progress: ${{ github.event_name == 'pull_request' }} 7 | 8 | on: 9 | # build on PR and push-to-master. This works for short-lived branches, and saves 10 | # CPU cycles on duplicated tests. 11 | # For long-lived branches that diverge, you'll want to run on all pushes, not 12 | # just on push-to-master. 13 | pull_request: {} 14 | push: 15 | branches: 16 | - master 17 | 18 | jobs: 19 | 20 | luacheck: 21 | runs-on: ubuntu-20.04 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | - uses: leafo/gh-actions-lua@v10 26 | - uses: leafo/gh-actions-luarocks@v4 27 | - name: Lint rockspecs 28 | run: | 29 | for i in $(find . -type f -name "*.rockspec"); do echo $i; luarocks lint $i || exit 1; done 30 | - name: Luacheck 31 | uses: lunarmodules/luacheck@v0 32 | -------------------------------------------------------------------------------- /rockspec/copas-1.1.4-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "Copas" 2 | version = "1.1.4-1" 3 | source = { 4 | url = "http://luaforge.net/frs/download.php/3896/copas-1.1.4.tar.gz", 5 | } 6 | description = { 7 | summary = "Coroutine Oriented Portable Asynchronous Services", 8 | detailed = [[ 9 | Copas is a dispatcher based on coroutines that can be used by 10 | TCP/IP servers. It uses LuaSocket as the interface with the 11 | TCP/IP stack. A server registered with Copas should provide a 12 | handler for requests and use Copas socket functions to send 13 | the response. Copas loops through requests and invokes the 14 | corresponding handlers. For a full implementation of a Copas 15 | HTTP server you can refer to Xavante as an example. 16 | ]], 17 | license = "MIT/X11", 18 | homepage = "http://www.keplerproject.org/copas/" 19 | } 20 | dependencies = { 21 | "lua >= 5.1", 22 | "luasocket >= 2.0", 23 | "coxpcall >= 1.13", 24 | } 25 | build = { 26 | type = "module", 27 | modules = { copas = "src/copas/copas.lua" } 28 | } 29 | -------------------------------------------------------------------------------- /rockspec/copas-1.1.5-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "Copas" 2 | version = "1.1.5-1" 3 | source = { 4 | url = "http://luaforge.net/frs/download.php/4027/copas-1.1.5.tar.gz", 5 | } 6 | description = { 7 | summary = "Coroutine Oriented Portable Asynchronous Services", 8 | detailed = [[ 9 | Copas is a dispatcher based on coroutines that can be used by 10 | TCP/IP servers. It uses LuaSocket as the interface with the 11 | TCP/IP stack. A server registered with Copas should provide a 12 | handler for requests and use Copas socket functions to send 13 | the response. Copas loops through requests and invokes the 14 | corresponding handlers. For a full implementation of a Copas 15 | HTTP server you can refer to Xavante as an example. 16 | ]], 17 | license = "MIT/X11", 18 | homepage = "http://www.keplerproject.org/copas/" 19 | } 20 | dependencies = { 21 | "lua >= 5.1", 22 | "luasocket >= 2.0", 23 | "coxpcall >= 1.13", 24 | } 25 | build = { 26 | type = "module", 27 | modules = { copas = "src/copas/copas.lua" } 28 | } 29 | -------------------------------------------------------------------------------- /rockspec/copas-1.1.6-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "Copas" 2 | version = "1.1.6-1" 3 | source = { 4 | url = "http://github.com/downloads/keplerproject/copas/copas-1.1.6.tar.gz", 5 | } 6 | description = { 7 | summary = "Coroutine Oriented Portable Asynchronous Services", 8 | detailed = [[ 9 | Copas is a dispatcher based on coroutines that can be used by 10 | TCP/IP servers. It uses LuaSocket as the interface with the 11 | TCP/IP stack. A server registered with Copas should provide a 12 | handler for requests and use Copas socket functions to send 13 | the response. Copas loops through requests and invokes the 14 | corresponding handlers. For a full implementation of a Copas 15 | HTTP server you can refer to Xavante as an example. 16 | ]], 17 | license = "MIT/X11", 18 | homepage = "http://www.keplerproject.org/copas/" 19 | } 20 | dependencies = { 21 | "lua >= 5.1", 22 | "luasocket >= 2.0", 23 | "coxpcall >= 1.13", 24 | } 25 | build = { 26 | type = "builtin", 27 | modules = { copas = "src/copas/copas.lua" } 28 | } 29 | -------------------------------------------------------------------------------- /rockspec/copas-1.1.2-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "Copas" 2 | version = "1.1.2-1" 3 | source = { 4 | url = "http://luaforge.net/frs/download.php/3367/copas-1.1.2.tar.gz", 5 | } 6 | description = { 7 | summary = "Coroutine Oriented Portable Asynchronous Services", 8 | detailed = [[ 9 | Copas is a dispatcher based on coroutines that can be used by 10 | TCP/IP servers. It uses LuaSocket as the interface with the 11 | TCP/IP stack. A server registered with Copas should provide a 12 | handler for requests and use Copas socket functions to send 13 | the response. Copas loops through requests and invokes the 14 | corresponding handlers. For a full implementation of a Copas 15 | HTTP server you can refer to Xavante as an example. 16 | ]], 17 | license = "MIT/X11", 18 | homepage = "http://www.keplerproject.org/copas/" 19 | } 20 | dependencies = { 21 | "lua >= 5.1", 22 | "luasocket >= 2.0" 23 | } 24 | build = { 25 | type = "make", 26 | build_pass = false, 27 | install_variables = { 28 | LUA_DIR = "$(LUADIR)" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /rockspec/copas-1.2.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "Copas" 2 | version = "1.2.0-1" 3 | source = { 4 | url = "https://github.com/keplerproject/copas/archive/v1_2_0.tar.gz", 5 | dir = "copas-1_2_0", 6 | } 7 | description = { 8 | summary = "Coroutine Oriented Portable Asynchronous Services", 9 | detailed = [[ 10 | Copas is a dispatcher based on coroutines that can be used by 11 | TCP/IP servers. It uses LuaSocket as the interface with the 12 | TCP/IP stack. A server registered with Copas should provide a 13 | handler for requests and use Copas socket functions to send 14 | the response. Copas loops through requests and invokes the 15 | corresponding handlers. For a full implementation of a Copas 16 | HTTP server you can refer to Xavante as an example. 17 | ]], 18 | license = "MIT/X11", 19 | homepage = "http://www.keplerproject.org/copas/" 20 | } 21 | dependencies = { 22 | "lua >= 5.1, < 5.3", 23 | "luasocket >= 2.1", 24 | "coxpcall >= 1.14", 25 | } 26 | build = { 27 | type = "builtin", 28 | modules = { copas = "src/copas/copas.lua" } 29 | } 30 | -------------------------------------------------------------------------------- /rockspec/copas-1.2.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "Copas" 2 | version = "1.2.1-1" 3 | source = { 4 | url = "https://github.com/keplerproject/copas/archive/v1_2_1.tar.gz", 5 | dir = "copas-1_2_1", 6 | } 7 | description = { 8 | summary = "Coroutine Oriented Portable Asynchronous Services", 9 | detailed = [[ 10 | Copas is a dispatcher based on coroutines that can be used by 11 | TCP/IP servers. It uses LuaSocket as the interface with the 12 | TCP/IP stack. A server registered with Copas should provide a 13 | handler for requests and use Copas socket functions to send 14 | the response. Copas loops through requests and invokes the 15 | corresponding handlers. For a full implementation of a Copas 16 | HTTP server you can refer to Xavante as an example. 17 | ]], 18 | license = "MIT/X11", 19 | homepage = "http://www.keplerproject.org/copas/" 20 | } 21 | dependencies = { 22 | "lua >= 5.1, < 5.3", 23 | "luasocket >= 2.1", 24 | "coxpcall >= 1.14", 25 | } 26 | build = { 27 | type = "builtin", 28 | modules = { copas = "src/copas/copas.lua" } 29 | } 30 | -------------------------------------------------------------------------------- /rockspec/copas-1.1.3-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "Copas" 2 | version = "1.1.3-1" 3 | source = { 4 | url = "http://luaforge.net/frs/download.php/3409/copas-1.1.3.tar.gz", 5 | } 6 | description = { 7 | summary = "Coroutine Oriented Portable Asynchronous Services", 8 | detailed = [[ 9 | Copas is a dispatcher based on coroutines that can be used by 10 | TCP/IP servers. It uses LuaSocket as the interface with the 11 | TCP/IP stack. A server registered with Copas should provide a 12 | handler for requests and use Copas socket functions to send 13 | the response. Copas loops through requests and invokes the 14 | corresponding handlers. For a full implementation of a Copas 15 | HTTP server you can refer to Xavante as an example. 16 | ]], 17 | license = "MIT/X11", 18 | homepage = "http://www.keplerproject.org/copas/" 19 | } 20 | dependencies = { 21 | "lua >= 5.1", 22 | "luasocket >= 2.0", 23 | "coxpcall >= 1.13", 24 | } 25 | build = { 26 | type = "make", 27 | build_pass = false, 28 | install_variables = { 29 | LUA_DIR = "$(LUADIR)" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/copas/smtp.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------- 2 | -- identical to the socket.smtp module except that it uses 3 | -- async wrapped Copas sockets 4 | 5 | local copas = require("copas") 6 | local smtp = require("socket.smtp") 7 | local socket = require("socket") 8 | 9 | local create = function() return copas.wrap(socket.tcp()) end 10 | local forwards = { -- setting these will be forwarded to the original smtp module 11 | PORT = true, 12 | SERVER = true, 13 | TIMEOUT = true, 14 | DOMAIN = true, 15 | TIMEZONE = true 16 | } 17 | 18 | copas.smtp = setmetatable({}, { 19 | -- use original module as metatable, to lookup constants like socket.SERVER, etc. 20 | __index = smtp, 21 | -- Setting constants is forwarded to the luasocket.smtp module. 22 | __newindex = function(self, key, value) 23 | if forwards[key] then smtp[key] = value return end 24 | return rawset(self, key, value) 25 | end, 26 | }) 27 | local _M = copas.smtp 28 | 29 | _M.send = function(mailt) 30 | mailt.create = mailt.create or create 31 | return smtp.send(mailt) 32 | end 33 | 34 | return _M -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2005-2013 Kepler Project, 2015-2025 Thijs Schreijer. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, copy, 7 | modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 18 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 19 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/no_luasocket.lua: -------------------------------------------------------------------------------- 1 | -- make sure we are pointing to the local copas first 2 | package.path = string.format("../src/?.lua;%s", package.path) 3 | 4 | print([[ 5 | Testing to run Copas without LuaSocket, just LuaSystem 6 | ============================================================================= 7 | ]]) 8 | 9 | -- patch require to no longer load luasocket 10 | local _require = require 11 | _G.require = function(name) 12 | if name == "socket" then 13 | error("luasocket is not allowed in this test") 14 | end 15 | return _require(name) 16 | end 17 | 18 | 19 | local copas = require "copas" 20 | local timer = copas.timer 21 | 22 | local successes = 0 23 | 24 | local t1 -- luacheck: ignore 25 | 26 | copas.loop(function() 27 | 28 | t1 = timer.new({ 29 | delay = 0.1, 30 | recurring = true, 31 | callback = function(timer_obj, params) 32 | successes = successes + 1 -- 6 to come 33 | if successes == 6 then 34 | timer_obj:cancel() 35 | end 36 | end, 37 | }) 38 | -- succes count = 6 39 | 40 | end) 41 | 42 | 43 | assert(successes == 6, "number of successes didn't match! got: "..successes) 44 | print("test success!") 45 | -------------------------------------------------------------------------------- /rockspec/copas-2.0.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "Copas" 2 | version = "2.0.0-1" 3 | source = { 4 | url = "https://github.com/keplerproject/copas/archive/v2_0_0.tar.gz", 5 | dir = "copas-2_0_0", 6 | } 7 | description = { 8 | summary = "Coroutine Oriented Portable Asynchronous Services", 9 | detailed = [[ 10 | Copas is a dispatcher based on coroutines that can be used by 11 | TCP/IP servers. It uses LuaSocket as the interface with the 12 | TCP/IP stack. A server registered with Copas should provide a 13 | handler for requests and use Copas socket functions to send 14 | the response. Copas loops through requests and invokes the 15 | corresponding handlers. For a full implementation of a Copas 16 | HTTP server you can refer to Xavante as an example. 17 | ]], 18 | license = "MIT/X11", 19 | homepage = "http://www.keplerproject.org/copas/" 20 | } 21 | dependencies = { 22 | "lua >= 5.1, < 5.3", 23 | "luasocket >= 2.1", 24 | "coxpcall >= 1.14", 25 | } 26 | build = { 27 | type = "builtin", 28 | modules = { 29 | ["copas"] = "src/copas.lua", 30 | ["copas.http"] = "src/copas/http.lua", 31 | ["copas.ftp"] = "src/copas/ftp.lua", 32 | ["copas.smtp"] = "src/copas/smtp.lua", 33 | ["copas.limit"] = "src/copas/limit.lua", 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rockspec/copas-2.0.0-2.rockspec: -------------------------------------------------------------------------------- 1 | package = "Copas" 2 | version = "2.0.0-2" 3 | source = { 4 | url = "https://github.com/keplerproject/copas/archive/v2_0_0.tar.gz", 5 | dir = "copas-2_0_0", 6 | } 7 | description = { 8 | summary = "Coroutine Oriented Portable Asynchronous Services", 9 | detailed = [[ 10 | Copas is a dispatcher based on coroutines that can be used by 11 | TCP/IP servers. It uses LuaSocket as the interface with the 12 | TCP/IP stack. A server registered with Copas should provide a 13 | handler for requests and use Copas socket functions to send 14 | the response. Copas loops through requests and invokes the 15 | corresponding handlers. For a full implementation of a Copas 16 | HTTP server you can refer to Xavante as an example. 17 | ]], 18 | license = "MIT/X11", 19 | homepage = "http://www.keplerproject.org/copas/" 20 | } 21 | dependencies = { 22 | "lua >= 5.1, < 5.4", 23 | "luasocket >= 2.1", 24 | "coxpcall >= 1.14", 25 | } 26 | build = { 27 | type = "builtin", 28 | modules = { 29 | ["copas"] = "src/copas.lua", 30 | ["copas.http"] = "src/copas/http.lua", 31 | ["copas.ftp"] = "src/copas/ftp.lua", 32 | ["copas.smtp"] = "src/copas/smtp.lua", 33 | ["copas.limit"] = "src/copas/limit.lua", 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rockspec/copas-2.0.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "Copas" 2 | version = "2.0.1-1" 3 | source = { 4 | url = "https://github.com/keplerproject/copas/archive/v2_0_1.tar.gz", 5 | dir = "copas-2_0_1", 6 | } 7 | description = { 8 | summary = "Coroutine Oriented Portable Asynchronous Services", 9 | detailed = [[ 10 | Copas is a dispatcher based on coroutines that can be used by 11 | TCP/IP servers. It uses LuaSocket as the interface with the 12 | TCP/IP stack. A server registered with Copas should provide a 13 | handler for requests and use Copas socket functions to send 14 | the response. Copas loops through requests and invokes the 15 | corresponding handlers. For a full implementation of a Copas 16 | HTTP server you can refer to Xavante as an example. 17 | ]], 18 | license = "MIT/X11", 19 | homepage = "http://www.keplerproject.org/copas/" 20 | } 21 | dependencies = { 22 | "lua >= 5.1, < 5.4", 23 | "luasocket >= 2.1", 24 | "coxpcall >= 1.14", 25 | } 26 | build = { 27 | type = "builtin", 28 | modules = { 29 | ["copas"] = "src/copas.lua", 30 | ["copas.http"] = "src/copas/http.lua", 31 | ["copas.ftp"] = "src/copas/ftp.lua", 32 | ["copas.smtp"] = "src/copas/smtp.lua", 33 | ["copas.limit"] = "src/copas/limit.lua", 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rockspec/copas-2.0.2-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "Copas" 2 | version = "2.0.2-1" 3 | source = { 4 | url = "https://github.com/keplerproject/copas/archive/v2_0_2.tar.gz", 5 | dir = "copas-2_0_2", 6 | } 7 | description = { 8 | summary = "Coroutine Oriented Portable Asynchronous Services", 9 | detailed = [[ 10 | Copas is a dispatcher based on coroutines that can be used by 11 | TCP/IP servers. It uses LuaSocket as the interface with the 12 | TCP/IP stack. A server registered with Copas should provide a 13 | handler for requests and use Copas socket functions to send 14 | the response. Copas loops through requests and invokes the 15 | corresponding handlers. For a full implementation of a Copas 16 | HTTP server you can refer to Xavante as an example. 17 | ]], 18 | license = "MIT/X11", 19 | homepage = "http://www.keplerproject.org/copas/" 20 | } 21 | dependencies = { 22 | "lua >= 5.1, < 5.4", 23 | "luasocket >= 2.1", 24 | "coxpcall >= 1.14", 25 | } 26 | build = { 27 | type = "builtin", 28 | modules = { 29 | ["copas"] = "src/copas.lua", 30 | ["copas.http"] = "src/copas/http.lua", 31 | ["copas.ftp"] = "src/copas/ftp.lua", 32 | ["copas.smtp"] = "src/copas/smtp.lua", 33 | ["copas.limit"] = "src/copas/limit.lua", 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/timeout_errors.lua: -------------------------------------------------------------------------------- 1 | -- Tests Copas timeout mnechanism, when a timeout handler errors out 2 | -- 3 | -- Run the test file, it should exit successfully without hanging. 4 | 5 | -- make sure we are pointing to the local copas first 6 | package.path = string.format("../src/?.lua;%s", package.path) 7 | local copas = require("copas") 8 | 9 | 10 | local tests = {} 11 | 12 | 13 | function tests.error_on_timeout() 14 | local err_received 15 | 16 | copas.addthread(function() 17 | copas.setErrorHandler(function(err, coro, skt) 18 | err_received = err 19 | end, true) 20 | print "setting timeout in 0.1 seconds" 21 | copas.timeout(0.1, function() 22 | print "throwing an error now..." 23 | error("oops...") 24 | end) 25 | print "going to sleep for 1 second" 26 | copas.pause(1) 27 | 28 | if not (err_received or ""):find("oops...", 1, true) then 29 | print("expected to find the error string 'oops...', but got: " .. tostring(err_received)) 30 | os.exit(1) 31 | end 32 | end) 33 | 34 | copas.loop() 35 | end 36 | 37 | 38 | 39 | 40 | 41 | -- test "framework" 42 | for name, test in pairs(tests) do 43 | print("testing: "..tostring(name)) 44 | local status, err = pcall(test) 45 | if not status then 46 | error(err) 47 | end 48 | end 49 | 50 | print("[✓] all tests completed successuly") 51 | -------------------------------------------------------------------------------- /tests/removeserver.lua: -------------------------------------------------------------------------------- 1 | 2 | --- Test for removeserver(skt, true) 3 | -- that keeps the socket open after removal. 4 | 5 | local copas = require("copas") 6 | local socket = require("socket") 7 | 8 | local wskt = socket.bind("*", 0) 9 | local whost, wport = wskt:getsockname() 10 | wport = tonumber(wport) 11 | 12 | -- set up a timeout to not hang on failure 13 | local timeout_timer = copas.timer.new { 14 | delay = 10, 15 | callback = function() 16 | print("timeout!") 17 | os.exit(1) 18 | end 19 | } 20 | 21 | local connection_handler = function(cskt) 22 | print(tostring(cskt).." ("..type(cskt)..") received a connection") 23 | local data, _, partial = cskt:receive() 24 | if partial and not data then 25 | data = partial 26 | end 27 | print("triggered", data) 28 | copas.removeserver(wskt, true) 29 | end 30 | 31 | local function wait_for_trigger() 32 | copas.addserver(wskt, copas.handler(connection_handler), "my_TCP_server") 33 | end 34 | 35 | local function trigger_it(n) 36 | local cskt = copas.wrap(socket.tcp()) 37 | local ok = cskt:connect(whost, wport) 38 | if ok then 39 | cskt:send("hi "..n) 40 | end 41 | cskt:close() 42 | end 43 | 44 | copas.addthread(function() 45 | for i = 1, 3 do 46 | wait_for_trigger() 47 | trigger_it(i) 48 | copas.pause(0.1) 49 | end 50 | timeout_timer:cancel() 51 | end) 52 | 53 | copas.loop() 54 | -------------------------------------------------------------------------------- /misc/testasyncspeed.lua: -------------------------------------------------------------------------------- 1 | local copas = require("copas") 2 | local synchttp = require("socket.http").request 3 | local asynchttp = copas.http.request 4 | local gettime = require("socket").gettime 5 | 6 | local targets = { 7 | "http://www.google.com", 8 | "http://www.microsoft.com", 9 | "http://www.apple.com", 10 | "http://www.facebook.com", 11 | "http://www.yahoo.com", 12 | } 13 | 14 | local function sync(list) 15 | for _, host in ipairs(list) do 16 | local res = synchttp(host) 17 | if not res then 18 | print("Error sync: "..host.." failed, rerun test!") 19 | else 20 | print("Sync host done: "..host) 21 | end 22 | end 23 | end 24 | 25 | local handler = function(host) 26 | local res = asynchttp(host) 27 | if not res then 28 | print("Error async: "..host.." failed, rerun test!") 29 | else 30 | print("Async host done: "..host) 31 | end 32 | end 33 | 34 | local function async(list) 35 | for _, host in ipairs(list) do copas.addthread(handler, host) end 36 | copas.loop() 37 | end 38 | 39 | -- three times to remove caching differences 40 | async(targets) 41 | async(targets) 42 | async(targets) 43 | print("\nNow starting the real test...\n") 44 | 45 | local t1 = gettime() 46 | print("Sync:") 47 | sync(targets) 48 | local t2 = gettime() 49 | print("Async:") 50 | async(targets) 51 | local t3 = gettime() 52 | 53 | print("\nResults:") 54 | print("========") 55 | print(" Sync : ", t2-t1) 56 | print(" Async: ", t3-t2) 57 | 58 | 59 | -------------------------------------------------------------------------------- /tests/exit.lua: -------------------------------------------------------------------------------- 1 | -- tests whether the main loop automatically exits when all work is done 2 | 3 | local copas = require("copas") 4 | local socket = require("socket") 5 | 6 | local done = false 7 | 8 | copas.addthread(function() 9 | for i = 1, 5 do 10 | copas.pause() 11 | print(i) 12 | end 13 | 14 | done = true 15 | end) 16 | 17 | print("Test 1 count to 5... then exit") 18 | copas.loop() 19 | 20 | if done then 21 | print("Test 1 done") 22 | else 23 | print("Loop completed with test 1 not finished") 24 | os.exit(1) 25 | end 26 | 27 | done = false 28 | local server = socket.bind("*", 20000) 29 | local message = "Hello world!" 30 | 31 | copas.addserver(server, function(skt) 32 | local received = copas.receive(skt) 33 | 34 | if received ~= message then 35 | print("Incorrect return from copas.receive: "..tostring(received)) 36 | os.exit(1) 37 | else 38 | print("Received "..message) 39 | end 40 | 41 | copas.removeserver(server) 42 | done = true 43 | end) 44 | 45 | copas.addthread(function() 46 | copas.pause(1) 47 | local skt = socket.connect("localhost", 20000) 48 | print("Sending "..message.."\\n") 49 | local bytes = copas.send(skt, message.."\n") 50 | 51 | if bytes ~= #message + 1 then 52 | print("Incorrect return from copas.send: "..tostring(bytes)) 53 | os.exit(1) 54 | end 55 | end) 56 | 57 | print("Test 2 send and receive some... then exit") 58 | copas.loop() 59 | 60 | if done then 61 | print("Test 2 done") 62 | else 63 | print("Loop completed with test 2 not finished") 64 | os.exit(1) 65 | end 66 | -------------------------------------------------------------------------------- /misc/cosocket.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------- 2 | -- Copas - Coroutine Oriented Portable Asynchronous Services 3 | -- 4 | -- Copas Wrapper for socket.http module 5 | -- 6 | -- Written by Leonardo Godinho da Cunha 7 | ------------------------------------------------------------------------------- 8 | local copas = require("copas") 9 | local socket = require("socket") 10 | 11 | local cosocket = {} 12 | 13 | -- Meta information is public even begining with an "_" 14 | cosocket._COPYRIGHT = "Copyright (C) 2004-2006 Kepler Project" 15 | cosocket._DESCRIPTION = "Coroutine Oriented Portable Asynchronous Services Wrapper for socket module" 16 | cosocket._NAME = "Copas.cosocket" 17 | cosocket._VERSION = "0.1" 18 | 19 | function cosocket.tcp () 20 | local skt = socket.tcp() 21 | local w_skt_mt = { __index = skt } 22 | local ret_skt = setmetatable ({ socket = skt }, w_skt_mt) 23 | ret_skt.settimeout = function (self,val) 24 | return self.socket:settimeout (val) 25 | end 26 | ret_skt.connect = function (self,host, port) 27 | local ret,err = copas.connect (self.socket,host, port) 28 | local d = copas.wrap(self.socket) 29 | 30 | self.send= function(client, data) 31 | local ret,val=d.send(client, data) 32 | return ret,val 33 | end 34 | self.receive=d.receive 35 | self.close = function (w_socket) 36 | ret=w_socket.socket:close() 37 | return ret 38 | end 39 | return ret,err 40 | end 41 | return ret_skt 42 | end 43 | 44 | return cosocket 45 | -------------------------------------------------------------------------------- /.github/workflows/unix_build.yml: -------------------------------------------------------------------------------- 1 | name: "Unix build" 2 | 3 | concurrency: 4 | # for PR's cancel the running task, if another commit is pushed 5 | group: ${{ github.workflow }} ${{ github.ref }} 6 | cancel-in-progress: ${{ github.event_name == 'pull_request' }} 7 | 8 | on: 9 | # build on PR and push-to-master. This works for short-lived branches, and saves 10 | # CPU cycles on duplicated tests. 11 | # For long-lived branches that diverge, you'll want to run on all pushes, not 12 | # just on push-to-master. 13 | pull_request: {} 14 | push: 15 | branches: 16 | - master 17 | 18 | jobs: 19 | test: 20 | runs-on: ubuntu-20.04 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | luaVersion: ["5.1", "5.2", "5.3", "5.4", "luajit-2.1.0-beta3"] 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | 30 | - uses: leafo/gh-actions-lua@v10 31 | with: 32 | luaVersion: ${{ matrix.luaVersion }} 33 | 34 | - uses: leafo/gh-actions-luarocks@v4 35 | 36 | - name: dependencies 37 | run: | 38 | luarocks install luacov-coveralls 39 | luarocks install luasec 40 | luarocks install luasystem 41 | 42 | - name: generate test certificates 43 | run: | 44 | make certs 45 | 46 | - name: build 47 | run: | 48 | luarocks make 49 | 50 | - name: test 51 | run: | 52 | make coverage 53 | 54 | - name: Report test coverage 55 | if: success() 56 | continue-on-error: true 57 | run: luacov-coveralls 58 | env: 59 | COVERALLS_REPO_TOKEN: ${{ github.token }} 60 | -------------------------------------------------------------------------------- /tests/removethread.lua: -------------------------------------------------------------------------------- 1 | --- Test for removethread(thread) 2 | 3 | -- make sure we are pointing to the local copas first 4 | package.path = string.format("../src/?.lua;%s", package.path) 5 | 6 | local copas = require("copas") 7 | local now = copas.gettime 8 | 9 | 10 | -- Test 1: basic test 11 | 12 | local t1 = copas.addthread( 13 | function() 14 | print("endless thread start") 15 | local n = 0 16 | while true do 17 | n = n + 1 18 | print("endless thread:",n) 19 | copas.pause(0.5) 20 | end 21 | end) 22 | 23 | local t2 = copas.addthread(function() 24 | for i = 1, 5 do 25 | copas.pause(0.6) 26 | end 27 | print("stopping endless thread externally") 28 | copas.removethread(t1) 29 | end) 30 | 31 | 32 | -- prepare GC test 33 | local validate_gc = setmetatable({ 34 | [t1] = true, 35 | [t2] = true, 36 | },{ __mode = "k" }) 37 | 38 | -- start test 39 | copas.loop() 40 | 41 | t1 = nil -- luacheck: ignore 42 | t2 = nil -- luacheck: ignore 43 | collectgarbage() 44 | collectgarbage() 45 | 46 | --check GC 47 | assert(next(validate_gc) == nil, "the 'validate_gc' table should have been empty!") 48 | print "test 1: success!" 49 | 50 | 51 | 52 | -- Test 2: ensure a waiting thread leaves in a timely manner 53 | 54 | local coro = copas.addthread(function() 55 | copas.pause(2) 56 | end) 57 | 58 | local start_time = now() 59 | copas(function() 60 | copas.pause(0.5) 61 | copas.removethread(coro) 62 | end) 63 | local duration = now() - start_time 64 | 65 | assert(duration < 0.6, ("Expected immediate exit, after removal of thread, took: %.2f"):format(duration)) 66 | print "test 2: success!" 67 | -------------------------------------------------------------------------------- /tests/request.lua: -------------------------------------------------------------------------------- 1 | local copas = require("copas") 2 | local http = copas.http 3 | 4 | local url = assert(arg[1], "missing url argument") 5 | local debug_mode = not not arg[2] 6 | 7 | print("Testing copas.http.request with url " .. url .. (debug_mode and "(in debug mode)" or "")) 8 | local switches, max_switches = 0, 10000000 9 | local done = false 10 | 11 | 12 | if debug_mode then 13 | copas.debug.start() 14 | local socket = require "socket" 15 | local old_tcp = socket.tcp 16 | socket.tcp = function(...) 17 | local sock, err = old_tcp(...) 18 | if not sock then 19 | return sock, err 20 | end 21 | return copas.debug.socket(sock) 22 | end 23 | end 24 | 25 | 26 | copas.addthread(function() 27 | while switches < max_switches do 28 | copas.pause() 29 | switches = switches + 1 30 | end 31 | 32 | if not done then 33 | print(("Error: Request not finished after %d thread switches"):format(switches)) 34 | os.exit(1) 35 | end 36 | end) 37 | 38 | copas.addthread(function() 39 | print("Starting request") 40 | local content, code, headers, status = http.request(url) 41 | print(("Finished request after %d thread switches"):format(switches)) 42 | 43 | if type(content) ~= "string" or type(code) ~= "number" or 44 | type(headers) ~= "table" or type(status) ~= "string" then 45 | print("Error: incorrect return values:") 46 | print(content) 47 | print(code) 48 | print(headers) 49 | print(status) 50 | os.exit(1) 51 | end 52 | 53 | print(("Status: %s, content: %d bytes"):format(status, #content)) 54 | done = true 55 | max_switches = switches + 10 -- just do a few more and finish the test 56 | end) 57 | 58 | print("Starting loop") 59 | copas.loop() 60 | 61 | if done then 62 | print("Finished loop") 63 | else 64 | print("Error: Finished loop but request is not complete") 65 | os.exit(1) 66 | end 67 | -------------------------------------------------------------------------------- /rockspec/copas-3.0.0-1.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "copas" 2 | local package_version = "3.0.0" 3 | local rockspec_revision = "1" 4 | local github_account_name = "keplerproject" 5 | local github_repo_name = package_name 6 | 7 | 8 | package = package_name 9 | version = package_version.."-"..rockspec_revision 10 | source = { 11 | url = "git://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 | branch = (package_version == "cvs") and "master" or nil, 13 | tag = (package_version ~= "cvs") and package_version or nil, 14 | } 15 | description = { 16 | summary = "Coroutine Oriented Portable Asynchronous Services", 17 | detailed = [[ 18 | Copas is a dispatcher based on coroutines that can be used by 19 | TCP/IP servers. It uses LuaSocket as the interface with the 20 | TCP/IP stack. A server registered with Copas should provide a 21 | handler for requests and use Copas socket functions to send 22 | the response. Copas loops through requests and invokes the 23 | corresponding handlers. For a full implementation of a Copas 24 | HTTP server you can refer to Xavante as an example. 25 | ]], 26 | license = "MIT/X11", 27 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 28 | } 29 | dependencies = { 30 | "lua >= 5.1, < 5.4", 31 | "luasocket >= 2.1", 32 | "coxpcall >= 1.14", 33 | "binaryheap >= 0.4", 34 | "timerwheel >= 0.2", 35 | } 36 | build = { 37 | type = "builtin", 38 | modules = { 39 | ["copas"] = "src/copas.lua", 40 | ["copas.http"] = "src/copas/http.lua", 41 | ["copas.ftp"] = "src/copas/ftp.lua", 42 | ["copas.smtp"] = "src/copas/smtp.lua", 43 | ["copas.limit"] = "src/copas/limit.lua", 44 | ["copas.timer"] = "src/copas/timer.lua", 45 | ["copas.lock"] = "src/copas/lock.lua", 46 | ["copas.semaphore"] = "src/copas/semaphore.lua", 47 | }, 48 | copy_directories = { 49 | "docs", 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /rockspec/copas-3.0.0-2.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "copas" 2 | local package_version = "3.0.0" 3 | local rockspec_revision = "2" 4 | local github_account_name = "lunarmodules" 5 | local github_repo_name = package_name 6 | 7 | 8 | package = package_name 9 | version = package_version.."-"..rockspec_revision 10 | source = { 11 | url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 | branch = (package_version == "cvs") and "master" or nil, 13 | tag = (package_version ~= "cvs") and package_version or nil, 14 | } 15 | description = { 16 | summary = "Coroutine Oriented Portable Asynchronous Services", 17 | detailed = [[ 18 | Copas is a dispatcher based on coroutines that can be used by 19 | TCP/IP servers. It uses LuaSocket as the interface with the 20 | TCP/IP stack. A server registered with Copas should provide a 21 | handler for requests and use Copas socket functions to send 22 | the response. Copas loops through requests and invokes the 23 | corresponding handlers. For a full implementation of a Copas 24 | HTTP server you can refer to Xavante as an example. 25 | ]], 26 | license = "MIT/X11", 27 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 28 | } 29 | dependencies = { 30 | "lua >= 5.1, < 5.5", 31 | "luasocket >= 2.1", 32 | "coxpcall >= 1.14", 33 | "binaryheap >= 0.4", 34 | "timerwheel >= 0.2", 35 | } 36 | build = { 37 | type = "builtin", 38 | modules = { 39 | ["copas"] = "src/copas.lua", 40 | ["copas.http"] = "src/copas/http.lua", 41 | ["copas.ftp"] = "src/copas/ftp.lua", 42 | ["copas.smtp"] = "src/copas/smtp.lua", 43 | ["copas.limit"] = "src/copas/limit.lua", 44 | ["copas.timer"] = "src/copas/timer.lua", 45 | ["copas.lock"] = "src/copas/lock.lua", 46 | ["copas.semaphore"] = "src/copas/semaphore.lua", 47 | }, 48 | copy_directories = { 49 | "docs", 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /rockspec/copas-3.0.0-3.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "copas" 2 | local package_version = "3.0.0" 3 | local rockspec_revision = "3" 4 | local github_account_name = "lunarmodules" 5 | local github_repo_name = package_name 6 | 7 | 8 | package = package_name 9 | version = package_version.."-"..rockspec_revision 10 | source = { 11 | url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 | branch = (package_version == "cvs") and "master" or nil, 13 | tag = (package_version ~= "cvs") and package_version or nil, 14 | } 15 | description = { 16 | summary = "Coroutine Oriented Portable Asynchronous Services", 17 | detailed = [[ 18 | Copas is a dispatcher based on coroutines that can be used by 19 | TCP/IP servers. It uses LuaSocket as the interface with the 20 | TCP/IP stack. A server registered with Copas should provide a 21 | handler for requests and use Copas socket functions to send 22 | the response. Copas loops through requests and invokes the 23 | corresponding handlers. For a full implementation of a Copas 24 | HTTP server you can refer to Xavante as an example. 25 | ]], 26 | license = "MIT/X11", 27 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 28 | } 29 | dependencies = { 30 | "lua >= 5.1, < 5.5", 31 | "luasocket >= 2.1, <= 3.0rc1-2", 32 | "coxpcall >= 1.14", 33 | "binaryheap >= 0.4", 34 | "timerwheel >= 0.2", 35 | } 36 | build = { 37 | type = "builtin", 38 | modules = { 39 | ["copas"] = "src/copas.lua", 40 | ["copas.http"] = "src/copas/http.lua", 41 | ["copas.ftp"] = "src/copas/ftp.lua", 42 | ["copas.smtp"] = "src/copas/smtp.lua", 43 | ["copas.limit"] = "src/copas/limit.lua", 44 | ["copas.timer"] = "src/copas/timer.lua", 45 | ["copas.lock"] = "src/copas/lock.lua", 46 | ["copas.semaphore"] = "src/copas/semaphore.lua", 47 | }, 48 | copy_directories = { 49 | "docs", 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /rockspec/copas-4.0.0-1.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "copas" 2 | local package_version = "4.0.0" 3 | local rockspec_revision = "1" 4 | local github_account_name = "lunarmodules" 5 | local github_repo_name = package_name 6 | 7 | 8 | package = package_name 9 | version = package_version.."-"..rockspec_revision 10 | source = { 11 | url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 | branch = (package_version == "cvs") and "master" or nil, 13 | tag = (package_version ~= "cvs") and package_version or nil, 14 | } 15 | description = { 16 | summary = "Coroutine Oriented Portable Asynchronous Services", 17 | detailed = [[ 18 | Copas is a dispatcher based on coroutines that can be used by 19 | TCP/IP servers. It uses LuaSocket as the interface with the 20 | TCP/IP stack. A server registered with Copas should provide a 21 | handler for requests and use Copas socket functions to send 22 | the response. Copas loops through requests and invokes the 23 | corresponding handlers. For a full implementation of a Copas 24 | HTTP server you can refer to Xavante as an example. 25 | ]], 26 | license = "MIT/X11", 27 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 28 | } 29 | dependencies = { 30 | "lua >= 5.1, < 5.5", 31 | "luasocket >= 2.1, <= 3.0rc1-2", 32 | "coxpcall >= 1.14", 33 | "binaryheap >= 0.4", 34 | "timerwheel >= 0.2", 35 | } 36 | build = { 37 | type = "builtin", 38 | modules = { 39 | ["copas"] = "src/copas.lua", 40 | ["copas.http"] = "src/copas/http.lua", 41 | ["copas.ftp"] = "src/copas/ftp.lua", 42 | ["copas.smtp"] = "src/copas/smtp.lua", 43 | ["copas.timer"] = "src/copas/timer.lua", 44 | ["copas.lock"] = "src/copas/lock.lua", 45 | ["copas.semaphore"] = "src/copas/semaphore.lua", 46 | ["copas.queue"] = "src/copas/queue.lua", 47 | }, 48 | copy_directories = { 49 | "docs", 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /rockspec/copas-4.1.0-1.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "copas" 2 | local package_version = "4.1.0" 3 | local rockspec_revision = "1" 4 | local github_account_name = "lunarmodules" 5 | local github_repo_name = package_name 6 | 7 | 8 | package = package_name 9 | version = package_version.."-"..rockspec_revision 10 | source = { 11 | url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 | branch = (package_version == "cvs") and "master" or nil, 13 | tag = (package_version ~= "cvs") and package_version or nil, 14 | } 15 | description = { 16 | summary = "Coroutine Oriented Portable Asynchronous Services", 17 | detailed = [[ 18 | Copas is a dispatcher based on coroutines that can be used by 19 | TCP/IP servers. It uses LuaSocket as the interface with the 20 | TCP/IP stack. A server registered with Copas should provide a 21 | handler for requests and use Copas socket functions to send 22 | the response. Copas loops through requests and invokes the 23 | corresponding handlers. For a full implementation of a Copas 24 | HTTP server you can refer to Xavante as an example. 25 | ]], 26 | license = "MIT/X11", 27 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 28 | } 29 | dependencies = { 30 | "lua >= 5.1, < 5.5", 31 | "luasocket >= 2.1, <= 3.0rc1-2", 32 | "coxpcall >= 1.14", 33 | "binaryheap >= 0.4", 34 | "timerwheel ~> 1", 35 | } 36 | build = { 37 | type = "builtin", 38 | modules = { 39 | ["copas"] = "src/copas.lua", 40 | ["copas.http"] = "src/copas/http.lua", 41 | ["copas.ftp"] = "src/copas/ftp.lua", 42 | ["copas.smtp"] = "src/copas/smtp.lua", 43 | ["copas.timer"] = "src/copas/timer.lua", 44 | ["copas.lock"] = "src/copas/lock.lua", 45 | ["copas.semaphore"] = "src/copas/semaphore.lua", 46 | ["copas.queue"] = "src/copas/queue.lua", 47 | }, 48 | copy_directories = { 49 | "docs", 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /rockspec/copas-4.2.0-1.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "copas" 2 | local package_version = "4.2.0" 3 | local rockspec_revision = "1" 4 | local github_account_name = "lunarmodules" 5 | local github_repo_name = package_name 6 | 7 | 8 | package = package_name 9 | version = package_version.."-"..rockspec_revision 10 | source = { 11 | url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 | branch = (package_version == "cvs") and "master" or nil, 13 | tag = (package_version ~= "cvs") and package_version or nil, 14 | } 15 | description = { 16 | summary = "Coroutine Oriented Portable Asynchronous Services", 17 | detailed = [[ 18 | Copas is a dispatcher based on coroutines that can be used by 19 | TCP/IP servers. It uses LuaSocket as the interface with the 20 | TCP/IP stack. A server registered with Copas should provide a 21 | handler for requests and use Copas socket functions to send 22 | the response. Copas loops through requests and invokes the 23 | corresponding handlers. For a full implementation of a Copas 24 | HTTP server you can refer to Xavante as an example. 25 | ]], 26 | license = "MIT/X11", 27 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 28 | } 29 | dependencies = { 30 | "lua >= 5.1, < 5.5", 31 | "luasocket >= 2.1, <= 3.0rc1-2", 32 | "coxpcall >= 1.14", 33 | "binaryheap >= 0.4", 34 | "timerwheel ~> 1", 35 | } 36 | build = { 37 | type = "builtin", 38 | modules = { 39 | ["copas"] = "src/copas.lua", 40 | ["copas.http"] = "src/copas/http.lua", 41 | ["copas.ftp"] = "src/copas/ftp.lua", 42 | ["copas.smtp"] = "src/copas/smtp.lua", 43 | ["copas.timer"] = "src/copas/timer.lua", 44 | ["copas.lock"] = "src/copas/lock.lua", 45 | ["copas.semaphore"] = "src/copas/semaphore.lua", 46 | ["copas.queue"] = "src/copas/queue.lua", 47 | }, 48 | copy_directories = { 49 | "docs", 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /rockspec/copas-4.3.0-1.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "copas" 2 | local package_version = "4.3.0" 3 | local rockspec_revision = "1" 4 | local github_account_name = "lunarmodules" 5 | local github_repo_name = package_name 6 | 7 | 8 | package = package_name 9 | version = package_version.."-"..rockspec_revision 10 | source = { 11 | url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 | branch = (package_version == "cvs") and "master" or nil, 13 | tag = (package_version ~= "cvs") and package_version or nil, 14 | } 15 | description = { 16 | summary = "Coroutine Oriented Portable Asynchronous Services", 17 | detailed = [[ 18 | Copas is a dispatcher based on coroutines that can be used by 19 | TCP/IP servers. It uses LuaSocket as the interface with the 20 | TCP/IP stack. A server registered with Copas should provide a 21 | handler for requests and use Copas socket functions to send 22 | the response. Copas loops through requests and invokes the 23 | corresponding handlers. For a full implementation of a Copas 24 | HTTP server you can refer to Xavante as an example. 25 | ]], 26 | license = "MIT/X11", 27 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 28 | } 29 | dependencies = { 30 | "lua >= 5.1, < 5.5", 31 | "luasocket >= 2.1, <= 3.0rc1-2", 32 | "coxpcall >= 1.14", 33 | "binaryheap >= 0.4", 34 | "timerwheel ~> 1", 35 | } 36 | build = { 37 | type = "builtin", 38 | modules = { 39 | ["copas"] = "src/copas.lua", 40 | ["copas.http"] = "src/copas/http.lua", 41 | ["copas.ftp"] = "src/copas/ftp.lua", 42 | ["copas.smtp"] = "src/copas/smtp.lua", 43 | ["copas.timer"] = "src/copas/timer.lua", 44 | ["copas.lock"] = "src/copas/lock.lua", 45 | ["copas.semaphore"] = "src/copas/semaphore.lua", 46 | ["copas.queue"] = "src/copas/queue.lua", 47 | }, 48 | copy_directories = { 49 | "docs", 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /rockspec/copas-4.3.1-1.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "copas" 2 | local package_version = "4.3.1" 3 | local rockspec_revision = "1" 4 | local github_account_name = "lunarmodules" 5 | local github_repo_name = package_name 6 | 7 | 8 | package = package_name 9 | version = package_version.."-"..rockspec_revision 10 | source = { 11 | url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 | branch = (package_version == "cvs") and "master" or nil, 13 | tag = (package_version ~= "cvs") and package_version or nil, 14 | } 15 | description = { 16 | summary = "Coroutine Oriented Portable Asynchronous Services", 17 | detailed = [[ 18 | Copas is a dispatcher based on coroutines that can be used by 19 | TCP/IP servers. It uses LuaSocket as the interface with the 20 | TCP/IP stack. A server registered with Copas should provide a 21 | handler for requests and use Copas socket functions to send 22 | the response. Copas loops through requests and invokes the 23 | corresponding handlers. For a full implementation of a Copas 24 | HTTP server you can refer to Xavante as an example. 25 | ]], 26 | license = "MIT/X11", 27 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 28 | } 29 | dependencies = { 30 | "lua >= 5.1, < 5.5", 31 | "luasocket >= 2.1, <= 3.0rc1-2", 32 | "coxpcall >= 1.14", 33 | "binaryheap >= 0.4", 34 | "timerwheel ~> 1", 35 | } 36 | build = { 37 | type = "builtin", 38 | modules = { 39 | ["copas"] = "src/copas.lua", 40 | ["copas.http"] = "src/copas/http.lua", 41 | ["copas.ftp"] = "src/copas/ftp.lua", 42 | ["copas.smtp"] = "src/copas/smtp.lua", 43 | ["copas.timer"] = "src/copas/timer.lua", 44 | ["copas.lock"] = "src/copas/lock.lua", 45 | ["copas.semaphore"] = "src/copas/semaphore.lua", 46 | ["copas.queue"] = "src/copas/queue.lua", 47 | }, 48 | copy_directories = { 49 | "docs", 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /rockspec/copas-4.3.2-1.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "copas" 2 | local package_version = "4.3.2" 3 | local rockspec_revision = "1" 4 | local github_account_name = "lunarmodules" 5 | local github_repo_name = package_name 6 | 7 | 8 | package = package_name 9 | version = package_version.."-"..rockspec_revision 10 | source = { 11 | url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 | branch = (package_version == "cvs") and "master" or nil, 13 | tag = (package_version ~= "cvs") and package_version or nil, 14 | } 15 | description = { 16 | summary = "Coroutine Oriented Portable Asynchronous Services", 17 | detailed = [[ 18 | Copas is a dispatcher based on coroutines that can be used by 19 | TCP/IP servers. It uses LuaSocket as the interface with the 20 | TCP/IP stack. A server registered with Copas should provide a 21 | handler for requests and use Copas socket functions to send 22 | the response. Copas loops through requests and invokes the 23 | corresponding handlers. For a full implementation of a Copas 24 | HTTP server you can refer to Xavante as an example. 25 | ]], 26 | license = "MIT/X11", 27 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 28 | } 29 | dependencies = { 30 | "lua >= 5.1, < 5.5", 31 | "luasocket >= 2.1, <= 3.0rc1-2", 32 | "coxpcall >= 1.14", 33 | "binaryheap >= 0.4", 34 | "timerwheel ~> 1", 35 | } 36 | build = { 37 | type = "builtin", 38 | modules = { 39 | ["copas"] = "src/copas.lua", 40 | ["copas.http"] = "src/copas/http.lua", 41 | ["copas.ftp"] = "src/copas/ftp.lua", 42 | ["copas.smtp"] = "src/copas/smtp.lua", 43 | ["copas.timer"] = "src/copas/timer.lua", 44 | ["copas.lock"] = "src/copas/lock.lua", 45 | ["copas.semaphore"] = "src/copas/semaphore.lua", 46 | ["copas.queue"] = "src/copas/queue.lua", 47 | }, 48 | copy_directories = { 49 | "docs", 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /rockspec/copas-4.4.0-1.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "copas" 2 | local package_version = "4.4.0" 3 | local rockspec_revision = "1" 4 | local github_account_name = "lunarmodules" 5 | local github_repo_name = package_name 6 | 7 | 8 | package = package_name 9 | version = package_version.."-"..rockspec_revision 10 | source = { 11 | url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 | branch = (package_version == "cvs") and "master" or nil, 13 | tag = (package_version ~= "cvs") and package_version or nil, 14 | } 15 | description = { 16 | summary = "Coroutine Oriented Portable Asynchronous Services", 17 | detailed = [[ 18 | Copas is a dispatcher based on coroutines that can be used by 19 | TCP/IP servers. It uses LuaSocket as the interface with the 20 | TCP/IP stack. A server registered with Copas should provide a 21 | handler for requests and use Copas socket functions to send 22 | the response. Copas loops through requests and invokes the 23 | corresponding handlers. For a full implementation of a Copas 24 | HTTP server you can refer to Xavante as an example. 25 | ]], 26 | license = "MIT/X11", 27 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 28 | } 29 | dependencies = { 30 | "lua >= 5.1, < 5.5", 31 | "luasocket >= 2.1, <= 3.0rc1-2", 32 | "coxpcall >= 1.14", 33 | "binaryheap >= 0.4", 34 | "timerwheel ~> 1", 35 | } 36 | build = { 37 | type = "builtin", 38 | modules = { 39 | ["copas"] = "src/copas.lua", 40 | ["copas.http"] = "src/copas/http.lua", 41 | ["copas.ftp"] = "src/copas/ftp.lua", 42 | ["copas.smtp"] = "src/copas/smtp.lua", 43 | ["copas.timer"] = "src/copas/timer.lua", 44 | ["copas.lock"] = "src/copas/lock.lua", 45 | ["copas.semaphore"] = "src/copas/semaphore.lua", 46 | ["copas.queue"] = "src/copas/queue.lua", 47 | }, 48 | copy_directories = { 49 | "docs", 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /rockspec/copas-4.5.0-1.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "copas" 2 | local package_version = "4.5.0" 3 | local rockspec_revision = "1" 4 | local github_account_name = "lunarmodules" 5 | local github_repo_name = package_name 6 | 7 | 8 | package = package_name 9 | version = package_version.."-"..rockspec_revision 10 | source = { 11 | url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 | branch = (package_version == "cvs") and "master" or nil, 13 | tag = (package_version ~= "cvs") and package_version or nil, 14 | } 15 | description = { 16 | summary = "Coroutine Oriented Portable Asynchronous Services", 17 | detailed = [[ 18 | Copas is a dispatcher based on coroutines that can be used by 19 | TCP/IP servers. It uses LuaSocket as the interface with the 20 | TCP/IP stack. A server registered with Copas should provide a 21 | handler for requests and use Copas socket functions to send 22 | the response. Copas loops through requests and invokes the 23 | corresponding handlers. For a full implementation of a Copas 24 | HTTP server you can refer to Xavante as an example. 25 | ]], 26 | license = "MIT/X11", 27 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 28 | } 29 | dependencies = { 30 | "lua >= 5.1, < 5.5", 31 | "luasocket >= 2.1, <= 3.0rc1-2", 32 | "coxpcall >= 1.14", 33 | "binaryheap >= 0.4", 34 | "timerwheel ~> 1", 35 | } 36 | build = { 37 | type = "builtin", 38 | modules = { 39 | ["copas"] = "src/copas.lua", 40 | ["copas.http"] = "src/copas/http.lua", 41 | ["copas.ftp"] = "src/copas/ftp.lua", 42 | ["copas.smtp"] = "src/copas/smtp.lua", 43 | ["copas.timer"] = "src/copas/timer.lua", 44 | ["copas.lock"] = "src/copas/lock.lua", 45 | ["copas.semaphore"] = "src/copas/semaphore.lua", 46 | ["copas.queue"] = "src/copas/queue.lua", 47 | }, 48 | copy_directories = { 49 | "docs", 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /rockspec/copas-4.6.0-1.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "copas" 2 | local package_version = "4.6.0" 3 | local rockspec_revision = "1" 4 | local github_account_name = "lunarmodules" 5 | local github_repo_name = package_name 6 | 7 | 8 | package = package_name 9 | version = package_version.."-"..rockspec_revision 10 | source = { 11 | url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 | branch = (package_version == "cvs") and "master" or nil, 13 | tag = (package_version ~= "cvs") and package_version or nil, 14 | } 15 | description = { 16 | summary = "Coroutine Oriented Portable Asynchronous Services", 17 | detailed = [[ 18 | Copas is a dispatcher based on coroutines that can be used by 19 | TCP/IP servers. It uses LuaSocket as the interface with the 20 | TCP/IP stack. A server registered with Copas should provide a 21 | handler for requests and use Copas socket functions to send 22 | the response. Copas loops through requests and invokes the 23 | corresponding handlers. For a full implementation of a Copas 24 | HTTP server you can refer to Xavante as an example. 25 | ]], 26 | license = "MIT/X11", 27 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 28 | } 29 | dependencies = { 30 | "lua >= 5.1, < 5.5", 31 | "luasocket >= 2.1, <= 3.0rc1-2", 32 | "coxpcall >= 1.14", 33 | "binaryheap >= 0.4", 34 | "timerwheel ~> 1", 35 | } 36 | build = { 37 | type = "builtin", 38 | modules = { 39 | ["copas"] = "src/copas.lua", 40 | ["copas.http"] = "src/copas/http.lua", 41 | ["copas.ftp"] = "src/copas/ftp.lua", 42 | ["copas.smtp"] = "src/copas/smtp.lua", 43 | ["copas.timer"] = "src/copas/timer.lua", 44 | ["copas.lock"] = "src/copas/lock.lua", 45 | ["copas.semaphore"] = "src/copas/semaphore.lua", 46 | ["copas.queue"] = "src/copas/queue.lua", 47 | }, 48 | copy_directories = { 49 | "docs", 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /copas-cvs-6.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "copas" 2 | local package_version = "cvs" 3 | local rockspec_revision = "6" 4 | local github_account_name = "lunarmodules" 5 | local github_repo_name = package_name 6 | 7 | 8 | package = package_name 9 | version = package_version.."-"..rockspec_revision 10 | source = { 11 | url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 | branch = (package_version == "cvs") and "master" or nil, 13 | tag = (package_version ~= "cvs") and package_version or nil, 14 | } 15 | description = { 16 | summary = "Coroutine Oriented Portable Asynchronous Services", 17 | detailed = [[ 18 | Copas is a dispatcher based on coroutines that can be used by 19 | TCP/IP servers. It uses LuaSocket as the interface with the 20 | TCP/IP stack. A server registered with Copas should provide a 21 | handler for requests and use Copas socket functions to send 22 | the response. Copas loops through requests and invokes the 23 | corresponding handlers. For a full implementation of a Copas 24 | HTTP server you can refer to Xavante as an example. 25 | ]], 26 | license = "MIT/X11", 27 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 28 | } 29 | dependencies = { 30 | "lua >= 5.1, < 5.5", 31 | "luasocket >= 2.1, <= 3.0rc1-2", 32 | "coxpcall >= 1.14", 33 | "binaryheap >= 0.4", 34 | "timerwheel ~> 1", 35 | } 36 | build = { 37 | type = "builtin", 38 | install = { 39 | bin = { 40 | copas = "bin/copas.lua", 41 | } 42 | }, 43 | modules = { 44 | ["copas"] = "src/copas.lua", 45 | ["copas.http"] = "src/copas/http.lua", 46 | ["copas.ftp"] = "src/copas/ftp.lua", 47 | ["copas.smtp"] = "src/copas/smtp.lua", 48 | ["copas.timer"] = "src/copas/timer.lua", 49 | ["copas.lock"] = "src/copas/lock.lua", 50 | ["copas.semaphore"] = "src/copas/semaphore.lua", 51 | ["copas.queue"] = "src/copas/queue.lua", 52 | }, 53 | copy_directories = { 54 | "docs", 55 | }, 56 | } 57 | -------------------------------------------------------------------------------- /rockspec/copas-4.7.0-1.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "copas" 2 | local package_version = "4.7.0" 3 | local rockspec_revision = "1" 4 | local github_account_name = "lunarmodules" 5 | local github_repo_name = package_name 6 | 7 | 8 | package = package_name 9 | version = package_version.."-"..rockspec_revision 10 | source = { 11 | url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 | branch = (package_version == "cvs") and "master" or nil, 13 | tag = (package_version ~= "cvs") and package_version or nil, 14 | } 15 | description = { 16 | summary = "Coroutine Oriented Portable Asynchronous Services", 17 | detailed = [[ 18 | Copas is a dispatcher based on coroutines that can be used by 19 | TCP/IP servers. It uses LuaSocket as the interface with the 20 | TCP/IP stack. A server registered with Copas should provide a 21 | handler for requests and use Copas socket functions to send 22 | the response. Copas loops through requests and invokes the 23 | corresponding handlers. For a full implementation of a Copas 24 | HTTP server you can refer to Xavante as an example. 25 | ]], 26 | license = "MIT/X11", 27 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 28 | } 29 | dependencies = { 30 | "lua >= 5.1, < 5.5", 31 | "luasocket >= 2.1, <= 3.0rc1-2", 32 | "coxpcall >= 1.14", 33 | "binaryheap >= 0.4", 34 | "timerwheel ~> 1", 35 | } 36 | build = { 37 | type = "builtin", 38 | install = { 39 | bin = { 40 | copas = "bin/copas.lua", 41 | } 42 | }, 43 | modules = { 44 | ["copas"] = "src/copas.lua", 45 | ["copas.http"] = "src/copas/http.lua", 46 | ["copas.ftp"] = "src/copas/ftp.lua", 47 | ["copas.smtp"] = "src/copas/smtp.lua", 48 | ["copas.timer"] = "src/copas/timer.lua", 49 | ["copas.lock"] = "src/copas/lock.lua", 50 | ["copas.semaphore"] = "src/copas/semaphore.lua", 51 | ["copas.queue"] = "src/copas/queue.lua", 52 | }, 53 | copy_directories = { 54 | "docs", 55 | }, 56 | } 57 | -------------------------------------------------------------------------------- /rockspec/copas-4.7.1-1.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "copas" 2 | local package_version = "4.7.1" 3 | local rockspec_revision = "1" 4 | local github_account_name = "lunarmodules" 5 | local github_repo_name = package_name 6 | 7 | 8 | package = package_name 9 | version = package_version.."-"..rockspec_revision 10 | source = { 11 | url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 | branch = (package_version == "cvs") and "master" or nil, 13 | tag = (package_version ~= "cvs") and package_version or nil, 14 | } 15 | description = { 16 | summary = "Coroutine Oriented Portable Asynchronous Services", 17 | detailed = [[ 18 | Copas is a dispatcher based on coroutines that can be used by 19 | TCP/IP servers. It uses LuaSocket as the interface with the 20 | TCP/IP stack. A server registered with Copas should provide a 21 | handler for requests and use Copas socket functions to send 22 | the response. Copas loops through requests and invokes the 23 | corresponding handlers. For a full implementation of a Copas 24 | HTTP server you can refer to Xavante as an example. 25 | ]], 26 | license = "MIT/X11", 27 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 28 | } 29 | dependencies = { 30 | "lua >= 5.1, < 5.5", 31 | "luasocket >= 2.1, <= 3.0rc1-2", 32 | "coxpcall >= 1.14", 33 | "binaryheap >= 0.4", 34 | "timerwheel ~> 1", 35 | } 36 | build = { 37 | type = "builtin", 38 | install = { 39 | bin = { 40 | copas = "bin/copas.lua", 41 | } 42 | }, 43 | modules = { 44 | ["copas"] = "src/copas.lua", 45 | ["copas.http"] = "src/copas/http.lua", 46 | ["copas.ftp"] = "src/copas/ftp.lua", 47 | ["copas.smtp"] = "src/copas/smtp.lua", 48 | ["copas.timer"] = "src/copas/timer.lua", 49 | ["copas.lock"] = "src/copas/lock.lua", 50 | ["copas.semaphore"] = "src/copas/semaphore.lua", 51 | ["copas.queue"] = "src/copas/queue.lua", 52 | }, 53 | copy_directories = { 54 | "docs", 55 | }, 56 | } 57 | -------------------------------------------------------------------------------- /rockspec/copas-4.8.0-1.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "copas" 2 | local package_version = "4.8.0" 3 | local rockspec_revision = "1" 4 | local github_account_name = "lunarmodules" 5 | local github_repo_name = package_name 6 | 7 | 8 | package = package_name 9 | version = package_version.."-"..rockspec_revision 10 | source = { 11 | url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", 12 | branch = (package_version == "cvs") and "master" or nil, 13 | tag = (package_version ~= "cvs") and package_version or nil, 14 | } 15 | description = { 16 | summary = "Coroutine Oriented Portable Asynchronous Services", 17 | detailed = [[ 18 | Copas is a dispatcher based on coroutines that can be used by 19 | TCP/IP servers. It uses LuaSocket as the interface with the 20 | TCP/IP stack. A server registered with Copas should provide a 21 | handler for requests and use Copas socket functions to send 22 | the response. Copas loops through requests and invokes the 23 | corresponding handlers. For a full implementation of a Copas 24 | HTTP server you can refer to Xavante as an example. 25 | ]], 26 | license = "MIT/X11", 27 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 28 | } 29 | dependencies = { 30 | "lua >= 5.1, < 5.5", 31 | "luasocket >= 2.1, <= 3.0rc1-2", 32 | "coxpcall >= 1.14", 33 | "binaryheap >= 0.4", 34 | "timerwheel ~> 1", 35 | } 36 | build = { 37 | type = "builtin", 38 | install = { 39 | bin = { 40 | copas = "bin/copas.lua", 41 | } 42 | }, 43 | modules = { 44 | ["copas"] = "src/copas.lua", 45 | ["copas.http"] = "src/copas/http.lua", 46 | ["copas.ftp"] = "src/copas/ftp.lua", 47 | ["copas.smtp"] = "src/copas/smtp.lua", 48 | ["copas.timer"] = "src/copas/timer.lua", 49 | ["copas.lock"] = "src/copas/lock.lua", 50 | ["copas.semaphore"] = "src/copas/semaphore.lua", 51 | ["copas.queue"] = "src/copas/queue.lua", 52 | }, 53 | copy_directories = { 54 | "docs", 55 | }, 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Copas 4.7 2 | 3 | [![Unix build](https://img.shields.io/github/actions/workflow/status/lunarmodules/copas/unix_build.yml?branch=master&label=Unix%20build&logo=linux)](https://github.com/lunarmodules/copas/actions) 4 | [![Coveralls code coverage](https://img.shields.io/coveralls/github/lunarmodules/copas?logo=coveralls)](https://coveralls.io/github/lunarmodules/copas) 5 | [![Luacheck](https://github.com/lunarmodules/copas/workflows/Luacheck/badge.svg)](https://github.com/lunarmodules/copas/actions) 6 | [![SemVer](https://img.shields.io/github/v/tag/lunarmodules/copas?color=brightgreen&label=SemVer&logo=semver&sort=semver)](CHANGELOG.md) 7 | [![Licence](http://img.shields.io/badge/Licence-MIT-brightgreen.svg)](LICENSE) 8 | 9 | Copas is a dispatcher based on coroutines that can be used for asynchronous networking. For example TCP or UDP based servers. But it also features timers and client support for http(s), ftp and smtp requests. 10 | 11 | It uses [LuaSocket](https://github.com/diegonehab/luasocket) as the interface with the TCP/IP stack and [LuaSec](https://github.com/brunoos/luasec) for ssl support. 12 | 13 | A server or thread registered with Copas should provide a handler for requests and use Copas socket functions to send the response. Copas loops through requests and invokes the corresponding handlers. For a full implementation of a Copas HTTP server you can refer to [Xavante](http://keplerproject.github.io/xavante/) as an example. 14 | 15 | Copas is free software and uses the same license as Lua (MIT), and can be downloaded from [its GitHub page](https://github.com/lunarmodules/copas). 16 | 17 | The easiest way to install Copas is through [LuaRocks](https://luarocks.org/): 18 | 19 | ``` 20 | luarocks install copas 21 | ``` 22 | 23 | For more details see [the documentation](http://lunarmodules.github.io/copas/). 24 | 25 | ### Releasing a new version 26 | 27 | - update changelog in docs (`index.html`, update `history` and `status` sections) 28 | - update version in `copas.lua` 29 | - update version at the top of this README, 30 | - update copyright years if needed 31 | - update rockspec 32 | - commit as `release X.Y.Z` 33 | - tag as `vX_Y_Z` and as `X.Y.Z` 34 | - push commit and tag 35 | - upload to luarocks 36 | - test luarocks installation 37 | -------------------------------------------------------------------------------- /bin/copas.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | -- luacheck: globals copas 4 | copas = require("copas") 5 | 6 | 7 | -- Error handler that forces an application exit 8 | local function errorhandler(err, co, skt) 9 | io.stderr:write(copas.gettraceback(err, co, skt).."\n") 10 | os.exit(1) 11 | end 12 | 13 | 14 | local function version_info() 15 | print(copas._VERSION, copas._COPYRIGHT) 16 | print(copas._DESCRIPTION) 17 | print("Lua VM:", _G._VERSION) 18 | end 19 | 20 | 21 | local function load_lib(lib_name) 22 | require(lib_name) 23 | end 24 | 25 | 26 | local function run_code(code) 27 | if loadstring then -- deprecated in Lua 5.2 28 | assert(loadstring(code, "command line"))() 29 | else 30 | assert(load(code, "command line"))() 31 | end 32 | end 33 | 34 | 35 | local function run_stdin() 36 | assert(loadfile())() 37 | end 38 | 39 | 40 | local function run_file(filename, i) 41 | -- shift arguments, such that the Lua file being executed is at index 0. The 42 | -- first argument following the name is at index 1. 43 | local last = #arg 44 | local first = #arg 45 | for idx, v in pairs(arg) do 46 | if idx < first then first = idx end 47 | end 48 | for n = first - i, last do 49 | arg[n] = arg[n+i] -- luacheck: ignore 50 | end 51 | assert(loadfile(filename))() 52 | end 53 | 54 | 55 | local function show_usage() 56 | print([[ 57 | usage: copas [options]... [script [args]...]. 58 | Available options are: 59 | -e chunk Execute string 'chunk'. 60 | -l name Require library 'name'. 61 | -v Show version information. 62 | -- Stop handling options. 63 | - Execute stdin and stop handling options.]]) 64 | os.exit(1) 65 | end 66 | 67 | 68 | copas(function() 69 | copas.seterrorhandler(errorhandler) 70 | local i = 0 71 | while i < math.max(#arg, 1) do -- if no args, use 1 being 'nil' 72 | i = i + 1 73 | local handled = false 74 | local opt = arg[i] or "-" -- set default action if no args 75 | -- options to continue handling 76 | if opt == "-v" then version_info() handled = true end 77 | if opt == "-l" then i = i + 1 load_lib(arg[i]) handled = true end 78 | if opt == "-e" then i = i + 1 run_code(arg[i]) handled = true end 79 | -- options that terminate handling 80 | if opt == "--" then return end 81 | if opt == "-" then return run_stdin() end 82 | if opt:sub(1,1) == "-" and not handled then return show_usage() end 83 | if not handled then return run_file(opt, i) end 84 | end 85 | end) 86 | -------------------------------------------------------------------------------- /tests/close.lua: -------------------------------------------------------------------------------- 1 | -- when a socket is being closed, while another coroutine 2 | -- is reading on it, the `select` call does not return an event on that 3 | -- socket. Hence the loop keeps running until the watch-dog kicks-in, reads 4 | -- on the socket, and that returns the final "closed" error to the reading 5 | -- coroutine. 6 | -- 7 | -- when a socket is closed, any read/write operation should return immediately 8 | -- with a "closed" error. 9 | 10 | local copas = require "copas" 11 | local socket = require "socket" 12 | 13 | copas.loop(function() 14 | 15 | local client_socket 16 | local close_time 17 | local send_end_time 18 | local receive_end_time 19 | 20 | print "------------- starting close test ---------------" 21 | 22 | local function check_exit() 23 | if receive_end_time and send_end_time then 24 | -- both set, so we're done 25 | print "success!" 26 | os.exit(0) 27 | end 28 | end 29 | 30 | 31 | do -- set up a server that accepts but doesn't read or write anything 32 | local server = socket.bind("localhost", 20000) 33 | 34 | copas.addserver(server, copas.handler(function(conn_skt) 35 | -- client connected, we're not doing anything, let the client 36 | -- wait in the read/write queues 37 | copas.pause(2) 38 | -- now we're closing the connecting_socket 39 | close_time = copas.gettime() 40 | print("closing client socket now, client receive and send operation should immediately error out now") 41 | client_socket:close() 42 | 43 | copas.pause(10) 44 | conn_skt:close() 45 | copas.removeserver(server) 46 | print "timeout, test failed" 47 | os.exit(1) 48 | end)) 49 | end 50 | 51 | 52 | do -- create a client that connect to the server 53 | client_socket = copas.wrap(socket.connect("localhost", 20000)) 54 | 55 | copas.addthread(function() 56 | local data, err = client_socket:receive(1) 57 | print("receive result: ", tostring(data), tostring(err)) 58 | receive_end_time = copas.gettime() 59 | print("receive took: ", receive_end_time - close_time) 60 | check_exit() 61 | end) 62 | 63 | copas.addthread(function() 64 | local ok, err = true, nil 65 | while ok do -- loop to fill any buffers, until we get stuck 66 | ok, err = client_socket:send(("hello world"):rep(100)) 67 | end 68 | print("send result: ", tostring(ok), tostring(err)) 69 | send_end_time = copas.gettime() 70 | print("send took: ", send_end_time - close_time) 71 | check_exit() 72 | end) 73 | end 74 | 75 | end) 76 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # $Id: Makefile,v 1.3 2007/10/29 22:50:16 carregal Exp $ 2 | 3 | DESTDIR ?= 4 | 5 | # Default prefix 6 | PREFIX ?= /usr/local 7 | 8 | # System's lua directory (where Lua libraries are installed) 9 | LUA_DIR ?= $(PREFIX)/share/lua/5.1 10 | 11 | DELIM=-e "print(([[=]]):rep(70))" 12 | PKGPATH=-e "package.path='src/?.lua;'..package.path" 13 | 14 | # Lua interpreter 15 | LUA=lua 16 | 17 | .PHONY: certs 18 | 19 | install: 20 | mkdir -p $(DESTDIR)$(LUA_DIR)/copas 21 | cp src/copas.lua $(DESTDIR)$(LUA_DIR)/copas.lua 22 | cp src/copas/ftp.lua $(DESTDIR)$(LUA_DIR)/copas/ftp.lua 23 | cp src/copas/smtp.lua $(DESTDIR)$(LUA_DIR)/copas/smtp.lua 24 | cp src/copas/http.lua $(DESTDIR)$(LUA_DIR)/copas/http.lua 25 | cp src/copas/timer.lua $(DESTDIR)$(LUA_DIR)/copas/timer.lua 26 | cp src/copas/lock.lua $(DESTDIR)$(LUA_DIR)/copas/lock.lua 27 | cp src/copas/semaphore.lua $(DESTDIR)$(LUA_DIR)/copas/semaphore.lua 28 | cp src/copas/queue.lua $(DESTDIR)$(LUA_DIR)/copas/queue.lua 29 | 30 | tests/certs/clientA.pem: 31 | cd ./tests/certs && \ 32 | ./rootA.sh && \ 33 | ./rootB.sh && \ 34 | ./serverA.sh && \ 35 | ./serverB.sh && \ 36 | ./clientA.sh && \ 37 | ./clientB.sh && \ 38 | cd ../.. 39 | 40 | certs: tests/certs/clientA.pem 41 | 42 | test: certs 43 | $(LUA) $(DELIM) $(PKGPATH) tests/close.lua 44 | $(LUA) $(DELIM) $(PKGPATH) tests/connecttwice.lua 45 | $(LUA) $(DELIM) $(PKGPATH) tests/errhandlers.lua 46 | $(LUA) $(DELIM) $(PKGPATH) tests/exit.lua 47 | $(LUA) $(DELIM) $(PKGPATH) tests/exittest.lua 48 | $(LUA) $(DELIM) $(PKGPATH) tests/http-timeout.lua 49 | $(LUA) $(DELIM) $(PKGPATH) tests/httpredirect.lua 50 | $(LUA) $(DELIM) $(PKGPATH) tests/largetransfer.lua 51 | $(LUA) $(DELIM) $(PKGPATH) tests/lock.lua 52 | $(LUA) $(DELIM) $(PKGPATH) tests/loop_starter.lua 53 | $(LUA) $(DELIM) $(PKGPATH) tests/no_luasocket.lua 54 | $(LUA) $(DELIM) $(PKGPATH) tests/pause.lua 55 | $(LUA) $(DELIM) $(PKGPATH) tests/queue.lua 56 | $(LUA) $(DELIM) $(PKGPATH) tests/removeserver.lua 57 | $(LUA) $(DELIM) $(PKGPATH) tests/removethread.lua 58 | $(LUA) $(DELIM) $(PKGPATH) tests/request.lua 'http://www.google.com' 59 | $(LUA) $(DELIM) $(PKGPATH) tests/request.lua 'https://www.google.nl' true 60 | $(LUA) $(DELIM) $(PKGPATH) tests/semaphore.lua 61 | $(LUA) $(DELIM) $(PKGPATH) tests/starve.lua 62 | $(LUA) $(DELIM) $(PKGPATH) tests/tcptimeout.lua 63 | $(LUA) $(DELIM) $(PKGPATH) tests/timer.lua 64 | $(LUA) $(DELIM) $(PKGPATH) tests/timeout_errors.lua 65 | $(LUA) $(DELIM) $(PKGPATH) tests/tls-sni.lua 66 | $(LUA) $(DELIM) $(PKGPATH) tests/udptimeout.lua 67 | $(LUA) $(DELIM) 68 | 69 | coverage: 70 | $(RM) luacov.stats.out 71 | $(MAKE) test LUA="$(LUA) -lluacov" 72 | luacov 73 | 74 | clean: 75 | $(RM) luacov.stats.out luacov.report.out 76 | $(RM) tests/certs/*.pem tests/certs/*.srl 77 | -------------------------------------------------------------------------------- /tests/starve.lua: -------------------------------------------------------------------------------- 1 | -- tests looping 100% in receive/send 2 | -- Should not prevent other threads from running 3 | -- 4 | -- Test should; 5 | -- * sleep incremental, not on absolute time so it slowly diverges if the timer 6 | -- thread is being starved 7 | -- * seconds printed and elapsed should stay very close 8 | 9 | local copas = require 'copas' 10 | local socket = require 'socket' 11 | 12 | --copas.debug.start() 13 | 14 | local body = ("A"):rep(1024*1024*50) -- 50 mb string 15 | local done = 0 16 | 17 | local function runtest() 18 | local s1 = socket.bind('*', 49500) 19 | copas.addserver(s1, copas.handler(function(skt) 20 | copas.setsocketname("Server 49500", skt) 21 | copas.setthreadname("Server 49500") 22 | print "Server 49500 accepted incoming connection" 23 | local end_time = copas.gettime() + 30 -- we run for 30 seconds 24 | while end_time > copas.gettime() do 25 | local res, err, _ = skt:receive(1) -- single byte from 50mb chunks 26 | if res == nil and err ~= "timeout" then 27 | print("Server 49500 returned: " .. err) 28 | os.exit(1) 29 | end 30 | end 31 | done = done + 1 32 | print("Server reading port 49500... Done!") 33 | skt:close() 34 | copas.removeserver(s1) 35 | end)) 36 | 37 | copas.addnamedthread("Client 49500", function() 38 | local skt = socket.tcp() 39 | skt = copas.wrap(skt) 40 | copas.setsocketname("Client 49500", skt) 41 | skt:connect("localhost", 49500) 42 | local last_byte_sent, err, complete 43 | while not complete do 44 | repeat 45 | last_byte_sent, err = skt:send(body, last_byte_sent or 1, -1) 46 | if err == "closed" then 47 | -- server closed connection, so exit, test is finished 48 | complete = true 49 | break 50 | end 51 | if last_byte_sent == nil and err ~= "timeout" then 52 | print("client 49500 returned: " .. err) 53 | os.exit(1) 54 | end 55 | until last_byte_sent == nil or last_byte_sent == #body 56 | end 57 | print("Client writing port 49500... Done!") 58 | skt:close() 59 | done = done + 1 60 | end) 61 | 62 | copas.addnamedthread("test timeout thread", function() 63 | local i = 0 64 | local start = copas.gettime() 65 | while done ~= 2 do 66 | copas.pause(1) -- delta sleep, so it slowly diverges if starved 67 | i = i + 1 68 | local time_passed = copas.gettime()-start 69 | print("slept "..i.." seconds, time passed: ".. time_passed.." seconds") 70 | if math.abs(i - time_passed) > 2 then 71 | print("timer diverged by more than 2 seconds: failed!") 72 | os.exit(1) 73 | end 74 | if i > 60 then 75 | print"timeout" 76 | os.exit(1) 77 | end 78 | end 79 | print "success!" 80 | end) 81 | 82 | print("starting loop") 83 | copas.loop() 84 | print("Loop done") 85 | end 86 | 87 | runtest() 88 | -------------------------------------------------------------------------------- /src/copas/ftp.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------- 2 | -- identical to the socket.ftp module except that it uses 3 | -- async wrapped Copas sockets 4 | 5 | local copas = require("copas") 6 | local socket = require("socket") 7 | local ftp = require("socket.ftp") 8 | local ltn12 = require("ltn12") 9 | local url = require("socket.url") 10 | 11 | 12 | local create = function() return copas.wrap(socket.tcp()) end 13 | local forwards = { -- setting these will be forwarded to the original smtp module 14 | PORT = true, 15 | TIMEOUT = true, 16 | PASSWORD = true, 17 | USER = true 18 | } 19 | 20 | copas.ftp = setmetatable({}, { 21 | -- use original module as metatable, to lookup constants like socket.TIMEOUT, etc. 22 | __index = ftp, 23 | -- Setting constants is forwarded to the luasocket.ftp module. 24 | __newindex = function(self, key, value) 25 | if forwards[key] then ftp[key] = value return end 26 | return rawset(self, key, value) 27 | end, 28 | }) 29 | local _M = copas.ftp 30 | 31 | ---[[ copy of Luasocket stuff here untile PR #133 is accepted 32 | -- a copy of the version in LuaSockets' ftp.lua 33 | -- no 'create' can be passed in the string form, hence a local copy here 34 | local default = { 35 | path = "/", 36 | scheme = "ftp" 37 | } 38 | 39 | -- a copy of the version in LuaSockets' ftp.lua 40 | -- no 'create' can be passed in the string form, hence a local copy here 41 | local function parse(u) 42 | local t = socket.try(url.parse(u, default)) 43 | socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") 44 | socket.try(t.host, "missing hostname") 45 | local pat = "^type=(.)$" 46 | if t.params then 47 | t.type = socket.skip(2, string.find(t.params, pat)) 48 | socket.try(t.type == "a" or t.type == "i", 49 | "invalid type '" .. t.type .. "'") 50 | end 51 | return t 52 | end 53 | 54 | -- parses a simple form into the advanced form 55 | -- if `body` is provided, a PUT, otherwise a GET. 56 | -- If GET, then a field `target` is added to store the results 57 | _M.parseRequest = function(u, body) 58 | local t = parse(u) 59 | if body then 60 | t.source = ltn12.source.string(body) 61 | else 62 | t.target = {} 63 | t.sink = ltn12.sink.table(t.target) 64 | end 65 | end 66 | --]] 67 | 68 | _M.put = socket.protect(function(putt, body) 69 | if type(putt) == "string" then 70 | putt = _M.parseRequest(putt, body) 71 | _M.put(putt) 72 | return table.concat(putt.target) 73 | else 74 | putt.create = putt.create or create 75 | return ftp.put(putt) 76 | end 77 | end) 78 | 79 | _M.get = socket.protect(function(gett) 80 | if type(gett) == "string" then 81 | gett = _M.parseRequest(gett) 82 | _M.get(gett) 83 | return table.concat(gett.target) 84 | else 85 | gett.create = gett.create or create 86 | return ftp.get(gett) 87 | end 88 | end) 89 | 90 | _M.command = function(cmdt) 91 | cmdt.create = cmdt.create or create 92 | return ftp.command(cmdt) 93 | end 94 | 95 | return _M 96 | -------------------------------------------------------------------------------- /tests/udptimeout.lua: -------------------------------------------------------------------------------- 1 | -- Tests Copas socket timeouts 2 | -- 3 | -- Run the test file, it should exit successfully without hanging. 4 | 5 | -- make sure we are pointing to the local copas first 6 | package.path = string.format("../src/?.lua;%s", package.path) 7 | 8 | 9 | local copas = require("copas") 10 | local socket = require("socket") 11 | 12 | -- hack; no way to kill copas.loop from thread 13 | local function error(err) 14 | print(debug.traceback(err, 2)) 15 | os.exit(-1) 16 | end 17 | local function assert(truthy, err) 18 | if not truthy then 19 | print(debug.traceback(err, 2)) 20 | os.exit(-1) 21 | end 22 | end 23 | 24 | -- udp echo server for testing against, returns `ip, port` to connect to 25 | -- send `quit\n` to cause server to disconnect client 26 | -- stops listen server after provided number of echos 27 | local function singleuseechoserver(die_after) 28 | die_after = die_after or 1 29 | local server = socket.udp() 30 | server:setsockname("127.0.0.1", 0) -- "localhost" fails because of IPv6 error 31 | local ip, port = server:getsockname() 32 | 33 | copas.addthread(function() 34 | local skt = copas.wrap(server) 35 | while die_after > 0 do 36 | local data, ip, port = skt:receivefrom() 37 | if not data or data == "quit" then 38 | break 39 | end 40 | skt:sendto(data, ip, port) 41 | die_after = die_after - 1 42 | end 43 | end) 44 | 45 | return ip, port 46 | end 47 | 48 | local tests = {} 49 | 50 | function tests.receive_timeout() 51 | local ip, port = singleuseechoserver(1) 52 | 53 | copas.addthread(function() 54 | local client = socket.udp() 55 | client = copas.wrap(client) 56 | client:settimeout(0.01) 57 | local status, err = client:setpeername(ip, port) 58 | assert(status, "failed to connect: "..tostring(err)) 59 | 60 | client:send("foo") 61 | local data, err = client:receive() 62 | assert(data, "failed to recieve: "..tostring(err)) 63 | assert(data == "foo", "recieved wrong echo: "..tostring(data)) 64 | 65 | local data, err = client:receive() 66 | assert(data == nil, "somehow recieved echo without sending") 67 | assert(err == "timeout", "failed with non-timeout error") 68 | 69 | client:close() 70 | end) 71 | 72 | copas.loop() 73 | end 74 | 75 | function tests.receivefrom_timeout() 76 | local ip, port = singleuseechoserver(1) 77 | 78 | copas.addthread(function() 79 | local client = socket.udp() 80 | client = copas.wrap(client) 81 | client:settimeout(0.01) 82 | 83 | client:sendto("foo", ip, port) 84 | local data, err = client:receivefrom() 85 | assert(data, "failed to recieve: "..tostring(err)) 86 | assert(data == "foo", "recieved wrong echo: "..tostring(data)) 87 | 88 | local data, err = client:receivefrom() 89 | assert(data == nil, "somehow recieved echo without sending") 90 | assert(err == "timeout", "failed with non-timeout error") 91 | 92 | client:close() 93 | end) 94 | 95 | copas.loop() 96 | end 97 | 98 | -- test "framework" 99 | for name, test in pairs(tests) do 100 | print("testing: "..tostring(name)) 101 | local status, err = pcall(test) 102 | if not status then 103 | error(err) 104 | end 105 | end 106 | 107 | print("[✓] all tests completed successuly") 108 | -------------------------------------------------------------------------------- /tests/tls-sni.lua: -------------------------------------------------------------------------------- 1 | -- Tests Copas with a simple Echo server 2 | -- 3 | -- Run the test file and the connect to the server using telnet on the used port. 4 | -- The server should be able to echo any input, to stop the test just send the command "quit" 5 | 6 | local port = 20000 7 | local copas = require("copas") 8 | local socket = require("socket") 9 | local ssl = require("ssl") 10 | local server 11 | 12 | if _VERSION=="Lua 5.1" and not jit then -- obsolete: only for Lua 5.1 compatibility 13 | pcall = require("coxpcall").pcall -- luacheck: ignore 14 | end 15 | 16 | local server_params = { 17 | wrap = { 18 | mode = "server", 19 | protocol = "any", 20 | key = "tests/certs/serverAkey.pem", 21 | certificate = "tests/certs/serverA.pem", 22 | cafile = "tests/certs/rootA.pem", 23 | verify = {"peer", "fail_if_no_peer_cert"}, 24 | options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"}, 25 | }, 26 | sni = { 27 | strict = true, -- only allow connection 'myhost.com' 28 | names = {} 29 | } 30 | } 31 | server_params.sni.names["myhost.com"] = ssl.newcontext(server_params.wrap) 32 | 33 | local client_params = { 34 | wrap = { 35 | mode = "client", 36 | protocol = "any", 37 | key = "tests/certs/clientAkey.pem", 38 | certificate = "tests/certs/clientA.pem", 39 | cafile = "tests/certs/rootA.pem", 40 | verify = {"peer", "fail_if_no_peer_cert"}, 41 | options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"}, 42 | }, 43 | sni = { 44 | names = "" -- will be added in test below 45 | } 46 | } 47 | 48 | local function echoHandler(skt) 49 | while true do 50 | local data, err = skt:receive() 51 | if not data then 52 | if err ~= "closed" then 53 | return error("client connection error: "..tostring(err)) 54 | else 55 | return -- client closed the connection 56 | end 57 | 58 | elseif data == "quit" then 59 | return -- close this client connection 60 | 61 | elseif data == "exit" then 62 | copas.removeserver(server) 63 | return -- close this client connection, after stopping the server 64 | 65 | end 66 | skt:send(data.."\n") 67 | end 68 | end 69 | 70 | server = assert(socket.bind("*", port)) 71 | copas.addserver(server, copas.handler(echoHandler, server_params)) 72 | 73 | copas.addthread(function() 74 | copas.pause(0.5) -- allow server socket to be ready 75 | 76 | ---------------------- 77 | -- Tests start here -- 78 | ---------------------- 79 | 80 | -- try with a bad SNI (non matching) 81 | client_params.sni.names = "badhost.com" 82 | local skt = copas.wrap(socket.tcp(), client_params) 83 | local _, err = pcall(skt.connect, skt, "localhost", port) 84 | if not tostring(err):match("TLS/SSL handshake failed:") then 85 | print "expected handshake to fail" 86 | os.exit(1) 87 | end 88 | 89 | 90 | -- try again with a proper SNI (matching) 91 | client_params.sni.names = "myhost.com" 92 | local skt = copas.wrap(socket.tcp(), client_params) 93 | local success, ok = pcall(skt.connect, skt, "localhost", port) 94 | if not (success and ok) then 95 | print("expected connection to be completed", success, ok) 96 | os.exit(1) 97 | end 98 | 99 | assert(skt:send("hello world\n")) 100 | assert(skt:receive() == "hello world") 101 | print "succesfully completed test" 102 | 103 | -- send exit signal to server 104 | skt:send("exit\n") 105 | end) 106 | 107 | -- no ugly errors please, comment out when debugging 108 | copas.setErrorHandler(function() end, true) 109 | 110 | copas.loop() 111 | -------------------------------------------------------------------------------- /tests/lock.lua: -------------------------------------------------------------------------------- 1 | -- make sure we are pointing to the local copas first 2 | package.path = string.format("../src/?.lua;%s", package.path) 3 | 4 | 5 | 6 | local copas = require "copas" 7 | local Lock = copas.lock 8 | local gettime = copas.gettime 9 | 10 | local test_complete = false 11 | copas.loop(function() 12 | 13 | local lock1 = Lock.new(nil, true) -- not re-entrant 14 | assert(lock1:get()) 15 | local s = gettime() 16 | local _, err = lock1:get(1) 17 | local duration = gettime() - s 18 | assert(err == "timeout", "got errror: "..tostring(err)) 19 | assert(duration > 1 and duration < 1.2, string.format("expected timeout of 1 second, but took: %f",duration)) 20 | 21 | -- let go and reacquire 22 | assert(lock1:release()) 23 | local _, err = lock1:release() 24 | assert(err == "cannot release a lock not owned", "got error: "..tostring(err)) 25 | 26 | assert(lock1:get()) 27 | lock1:destroy() 28 | local _, err = lock1:release() 29 | assert(err == "destroyed", "got errror: "..tostring(err)) 30 | 31 | 32 | -- let's scale, go grab a lock 33 | lock1 = assert(Lock.new(10)) 34 | assert(lock1:get()) 35 | 36 | local success_count = 0 37 | local timeout_count = 0 38 | local destroyed_count = 0 39 | -- now add another bunch of threads for the same lock 40 | local size = 750 -- must be multiple of 3 !! 41 | print("creating "..size.." threads hitting the lock...", gettime()) 42 | local tracker = {} 43 | for i = 1, size do 44 | tracker[i] = true 45 | copas.addthread(function() 46 | local timeout 47 | if i > (size*2)/3 then 48 | timeout = 60 -- the ones to hit "destroyed" 49 | elseif i > size/3 and i <= (size*2)/3 then 50 | timeout = 2 -- the ones to hit "timeout" 51 | else 52 | timeout = 1 -- the ones to succeed 53 | end 54 | --print(i, "waiting...") 55 | local ok, err = lock1:get(timeout) 56 | if ok then 57 | --print(i, "got it!") 58 | success_count = success_count + 1 59 | if i == size/3 then 60 | copas.pause(3) -- keep it long enough for the next 500 to timeout 61 | --print(i, "releasing ") 62 | assert(lock1:release()) -- by now the 2nd 500 timed out 63 | --print(i, "destroying ") 64 | assert(lock1:destroy()) -- make the last 500 fail on "destroyed" 65 | else 66 | --print(i, "releasing ") 67 | assert(lock1:release()) 68 | end 69 | tracker[i] = nil 70 | 71 | elseif err == "timeout" then 72 | --print(i, "timed out!") 73 | timeout_count = timeout_count + 1 74 | --if i == (size*2)/3 then 75 | -- copas.pause(2) -- to ensure thread 500 finished its sleep above 76 | --end 77 | tracker[i] = nil 78 | 79 | elseif err == "destroyed" then 80 | --print(i, "destroyed!") 81 | destroyed_count = destroyed_count + 1 82 | tracker[i] = nil 83 | 84 | else 85 | tracker[i] = nil 86 | error("didn't expect error: '"..tostring(err).."' thread "..i) 87 | end 88 | 89 | end) -- added thread function 90 | end -- for loop 91 | print("releasing "..size.." threads...", gettime()) 92 | assert(lock1:release()) 93 | print("waiting to finish...") 94 | while next(tracker) do copas.pause(0.1) end 95 | -- check results 96 | print("success: ", success_count) 97 | print("timeout: ", timeout_count) 98 | print("destroyed: ", destroyed_count) 99 | assert(success_count == size/3) 100 | assert(timeout_count == size/3) 101 | assert(destroyed_count == size/3) 102 | 103 | test_complete = true 104 | end) 105 | assert(test_complete, "test did not complete!") 106 | 107 | print("test success!") 108 | -------------------------------------------------------------------------------- /tests/httpredirect.lua: -------------------------------------------------------------------------------- 1 | -- test redirecting http <-> https combinations 2 | 3 | local copas = require("copas") 4 | local http = copas.http 5 | local ltn12 = require("ltn12") 6 | local dump_all_headers = false 7 | local redirect 8 | local socket = require "socket" 9 | 10 | 11 | local function doreq(url) 12 | local reqt = { 13 | url = url, 14 | redirect = redirect, --> allows https-> http redirect 15 | target = {}, 16 | } 17 | reqt.sink = ltn12.sink.table(reqt.target) 18 | 19 | local result, code, headers, status = http.request(reqt) 20 | print(string.rep("-",70)) 21 | print("Fetching:",url,"==>",code, status) 22 | if dump_all_headers then 23 | if headers then 24 | print("HEADERS") 25 | for k,v in pairs(headers) do print("",k,v) end 26 | end 27 | else 28 | print(" at:", (headers or {}).location) 29 | end 30 | --print(string.rep("=",70)) 31 | return result, code, headers, status 32 | end 33 | 34 | local done = false 35 | 36 | copas.addthread(function() 37 | local _, code, headers = doreq("https://goo.gl/UBCUc5") -- https --> https redirect 38 | assert(tonumber(code)==200, "unexpected status code: "..tostring(code)) 39 | assert(headers.location == "https://github.com/lunarmodules/luasec", "unexpected location header: "..tostring(headers.location)) 40 | print("https -> https redirect OK!") 41 | copas.addthread(function() 42 | local _, code, headers = doreq("http://goo.gl/UBCUc5") -- http --> https redirect 43 | assert(tonumber(code)==200, "unexpected status code: "..tostring(code)) 44 | assert(headers.location == "https://github.com/lunarmodules/luasec", "unexpected location header: "..tostring(headers.location)) 45 | print("http -> https redirect OK!") 46 | copas.addthread(function() 47 | --local result, code, headers, status = doreq("http://goo.gl/tBfqNu") -- http --> http redirect 48 | -- the above no longer works for testing, since Google auto-inserts a 49 | -- initial redirect to the same url, over https, hence the final 50 | -- redirect is a downgrade which then errors out 51 | -- so we set up a local http-server to deal with this 52 | local server = assert(socket.bind("127.0.0.1", 9876)) 53 | local crlf = string.char(13)..string.char(10) 54 | copas.addserver(server, function(skt) 55 | skt = copas.wrap(skt) 56 | assert(skt:receive()) 57 | local response = 58 | "HTTP/1.1 302 Found" .. crlf .. 59 | "Location: http://www.httpvshttps.com" .. crlf .. crlf 60 | assert(skt:send(response)) 61 | skt:close() 62 | end) 63 | -- execute test request 64 | local _, code, headers = doreq("http://localhost:9876/") -- http --> http redirect 65 | copas.removeserver(server) -- immediately close server again 66 | assert(tonumber(code)==200, "unexpected status code: "..tostring(code)) 67 | assert(headers.location == "http://www.httpvshttps.com") 68 | print("http -> http redirect OK!") 69 | copas.addthread(function() 70 | local result, code = doreq("https://bit.ly/3vmhXhW") -- https --> http security test case 71 | assert(result==nil and code == "Unallowed insecure redirect https to http") 72 | print("https -> http redirect, while not allowed OK!:", code) 73 | copas.addthread(function() 74 | redirect = "all" 75 | local _, code, headers = doreq("https://bit.ly/3vmhXhW") -- https --> http security test case 76 | assert(tonumber(code)==200, "unexpected status code: "..tostring(code)) 77 | assert(headers.location == "http://www.httpvshttps.com/") 78 | print("https -> http redirect, while allowed OK!") 79 | done = true 80 | end) 81 | end) 82 | end) 83 | end) 84 | end) 85 | 86 | copas.loop() 87 | 88 | if not done then 89 | print("Some checks above failed") 90 | os.exit(1) 91 | end 92 | -------------------------------------------------------------------------------- /tests/exittest.lua: -------------------------------------------------------------------------------- 1 | print([[ 2 | Testing to automatically exit the copas loop when nothing remains to be done. 3 | So none of the tests below should hang, as that means it did not exit... 4 | ============================================================================= 5 | 6 | ]]) 7 | 8 | local copas = require("copas") 9 | local testran 10 | 11 | print("1) Testing exiting when a task finishes before the loop even starts") 12 | copas.addthread(function() 13 | print("","1 running...") 14 | testran = 1 15 | end) 16 | copas.loop() 17 | assert(testran == 1, "Test 1 was not executed!") 18 | print("1) success") 19 | 20 | print("2) Testing exiting when a task finishes within the loop") 21 | copas.addthread(function() 22 | copas.pause(0.1) -- wait until loop is running 23 | copas.pause(0.1) -- wait again to make sure its not the initial step in the loop 24 | print("","2 running...") 25 | testran = 2 26 | end) 27 | copas.loop() 28 | assert(testran == 2, "Test 2 was not executed!") 29 | print("2) success") 30 | 31 | print("3) Testing exiting when a task fails before the loop even starts") 32 | copas.addthread(function() 33 | print("","3 running...") 34 | testran = 3 35 | error("error on purpose") 36 | end) 37 | copas.loop() 38 | assert(testran == 3, "Test 3 was not executed!") 39 | print("3) success") 40 | 41 | print("4) Testing exiting when a task fails in the loop") 42 | copas.addthread(function() 43 | copas.pause(0.1) -- wait until loop is running 44 | copas.pause(0.1) -- wait again to make sure its not the initial step in the loop 45 | print("","4 running...") 46 | testran = 4 47 | error("error on purpose") 48 | end) 49 | copas.loop() 50 | assert(testran == 4, "Test 4 was not executed!") 51 | print("4) success") 52 | 53 | print("5) Testing exiting when a task permanently sleeps before the loop") 54 | copas.addthread(function() 55 | print("","5 running...") 56 | testran = 5 57 | copas.pauseforever() -- sleep until explicitly woken up 58 | end) 59 | copas.loop() 60 | assert(testran == 5, "Test 5 was not executed!") 61 | print("5) success") 62 | 63 | print("6) Testing exiting when a task permanently sleeps in the loop") 64 | copas.addthread(function() 65 | copas.pause(0.1) -- wait until loop is running 66 | copas.pause(0.1) -- wait again to make sure its not the initial step in the loop 67 | print("","6 running...") 68 | testran = 6 69 | copas.pauseforever() -- sleep until explicitly woken up 70 | end) 71 | copas.loop() 72 | assert(testran == 6, "Test 6 was not executed!") 73 | print("6) success") 74 | 75 | print("7) Testing exiting releasing the exitsemaphore (implicit, no call to copas.exit)") 76 | copas.addthread(function() 77 | print("","7 running...") 78 | copas.addthread(function() 79 | copas.waitforexit() 80 | testran = 7 81 | end) 82 | end) 83 | copas.loop() 84 | assert(testran == 7, "Test 7 was not executed!") 85 | print("7) success") 86 | 87 | print("8) Testing schduling new tasks while exiting (explicit exit by calling copas.exit)") 88 | testran = 0 89 | copas.addthread(function() 90 | print("","8 running...") 91 | copas.addthread(function() 92 | while true do 93 | copas.pause(0.1) 94 | testran = testran + 1 95 | print("count...") 96 | if testran == 3 then -- testran == 3 97 | print("initiating exit...") 98 | copas.exit() 99 | break 100 | end 101 | end 102 | end) 103 | copas.addthread(function() 104 | copas.waitforexit() 105 | print("exit signal received...") 106 | testran = testran + 1 -- testran == 4 107 | copas.addthread(function() 108 | print("running new task from exit handler...") 109 | copas.pause(1) 110 | testran = testran + 1 -- testran == 5 111 | print("new task from exit handler done!") 112 | end) 113 | end) 114 | end) 115 | copas.loop() 116 | assert(testran == 5, "Test 8 was not executed!") 117 | print("8) success") 118 | -------------------------------------------------------------------------------- /src/copas/timer.lua: -------------------------------------------------------------------------------- 1 | local copas = require("copas") 2 | 3 | local xpcall = xpcall 4 | local coroutine_running = coroutine.running 5 | 6 | if _VERSION=="Lua 5.1" and not jit then -- obsolete: only for Lua 5.1 compatibility 7 | xpcall = require("coxpcall").xpcall 8 | coroutine_running = require("coxpcall").running 9 | end 10 | 11 | 12 | local timer = {} 13 | timer.__index = timer 14 | 15 | 16 | local new_name do 17 | local count = 0 18 | 19 | function new_name() 20 | count = count + 1 21 | return "copas_timer_" .. count 22 | end 23 | end 24 | 25 | 26 | do 27 | local function expire_func(self, initial_delay) 28 | if self.errorhandler then 29 | copas.seterrorhandler(self.errorhandler) 30 | end 31 | copas.pause(initial_delay) 32 | while true do 33 | if not self.cancelled then 34 | if not self.recurring then 35 | -- non-recurring timer 36 | self.cancelled = true 37 | self.co = nil 38 | 39 | self:callback(self.params) 40 | return 41 | 42 | else 43 | -- recurring timer 44 | self:callback(self.params) 45 | end 46 | end 47 | 48 | if self.cancelled then 49 | -- clean up and exit the thread 50 | self.co = nil 51 | self.cancelled = true 52 | return 53 | end 54 | 55 | copas.pause(self.delay) 56 | end 57 | end 58 | 59 | 60 | --- Arms the timer object. 61 | -- @param initial_delay (optional) the first delay to use, if not provided uses the timer delay 62 | -- @return timer object, nil+error, or throws an error on bad input 63 | function timer:arm(initial_delay) 64 | assert(initial_delay == nil or initial_delay >= 0, "delay must be greater than or equal to 0") 65 | if self.co then 66 | return nil, "already armed" 67 | end 68 | 69 | self.cancelled = false 70 | self.co = copas.addnamedthread(self.name, expire_func, self, initial_delay or self.delay) 71 | return self 72 | end 73 | end 74 | 75 | 76 | 77 | --- Cancels a running timer. 78 | -- @return timer object, or nil+error 79 | function timer:cancel() 80 | if not self.co then 81 | return nil, "not armed" 82 | end 83 | 84 | if self.cancelled then 85 | return nil, "already cancelled" 86 | end 87 | 88 | self.cancelled = true 89 | copas.wakeup(self.co) -- resume asap 90 | copas.removethread(self.co) -- will immediately drop the thread upon resuming 91 | self.co = nil 92 | return self 93 | end 94 | 95 | 96 | do 97 | -- xpcall error handler that forwards to the copas errorhandler 98 | local ehandler = function(err_obj) 99 | return copas.geterrorhandler()(err_obj, coroutine_running(), nil) 100 | end 101 | 102 | 103 | --- Creates a new timer object. 104 | -- Note: the callback signature is: `function(timer_obj, params)`. 105 | -- @param opts (table) `opts.delay` timer delay in seconds, `opts.callback` function to execute, `opts.recurring` boolean 106 | -- `opts.params` (optional) this value will be passed to the timer callback, `opts.initial_delay` (optional) the first delay to use, defaults to `delay`. 107 | -- @return timer object, or throws an error on bad input 108 | function timer.new(opts) 109 | assert((opts.delay or -1) >= 0, "delay must be greater than or equal to 0") 110 | assert(type(opts.callback) == "function", "expected callback to be a function") 111 | 112 | local callback = function(timer_obj, params) 113 | xpcall(opts.callback, ehandler, timer_obj, params) 114 | end 115 | 116 | return setmetatable({ 117 | name = opts.name or new_name(), 118 | delay = opts.delay, 119 | callback = callback, 120 | recurring = not not opts.recurring, 121 | params = opts.params, 122 | cancelled = false, 123 | errorhandler = opts.errorhandler, 124 | }, timer):arm(opts.initial_delay) 125 | end 126 | end 127 | 128 | 129 | 130 | return timer 131 | -------------------------------------------------------------------------------- /docs/license.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Copas License 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | 16 |
Copas
17 |
Coroutine Oriented Portable Asynchronous Services for Lua
18 |
19 | 20 | 21 |
22 | 23 | 46 | 47 |
48 |

License

49 |

50 | Copas is free software: it can be used for both academic and 51 | commercial purposes at absolutely no cost.

52 |

53 | The spirit of the license is that you are free to use Copas for 54 | any purpose at no cost without having to ask us. The only 55 | requirement is that if you do use Copas, then you should give us 56 | credit by including the appropriate copyright notice somewhere in 57 | your product or its documentation.

58 |

59 | Copas was designed and implemented by André Carregal and 60 | Javier Guerra. The implementation is not derived from 61 | licensed software.

62 |

63 | 64 |

65 | 66 |
67 |

Copyright © 2005-2013 Kepler Project, 2015-2025 Thijs Schreijer.

68 |

69 | Permission is hereby granted, free of charge, to any person 70 | obtaining a copy of this software and associated documentation 71 | files (the "Software"), to deal in the Software without 72 | restriction, including without limitation the rights to use, copy, 73 | modify, merge, publish, distribute, sublicense, and/or sell copies 74 | of the Software, and to permit persons to whom the Software is 75 | furnished to do so, subject to the following conditions:

76 |

77 | The above copyright notice and this permission notice shall be 78 | included in all copies or substantial portions of the Software.

79 |

80 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 81 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 82 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 83 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 84 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 85 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 86 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 87 | SOFTWARE.

88 |
89 | 90 |
91 | 92 |
93 |

Valid XHTML 1.0!

94 |

$Id: license.html,v 1.17 2009/03/24 22:04:26 carregal Exp $

95 |
96 | 97 |
98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /docs/doc.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #47555c; 3 | font-size: 16px; 4 | font-family: "Open Sans", sans-serif; 5 | margin: 0; 6 | padding: 0; 7 | background: #eff4ff; 8 | } 9 | 10 | a:link { color: #008fee; } 11 | a:visited { color: #008fee; } 12 | a:hover { color: #22a7ff; } 13 | 14 | h1 { font-size:26px; } 15 | h2 { font-size:24px; } 16 | h3 { font-size:18px; } 17 | h4 { font-size:16px; } 18 | 19 | hr { 20 | height: 1px; 21 | background: #c1cce4; 22 | border: 0px; 23 | margin: 20px 0; 24 | } 25 | 26 | code { 27 | font-family: "Open Sans Mono", "Andale Mono", monospace; 28 | } 29 | 30 | tt { 31 | font-family: "Open Sans Mono", "Andale Mono", monospace; 32 | } 33 | 34 | body, td, th { 35 | } 36 | 37 | textarea, pre, tt { 38 | font-family: "Open Sans Mono", "Andale Mono", monospace; 39 | } 40 | 41 | img { 42 | border-width: 0px; 43 | } 44 | 45 | .example { 46 | background-color: #323744; 47 | color: white; 48 | font-size: 16px; 49 | padding: 16px 24px; 50 | border-radius: 2px; 51 | } 52 | 53 | div.header, div.footer { 54 | } 55 | 56 | #container { 57 | } 58 | 59 | #product { 60 | background-color: white; 61 | padding: 10px; 62 | height: 130px; 63 | border-bottom: solid #d3dbec 1px; 64 | } 65 | 66 | #product big { 67 | font-size: 42px; 68 | } 69 | #product strong { 70 | font-weight: normal; 71 | } 72 | 73 | #product_logo { 74 | float: right; 75 | } 76 | 77 | #product_name { 78 | padding-top: 15px; 79 | padding-left: 30px; 80 | font-size: 42px; 81 | font-weight: normal; 82 | } 83 | 84 | #product_description { 85 | padding-left: 30px; 86 | color: #757779; 87 | } 88 | 89 | #main { 90 | background: #eff4ff; 91 | margin: 0; 92 | } 93 | 94 | #navigation { 95 | width: 100%; 96 | background-color: rgb(44,62,103); 97 | padding: 10px; 98 | margin: 0; 99 | } 100 | 101 | #navigation h1 { 102 | display: none; 103 | } 104 | 105 | #navigation a:hover { 106 | text-decoration: underline; 107 | } 108 | 109 | #navigation ul li a { 110 | color: rgb(136, 208, 255); 111 | font-weight: bold; 112 | text-decoration: none; 113 | } 114 | 115 | #navigation ul li li a { 116 | color: rgb(136, 208, 255); 117 | font-weight: normal; 118 | text-decoration: none; 119 | } 120 | 121 | #navigation ul { 122 | display: inline; 123 | color: white; 124 | padding: 0px; 125 | padding-top: 10px; 126 | padding-bottom: 10px; 127 | } 128 | 129 | #navigation li { 130 | display: inline; 131 | list-style-type: none; 132 | padding-left: 5px; 133 | padding-right: 5px; 134 | } 135 | 136 | #navigation li { 137 | padding: 10px; 138 | padding: 10px; 139 | } 140 | 141 | #navigation li li { 142 | } 143 | 144 | #navigation li:hover a { 145 | color: rgb(166, 238, 255); 146 | } 147 | 148 | #content { 149 | padding: 20px; 150 | width: 800px; 151 | margin-left: auto; 152 | margin-right: auto; 153 | } 154 | 155 | #about { 156 | display: none; 157 | } 158 | 159 | dl.reference { 160 | background-color: white; 161 | padding: 20px; 162 | border: solid #d3dbec 1px; 163 | } 164 | 165 | dl.reference dt { 166 | padding: 5px; 167 | padding-top: 25px; 168 | color: #637bbc; 169 | } 170 | 171 | dl.reference dl dt { 172 | padding-top: 5px; 173 | color: #637383; 174 | } 175 | 176 | dl.reference dd { 177 | } 178 | 179 | @media print { 180 | body { 181 | font: 10pt "Times New Roman", "TimeNR", Times, serif; 182 | } 183 | a { 184 | font-weight:bold; color: #004080; text-decoration: underline; 185 | } 186 | #main { 187 | background-color: #ffffff; border-left: 0px; 188 | } 189 | #container { 190 | margin-left: 2%; margin-right: 2%; background-color: #ffffff; 191 | } 192 | #content { 193 | margin-left: 0px; padding: 1em; border-left: 0px; border-right: 0px; background-color: #ffffff; 194 | } 195 | #navigation { 196 | display: none; 197 | } 198 | #product_logo { 199 | display: none; 200 | } 201 | #about img { 202 | display: none; 203 | } 204 | .example { 205 | font-family: "Andale Mono", monospace; 206 | font-size: 8pt; 207 | page-break-inside: avoid; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /tests/largetransfer.lua: -------------------------------------------------------------------------------- 1 | -- tests large transmissions, sending and receiving 2 | -- uses `receive` and `receivepartial` 3 | -- Does send the same string twice simultaneously 4 | -- 5 | -- Test should; 6 | -- * show timer output, once per second, and actual time should be 1 second increments 7 | -- * both transmissions should take appr. equal time, then they we're nicely cooperative 8 | 9 | local copas = require 'copas' 10 | local socket = require 'socket' 11 | 12 | -- copas.debug.start() 13 | 14 | local body = ("A"):rep(1024*1024*50) -- 50 mb string 15 | local start = copas.gettime() 16 | local done = 0 17 | local sparams, cparams 18 | 19 | local function runtest() 20 | local s1 = socket.bind('*', 49500) 21 | copas.addserver(s1, copas.handler(function(skt) 22 | copas.setsocketname("Server 49500", skt) 23 | copas.setthreadname("Server 49500") 24 | --skt:settimeout(0) -- don't set, uses `receive` method 25 | local res, err, part = skt:receive('*a') 26 | res = res or part 27 | if res ~= body then print("Received doesn't match send") end 28 | print("Server reading port 49500... Done!", copas.gettime()-start, err, #res) 29 | copas.removeserver(s1) 30 | done = done + 1 31 | end, sparams)) 32 | 33 | local s2 = socket.bind('*', 49501) 34 | copas.addserver(s2, copas.handler(function(skt) 35 | skt:settimeout(0) -- set, uses the `receivepartial` method 36 | copas.setsocketname("Server 49501", skt) 37 | copas.setthreadname("Server 49501") 38 | local res, err, part = skt:receive('*a') 39 | res = res or part 40 | if res ~= body then print("Received doesn't match send") end 41 | print("Server reading port 49501... Done!", copas.gettime()-start, err, #res) 42 | copas.removeserver(s2) 43 | done = done + 1 44 | end, sparams)) 45 | 46 | copas.addnamedthread("Client 49500", function() 47 | local skt = socket.tcp() 48 | skt = copas.wrap(skt, cparams) 49 | copas.setsocketname("Client 49500", skt) 50 | skt:connect("localhost", 49500) 51 | local last_byte_sent, err 52 | repeat 53 | last_byte_sent, err = skt:send(body, last_byte_sent or 1, -1) 54 | until last_byte_sent == nil or last_byte_sent == #body 55 | print("Client writing port 49500... Done!", copas.gettime()-start, err, #body) 56 | -- we're not closing the socket, so the Copas GC-when-idle can kick-in to clean up 57 | skt = nil -- luacheck: ignore 58 | done = done + 1 59 | end) 60 | 61 | copas.addnamedthread("Client 49501", function() 62 | local skt = socket.tcp() 63 | skt = copas.wrap(skt, cparams) 64 | copas.setsocketname("Client 49501", skt) 65 | skt:connect("localhost", 49501) 66 | local last_byte_sent, err 67 | repeat 68 | last_byte_sent, err = skt:send(body, last_byte_sent or 1, -1) 69 | until last_byte_sent == nil or last_byte_sent == #body 70 | print("Client writing port 49501... Done!", copas.gettime()-start, err, #body) 71 | -- we're not closing the socket, so the Copas GC-when-idle can kick-in to clean up 72 | skt = nil -- luacheck: ignore 73 | done = done + 1 74 | end) 75 | 76 | copas.addnamedthread("test timeout thread", function() 77 | local i = 1 78 | while done ~= 4 do 79 | copas.pause(1) 80 | print(i, "seconds:", copas.gettime()-start) 81 | i = i + 1 82 | if i > 60 then 83 | print"timeout" 84 | os.exit(1) 85 | end 86 | end 87 | print "success!" 88 | end) 89 | 90 | print("starting loop") 91 | copas.loop() 92 | print("Loop done") 93 | end 94 | 95 | runtest() -- run test using regular connection (s/cparams == nil) 96 | 97 | -- set ssl parameters and do it again 98 | sparams = { 99 | mode = "server", 100 | protocol = "any", 101 | key = "tests/certs/serverAkey.pem", 102 | certificate = "tests/certs/serverA.pem", 103 | cafile = "tests/certs/rootA.pem", 104 | verify = {"peer", "fail_if_no_peer_cert"}, 105 | options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"}, 106 | } 107 | cparams = { 108 | mode = "client", 109 | protocol = "any", 110 | key = "tests/certs/clientAkey.pem", 111 | certificate = "tests/certs/clientA.pem", 112 | cafile = "tests/certs/rootA.pem", 113 | verify = {"peer", "fail_if_no_peer_cert"}, 114 | options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"}, 115 | } 116 | done = 0 117 | start = copas.gettime() 118 | runtest() 119 | -------------------------------------------------------------------------------- /tests/timer.lua: -------------------------------------------------------------------------------- 1 | -- make sure we are pointing to the local copas first 2 | package.path = string.format("../src/?.lua;%s", package.path) 3 | 4 | 5 | 6 | local copas = require "copas" 7 | local gettime = copas.gettime 8 | local timer = copas.timer 9 | 10 | local successes = 0 11 | 12 | copas.loop(function() 13 | 14 | local count_t1 = 0 15 | local t1 = timer.new({ 16 | delay = 0.5, 17 | recurring = true, 18 | params = "hello world", 19 | callback = function(timer_obj, params) 20 | -- let's ensure parameters get passed 21 | assert(params == "hello world", "expected: hello world") 22 | successes = successes + 1 -- 6 to come 23 | count_t1 = count_t1 + 1 24 | print(params .. " " .. count_t1) 25 | end, 26 | }) 27 | -- succes count = 6 28 | 29 | local t2 = timer.new({ 30 | delay = 0.2, -- we'll override this with 0.1 below 31 | recurring = false, 32 | params = { 33 | start_time = gettime() 34 | }, 35 | initial_delay = 0.1, -- initial delay, only 0.1 36 | callback = function(timer_obj, params) 37 | assert(gettime() - params.start_time < 0.11, "didn't honour initial delay, or recurred") 38 | print("this seems to go well, and should print only once") 39 | successes = successes + 1 -- 1 to come 40 | end, 41 | }) 42 | -- succes count = 7 43 | 44 | timer.new({ 45 | delay = 3.3, --> allows T1 to run 6 times 46 | callback = function(timer_obj, params) 47 | t1:cancel() 48 | local _, err = t2:cancel() 49 | assert(err == "not armed", "expected t2 to already be stopped") 50 | successes = successes + 1 -- 1 to come 51 | assert(count_t1 == 6, "expected t1 to run 6 times!") 52 | successes = successes + 1 -- 1 to come 53 | timer_obj:cancel() -- cancel myself 54 | end, 55 | }) 56 | -- succes count = 9 57 | 58 | timer.new({ 59 | delay = 0.1, 60 | recurring = true, 61 | callback = function(timer_obj, params) 62 | -- re-arm myself (recurring), should not be possible 63 | local ok, err = timer_obj:arm(1) 64 | assert(err == "already armed", "expected myself to be already armed") 65 | assert(ok == nil, "expected 'ok' to be nil") 66 | print("failed to re-arm a recurring timer, so that's ok") 67 | successes = successes + 1 -- 1 to come 68 | assert(timer_obj:cancel()) -- cancel myself 69 | end, 70 | }) 71 | -- succes count = 10 72 | 73 | local touched = false 74 | timer.new({ 75 | delay = 0.1, 76 | recurring = false, 77 | callback = function(timer_obj, params) 78 | if touched == false then 79 | -- re-arm myself (non-recurring), should be possible 80 | local ok, err = timer_obj:arm(3) 81 | assert(ok == timer_obj) 82 | assert(err == nil, "expected 'err' to be nil") 83 | touched = gettime() 84 | print("re-armed a non-recurring timer, so that's ok") 85 | successes = successes + 1 -- 1 to come 86 | else 87 | print("a re-armed non-recurring timer executed, so that's ok") 88 | successes = successes + 1 -- 1 to come 89 | local t = math.abs(gettime() - touched - 3) 90 | assert(t < 0.01, "expected a 3 second delay for the rearmed timer. Got: "..(gettime() - touched)) 91 | successes = successes + 1 -- 1 to come 92 | end 93 | end, 94 | }) 95 | -- succes count = 13 96 | 97 | local count = 0 98 | local params_in = {} 99 | -- timer shouldn't be cancelled if its handler errors 100 | timer.new({ 101 | name = "error-test", 102 | delay = 0.1, 103 | recurring = true, 104 | params = params_in, 105 | errorhandler = function(msg, co, skt) 106 | local errmsg = copas.gettraceback(msg, co, skt) 107 | assert(errmsg:find("error%-test"), "the threadname wasn't found") 108 | assert(errmsg:find("error 1!") or errmsg:find("error 2!"), "the error message wasn't found") 109 | --print(errmsg) 110 | successes = successes + 1 111 | end, 112 | callback = function(timer_obj, params) 113 | assert(params == params_in, "Params table wasn't passed along") 114 | count = count + 1 115 | if count == 2 then 116 | -- 2nd call, so we're done 117 | timer_obj:cancel() 118 | end 119 | error("error "..count.."!") 120 | end, 121 | }) 122 | -- succes count = 15 123 | 124 | end) 125 | 126 | assert(successes == 15, "number of successes didn't match! got: "..successes) 127 | print("test success!") 128 | -------------------------------------------------------------------------------- /tests/errhandlers.lua: -------------------------------------------------------------------------------- 1 | -- Tests Copas socket timeouts 2 | -- 3 | -- Run the test file, it should exit successfully without hanging. 4 | 5 | -- make sure we are pointing to the local copas first 6 | package.path = string.format("../src/?.lua;%s", package.path) 7 | 8 | 9 | 10 | --local platform = "unix" 11 | --if package.config:sub(1,1) == "\\" then 12 | -- platform = "windows" 13 | --elseif io.popen("uname", "r"):read("*a"):find("Darwin") then 14 | -- platform = "mac" 15 | --end 16 | --print("Testing platform: " .. platform) 17 | 18 | 19 | 20 | local copas = require("copas") 21 | 22 | 23 | 24 | local tests = {} 25 | 26 | if _VERSION ~= "Lua 5.1" then 27 | -- do not run these for Lua 5.1 since it has a different stacktrace 28 | 29 | tests.default_properly_formats_coro_errors = function() 30 | local old_print = print 31 | local msg 32 | print = function(errmsg) --luacheck: ignore 33 | msg = errmsg 34 | --old_print(msg) 35 | end 36 | 37 | copas.loop(function() 38 | local f = function() 39 | error("hi there!") 40 | end 41 | f() 42 | end) 43 | 44 | print = old_print --luacheck: ignore 45 | 46 | assert(msg:find("errhandlers%.lua:%d-: hi there! %(coroutine: copas_initializer, socket: nil%)"), "got:\n"..msg) 47 | assert(msg:find("stack traceback:.+errhandlers%.lua"), "got:\n"..msg) 48 | end 49 | 50 | 51 | tests.default_properly_formats_timerwheel_errors = function() 52 | local old_print = print 53 | local msg 54 | print = function(errmsg) --luacheck: ignore 55 | msg = errmsg 56 | --old_print(msg) 57 | end 58 | 59 | copas.loop(function() 60 | copas.timeout(0.01, function(co) 61 | local f = function() 62 | error("hi there!") 63 | end 64 | f() 65 | end) 66 | copas.pause(1) 67 | end) 68 | 69 | print = old_print --luacheck: ignore 70 | 71 | assert(msg:find("errhandlers%.lua:%d-: hi there! %(coroutine: copas_core_timer, socket: nil%)"), "got:\n"..msg) 72 | assert(msg:find("stack traceback:.+errhandlers%.lua"), "got:\n"..msg) 73 | end 74 | end 75 | 76 | 77 | tests.yielding_from_user_code_fails = function() 78 | local old_print = print 79 | local msg 80 | print = function(errmsg) --luacheck: ignore 81 | msg = errmsg 82 | --old_print(msg) 83 | end 84 | 85 | copas.loop(function() 86 | copas.pause(1) 87 | coroutine.yield() -- directly yield to Copas 88 | end) 89 | 90 | print = old_print --luacheck: ignore 91 | 92 | assert(msg:find("coroutine.yield was called without a resume first, user-code cannot yield to Copas", 1, true), "got:\n"..msg) 93 | end 94 | 95 | 96 | tests.handler_gets_called_if_set = function() 97 | local call_count = 0 98 | copas.loop(function() 99 | copas.setErrorHandler(function() call_count = call_count + 1 end) 100 | 101 | error("end of the world!") 102 | end) 103 | 104 | assert(call_count == 1, "expected callcount 1, got: " .. tostring(call_count)) 105 | end 106 | 107 | 108 | 109 | tests.default_handler_gets_called_if_set = function() 110 | local call_count = 0 111 | copas.setErrorHandler(function() call_count = call_count + 10 end, true) 112 | copas.loop(function() 113 | 114 | error("end of the world!") 115 | end) 116 | 117 | assert(call_count == 10, "expected callcount 10, got: " .. tostring(call_count)) 118 | end 119 | 120 | 121 | 122 | tests.default_handler_doesnt_get_called_if_overridden = function() 123 | local call_count = 0 124 | copas.setErrorHandler(function() call_count = call_count + 10 end, true) 125 | copas.loop(function() 126 | copas.setErrorHandler(function() call_count = call_count + 1 end) 127 | 128 | error("end of the world!") 129 | end) 130 | 131 | assert(call_count == 1, "expected callcount 1, got: " .. tostring(call_count)) 132 | end 133 | 134 | 135 | tests.timerwheel_callbacks_call_the_default_error_handler = function() 136 | local call_count = 0 137 | copas.setErrorHandler(function() call_count = call_count - 10 end, true) 138 | copas.loop(function() 139 | copas.timeout(0.01, function(co) error("hi there!") end) 140 | copas.pause(1) 141 | end) 142 | 143 | assert(call_count == -10, "expected callcount -10, got: " .. tostring(call_count)) 144 | end 145 | 146 | 147 | -- test "framework" 148 | for name, test in pairs(tests) do 149 | -- reload copas, to clear default err handlers 150 | package.loaded.copas = nil 151 | copas = require "copas" 152 | 153 | print("testing: "..tostring(name)) 154 | local status, err = pcall(test) 155 | if not status then 156 | error(err) 157 | end 158 | end 159 | 160 | print("[✓] all tests completed successuly") 161 | -------------------------------------------------------------------------------- /tests/semaphore.lua: -------------------------------------------------------------------------------- 1 | -- make sure we are pointing to the local copas first 2 | package.path = string.format("../src/?.lua;%s", package.path) 3 | 4 | 5 | local copas = require "copas" 6 | local now = copas.gettime 7 | local semaphore = copas.semaphore 8 | 9 | 10 | 11 | local test_complete = false 12 | copas.loop(function() 13 | 14 | local sema = semaphore.new(10, 5, 1) 15 | assert(sema:get_count() == 5) 16 | 17 | assert(sema:take(3)) 18 | assert(sema:get_count() == 2) 19 | 20 | local ok, _, err 21 | local start = now() 22 | _, err = sema:take(3, 0) -- 1 too many, immediate timeout 23 | assert(err == "timeout", "expected a timeout") 24 | assert(now() - start < 0.001, "expected it not to block with timeout = 0") 25 | 26 | start = now() 27 | _, err = sema:take(10, 0) -- way too many, immediate timeout 28 | assert(err == "timeout", "expected a timeout") 29 | assert(now() - start < 0.001, "expected it not to block with timeout = 0") 30 | 31 | start = now() 32 | _, err = sema:take(11) -- more than 'max'; "too many" error 33 | assert(err == "too many", "expected a 'too many' error") 34 | assert(now() - start < 0.001, "expected it not to block") 35 | 36 | start = now() 37 | _, err = sema:take(10) -- not too many, let's timeout 38 | assert(err == "timeout", "expected a 'timeout' error") 39 | assert(now() - start > 1, "expected it to block for 1s") 40 | 41 | assert(sema:get_count() == 2) 42 | 43 | --validate async threads 44 | local state = 0 45 | copas.addthread(function() 46 | assert(sema:take(5)) 47 | print("got the first 5!") 48 | state = state + 1 49 | end) 50 | copas.addthread(function() 51 | assert(sema:take(5)) 52 | print("got the next 5!") 53 | state = state + 2 54 | end) 55 | copas.pause(0.1) 56 | assert(state == 0, "expected state to still be 0") 57 | assert(sema:get_count() == 2, "expected count to still have 2 resources") 58 | 59 | assert(sema:give(4)) 60 | assert(sema:get_count() == 1, "expected count to now have 1 resource") 61 | copas.pause(0.1) 62 | assert(state == 1, "expected 1 from the first thread to be added to state") 63 | 64 | assert(sema:give(4)) 65 | assert(sema:get_count() == 0, "gave 4 more, so 5 in total, releasing 5, leaves 0 as expected") 66 | copas.pause(0.1) 67 | assert(state == 3, "expected 2 from the 2nd thread to be added to state") 68 | 69 | 70 | ok, err = sema:give(100) 71 | assert(not ok) 72 | assert(err == "too many") 73 | assert(sema:get_count() == 10) 74 | 75 | -- validate destroying 76 | assert(sema:take(sema:get_count())) -- empty the semaphore 77 | assert(sema:get_count() == 0, "should be empty now") 78 | local state = 0 79 | copas.addthread(function() 80 | local ok, err = sema:take(5) 81 | if ok then 82 | print("got 5, this is unexpected") 83 | elseif err == "destroyed" then 84 | state = state + 1 85 | end 86 | end) 87 | copas.addthread(function() 88 | local ok, err = sema:take(5) 89 | if ok then 90 | print("got 5, this is unexpected") 91 | elseif err == "destroyed" then 92 | state = state + 1 93 | end 94 | end) 95 | copas.pause(0.1) 96 | assert(sema:destroy()) 97 | copas.pause(0.1) 98 | assert(state == 2, "expected 2 threads to error with 'destroyed'") 99 | 100 | -- only returns errors from now on, on all methods 101 | ok, err = sema:destroy(); assert(ok == nil and err == "destroyed", "expected an error") 102 | ok, err = sema:give(1); assert(ok == nil and err == "destroyed", "expected an error") 103 | ok, err = sema:take(1); assert(ok == nil and err == "destroyed", "expected an error") 104 | ok, err = sema:get_count(); assert(ok == nil and err == "destroyed", "expected an error") 105 | 106 | 107 | 108 | -- timeouts get cancelled upon destruction 109 | -- we set a timeout to 0.5 seconds, then destroy the semaphore 110 | -- the timeout should not execute 111 | -- Reproduce https://github.com/lunarmodules/copas/issues/118 112 | local track_table = setmetatable({}, { __mode = "v" }) 113 | local sema = semaphore.new(10, 0, 0.5) 114 | track_table.sema = sema 115 | local state = 0 116 | track_table.coro = copas.addthread(function() 117 | local ok, err = sema:take(1) 118 | if ok then 119 | print("got one, this is unexpected") 120 | elseif err == "destroyed" then 121 | state = state + 1 122 | end 123 | end) 124 | copas.pause(0.1) 125 | assert(sema:destroy()) 126 | copas.pause(0.1) 127 | assert(state == 1, "expected 1 thread to error with 'destroyed'") 128 | sema = nil 129 | 130 | local errors = 0 131 | copas.setErrorHandler(function(msg) 132 | print("got error: "..tostring(msg)) 133 | print("--------------------------------------") 134 | errors = errors + 1 135 | end, true) 136 | 137 | collectgarbage() -- collect garbage to force eviction from the semaphore registry 138 | collectgarbage() 139 | 140 | copas.pause(0.5) -- wait for the timeout to expire if it is still set 141 | assert(errors == 0, "expected no errors") 142 | 143 | test_complete = true 144 | end) 145 | assert(test_complete, "test did not complete!") 146 | print("test success!") 147 | -------------------------------------------------------------------------------- /tests/tcptimeout.lua: -------------------------------------------------------------------------------- 1 | -- Tests Copas socket timeouts 2 | -- 3 | -- Run the test file, it should exit successfully without hanging. 4 | 5 | -- make sure we are pointing to the local copas first 6 | package.path = string.format("../src/?.lua;%s", package.path) 7 | 8 | local platform = "unix" 9 | if package.config:sub(1,1) == "\\" then 10 | platform = "windows" 11 | elseif io.popen("uname", "r"):read("*a"):find("Darwin") then 12 | platform = "mac" 13 | end 14 | print("Testing platform: " .. platform) 15 | 16 | 17 | local copas = require("copas") 18 | local socket = require("socket") 19 | 20 | -- hack; no way to kill copas.loop from thread 21 | local function error(err) 22 | print(debug.traceback(err, 2)) 23 | os.exit(-1) 24 | end 25 | local function assert(truthy, err) 26 | if not truthy then 27 | print(debug.traceback(err, 2)) 28 | os.exit(-1) 29 | end 30 | end 31 | 32 | -- tcp echo server for testing against, returns `ip, port` to connect to 33 | -- send `quit\n` to cause server to disconnect client 34 | -- stops listen server after first connection 35 | local function singleuseechoserver() 36 | local server = socket.bind("127.0.0.1", 0) -- "localhost" fails because of IPv6 error 37 | local ip, port = server:getsockname() 38 | 39 | local function echoHandler(skt) 40 | -- remove server after first connection 41 | copas.removeserver(server) 42 | 43 | skt = copas.wrap(skt) 44 | while true do 45 | local data = skt:receive() 46 | if not data or data == "quit" then 47 | break 48 | end 49 | skt:send(data..'\n') 50 | end 51 | end 52 | 53 | copas.addserver(server, echoHandler) 54 | 55 | return ip, port 56 | end 57 | 58 | 59 | 60 | 61 | local tests = {} 62 | 63 | function tests.just_exit() 64 | copas.loop() 65 | end 66 | 67 | function tests.connect_and_exit() 68 | local ip, port = singleuseechoserver() 69 | copas.addthread(function() 70 | local client = socket.connect(ip, port) 71 | client = copas.wrap(client) 72 | 73 | client:close() 74 | end) 75 | 76 | copas.loop() 77 | end 78 | 79 | 80 | if platform == "mac" then 81 | -- this test fails on a Mac, looks like the 'listen(0)' isn't being honoured 82 | print("\nSkipping test on Mac!\n") 83 | else 84 | function tests.connect_timeout_copas() 85 | local server = socket.tcp() 86 | server:bind("localhost", 0) 87 | server:listen(0) -- zero backlog, single connection will block further connections 88 | -- note: not servicing connections 89 | local ip, port = server:getsockname() 90 | 91 | copas.addthread(function() 92 | -- fill server's implicit connection backlog 93 | socket.connect(ip,port) 94 | 95 | local client = socket.tcp() 96 | client = copas.wrap(client) 97 | client:settimeout(0.01) 98 | local status, err = client:connect(ip, port) 99 | assert(status == nil, "connect somehow succeeded") 100 | assert(err == "timeout", "connect failed with non-timeout error: "..tostring(err)) 101 | client:close() 102 | end) 103 | 104 | copas.loop() 105 | end 106 | 107 | 108 | function tests.connect_timeout_socket() 109 | local server = socket.tcp() 110 | server:bind("localhost", 0) 111 | server:listen(0) -- zero backlog, single connection will block further connections 112 | -- note: not servicing connections 113 | local ip, port = server:getsockname() 114 | 115 | copas.addthread(function() 116 | copas.useSocketTimeoutErrors(true) 117 | -- fill server's implicit connection backlog 118 | socket.connect(ip,port) 119 | 120 | local client = socket.tcp() 121 | client = copas.wrap(client) 122 | client:settimeout(0.01) 123 | local status, err = client:connect(ip, port) 124 | assert(status == nil, "connect somehow succeeded") 125 | -- we test for a different error message becasue we expect socket errors, not copas ones 126 | assert(err == "Operation already in progress", "connect failed with non-timeout error: "..tostring(err)) 127 | client:close() 128 | end) 129 | 130 | copas.loop() 131 | end 132 | end 133 | 134 | 135 | function tests.receive_timeout() 136 | local ip, port = singleuseechoserver() 137 | 138 | copas.addthread(function() 139 | local client = socket.tcp() 140 | client = copas.wrap(client) 141 | client:settimeout(0.01) 142 | local status, err = client:connect(ip, port) 143 | assert(status, "failed to connect: "..tostring(err)) 144 | 145 | client:send("foo\n") 146 | local data, err = client:receive() 147 | assert(data, "failed to recieve: "..tostring(err)) 148 | assert(data == "foo", "recieved wrong echo: "..tostring(data)) 149 | 150 | local data, err = client:receive() 151 | assert(data == nil, "somehow recieved echo without sending") 152 | assert(err == "timeout", "failed with non-timeout error: "..tostring(err)) 153 | 154 | client:close() 155 | end) 156 | 157 | copas.loop() 158 | end 159 | 160 | -- test "framework" 161 | for name, test in pairs(tests) do 162 | print("testing: "..tostring(name)) 163 | local status, err = pcall(test) 164 | if not status then 165 | error(err) 166 | end 167 | end 168 | 169 | print("[✓] all tests completed successuly") 170 | -------------------------------------------------------------------------------- /tests/queue.lua: -------------------------------------------------------------------------------- 1 | -- make sure we are pointing to the local copas first 2 | package.path = string.format("../src/?.lua;%s", package.path) 3 | 4 | 5 | local copas = require "copas" 6 | local now = copas.gettime 7 | local Queue = copas.queue 8 | 9 | 10 | 11 | local test_complete = false 12 | copas.loop(function() 13 | 14 | -- basic push/pop 15 | local q = Queue:new() 16 | q:push "hello" 17 | assert(q:pop() == "hello", "expected the input to be returned") 18 | 19 | -- yielding on pop when queue is empty 20 | local s = now() 21 | copas.addthread(function() 22 | copas.pause(0.5) 23 | q:push("delayed") 24 | end) 25 | assert(q:pop() == "delayed", "expected a delayed result") 26 | assert(now() - s >= 0.5, "result was not delayed!") 27 | 28 | -- pop times out 29 | local ok, err = q:pop(0.5) 30 | assert(err == "timeout", "expected a timeout") 31 | assert(ok == nil) 32 | 33 | -- get_size returns queue size 34 | assert(q:get_size() == 0) 35 | q:push(1) 36 | assert(q:get_size() == 1) 37 | q:push(2) 38 | assert(q:get_size() == 2) 39 | q:push(3) 40 | assert(q:get_size() == 3) 41 | 42 | -- queue behaves as fifo 43 | assert(q:pop() == 1) 44 | assert(q:pop() == 2) 45 | assert(q:pop() == 3) 46 | 47 | -- handles nil values 48 | q:push(1) 49 | q:push(nil) 50 | q:push(3) 51 | 52 | assert(q:pop() == 1) 53 | local val, err = q:pop() 54 | assert(val == nil) 55 | assert(err == nil) 56 | assert(q:pop() == 3) 57 | 58 | -- stopping 59 | q:push(1) 60 | q:push(2) 61 | q:push(3) 62 | assert(q:stop()) 63 | local count = 0 64 | local coro = q:add_worker(function(item) 65 | count = count + 1 66 | end) 67 | copas.pause(0.1) 68 | assert(count == 3, "expected all 3 items handled") 69 | assert(coroutine.status(coro) == "dead", "expected thread to be gone") 70 | -- coro should be GC'able 71 | local weak = setmetatable({}, {__mode="v"}) 72 | weak[{}] = coro 73 | coro = nil -- luacheck: ignore 74 | collectgarbage() 75 | collectgarbage() 76 | assert(not next(weak)) 77 | -- worker exited, so queue is destroyed now? 78 | ok, err = q:push() 79 | assert(err == "destroyed", "expected queue to be destroyed") 80 | assert(ok == nil) 81 | ok, err = q:pop() 82 | assert(err == "destroyed", "expected queue to be destroyed") 83 | assert(ok == nil) 84 | 85 | 86 | test_complete = true 87 | end) 88 | 89 | -- copas loop exited when here 90 | 91 | assert(test_complete, "test did not complete!") 92 | print("test 1 success!") 93 | 94 | 95 | 96 | -- a worker handling nil values 97 | local count = 0 98 | copas.loop(function() 99 | local q = Queue:new() 100 | q:push(1) 101 | q:push(nil) 102 | q:push(3) 103 | q:add_worker(function() count = count + 1 end) 104 | copas.pause(0.5) -- to activate the worker, which will now be blocked on the q semaphore 105 | assert(q:finish(5)) 106 | end) 107 | assert(count == 3, "expected count to be 3, got "..tostring(count)) 108 | print("test 2 success!") 109 | 110 | 111 | -- finish blocks for a timeout 112 | local passed = false 113 | copas.loop(function() 114 | local q = Queue:new() 115 | q:push(1) -- no workers, so this one will not be handled 116 | 117 | local s = now() 118 | local ok, err = q:finish(1) 119 | local duration = now() - s 120 | 121 | assert(not ok, "expected a falsy value, got: "..tostring(ok)) 122 | assert(err == "timeout", "expected error 'timeout', got: "..tostring(err)) 123 | assert(duration > 1 and duration < 1.2, string.format("expected timeout of 1 second, but took: %f",duration)) 124 | passed = true 125 | end) 126 | assert(passed, "test failed!") 127 | print("test 3 success!") 128 | 129 | 130 | -- destroying a queue while workers are idle 131 | copas.loop(function() 132 | local q = Queue:new() 133 | q:add_worker(function() end) 134 | copas.pause(0.5) -- to activate the worker, which will now be blocked on the q semaphore 135 | q:stop() -- this should exit the idle workers and exit the copas loop 136 | end) 137 | print("test 4 success!") 138 | 139 | 140 | -- finish a queue while workers are idle 141 | copas.loop(function() 142 | local q = Queue:new() 143 | q:add_worker(function() end) 144 | copas.pause(0.5) -- to activate the worker, which will now be blocked on the q semaphore 145 | q:finish() -- this should exit the idle workers and exit the copas loop 146 | end) 147 | print("test 5 success!") 148 | 149 | 150 | -- finish doesn't return until all workers are done (finished handling the last queue item) 151 | local result = {} 152 | local passed = true 153 | copas.loop(function() 154 | local q = Queue:new() 155 | q:push(1) 156 | q:push(2) 157 | q:push(3) 158 | for i = 1,2 do -- add 2 workers 159 | q:add_worker(function(n) 160 | table.insert(result, "start item " .. n) 161 | copas.pause(0.5) 162 | table.insert(result, "end item " .. n) 163 | end) 164 | end 165 | -- local s = now() 166 | table.insert(result, "start queue") 167 | copas.pause(0.75) 168 | table.insert(result, "start finish") 169 | local ok, err = q:finish() 170 | table.insert(result, "finished "..tostring(ok).." "..tostring(err)) 171 | copas.pause(1) 172 | local expected = { 173 | "start queue", 174 | "start item 1", 175 | "start item 2", 176 | "end item 1", 177 | "start item 3", 178 | "end item 2", 179 | "start finish", 180 | "end item 3", 181 | "finished true nil", 182 | } 183 | for i = 1, math.max(#result, #expected) do 184 | if result[i] ~= expected[i] then 185 | for n = 1, math.max(#result, #expected) do 186 | print(n, result[n], expected[n], result[n] == expected[n] and "" or " <--- failed") 187 | end 188 | passed = false 189 | break 190 | end 191 | end 192 | end) 193 | assert(passed, "test 6 failed!") 194 | print("test 6 success!") 195 | -------------------------------------------------------------------------------- /src/copas/lock.lua: -------------------------------------------------------------------------------- 1 | local copas = require("copas") 2 | local gettime = copas.gettime 3 | 4 | local DEFAULT_TIMEOUT = 10 5 | 6 | local lock = {} 7 | lock.__index = lock 8 | 9 | 10 | -- registry, locks indexed by the coroutines using them. 11 | local registry = setmetatable({}, { __mode="kv" }) 12 | 13 | 14 | 15 | --- Creates a new lock. 16 | -- @param seconds (optional) default timeout in seconds when acquiring the lock (defaults to 10), 17 | -- set to `math.huge` to have no timeout. 18 | -- @param not_reentrant (optional) if truthy the lock will not allow a coroutine to grab the same lock multiple times 19 | -- @return the lock object 20 | function lock.new(seconds, not_reentrant) 21 | local timeout = tonumber(seconds or DEFAULT_TIMEOUT) or -1 22 | if timeout < 0 then 23 | error("expected timeout (1st argument) to be a number greater than or equal to 0, got: " .. tostring(seconds), 2) 24 | end 25 | return setmetatable({ 26 | timeout = timeout, 27 | not_reentrant = not_reentrant, 28 | queue = {}, 29 | q_tip = 0, -- index of the first in line waiting 30 | q_tail = 0, -- index where the next one will be inserted 31 | owner = nil, -- coroutine holding lock currently 32 | call_count = nil, -- recursion call count 33 | errors = setmetatable({}, { __mode = "k" }), -- error indexed by coroutine 34 | }, lock) 35 | end 36 | 37 | 38 | 39 | do 40 | local destroyed_func = function() 41 | return nil, "destroyed" 42 | end 43 | 44 | local destroyed_lock_mt = { 45 | __index = function() 46 | return destroyed_func 47 | end 48 | } 49 | 50 | --- destroy a lock. 51 | -- Releases all waiting threads with `nil+"destroyed"` 52 | function lock:destroy() 53 | --print("destroying ",self) 54 | for i = self.q_tip, self.q_tail do 55 | local co = self.queue[i] 56 | self.queue[i] = nil 57 | 58 | if co then 59 | self.errors[co] = "destroyed" 60 | --print("marked destroyed ", co) 61 | copas.wakeup(co) 62 | end 63 | end 64 | 65 | if self.owner then 66 | self.errors[self.owner] = "destroyed" 67 | --print("marked destroyed ", co) 68 | end 69 | self.queue = {} 70 | self.q_tip = 0 71 | self.q_tail = 0 72 | self.destroyed = true 73 | 74 | setmetatable(self, destroyed_lock_mt) 75 | return true 76 | end 77 | end 78 | 79 | 80 | local function timeout_handler(co) 81 | local self = registry[co] 82 | if not self then 83 | return 84 | end 85 | 86 | for i = self.q_tip, self.q_tail do 87 | if co == self.queue[i] then 88 | self.queue[i] = nil 89 | self.errors[co] = "timeout" 90 | --print("marked timeout ", co) 91 | copas.wakeup(co) 92 | return 93 | end 94 | end 95 | -- if we get here, we own it currently, or we finished it by now, or 96 | -- the lock was destroyed. Anyway, nothing to do here... 97 | end 98 | 99 | 100 | --- Acquires the lock. 101 | -- If the lock is owned by another thread, this will yield control, until the 102 | -- lock becomes available, or it times out. 103 | -- If `timeout == 0` then it will immediately return (without yielding). 104 | -- @param timeout (optional) timeout in seconds, defaults to the timeout passed to `new` (use `math.huge` to have no timeout). 105 | -- @return wait-time on success, or nil+error+wait_time on failure. Errors can be "timeout", "destroyed", or "lock is not re-entrant" 106 | function lock:get(timeout) 107 | local co = coroutine.running() 108 | local start_time 109 | 110 | -- is the lock already taken? 111 | if self.owner then 112 | -- are we re-entering? 113 | if co == self.owner and not self.not_reentrant then 114 | self.call_count = self.call_count + 1 115 | return 0 116 | end 117 | 118 | self.queue[self.q_tail] = co 119 | self.q_tail = self.q_tail + 1 120 | timeout = timeout or self.timeout 121 | if timeout == 0 then 122 | return nil, "timeout", 0 123 | end 124 | 125 | -- set up timeout 126 | registry[co] = self 127 | copas.timeout(timeout, timeout_handler) 128 | 129 | start_time = gettime() 130 | copas.pauseforever() 131 | 132 | local err = self.errors[co] 133 | self.errors[co] = nil 134 | registry[co] = nil 135 | 136 | --print("released ", co, err) 137 | if err ~= "timeout" then 138 | copas.timeout(0) 139 | end 140 | if err then 141 | return nil, err, gettime() - start_time 142 | end 143 | end 144 | 145 | -- it's ours to have 146 | self.owner = co 147 | self.call_count = 1 148 | return start_time and (gettime() - start_time) or 0 149 | end 150 | 151 | 152 | --- Releases the lock currently held. 153 | -- Releasing a lock that is not owned by the current co-routine will return 154 | -- an error. 155 | -- returns true, or nil+err on an error 156 | function lock:release() 157 | local co = coroutine.running() 158 | 159 | if co ~= self.owner then 160 | return nil, "cannot release a lock not owned" 161 | end 162 | 163 | self.call_count = self.call_count - 1 164 | if self.call_count > 0 then 165 | -- same coro is still holding it 166 | return true 167 | end 168 | 169 | -- need a loop, since individual coroutines might have been removed 170 | -- so there might be holes 171 | while self.q_tip < self.q_tail do 172 | local next_up = self.queue[self.q_tip] 173 | if next_up then 174 | self.owner = next_up 175 | self.queue[self.q_tip] = nil 176 | self.q_tip = self.q_tip + 1 177 | copas.wakeup(next_up) 178 | return true 179 | end 180 | self.q_tip = self.q_tip + 1 181 | end 182 | -- queue is empty, reset pointers 183 | self.owner = nil 184 | self.q_tip = 0 185 | self.q_tail = 0 186 | return true 187 | end 188 | 189 | 190 | 191 | return lock 192 | -------------------------------------------------------------------------------- /src/copas/semaphore.lua: -------------------------------------------------------------------------------- 1 | local copas = require("copas") 2 | 3 | local DEFAULT_TIMEOUT = 10 4 | 5 | local semaphore = {} 6 | semaphore.__index = semaphore 7 | 8 | 9 | -- registry, semaphore indexed by the coroutines using them. 10 | local registry = setmetatable({}, { __mode="kv" }) 11 | 12 | 13 | -- create a new semaphore 14 | -- @param max maximum number of resources the semaphore can hold (this maximum does NOT include resources that have been given but not yet returned). 15 | -- @param start (optional, default 0) the initial resources available 16 | -- @param seconds (optional, default 10) default semaphore timeout in seconds, or `math.huge` to have no timeout. 17 | function semaphore.new(max, start, seconds) 18 | local timeout = tonumber(seconds or DEFAULT_TIMEOUT) or -1 19 | if timeout < 0 then 20 | error("expected timeout (2nd argument) to be a number greater than or equal to 0, got: " .. tostring(seconds), 2) 21 | end 22 | if type(max) ~= "number" or max < 1 then 23 | error("expected max resources (1st argument) to be a number greater than 0, got: " .. tostring(max), 2) 24 | end 25 | 26 | local self = setmetatable({ 27 | count = start or 0, 28 | max = max, 29 | timeout = timeout, 30 | q_tip = 1, -- position of next entry waiting 31 | q_tail = 1, -- position where next one will be inserted 32 | queue = {}, 33 | to_flags = setmetatable({}, { __mode = "k" }), -- timeout flags indexed by coroutine 34 | }, semaphore) 35 | 36 | return self 37 | end 38 | 39 | 40 | do 41 | local destroyed_func = function() 42 | return nil, "destroyed" 43 | end 44 | 45 | local destroyed_semaphore_mt = { 46 | __index = function() 47 | return destroyed_func 48 | end 49 | } 50 | 51 | -- destroy a semaphore. 52 | -- Releases all waiting threads with `nil+"destroyed"` 53 | function semaphore:destroy() 54 | self:give(math.huge) 55 | self.destroyed = true 56 | setmetatable(self, destroyed_semaphore_mt) 57 | return true 58 | end 59 | end 60 | 61 | 62 | -- Gives resources. 63 | -- @param given (optional, default 1) number of resources to return. If more 64 | -- than the maximum are returned then it will be capped at the maximum and 65 | -- error "too many" will be returned. 66 | function semaphore:give(given) 67 | local err 68 | given = given or 1 69 | local count = self.count + given 70 | --print("now at",count, ", after +"..given) 71 | if count > self.max then 72 | count = self.max 73 | err = "too many" 74 | end 75 | 76 | while self.q_tip < self.q_tail do 77 | local i = self.q_tip 78 | local nxt = self.queue[i] -- there can be holes, so nxt might be nil 79 | if not nxt then 80 | self.q_tip = i + 1 81 | else 82 | if count >= nxt.requested then 83 | -- release it 84 | self.queue[i] = nil 85 | self.to_flags[nxt.co] = nil 86 | count = count - nxt.requested 87 | self.q_tip = i + 1 88 | copas.wakeup(nxt.co) 89 | nxt.co = nil 90 | else 91 | break -- we ran out of resources 92 | end 93 | end 94 | end 95 | 96 | if self.q_tip == self.q_tail then -- reset queue 97 | self.queue = {} 98 | self.q_tip = 1 99 | self.q_tail = 1 100 | end 101 | 102 | self.count = count 103 | if err then 104 | return nil, err 105 | end 106 | return true 107 | end 108 | 109 | 110 | 111 | local function timeout_handler(co) 112 | local self = registry[co] 113 | --print("checking timeout ", co) 114 | if not self then 115 | return 116 | end 117 | 118 | for i = self.q_tip, self.q_tail do 119 | local item = self.queue[i] 120 | if item and co == item.co then 121 | self.queue[i] = nil 122 | self.to_flags[co] = true 123 | --print("marked timeout ", co) 124 | copas.wakeup(co) 125 | return 126 | end 127 | end 128 | -- nothing to do here... 129 | end 130 | 131 | 132 | -- Requests resources from the semaphore. 133 | -- Waits if there are not enough resources available before returning. 134 | -- @param requested (optional, default 1) the number of resources requested 135 | -- @param timeout (optional, defaults to semaphore timeout) timeout in 136 | -- seconds. If 0 it will either succeed or return immediately with error "timeout". 137 | -- If `math.huge` it will wait forever. 138 | -- @return true, or nil+"destroyed" 139 | function semaphore:take(requested, timeout) 140 | requested = requested or 1 141 | if self.q_tail == 1 and self.count >= requested then 142 | -- nobody is waiting before us, and there is enough in store 143 | self.count = self.count - requested 144 | return true 145 | end 146 | 147 | if requested > self.max then 148 | return nil, "too many" 149 | end 150 | 151 | local to = timeout or self.timeout 152 | if to == 0 then 153 | return nil, "timeout" 154 | end 155 | 156 | -- get in line 157 | local co = coroutine.running() 158 | self.to_flags[co] = nil 159 | registry[co] = self 160 | copas.timeout(to, timeout_handler) 161 | 162 | self.queue[self.q_tail] = { 163 | co = co, 164 | requested = requested, 165 | --timeout = nil, -- flag indicating timeout 166 | } 167 | self.q_tail = self.q_tail + 1 168 | 169 | copas.pauseforever() -- block until woken 170 | registry[co] = nil 171 | 172 | if self.to_flags[co] then 173 | -- a timeout happened 174 | self.to_flags[co] = nil 175 | return nil, "timeout" 176 | end 177 | 178 | copas.timeout(0) 179 | 180 | if self.destroyed then 181 | return nil, "destroyed" 182 | end 183 | 184 | return true 185 | end 186 | 187 | -- returns current available resources 188 | function semaphore:get_count() 189 | return self.count 190 | end 191 | 192 | -- returns total shortage for requested resources 193 | function semaphore:get_wait() 194 | local wait = 0 195 | for i = self.q_tip, self.q_tail - 1 do 196 | wait = wait + ((self.queue[i] or {}).requested or 0) 197 | end 198 | return wait - self.count 199 | end 200 | 201 | 202 | return semaphore 203 | -------------------------------------------------------------------------------- /src/copas/queue.lua: -------------------------------------------------------------------------------- 1 | local copas = require "copas" 2 | local gettime = copas.gettime 3 | local Sema = copas.semaphore 4 | local Lock = copas.lock 5 | 6 | 7 | local Queue = {} 8 | Queue.__index = Queue 9 | 10 | 11 | local new_name do 12 | local count = 0 13 | 14 | function new_name() 15 | count = count + 1 16 | return "copas_queue_" .. count 17 | end 18 | end 19 | 20 | 21 | -- Creates a new Queue instance 22 | function Queue.new(opts) 23 | opts = opts or {} 24 | local self = {} 25 | setmetatable(self, Queue) 26 | self.name = opts.name or new_name() 27 | self.sema = Sema.new(10^9) 28 | self.head = 1 29 | self.tail = 1 30 | self.list = {} 31 | self.workers = setmetatable({}, { __mode = "k" }) 32 | self.stopping = false 33 | self.worker_id = 0 34 | self.exit_semaphore = Sema.new(10^9) 35 | return self 36 | end 37 | 38 | 39 | -- Pushes an item in the queue (can be 'nil') 40 | -- returns true, or nil+err ("stopping", or "destroyed") 41 | function Queue:push(item) 42 | if self.stopping then 43 | return nil, "stopping" 44 | end 45 | self.list[self.head] = item 46 | self.head = self.head + 1 47 | self.sema:give() 48 | return true 49 | end 50 | 51 | 52 | -- Pops and item from the queue. If there are no items in the queue it will yield 53 | -- until there are or a timeout happens (exception is when `timeout == 0`, then it will 54 | -- not yield but return immediately). If the timeout is `math.huge` it will wait forever. 55 | -- Returns item, or nil+err ("timeout", or "destroyed") 56 | function Queue:pop(timeout) 57 | local ok, err = self.sema:take(1, timeout) 58 | if not ok then 59 | return ok, err 60 | end 61 | 62 | local item = self.list[self.tail] 63 | self.list[self.tail] = nil 64 | self.tail = self.tail + 1 65 | 66 | if self.tail == self.head then 67 | -- reset queue 68 | self.list = {} 69 | self.tail = 1 70 | self.head = 1 71 | if self.stopping then 72 | -- we're stopping and last item being returned, so we're done 73 | self:destroy() 74 | end 75 | end 76 | return item 77 | end 78 | 79 | 80 | -- return the number of items left in the queue 81 | function Queue:get_size() 82 | return self.head - self.tail 83 | end 84 | 85 | 86 | -- instructs the queue to stop. Will not accept any more 'push' calls. 87 | -- will autocall 'destroy' when the queue is empty. 88 | -- returns immediately. See `finish` 89 | function Queue:stop() 90 | if not self.stopping then 91 | self.stopping = true 92 | self.lock = Lock.new(nil, true) 93 | self.lock:get() -- close the lock 94 | if self:get_size() == 0 then 95 | -- queue is already empty, so "pop" function cannot call destroy on next 96 | -- pop, so destroy now. 97 | self:destroy() 98 | end 99 | end 100 | return true 101 | end 102 | 103 | 104 | -- Finishes a queue. Calls stop and then waits for the queue to run empty (and be 105 | -- destroyed) before returning. returns true or nil+err ("timeout", or "destroyed") 106 | -- Parameter no_destroy_on_timeout indicates if the queue is not to be forcefully 107 | -- destroyed on a timeout. 108 | function Queue:finish(timeout, no_destroy_on_timeout) 109 | self:stop() 110 | timeout = timeout or self.lock.timeout 111 | local endtime = gettime() + timeout 112 | local _, err = self.lock:get(timeout) 113 | -- the lock never gets released, only destroyed, so we have to check the error string 114 | if err == "timeout" then 115 | if not no_destroy_on_timeout then 116 | self:destroy() 117 | end 118 | return nil, err 119 | end 120 | 121 | -- if we get here, the lock was destroyed, so the queue is empty, now wait for all workers to exit 122 | if not next(self.workers) then 123 | -- all workers already exited, we're done 124 | return true 125 | end 126 | 127 | -- multiple threads can call this "finish" method, so we must check exiting workers 128 | -- one by one. 129 | while true do 130 | local _, err = self.exit_semaphore:take(1, math.max(0, endtime - gettime())) 131 | if err == "destroyed" then 132 | return true -- someone else destroyed/finished it, so we're done 133 | end 134 | if err == "timeout" then 135 | if not no_destroy_on_timeout then 136 | self:destroy() 137 | end 138 | return nil, "timeout" 139 | end 140 | if not next(self.workers) then 141 | self.exit_semaphore:destroy() 142 | return true -- all workers exited, we're done 143 | end 144 | end 145 | end 146 | 147 | 148 | do 149 | local destroyed_func = function() 150 | return nil, "destroyed" 151 | end 152 | 153 | local destroyed_queue_mt = { 154 | __index = function() 155 | return destroyed_func 156 | end 157 | } 158 | 159 | -- destroys a queue immediately. Abandons what is left in the queue. 160 | -- Releases all waiting threads with `nil+"destroyed"` 161 | function Queue:destroy() 162 | if self.lock then 163 | self.lock:destroy() 164 | end 165 | self.sema:destroy() 166 | setmetatable(self, destroyed_queue_mt) 167 | 168 | -- clear anything left in the queue 169 | for key in pairs(self.list) do 170 | self.list[key] = nil 171 | end 172 | 173 | return true 174 | end 175 | end 176 | 177 | 178 | -- adds a worker that will handle whatever is passed into the queue. Can be called 179 | -- multiple times to add more workers. 180 | -- The threads automatically exit when the queue is destroyed. 181 | -- worker function signature: `function(item)` (Note: worker functions run 182 | -- unprotected, so wrap code in an (x)pcall if errors are expected, otherwise the 183 | -- worker will exit on an error, and queue handling will stop) 184 | -- Returns the coroutine added. 185 | function Queue:add_worker(worker) 186 | assert(type(worker) == "function", "expected worker to be a function") 187 | local coro 188 | 189 | self.worker_id = self.worker_id + 1 190 | local worker_name = self.name .. ":worker_" .. self.worker_id 191 | 192 | coro = copas.addnamedthread(worker_name, function() 193 | while true do 194 | local item, err = self:pop(math.huge) -- wait forever 195 | if err then 196 | break -- queue destroyed, exit 197 | end 198 | worker(item) -- TODO: wrap in errorhandling 199 | end 200 | self.workers[coro] = nil 201 | if self.exit_semaphore then 202 | self.exit_semaphore:give(1) 203 | end 204 | end) 205 | 206 | self.workers[coro] = true 207 | return coro 208 | end 209 | 210 | -- returns a list/array of current workers (coroutines) handling the queue. 211 | -- (only the workers added by `add_worker`, and still active, will be in this list) 212 | function Queue:get_workers() 213 | local lst = {} 214 | for coro in pairs(self.workers) do 215 | if coroutine.status(coro) ~= "dead" then 216 | lst[#lst+1] = coro 217 | end 218 | end 219 | return lst 220 | end 221 | 222 | return Queue 223 | -------------------------------------------------------------------------------- /tests/certs/rootA.cnf: -------------------------------------------------------------------------------- 1 | # 2 | # OpenSSL example configuration file. 3 | # This is mostly being used for generation of certificate requests. 4 | # 5 | 6 | # This definition stops the following lines choking if HOME isn't 7 | # defined. 8 | HOME = . 9 | RANDFILE = $ENV::HOME/.rnd 10 | 11 | # Extra OBJECT IDENTIFIER info: 12 | #oid_file = $ENV::HOME/.oid 13 | oid_section = new_oids 14 | 15 | # To use this configuration file with the "-extfile" option of the 16 | # "openssl x509" utility, name here the section containing the 17 | # X.509v3 extensions to use: 18 | # extensions = 19 | # (Alternatively, use a configuration file that has only 20 | # X.509v3 extensions in its main [= default] section.) 21 | 22 | [ new_oids ] 23 | 24 | # We can add new OIDs in here for use by 'ca' and 'req'. 25 | # Add a simple OID like this: 26 | # testoid1=1.2.3.4 27 | # Or use config file substitution like this: 28 | # testoid2=${testoid1}.5.6 29 | 30 | #################################################################### 31 | [ ca ] 32 | default_ca = CA_default # The default ca section 33 | 34 | #################################################################### 35 | [ CA_default ] 36 | 37 | dir = ./demoCA # Where everything is kept 38 | certs = $dir/certs # Where the issued certs are kept 39 | crl_dir = $dir/crl # Where the issued crl are kept 40 | database = $dir/index.txt # database index file. 41 | #unique_subject = no # Set to 'no' to allow creation of 42 | # several ctificates with same subject. 43 | new_certs_dir = $dir/newcerts # default place for new certs. 44 | 45 | certificate = $dir/cacert.pem # The CA certificate 46 | serial = $dir/serial # The current serial number 47 | crlnumber = $dir/crlnumber # the current crl number 48 | # must be commented out to leave a V1 CRL 49 | crl = $dir/crl.pem # The current CRL 50 | private_key = $dir/private/cakey.pem # The private key 51 | RANDFILE = $dir/private/.rand # private random number file 52 | 53 | x509_extensions = usr_cert # The extensions to add to the cert 54 | 55 | # Comment out the following two lines for the "traditional" 56 | # (and highly broken) format. 57 | name_opt = ca_default # Subject Name options 58 | cert_opt = ca_default # Certificate field options 59 | 60 | # Extension copying option: use with caution. 61 | # copy_extensions = copy 62 | 63 | # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs 64 | # so this is commented out by default to leave a V1 CRL. 65 | # crlnumber must also be commented out to leave a V1 CRL. 66 | # crl_extensions = crl_ext 67 | 68 | default_days = 365 # how long to certify for 69 | default_crl_days= 30 # how long before next CRL 70 | default_md = sha1 # which md to use. 71 | preserve = no # keep passed DN ordering 72 | 73 | # A few difference way of specifying how similar the request should look 74 | # For type CA, the listed attributes must be the same, and the optional 75 | # and supplied fields are just that :-) 76 | policy = policy_match 77 | 78 | # For the CA policy 79 | [ policy_match ] 80 | countryName = match 81 | stateOrProvinceName = match 82 | organizationName = match 83 | organizationalUnitName = optional 84 | commonName = supplied 85 | emailAddress = optional 86 | 87 | # For the 'anything' policy 88 | # At this point in time, you must list all acceptable 'object' 89 | # types. 90 | [ policy_anything ] 91 | countryName = optional 92 | stateOrProvinceName = optional 93 | localityName = optional 94 | organizationName = optional 95 | organizationalUnitName = optional 96 | commonName = supplied 97 | emailAddress = optional 98 | 99 | #################################################################### 100 | [ req ] 101 | default_bits = 1024 102 | default_keyfile = privkey.pem 103 | distinguished_name = req_distinguished_name 104 | attributes = req_attributes 105 | x509_extensions = v3_ca # The extensions to add to the self signed cert 106 | 107 | # Passwords for private keys if not present they will be prompted for 108 | # input_password = secret 109 | # output_password = secret 110 | 111 | # This sets a mask for permitted string types. There are several options. 112 | # default: PrintableString, T61String, BMPString. 113 | # pkix : PrintableString, BMPString. 114 | # utf8only: only UTF8Strings. 115 | # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). 116 | # MASK:XXXX a literal mask value. 117 | # WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings 118 | # so use this option with caution! 119 | string_mask = nombstr 120 | 121 | # req_extensions = v3_req # The extensions to add to a certificate request 122 | 123 | [ req_distinguished_name ] 124 | countryName = Country Name (2 letter code) 125 | countryName_default = BR 126 | countryName_min = 2 127 | countryName_max = 2 128 | 129 | stateOrProvinceName = State or Province Name (full name) 130 | stateOrProvinceName_default = Espirito Santo 131 | 132 | localityName = Locality Name (eg, city) 133 | localityName_default = Santo Antonio do Canaa 134 | 135 | 0.organizationName = Organization Name (eg, company) 136 | 0.organizationName_default = Santo Tonico Ltda 137 | 138 | # we can do this but it is not needed normally :-) 139 | #1.organizationName = Second Organization Name (eg, company) 140 | #1.organizationName_default = World Wide Web Pty Ltd 141 | 142 | organizationalUnitName = Organizational Unit Name (eg, section) 143 | organizationalUnitName_default = Department of Computer Science 144 | 145 | commonName = Common Name (eg, YOUR name) 146 | commonName_max = 64 147 | commonName_default = Root A 148 | 149 | emailAddress = Email Address 150 | emailAddress_max = 64 151 | 152 | # SET-ex3 = SET extension number 3 153 | 154 | [ req_attributes ] 155 | challengePassword = A challenge password 156 | challengePassword_min = 4 157 | challengePassword_max = 20 158 | 159 | unstructuredName = An optional company name 160 | 161 | [ usr_cert ] 162 | 163 | # These extensions are added when 'ca' signs a request. 164 | 165 | # This goes against PKIX guidelines but some CAs do it and some software 166 | # requires this to avoid interpreting an end user certificate as a CA. 167 | 168 | basicConstraints=CA:FALSE 169 | 170 | # Here are some examples of the usage of nsCertType. If it is omitted 171 | # the certificate can be used for anything *except* object signing. 172 | 173 | # This is OK for an SSL server. 174 | # nsCertType = server 175 | 176 | # For an object signing certificate this would be used. 177 | # nsCertType = objsign 178 | 179 | # For normal client use this is typical 180 | # nsCertType = client, email 181 | 182 | # and for everything including object signing: 183 | # nsCertType = client, email, objsign 184 | 185 | # This is typical in keyUsage for a client certificate. 186 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 187 | 188 | # This will be displayed in Netscape's comment listbox. 189 | nsComment = "OpenSSL Generated Certificate" 190 | 191 | # PKIX recommendations harmless if included in all certificates. 192 | subjectKeyIdentifier=hash 193 | authorityKeyIdentifier=keyid,issuer 194 | 195 | # This stuff is for subjectAltName and issuerAltname. 196 | # Import the email address. 197 | # subjectAltName=email:copy 198 | # An alternative to produce certificates that aren't 199 | # deprecated according to PKIX. 200 | # subjectAltName=email:move 201 | 202 | # Copy subject details 203 | # issuerAltName=issuer:copy 204 | 205 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 206 | #nsBaseUrl 207 | #nsRevocationUrl 208 | #nsRenewalUrl 209 | #nsCaPolicyUrl 210 | #nsSslServerName 211 | 212 | [ v3_req ] 213 | 214 | # Extensions to add to a certificate request 215 | 216 | basicConstraints = CA:FALSE 217 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 218 | 219 | [ v3_ca ] 220 | 221 | 222 | # Extensions for a typical CA 223 | 224 | 225 | # PKIX recommendation. 226 | 227 | subjectKeyIdentifier=hash 228 | 229 | authorityKeyIdentifier=keyid:always,issuer:always 230 | 231 | # This is what PKIX recommends but some broken software chokes on critical 232 | # extensions. 233 | #basicConstraints = critical,CA:true 234 | # So we do this instead. 235 | basicConstraints = CA:true 236 | 237 | # Key usage: this is typical for a CA certificate. However since it will 238 | # prevent it being used as an test self-signed certificate it is best 239 | # left out by default. 240 | # keyUsage = cRLSign, keyCertSign 241 | 242 | # Some might want this also 243 | # nsCertType = sslCA, emailCA 244 | 245 | # Include email address in subject alt name: another PKIX recommendation 246 | # subjectAltName=email:copy 247 | # Copy issuer details 248 | # issuerAltName=issuer:copy 249 | 250 | # DER hex encoding of an extension: beware experts only! 251 | # obj=DER:02:03 252 | # Where 'obj' is a standard or added object 253 | # You can even override a supported extension: 254 | # basicConstraints= critical, DER:30:03:01:01:FF 255 | 256 | [ crl_ext ] 257 | 258 | # CRL extensions. 259 | # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. 260 | 261 | # issuerAltName=issuer:copy 262 | authorityKeyIdentifier=keyid:always,issuer:always 263 | 264 | [ proxy_cert_ext ] 265 | # These extensions should be added when creating a proxy certificate 266 | 267 | # This goes against PKIX guidelines but some CAs do it and some software 268 | # requires this to avoid interpreting an end user certificate as a CA. 269 | 270 | basicConstraints=CA:FALSE 271 | 272 | # Here are some examples of the usage of nsCertType. If it is omitted 273 | # the certificate can be used for anything *except* object signing. 274 | 275 | # This is OK for an SSL server. 276 | # nsCertType = server 277 | 278 | # For an object signing certificate this would be used. 279 | # nsCertType = objsign 280 | 281 | # For normal client use this is typical 282 | # nsCertType = client, email 283 | 284 | # and for everything including object signing: 285 | # nsCertType = client, email, objsign 286 | 287 | # This is typical in keyUsage for a client certificate. 288 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 289 | 290 | # This will be displayed in Netscape's comment listbox. 291 | nsComment = "OpenSSL Generated Certificate" 292 | 293 | # PKIX recommendations harmless if included in all certificates. 294 | subjectKeyIdentifier=hash 295 | authorityKeyIdentifier=keyid,issuer:always 296 | 297 | # This stuff is for subjectAltName and issuerAltname. 298 | # Import the email address. 299 | # subjectAltName=email:copy 300 | # An alternative to produce certificates that aren't 301 | # deprecated according to PKIX. 302 | # subjectAltName=email:move 303 | 304 | # Copy subject details 305 | # issuerAltName=issuer:copy 306 | 307 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 308 | #nsBaseUrl 309 | #nsRevocationUrl 310 | #nsRenewalUrl 311 | #nsCaPolicyUrl 312 | #nsSslServerName 313 | 314 | # This really needs to be in place for it to be a proxy certificate. 315 | proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo 316 | -------------------------------------------------------------------------------- /tests/certs/rootB.cnf: -------------------------------------------------------------------------------- 1 | # 2 | # OpenSSL example configuration file. 3 | # This is mostly being used for generation of certificate requests. 4 | # 5 | 6 | # This definition stops the following lines choking if HOME isn't 7 | # defined. 8 | HOME = . 9 | RANDFILE = $ENV::HOME/.rnd 10 | 11 | # Extra OBJECT IDENTIFIER info: 12 | #oid_file = $ENV::HOME/.oid 13 | oid_section = new_oids 14 | 15 | # To use this configuration file with the "-extfile" option of the 16 | # "openssl x509" utility, name here the section containing the 17 | # X.509v3 extensions to use: 18 | # extensions = 19 | # (Alternatively, use a configuration file that has only 20 | # X.509v3 extensions in its main [= default] section.) 21 | 22 | [ new_oids ] 23 | 24 | # We can add new OIDs in here for use by 'ca' and 'req'. 25 | # Add a simple OID like this: 26 | # testoid1=1.2.3.4 27 | # Or use config file substitution like this: 28 | # testoid2=${testoid1}.5.6 29 | 30 | #################################################################### 31 | [ ca ] 32 | default_ca = CA_default # The default ca section 33 | 34 | #################################################################### 35 | [ CA_default ] 36 | 37 | dir = ./demoCA # Where everything is kept 38 | certs = $dir/certs # Where the issued certs are kept 39 | crl_dir = $dir/crl # Where the issued crl are kept 40 | database = $dir/index.txt # database index file. 41 | #unique_subject = no # Set to 'no' to allow creation of 42 | # several ctificates with same subject. 43 | new_certs_dir = $dir/newcerts # default place for new certs. 44 | 45 | certificate = $dir/cacert.pem # The CA certificate 46 | serial = $dir/serial # The current serial number 47 | crlnumber = $dir/crlnumber # the current crl number 48 | # must be commented out to leave a V1 CRL 49 | crl = $dir/crl.pem # The current CRL 50 | private_key = $dir/private/cakey.pem # The private key 51 | RANDFILE = $dir/private/.rand # private random number file 52 | 53 | x509_extensions = usr_cert # The extensions to add to the cert 54 | 55 | # Comment out the following two lines for the "traditional" 56 | # (and highly broken) format. 57 | name_opt = ca_default # Subject Name options 58 | cert_opt = ca_default # Certificate field options 59 | 60 | # Extension copying option: use with caution. 61 | # copy_extensions = copy 62 | 63 | # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs 64 | # so this is commented out by default to leave a V1 CRL. 65 | # crlnumber must also be commented out to leave a V1 CRL. 66 | # crl_extensions = crl_ext 67 | 68 | default_days = 365 # how long to certify for 69 | default_crl_days= 30 # how long before next CRL 70 | default_md = sha1 # which md to use. 71 | preserve = no # keep passed DN ordering 72 | 73 | # A few difference way of specifying how similar the request should look 74 | # For type CA, the listed attributes must be the same, and the optional 75 | # and supplied fields are just that :-) 76 | policy = policy_match 77 | 78 | # For the CA policy 79 | [ policy_match ] 80 | countryName = match 81 | stateOrProvinceName = match 82 | organizationName = match 83 | organizationalUnitName = optional 84 | commonName = supplied 85 | emailAddress = optional 86 | 87 | # For the 'anything' policy 88 | # At this point in time, you must list all acceptable 'object' 89 | # types. 90 | [ policy_anything ] 91 | countryName = optional 92 | stateOrProvinceName = optional 93 | localityName = optional 94 | organizationName = optional 95 | organizationalUnitName = optional 96 | commonName = supplied 97 | emailAddress = optional 98 | 99 | #################################################################### 100 | [ req ] 101 | default_bits = 1024 102 | default_keyfile = privkey.pem 103 | distinguished_name = req_distinguished_name 104 | attributes = req_attributes 105 | x509_extensions = v3_ca # The extensions to add to the self signed cert 106 | 107 | # Passwords for private keys if not present they will be prompted for 108 | # input_password = secret 109 | # output_password = secret 110 | 111 | # This sets a mask for permitted string types. There are several options. 112 | # default: PrintableString, T61String, BMPString. 113 | # pkix : PrintableString, BMPString. 114 | # utf8only: only UTF8Strings. 115 | # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). 116 | # MASK:XXXX a literal mask value. 117 | # WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings 118 | # so use this option with caution! 119 | string_mask = nombstr 120 | 121 | # req_extensions = v3_req # The extensions to add to a certificate request 122 | 123 | [ req_distinguished_name ] 124 | countryName = Country Name (2 letter code) 125 | countryName_default = BR 126 | countryName_min = 2 127 | countryName_max = 2 128 | 129 | stateOrProvinceName = State or Province Name (full name) 130 | stateOrProvinceName_default = Espirito Santo 131 | 132 | localityName = Locality Name (eg, city) 133 | localityName_default = Santo Antonio do Canaa 134 | 135 | 0.organizationName = Organization Name (eg, company) 136 | 0.organizationName_default = Sao Tonico Ltda 137 | 138 | # we can do this but it is not needed normally :-) 139 | #1.organizationName = Second Organization Name (eg, company) 140 | #1.organizationName_default = World Wide Web Pty Ltd 141 | 142 | organizationalUnitName = Organizational Unit Name (eg, section) 143 | organizationalUnitName_default = Department of Computer Science 144 | 145 | commonName = Common Name (eg, YOUR name) 146 | commonName_default = Root B 147 | commonName_max = 64 148 | 149 | emailAddress = Email Address 150 | emailAddress_max = 64 151 | 152 | # SET-ex3 = SET extension number 3 153 | 154 | [ req_attributes ] 155 | challengePassword = A challenge password 156 | challengePassword_min = 4 157 | challengePassword_max = 20 158 | 159 | unstructuredName = An optional company name 160 | 161 | [ usr_cert ] 162 | 163 | # These extensions are added when 'ca' signs a request. 164 | 165 | # This goes against PKIX guidelines but some CAs do it and some software 166 | # requires this to avoid interpreting an end user certificate as a CA. 167 | 168 | basicConstraints=CA:FALSE 169 | 170 | # Here are some examples of the usage of nsCertType. If it is omitted 171 | # the certificate can be used for anything *except* object signing. 172 | 173 | # This is OK for an SSL server. 174 | # nsCertType = server 175 | 176 | # For an object signing certificate this would be used. 177 | # nsCertType = objsign 178 | 179 | # For normal client use this is typical 180 | # nsCertType = client, email 181 | 182 | # and for everything including object signing: 183 | # nsCertType = client, email, objsign 184 | 185 | # This is typical in keyUsage for a client certificate. 186 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 187 | 188 | # This will be displayed in Netscape's comment listbox. 189 | nsComment = "OpenSSL Generated Certificate" 190 | 191 | # PKIX recommendations harmless if included in all certificates. 192 | subjectKeyIdentifier=hash 193 | authorityKeyIdentifier=keyid,issuer 194 | 195 | # This stuff is for subjectAltName and issuerAltname. 196 | # Import the email address. 197 | # subjectAltName=email:copy 198 | # An alternative to produce certificates that aren't 199 | # deprecated according to PKIX. 200 | # subjectAltName=email:move 201 | 202 | # Copy subject details 203 | # issuerAltName=issuer:copy 204 | 205 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 206 | #nsBaseUrl 207 | #nsRevocationUrl 208 | #nsRenewalUrl 209 | #nsCaPolicyUrl 210 | #nsSslServerName 211 | 212 | [ v3_req ] 213 | 214 | # Extensions to add to a certificate request 215 | 216 | basicConstraints = CA:FALSE 217 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 218 | 219 | [ v3_ca ] 220 | 221 | 222 | # Extensions for a typical CA 223 | 224 | 225 | # PKIX recommendation. 226 | 227 | subjectKeyIdentifier=hash 228 | 229 | authorityKeyIdentifier=keyid:always,issuer:always 230 | 231 | # This is what PKIX recommends but some broken software chokes on critical 232 | # extensions. 233 | #basicConstraints = critical,CA:true 234 | # So we do this instead. 235 | basicConstraints = CA:true 236 | 237 | # Key usage: this is typical for a CA certificate. However since it will 238 | # prevent it being used as an test self-signed certificate it is best 239 | # left out by default. 240 | # keyUsage = cRLSign, keyCertSign 241 | 242 | # Some might want this also 243 | # nsCertType = sslCA, emailCA 244 | 245 | # Include email address in subject alt name: another PKIX recommendation 246 | # subjectAltName=email:copy 247 | # Copy issuer details 248 | # issuerAltName=issuer:copy 249 | 250 | # DER hex encoding of an extension: beware experts only! 251 | # obj=DER:02:03 252 | # Where 'obj' is a standard or added object 253 | # You can even override a supported extension: 254 | # basicConstraints= critical, DER:30:03:01:01:FF 255 | 256 | [ crl_ext ] 257 | 258 | # CRL extensions. 259 | # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. 260 | 261 | # issuerAltName=issuer:copy 262 | authorityKeyIdentifier=keyid:always,issuer:always 263 | 264 | [ proxy_cert_ext ] 265 | # These extensions should be added when creating a proxy certificate 266 | 267 | # This goes against PKIX guidelines but some CAs do it and some software 268 | # requires this to avoid interpreting an end user certificate as a CA. 269 | 270 | basicConstraints=CA:FALSE 271 | 272 | # Here are some examples of the usage of nsCertType. If it is omitted 273 | # the certificate can be used for anything *except* object signing. 274 | 275 | # This is OK for an SSL server. 276 | # nsCertType = server 277 | 278 | # For an object signing certificate this would be used. 279 | # nsCertType = objsign 280 | 281 | # For normal client use this is typical 282 | # nsCertType = client, email 283 | 284 | # and for everything including object signing: 285 | # nsCertType = client, email, objsign 286 | 287 | # This is typical in keyUsage for a client certificate. 288 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 289 | 290 | # This will be displayed in Netscape's comment listbox. 291 | nsComment = "OpenSSL Generated Certificate" 292 | 293 | # PKIX recommendations harmless if included in all certificates. 294 | subjectKeyIdentifier=hash 295 | authorityKeyIdentifier=keyid,issuer:always 296 | 297 | # This stuff is for subjectAltName and issuerAltname. 298 | # Import the email address. 299 | # subjectAltName=email:copy 300 | # An alternative to produce certificates that aren't 301 | # deprecated according to PKIX. 302 | # subjectAltName=email:move 303 | 304 | # Copy subject details 305 | # issuerAltName=issuer:copy 306 | 307 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 308 | #nsBaseUrl 309 | #nsRevocationUrl 310 | #nsRenewalUrl 311 | #nsCaPolicyUrl 312 | #nsSslServerName 313 | 314 | # This really needs to be in place for it to be a proxy certificate. 315 | proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo 316 | -------------------------------------------------------------------------------- /tests/certs/serverA.cnf: -------------------------------------------------------------------------------- 1 | # 2 | # OpenSSL example configuration file. 3 | # This is mostly being used for generation of certificate requests. 4 | # 5 | 6 | # This definition stops the following lines choking if HOME isn't 7 | # defined. 8 | HOME = . 9 | RANDFILE = $ENV::HOME/.rnd 10 | 11 | # Extra OBJECT IDENTIFIER info: 12 | #oid_file = $ENV::HOME/.oid 13 | oid_section = new_oids 14 | 15 | # To use this configuration file with the "-extfile" option of the 16 | # "openssl x509" utility, name here the section containing the 17 | # X.509v3 extensions to use: 18 | # extensions = 19 | # (Alternatively, use a configuration file that has only 20 | # X.509v3 extensions in its main [= default] section.) 21 | 22 | [ new_oids ] 23 | 24 | # We can add new OIDs in here for use by 'ca' and 'req'. 25 | # Add a simple OID like this: 26 | # testoid1=1.2.3.4 27 | # Or use config file substitution like this: 28 | # testoid2=${testoid1}.5.6 29 | 30 | #################################################################### 31 | [ ca ] 32 | default_ca = CA_default # The default ca section 33 | 34 | #################################################################### 35 | [ CA_default ] 36 | 37 | dir = ./demoCA # Where everything is kept 38 | certs = $dir/certs # Where the issued certs are kept 39 | crl_dir = $dir/crl # Where the issued crl are kept 40 | database = $dir/index.txt # database index file. 41 | #unique_subject = no # Set to 'no' to allow creation of 42 | # several ctificates with same subject. 43 | new_certs_dir = $dir/newcerts # default place for new certs. 44 | 45 | certificate = $dir/cacert.pem # The CA certificate 46 | serial = $dir/serial # The current serial number 47 | crlnumber = $dir/crlnumber # the current crl number 48 | # must be commented out to leave a V1 CRL 49 | crl = $dir/crl.pem # The current CRL 50 | private_key = $dir/private/cakey.pem # The private key 51 | RANDFILE = $dir/private/.rand # private random number file 52 | 53 | x509_extensions = usr_cert # The extensions to add to the cert 54 | 55 | # Comment out the following two lines for the "traditional" 56 | # (and highly broken) format. 57 | name_opt = ca_default # Subject Name options 58 | cert_opt = ca_default # Certificate field options 59 | 60 | # Extension copying option: use with caution. 61 | # copy_extensions = copy 62 | 63 | # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs 64 | # so this is commented out by default to leave a V1 CRL. 65 | # crlnumber must also be commented out to leave a V1 CRL. 66 | # crl_extensions = crl_ext 67 | 68 | default_days = 365 # how long to certify for 69 | default_crl_days= 30 # how long before next CRL 70 | default_md = sha1 # which md to use. 71 | preserve = no # keep passed DN ordering 72 | 73 | # A few difference way of specifying how similar the request should look 74 | # For type CA, the listed attributes must be the same, and the optional 75 | # and supplied fields are just that :-) 76 | policy = policy_match 77 | 78 | # For the CA policy 79 | [ policy_match ] 80 | countryName = match 81 | stateOrProvinceName = match 82 | organizationName = match 83 | organizationalUnitName = optional 84 | commonName = supplied 85 | emailAddress = optional 86 | 87 | # For the 'anything' policy 88 | # At this point in time, you must list all acceptable 'object' 89 | # types. 90 | [ policy_anything ] 91 | countryName = optional 92 | stateOrProvinceName = optional 93 | localityName = optional 94 | organizationName = optional 95 | organizationalUnitName = optional 96 | commonName = supplied 97 | emailAddress = optional 98 | 99 | #################################################################### 100 | [ req ] 101 | default_bits = 1024 102 | default_keyfile = privkey.pem 103 | distinguished_name = req_distinguished_name 104 | attributes = req_attributes 105 | x509_extensions = v3_ca # The extensions to add to the self signed cert 106 | 107 | # Passwords for private keys if not present they will be prompted for 108 | # input_password = secret 109 | # output_password = secret 110 | 111 | # This sets a mask for permitted string types. There are several options. 112 | # default: PrintableString, T61String, BMPString. 113 | # pkix : PrintableString, BMPString. 114 | # utf8only: only UTF8Strings. 115 | # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). 116 | # MASK:XXXX a literal mask value. 117 | # WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings 118 | # so use this option with caution! 119 | string_mask = nombstr 120 | 121 | # req_extensions = v3_req # The extensions to add to a certificate request 122 | 123 | [ req_distinguished_name ] 124 | countryName = Country Name (2 letter code) 125 | countryName_default = BR 126 | countryName_min = 2 127 | countryName_max = 2 128 | 129 | stateOrProvinceName = State or Province Name (full name) 130 | stateOrProvinceName_default = Some-State 131 | stateOrProvinceName_default = Espirito Santo 132 | 133 | localityName = Locality Name (eg, city) 134 | localityName_default = Santo Antonio do Canaa 135 | 136 | 0.organizationName = Organization Name (eg, company) 137 | 0.organizationName_default = Sao Tonico Ltda 138 | 139 | # we can do this but it is not needed normally :-) 140 | #1.organizationName = Second Organization Name (eg, company) 141 | #1.organizationName_default = World Wide Web Pty Ltd 142 | 143 | organizationalUnitName = Organizational Unit Name (eg, section) 144 | organizationalUnitName_default = Department of Computer Science 145 | 146 | commonName = Common Name (eg, YOUR name) 147 | commonName_default = Server A 148 | commonName_max = 64 149 | 150 | emailAddress = Email Address 151 | emailAddress_max = 64 152 | 153 | # SET-ex3 = SET extension number 3 154 | 155 | [ req_attributes ] 156 | challengePassword = A challenge password 157 | challengePassword_min = 4 158 | challengePassword_max = 20 159 | 160 | unstructuredName = An optional company name 161 | 162 | [ usr_cert ] 163 | 164 | # These extensions are added when 'ca' signs a request. 165 | 166 | # This goes against PKIX guidelines but some CAs do it and some software 167 | # requires this to avoid interpreting an end user certificate as a CA. 168 | 169 | basicConstraints=CA:FALSE 170 | 171 | # Here are some examples of the usage of nsCertType. If it is omitted 172 | # the certificate can be used for anything *except* object signing. 173 | 174 | # This is OK for an SSL server. 175 | nsCertType = server 176 | 177 | # For an object signing certificate this would be used. 178 | # nsCertType = objsign 179 | 180 | # For normal client use this is typical 181 | # nsCertType = client, email 182 | 183 | # and for everything including object signing: 184 | # nsCertType = client, email, objsign 185 | 186 | # This is typical in keyUsage for a client certificate. 187 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 188 | 189 | # This will be displayed in Netscape's comment listbox. 190 | nsComment = "OpenSSL Generated Certificate" 191 | 192 | # PKIX recommendations harmless if included in all certificates. 193 | subjectKeyIdentifier=hash 194 | authorityKeyIdentifier=keyid,issuer 195 | 196 | # This stuff is for subjectAltName and issuerAltname. 197 | # Import the email address. 198 | # subjectAltName=email:copy 199 | # An alternative to produce certificates that aren't 200 | # deprecated according to PKIX. 201 | # subjectAltName=email:move 202 | 203 | # Copy subject details 204 | # issuerAltName=issuer:copy 205 | 206 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 207 | #nsBaseUrl 208 | #nsRevocationUrl 209 | #nsRenewalUrl 210 | #nsCaPolicyUrl 211 | #nsSslServerName 212 | 213 | [ v3_req ] 214 | 215 | # Extensions to add to a certificate request 216 | 217 | basicConstraints = CA:FALSE 218 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 219 | 220 | [ v3_ca ] 221 | 222 | 223 | # Extensions for a typical CA 224 | 225 | 226 | # PKIX recommendation. 227 | 228 | subjectKeyIdentifier=hash 229 | 230 | authorityKeyIdentifier=keyid:always,issuer:always 231 | 232 | # This is what PKIX recommends but some broken software chokes on critical 233 | # extensions. 234 | #basicConstraints = critical,CA:true 235 | # So we do this instead. 236 | basicConstraints = CA:true 237 | 238 | # Key usage: this is typical for a CA certificate. However since it will 239 | # prevent it being used as an test self-signed certificate it is best 240 | # left out by default. 241 | # keyUsage = cRLSign, keyCertSign 242 | 243 | # Some might want this also 244 | # nsCertType = sslCA, emailCA 245 | 246 | # Include email address in subject alt name: another PKIX recommendation 247 | # subjectAltName=email:copy 248 | # Copy issuer details 249 | # issuerAltName=issuer:copy 250 | 251 | # DER hex encoding of an extension: beware experts only! 252 | # obj=DER:02:03 253 | # Where 'obj' is a standard or added object 254 | # You can even override a supported extension: 255 | # basicConstraints= critical, DER:30:03:01:01:FF 256 | 257 | [ crl_ext ] 258 | 259 | # CRL extensions. 260 | # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. 261 | 262 | # issuerAltName=issuer:copy 263 | authorityKeyIdentifier=keyid:always,issuer:always 264 | 265 | [ proxy_cert_ext ] 266 | # These extensions should be added when creating a proxy certificate 267 | 268 | # This goes against PKIX guidelines but some CAs do it and some software 269 | # requires this to avoid interpreting an end user certificate as a CA. 270 | 271 | basicConstraints=CA:FALSE 272 | 273 | # Here are some examples of the usage of nsCertType. If it is omitted 274 | # the certificate can be used for anything *except* object signing. 275 | 276 | # This is OK for an SSL server. 277 | # nsCertType = server 278 | 279 | # For an object signing certificate this would be used. 280 | # nsCertType = objsign 281 | 282 | # For normal client use this is typical 283 | # nsCertType = client, email 284 | 285 | # and for everything including object signing: 286 | # nsCertType = client, email, objsign 287 | 288 | # This is typical in keyUsage for a client certificate. 289 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 290 | 291 | # This will be displayed in Netscape's comment listbox. 292 | nsComment = "OpenSSL Generated Certificate" 293 | 294 | # PKIX recommendations harmless if included in all certificates. 295 | subjectKeyIdentifier=hash 296 | authorityKeyIdentifier=keyid,issuer:always 297 | 298 | # This stuff is for subjectAltName and issuerAltname. 299 | # Import the email address. 300 | # subjectAltName=email:copy 301 | # An alternative to produce certificates that aren't 302 | # deprecated according to PKIX. 303 | # subjectAltName=email:move 304 | 305 | # Copy subject details 306 | # issuerAltName=issuer:copy 307 | 308 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 309 | #nsBaseUrl 310 | #nsRevocationUrl 311 | #nsRenewalUrl 312 | #nsCaPolicyUrl 313 | #nsSslServerName 314 | 315 | # This really needs to be in place for it to be a proxy certificate. 316 | proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo 317 | -------------------------------------------------------------------------------- /tests/certs/serverB.cnf: -------------------------------------------------------------------------------- 1 | # 2 | # OpenSSL example configuration file. 3 | # This is mostly being used for generation of certificate requests. 4 | # 5 | 6 | # This definition stops the following lines choking if HOME isn't 7 | # defined. 8 | HOME = . 9 | RANDFILE = $ENV::HOME/.rnd 10 | 11 | # Extra OBJECT IDENTIFIER info: 12 | #oid_file = $ENV::HOME/.oid 13 | oid_section = new_oids 14 | 15 | # To use this configuration file with the "-extfile" option of the 16 | # "openssl x509" utility, name here the section containing the 17 | # X.509v3 extensions to use: 18 | # extensions = 19 | # (Alternatively, use a configuration file that has only 20 | # X.509v3 extensions in its main [= default] section.) 21 | 22 | [ new_oids ] 23 | 24 | # We can add new OIDs in here for use by 'ca' and 'req'. 25 | # Add a simple OID like this: 26 | # testoid1=1.2.3.4 27 | # Or use config file substitution like this: 28 | # testoid2=${testoid1}.5.6 29 | 30 | #################################################################### 31 | [ ca ] 32 | default_ca = CA_default # The default ca section 33 | 34 | #################################################################### 35 | [ CA_default ] 36 | 37 | dir = ./demoCA # Where everything is kept 38 | certs = $dir/certs # Where the issued certs are kept 39 | crl_dir = $dir/crl # Where the issued crl are kept 40 | database = $dir/index.txt # database index file. 41 | #unique_subject = no # Set to 'no' to allow creation of 42 | # several ctificates with same subject. 43 | new_certs_dir = $dir/newcerts # default place for new certs. 44 | 45 | certificate = $dir/cacert.pem # The CA certificate 46 | serial = $dir/serial # The current serial number 47 | crlnumber = $dir/crlnumber # the current crl number 48 | # must be commented out to leave a V1 CRL 49 | crl = $dir/crl.pem # The current CRL 50 | private_key = $dir/private/cakey.pem # The private key 51 | RANDFILE = $dir/private/.rand # private random number file 52 | 53 | x509_extensions = usr_cert # The extensions to add to the cert 54 | 55 | # Comment out the following two lines for the "traditional" 56 | # (and highly broken) format. 57 | name_opt = ca_default # Subject Name options 58 | cert_opt = ca_default # Certificate field options 59 | 60 | # Extension copying option: use with caution. 61 | # copy_extensions = copy 62 | 63 | # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs 64 | # so this is commented out by default to leave a V1 CRL. 65 | # crlnumber must also be commented out to leave a V1 CRL. 66 | # crl_extensions = crl_ext 67 | 68 | default_days = 365 # how long to certify for 69 | default_crl_days= 30 # how long before next CRL 70 | default_md = sha1 # which md to use. 71 | preserve = no # keep passed DN ordering 72 | 73 | # A few difference way of specifying how similar the request should look 74 | # For type CA, the listed attributes must be the same, and the optional 75 | # and supplied fields are just that :-) 76 | policy = policy_match 77 | 78 | # For the CA policy 79 | [ policy_match ] 80 | countryName = match 81 | stateOrProvinceName = match 82 | organizationName = match 83 | organizationalUnitName = optional 84 | commonName = supplied 85 | emailAddress = optional 86 | 87 | # For the 'anything' policy 88 | # At this point in time, you must list all acceptable 'object' 89 | # types. 90 | [ policy_anything ] 91 | countryName = optional 92 | stateOrProvinceName = optional 93 | localityName = optional 94 | organizationName = optional 95 | organizationalUnitName = optional 96 | commonName = supplied 97 | emailAddress = optional 98 | 99 | #################################################################### 100 | [ req ] 101 | default_bits = 1024 102 | default_keyfile = privkey.pem 103 | distinguished_name = req_distinguished_name 104 | attributes = req_attributes 105 | x509_extensions = v3_ca # The extensions to add to the self signed cert 106 | 107 | # Passwords for private keys if not present they will be prompted for 108 | # input_password = secret 109 | # output_password = secret 110 | 111 | # This sets a mask for permitted string types. There are several options. 112 | # default: PrintableString, T61String, BMPString. 113 | # pkix : PrintableString, BMPString. 114 | # utf8only: only UTF8Strings. 115 | # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). 116 | # MASK:XXXX a literal mask value. 117 | # WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings 118 | # so use this option with caution! 119 | string_mask = nombstr 120 | 121 | # req_extensions = v3_req # The extensions to add to a certificate request 122 | 123 | [ req_distinguished_name ] 124 | countryName = Country Name (2 letter code) 125 | countryName_default = BR 126 | countryName_min = 2 127 | countryName_max = 2 128 | 129 | stateOrProvinceName = State or Province Name (full name) 130 | stateOrProvinceName_default = Some-State 131 | stateOrProvinceName_default = Espirito Santo 132 | 133 | localityName = Locality Name (eg, city) 134 | localityName_default = Santo Antonio do Canaa 135 | 136 | 0.organizationName = Organization Name (eg, company) 137 | 0.organizationName_default = Sao Tonico Ltda 138 | 139 | # we can do this but it is not needed normally :-) 140 | #1.organizationName = Second Organization Name (eg, company) 141 | #1.organizationName_default = World Wide Web Pty Ltd 142 | 143 | organizationalUnitName = Organizational Unit Name (eg, section) 144 | organizationalUnitName_default = Department of Computer Science 145 | 146 | commonName = Common Name (eg, YOUR name) 147 | commonName_default = Server B 148 | commonName_max = 64 149 | 150 | emailAddress = Email Address 151 | emailAddress_max = 64 152 | 153 | # SET-ex3 = SET extension number 3 154 | 155 | [ req_attributes ] 156 | challengePassword = A challenge password 157 | challengePassword_min = 4 158 | challengePassword_max = 20 159 | 160 | unstructuredName = An optional company name 161 | 162 | [ usr_cert ] 163 | 164 | # These extensions are added when 'ca' signs a request. 165 | 166 | # This goes against PKIX guidelines but some CAs do it and some software 167 | # requires this to avoid interpreting an end user certificate as a CA. 168 | 169 | basicConstraints=CA:FALSE 170 | 171 | # Here are some examples of the usage of nsCertType. If it is omitted 172 | # the certificate can be used for anything *except* object signing. 173 | 174 | # This is OK for an SSL server. 175 | nsCertType = server 176 | 177 | # For an object signing certificate this would be used. 178 | # nsCertType = objsign 179 | 180 | # For normal client use this is typical 181 | # nsCertType = client, email 182 | 183 | # and for everything including object signing: 184 | # nsCertType = client, email, objsign 185 | 186 | # This is typical in keyUsage for a client certificate. 187 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 188 | 189 | # This will be displayed in Netscape's comment listbox. 190 | nsComment = "OpenSSL Generated Certificate" 191 | 192 | # PKIX recommendations harmless if included in all certificates. 193 | subjectKeyIdentifier=hash 194 | authorityKeyIdentifier=keyid,issuer 195 | 196 | # This stuff is for subjectAltName and issuerAltname. 197 | # Import the email address. 198 | # subjectAltName=email:copy 199 | # An alternative to produce certificates that aren't 200 | # deprecated according to PKIX. 201 | # subjectAltName=email:move 202 | 203 | # Copy subject details 204 | # issuerAltName=issuer:copy 205 | 206 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 207 | #nsBaseUrl 208 | #nsRevocationUrl 209 | #nsRenewalUrl 210 | #nsCaPolicyUrl 211 | #nsSslServerName 212 | 213 | [ v3_req ] 214 | 215 | # Extensions to add to a certificate request 216 | 217 | basicConstraints = CA:FALSE 218 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 219 | 220 | [ v3_ca ] 221 | 222 | 223 | # Extensions for a typical CA 224 | 225 | 226 | # PKIX recommendation. 227 | 228 | subjectKeyIdentifier=hash 229 | 230 | authorityKeyIdentifier=keyid:always,issuer:always 231 | 232 | # This is what PKIX recommends but some broken software chokes on critical 233 | # extensions. 234 | #basicConstraints = critical,CA:true 235 | # So we do this instead. 236 | basicConstraints = CA:true 237 | 238 | # Key usage: this is typical for a CA certificate. However since it will 239 | # prevent it being used as an test self-signed certificate it is best 240 | # left out by default. 241 | # keyUsage = cRLSign, keyCertSign 242 | 243 | # Some might want this also 244 | # nsCertType = sslCA, emailCA 245 | 246 | # Include email address in subject alt name: another PKIX recommendation 247 | # subjectAltName=email:copy 248 | # Copy issuer details 249 | # issuerAltName=issuer:copy 250 | 251 | # DER hex encoding of an extension: beware experts only! 252 | # obj=DER:02:03 253 | # Where 'obj' is a standard or added object 254 | # You can even override a supported extension: 255 | # basicConstraints= critical, DER:30:03:01:01:FF 256 | 257 | [ crl_ext ] 258 | 259 | # CRL extensions. 260 | # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. 261 | 262 | # issuerAltName=issuer:copy 263 | authorityKeyIdentifier=keyid:always,issuer:always 264 | 265 | [ proxy_cert_ext ] 266 | # These extensions should be added when creating a proxy certificate 267 | 268 | # This goes against PKIX guidelines but some CAs do it and some software 269 | # requires this to avoid interpreting an end user certificate as a CA. 270 | 271 | basicConstraints=CA:FALSE 272 | 273 | # Here are some examples of the usage of nsCertType. If it is omitted 274 | # the certificate can be used for anything *except* object signing. 275 | 276 | # This is OK for an SSL server. 277 | # nsCertType = server 278 | 279 | # For an object signing certificate this would be used. 280 | # nsCertType = objsign 281 | 282 | # For normal client use this is typical 283 | # nsCertType = client, email 284 | 285 | # and for everything including object signing: 286 | # nsCertType = client, email, objsign 287 | 288 | # This is typical in keyUsage for a client certificate. 289 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 290 | 291 | # This will be displayed in Netscape's comment listbox. 292 | nsComment = "OpenSSL Generated Certificate" 293 | 294 | # PKIX recommendations harmless if included in all certificates. 295 | subjectKeyIdentifier=hash 296 | authorityKeyIdentifier=keyid,issuer:always 297 | 298 | # This stuff is for subjectAltName and issuerAltname. 299 | # Import the email address. 300 | # subjectAltName=email:copy 301 | # An alternative to produce certificates that aren't 302 | # deprecated according to PKIX. 303 | # subjectAltName=email:move 304 | 305 | # Copy subject details 306 | # issuerAltName=issuer:copy 307 | 308 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 309 | #nsBaseUrl 310 | #nsRevocationUrl 311 | #nsRenewalUrl 312 | #nsCaPolicyUrl 313 | #nsSslServerName 314 | 315 | # This really needs to be in place for it to be a proxy certificate. 316 | proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo 317 | -------------------------------------------------------------------------------- /tests/certs/clientA.cnf: -------------------------------------------------------------------------------- 1 | # 2 | # OpenSSL example configuration file. 3 | # This is mostly being used for generation of certificate requests. 4 | # 5 | 6 | # This definition stops the following lines choking if HOME isn't 7 | # defined. 8 | HOME = . 9 | RANDFILE = $ENV::HOME/.rnd 10 | 11 | # Extra OBJECT IDENTIFIER info: 12 | #oid_file = $ENV::HOME/.oid 13 | oid_section = new_oids 14 | 15 | # To use this configuration file with the "-extfile" option of the 16 | # "openssl x509" utility, name here the section containing the 17 | # X.509v3 extensions to use: 18 | # extensions = 19 | # (Alternatively, use a configuration file that has only 20 | # X.509v3 extensions in its main [= default] section.) 21 | 22 | [ new_oids ] 23 | 24 | # We can add new OIDs in here for use by 'ca' and 'req'. 25 | # Add a simple OID like this: 26 | # testoid1=1.2.3.4 27 | # Or use config file substitution like this: 28 | # testoid2=${testoid1}.5.6 29 | 30 | #################################################################### 31 | [ ca ] 32 | default_ca = CA_default # The default ca section 33 | 34 | #################################################################### 35 | [ CA_default ] 36 | 37 | dir = ./demoCA # Where everything is kept 38 | certs = $dir/certs # Where the issued certs are kept 39 | crl_dir = $dir/crl # Where the issued crl are kept 40 | database = $dir/index.txt # database index file. 41 | #unique_subject = no # Set to 'no' to allow creation of 42 | # several ctificates with same subject. 43 | new_certs_dir = $dir/newcerts # default place for new certs. 44 | 45 | certificate = $dir/cacert.pem # The CA certificate 46 | serial = $dir/serial # The current serial number 47 | crlnumber = $dir/crlnumber # the current crl number 48 | # must be commented out to leave a V1 CRL 49 | crl = $dir/crl.pem # The current CRL 50 | private_key = $dir/private/cakey.pem # The private key 51 | RANDFILE = $dir/private/.rand # private random number file 52 | 53 | x509_extensions = usr_cert # The extensions to add to the cert 54 | 55 | # Comment out the following two lines for the "traditional" 56 | # (and highly broken) format. 57 | name_opt = ca_default # Subject Name options 58 | cert_opt = ca_default # Certificate field options 59 | 60 | # Extension copying option: use with caution. 61 | # copy_extensions = copy 62 | 63 | # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs 64 | # so this is commented out by default to leave a V1 CRL. 65 | # crlnumber must also be commented out to leave a V1 CRL. 66 | # crl_extensions = crl_ext 67 | 68 | default_days = 365 # how long to certify for 69 | default_crl_days= 30 # how long before next CRL 70 | default_md = sha1 # which md to use. 71 | preserve = no # keep passed DN ordering 72 | 73 | # A few difference way of specifying how similar the request should look 74 | # For type CA, the listed attributes must be the same, and the optional 75 | # and supplied fields are just that :-) 76 | policy = policy_match 77 | 78 | # For the CA policy 79 | [ policy_match ] 80 | countryName = match 81 | stateOrProvinceName = match 82 | organizationName = match 83 | organizationalUnitName = optional 84 | commonName = supplied 85 | emailAddress = optional 86 | 87 | # For the 'anything' policy 88 | # At this point in time, you must list all acceptable 'object' 89 | # types. 90 | [ policy_anything ] 91 | countryName = optional 92 | stateOrProvinceName = optional 93 | localityName = optional 94 | organizationName = optional 95 | organizationalUnitName = optional 96 | commonName = supplied 97 | emailAddress = optional 98 | 99 | #################################################################### 100 | [ req ] 101 | default_bits = 1024 102 | default_keyfile = privkey.pem 103 | distinguished_name = req_distinguished_name 104 | attributes = req_attributes 105 | x509_extensions = v3_ca # The extensions to add to the self signed cert 106 | 107 | # Passwords for private keys if not present they will be prompted for 108 | # input_password = secret 109 | # output_password = secret 110 | 111 | # This sets a mask for permitted string types. There are several options. 112 | # default: PrintableString, T61String, BMPString. 113 | # pkix : PrintableString, BMPString. 114 | # utf8only: only UTF8Strings. 115 | # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). 116 | # MASK:XXXX a literal mask value. 117 | # WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings 118 | # so use this option with caution! 119 | string_mask = nombstr 120 | 121 | # req_extensions = v3_req # The extensions to add to a certificate request 122 | 123 | [ req_distinguished_name ] 124 | countryName = Country Name (2 letter code) 125 | countryName_default = BR 126 | countryName_min = 2 127 | countryName_max = 2 128 | 129 | stateOrProvinceName = State or Province Name (full name) 130 | stateOrProvinceName_default = Some-State 131 | stateOrProvinceName_default = Espirito Santo 132 | 133 | localityName = Locality Name (eg, city) 134 | localityName_default = Santo Antonio do Canaa 135 | 136 | 0.organizationName = Organization Name (eg, company) 137 | 0.organizationName_default = Sao Tonico Ltda 138 | 139 | # we can do this but it is not needed normally :-) 140 | #1.organizationName = Second Organization Name (eg, company) 141 | #1.organizationName_default = World Wide Web Pty Ltd 142 | 143 | organizationalUnitName = Organizational Unit Name (eg, section) 144 | organizationalUnitName_default = Department of Computer Science 145 | 146 | commonName = Common Name (eg, YOUR name) 147 | commonName_default = Client A 148 | commonName_max = 64 149 | 150 | emailAddress = Email Address 151 | emailAddress_max = 64 152 | 153 | # SET-ex3 = SET extension number 3 154 | 155 | [ req_attributes ] 156 | challengePassword = A challenge password 157 | challengePassword_min = 4 158 | challengePassword_max = 20 159 | 160 | unstructuredName = An optional company name 161 | 162 | [ usr_cert ] 163 | 164 | # These extensions are added when 'ca' signs a request. 165 | 166 | # This goes against PKIX guidelines but some CAs do it and some software 167 | # requires this to avoid interpreting an end user certificate as a CA. 168 | 169 | basicConstraints=CA:FALSE 170 | 171 | # Here are some examples of the usage of nsCertType. If it is omitted 172 | # the certificate can be used for anything *except* object signing. 173 | 174 | # This is OK for an SSL server. 175 | # nsCertType = server 176 | 177 | # For an object signing certificate this would be used. 178 | # nsCertType = objsign 179 | 180 | # For normal client use this is typical 181 | # nsCertType = client, email 182 | 183 | # and for everything including object signing: 184 | # nsCertType = client, email, objsign 185 | 186 | # This is typical in keyUsage for a client certificate. 187 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 188 | 189 | # This will be displayed in Netscape's comment listbox. 190 | nsComment = "OpenSSL Generated Certificate" 191 | 192 | # PKIX recommendations harmless if included in all certificates. 193 | subjectKeyIdentifier=hash 194 | authorityKeyIdentifier=keyid,issuer 195 | 196 | # This stuff is for subjectAltName and issuerAltname. 197 | # Import the email address. 198 | # subjectAltName=email:copy 199 | # An alternative to produce certificates that aren't 200 | # deprecated according to PKIX. 201 | # subjectAltName=email:move 202 | 203 | # Copy subject details 204 | # issuerAltName=issuer:copy 205 | 206 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 207 | #nsBaseUrl 208 | #nsRevocationUrl 209 | #nsRenewalUrl 210 | #nsCaPolicyUrl 211 | #nsSslServerName 212 | 213 | [ v3_req ] 214 | 215 | # Extensions to add to a certificate request 216 | 217 | basicConstraints = CA:FALSE 218 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 219 | 220 | [ v3_ca ] 221 | 222 | 223 | # Extensions for a typical CA 224 | 225 | 226 | # PKIX recommendation. 227 | 228 | subjectKeyIdentifier=hash 229 | 230 | authorityKeyIdentifier=keyid:always,issuer:always 231 | 232 | # This is what PKIX recommends but some broken software chokes on critical 233 | # extensions. 234 | #basicConstraints = critical,CA:true 235 | # So we do this instead. 236 | basicConstraints = CA:true 237 | 238 | # Key usage: this is typical for a CA certificate. However since it will 239 | # prevent it being used as an test self-signed certificate it is best 240 | # left out by default. 241 | # keyUsage = cRLSign, keyCertSign 242 | 243 | # Some might want this also 244 | # nsCertType = sslCA, emailCA 245 | 246 | # Include email address in subject alt name: another PKIX recommendation 247 | # subjectAltName=email:copy 248 | # Copy issuer details 249 | # issuerAltName=issuer:copy 250 | 251 | # DER hex encoding of an extension: beware experts only! 252 | # obj=DER:02:03 253 | # Where 'obj' is a standard or added object 254 | # You can even override a supported extension: 255 | # basicConstraints= critical, DER:30:03:01:01:FF 256 | 257 | [ crl_ext ] 258 | 259 | # CRL extensions. 260 | # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. 261 | 262 | # issuerAltName=issuer:copy 263 | authorityKeyIdentifier=keyid:always,issuer:always 264 | 265 | [ proxy_cert_ext ] 266 | # These extensions should be added when creating a proxy certificate 267 | 268 | # This goes against PKIX guidelines but some CAs do it and some software 269 | # requires this to avoid interpreting an end user certificate as a CA. 270 | 271 | basicConstraints=CA:FALSE 272 | 273 | # Here are some examples of the usage of nsCertType. If it is omitted 274 | # the certificate can be used for anything *except* object signing. 275 | 276 | # This is OK for an SSL server. 277 | # nsCertType = server 278 | 279 | # For an object signing certificate this would be used. 280 | # nsCertType = objsign 281 | 282 | # For normal client use this is typical 283 | # nsCertType = client, email 284 | 285 | # and for everything including object signing: 286 | # nsCertType = client, email, objsign 287 | 288 | # This is typical in keyUsage for a client certificate. 289 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment 290 | 291 | # This will be displayed in Netscape's comment listbox. 292 | nsComment = "OpenSSL Generated Certificate" 293 | 294 | # PKIX recommendations harmless if included in all certificates. 295 | subjectKeyIdentifier=hash 296 | authorityKeyIdentifier=keyid,issuer:always 297 | 298 | # This stuff is for subjectAltName and issuerAltname. 299 | # Import the email address. 300 | # subjectAltName=email:copy 301 | # An alternative to produce certificates that aren't 302 | # deprecated according to PKIX. 303 | # subjectAltName=email:move 304 | 305 | # Copy subject details 306 | # issuerAltName=issuer:copy 307 | 308 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem 309 | #nsBaseUrl 310 | #nsRevocationUrl 311 | #nsRenewalUrl 312 | #nsCaPolicyUrl 313 | #nsSslServerName 314 | 315 | # This really needs to be in place for it to be a proxy certificate. 316 | proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo 317 | --------------------------------------------------------------------------------