pax_global_header00006660000000000000000000000064147646424240014527gustar00rootroot0000000000000052 comment=6369440fdd7a9be08cc1b98792e342af8f15049c copas-4.8.0/000077500000000000000000000000001476464242400126455ustar00rootroot00000000000000copas-4.8.0/.editorconfig000066400000000000000000000002731476464242400153240ustar00rootroot00000000000000root = true [*] end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 [*.lua] indent_style = space indent_size = 2 [Makefile] indent_style = tab copas-4.8.0/.github/000077500000000000000000000000001476464242400142055ustar00rootroot00000000000000copas-4.8.0/.github/workflows/000077500000000000000000000000001476464242400162425ustar00rootroot00000000000000copas-4.8.0/.github/workflows/luacheck.yml000066400000000000000000000016041476464242400205450ustar00rootroot00000000000000name: Luacheck concurrency: # for PR's cancel the running task, if another commit is pushed group: ${{ github.workflow }} ${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} on: # build on PR and push-to-master. This works for short-lived branches, and saves # CPU cycles on duplicated tests. # For long-lived branches that diverge, you'll want to run on all pushes, not # just on push-to-master. pull_request: {} push: branches: - master jobs: luacheck: runs-on: ubuntu-20.04 steps: - name: Checkout uses: actions/checkout@v4 - uses: leafo/gh-actions-lua@v10 - uses: leafo/gh-actions-luarocks@v4 - name: Lint rockspecs run: | for i in $(find . -type f -name "*.rockspec"); do echo $i; luarocks lint $i || exit 1; done - name: Luacheck uses: lunarmodules/luacheck@v0 copas-4.8.0/.github/workflows/unix_build.yml000066400000000000000000000025011476464242400211250ustar00rootroot00000000000000name: "Unix build" concurrency: # for PR's cancel the running task, if another commit is pushed group: ${{ github.workflow }} ${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} on: # build on PR and push-to-master. This works for short-lived branches, and saves # CPU cycles on duplicated tests. # For long-lived branches that diverge, you'll want to run on all pushes, not # just on push-to-master. pull_request: {} push: branches: - master jobs: test: runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: luaVersion: ["5.1", "5.2", "5.3", "5.4", "luajit-2.1.0-beta3"] steps: - uses: actions/checkout@v4 - uses: leafo/gh-actions-lua@v10 with: luaVersion: ${{ matrix.luaVersion }} - uses: leafo/gh-actions-luarocks@v4 - name: dependencies run: | luarocks install luacov-coveralls luarocks install luasec luarocks install luasystem - name: generate test certificates run: | make certs - name: build run: | luarocks make - name: test run: | make coverage - name: Report test coverage if: success() continue-on-error: true run: luacov-coveralls env: COVERALLS_REPO_TOKEN: ${{ github.token }} copas-4.8.0/.gitignore000066400000000000000000000001061476464242400146320ustar00rootroot00000000000000.DS_Store **/*.srl **/*.pem *.rock luacov.report.out luacov.stats.out copas-4.8.0/.luacheckrc000066400000000000000000000006711476464242400147560ustar00rootroot00000000000000--std = "ngx_lua+busted" unused_args = false redefined = false max_line_length = false globals = { --"_KONG", --"kong", --"ngx.IS_CLI", } not_globals = { "string.len", "table.getn", } ignore = { --"6.", -- ignore whitespace warnings } exclude_files = { ".install/**", ".luarocks/**", --"spec/fixtures/invalid-module.lua", --"spec-old-api/fixtures/invalid-module.lua", } copas-4.8.0/.luacov000066400000000000000000000001051476464242400141330ustar00rootroot00000000000000modules = { ["copas"] = "src/copas.lua", ["copas.*"] = "src" } copas-4.8.0/LICENSE000066400000000000000000000021021476464242400136450ustar00rootroot00000000000000Copyright © 2005-2013 Kepler Project, 2015-2025 Thijs Schreijer. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. copas-4.8.0/Makefile000066400000000000000000000046731476464242400143170ustar00rootroot00000000000000# $Id: Makefile,v 1.3 2007/10/29 22:50:16 carregal Exp $ DESTDIR ?= # Default prefix PREFIX ?= /usr/local # System's lua directory (where Lua libraries are installed) LUA_DIR ?= $(PREFIX)/share/lua/5.1 DELIM=-e "print(([[=]]):rep(70))" PKGPATH=-e "package.path='src/?.lua;'..package.path" # Lua interpreter LUA=lua .PHONY: certs install: mkdir -p $(DESTDIR)$(LUA_DIR)/copas cp src/copas.lua $(DESTDIR)$(LUA_DIR)/copas.lua cp src/copas/ftp.lua $(DESTDIR)$(LUA_DIR)/copas/ftp.lua cp src/copas/smtp.lua $(DESTDIR)$(LUA_DIR)/copas/smtp.lua cp src/copas/http.lua $(DESTDIR)$(LUA_DIR)/copas/http.lua cp src/copas/timer.lua $(DESTDIR)$(LUA_DIR)/copas/timer.lua cp src/copas/lock.lua $(DESTDIR)$(LUA_DIR)/copas/lock.lua cp src/copas/semaphore.lua $(DESTDIR)$(LUA_DIR)/copas/semaphore.lua cp src/copas/queue.lua $(DESTDIR)$(LUA_DIR)/copas/queue.lua tests/certs/clientA.pem: cd ./tests/certs && \ ./rootA.sh && \ ./rootB.sh && \ ./serverA.sh && \ ./serverB.sh && \ ./clientA.sh && \ ./clientB.sh && \ cd ../.. certs: tests/certs/clientA.pem test: certs $(LUA) $(DELIM) $(PKGPATH) tests/close.lua $(LUA) $(DELIM) $(PKGPATH) tests/connecttwice.lua $(LUA) $(DELIM) $(PKGPATH) tests/errhandlers.lua $(LUA) $(DELIM) $(PKGPATH) tests/exit.lua $(LUA) $(DELIM) $(PKGPATH) tests/exittest.lua $(LUA) $(DELIM) $(PKGPATH) tests/http-timeout.lua $(LUA) $(DELIM) $(PKGPATH) tests/httpredirect.lua $(LUA) $(DELIM) $(PKGPATH) tests/largetransfer.lua $(LUA) $(DELIM) $(PKGPATH) tests/lock.lua $(LUA) $(DELIM) $(PKGPATH) tests/loop_starter.lua $(LUA) $(DELIM) $(PKGPATH) tests/no_luasocket.lua $(LUA) $(DELIM) $(PKGPATH) tests/pause.lua $(LUA) $(DELIM) $(PKGPATH) tests/queue.lua $(LUA) $(DELIM) $(PKGPATH) tests/removeserver.lua $(LUA) $(DELIM) $(PKGPATH) tests/removethread.lua $(LUA) $(DELIM) $(PKGPATH) tests/request.lua 'http://www.google.com' $(LUA) $(DELIM) $(PKGPATH) tests/request.lua 'https://www.google.nl' true $(LUA) $(DELIM) $(PKGPATH) tests/semaphore.lua $(LUA) $(DELIM) $(PKGPATH) tests/starve.lua $(LUA) $(DELIM) $(PKGPATH) tests/tcptimeout.lua $(LUA) $(DELIM) $(PKGPATH) tests/timer.lua $(LUA) $(DELIM) $(PKGPATH) tests/timeout_errors.lua $(LUA) $(DELIM) $(PKGPATH) tests/tls-sni.lua $(LUA) $(DELIM) $(PKGPATH) tests/udptimeout.lua $(LUA) $(DELIM) coverage: $(RM) luacov.stats.out $(MAKE) test LUA="$(LUA) -lluacov" luacov clean: $(RM) luacov.stats.out luacov.report.out $(RM) tests/certs/*.pem tests/certs/*.srl copas-4.8.0/Makefile.win000066400000000000000000000010641476464242400151020ustar00rootroot00000000000000# $Id: Makefile.win,v 1.5 2008/01/16 18:07:17 mascarenhas Exp $ LUA_DIR= c:\lua5.1\lua build clean: install: mkdir "$(LUA_DIR)\copas" copy src\copas.lua "$(LUA_DIR)\copas.lua" copy src\copas\ftp.lua "$(LUA_DIR)\copas\ftp.lua" copy src\copas\http.lua "$(LUA_DIR)\copas\http.lua" copy src\copas\lock.lua "$(LUA_DIR)\copas\lock.lua" copy src\copas\queue.lua "$(LUA_DIR)\copas\queue.lua" copy src\copas\semaphore.lua "$(LUA_DIR)\copas\semaphore.lua" copy src\copas\smtp.lua "$(LUA_DIR)\copas\smtp.lua" copy src\copas\timer.lua "$(LUA_DIR)\copas\timer.lua" copas-4.8.0/README.md000066400000000000000000000041361476464242400141300ustar00rootroot00000000000000# Copas 4.7 [![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) [![Coveralls code coverage](https://img.shields.io/coveralls/github/lunarmodules/copas?logo=coveralls)](https://coveralls.io/github/lunarmodules/copas) [![Luacheck](https://github.com/lunarmodules/copas/workflows/Luacheck/badge.svg)](https://github.com/lunarmodules/copas/actions) [![SemVer](https://img.shields.io/github/v/tag/lunarmodules/copas?color=brightgreen&label=SemVer&logo=semver&sort=semver)](CHANGELOG.md) [![Licence](http://img.shields.io/badge/Licence-MIT-brightgreen.svg)](LICENSE) 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. 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. 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. 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). The easiest way to install Copas is through [LuaRocks](https://luarocks.org/): ``` luarocks install copas ``` For more details see [the documentation](http://lunarmodules.github.io/copas/). ### Releasing a new version - update changelog in docs (`index.html`, update `history` and `status` sections) - update version in `copas.lua` - update version at the top of this README, - update copyright years if needed - update rockspec - commit as `release X.Y.Z` - tag as `vX_Y_Z` and as `X.Y.Z` - push commit and tag - upload to luarocks - test luarocks installation copas-4.8.0/bin/000077500000000000000000000000001476464242400134155ustar00rootroot00000000000000copas-4.8.0/bin/copas.lua000077500000000000000000000041301476464242400152260ustar00rootroot00000000000000#!/usr/bin/env lua -- luacheck: globals copas copas = require("copas") -- Error handler that forces an application exit local function errorhandler(err, co, skt) io.stderr:write(copas.gettraceback(err, co, skt).."\n") os.exit(1) end local function version_info() print(copas._VERSION, copas._COPYRIGHT) print(copas._DESCRIPTION) print("Lua VM:", _G._VERSION) end local function load_lib(lib_name) require(lib_name) end local function run_code(code) if loadstring then -- deprecated in Lua 5.2 assert(loadstring(code, "command line"))() else assert(load(code, "command line"))() end end local function run_stdin() assert(loadfile())() end local function run_file(filename, i) -- shift arguments, such that the Lua file being executed is at index 0. The -- first argument following the name is at index 1. local last = #arg local first = #arg for idx, v in pairs(arg) do if idx < first then first = idx end end for n = first - i, last do arg[n] = arg[n+i] -- luacheck: ignore end assert(loadfile(filename))() end local function show_usage() print([[ usage: copas [options]... [script [args]...]. Available options are: -e chunk Execute string 'chunk'. -l name Require library 'name'. -v Show version information. -- Stop handling options. - Execute stdin and stop handling options.]]) os.exit(1) end copas(function() copas.seterrorhandler(errorhandler) local i = 0 while i < math.max(#arg, 1) do -- if no args, use 1 being 'nil' i = i + 1 local handled = false local opt = arg[i] or "-" -- set default action if no args -- options to continue handling if opt == "-v" then version_info() handled = true end if opt == "-l" then i = i + 1 load_lib(arg[i]) handled = true end if opt == "-e" then i = i + 1 run_code(arg[i]) handled = true end -- options that terminate handling if opt == "--" then return end if opt == "-" then return run_stdin() end if opt:sub(1,1) == "-" and not handled then return show_usage() end if not handled then return run_file(opt, i) end end end) copas-4.8.0/copas-cvs-6.rockspec000066400000000000000000000034401476464242400164420ustar00rootroot00000000000000local package_name = "copas" local package_version = "cvs" local rockspec_revision = "6" local github_account_name = "lunarmodules" local github_repo_name = package_name package = package_name version = package_version.."-"..rockspec_revision source = { url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", branch = (package_version == "cvs") and "master" or nil, tag = (package_version ~= "cvs") and package_version or nil, } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, } dependencies = { "lua >= 5.1, < 5.5", "luasocket >= 2.1, <= 3.0rc1-2", "coxpcall >= 1.14", "binaryheap >= 0.4", "timerwheel ~> 1", } build = { type = "builtin", install = { bin = { copas = "bin/copas.lua", } }, modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.timer"] = "src/copas/timer.lua", ["copas.lock"] = "src/copas/lock.lua", ["copas.semaphore"] = "src/copas/semaphore.lua", ["copas.queue"] = "src/copas/queue.lua", }, copy_directories = { "docs", }, } copas-4.8.0/docs/000077500000000000000000000000001476464242400135755ustar00rootroot00000000000000copas-4.8.0/docs/copas.png000066400000000000000000000266501476464242400154210ustar00rootroot00000000000000PNG  IHDRL\ pHYsHHFk>-IDATx=ixTEno $@UdQAQdQ\A@ATaI%aIHb!KޏJ_nӝN 9_|Mnݪs:[BswzC?az-#Z,E_B== Ts1M/D@]r??pF4S]cLSWQkf:m-auZL `۷ԩSnWAu7J9B@.;-ջ!D?2!>qҥKN:k ,ڷo_yy9nte˖L&Ƹ!333 W^!!!~g0B@ժTT@CXNG1N)#Akyן?b.]4dȐxبQӵ"1f41FQRftflfG?… D'^R9$h׿.~q^NNEee,S0tGG-Z2cF^O(eƞnJ#G:twsvZC}iii2t `6Zu{߿cbbF7=y#@ p A^yeى2t- 8ԩSvZtiRRcOçMf۳^2 W^}衇O`~jB־K:쬬*PBBM&U t0nkjoׁcŸccvڰpPjBcfYQ & qƍ7NkTѭڷSu0iii#FX,yyyj bsBpyywIR \ rco~sIvԵY^~I$뮻&NǏ6rHAؽ{.._\UU5p@pHU\ic*wȑ-2 S%%%=i{55+$EsWkS~ev-mYt hyRk׮N{MOOOIIA-[c233njsK.@MM͔)SRSS @9wÇG9cƌ޽{qB'txD,+ŬY3ZObPE*999˖-ܿEƌs>쩧۷/99yF1777(((111((ȝ1|G !TPPw޲ٳg D1HQ%Fz6=hddcV|'9@+BEEEnjjZpaDDtƌ:n׮]Æ SE$^.\|E.^h0)0,UpQ8籱k׮=}tEE[ws;!m}o"Խ矟,ɹA=֬noذ۷/T_:ti^z%TQQ|'+FsO^O<8#"ف$ɘZl6m /~ 4͛7766Bl6L&411q̘1W\q"!% lͧ 8{={Z !0c\Q 9X }{7%eÆ eI‚ NNG1tiia !msO666+ӦM&X,[l\tvWcqzJ?q뢢r!^5~g/Q=rtt~7~ᒄ~c_$zgC_-aJ׭zu;yIJtmƄ Q6ƨ#= 8Ƙ1966̙GVW"HeHMBVLJJ*..>vN>|Y.(ܬ<辌ME"v 1)I=[2q[+W~f 瞛 ФulT+ !РR1JKJJ6lؙ3ga:V#FGalts{c@)@~oI”raO>pHY6t`!aJO<1ydJT+DB"J)z_˗$q:䓟F!$B;|ADZ0W_]3Y-g?C-..V9Õ'OV žDs2k֎͛$?JQ.|);qN9c5pH߻1cl's_= SEiʹ"\YQ1Fs}ݣ?>ZhV<SC)P__{]|999yРA$]pʕ+K.2djOΚ+( TLqnz__D)7mlP -$ +Jczz}K{6)$]]̼V\\&fOBm7E~>?~ӧ_M}l1ckff$*J'cߡ&4tɓM5*"22GG)ꫢ>+((GHBHI"b5jD@"d67+#Ȳ`^4cVJEQ@ԐSN,3}e$u;1¹ӟ'&&bD_|/ +f ,3Z{n$Q)HX0vku.#-.x۶ݏ}1c Ӗ- M!UªijW;DN4.Z4UfbDj =!5ʐ!f;9ի͛7Ϟ=;z6#ZhfVQѸrnyG]( #k1"n"e3"$8B0cWgdSڌ1vcG>))SԩS+++ y+RVV(k0˩w͚jsޭ>Ƙ1Ƀx`L'W;"sFvϜ >}1BH!3]~ww#ħS.$o~s:$@Qؘ1KO?=ufٳ,Xt@}  u8\p]gH]İSc[4P/Y2AQȢwƍ&HF׫Zm gdd (?-*vᣢO4iNGTr']wE0F/-:Lf~;C8GwIIOs֯0Rc>δy\@qLYđL7ϯ$ v%&&.\гe!VĺǘHFiHtt1]Є׿fKaBxΝo[<UT_} \(x{R}W&JL`ؾlqq`"^ӧO}`u:B8v ]Q1x`HfsÖ- .\o Y?yJgt=\T TdawI֋H~ǎs"*&.^.'Z8l6SEb>\xb%BZ;Sq;]jj n,JXwjArr,uuum-Tu…7 ~x]vp 9R6c,?u׃DEE=cm6k]If=+1*b2m-Vͣ:u&B.)&I(ѨLٳ} `%9`,UUee#g{ΙAWVVĀ{ZtxP!fKb#dڳ\ 'rhVƽGM YJJ E$`dÆSMm Jyy7-‰;wI=N0AmVXxFG[T(>]BƆ+Wǎ|hɨ9".e_Z c(quу'^]] .yʒ87 p ɽFhpߒ%c>>ѣ%ǎvBz䑱<3.,ɗP #.z≃99jD]ucD|⍱c=O$88MSmh*&՛9c,,f2/=v2!4: 㜿w͙;7yBC ol}wB ėRoĔ^ 9@#d^QR_&?y9) gҀs9"lmۖm ΧO_Ibjklc#8Bx@u򖺂V F/ !XZ4 F)嵎5$͠"7 ӿ^@QP[kuX@*D1؉ejkrN:EN \zx F{&wYn%;!,zϋdX< wq^e?ԌoC(Sq"Nr{,3Ƹ;<&>>>K,۷/؞J3X$PaCP>oh֬g6B*J#6Qh29s AO7o|I 5 Dsq\;!Ε?LQXmm믟tu 8 Z(EQXyyæM5m81_ {0F@Anْ]YycwU=#Bb9sfԩzhGC#zҥ1J||ώ N:gE!CByf=[i) Buʌ?yiR ?]9o ,8E, Xh5)3ٺuS Qaڴ8??CsZ_fSZ,keFsyM4@BBʕ+u:5xC]Qӥ >>.ڿ?W&`|D QƽqM&8[;@t׷o_9=$& Ue3XKbѣha\MM,$&CJmi -UCAAA"ŽA!z0]>̇t8BDx8g+r0 xj=!&cճm5 hW\ Q3QLiX0c\\Cݽalo{D@ khhht$%%9m`+,X]~(,[}|:4tРBCBB|zMÆ]־+V7x BytEL4=Gs.xYUe $ox H]UUURU@ c9 !& @'*8#"?5 2siiCQQ+5GSNX^(eI@8rg'AqqqHHo힯t4k"554--,;ƆU~{^jj_U1cJ6n|xo W˗E4F jBs,BϞ8Lҵp:J3`ƌx_r<#c?y,StHQzm$g$N*O\ұDvS}ɘ% E:wŒt=: zR^nH䋐$Qp!QqLøp`p Tt'?ILL.ł'cJ w9SRB{ݛEV<|&-0%)!r:i߾& l|~[qMYXZjI"pˀ`&N쟖Z|[ޫ.((eك[sbQS}8o9#'BCz}KI"`o;.$GTv.s.IH:ևra*n9ouтԔO>ʕ+ 9IW`w 1/9&f L&Ϯ\T 23L0EjsX'P hϝb78Ę1$bzCzzܶmzL`FmJ0cA, ^whGo]' 00pҥF/-tx1>BgTEa'2%փuS_\tM|/_:vd6ȺuYbc{-]:lXرܔ)հ?tjAAM\\oYG?ܪFϙ8sf 6ш G3^{-b[scJ5kƈS!Va}LI&i B]Zg|6v;5js Zh UО܉[z?y课^S$BM_|ˏr BbΜ9xϞ_,vW/sUףq ӧo}r<.oܰW_b3t:dpN{nv#Gagt#G׌ *:0Ƴfmۻ7WSCMb} $I0!nĘ ܸtL޽Wn`lb d1;8R )S32B', 3f3'eDI*+3NŋI!ƚLIŅTіz/rbby-o "ڵk!݂/^>jFEqiJv Y"sι @MSЫ[\{|"s(BN щ\V5@hDYY۱c… <b]رC\K>fXzfX,r;:s"iE(?zbwDa M16[vt)S.\.F_z̙"rn3bSrE ˮ<>@TS~z]8|J3BCC vd\ Նܼyntt$w`A^̄kG6Cs޶m[~~?ޫW;@6iVVU57m:i61!w:ʹ90&}}Vc%!y;ݱcGnnի]3Z#xb^^lg+XA Mo]^~"3%: "}Z  ,,~xddd.Yo>nܸ8E=L7w}ތI]nZ,X,{nȑ&3$G9ww{J]Em2ٲ;HTiƇ]UryiaN@]6l_d7[+# /̼VZZ nJiձ#va#"Ǝ7cFqQQ-IѭQ\2q[]]݆ f̘5ʽɄP-BTfs򮗖6TW[f,L:7** )ϐ!#FD$%L:q!lȑZuwN4E&ԫ+VBAd[mVU*t:l4JƀCPii0ƪhqj'Nll>9 vwgwu9noBNG対?y-EΚjNJQ?~ի˗/ljڜ̋8PTTԷoQF R"+>sj0XVBNScGة̼rʕ+BzmM[3imphת7oYnI&BT[8''')))""" @EtJ:O>9v옿>h q97^.܁ddpiۋΝ;9tsIMMURss3Xӹ겊644Օgر*NㅨFoGu=(YMMMx7ʄYb`7xCxz}ZZzDK Y+B- vj,)),#Fh~ ݋N 0IhMU|{pAkRͱ!0zl mf-h/%Ҫ{?p[ۇ9=L4 Copas - Coroutine Oriented Portable Asynchronous Services for Lua
Copas
Coroutine Oriented Portable Asynchronous Services for Lua

Overview

Copas is a dispatcher based on coroutines that can be used for asynchroneous networking. For example TCP or UDP based servers. But it also features timers and client support for http(s), ftp and smtp requests.

It uses LuaSocket as the interface with the TCP/IP stack and LuaSec for ssl support.

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 as an example.

Copas is free software and uses the same license as Lua 5.1 to 5.4

Status

Current version is 4.8.0 and was developed for Lua 5.1 to 5.4.

Download

Copas can be downloaded from its Github page, in the "Downloads" tab.

You can also install Copas using LuaRocks:

luarocks install copas

Dependencies

Copas depends on LuaSocket (or LuaSystem), Coxpcall (only when using Lua 5.1), and (optionally) LuaSec.

History

Copas 4.8.0 [13/Mar/2025]
  • Change: Copas no longer requires LuaSocket, if no sockets are needed, LuaSystem will be enough as a fallback.
  • Feat: added copas.gettime(), which transparently maps to either LuaSockets or LuaSystems implementation, ensuring independence of the availability of either one of those.
  • Feat: Controlled exit of the Copas loop. Adding copas.exit(), copas.exiting(), and copas.waitforexit().
Copas 4.7.1 [9/Mar/2024]
  • Fix: copas.removethread would not remove a sleeping thread immediately (it would not execute, but would prevent the Copas loop from exiting until the timer expired).
  • Fix: queue:finish will return after the last item has been handled, not just popped (if using workers).
Copas 4.7.0 [15/Jan/2023]
  • Fix: windows makefile didn't include all submodules.
  • Fix: creating a new timer with a bad delay setting would throw a bad error message.
  • Refactor: submodules are now part of copas (lazily loaded) and do not need to be required anymore
  • Feat: runtime script added to directly run copas based code
  • Fix: improved socket wrapper for lacking LuaSec methods (setoption, getoption, getpeername, getsockname)
  • Feat: added LuaSec methods to wrapper (getalpn, getsniname)
Copas 4.6.0 [30/Dec/2022]
  • Added: for timeouts in copas, lock, semaphore, and queue, allow math.huge to specify no timeout/wait forever. Using math.huge over long timeouts will reduce pressure on the timer-wheel.
  • Refactor: increase ringsize (timer-wheel) from 1 minute to 1 day to reduce timer-wheel pressure.
Copas 4.5.0 [18/Dec/2022]
  • Added: copas.status got an extra parameter to track more detailed stats.
  • Fix: queue workers would not properly handle falsy items in the queue. The worker would exit instead of handle the item.
  • Fix: a non-reentrant lock should block instead of returning an error when entered again.
  • Fix: finishing a queue would not honour the timeout.
  • Refactor: more manual cleanup instead of relying on weak-tables.
Copas 4.4.0 [23/Oct/2022]
  • Fix: an error in the timer callback would kill the timer.
  • Added: copas.geterrorhandler to retrieve the active errorhandler.
  • Added: option errorhandler for timer objects.
  • Added: copas.pause and copas.pauseforever to replace copas.sleep. The latter method can accidentally sleep-forever if time arithmetic returns a negative result.
  • Added: copas.status which returns an object with number of tasks/timers/sockets.
  • Change: renamed copas.setErrorHandler to copas.seterrorhandler.
  • Change: renamed copas.useSocketTimeoutErrors to copas.usesockettimeouterrors.
Copas 4.3.2 [03/Oct/2022]
  • Fix: error handler for timeouts. Underlying bug is in coxpcall, and hence this only applies to PuC Lua 5.1.
Copas 4.3.1 [21/Sep/2022]
  • Fix: with Lua 5.1 the timeouts would resume the wrapped (by coxpcall) coroutines, instead of the original ones. Causing errors to bubble up one level too many.
Copas 4.3.0 [19/Sep/2022]
  • Fix: when the loop is idle, do an occasional GC to clean out any lingering non-closed sockets. This could prevent the loop from exiting.
  • Fix: in debug mode very large data is now truncated when displayed.
  • Fix: the receive methods could starve other threads on high-throughput.
  • Change: order of copas.addnamedthread args.
  • Fix: copas.receivepartial could return early with no data received if the `prefix` parameter was specified.
  • Change: renamed copas.receivePartial to copas.receivepartial.
  • Added: sock:receivepartial to better process streaming TCP data.
  • fix: copas.receivepartial is now documented.
  • fix: copas.http was missing some error handling.
  • fix: Copas timeouts when receiving/sending would not return partial results, or last bytes sent.
Copas 4.2.0 [06/Sep/2022]
  • Change: pick up datagram size from newer LuaSocket versions.
  • Fix: non-recurring timer can now be armed again from its own handler.
  • Added: calling on the module table now invokes the copas.loop method.
Copas 4.1.0 [25/Aug/2022]
  • Fix: handle errors thrown by the error handlers themselves.
  • Deps: Bump timerwheel to 1.0 (no changes, just a small fix)
  • Added: copas.gettraceback, previously internal to the default error handler, now exposed to make it easier to write proper error handlers
  • Added: http-request now takes a timeout setting, previously it would always use the default value of 30 seconds.
  • Added: the previously internal function for generating a TCP socket in the http-request module, is now exported as http.getcreatefunc(). This allows to capture the socket used by the request. When using streaming responses, for example with server-sent events, this can be used to modify the timeouts, or for closing the stream.
  • Fix: empty queues were not destroyed properly and could prevent Copas from exiting
Copas 4.0.0 [29/Jul/2022]
  • [breaking] Change: removed the "limitset". Its functionality can easily be recreated with the new "queue" class, which is a better abstraction.
  • [breaking] Change: threads added through copas.addthread or copas.addnamedthread will now be "scheduled", instead of immediately started.
  • Fixed: yielding to the Copas scheduler from user-code now throws a proper error and no longer breaks the loop. Breaking the loop could also happen if a thread returned with at least 2 return values.
  • Fixed: wrongly auto-closing sockets. Upon exiting a coroutine, sockets would be automatically closed. This should only be the case for accepted TCP connections on a TCP server socket. This caused issues for sockets shared between threads.
    [breaking]: this changes behavior, auto-close is now determined when accepting the connection, and no longer when terminating the handler thread. This will only affect users that dynamically change copas.autoclose at runtime.
  • Fixed: http requests would not set SNI defaults. Setting fields protocol, options, and verify directly on the http options table is now deprecated. Instead specify sslparams, similar to other SSL/TLS functions.
  • Added: added sempahore:destroy()
  • Added: copas.settimeouts, to set separate timeouts for connect, send, receive
  • Added: queue class, see module "copas.queue"
  • Added: names for sockets and coroutines:
    • copas.addserver() has a 4th argument; name, to name the server socket
    • copas.addnamedthread() is new and identical to copas.addthread(), but also accepts a name
    • copas.setsocketname(), copas.getsocketname(), copas.setthreadname(), copas.getthreadname() added to manage names
    • copas.debug.start() and copas.debug.end() to enable debug logging for the scheduler itself.
    • copas.debug.socket() to enable debug logging for socket methods (experimental).
Copas 3.0.0 [12/Nov/2021]
  • [breaking] Change: copas.addserver() now uses the timeout value as a copas timeout, instead of a luasocket timeout. The client sockets for incoming connections will inherit the timeout from the server socket.
  • Added: support for SNI on TLS connections #81 (@amyspark)
  • Added: copas.settimeout() so Copas can manage its own timeouts instead of spinning forever (Patrick Barrett )
  • Added: timer class, see module "copas.timer"
  • Added: lock class, see module "copas.lock"
  • Added: semaphore class, see module "copas.semaphore"
  • Added: timeout interface copas.timeout()
  • Added: option to override the default errorhandler, and fixes to the handler
  • Added: copas.removethread() added to be able to forcefully remove a previously added thread
  • Added: copas.loop() now takes an optional initialization function
  • Fixed: closing sockets from another thread would make the read/write ops hang #104
  • Fixed: coxpcall dependency in limit.lua #63 (Francois Perrad)
  • Fixed: CI now generates the certificates for testing, on unix make can be used, on Windows generate them manually
  • Fixed: type in wrapped udp:setpeername was actually calling udp:getpeername
  • Fixed: default error handler didn't print the stacktrace
  • Fixed: small memory leak when sleeping until woken
  • Fixed: do not wrap udp:sendto() method, since udp send doesn't block
  • Change: performance improvement in big limit-sets (Francisco Castro)
  • Change: update deprecated tls default to tls 1.2 in (copas.http)
Copas 2.0.2 [2017]
  • Added: copas.running flag
  • Fixed: fix for http request #53 (Peter Melnichenko)
  • Added: extra parameter keep_open for the removeserver() method (Hisham Muhammad)
  • Change: tweaked makefile with a DESTDIR variable (Richard Leitner)
Copas 2.0.1 [2016]
  • Added: support for Lua 5.3 (no code changes, just rockspec update)
  • Fixed: yield across c boundary error (by Peter Melnichenko)
  • Fixed: bug in wrappers for setoption() and shutdown() (reported by Rob Probin)
Copas 2.0.0 [2015]
  • Added: removeserver() function to remove servers from the scheduler (by Paul Kulchenko)
  • Added: client requests for http(s), ftp, and smtp (like LuaSocket/LuaSec, but async)
  • Added: transparent async support (handshake, and send/receive) for ssl using LuaSec
  • Added: handler() as a convenience for full copas and ssl wrapping
  • [breaking] Change: the loop now exits when there is nothing more to do
  • [breaking] Change: dummy first argument to new tasks removed
  • Fixed: completed the socket wrappers, missing functions were added
  • Fixed: connect issue, step() errorring out instead of returning nil + error
  • Fixed: UDP sockets being auto closed
  • Fixed: the receivePartial function for http request support (by Paul Kulchenko)
Copas 1.2.1 [2013]
  • Fixed bad version constant
  • Fixed timer issue
  • updated documentation
Copas 1.2.0 [2013]
  • Support for Lua 5.2
  • UDP support
  • suspending threads
  • other minor updates
Copas 1.1.6 [18/Mar/2010]
  • Now checks to see if socket.http was required before copas
Copas 1.1.5 [07/Apr/2009]
  • Fixed bug reported by Sam Roberts on the Kepler list (found due to Xavante locking up on some POST requests)
Copas 1.1.4 [10/Dec/2008]
  • Fixed bug [#5372] - copas.connect is semi-broken (found by Gary NG)
Copas 1.1.3 [19/May/2008]
  • Using copcall instead of pcall in socket.protect (feature request [#5274] by Gary NG)
Copas 1.1.2 [15/May/2008]
  • Fixed Bug [#4249] - bugs in copas.receive (found by Gary NG)
Copas 1.1.1 [13/Aug/2007]
  • Compatible with Lua 5.1
  • Refactored by Thomas Harning Jr. (for more details check Bug 766)
  • Patch by Gary NG concerning the handling of stopped sockets
Copas 1.1 [20/Sep/2006]
Copas 1.0 [17/May/2005]
Copas 1.0 Beta[17/Feb/2005]
  • First public version

Credits

Copas was designed and implemented by André Carregal and Javier Guerra as part of the Kepler Project which holds its copyright. Copas development had significative contributions from Diego Nehab, Mike Pall, David Burgess, Leonardo Godinho, Thomas Harning Jr. and Gary NG.

Contact us

For more information please contact us. Comments are welcome!

You can also reach other Kepler developers and users on the Kepler Project mailing list.

copas-4.8.0/docs/license.html000066400000000000000000000071201476464242400161050ustar00rootroot00000000000000 Copas License
Copas
Coroutine Oriented Portable Asynchronous Services for Lua

License

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

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

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


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

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

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

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

Valid XHTML 1.0!

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

copas-4.8.0/docs/manual.html000066400000000000000000000370221476464242400157440ustar00rootroot00000000000000 Copas - Coroutine Oriented Portable Asynchronous Services for Lua
Copas
Coroutine Oriented Portable Asynchronous Services for Lua

Installing

You can install Copas using LuaRocks:

luarocks install copas

Note: LuaSec is not automatically installed as a dependency. If you want to use ssl with Copas, you need to manually install LuaSec as well.

Runtime

Copas can either be used as a regular Lua library, or as a runtime. A command line script that acts as a runtime engine is included. When using the runtime, the library is available as a global (copas), and the scheduler will automatically be started. For example:
  #!/usr/bin/env copas

  local count = 0
  copas.timer.new {
    delay = 1,
    recurring = true,
    callback = function(self)
      count = count + 1
      print('hello world ' .. count)
      if count >= 5 then
        self:cancel()
      end
    end
  }

Introduction to Copas

Copas is a dispatcher that can help a lot in the creation of servers based on LuaSocket. Here we present a quick introduction to Copas and how to implement a server with it.

Assuming you know how to implement the desired server protocol, the first thing you have to do in order to create a Copas based server is create a server socket to receive the client connections. To do this you have to bind a host and a port using LuaSocket:

server = socket.bind(host, port)

Then you have to create a handler function that implements the server protocol. The handler function will be called with a socket for each client connection and you can use copas.send() and copas.receive() on that socket to exchange data with the client.

For example, a simple echo handler would be:

function echoHandler(skt)
  while true do
    local data = copas.receive(skt)
    if data == "quit" then
      break
    end
    copas.send(skt, data)
  end
end

You may alternatively use copas.wrap() to let your code more close to a standard LuaSocket use:

function echoHandler(skt)
  skt = copas.wrap(skt)
  while true do
    local data = skt:receive()
    if data == "quit" then
      break
    end
    skt:send(data)
  end
end

To register the server socket with Copas and associate it with the corresponding handler we do:

copas.addserver(server, echoHandler)

Finally, to start Copas and all the registered servers we just call:

copas()

As long as every handler uses Copas's send and receive, simultaneous connections will be handled transparently by Copas for every registered server.

Since Copas is coroutine based, using it within a Lua pcall or xpcall context does not work with Lua 5.1 yielding. If you need to use any of those functions in your handler we strongly suggest using coxpcall, a coroutine safe version of the Lua 5.1 protected calls. For an example of this usage please check Xavante.

Why use Copas?

For those who already have a server implemented, here is an explanation of why and how to migrate to Copas. In a typical LuaSocket server usually there is a dispatcher loop like the one below:

server = socket.bind(host, port)
while true do
  skt = server:accept()
  handle(skt)
end

Here handle is a function that implements the server protocol using LuaSocket's socket functions:

function handle(skt)
  ...
  -- gets some data from the client - "the request"
  reqdata = skt:receive(pattern)
  ...
  -- sends some data to the client - "the response"
  skt:send(respdata)
  ...
end

The problem with that approach is that the dispatcher loop is doing a busy wait and can handle just one connection at a time. To solve the busy waiting we can use LuaSocket's socket.select(), like in:

server = socket.bind(host, port)
reading = {server}
while true do
  input = socket.select(reading)
  skt = input:accept()
  handle(skt)
end

While this helps our CPU usage, the server is still accepting only one client connection at a time. To handle more than one client the server must be able to multitask, and the solution usually involves some kind of threads.

The dispatcher loop then becomes something like:

server = socket.bind(host, port)
reading = {server}
while true do
  input = socket.select(reading)
  skt = input:accept()
  newthread(handle(skt))
end

where newthread is able to create a new thread that executes independently the handler function.

The use of threads in the new loop solves the multitasking problem but may create another. Some platforms does not offer multithreading or maybe you don't want to use threads at all.

If that is the case, using Lua's coroutines may help a lot, and that's exactly what Copas does. Copas implements the dispatcher loop using coroutines so the handlers can multitask without the use of threads.

Using Copas with an existing server

If you already have a running server using some dispatcher like the previous example, migrating to Copas is quite simple, usually consisting of just three steps.

First each server socket and its corresponding handler function have to be registered with Copas:

server = socket.bind(host, port)
copas.addserver(server, handle)

Secondly the server handler has to be adapted to use Copas. One solution is to use Copas send and receive functions to receive and send data to the client:

function handle(skt)
  ...
  -- gets some data from the client - "the request"
  reqdata = copas.receive(skt, pattern)
  ...
  -- sends some data to the client - "the response"
  copas.send(skt, respdata)
   ...
end

The other alternative is to wrap the socket in a Copas socket. This allows your handler code to remain basically the same:

function handle(skt)
  -- this line may suffice for your handler to work with Copas
  skt = copas.wrap(skt)   -- or... skip this line and wrap `handle` using copas.handler()
  -- now skt behaves like a LuaSocket socket but uses Copas'
  ...
  -- gets some data from the client - "the request"
  reqdata = skt:receive(pattern)
  ...
  -- sends some data to the client - "the response"
  skt:send(respdata)
   ...
end

Note that by default Copas might return different timeout errors than the traditional Lua libraries. Checkout copas.useSocketTimeoutErrors() for more information.

Finally, to run the dispatcher loop you just call:

copas()

During the loop Copas' dispatcher accepts connections from clients and automatically calls the corresponding handler functions.

Using UDP servers

Copas may also be used for UDP servers. Here is an example;

local port = 51034
local server = socket.udp()
server:setsockname("*",port)

function handler(skt)
  skt = copas.wrap(skt)
  print("UDP connection handler")

  while true do
    local s, err
    print("receiving...")
    s, err = skt:receive(2048)
    if not s then
      print("Receive error: ", err)
      return
    end
    print("Received data, bytes:" , #s)
  end
end

copas.addserver(server, handler, 1)
copas()

For UDP sockets the receivefrom() and sendto() methods are available, both for copas and when the socket is wrapped. These methods cannot be used on TCP sockets.

IMPORTANT: UDP sockets do not have the notion of master and client sockets, so where a handler function can close the client socket for a TCP connection, a handler should never close a UDP socket, because the socket is the same as the server socket, hence closing it destroys the server.

NOTE: When using the copas.receive([size]) method on a UDP socket, the size parameter is NOT optional as with regular luasocket UDP sockets. This limitation is removed when the socket is wrapped (it then defaults to 8192, the max UDP datagram size luasocket supports).

Adding tasks

Additional threads may be added to the scheduler, as long as they use the Copas send, receive or sleep methods. Below an example of a thread being added to create an outgoing TCP connection using Copas;

local socket = require("socket")
local copas = require("copas")

local host = "127.0.0.1"
local port = 10000

local skt = socket.connect(host, port)
skt:settimeout(0)  -- important: make it non-blocking

copas.addthread(function()
   while true do
      print("receiving...")
      local resp = copas.receive(skt, 6)
      print("received:", resp or "nil")
      if resp and resp:sub(1,4) == "quit" then
         skt:close()
         break
      end
   end
end)

copas()

The example connects, echoes whatever it receives and exits upon receiving 'quit'. For an example passing arguments to a task, see the async http example below.

Creating timers

Timers can be created using the copas.timer module. Below an example of a timer;

local copas = require("copas")

copas(function()
   copas.timer.new({
     delay = 1,                        -- delay in seconds
     recurring = true,                 -- make the timer repeat
     params = "hello world",
     callback = function(timer_obj, params)
       print(params)                   -- prints "hello world"
       timer_obj:cancel()              -- cancel the timer after 1 occurence
     end
   })
end)

The example simply prints a message once every second, but gets cancelled right after the first one.

Synchronization primitives

Since Copas allows to asynchroneously schedule tasks, synchronization might be required to protect resources from concurrent access. In this case the copas.lock and copas.semaphore classes can be used. The lock/semaphore will ensure that the coroutine running will be yielded until the protected resource becomes available, without blocking other threads.

local copas = require("copas")

local lock = copas.lock.new()

local function some_func()
  local ok, err, wait = lock:get()
  if not ok then
    return nil, "we got error '" .. err .. "' after " .. wait .. " seconds"
  end

  print("doing something on my own")
  copas.pause()  -- allow to yield, while inside the lock
  print("after " .. ok .. " seconds waiting")

  lock:release()
end

The some_func function may now be called and the 2 lines will be printed together because of the lock.

Ssl support

LuaSec is transparently integrated in the Copas scheduler (though must be installed separately when using LuaRocks).

Here's an example for an incoming connection in a server scenario;

function handler(skt)
  skt = copas.wrap(skt):dohandshake(sslparams)
  -- skt = copas.wrap(skt, sslparams):dohandshake()  -- would be identical

  while true do
    -- perform the regular reading/writing ops on skt
  end
end

A simpler handler would wrap the handler function to do the wrapping and handshake before the handler gets called;

function handle(skt)
  -- by now `skt` is copas wrapped, and has its handshake already completed

  while true do
    -- perform the regular reading/writing ops on skt
  end
end
handle = copas.handler(handle, sslparams)  -- wraps the handler to auto wrap and handshake

Here's an example for an outgoing request;

copas.addthread(function()
  local skt = copas.wrap(socket.tcp(), sslparams)
  skt:connect(host, port)  -- connecting will also perform the handshake on a wrapped socket

  while true do

    -- perform the regular reading/writing ops on skt

  end
end

High level requests

For creating high level requests; http(s), ftp or smtp versions of the methods are available that handle them async. As opposed to the original LuaSocket and LuaSec implementations.

Below an example that schedules a number of http requests, then starts the Copas loop to execute them. The loop exits when it's done.

local copas = require("copas")
local asynchttp = require("copas.http").request

local list = {
  "http://www.google.com",
  "http://www.microsoft.com",
  "http://www.apple.com",
  "http://www.facebook.com",
  "http://www.yahoo.com",
}

local handler = function(host)
  res, err = asynchttp(host)
  print("Host done: "..host)
end

for _, host in ipairs(list) do copas.addthread(handler, host) end
copas()

Controlling Copas

If you do not want copas to simply enter an infinite loop (maybe you have to respond to events from other sources, such as an user interface), you should have your own loop and just call copas.step() at each iteration of the loop:

while condition do
  copas.step()
  -- processing for other events from your system here
end

When using your own main loop, you should consider manually setting the copas.running flag.

Valid XHTML 1.0!

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

copas-4.8.0/docs/reference.html000066400000000000000000001373201476464242400164270ustar00rootroot00000000000000 Copas - Coroutine Oriented Portable Asynchronous Services for Lua
Copas
Coroutine Oriented Portable Asynchronous Services for Lua

Reference

NOTE: Some functions require DNS lookups, which is handled internally by LuaSocket. This is being done in a blocking manner. Hence every function that accepts a hostname as an argument (e.g. tcp:connect(), udp:sendto(), etc.) is potentially blocking on the DNS resolving part. So either provide IP addresses (assuming the underlying OS will detect those and resolve locally, non-blocking) or accept that the lookup might block.

Getting started examples

Example for a server handling incoming connections:

local copas = require("copas")
local socket = require("socket")

local address = "*"
local port = 20000
local ssl_params = {
    wrap = {
        mode = "server",
        protocol = "any",  -- not really secure...
    },
}

local server_socket = assert(socket.bind(address, port))

local function connection_handler(skt)
    local data, err = skt:receive()

    -- do something

end

copas.addthread(function()
    copas.addserver(server_socket, copas.handler(connection_handler,
        ssl_params), "my_TCP_server")
    copas.waitforexit()
    copas.removeserver(server_socket)
end)

copas()

Example for a client making a connection to a remote server:

local copas = require("copas")
local socket = require("socket")

copas.addthread(function()
    local port = 20000
    local host = "somehost.com"
    local ssl_params = {
        wrap = {
            mode = "client",
            protocol = "any",  -- not really secure...
        },
    }

    local sock = copas.wrap(socket.tcp(), ssl_params)
    copas.setsocketname("my_TCP_client", sock)
    assert(sock:connect(host, port))

    local data, err = sock:receive("*l")

    -- do something

end)

copas()

Copas dispatcher main functions

The group of functions is relative to the use of the dispatcher itself and are used to register servers and to execute the main loop of Copas:

copas([init_func, ][timeout])

This is a shortcut to copas.loop.

copas.addserver(server, handler [, timeout [, name]])

Adds a new server and its handler to the dispatcher using an optional timeout.

server is a LuaSocket server socket created using socket.bind().

handler is a function that receives a LuaSocket client socket and handles the communication with that client. The handler will be executed in parallel with other threads and registered handlers as long as it uses the Copas socket functions.

timeout is the timeout in seconds. Upon accepting connections, the timeout will be inherited by TCP client sockets (only applies to TCP).

name is the internal name to use for this socket. The handler threads and (in case of TCP) the incoming client connections will get a name derived from the server socket.

  • TCP: client-socket name: "[server_name]:client_XX" and the handler thread "[server_name]:handler_XX" where XX is a sequential number matching between the client-socket and handler.
  • UDP: the handler thread will be named "[server_name]:handler"

coro = copas.addnamedthread(name, func [, ...])

Same as copas.addthread, but also names the new thread.

coro = copas.addthread(func [, ...])

Adds a function as a new coroutine/thread to the dispatcher. The optional parameters will be passed to the function func.

The thread will be executed in parallel with other threads and the registered handlers as long as it uses the Copas socket/sleep functions.

It returns the created coroutine.

copas.autoclose

Boolean that controls whether sockets are automatically closed (defaults to true). This only applies to incoming connections accepted on a TCP server socket.

When a TCP handler function completes and terminates, then the client socket will automatically be closed when copas.autoclose is truthy.

copas.exit()

Sets a flag that the application is intending to exit. After calling this function copas.exiting() will be returning true, and all threads blocked on copas.waitforexit() will be released.

Copas itself will call this function when copas.finished() returns true.

bool = copas.exiting()

Returns a flag indicating whether the application is supposed to exit. Returns false until after copas.exit() has been called, after which it will start returning true.

Clients should check whether they are to cease their operation and exit. They can do this by checking this flag, or by registering a task waiting on copas.waitforexit(). Clients should cancel pending work and close sockets when an exit is announced, otherwise Copas will not exit.

bool = copas.finished()

Checks whether anything remains to be done.

Returns false when the socket lists for reading and writing are empty and there is not another (sleeping) task to execute.

NOTE: when tasks or sockets have been scheduled/setup this function will return true even if the loop has not yet started. See also copas.running.

func = copas.geterrorhandler([coro])

Returns the currently active errorhandler function for the coroutine (either the explicitly set errorhandler, or the default one). coro will default to the currently running coroutine if omitted.

string = copas.getsocketname(skt)

Returns the name for the socket.

string = copas.getthreadname([co])

Returns the name for the coroutine/thread. If not given defaults to the currently running coroutine.

number = copas.gettime()

Returns the (fractional) number of seconds since the epoch. This directly maps to either the LuaSocket or LuaSystem implementation of gettime().

string = copas.gettraceback([msg], [co], [skt])

Creates a traceback (string). Can be used from custom errorhandlers to create a proper trace. See copas.seterrorhandler.

func = copas.handler(connhandler [, sslparams])

Wraps the connhandler function.

Returns a new function that wraps the client socket, and (if sslparams is provided) performs the ssl handshake, before calling connhandler.

See sslparams definition below.

copas.loop([init_func, ][timeout])

Starts the Copas loop accepting client connections for the registered servers and handling those connections with the corresponding handlers. Calling on the module table itself is a shortcut to this function. Every time a server accepts a connection, Copas calls the associated handler passing the client socket returned by socket.accept().

The init_func function is an optional initialization function that runs as a Copas thread (with name "copas_initializer"). The timeout parameter is optional, and is passed to the copas.step() function.

The loop returns when copas.finished() == true.

result = copas.removeserver(skt [, keep_open])

Removes a server socket from the Copas scheduler. By default, the socket will be closed to allow the socket to be reused right away after removing the server. If keep_open is true, the socket is removed from the scheduler but it is not closed.

Returns the result of skt:close() or true if the socket was kept open.

copas.removethread(coroutine)

Removes a coroutine added to the Copas scheduler. Takes a coroutine created by copas.addthread() and removes it from the dispatcher the next time it tries to resume. If coroutine isn't registered, it does nothing.

copas.running

A flag set to true when copas.loop() starts, and reset to false when the loop exits. See also copas.finished().

copas.seterrorhandler([func], [default])

Sets the error handling function for the current thread. Any errors will be forwarded to this handler, it will receive the error, coroutine, and socket as arguments; function(err, co, skt). See the Copas source code on how to deal with the arguments when implementing your own, and check copas.gettraceback.

If func is omitted, then the error handler is cleared (restores the default handler for this coroutine).

If default is truthy, then the handler will become the new default, used for all threads that do not have their own set (in this case func must be provided).

copas.setsocketname(name, skt)

Sets the name for the socket.

copas.setthreadname(name [,co])

Sets the name for the coroutine/thread. co defaults to the currently running coroutine.

copas.waitforexit()

This will block the calling coroutine until the copas.exit() function is called. Clients should check whether they are to cease their operation and exit. They can do this by waiting on this call, or by checking the copas.exiting() flag. Clients should cancel pending work and close sockets when an exit is announced, otherwise Copas will not exit.

skt = copas.wrap(skt [, sslparams] )

Wraps a LuaSocket socket and returns a Copas socket that implements LuaSocket's API but use Copas' methods like copas.send() and copas.receive() automatically. If the socket was already wrapped, then it will not wrap it again.

If the sslparams is provided, then a call to the wrapped sock:connect() method will automatically include the handshake (and in that case connect() might throw an error instead of returning nil+error, see copas.dohandshake()).

See sslparams definition below.

sslparams

This is the data-structure that is passed to the copas.handler, and copas.wrap functions. Passing the structure will allow Copas to take care of the entire TLS handshake process.

The structure is set up to mimic the LuaSec functions for the handshake.

{
  wrap = table | context,    -- parameter to LuaSec 'wrap()'
  sni = {                    -- parameters to LuaSec 'sni()'
    names = string | table   --   1st parameter
    strict = bool            --   2nd parameter
  }
}

Non-blocking data exchange and timer/sleep functions

These are used by the handler functions to exchange data with the clients, and by threads registered with addthread to exchange data with other services.

copas.pause([delay])

Pauses the current co-routine. Parameter delay (in seconds) is optional and defaults to 0. If delay <= 0 then it will pause for 0 seconds.

copas.pauseforever()

Pauses the current co-routine until explicitly woken by a call to copas.wakeup().

copas.sleep([sleeptime])

Deprecated: use copas.pause and copas.pauseforever instead.

copas.wakeup(co)

Immediately wakes up a coroutine that was sleeping or sleeping-forever. co is the coroutine to wakeup, see copas.pause() and copas.pauseforever(). Does nothing if the coroutine wasn't sleeping.

sock:close()

Equivalent to the LuaSocket method (after copas.wrap).

sock:connect(address, port)

Non-blocking equivalent to the LuaSocket method (after copas.wrap).

If sslparams was provided when wrapping the socket, the connect method will also perform the full TLS handshake. So after connect returns the connection will be secured.

sock:dohandshake(sslparams)

Non-blocking quivalent to the LuaSec method (after copas.wrap). Instead of using this method, it is preferred to pass the sslparams to the functions copas.handler (for incoming connections) and copas.wrap (for outgoing connections), which then ensures that the connection will automatically be secured when started.

sock:receive([pattern [, prefix]])

Non-blocking equivalent to the LuaSocket method (after copas.wrap). Please see sock:receivepartial for differences with LuaSocket, especially when using the "*a" pattern.

sock:receivefrom([size])

Reads data from a UDP socket just like LuaSocket, but non-blocking. socket:receivefrom().

sock:receivepartial([pattern [, prefix]])

This method is the same as the receive method, the difference being that this method will return on any data received, even if the specified pattern was not yet satisfied.

When using delimited formats or known byte-size (pattern is "*l" or a number) the regular receive method will usually be fine. But when reading a stream with the "*a" pattern the receivepartial method should be used.

The reason for this is the difference in timeouts between Copas and LuaSocket. The Copas timeout will apply on each underlying socket read/write operation. So on every chunk received Copas will reset the timeout. So if reading pattern "*a" with a 10 second timeout, and the sender sends a stream of data (unlimited size), in 1kb chunks, with 5 seconds intervals, then there will never be a timeout when using receive, and hence the call would never return.

If using receivepartial with the "*a" pattern, the (repeated) call would return the 1kb chunks, with a "timeout" error.

sock:send(data [, i [, j]])

Non-blocking equivalent to the LuaSocket method (after copas.wrap).

sock:settimeout([timeout])

Sets the timeouts (in seconds) for a socket (after copas.wrap). The default is to not have a timeout and wait indefinitely. If a timeout is hit, the operation will return nil + "timeout". This method is compatible with LuaSocket, but sets all three timeouts to the same value.

Behaviour:

  • nil: block indefinitely
  • number < 0: block indefinitely
  • number >= 0: timeout value in seconds
Important: this behaviour is the same as LuaSocket, but different from sock:settimeouts, where nil means 'do not change' the timeout.

sock:settimeouts([connect], [send], [receive])

Sets the timeouts (in seconds) for a socket (after copas.wrap). A positive number sets the timeout, a negative number removes the timeout, and nil will not change the currently set timeout. The default is to not have a timeout and wait indefinitely.

Behaviour:

  • nil: do not change the current setting
  • number < 0: block indefinitely
  • number >= 0: timeout value in seconds
Important: this behaviour is different from sock:settimeout, where nil means 'wait indefinitely'.

If a timeout is hit, the operation will return nil + "timeout".

sock:sni(...)

Equivalent to the LuaSec method (after copas.wrap). Instead of using this method, it is preferred to pass the sslparams to the functions copas.handler (for incoming connections) and copas.wrap (for outgoing connections), which then ensures that the connection will automatically be secured when started.

lock:destroy()

Will destroy the lock and release all waiting threads. The result for those threads will be nil + "destroyed" + wait_time, any new call on any method will return nil + "destroyed" from here on.

lock:get([timeout])

Will try and acquire the lock. The optional timeout can be used to override the timeout value set when the lock was created.

If the lock is not available, the coroutine will yield until either the lock becomes available, or it times out. The one exception is when timeout is 0, then it will immediately return without yielding. If the timeout is set to math.huge, then it will wait forever.

Upon success, it will return the wait-time in seconds. Upon failure it will return nil + error + wait-time. Upon a timeout the error value will be "timeout".

copas.lock.new([timeout], [not_reentrant])

Creates and returns a new lock. The timeout specifies the default timeout for the lock in seconds, and defaults to 10 (set it to math.huge to wait forever).

By default the lock is re-entrant, except if not_reentrant is set to a truthy value.

lock:release()

Releases the currently held lock.

Returns true or nil + error.

queue:add_worker(func)

Adds a worker that will handle whatever is passed into the queue. Can be called multiple times to add more workers. The function func is wrapped and added as a copas thread. The threads automatically exit when the queue is destroyed.

Worker function signature: function(item) (Note: worker functions run unprotected, so wrap code in an (x)pcall if errors are expected, otherwise the worker will exit on an error, and queue handling will stop).

Returns the coroutine added, or nil+"destroyed".

queue:destroy()

Destroys a queue immediately. Abandons what is left in the queue. Releases all waiting calls to queue:pop() with nil+"destroyed". Returns true, or nil+"destroyed".

queue:finish([timeout], [no_destroy_on_timeout])

Finishes a queue. Calls queue:stop() and then waits for the queue to run empty (and be destroyed) before returning.

When using "workers" via queue:add_worker() then this method will return after the worker has finished processing the popped item. When using your own threads and calling queue:pop(), then this method will return after the last item has been popped, but not necessarily also processed.

The timeout defaults to 10 seconds (the default timeout value for a lock), math.huge can be used to wait forever.

Parameter no_destroy_on_timeout indicates if the queue is not to be forcefully destroyed on a timeout (abandonning what ever is left in the queue).

Returns true, or nil+"timeout", or nil+"destroyed".

queue:get_size()

Gets the number of items in the queue currently. Returns number or nil + "destroyed".

queue:get_workers()

Returns a list/array of current workers (coroutines) handling the queue (only the workers added by queue:add_worker(), and still active, will be in the list). Returns list or nil + "destroyed".

queue.name

A field set to name of the queue. See copas.queue.new().

copas.queue.new([options])

Creates and returns a new queue. The options table has the following fields:

  • options.name: (optional) the name for the queue, the default name will be "copas_queue_XX". The name will be used to name any workers added to the queue using queue:add_worker(), their name will be "[queue_name]:worker_XX"

queue:pop([timeout])

Will pop an item from the queue. If there are no items in the queue it will yield until there are or a timeout happens (exception is when timeout == 0, then it will not yield but return immediately, be careful not to create a hanging loop!).

Timeout defaults to the default time-out of a semaphore. If the timeout is math.huge then it will wait forever.

Returns an item, or nil+"timeout", or nil+"destroyed". Since an item can be nil, make sure to check for the error message to detect errors.

queue:push(item)

Will push a new item in the queue. Item can be any type, including 'nil'.

Returns true or nil + "destroyed".

queue:stop()

Instructs the queue to stop, and returns immediately. It will no longer accept calls to queue:push(), and will call queue:destroy() once the queue is empty.

Returns true or nil + "destroyed".

semaphore:destroy()

Will destroy the sempahore and release all waiting threads. The result for those threads will be nil + "destroyed", any new call on any method will return nil + "destroyed" from here on.

semaphore:get_count()

Returns the number of resources currently available in the semaphore.

semaphore:get_wait()

Returns the total number of resources requested by all currently waiting threads minus the available resources. Such that sempahore:give(semaphore:get_wait()) will release all waiting threads and leave the semaphore with 0 resources. If there are no waiting threads then the result will be 0, and the number of resources in the semaphore will be greater than or equal to 0.

semaphore:give([given])

Gives resources to the semaphore. Parameter given is the number of resources given to the semaphore, if omitted it defaults to 1.

If the total resources in the semaphore exceed the maximum, then it will be capped at the maximum. In that case the result will be nil + "too many".

copas.semaphore.new(max, [start], [timeout])

Creates and returns a new semaphore (fifo).

max specifies the maximum number of resources the semaphore can hold. The optional start parameter (default 0) specifies the number of resources upon creation.

The timeout specifies the default timeout for the semaphore in seconds, and defaults to 10 (math.huge can be used to wait forever).

semaphore:take([requested], [timeout])

Takes resources from the semaphore. Parameter requested is the number of resources requested from the semaphore, if omitted it defaults to 1.

If not enough resources are available it will yield and wait until enough resources are available, or a timeout occurs. The exception is when timeout is set to 0, in that case it will immediately return without yielding if there are not enough resources available.

The optional timeout parameter can be used to override the default timeout as set upon semaphore creation. If the timeout is math.huge then it will wait forever.

Returns true upon success or nil + "timeout" on a timeout. In case more resources are requested than maximum available the error will be "too many".

copas.timer.new(options)

Creates and returns an (armed) timer object. The options table has the following fields;

  • options.name (optional): the name for the timer, the default name will be "copas_timer_XX". The name will be used to name the timer thread.
  • options.recurring (optional, default false): boolean
  • options.delay: expiry delay in seconds
  • options.initial_delay (optional): see timer:arm()
  • options.params (optional): an opaque value that is passed to the callback upon expiry
  • options.callback: is the function to execute on timer expiry. The callback function has function(timer_obj, params) as signature, where params is the value initially passed in options.params
  • options.errorhandler (optional): a Copas errorhandler function (see copas.seterrorhandler for the signature.

timer:arm([initial_delay])

Arms a timer that was previously cancelled or exited (arming a non-recurring timer again from its own handler is explicitly supported). Returns the timer.

The optional parameter initial_delay, determines the first delay. For example a recurring timer with delay = 5, and initial_delay = 0 will execute immediately and then recur every 5 seconds.

timer:cancel()

Will cancel the timer.

High level request functions

The last ones are the higher level client functions to perform requests to (remote) servers.

copas.http.request(url [, body]) or
copas.http.request(requestparams)

Performs an http or https request, identical to the LuaSocket and LuaSec implementations, but wrapped in an async operation. As opposed to the original implementations, this one also allows for redirects cross scheme (http to https and viceversa).

Options:

  • options.url: the URL for the request.
  • options.sink (optional): the LTN12 sink to pass the body chunks to.
  • options.method (optional, default "GET"): the http request method.
  • options.headers (optional): any additional HTTP headers to send with the request. Hash-table, header-values by header-names.
  • options.source (optional): simple LTN12 source to provide the request body. If there is a body, you need to provide an appropriate "content-length" request header field, or the function will attempt to send the body as "chunked" (something few servers support). Defaults to the empty source
  • options.step (optional): LTN12 pump step function used to move data. Defaults to the LTN12 pump.step function.
  • proxy (optional, default none): The URL of a proxy server to use.
  • options.redirect (optional, default true): Set to false to prevent GET or HEAD requests from automatically following 301, 302, 303, and 307 server redirect messages.
    Note: https to http redirects are not allowed by default, but only when this option is set to a string value "all".
  • options.create (optional): a function to be used instead of socket.tcp when the communications socket is created.
  • options.maxredirects (optional, default 5): An optional number specifying the maximum number of redirects to follow. A boolean false value means no maximum (unlimited).
  • options.timeout (optional, default 60): A number specifying the timeout for connect/send/receive operations. Or a table with keys "connect", "send", and "receive", to specify individual timeouts (keys omitted from the table will get a default of 30).

copas.ftp.put(url, content) or
copas.ftp.put(requestparams)

Performs an ftp request, identical to the LuaSocket implementation, but wrapped in an async operation.

copas.ftp.get(url) or
copas.ftp.get(requestparams)

Performs an ftp request, identical to the LuaSocket implementation, but wrapped in an async operation.

copas.smtp.send(msgparams)

Sends an smtp request, identical to the LuaSocket implementation, but wrapped in an async operation.

copas.smtp.message(msgt)

Just points to socket.smtp.message, provided so the copas.smtp module is a drop-in replacement for the socket.smtp module.

Low level Copas functions

Most of these are wrapped in the socket wrapper functions, and wouldn't need to be used by user code on a regular basis.

copas.close(skt)

Closes the socket. Any read/write operations in progress will return with an error.

copas.connect(skt, address, port)

Connects and transforms a master socket to a client just like LuaSocket socket:connect(). The Copas version does not block and allows the multitasking of the other handlers and threads.

copas.dohandshake(skt, sslparams)

Performs an ssl handshake on an already connected TCP client socket. It returns the new ssl-socket on success, or throws an error on failure.

copas.flush(skt)

(deprecated)

copas.receive(skt [, pattern [, prefix]]) (TCP) or
copas.receive(size) (UDP)

Reads data from a client socket according to a pattern just like LuaSocket socket:receive(). The Copas version does not block and allows the multitasking of the other handlers and threads.

Note: for UDP sockets the size parameter is NOT optional. For the wrapped function socket:receive([size]) it is optional again.

copas.receivepartial(skt [, pattern [, prefix]])

The same as receive, except that this method will return on any data received. See sock:receivepartial for details.

copas.receivefrom(skt [, size])

Reads data from a UDP socket just like LuaSocket socket:receivefrom(). The Copas version does not block and allows the multitasking of the other handlers and threads.

copas.send(skt, data [, i [, j]]) (TCP) or
copas.send(skt, datagram) (UDP)

Sends data to a client socket just like socket:send(). The Copas version is buffered and does not block, allowing the multitasking of the other handlers and threads.

Note: only for TCP, UDP send doesn't block, hence doesn't require this function to be used.

copas.settimeout(skt, [timeout])

Sets the timeout (in seconds) for a socket. A negative timout or absent timeout (nil) will wait indefinitely.

Important: this behaviour is the same as LuaSocket, but different from copas.settimeouts, where nil means 'do not change' the timeout.

If a timeout is hit, the operation will return nil + "timeout". Timeouts are applied on: receive, receivefrom, receivepartial, send, connect, dohandshake.

See copas.usesockettimeouterrors() below for alternative error messages.

copas.settimeouts(skt, [connect], [send], [receive])

Sets the timeouts (in seconds) for a socket. The default is to not have a timeout and wait indefinitely.

Important: this behaviour is different from copas.settimeout, where nil means 'wait indefinitely'.

If a timeout is hit, the operation will return nil + "timeout". Timeouts are applied on: receive, receivefrom, receivepartial, send, connect, dohandshake.

See copas.usesockettimeouterrors() below for alternative error messages.

t = copas.status([enable_stats])

Returns metadata about the current scheduler status. By default only number/type of tasks is being reported. If enable_stats == true then detailed statistics will be enabled. Calling it again later with enable_stats == false will disabled it again.

  • t.running: boolean, same as copas.running.
  • t.read: the number of tasks waiting to read on a socket.
  • t.write: the number of tasks waiting to write to a socket.
  • t.active: the number of tasks ready to resume.
  • t.timer: the number of timers for tasks (in the binary tree).
  • t.inactive: the number of tasks waiting to be woken up.
  • t.timeout: the number of timers for timeouts (in the timerwheel).
  • t.time_start*: measurement time started (seconds, previous call to status).
  • t.time_end*: measurement time ended (seconds, now, current call to status).
  • t.time_avg*: average time per step (millisec).
  • t.steps*: the number of loop steps executed.
  • t.duration_tot*: total time spent executing (millisec, processing tasks, excluding waiting for the network select call).
  • t.duration_min*: smallest execution time per step (millisec).
  • t.duration_min_ever*: smallest execution time per step ever (millisec).
  • t.duration_max*: highest execution time per step (millisec).
  • t.duration_max_ever*: highest execution time per step ever (millisec).
  • t.duration_avg*: average execution time per step (millisec).
The properties marked with * will only be reported if detailed statistics are enabled. Every call to this function will reset the time and duration statistics (except for the "ever" ones).

bool = copas.step([timeout])

Executes one copas iteration accepting client connections for the registered servers and handling those connections with the corresponding handlers. When a server accepts a connection, Copas calls the associated handler passing the client socket returned by socket.accept(). The timeout parameter is optional. It returns false when no data was handled (timeout) or true if there was data handled (or alternatively nil + error message in case of errors).

NOTE: the copas.running flag will not automatically be (un)set. So when using your own main loop, consider manually setting the flag.

copas.timeout(delay [, callback])

Creates a timeout timer for the current coroutine. The delay is the timeout in seconds, and the callback will be called upon an actual timeout occuring.

Calling it with delay = 0 (or math.huge) will cancel the timeout.

Calling it repeatedly will simply replace the timeout on the current coroutine and any previous callback set will no longer be called.

NOTE: The timeouts are a low-level Copas feature, and should only be used to wrap an explicit yield to the Copas scheduler. They should not be used to wrap user code.

Example usage:

local copas = require "copas"
local result = "nothing"

copas(function()

  local function timeout_handler(co)  -- co will be the coroutine from which 'timeout()' was called
    print("executing timeout handler")
    result = "timeout"
    copas.removethread(co)            -- drop the thread, because we timed out
  end

  copas.addthread(function()
    copas.timeout(5, timeout_handler) -- timeout on the current coroutine after 5 seconds
    copas.pause(10)                   -- sleep for 10 seconds
    print("just woke up")
    result = "slept like a baby"
    copas.timeout(0)                  -- cancel the timeout on the current coroutine
  end)
end)

print("result: ", result)

For usage examples see the lock and semaphore implementations.

copas.usesockettimeouterrors([bool])

Sets the timeout errors to return for the current co-routine. The default is false, meaning that a timeout error will always return an error string "timeout". If you are porting an existing application to Copas and want LuaSocket or LuaSec compatible error messages then set it to true.

In case of using socket timeout errors, they can also be "wantread" or "wantwrite" when using ssl/tls connections. These can be returned at any point if during a read or write operation an ssl-renegotiation happens.

Due to platform difference the connect method may also return "Operation already in progress" as a timeout error message.

Copas debugging functions

These functions are mainly used for debugging Copas itself and should be considered experimental.

copas.debug.start([logger] [, core])

This will internally replace coroutine handler functions to provide log output to the provided logger function. The logger signature is function(...) and the default value is the global print() function.

If the core parameter is truthy, then also the Copas core timer will be logged (very noisy).

copas.debug.stop()

Stops the debug output being generated.

debug_socket = copas.debug.socket(socket)

This wraps the socket in a debug-wrapper. This will for each method call on the socket object print the method, the parameters and the return values. Check the source code on how to add additional introspection using extra callbacks etc.

Extremely noisy and experimental!

NOTE 1: for TLS you'll probably need to first do the TLS handshake.

NOTE 2: this is separate from the other debugging functions.

Valid XHTML 1.0!

$Id: reference.html,v 1.16 2009/04/07 21:34:52 carregal Exp $

copas-4.8.0/misc/000077500000000000000000000000001476464242400136005ustar00rootroot00000000000000copas-4.8.0/misc/cosocket.lua000066400000000000000000000025471476464242400161250ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Copas - Coroutine Oriented Portable Asynchronous Services -- -- Copas Wrapper for socket.http module -- -- Written by Leonardo Godinho da Cunha ------------------------------------------------------------------------------- local copas = require("copas") local socket = require("socket") local cosocket = {} -- Meta information is public even begining with an "_" cosocket._COPYRIGHT = "Copyright (C) 2004-2006 Kepler Project" cosocket._DESCRIPTION = "Coroutine Oriented Portable Asynchronous Services Wrapper for socket module" cosocket._NAME = "Copas.cosocket" cosocket._VERSION = "0.1" function cosocket.tcp () local skt = socket.tcp() local w_skt_mt = { __index = skt } local ret_skt = setmetatable ({ socket = skt }, w_skt_mt) ret_skt.settimeout = function (self,val) return self.socket:settimeout (val) end ret_skt.connect = function (self,host, port) local ret,err = copas.connect (self.socket,host, port) local d = copas.wrap(self.socket) self.send= function(client, data) local ret,val=d.send(client, data) return ret,val end self.receive=d.receive self.close = function (w_socket) ret=w_socket.socket:close() return ret end return ret,err end return ret_skt end return cosocket copas-4.8.0/misc/echoserver.lua000066400000000000000000000011011476464242400164410ustar00rootroot00000000000000-- Tests Copas with a simple Echo server -- -- Run the test file and the connect to the server using telnet on the used port. -- The server should be able to echo any input, to stop the test just send the command "quit" local copas = require("copas") local socket = require("socket") local function echoHandler(skt) skt = copas.wrap(skt) while true do local data = skt:receive() if not data or data == "quit" then break end skt:send(data) end end local server = socket.bind("localhost", 20000) copas.addserver(server, echoHandler) copas.loop() copas-4.8.0/misc/testasyncspeed.lua000066400000000000000000000022721476464242400173440ustar00rootroot00000000000000local copas = require("copas") local synchttp = require("socket.http").request local asynchttp = copas.http.request local gettime = require("socket").gettime local targets = { "http://www.google.com", "http://www.microsoft.com", "http://www.apple.com", "http://www.facebook.com", "http://www.yahoo.com", } local function sync(list) for _, host in ipairs(list) do local res = synchttp(host) if not res then print("Error sync: "..host.." failed, rerun test!") else print("Sync host done: "..host) end end end local handler = function(host) local res = asynchttp(host) if not res then print("Error async: "..host.." failed, rerun test!") else print("Async host done: "..host) end end local function async(list) for _, host in ipairs(list) do copas.addthread(handler, host) end copas.loop() end -- three times to remove caching differences async(targets) async(targets) async(targets) print("\nNow starting the real test...\n") local t1 = gettime() print("Sync:") sync(targets) local t2 = gettime() print("Async:") async(targets) local t3 = gettime() print("\nResults:") print("========") print(" Sync : ", t2-t1) print(" Async: ", t3-t2) copas-4.8.0/rockspec/000077500000000000000000000000001476464242400144565ustar00rootroot00000000000000copas-4.8.0/rockspec/copas-1.1.2-1.rockspec000066400000000000000000000016501476464242400201130ustar00rootroot00000000000000package = "Copas" version = "1.1.2-1" source = { url = "http://luaforge.net/frs/download.php/3367/copas-1.1.2.tar.gz", } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "http://www.keplerproject.org/copas/" } dependencies = { "lua >= 5.1", "luasocket >= 2.0" } build = { type = "make", build_pass = false, install_variables = { LUA_DIR = "$(LUADIR)" } } copas-4.8.0/rockspec/copas-1.1.3-1.rockspec000066400000000000000000000017001476464242400201100ustar00rootroot00000000000000package = "Copas" version = "1.1.3-1" source = { url = "http://luaforge.net/frs/download.php/3409/copas-1.1.3.tar.gz", } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "http://www.keplerproject.org/copas/" } dependencies = { "lua >= 5.1", "luasocket >= 2.0", "coxpcall >= 1.13", } build = { type = "make", build_pass = false, install_variables = { LUA_DIR = "$(LUADIR)" } } copas-4.8.0/rockspec/copas-1.1.4-1.rockspec000066400000000000000000000016411476464242400201150ustar00rootroot00000000000000package = "Copas" version = "1.1.4-1" source = { url = "http://luaforge.net/frs/download.php/3896/copas-1.1.4.tar.gz", } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "http://www.keplerproject.org/copas/" } dependencies = { "lua >= 5.1", "luasocket >= 2.0", "coxpcall >= 1.13", } build = { type = "module", modules = { copas = "src/copas/copas.lua" } } copas-4.8.0/rockspec/copas-1.1.5-1.rockspec000066400000000000000000000016411476464242400201160ustar00rootroot00000000000000package = "Copas" version = "1.1.5-1" source = { url = "http://luaforge.net/frs/download.php/4027/copas-1.1.5.tar.gz", } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "http://www.keplerproject.org/copas/" } dependencies = { "lua >= 5.1", "luasocket >= 2.0", "coxpcall >= 1.13", } build = { type = "module", modules = { copas = "src/copas/copas.lua" } } copas-4.8.0/rockspec/copas-1.1.6-1.rockspec000066400000000000000000000016501476464242400201170ustar00rootroot00000000000000package = "Copas" version = "1.1.6-1" source = { url = "http://github.com/downloads/keplerproject/copas/copas-1.1.6.tar.gz", } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "http://www.keplerproject.org/copas/" } dependencies = { "lua >= 5.1", "luasocket >= 2.0", "coxpcall >= 1.13", } build = { type = "builtin", modules = { copas = "src/copas/copas.lua" } } copas-4.8.0/rockspec/copas-1.2.0-1.rockspec000066400000000000000000000017011476464242400201070ustar00rootroot00000000000000package = "Copas" version = "1.2.0-1" source = { url = "https://github.com/keplerproject/copas/archive/v1_2_0.tar.gz", dir = "copas-1_2_0", } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "http://www.keplerproject.org/copas/" } dependencies = { "lua >= 5.1, < 5.3", "luasocket >= 2.1", "coxpcall >= 1.14", } build = { type = "builtin", modules = { copas = "src/copas/copas.lua" } } copas-4.8.0/rockspec/copas-1.2.1-1.rockspec000066400000000000000000000017011476464242400201100ustar00rootroot00000000000000package = "Copas" version = "1.2.1-1" source = { url = "https://github.com/keplerproject/copas/archive/v1_2_1.tar.gz", dir = "copas-1_2_1", } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "http://www.keplerproject.org/copas/" } dependencies = { "lua >= 5.1, < 5.3", "luasocket >= 2.1", "coxpcall >= 1.14", } build = { type = "builtin", modules = { copas = "src/copas/copas.lua" } } copas-4.8.0/rockspec/copas-2.0.0-1.rockspec000066400000000000000000000021701476464242400201070ustar00rootroot00000000000000package = "Copas" version = "2.0.0-1" source = { url = "https://github.com/keplerproject/copas/archive/v2_0_0.tar.gz", dir = "copas-2_0_0", } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "http://www.keplerproject.org/copas/" } dependencies = { "lua >= 5.1, < 5.3", "luasocket >= 2.1", "coxpcall >= 1.14", } build = { type = "builtin", modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.limit"] = "src/copas/limit.lua", } } copas-4.8.0/rockspec/copas-2.0.0-2.rockspec000066400000000000000000000021701476464242400201100ustar00rootroot00000000000000package = "Copas" version = "2.0.0-2" source = { url = "https://github.com/keplerproject/copas/archive/v2_0_0.tar.gz", dir = "copas-2_0_0", } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "http://www.keplerproject.org/copas/" } dependencies = { "lua >= 5.1, < 5.4", "luasocket >= 2.1", "coxpcall >= 1.14", } build = { type = "builtin", modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.limit"] = "src/copas/limit.lua", } } copas-4.8.0/rockspec/copas-2.0.1-1.rockspec000066400000000000000000000021701476464242400201100ustar00rootroot00000000000000package = "Copas" version = "2.0.1-1" source = { url = "https://github.com/keplerproject/copas/archive/v2_0_1.tar.gz", dir = "copas-2_0_1", } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "http://www.keplerproject.org/copas/" } dependencies = { "lua >= 5.1, < 5.4", "luasocket >= 2.1", "coxpcall >= 1.14", } build = { type = "builtin", modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.limit"] = "src/copas/limit.lua", } } copas-4.8.0/rockspec/copas-2.0.2-1.rockspec000066400000000000000000000021701476464242400201110ustar00rootroot00000000000000package = "Copas" version = "2.0.2-1" source = { url = "https://github.com/keplerproject/copas/archive/v2_0_2.tar.gz", dir = "copas-2_0_2", } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "http://www.keplerproject.org/copas/" } dependencies = { "lua >= 5.1, < 5.4", "luasocket >= 2.1", "coxpcall >= 1.14", } build = { type = "builtin", modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.limit"] = "src/copas/limit.lua", } } copas-4.8.0/rockspec/copas-3.0.0-1.rockspec000066400000000000000000000033041476464242400201100ustar00rootroot00000000000000local package_name = "copas" local package_version = "3.0.0" local rockspec_revision = "1" local github_account_name = "keplerproject" local github_repo_name = package_name package = package_name version = package_version.."-"..rockspec_revision source = { url = "git://github.com/"..github_account_name.."/"..github_repo_name..".git", branch = (package_version == "cvs") and "master" or nil, tag = (package_version ~= "cvs") and package_version or nil, } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, } dependencies = { "lua >= 5.1, < 5.4", "luasocket >= 2.1", "coxpcall >= 1.14", "binaryheap >= 0.4", "timerwheel >= 0.2", } build = { type = "builtin", modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.limit"] = "src/copas/limit.lua", ["copas.timer"] = "src/copas/timer.lua", ["copas.lock"] = "src/copas/lock.lua", ["copas.semaphore"] = "src/copas/semaphore.lua", }, copy_directories = { "docs", }, } copas-4.8.0/rockspec/copas-3.0.0-2.rockspec000066400000000000000000000033111476464242400201070ustar00rootroot00000000000000local package_name = "copas" local package_version = "3.0.0" local rockspec_revision = "2" local github_account_name = "lunarmodules" local github_repo_name = package_name package = package_name version = package_version.."-"..rockspec_revision source = { url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", branch = (package_version == "cvs") and "master" or nil, tag = (package_version ~= "cvs") and package_version or nil, } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, } dependencies = { "lua >= 5.1, < 5.5", "luasocket >= 2.1", "coxpcall >= 1.14", "binaryheap >= 0.4", "timerwheel >= 0.2", } build = { type = "builtin", modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.limit"] = "src/copas/limit.lua", ["copas.timer"] = "src/copas/timer.lua", ["copas.lock"] = "src/copas/lock.lua", ["copas.semaphore"] = "src/copas/semaphore.lua", }, copy_directories = { "docs", }, } copas-4.8.0/rockspec/copas-3.0.0-3.rockspec000066400000000000000000000033261476464242400201160ustar00rootroot00000000000000local package_name = "copas" local package_version = "3.0.0" local rockspec_revision = "3" local github_account_name = "lunarmodules" local github_repo_name = package_name package = package_name version = package_version.."-"..rockspec_revision source = { url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", branch = (package_version == "cvs") and "master" or nil, tag = (package_version ~= "cvs") and package_version or nil, } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, } dependencies = { "lua >= 5.1, < 5.5", "luasocket >= 2.1, <= 3.0rc1-2", "coxpcall >= 1.14", "binaryheap >= 0.4", "timerwheel >= 0.2", } build = { type = "builtin", modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.limit"] = "src/copas/limit.lua", ["copas.timer"] = "src/copas/timer.lua", ["copas.lock"] = "src/copas/lock.lua", ["copas.semaphore"] = "src/copas/semaphore.lua", }, copy_directories = { "docs", }, } copas-4.8.0/rockspec/copas-4.0.0-1.rockspec000066400000000000000000000033261476464242400201150ustar00rootroot00000000000000local package_name = "copas" local package_version = "4.0.0" local rockspec_revision = "1" local github_account_name = "lunarmodules" local github_repo_name = package_name package = package_name version = package_version.."-"..rockspec_revision source = { url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", branch = (package_version == "cvs") and "master" or nil, tag = (package_version ~= "cvs") and package_version or nil, } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, } dependencies = { "lua >= 5.1, < 5.5", "luasocket >= 2.1, <= 3.0rc1-2", "coxpcall >= 1.14", "binaryheap >= 0.4", "timerwheel >= 0.2", } build = { type = "builtin", modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.timer"] = "src/copas/timer.lua", ["copas.lock"] = "src/copas/lock.lua", ["copas.semaphore"] = "src/copas/semaphore.lua", ["copas.queue"] = "src/copas/queue.lua", }, copy_directories = { "docs", }, } copas-4.8.0/rockspec/copas-4.1.0-1.rockspec000066400000000000000000000033241476464242400201140ustar00rootroot00000000000000local package_name = "copas" local package_version = "4.1.0" local rockspec_revision = "1" local github_account_name = "lunarmodules" local github_repo_name = package_name package = package_name version = package_version.."-"..rockspec_revision source = { url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", branch = (package_version == "cvs") and "master" or nil, tag = (package_version ~= "cvs") and package_version or nil, } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, } dependencies = { "lua >= 5.1, < 5.5", "luasocket >= 2.1, <= 3.0rc1-2", "coxpcall >= 1.14", "binaryheap >= 0.4", "timerwheel ~> 1", } build = { type = "builtin", modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.timer"] = "src/copas/timer.lua", ["copas.lock"] = "src/copas/lock.lua", ["copas.semaphore"] = "src/copas/semaphore.lua", ["copas.queue"] = "src/copas/queue.lua", }, copy_directories = { "docs", }, } copas-4.8.0/rockspec/copas-4.2.0-1.rockspec000066400000000000000000000033241476464242400201150ustar00rootroot00000000000000local package_name = "copas" local package_version = "4.2.0" local rockspec_revision = "1" local github_account_name = "lunarmodules" local github_repo_name = package_name package = package_name version = package_version.."-"..rockspec_revision source = { url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", branch = (package_version == "cvs") and "master" or nil, tag = (package_version ~= "cvs") and package_version or nil, } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, } dependencies = { "lua >= 5.1, < 5.5", "luasocket >= 2.1, <= 3.0rc1-2", "coxpcall >= 1.14", "binaryheap >= 0.4", "timerwheel ~> 1", } build = { type = "builtin", modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.timer"] = "src/copas/timer.lua", ["copas.lock"] = "src/copas/lock.lua", ["copas.semaphore"] = "src/copas/semaphore.lua", ["copas.queue"] = "src/copas/queue.lua", }, copy_directories = { "docs", }, } copas-4.8.0/rockspec/copas-4.3.0-1.rockspec000066400000000000000000000033241476464242400201160ustar00rootroot00000000000000local package_name = "copas" local package_version = "4.3.0" local rockspec_revision = "1" local github_account_name = "lunarmodules" local github_repo_name = package_name package = package_name version = package_version.."-"..rockspec_revision source = { url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", branch = (package_version == "cvs") and "master" or nil, tag = (package_version ~= "cvs") and package_version or nil, } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, } dependencies = { "lua >= 5.1, < 5.5", "luasocket >= 2.1, <= 3.0rc1-2", "coxpcall >= 1.14", "binaryheap >= 0.4", "timerwheel ~> 1", } build = { type = "builtin", modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.timer"] = "src/copas/timer.lua", ["copas.lock"] = "src/copas/lock.lua", ["copas.semaphore"] = "src/copas/semaphore.lua", ["copas.queue"] = "src/copas/queue.lua", }, copy_directories = { "docs", }, } copas-4.8.0/rockspec/copas-4.3.1-1.rockspec000066400000000000000000000033241476464242400201170ustar00rootroot00000000000000local package_name = "copas" local package_version = "4.3.1" local rockspec_revision = "1" local github_account_name = "lunarmodules" local github_repo_name = package_name package = package_name version = package_version.."-"..rockspec_revision source = { url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", branch = (package_version == "cvs") and "master" or nil, tag = (package_version ~= "cvs") and package_version or nil, } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, } dependencies = { "lua >= 5.1, < 5.5", "luasocket >= 2.1, <= 3.0rc1-2", "coxpcall >= 1.14", "binaryheap >= 0.4", "timerwheel ~> 1", } build = { type = "builtin", modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.timer"] = "src/copas/timer.lua", ["copas.lock"] = "src/copas/lock.lua", ["copas.semaphore"] = "src/copas/semaphore.lua", ["copas.queue"] = "src/copas/queue.lua", }, copy_directories = { "docs", }, } copas-4.8.0/rockspec/copas-4.3.2-1.rockspec000066400000000000000000000033241476464242400201200ustar00rootroot00000000000000local package_name = "copas" local package_version = "4.3.2" local rockspec_revision = "1" local github_account_name = "lunarmodules" local github_repo_name = package_name package = package_name version = package_version.."-"..rockspec_revision source = { url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", branch = (package_version == "cvs") and "master" or nil, tag = (package_version ~= "cvs") and package_version or nil, } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, } dependencies = { "lua >= 5.1, < 5.5", "luasocket >= 2.1, <= 3.0rc1-2", "coxpcall >= 1.14", "binaryheap >= 0.4", "timerwheel ~> 1", } build = { type = "builtin", modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.timer"] = "src/copas/timer.lua", ["copas.lock"] = "src/copas/lock.lua", ["copas.semaphore"] = "src/copas/semaphore.lua", ["copas.queue"] = "src/copas/queue.lua", }, copy_directories = { "docs", }, } copas-4.8.0/rockspec/copas-4.4.0-1.rockspec000066400000000000000000000033241476464242400201170ustar00rootroot00000000000000local package_name = "copas" local package_version = "4.4.0" local rockspec_revision = "1" local github_account_name = "lunarmodules" local github_repo_name = package_name package = package_name version = package_version.."-"..rockspec_revision source = { url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", branch = (package_version == "cvs") and "master" or nil, tag = (package_version ~= "cvs") and package_version or nil, } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, } dependencies = { "lua >= 5.1, < 5.5", "luasocket >= 2.1, <= 3.0rc1-2", "coxpcall >= 1.14", "binaryheap >= 0.4", "timerwheel ~> 1", } build = { type = "builtin", modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.timer"] = "src/copas/timer.lua", ["copas.lock"] = "src/copas/lock.lua", ["copas.semaphore"] = "src/copas/semaphore.lua", ["copas.queue"] = "src/copas/queue.lua", }, copy_directories = { "docs", }, } copas-4.8.0/rockspec/copas-4.5.0-1.rockspec000066400000000000000000000033241476464242400201200ustar00rootroot00000000000000local package_name = "copas" local package_version = "4.5.0" local rockspec_revision = "1" local github_account_name = "lunarmodules" local github_repo_name = package_name package = package_name version = package_version.."-"..rockspec_revision source = { url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", branch = (package_version == "cvs") and "master" or nil, tag = (package_version ~= "cvs") and package_version or nil, } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, } dependencies = { "lua >= 5.1, < 5.5", "luasocket >= 2.1, <= 3.0rc1-2", "coxpcall >= 1.14", "binaryheap >= 0.4", "timerwheel ~> 1", } build = { type = "builtin", modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.timer"] = "src/copas/timer.lua", ["copas.lock"] = "src/copas/lock.lua", ["copas.semaphore"] = "src/copas/semaphore.lua", ["copas.queue"] = "src/copas/queue.lua", }, copy_directories = { "docs", }, } copas-4.8.0/rockspec/copas-4.6.0-1.rockspec000066400000000000000000000033241476464242400201210ustar00rootroot00000000000000local package_name = "copas" local package_version = "4.6.0" local rockspec_revision = "1" local github_account_name = "lunarmodules" local github_repo_name = package_name package = package_name version = package_version.."-"..rockspec_revision source = { url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", branch = (package_version == "cvs") and "master" or nil, tag = (package_version ~= "cvs") and package_version or nil, } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, } dependencies = { "lua >= 5.1, < 5.5", "luasocket >= 2.1, <= 3.0rc1-2", "coxpcall >= 1.14", "binaryheap >= 0.4", "timerwheel ~> 1", } build = { type = "builtin", modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.timer"] = "src/copas/timer.lua", ["copas.lock"] = "src/copas/lock.lua", ["copas.semaphore"] = "src/copas/semaphore.lua", ["copas.queue"] = "src/copas/queue.lua", }, copy_directories = { "docs", }, } copas-4.8.0/rockspec/copas-4.7.0-1.rockspec000066400000000000000000000034421476464242400201230ustar00rootroot00000000000000local package_name = "copas" local package_version = "4.7.0" local rockspec_revision = "1" local github_account_name = "lunarmodules" local github_repo_name = package_name package = package_name version = package_version.."-"..rockspec_revision source = { url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", branch = (package_version == "cvs") and "master" or nil, tag = (package_version ~= "cvs") and package_version or nil, } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, } dependencies = { "lua >= 5.1, < 5.5", "luasocket >= 2.1, <= 3.0rc1-2", "coxpcall >= 1.14", "binaryheap >= 0.4", "timerwheel ~> 1", } build = { type = "builtin", install = { bin = { copas = "bin/copas.lua", } }, modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.timer"] = "src/copas/timer.lua", ["copas.lock"] = "src/copas/lock.lua", ["copas.semaphore"] = "src/copas/semaphore.lua", ["copas.queue"] = "src/copas/queue.lua", }, copy_directories = { "docs", }, } copas-4.8.0/rockspec/copas-4.7.1-1.rockspec000066400000000000000000000034421476464242400201240ustar00rootroot00000000000000local package_name = "copas" local package_version = "4.7.1" local rockspec_revision = "1" local github_account_name = "lunarmodules" local github_repo_name = package_name package = package_name version = package_version.."-"..rockspec_revision source = { url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", branch = (package_version == "cvs") and "master" or nil, tag = (package_version ~= "cvs") and package_version or nil, } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, } dependencies = { "lua >= 5.1, < 5.5", "luasocket >= 2.1, <= 3.0rc1-2", "coxpcall >= 1.14", "binaryheap >= 0.4", "timerwheel ~> 1", } build = { type = "builtin", install = { bin = { copas = "bin/copas.lua", } }, modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.timer"] = "src/copas/timer.lua", ["copas.lock"] = "src/copas/lock.lua", ["copas.semaphore"] = "src/copas/semaphore.lua", ["copas.queue"] = "src/copas/queue.lua", }, copy_directories = { "docs", }, } copas-4.8.0/rockspec/copas-4.8.0-1.rockspec000066400000000000000000000034421476464242400201240ustar00rootroot00000000000000local package_name = "copas" local package_version = "4.8.0" local rockspec_revision = "1" local github_account_name = "lunarmodules" local github_repo_name = package_name package = package_name version = package_version.."-"..rockspec_revision source = { url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git", branch = (package_version == "cvs") and "master" or nil, tag = (package_version ~= "cvs") and package_version or nil, } description = { summary = "Coroutine Oriented Portable Asynchronous Services", detailed = [[ Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket as the interface with the TCP/IP stack. A server 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 as an example. ]], license = "MIT/X11", homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, } dependencies = { "lua >= 5.1, < 5.5", "luasocket >= 2.1, <= 3.0rc1-2", "coxpcall >= 1.14", "binaryheap >= 0.4", "timerwheel ~> 1", } build = { type = "builtin", install = { bin = { copas = "bin/copas.lua", } }, modules = { ["copas"] = "src/copas.lua", ["copas.http"] = "src/copas/http.lua", ["copas.ftp"] = "src/copas/ftp.lua", ["copas.smtp"] = "src/copas/smtp.lua", ["copas.timer"] = "src/copas/timer.lua", ["copas.lock"] = "src/copas/lock.lua", ["copas.semaphore"] = "src/copas/semaphore.lua", ["copas.queue"] = "src/copas/queue.lua", }, copy_directories = { "docs", }, } copas-4.8.0/src/000077500000000000000000000000001476464242400134345ustar00rootroot00000000000000copas-4.8.0/src/copas.lua000066400000000000000000001672461476464242400152640ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Copas - Coroutine Oriented Portable Asynchronous Services -- -- A dispatcher based on coroutines that can be used by TCP/IP servers. -- Uses LuaSocket as the interface with the TCP/IP stack. -- -- Authors: Andre Carregal, Javier Guerra, and Fabio Mascarenhas -- Contributors: Diego Nehab, Mike Pall, David Burgess, Leonardo Godinho, -- Thomas Harning Jr., and Gary NG -- -- Copyright 2005-2013 - Kepler Project (www.keplerproject.org), 2015-2025 Thijs Schreijer -- -- $Id: copas.lua,v 1.37 2009/04/07 22:09:52 carregal Exp $ ------------------------------------------------------------------------------- if package.loaded["socket.http"] and (_VERSION=="Lua 5.1") then -- obsolete: only for Lua 5.1 compatibility error("you must require copas before require'ing socket.http") end if package.loaded["copas.http"] and (_VERSION=="Lua 5.1") then -- obsolete: only for Lua 5.1 compatibility error("you must require copas before require'ing copas.http") end -- load either LuaSocket, or LuaSystem -- note: with luasocket we don't use 'sleep' but 'select' with no sockets local socket, system do if pcall(require, "socket") then -- found LuaSocket socket = require "socket" end -- try LuaSystem as fallback if pcall(require, "system") then system = require "system" end if not (socket or system) then error("Neither LuaSocket nor LuaSystem found, Copas requires at least one of them") end end local binaryheap = require "binaryheap" local gettime = (socket or system).gettime local block_sleep = (socket or system).sleep local ssl -- only loaded upon demand local WATCH_DOG_TIMEOUT = 120 local UDP_DATAGRAM_MAX = (socket or {})._DATAGRAMSIZE or 8192 local TIMEOUT_PRECISION = 0.1 -- 100ms local fnil = function() end local coroutine_create = coroutine.create local coroutine_running = coroutine.running local coroutine_yield = coroutine.yield local coroutine_resume = coroutine.resume local coroutine_status = coroutine.status -- nil-safe versions for pack/unpack local _unpack = unpack or table.unpack local unpack = function(t, i, j) return _unpack(t, i or 1, j or t.n or #t) end local pack = function(...) return { n = select("#", ...), ...} end local pcall = pcall if _VERSION=="Lua 5.1" and not jit then -- obsolete: only for Lua 5.1 compatibility pcall = require("coxpcall").pcall coroutine_running = require("coxpcall").running end if socket then -- Redefines LuaSocket functions with coroutine safe versions (pure Lua) -- (this allows the use of socket.http from within copas) local err_mt = { __tostring = function (self) return "Copas 'try' error intermediate table: '"..tostring(self[1].."'") end, } local function statusHandler(status, ...) if status then return ... end local err = (...) if type(err) == "table" and getmetatable(err) == err_mt then return nil, err[1] else error(err) end end function socket.protect(func) return function (...) return statusHandler(pcall(func, ...)) end end function socket.newtry(finalizer) return function (...) local status = (...) if not status then pcall(finalizer or fnil, select(2, ...)) error(setmetatable({ (select(2, ...)) }, err_mt), 0) end return ... end end socket.try = socket.newtry() end -- Setup the Copas meta table to auto-load submodules and define a default method local copas do local submodules = { "ftp", "http", "lock", "queue", "semaphore", "smtp", "timer" } for i, key in ipairs(submodules) do submodules[key] = true submodules[i] = nil end copas = setmetatable({},{ __index = function(self, key) if submodules[key] then self[key] = require("copas."..key) submodules[key] = nil return rawget(self, key) end end, __call = function(self, ...) return self.loop(...) end, }) end -- Meta information is public even if beginning with an "_" copas._COPYRIGHT = "Copyright (C) 2005-2013 Kepler Project, 2015-2025 Thijs Schreijer" copas._DESCRIPTION = "Coroutine Oriented Portable Asynchronous Services" copas._VERSION = "Copas 4.8.0" -- Close the socket associated with the current connection after the handler finishes copas.autoclose = true -- indicator for the loop running copas.running = false -- gettime method from either LuaSocket or LuaSystem: time in (fractional) seconds, since epoch. copas.gettime = gettime ------------------------------------------------------------------------------- -- Object names, to track names of thread/coroutines and sockets ------------------------------------------------------------------------------- local object_names = setmetatable({}, { __mode = "k", __index = function(self, key) local name = tostring(key) if key ~= nil then rawset(self, key, name) end return name end }) ------------------------------------------------------------------------------- -- Simple set implementation -- adds a FIFO queue for each socket in the set ------------------------------------------------------------------------------- local function newsocketset() local set = {} do -- set implementation local reverse = {} -- Adds a socket to the set, does nothing if it exists -- @return skt if added, or nil if it existed function set:insert(skt) if not reverse[skt] then self[#self + 1] = skt reverse[skt] = #self return skt end end -- Removes socket from the set, does nothing if not found -- @return skt if removed, or nil if it wasn't in the set function set:remove(skt) local index = reverse[skt] if index then reverse[skt] = nil local top = self[#self] self[#self] = nil if top ~= skt then reverse[top] = index self[index] = top end return skt end end end do -- queues implementation local fifo_queues = setmetatable({},{ __mode = "k", -- auto collect queue if socket is gone __index = function(self, skt) -- auto create fifo queue if not found local newfifo = {} self[skt] = newfifo return newfifo end, }) -- pushes an item in the fifo queue for the socket. function set:push(skt, itm) local queue = fifo_queues[skt] queue[#queue + 1] = itm end -- pops an item from the fifo queue for the socket function set:pop(skt) local queue = fifo_queues[skt] return table.remove(queue, 1) end end return set end -- Threads immediately resumable local _resumable = {} do local resumelist = {} function _resumable:push(co) resumelist[#resumelist + 1] = co end function _resumable:clear_resumelist() local lst = resumelist resumelist = {} return lst end function _resumable:done() return resumelist[1] == nil end function _resumable:count() return #resumelist + #_resumable end end -- Similar to the socket set above, but tailored for the use of -- sleeping threads local _sleeping = {} do local heap = binaryheap.minUnique() local lethargy = setmetatable({}, { __mode = "k" }) -- list of coroutines sleeping without a wakeup time -- Required base implementation ----------------------------------------- _sleeping.insert = fnil _sleeping.remove = fnil -- push a new timer on the heap function _sleeping:push(sleeptime, co) if sleeptime < 0 then lethargy[co] = true elseif sleeptime == 0 then _resumable:push(co) else heap:insert(gettime() + sleeptime, co) end end -- find the thread that should wake up to the time, if any function _sleeping:pop(time) if time < (heap:peekValue() or math.huge) then return end return heap:pop() end -- additional methods for time management ----------------------------------------- function _sleeping:getnext() -- returns delay until next sleep expires, or nil if there is none local t = heap:peekValue() if t then -- never report less than 0, because select() might block return math.max(t - gettime(), 0) end end function _sleeping:wakeup(co) if lethargy[co] then lethargy[co] = nil _resumable:push(co) return end if heap:remove(co) then _resumable:push(co) end end function _sleeping:cancel(co) lethargy[co] = nil heap:remove(co) end -- @param tos number of timeouts running function _sleeping:done(tos) -- return true if we have nothing more to do -- the timeout task doesn't qualify as work (fallbacks only), -- the lethargy also doesn't qualify as work ('dead' tasks), -- but the combination of a timeout + a lethargy can be work return heap:size() == 1 -- 1 means only the timeout-timer task is running and not (tos > 0 and next(lethargy)) end -- gets number of threads in binaryheap and lethargy function _sleeping:status() local c = 0 for _ in pairs(lethargy) do c = c + 1 end return heap:size(), c end end -- _sleeping ------------------------------------------------------------------------------- -- Tracking coroutines and sockets ------------------------------------------------------------------------------- local _servers = newsocketset() -- servers being handled local _threads = setmetatable({}, {__mode = "k"}) -- registered threads added with addthread() local _canceled = setmetatable({}, {__mode = "k"}) -- threads that are canceled and pending removal local _autoclose = setmetatable({}, {__mode = "kv"}) -- sockets (value) to close when a thread (key) exits local _autoclose_r = setmetatable({}, {__mode = "kv"}) -- reverse: sockets (key) to close when a thread (value) exits -- for each socket we log the last read and last write times to enable the -- watchdog to follow up if it takes too long. -- tables contain the time, indexed by the socket local _reading_log = {} local _writing_log = {} local _closed = {} -- track sockets that have been closed (list/array) local _reading = newsocketset() -- sockets currently being read local _writing = newsocketset() -- sockets currently being written local _isSocketTimeout = { -- set of errors indicating a socket-timeout ["timeout"] = true, -- default LuaSocket timeout ["wantread"] = true, -- LuaSec specific timeout ["wantwrite"] = true, -- LuaSec specific timeout } ------------------------------------------------------------------------------- -- Coroutine based socket timeouts. ------------------------------------------------------------------------------- local user_timeouts_connect local user_timeouts_send local user_timeouts_receive do local timeout_mt = { __mode = "k", __index = function(self, skt) -- if there is no timeout found, we insert one automatically, to block forever self[skt] = math.huge return self[skt] end, } user_timeouts_connect = setmetatable({}, timeout_mt) user_timeouts_send = setmetatable({}, timeout_mt) user_timeouts_receive = setmetatable({}, timeout_mt) end local useSocketTimeoutErrors = setmetatable({},{ __mode = "k" }) -- sto = socket-time-out local sto_timeout, sto_timed_out, sto_change_queue, sto_error do local socket_register = setmetatable({}, { __mode = "k" }) -- socket by coroutine local operation_register = setmetatable({}, { __mode = "k" }) -- operation "read"/"write" by coroutine local timeout_flags = setmetatable({}, { __mode = "k" }) -- true if timedout, by coroutine local function socket_callback(co) local skt = socket_register[co] local queue = operation_register[co] -- flag the timeout and resume the coroutine timeout_flags[co] = true _resumable:push(co) -- clear the socket from the current queue if queue == "read" then _reading:remove(skt) elseif queue == "write" then _writing:remove(skt) else error("bad queue name; expected 'read'/'write', got: "..tostring(queue)) end end -- Sets a socket timeout. -- Calling it as `sto_timeout()` will cancel the timeout. -- @param queue (string) the queue the socket is currently in, must be either "read" or "write" -- @param skt (socket) the socket on which to operate -- @param use_connect_to (bool) timeout to use is determined based on queue (read/write) or if this -- is truthy, it is the connect timeout. -- @return true function sto_timeout(skt, queue, use_connect_to) local co = coroutine_running() socket_register[co] = skt operation_register[co] = queue timeout_flags[co] = nil if skt then local to = (use_connect_to and user_timeouts_connect[skt]) or (queue == "read" and user_timeouts_receive[skt]) or user_timeouts_send[skt] copas.timeout(to, socket_callback) else copas.timeout(0) end return true end -- Changes the timeout to a different queue (read/write). -- Only usefull with ssl-handshakes and "wantread", "wantwrite" errors, when -- the queue has to be changed, so the timeout handler knows where to find the socket. -- @param queue (string) the new queue the socket is in, must be either "read" or "write" -- @return true function sto_change_queue(queue) operation_register[coroutine_running()] = queue return true end -- Responds with `true` if the operation timed-out. function sto_timed_out() return timeout_flags[coroutine_running()] end -- Returns the proper timeout error function sto_error(err) return useSocketTimeoutErrors[coroutine_running()] and err or "timeout" end end ------------------------------------------------------------------------------- -- Coroutine based socket I/O functions. ------------------------------------------------------------------------------- -- Returns "tcp"" for plain TCP and "ssl" for ssl-wrapped sockets, so truthy -- for tcp based, and falsy for udp based. local isTCP do local lookup = { tcp = "tcp", SSL = "ssl", } function isTCP(socket) return lookup[tostring(socket):sub(1,3)] end end function copas.close(skt, ...) _closed[#_closed+1] = skt return skt:close(...) end -- nil or negative is indefinitly function copas.settimeout(skt, timeout) timeout = timeout or -1 if type(timeout) ~= "number" then return nil, "timeout must be 'nil' or a number" end return copas.settimeouts(skt, timeout, timeout, timeout) end -- negative is indefinitly, nil means do not change function copas.settimeouts(skt, connect, send, read) if connect ~= nil and type(connect) ~= "number" then return nil, "connect timeout must be 'nil' or a number" end if connect then if connect < 0 then connect = nil end user_timeouts_connect[skt] = connect end if send ~= nil and type(send) ~= "number" then return nil, "send timeout must be 'nil' or a number" end if send then if send < 0 then send = nil end user_timeouts_send[skt] = send end if read ~= nil and type(read) ~= "number" then return nil, "read timeout must be 'nil' or a number" end if read then if read < 0 then read = nil end user_timeouts_receive[skt] = read end return true end -- reads a pattern from a client and yields to the reading set on timeouts -- UDP: a UDP socket expects a second argument to be a number, so it MUST -- be provided as the 'pattern' below defaults to a string. Will throw a -- 'bad argument' error if omitted. function copas.receive(client, pattern, part) local s, err pattern = pattern or "*l" local current_log = _reading_log sto_timeout(client, "read") repeat s, err, part = client:receive(pattern, part) -- guarantees that high throughput doesn't take other threads to starvation if (math.random(100) > 90) then copas.pause() end if s then current_log[client] = nil sto_timeout() return s, err, part elseif not _isSocketTimeout[err] then current_log[client] = nil sto_timeout() return s, err, part elseif sto_timed_out() then current_log[client] = nil return nil, sto_error(err), part end if err == "wantwrite" then -- wantwrite may be returned during SSL renegotiations current_log = _writing_log current_log[client] = gettime() sto_change_queue("write") coroutine_yield(client, _writing) else current_log = _reading_log current_log[client] = gettime() sto_change_queue("read") coroutine_yield(client, _reading) end until false end -- receives data from a client over UDP. Not available for TCP. -- (this is a copy of receive() method, adapted for receivefrom() use) function copas.receivefrom(client, size) local s, err, port size = size or UDP_DATAGRAM_MAX sto_timeout(client, "read") repeat s, err, port = client:receivefrom(size) -- upon success err holds ip address -- garantees that high throughput doesn't take other threads to starvation if (math.random(100) > 90) then copas.pause() end if s then _reading_log[client] = nil sto_timeout() return s, err, port elseif err ~= "timeout" then _reading_log[client] = nil sto_timeout() return s, err, port elseif sto_timed_out() then _reading_log[client] = nil return nil, sto_error(err), port end _reading_log[client] = gettime() coroutine_yield(client, _reading) until false end -- same as above but with special treatment when reading chunks, -- unblocks on any data received. function copas.receivepartial(client, pattern, part) local s, err pattern = pattern or "*l" local orig_size = #(part or "") local current_log = _reading_log sto_timeout(client, "read") repeat s, err, part = client:receive(pattern, part) -- guarantees that high throughput doesn't take other threads to starvation if (math.random(100) > 90) then copas.pause() end if s or (type(part) == "string" and #part > orig_size) then current_log[client] = nil sto_timeout() return s, err, part elseif not _isSocketTimeout[err] then current_log[client] = nil sto_timeout() return s, err, part elseif sto_timed_out() then current_log[client] = nil return nil, sto_error(err), part end if err == "wantwrite" then current_log = _writing_log current_log[client] = gettime() sto_change_queue("write") coroutine_yield(client, _writing) else current_log = _reading_log current_log[client] = gettime() sto_change_queue("read") coroutine_yield(client, _reading) end until false end copas.receivePartial = copas.receivepartial -- compat: receivePartial is deprecated -- sends data to a client. The operation is buffered and -- yields to the writing set on timeouts -- Note: from and to parameters will be ignored by/for UDP sockets function copas.send(client, data, from, to) local s, err from = from or 1 local lastIndex = from - 1 local current_log = _writing_log sto_timeout(client, "write") repeat s, err, lastIndex = client:send(data, lastIndex + 1, to) -- guarantees that high throughput doesn't take other threads to starvation if (math.random(100) > 90) then copas.pause() end if s then current_log[client] = nil sto_timeout() return s, err, lastIndex elseif not _isSocketTimeout[err] then current_log[client] = nil sto_timeout() return s, err, lastIndex elseif sto_timed_out() then current_log[client] = nil return nil, sto_error(err), lastIndex end if err == "wantread" then current_log = _reading_log current_log[client] = gettime() sto_change_queue("read") coroutine_yield(client, _reading) else current_log = _writing_log current_log[client] = gettime() sto_change_queue("write") coroutine_yield(client, _writing) end until false end function copas.sendto(client, data, ip, port) -- deprecated; for backward compatibility only, since UDP doesn't block on sending return client:sendto(data, ip, port) end -- waits until connection is completed function copas.connect(skt, host, port) skt:settimeout(0) local ret, err, tried_more_than_once sto_timeout(skt, "write", true) repeat ret, err = skt:connect(host, port) -- non-blocking connect on Windows results in error "Operation already -- in progress" to indicate that it is completing the request async. So essentially -- it is the same as "timeout" if ret or (err ~= "timeout" and err ~= "Operation already in progress") then _writing_log[skt] = nil sto_timeout() -- Once the async connect completes, Windows returns the error "already connected" -- to indicate it is done, so that error should be ignored. Except when it is the -- first call to connect, then it was already connected to something else and the -- error should be returned if (not ret) and (err == "already connected" and tried_more_than_once) then return 1 end return ret, err elseif sto_timed_out() then _writing_log[skt] = nil return nil, sto_error(err) end tried_more_than_once = tried_more_than_once or true _writing_log[skt] = gettime() coroutine_yield(skt, _writing) until false end -- Wraps a tcp socket in an ssl socket and configures it. If the socket was -- already wrapped, it does nothing and returns the socket. -- @param wrap_params the parameters for the ssl-context -- @return wrapped socket, or throws an error local function ssl_wrap(skt, wrap_params) if isTCP(skt) == "ssl" then return skt end -- was already wrapped if not wrap_params then error("cannot wrap socket into a secure socket (using 'ssl.wrap()') without parameters/context") end ssl = ssl or require("ssl") local nskt = assert(ssl.wrap(skt, wrap_params)) -- assert, because we do not want to silently ignore this one!! nskt:settimeout(0) -- non-blocking on the ssl-socket copas.settimeouts(nskt, user_timeouts_connect[skt], user_timeouts_send[skt], user_timeouts_receive[skt]) -- copy copas user-timeout to newly wrapped one local co = _autoclose_r[skt] if co then -- socket registered for autoclose, move registration to wrapped one _autoclose[co] = nskt _autoclose_r[skt] = nil _autoclose_r[nskt] = co end local sock_name = object_names[skt] if sock_name ~= tostring(skt) then -- socket had a custom name, so copy it over object_names[nskt] = sock_name end return nskt end -- For each luasec method we have a subtable, allows for future extension. -- Required structure: -- { -- wrap = ... -- parameter to 'wrap()'; the ssl parameter table, or the context object -- sni = { -- parameters to 'sni()' -- names = string | table -- 1st parameter -- strict = bool -- 2nd parameter -- } -- } local function normalize_sslt(sslt) local t = type(sslt) local r = setmetatable({}, { __index = function(self, key) -- a bug if this happens, here as a sanity check, just being careful since -- this is security stuff error("accessing unknown 'ssl_params' table key: "..tostring(key)) end, }) if t == "nil" then r.wrap = false r.sni = false elseif t == "table" then if sslt.mode or sslt.protocol then -- has the mandatory fields for the ssl-params table for handshake -- backward compatibility r.wrap = sslt r.sni = false else -- has the target definition, copy our known keys r.wrap = sslt.wrap or false -- 'or false' because we do not want nils r.sni = sslt.sni or false -- 'or false' because we do not want nils end elseif t == "userdata" then -- it's an ssl-context object for the handshake -- backward compatibility r.wrap = sslt r.sni = false else error("ssl parameters; did not expect type "..tostring(sslt)) end return r end --- -- Peforms an (async) ssl handshake on a connected TCP client socket. -- NOTE: if not ssl-wrapped already, then replace all previous socket references, with the returned new ssl wrapped socket -- Throws error and does not return nil+error, as that might silently fail -- in code like this; -- copas.addserver(s1, function(skt) -- skt = copas.wrap(skt, sparams) -- skt:dohandshake() --> without explicit error checking, this fails silently and -- skt:send(body) --> continues unencrypted -- @param skt Regular LuaSocket CLIENT socket object -- @param wrap_params Table with ssl parameters -- @return wrapped ssl socket, or throws an error function copas.dohandshake(skt, wrap_params) ssl = ssl or require("ssl") local nskt = ssl_wrap(skt, wrap_params) sto_timeout(nskt, "write", true) local queue repeat local success, err = nskt:dohandshake() if success then sto_timeout() return nskt elseif not _isSocketTimeout[err] then sto_timeout() error("TLS/SSL handshake failed: " .. tostring(err)) elseif sto_timed_out() then return nil, sto_error(err) elseif err == "wantwrite" then sto_change_queue("write") queue = _writing elseif err == "wantread" then sto_change_queue("read") queue = _reading else error("TLS/SSL handshake failed: " .. tostring(err)) end coroutine_yield(nskt, queue) until false end -- flushes a client write buffer (deprecated) function copas.flush() end -- wraps a TCP socket to use Copas methods (send, receive, flush and settimeout) local _skt_mt_tcp = { __tostring = function(self) return tostring(self.socket).." (copas wrapped)" end, __index = { send = function (self, data, from, to) return copas.send (self.socket, data, from, to) end, receive = function (self, pattern, prefix) if user_timeouts_receive[self.socket] == 0 then return copas.receivepartial(self.socket, pattern, prefix) end return copas.receive(self.socket, pattern, prefix) end, receivepartial = function (self, pattern, prefix) return copas.receivepartial(self.socket, pattern, prefix) end, flush = function (self) return copas.flush(self.socket) end, settimeout = function (self, time) return copas.settimeout(self.socket, time) end, settimeouts = function (self, connect, send, receive) return copas.settimeouts(self.socket, connect, send, receive) end, -- TODO: socket.connect is a shortcut, and must be provided with an alternative -- if ssl parameters are available, it will also include a handshake connect = function(self, ...) local res, err = copas.connect(self.socket, ...) if res then if self.ssl_params.sni then self:sni() end if self.ssl_params.wrap then res, err = self:dohandshake() end end return res, err end, close = function(self, ...) return copas.close(self.socket, ...) end, -- TODO: socket.bind is a shortcut, and must be provided with an alternative bind = function(self, ...) return self.socket:bind(...) end, -- TODO: is this DNS related? hence blocking? getsockname = function(self, ...) local ok, ip, port, family = pcall(self.socket.getsockname, self.socket, ...) if ok then return ip, port, family else return nil, "not implemented by LuaSec" end end, getstats = function(self, ...) return self.socket:getstats(...) end, setstats = function(self, ...) return self.socket:setstats(...) end, listen = function(self, ...) return self.socket:listen(...) end, accept = function(self, ...) return self.socket:accept(...) end, setoption = function(self, ...) local ok, res, err = pcall(self.socket.setoption, self.socket, ...) if ok then return res, err else return nil, "not implemented by LuaSec" end end, getoption = function(self, ...) local ok, val, err = pcall(self.socket.getoption, self.socket, ...) if ok then return val, err else return nil, "not implemented by LuaSec" end end, -- TODO: is this DNS related? hence blocking? getpeername = function(self, ...) local ok, ip, port, family = pcall(self.socket.getpeername, self.socket, ...) if ok then return ip, port, family else return nil, "not implemented by LuaSec" end end, shutdown = function(self, ...) return self.socket:shutdown(...) end, sni = function(self, names, strict) local sslp = self.ssl_params self.socket = ssl_wrap(self.socket, sslp.wrap) if names == nil then names = sslp.sni.names strict = sslp.sni.strict end return self.socket:sni(names, strict) end, dohandshake = function(self, wrap_params) local nskt, err = copas.dohandshake(self.socket, wrap_params or self.ssl_params.wrap) if not nskt then return nskt, err end self.socket = nskt -- replace internal socket with the newly wrapped ssl one return self end, getalpn = function(self, ...) local ok, proto, err = pcall(self.socket.getalpn, self.socket, ...) if ok then return proto, err else return nil, "not a tls socket" end end, getsniname = function(self, ...) local ok, name, err = pcall(self.socket.getsniname, self.socket, ...) if ok then return name, err else return nil, "not a tls socket" end end, } } -- wraps a UDP socket, copy of TCP one adapted for UDP. local _skt_mt_udp = {__index = { }} for k,v in pairs(_skt_mt_tcp) do _skt_mt_udp[k] = _skt_mt_udp[k] or v end for k,v in pairs(_skt_mt_tcp.__index) do _skt_mt_udp.__index[k] = v end _skt_mt_udp.__index.send = function(self, ...) return self.socket:send(...) end _skt_mt_udp.__index.sendto = function(self, ...) return self.socket:sendto(...) end _skt_mt_udp.__index.receive = function (self, size) return copas.receive (self.socket, (size or UDP_DATAGRAM_MAX)) end _skt_mt_udp.__index.receivefrom = function (self, size) return copas.receivefrom (self.socket, (size or UDP_DATAGRAM_MAX)) end -- TODO: is this DNS related? hence blocking? _skt_mt_udp.__index.setpeername = function(self, ...) return self.socket:setpeername(...) end _skt_mt_udp.__index.setsockname = function(self, ...) return self.socket:setsockname(...) end -- do not close client, as it is also the server for udp. _skt_mt_udp.__index.close = function(self, ...) return true end _skt_mt_udp.__index.settimeouts = function (self, connect, send, receive) return copas.settimeouts(self.socket, connect, send, receive) end --- -- Wraps a LuaSocket socket object in an async Copas based socket object. -- @param skt The socket to wrap -- @sslt (optional) Table with ssl parameters, use an empty table to use ssl with defaults -- @return wrapped socket object function copas.wrap (skt, sslt) if (getmetatable(skt) == _skt_mt_tcp) or (getmetatable(skt) == _skt_mt_udp) then return skt -- already wrapped end skt:settimeout(0) if isTCP(skt) then return setmetatable ({socket = skt, ssl_params = normalize_sslt(sslt)}, _skt_mt_tcp) else return setmetatable ({socket = skt}, _skt_mt_udp) end end --- Wraps a handler in a function that deals with wrapping the socket and doing the -- optional ssl handshake. function copas.handler(handler, sslparams) -- TODO: pass a timeout value to set, and use during handshake return function (skt, ...) skt = copas.wrap(skt, sslparams) -- this call will normalize the sslparams table local sslp = skt.ssl_params if sslp.sni then skt:sni(sslp.sni.names, sslp.sni.strict) end if sslp.wrap then skt:dohandshake(sslp.wrap) end return handler(skt, ...) end end -------------------------------------------------- -- Error handling -------------------------------------------------- local _errhandlers = setmetatable({}, { __mode = "k" }) -- error handler per coroutine function copas.gettraceback(msg, co, skt) local co_str = co == nil and "nil" or copas.getthreadname(co) local skt_str = skt == nil and "nil" or copas.getsocketname(skt) local msg_str = msg == nil and "" or tostring(msg) if msg_str == "" then msg_str = ("(coroutine: %s, socket: %s)"):format(msg_str, co_str, skt_str) else msg_str = ("%s (coroutine: %s, socket: %s)"):format(msg_str, co_str, skt_str) end if type(co) == "thread" then -- regular Copas coroutine return debug.traceback(co, msg_str) end -- not a coroutine, but the main thread, this happens if a timeout callback -- (see `copas.timeout` causes an error (those callbacks run on the main thread). return debug.traceback(msg_str, 2) end local function _deferror(msg, co, skt) print(copas.gettraceback(msg, co, skt)) end function copas.seterrorhandler(err, default) assert(err == nil or type(err) == "function", "Expected the handler to be a function, or nil") if default then assert(err ~= nil, "Expected the handler to be a function when setting the default") _deferror = err else _errhandlers[coroutine_running()] = err end end copas.setErrorHandler = copas.seterrorhandler -- deprecated; old casing function copas.geterrorhandler(co) co = co or coroutine_running() return _errhandlers[co] or _deferror end -- if `bool` is truthy, then the original socket errors will be returned in case of timeouts; -- `timeout, wantread, wantwrite, Operation already in progress`. If falsy, it will always -- return `timeout`. function copas.useSocketTimeoutErrors(bool) useSocketTimeoutErrors[coroutine_running()] = not not bool -- force to a boolean end ------------------------------------------------------------------------------- -- Thread handling ------------------------------------------------------------------------------- local function _doTick (co, skt, ...) if not co then return end -- if a coroutine was canceled/removed, don't resume it if _canceled[co] then _canceled[co] = nil -- also clean up the registry _threads[co] = nil return end -- res: the socket (being read/write on) or the time to sleep -- new_q: either _writing, _reading, or _sleeping -- local time_before = gettime() local ok, res, new_q = coroutine_resume(co, skt, ...) -- local duration = gettime() - time_before -- if duration > 1 then -- duration = math.floor(duration * 1000) -- pcall(_errhandlers[co] or _deferror, "task ran for "..tostring(duration).." milliseconds.", co, skt) -- end if new_q == _reading or new_q == _writing or new_q == _sleeping then -- we're yielding to a new queue new_q:insert (res) new_q:push (res, co) return end -- coroutine is terminating if ok and coroutine_status(co) ~= "dead" then -- it called coroutine.yield from a non-Copas function which is unexpected ok = false res = "coroutine.yield was called without a resume first, user-code cannot yield to Copas" end if not ok then local k, e = pcall(_errhandlers[co] or _deferror, res, co, skt) if not k then print("Failed executing error handler: " .. tostring(e)) end end local skt_to_close = _autoclose[co] if skt_to_close then skt_to_close:close() _autoclose[co] = nil _autoclose_r[skt_to_close] = nil end _errhandlers[co] = nil end local _accept do local client_counters = setmetatable({}, { __mode = "k" }) -- accepts a connection on socket input function _accept(server_skt, handler) local client_skt = server_skt:accept() if client_skt then local count = (client_counters[server_skt] or 0) + 1 client_counters[server_skt] = count object_names[client_skt] = object_names[server_skt] .. ":client_" .. count client_skt:settimeout(0) copas.settimeouts(client_skt, user_timeouts_connect[server_skt], -- copy server socket timeout settings user_timeouts_send[server_skt], user_timeouts_receive[server_skt]) local co = coroutine_create(handler) object_names[co] = object_names[server_skt] .. ":handler_" .. count if copas.autoclose then _autoclose[co] = client_skt _autoclose_r[client_skt] = co end _doTick(co, client_skt) end end end ------------------------------------------------------------------------------- -- Adds a server/handler pair to Copas dispatcher ------------------------------------------------------------------------------- do local function addTCPserver(server, handler, timeout, name) server:settimeout(0) if name then object_names[server] = name end _servers[server] = handler _reading:insert(server) if timeout then copas.settimeout(server, timeout) end end local function addUDPserver(server, handler, timeout, name) server:settimeout(0) local co = coroutine_create(handler) if name then object_names[server] = name end object_names[co] = object_names[server]..":handler" _reading:insert(server) if timeout then copas.settimeout(server, timeout) end _doTick(co, server) end function copas.addserver(server, handler, timeout, name) if isTCP(server) then addTCPserver(server, handler, timeout, name) else addUDPserver(server, handler, timeout, name) end end end function copas.removeserver(server, keep_open) local skt = server local mt = getmetatable(server) if mt == _skt_mt_tcp or mt == _skt_mt_udp then skt = server.socket end _servers:remove(skt) _reading:remove(skt) if keep_open then return true end return server:close() end ------------------------------------------------------------------------------- -- Adds an new coroutine thread to Copas dispatcher ------------------------------------------------------------------------------- function copas.addnamedthread(name, handler, ...) if type(name) == "function" and type(handler) == "string" then -- old call, flip args for compatibility name, handler = handler, name end -- create a coroutine that skips the first argument, which is always the socket -- passed by the scheduler, but `nil` in case of a task/thread local thread = coroutine_create(function(_, ...) copas.pause() return handler(...) end) if name then object_names[thread] = name end _threads[thread] = true -- register this thread so it can be removed _doTick (thread, nil, ...) return thread end function copas.addthread(handler, ...) return copas.addnamedthread(nil, handler, ...) end function copas.removethread(thread) -- if the specified coroutine is registered, add it to the canceled table so -- that next time it tries to resume it exits. _canceled[thread] = _threads[thread or 0] _sleeping:cancel(thread) end ------------------------------------------------------------------------------- -- Sleep/pause management functions ------------------------------------------------------------------------------- -- yields the current coroutine and wakes it after 'sleeptime' seconds. -- If sleeptime < 0 then it sleeps until explicitly woken up using 'wakeup' -- TODO: deprecated, remove in next major function copas.sleep(sleeptime) coroutine_yield((sleeptime or 0), _sleeping) end -- yields the current coroutine and wakes it after 'sleeptime' seconds. -- if sleeptime < 0 then it sleeps 0 seconds. function copas.pause(sleeptime) if sleeptime and sleeptime > 0 then coroutine_yield(sleeptime, _sleeping) else coroutine_yield(0, _sleeping) end end -- yields the current coroutine until explicitly woken up using 'wakeup' function copas.pauseforever() coroutine_yield(-1, _sleeping) end -- Wakes up a sleeping coroutine 'co'. function copas.wakeup(co) _sleeping:wakeup(co) end ------------------------------------------------------------------------------- -- Timeout management ------------------------------------------------------------------------------- do local timeout_register = setmetatable({}, { __mode = "k" }) local time_out_thread local timerwheel = require("timerwheel").new({ now = gettime, precision = TIMEOUT_PRECISION, ringsize = math.floor(60*60*24/TIMEOUT_PRECISION), -- ring size 1 day err_handler = function(err) return _deferror(err, time_out_thread) end, }) time_out_thread = copas.addnamedthread("copas_core_timer", function() while true do copas.pause(TIMEOUT_PRECISION) timerwheel:step() end end) -- get the number of timeouts running function copas.gettimeouts() return timerwheel:count() end --- Sets the timeout for the current coroutine. -- @param delay delay (seconds), use 0 (or math.huge) to cancel the timerout -- @param callback function with signature: `function(coroutine)` where coroutine is the routine that timed-out -- @return true function copas.timeout(delay, callback) local co = coroutine_running() local existing_timer = timeout_register[co] if existing_timer then timerwheel:cancel(existing_timer) end if delay > 0 and delay ~= math.huge then timeout_register[co] = timerwheel:set(delay, callback, co) elseif delay == 0 or delay == math.huge then timeout_register[co] = nil else error("timout value must be greater than or equal to 0, got: "..tostring(delay)) end return true end end ------------------------------------------------------------------------------- -- main tasks: manage readable and writable socket sets ------------------------------------------------------------------------------- -- a task is an object with a required method `step()` that deals with a -- single step for that task. local _tasks = {} do function _tasks:add(tsk) _tasks[#_tasks + 1] = tsk end end -- a task to check ready to read events local _readable_task = {} do _readable_task._events = {} local function tick(skt) local handler = _servers[skt] if handler then _accept(skt, handler) else _reading:remove(skt) _doTick(_reading:pop(skt), skt) end end function _readable_task:step() for _, skt in ipairs(self._events) do tick(skt) end end _tasks:add(_readable_task) end -- a task to check ready to write events local _writable_task = {} do _writable_task._events = {} local function tick(skt) _writing:remove(skt) _doTick(_writing:pop(skt), skt) end function _writable_task:step() for _, skt in ipairs(self._events) do tick(skt) end end _tasks:add(_writable_task) end -- sleeping threads task local _sleeping_task = {} do function _sleeping_task:step() local now = gettime() local co = _sleeping:pop(now) while co do -- we're pushing them to _resumable, since that list will be replaced before -- executing. This prevents tasks running twice in a row with pause(0) for example. -- So here we won't execute, but at _resumable step which is next _resumable:push(co) co = _sleeping:pop(now) end end _tasks:add(_sleeping_task) end -- resumable threads task local _resumable_task = {} do function _resumable_task:step() -- replace the resume list before iterating, so items placed in there -- will indeed end up in the next copas step, not in this one, and not -- create a loop local resumelist = _resumable:clear_resumelist() for _, co in ipairs(resumelist) do _doTick(co) end end _tasks:add(_resumable_task) end ------------------------------------------------------------------------------- -- Checks for reads and writes on sockets ------------------------------------------------------------------------------- local _select_plain do local last_cleansing = 0 local duration = function(t2, t1) return t2-t1 end if not socket then -- socket module unavailable, switch to luasystem sleep _select_plain = block_sleep else -- use socket.select to handle socket-io _select_plain = function(timeout) local err local now = gettime() -- remove any closed sockets to prevent select from hanging on them if _closed[1] then for i, skt in ipairs(_closed) do _closed[i] = { _reading:remove(skt), _writing:remove(skt) } end end _readable_task._events, _writable_task._events, err = socket.select(_reading, _writing, timeout) local r_events, w_events = _readable_task._events, _writable_task._events -- inject closed sockets in readable/writeable task so they can error out properly if _closed[1] then for i, skts in ipairs(_closed) do _closed[i] = nil r_events[#r_events+1] = skts[1] w_events[#w_events+1] = skts[2] end end if duration(now, last_cleansing) > WATCH_DOG_TIMEOUT then last_cleansing = now -- Check all sockets selected for reading, and check how long they have been waiting -- for data already, without select returning them as readable for skt,time in pairs(_reading_log) do if not r_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then -- This one timedout while waiting to become readable, so move -- it in the readable list and try and read anyway, despite not -- having been returned by select _reading_log[skt] = nil r_events[#r_events + 1] = skt r_events[skt] = #r_events end end -- Do the same for writing for skt,time in pairs(_writing_log) do if not w_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then _writing_log[skt] = nil w_events[#w_events + 1] = skt w_events[skt] = #w_events end end end if err == "timeout" and #r_events + #w_events > 0 then return nil else return err end end end end ------------------------------------------------------------------------------- -- Dispatcher loop step. -- Listen to client requests and handles them -- Returns false if no socket-data was handled, or true if there was data -- handled (or nil + error message) ------------------------------------------------------------------------------- local copas_stats local min_ever, max_ever local _select = _select_plain -- instrumented version of _select() to collect stats local _select_instrumented = function(timeout) if copas_stats then local step_duration = gettime() - copas_stats.step_start copas_stats.duration_max = math.max(copas_stats.duration_max, step_duration) copas_stats.duration_min = math.min(copas_stats.duration_min, step_duration) copas_stats.duration_tot = copas_stats.duration_tot + step_duration copas_stats.steps = copas_stats.steps + 1 else copas_stats = { duration_max = -1, duration_min = 999999, duration_tot = 0, steps = 0, } end local err = _select_plain(timeout) local now = gettime() copas_stats.time_start = copas_stats.time_start or now copas_stats.step_start = now return err end function copas.step(timeout) -- Need to wake up the select call in time for the next sleeping event if not _resumable:done() then timeout = 0 else timeout = math.min(_sleeping:getnext(), timeout or math.huge) end local err = _select(timeout) for _, tsk in ipairs(_tasks) do tsk:step() end if err then if err == "timeout" then if timeout + 0.01 > TIMEOUT_PRECISION and math.random(100) > 90 then -- we were idle, so occasionally do a GC sweep to ensure lingering -- sockets are closed, and we don't accidentally block the loop from -- exiting collectgarbage() end return false end return nil, err end return true end ------------------------------------------------------------------------------- -- Check whether there is something to do. -- returns false if there are no sockets for read/write nor tasks scheduled -- (which means Copas is in an empty spin) ------------------------------------------------------------------------------- function copas.finished() return #_reading == 0 and #_writing == 0 and _resumable:done() and _sleeping:done(copas.gettimeouts()) end local resetexit do local exit_semaphore, exiting function resetexit() exit_semaphore = copas.semaphore.new(1, 0, math.huge) exiting = false end -- Signals tasks to exit. But only if they check for it. By calling `copas.exiting` -- they can check if they should exit. Or by calling `copas.waitforexit` they can -- wait until the exit signal is given. function copas.exit() if exiting then return end exiting = true exit_semaphore:destroy() end -- returns whether Copas is in the process of exiting. Exit can be started by -- calling `copas.exit()`. function copas.exiting() return exiting end -- Pauses the current coroutine until Copas is exiting. To be used as an exit -- signal for tasks that need to clean up before exiting. function copas.waitforexit() exit_semaphore:take(1) end end local _getstats do local _getstats_instrumented, _getstats_plain function _getstats_plain(enable) -- this function gets hit if turned off, so turn on if true if enable == true then _select = _select_instrumented _getstats = _getstats_instrumented -- reset stats min_ever = nil max_ever = nil copas_stats = nil end return {} end -- convert from seconds to millisecs, with microsec precision local function useconds(t) return math.floor((t * 1000000) + 0.5) / 1000 end -- convert from seconds to seconds, with millisec precision local function mseconds(t) return math.floor((t * 1000) + 0.5) / 1000 end function _getstats_instrumented(enable) if enable == false then _select = _select_plain _getstats = _getstats_plain -- instrumentation disabled, so switch to the plain implementation return _getstats(enable) end if (not copas_stats) or (copas_stats.step == 0) then return {} end local stats = copas_stats copas_stats = nil min_ever = math.min(min_ever or 9999999, stats.duration_min) max_ever = math.max(max_ever or 0, stats.duration_max) stats.duration_min_ever = min_ever stats.duration_max_ever = max_ever stats.duration_avg = stats.duration_tot / stats.steps stats.step_start = nil stats.time_end = gettime() stats.time_tot = stats.time_end - stats.time_start stats.time_avg = stats.time_tot / stats.steps stats.duration_avg = useconds(stats.duration_avg) stats.duration_max = useconds(stats.duration_max) stats.duration_max_ever = useconds(stats.duration_max_ever) stats.duration_min = useconds(stats.duration_min) stats.duration_min_ever = useconds(stats.duration_min_ever) stats.duration_tot = useconds(stats.duration_tot) stats.time_avg = useconds(stats.time_avg) stats.time_start = mseconds(stats.time_start) stats.time_end = mseconds(stats.time_end) stats.time_tot = mseconds(stats.time_tot) return stats end _getstats = _getstats_plain end function copas.status(enable_stats) local res = _getstats(enable_stats) res.running = not not copas.running res.timeout = copas.gettimeouts() res.timer, res.inactive = _sleeping:status() res.read = #_reading res.write = #_writing res.active = _resumable:count() return res end ------------------------------------------------------------------------------- -- Dispatcher endless loop. -- Listen to client requests and handles them forever ------------------------------------------------------------------------------- function copas.loop(initializer, timeout) if type(initializer) == "function" then copas.addnamedthread("copas_initializer", initializer) else timeout = initializer or timeout end resetexit() copas.running = true while true do copas.step(timeout) if copas.finished() then if copas.exiting() then break end copas.exit() end end copas.running = false end ------------------------------------------------------------------------------- -- Naming sockets and coroutines. ------------------------------------------------------------------------------- do local function realsocket(skt) local mt = getmetatable(skt) if mt == _skt_mt_tcp or mt == _skt_mt_udp then return skt.socket else return skt end end function copas.setsocketname(name, skt) assert(type(name) == "string", "expected arg #1 to be a string") skt = assert(realsocket(skt), "expected arg #2 to be a socket") object_names[skt] = name end function copas.getsocketname(skt) skt = assert(realsocket(skt), "expected arg #1 to be a socket") return object_names[skt] end end function copas.setthreadname(name, coro) assert(type(name) == "string", "expected arg #1 to be a string") coro = coro or coroutine_running() assert(type(coro) == "thread", "expected arg #2 to be a coroutine or nil") object_names[coro] = name end function copas.getthreadname(coro) coro = coro or coroutine_running() assert(type(coro) == "thread", "expected arg #1 to be a coroutine or nil") return object_names[coro] end ------------------------------------------------------------------------------- -- Debug functionality. ------------------------------------------------------------------------------- do copas.debug = {} local log_core -- if truthy, the core-timer will also be logged local debug_log -- function used as logger local debug_yield = function(skt, queue) local name = object_names[coroutine_running()] if log_core or name ~= "copas_core_timer" then if queue == _sleeping then debug_log("yielding '", name, "' to SLEEP for ", skt," seconds") elseif queue == _writing then debug_log("yielding '", name, "' to WRITE on '", object_names[skt], "'") elseif queue == _reading then debug_log("yielding '", name, "' to READ on '", object_names[skt], "'") else debug_log("thread '", name, "' yielding to unexpected queue; ", tostring(queue), " (", type(queue), ")", debug.traceback()) end end return coroutine.yield(skt, queue) end local debug_resume = function(coro, skt, ...) local name = object_names[coro] if skt then debug_log("resuming '", name, "' for socket '", object_names[skt], "'") else if log_core or name ~= "copas_core_timer" then debug_log("resuming '", name, "'") end end return coroutine.resume(coro, skt, ...) end local debug_create = function(f) local f_wrapped = function(...) local results = pack(f(...)) debug_log("exiting '", object_names[coroutine_running()], "'") return unpack(results) end return coroutine.create(f_wrapped) end debug_log = fnil -- enables debug output for all coroutine operations. function copas.debug.start(logger, core) log_core = core debug_log = logger or print coroutine_yield = debug_yield coroutine_resume = debug_resume coroutine_create = debug_create end -- disables debug output for coroutine operations. function copas.debug.stop() debug_log = fnil coroutine_yield = coroutine.yield coroutine_resume = coroutine.resume coroutine_create = coroutine.create end do local call_id = 0 -- Description table of socket functions for debug output. -- each socket function name has TWO entries; -- 'name_in' and 'name_out', each being an array of names/descriptions of respectively -- input parameters and return values. -- If either table has a 'callback' key, then that is a function that will be called -- with the parameters/return-values for further inspection. local args = { settimeout_in = { "socket ", "seconds", "mode ", }, settimeout_out = { "success", "error ", }, connect_in = { "socket ", "address", "port ", }, connect_out = { "success", "error ", }, getfd_in = { "socket ", -- callback = function(...) -- print(debug.traceback("called from:", 4)) -- end, }, getfd_out = { "fd", }, send_in = { "socket ", "data ", "idx-start", "idx-end ", }, send_out = { "last-idx-send ", "error ", "err-last-idx-send", }, receive_in = { "socket ", "pattern", "prefix ", }, receive_out = { "received ", "error ", "partial data", }, dirty_in = { "socket", -- callback = function(...) -- print(debug.traceback("called from:", 4)) -- end, }, dirty_out = { "data in read-buffer", }, close_in = { "socket", -- callback = function(...) -- print(debug.traceback("called from:", 4)) -- end, }, close_out = { "success", "error", }, } local function print_call(func, msg, ...) print(msg) local arg = pack(...) local desc = args[func] or {} for i = 1, math.max(arg.n, #desc) do local value = arg[i] if type(value) == "string" then local xvalue = value:sub(1,30) if xvalue ~= value then xvalue = xvalue .."(...truncated)" end print("\t"..(desc[i] or i)..": '"..tostring(xvalue).."' ("..type(value).." #"..#value..")") else print("\t"..(desc[i] or i)..": '"..tostring(value).."' ("..type(value)..")") end end if desc.callback then desc.callback(...) end end local debug_mt = { __index = function(self, key) local value = self.__original_socket[key] if type(value) ~= "function" then return value end return function(self2, ...) local my_id = call_id + 1 call_id = my_id local results if self2 ~= self then -- there is no self print_call(tostring(key).."_in", my_id .. "-calling '"..tostring(key) .. "' with; ", self, ...) results = pack(value(self, ...)) else print_call(tostring(key).."_in", my_id .. "-calling '" .. tostring(key) .. "' with; ", self.__original_socket, ...) results = pack(value(self.__original_socket, ...)) end print_call(tostring(key).."_out", my_id .. "-results '"..tostring(key) .. "' returned; ", unpack(results)) return unpack(results) end end, __tostring = function(self) return tostring(self.__original_socket) end } -- wraps a socket (copas or luasocket) in a debug version printing all calls -- and their parameters/return values. Extremely noisy! -- returns the wrapped socket. -- NOTE: only for plain sockets, will not support TLS function copas.debug.socket(original_skt) if (getmetatable(original_skt) == _skt_mt_tcp) or (getmetatable(original_skt) == _skt_mt_udp) then -- already wrapped as Copas socket, so recurse with the original luasocket one original_skt.socket = copas.debug.socket(original_skt.socket) return original_skt end local proxy = setmetatable({ __original_socket = original_skt }, debug_mt) return proxy end end end return copas copas-4.8.0/src/copas/000077500000000000000000000000001476464242400145415ustar00rootroot00000000000000copas-4.8.0/src/copas/ftp.lua000066400000000000000000000052061476464242400160400ustar00rootroot00000000000000------------------------------------------------------------------- -- identical to the socket.ftp module except that it uses -- async wrapped Copas sockets local copas = require("copas") local socket = require("socket") local ftp = require("socket.ftp") local ltn12 = require("ltn12") local url = require("socket.url") local create = function() return copas.wrap(socket.tcp()) end local forwards = { -- setting these will be forwarded to the original smtp module PORT = true, TIMEOUT = true, PASSWORD = true, USER = true } copas.ftp = setmetatable({}, { -- use original module as metatable, to lookup constants like socket.TIMEOUT, etc. __index = ftp, -- Setting constants is forwarded to the luasocket.ftp module. __newindex = function(self, key, value) if forwards[key] then ftp[key] = value return end return rawset(self, key, value) end, }) local _M = copas.ftp ---[[ copy of Luasocket stuff here untile PR #133 is accepted -- a copy of the version in LuaSockets' ftp.lua -- no 'create' can be passed in the string form, hence a local copy here local default = { path = "/", scheme = "ftp" } -- a copy of the version in LuaSockets' ftp.lua -- no 'create' can be passed in the string form, hence a local copy here local function parse(u) local t = socket.try(url.parse(u, default)) socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") socket.try(t.host, "missing hostname") local pat = "^type=(.)$" if t.params then t.type = socket.skip(2, string.find(t.params, pat)) socket.try(t.type == "a" or t.type == "i", "invalid type '" .. t.type .. "'") end return t end -- parses a simple form into the advanced form -- if `body` is provided, a PUT, otherwise a GET. -- If GET, then a field `target` is added to store the results _M.parseRequest = function(u, body) local t = parse(u) if body then t.source = ltn12.source.string(body) else t.target = {} t.sink = ltn12.sink.table(t.target) end end --]] _M.put = socket.protect(function(putt, body) if type(putt) == "string" then putt = _M.parseRequest(putt, body) _M.put(putt) return table.concat(putt.target) else putt.create = putt.create or create return ftp.put(putt) end end) _M.get = socket.protect(function(gett) if type(gett) == "string" then gett = _M.parseRequest(gett) _M.get(gett) return table.concat(gett.target) else gett.create = gett.create or create return ftp.get(gett) end end) _M.command = function(cmdt) cmdt.create = cmdt.create or create return ftp.command(cmdt) end return _M copas-4.8.0/src/copas/http.lua000066400000000000000000000370261476464242400162330ustar00rootroot00000000000000----------------------------------------------------------------------------- -- Full copy of the LuaSocket code, modified to include -- https and http/https redirects, and Copas async enabled. ----------------------------------------------------------------------------- -- HTTP/1.1 client support for the Lua language. -- LuaSocket toolkit. -- Author: Diego Nehab ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- -- Declare module and import dependencies ------------------------------------------------------------------------------- local socket = require("socket") local url = require("socket.url") local ltn12 = require("ltn12") local mime = require("mime") local string = require("string") local headers = require("socket.headers") local base = _G local table = require("table") local copas = require("copas") copas.http = {} local _M = copas.http ----------------------------------------------------------------------------- -- Program constants ----------------------------------------------------------------------------- -- connection timeout in seconds _M.TIMEOUT = 60 -- default port for document retrieval _M.PORT = 80 -- user agent field sent in request _M.USERAGENT = socket._VERSION -- Default settings for SSL _M.SSLPORT = 443 _M.SSLPROTOCOL = "tlsv1_2" _M.SSLOPTIONS = "all" _M.SSLVERIFY = "none" _M.SSLSNISTRICT = false ----------------------------------------------------------------------------- -- Reads MIME headers from a connection, unfolding where needed ----------------------------------------------------------------------------- local function receiveheaders(sock, headers) local line, name, value, err headers = headers or {} -- get first line line, err = sock:receive() if err then return nil, err end -- headers go until a blank line is found while line ~= "" do -- get field-name and value name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) if not (name and value) then return nil, "malformed reponse headers" end name = string.lower(name) -- get next line (value might be folded) line, err = sock:receive() if err then return nil, err end -- unfold any folded values while string.find(line, "^%s") do value = value .. line line, err = sock:receive() if err then return nil, err end end -- save pair in table if headers[name] then headers[name] = headers[name] .. ", " .. value else headers[name] = value end end return headers end ----------------------------------------------------------------------------- -- Extra sources and sinks ----------------------------------------------------------------------------- socket.sourcet["http-chunked"] = function(sock, headers) return base.setmetatable({ getfd = function() return sock:getfd() end, dirty = function() return sock:dirty() end }, { __call = function() -- get chunk size, skip extention local line, err = sock:receive() if err then return nil, err end local size = base.tonumber(string.gsub(line, ";.*", ""), 16) if not size then return nil, "invalid chunk size" end -- was it the last chunk? if size > 0 then -- if not, get chunk and skip terminating CRLF local chunk, err = sock:receive(size) if chunk then sock:receive() end return chunk, err else -- if it was, read trailers into headers table headers, err = receiveheaders(sock, headers) if not headers then return nil, err end end end }) end socket.sinkt["http-chunked"] = function(sock) return base.setmetatable({ getfd = function() return sock:getfd() end, dirty = function() return sock:dirty() end }, { __call = function(self, chunk, err) if not chunk then return sock:send("0\r\n\r\n") end local size = string.format("%X\r\n", string.len(chunk)) return sock:send(size .. chunk .. "\r\n") end }) end ----------------------------------------------------------------------------- -- Low level HTTP API ----------------------------------------------------------------------------- local metat = { __index = {} } function _M.open(reqt) -- create socket with user connect function local c = socket.try(reqt:create()) -- method call, passing reqt table as self! local h = base.setmetatable({ c = c }, metat) -- create finalized try h.try = socket.newtry(function() h:close() end) -- set timeout before connecting local to = reqt.timeout or _M.TIMEOUT if type(to) == "table" then h.try(c:settimeouts( to.connect or _M.TIMEOUT, to.send or _M.TIMEOUT, to.receive or _M.TIMEOUT)) else h.try(c:settimeout(to)) end h.try(c:connect(reqt.host, reqt.port or _M.PORT)) -- here everything worked return h end function metat.__index:sendrequestline(method, uri) local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) return self.try(self.c:send(reqline)) end function metat.__index:sendheaders(tosend) local canonic = headers.canonic local h = "\r\n" for f, v in base.pairs(tosend) do h = (canonic[f] or f) .. ": " .. v .. "\r\n" .. h end self.try(self.c:send(h)) return 1 end function metat.__index:sendbody(headers, source, step) source = source or ltn12.source.empty() step = step or ltn12.pump.step -- if we don't know the size in advance, send chunked and hope for the best local mode = "http-chunked" if headers["content-length"] then mode = "keep-open" end return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step)) end function metat.__index:receivestatusline() local status = self.try(self.c:receive(5)) -- identify HTTP/0.9 responses, which do not contain a status line -- this is just a heuristic, but is what the RFC recommends if status ~= "HTTP/" then return nil, status end -- otherwise proceed reading a status line status = self.try(self.c:receive("*l", status)) local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) return self.try(base.tonumber(code), status) end function metat.__index:receiveheaders() return self.try(receiveheaders(self.c)) end function metat.__index:receivebody(headers, sink, step) sink = sink or ltn12.sink.null() step = step or ltn12.pump.step local length = base.tonumber(headers["content-length"]) local t = headers["transfer-encoding"] -- shortcut local mode = "default" -- connection close if t and t ~= "identity" then mode = "http-chunked" elseif base.tonumber(headers["content-length"]) then mode = "by-length" end return self.try(ltn12.pump.all(socket.source(mode, self.c, length), sink, step)) end function metat.__index:receive09body(status, sink, step) local source = ltn12.source.rewind(socket.source("until-closed", self.c)) source(status) return self.try(ltn12.pump.all(source, sink, step)) end function metat.__index:close() return self.c:close() end ----------------------------------------------------------------------------- -- High level HTTP API ----------------------------------------------------------------------------- local function adjusturi(reqt) local u = reqt -- if there is a proxy, we need the full url. otherwise, just a part. if not reqt.proxy and not _M.PROXY then u = { path = socket.try(reqt.path, "invalid path 'nil'"), params = reqt.params, query = reqt.query, fragment = reqt.fragment } end return url.build(u) end local function adjustproxy(reqt) local proxy = reqt.proxy or _M.PROXY if proxy then proxy = url.parse(proxy) return proxy.host, proxy.port or 3128 else return reqt.host, reqt.port end end local function adjustheaders(reqt) -- default headers local host = string.gsub(reqt.authority, "^.-@", "") local lower = { ["user-agent"] = _M.USERAGENT, ["host"] = host, ["connection"] = "close, TE", ["te"] = "trailers" } -- if we have authentication information, pass it along if reqt.user and reqt.password then lower["authorization"] = "Basic " .. (mime.b64(reqt.user .. ":" .. reqt.password)) end -- override with user headers for i,v in base.pairs(reqt.headers or lower) do lower[string.lower(i)] = v end return lower end -- default url parts local default = { host = "", port = _M.PORT, path ="/", scheme = "http" } local function adjustrequest(reqt) -- parse url if provided local nreqt = reqt.url and url.parse(reqt.url, default) or {} -- explicit components override url for i,v in base.pairs(reqt) do nreqt[i] = v end if nreqt.port == "" then nreqt.port = 80 end socket.try(nreqt.host and nreqt.host ~= "", "invalid host '" .. base.tostring(nreqt.host) .. "'") -- compute uri if user hasn't overriden nreqt.uri = reqt.uri or adjusturi(nreqt) -- ajust host and port if there is a proxy nreqt.host, nreqt.port = adjustproxy(nreqt) -- adjust headers in request nreqt.headers = adjustheaders(nreqt) return nreqt end local function shouldredirect(reqt, code, headers) return headers.location and string.gsub(headers.location, "%s", "") ~= "" and (reqt.redirect ~= false) and (code == 301 or code == 302 or code == 303 or code == 307) and (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") and (not reqt.nredirects or reqt.nredirects < 5) end local function shouldreceivebody(reqt, code) if reqt.method == "HEAD" then return nil end if code == 204 or code == 304 then return nil end if code >= 100 and code < 200 then return nil end return 1 end -- forward declarations local trequest, tredirect --[[local]] function tredirect(reqt, location) local result, code, headers, status = trequest { -- the RFC says the redirect URL has to be absolute, but some -- servers do not respect that url = url.absolute(reqt.url, location), source = reqt.source, sink = reqt.sink, headers = reqt.headers, proxy = reqt.proxy, nredirects = (reqt.nredirects or 0) + 1, create = reqt.create, timeout = reqt.timeout, } -- pass location header back as a hint we redirected headers = headers or {} headers.location = headers.location or location return result, code, headers, status end --[[local]] function trequest(reqt) -- we loop until we get what we want, or -- until we are sure there is no way to get it local nreqt = adjustrequest(reqt) local h = _M.open(nreqt) -- send request line and headers h:sendrequestline(nreqt.method, nreqt.uri) h:sendheaders(nreqt.headers) -- if there is a body, send it if nreqt.source then h:sendbody(nreqt.headers, nreqt.source, nreqt.step) end local code, status = h:receivestatusline() -- if it is an HTTP/0.9 server, simply get the body and we are done if not code then h:receive09body(status, nreqt.sink, nreqt.step) return 1, 200 end local headers -- ignore any 100-continue messages while code == 100 do h:receiveheaders() code, status = h:receivestatusline() end headers = h:receiveheaders() -- at this point we should have a honest reply from the server -- we can't redirect if we already used the source, so we report the error if shouldredirect(nreqt, code, headers) and not nreqt.source then h:close() return tredirect(reqt, headers.location) end -- here we are finally done if shouldreceivebody(nreqt, code) then h:receivebody(headers, nreqt.sink, nreqt.step) end h:close() return 1, code, headers, status end -- Return a function which creates a tcp socket that will -- include the optional SSL/TLS connection, and unsafe redirect checks function _M.getcreatefunc(params) params = params or {} local ssl_params = params.sslparams or {} ssl_params.wrap = ssl_params.wrap or { -- backward compatibility protocol = params.protocol, options = params.options, verify = params.verify, } ssl_params.sni = ssl_params.sni or { strict = _M.SSLSNISTRICT } -- Default settings ssl_params.wrap.protocol = ssl_params.wrap.protocol or _M.SSLPROTOCOL ssl_params.wrap.options = ssl_params.wrap.options or _M.SSLOPTIONS if ssl_params.wrap.verify == nil then ssl_params.wrap.verify = _M.SSLVERIFY end ssl_params.wrap.mode = "client" -- Force client mode if not ssl_params.sni.names then -- names haven't been set, and hence will be set below. Since this alters -- the table, we must make a copy. Otherwise the altered table might be -- reused if a redirect is encountered. local old_params = ssl_params ssl_params = {} for k,v in pairs(old_params) do ssl_params[k] = v end ssl_params.sni = { strict = old_params.sni.strict } end -- upvalue to track https -> http redirection local washttps = false -- 'create' function for LuaSocket return function (reqt) local u = url.parse(reqt.url) if (reqt.scheme or u.scheme) == "https" then -- set SNI name to host if not given ssl_params.sni.names = ssl_params.sni.names or u.host -- https, provide an ssl wrapped socket local conn = copas.wrap(socket.tcp(), ssl_params) -- insert https default port, overriding http port inserted by LuaSocket if not u.port then u.port = _M.SSLPORT reqt.url = url.build(u) reqt.port = _M.SSLPORT end washttps = true return conn else -- regular http, needs just a socket... if washttps and params.redirect ~= "all" then socket.try(nil, "Unallowed insecure redirect https to http") end return copas.wrap(socket.tcp()) end end end -- parses a shorthand form into the advanced table form. -- adds field `target` to the table. This will hold the return values. _M.parseRequest = function(u, b) local reqt = { url = u, target = {}, } reqt.sink = ltn12.sink.table(reqt.target) if b then reqt.source = ltn12.source.string(b) reqt.headers = { ["content-length"] = string.len(b), ["content-type"] = "application/x-www-form-urlencoded" } reqt.method = "POST" end return reqt end _M.request = socket.protect(function(reqt, body) if base.type(reqt) == "string" then reqt = _M.parseRequest(reqt, body) local ok, code, headers, status = _M.request(reqt) if ok then return table.concat(reqt.target), code, headers, status else return nil, code end else -- strict check on timeout table to prevent typo's from going unnoticed if type(reqt.timeout) == "table" then local allowed = { connect = true, send = true, receive = true } for k in pairs(reqt.timeout) do assert(allowed[k], "'"..tostring(k).."' is not a valid timeout option. Valid: 'connect', 'send', 'receive'") end end reqt.create = reqt.create or _M.getcreatefunc(reqt) return trequest(reqt) end end) return _M copas-4.8.0/src/copas/lock.lua000066400000000000000000000115211476464242400161740ustar00rootroot00000000000000local copas = require("copas") local gettime = copas.gettime local DEFAULT_TIMEOUT = 10 local lock = {} lock.__index = lock -- registry, locks indexed by the coroutines using them. local registry = setmetatable({}, { __mode="kv" }) --- Creates a new lock. -- @param seconds (optional) default timeout in seconds when acquiring the lock (defaults to 10), -- set to `math.huge` to have no timeout. -- @param not_reentrant (optional) if truthy the lock will not allow a coroutine to grab the same lock multiple times -- @return the lock object function lock.new(seconds, not_reentrant) local timeout = tonumber(seconds or DEFAULT_TIMEOUT) or -1 if timeout < 0 then error("expected timeout (1st argument) to be a number greater than or equal to 0, got: " .. tostring(seconds), 2) end return setmetatable({ timeout = timeout, not_reentrant = not_reentrant, queue = {}, q_tip = 0, -- index of the first in line waiting q_tail = 0, -- index where the next one will be inserted owner = nil, -- coroutine holding lock currently call_count = nil, -- recursion call count errors = setmetatable({}, { __mode = "k" }), -- error indexed by coroutine }, lock) end do local destroyed_func = function() return nil, "destroyed" end local destroyed_lock_mt = { __index = function() return destroyed_func end } --- destroy a lock. -- Releases all waiting threads with `nil+"destroyed"` function lock:destroy() --print("destroying ",self) for i = self.q_tip, self.q_tail do local co = self.queue[i] self.queue[i] = nil if co then self.errors[co] = "destroyed" --print("marked destroyed ", co) copas.wakeup(co) end end if self.owner then self.errors[self.owner] = "destroyed" --print("marked destroyed ", co) end self.queue = {} self.q_tip = 0 self.q_tail = 0 self.destroyed = true setmetatable(self, destroyed_lock_mt) return true end end local function timeout_handler(co) local self = registry[co] if not self then return end for i = self.q_tip, self.q_tail do if co == self.queue[i] then self.queue[i] = nil self.errors[co] = "timeout" --print("marked timeout ", co) copas.wakeup(co) return end end -- if we get here, we own it currently, or we finished it by now, or -- the lock was destroyed. Anyway, nothing to do here... end --- Acquires the lock. -- If the lock is owned by another thread, this will yield control, until the -- lock becomes available, or it times out. -- If `timeout == 0` then it will immediately return (without yielding). -- @param timeout (optional) timeout in seconds, defaults to the timeout passed to `new` (use `math.huge` to have no timeout). -- @return wait-time on success, or nil+error+wait_time on failure. Errors can be "timeout", "destroyed", or "lock is not re-entrant" function lock:get(timeout) local co = coroutine.running() local start_time -- is the lock already taken? if self.owner then -- are we re-entering? if co == self.owner and not self.not_reentrant then self.call_count = self.call_count + 1 return 0 end self.queue[self.q_tail] = co self.q_tail = self.q_tail + 1 timeout = timeout or self.timeout if timeout == 0 then return nil, "timeout", 0 end -- set up timeout registry[co] = self copas.timeout(timeout, timeout_handler) start_time = gettime() copas.pauseforever() local err = self.errors[co] self.errors[co] = nil registry[co] = nil --print("released ", co, err) if err ~= "timeout" then copas.timeout(0) end if err then return nil, err, gettime() - start_time end end -- it's ours to have self.owner = co self.call_count = 1 return start_time and (gettime() - start_time) or 0 end --- Releases the lock currently held. -- Releasing a lock that is not owned by the current co-routine will return -- an error. -- returns true, or nil+err on an error function lock:release() local co = coroutine.running() if co ~= self.owner then return nil, "cannot release a lock not owned" end self.call_count = self.call_count - 1 if self.call_count > 0 then -- same coro is still holding it return true end -- need a loop, since individual coroutines might have been removed -- so there might be holes while self.q_tip < self.q_tail do local next_up = self.queue[self.q_tip] if next_up then self.owner = next_up self.queue[self.q_tip] = nil self.q_tip = self.q_tip + 1 copas.wakeup(next_up) return true end self.q_tip = self.q_tip + 1 end -- queue is empty, reset pointers self.owner = nil self.q_tip = 0 self.q_tail = 0 return true end return lock copas-4.8.0/src/copas/queue.lua000066400000000000000000000133331476464242400163730ustar00rootroot00000000000000local copas = require "copas" local gettime = copas.gettime local Sema = copas.semaphore local Lock = copas.lock local Queue = {} Queue.__index = Queue local new_name do local count = 0 function new_name() count = count + 1 return "copas_queue_" .. count end end -- Creates a new Queue instance function Queue.new(opts) opts = opts or {} local self = {} setmetatable(self, Queue) self.name = opts.name or new_name() self.sema = Sema.new(10^9) self.head = 1 self.tail = 1 self.list = {} self.workers = setmetatable({}, { __mode = "k" }) self.stopping = false self.worker_id = 0 self.exit_semaphore = Sema.new(10^9) return self end -- Pushes an item in the queue (can be 'nil') -- returns true, or nil+err ("stopping", or "destroyed") function Queue:push(item) if self.stopping then return nil, "stopping" end self.list[self.head] = item self.head = self.head + 1 self.sema:give() return true end -- Pops and item from the queue. If there are no items in the queue it will yield -- until there are or a timeout happens (exception is when `timeout == 0`, then it will -- not yield but return immediately). If the timeout is `math.huge` it will wait forever. -- Returns item, or nil+err ("timeout", or "destroyed") function Queue:pop(timeout) local ok, err = self.sema:take(1, timeout) if not ok then return ok, err end local item = self.list[self.tail] self.list[self.tail] = nil self.tail = self.tail + 1 if self.tail == self.head then -- reset queue self.list = {} self.tail = 1 self.head = 1 if self.stopping then -- we're stopping and last item being returned, so we're done self:destroy() end end return item end -- return the number of items left in the queue function Queue:get_size() return self.head - self.tail end -- instructs the queue to stop. Will not accept any more 'push' calls. -- will autocall 'destroy' when the queue is empty. -- returns immediately. See `finish` function Queue:stop() if not self.stopping then self.stopping = true self.lock = Lock.new(nil, true) self.lock:get() -- close the lock if self:get_size() == 0 then -- queue is already empty, so "pop" function cannot call destroy on next -- pop, so destroy now. self:destroy() end end return true end -- Finishes a queue. Calls stop and then waits for the queue to run empty (and be -- destroyed) before returning. returns true or nil+err ("timeout", or "destroyed") -- Parameter no_destroy_on_timeout indicates if the queue is not to be forcefully -- destroyed on a timeout. function Queue:finish(timeout, no_destroy_on_timeout) self:stop() timeout = timeout or self.lock.timeout local endtime = gettime() + timeout local _, err = self.lock:get(timeout) -- the lock never gets released, only destroyed, so we have to check the error string if err == "timeout" then if not no_destroy_on_timeout then self:destroy() end return nil, err end -- if we get here, the lock was destroyed, so the queue is empty, now wait for all workers to exit if not next(self.workers) then -- all workers already exited, we're done return true end -- multiple threads can call this "finish" method, so we must check exiting workers -- one by one. while true do local _, err = self.exit_semaphore:take(1, math.max(0, endtime - gettime())) if err == "destroyed" then return true -- someone else destroyed/finished it, so we're done end if err == "timeout" then if not no_destroy_on_timeout then self:destroy() end return nil, "timeout" end if not next(self.workers) then self.exit_semaphore:destroy() return true -- all workers exited, we're done end end end do local destroyed_func = function() return nil, "destroyed" end local destroyed_queue_mt = { __index = function() return destroyed_func end } -- destroys a queue immediately. Abandons what is left in the queue. -- Releases all waiting threads with `nil+"destroyed"` function Queue:destroy() if self.lock then self.lock:destroy() end self.sema:destroy() setmetatable(self, destroyed_queue_mt) -- clear anything left in the queue for key in pairs(self.list) do self.list[key] = nil end return true end end -- adds a worker that will handle whatever is passed into the queue. Can be called -- multiple times to add more workers. -- The threads automatically exit when the queue is destroyed. -- worker function signature: `function(item)` (Note: worker functions run -- unprotected, so wrap code in an (x)pcall if errors are expected, otherwise the -- worker will exit on an error, and queue handling will stop) -- Returns the coroutine added. function Queue:add_worker(worker) assert(type(worker) == "function", "expected worker to be a function") local coro self.worker_id = self.worker_id + 1 local worker_name = self.name .. ":worker_" .. self.worker_id coro = copas.addnamedthread(worker_name, function() while true do local item, err = self:pop(math.huge) -- wait forever if err then break -- queue destroyed, exit end worker(item) -- TODO: wrap in errorhandling end self.workers[coro] = nil if self.exit_semaphore then self.exit_semaphore:give(1) end end) self.workers[coro] = true return coro end -- returns a list/array of current workers (coroutines) handling the queue. -- (only the workers added by `add_worker`, and still active, will be in this list) function Queue:get_workers() local lst = {} for coro in pairs(self.workers) do if coroutine.status(coro) ~= "dead" then lst[#lst+1] = coro end end return lst end return Queue copas-4.8.0/src/copas/semaphore.lua000066400000000000000000000117161476464242400172350ustar00rootroot00000000000000local copas = require("copas") local DEFAULT_TIMEOUT = 10 local semaphore = {} semaphore.__index = semaphore -- registry, semaphore indexed by the coroutines using them. local registry = setmetatable({}, { __mode="kv" }) -- create a new semaphore -- @param max maximum number of resources the semaphore can hold (this maximum does NOT include resources that have been given but not yet returned). -- @param start (optional, default 0) the initial resources available -- @param seconds (optional, default 10) default semaphore timeout in seconds, or `math.huge` to have no timeout. function semaphore.new(max, start, seconds) local timeout = tonumber(seconds or DEFAULT_TIMEOUT) or -1 if timeout < 0 then error("expected timeout (2nd argument) to be a number greater than or equal to 0, got: " .. tostring(seconds), 2) end if type(max) ~= "number" or max < 1 then error("expected max resources (1st argument) to be a number greater than 0, got: " .. tostring(max), 2) end local self = setmetatable({ count = start or 0, max = max, timeout = timeout, q_tip = 1, -- position of next entry waiting q_tail = 1, -- position where next one will be inserted queue = {}, to_flags = setmetatable({}, { __mode = "k" }), -- timeout flags indexed by coroutine }, semaphore) return self end do local destroyed_func = function() return nil, "destroyed" end local destroyed_semaphore_mt = { __index = function() return destroyed_func end } -- destroy a semaphore. -- Releases all waiting threads with `nil+"destroyed"` function semaphore:destroy() self:give(math.huge) self.destroyed = true setmetatable(self, destroyed_semaphore_mt) return true end end -- Gives resources. -- @param given (optional, default 1) number of resources to return. If more -- than the maximum are returned then it will be capped at the maximum and -- error "too many" will be returned. function semaphore:give(given) local err given = given or 1 local count = self.count + given --print("now at",count, ", after +"..given) if count > self.max then count = self.max err = "too many" end while self.q_tip < self.q_tail do local i = self.q_tip local nxt = self.queue[i] -- there can be holes, so nxt might be nil if not nxt then self.q_tip = i + 1 else if count >= nxt.requested then -- release it self.queue[i] = nil self.to_flags[nxt.co] = nil count = count - nxt.requested self.q_tip = i + 1 copas.wakeup(nxt.co) nxt.co = nil else break -- we ran out of resources end end end if self.q_tip == self.q_tail then -- reset queue self.queue = {} self.q_tip = 1 self.q_tail = 1 end self.count = count if err then return nil, err end return true end local function timeout_handler(co) local self = registry[co] --print("checking timeout ", co) if not self then return end for i = self.q_tip, self.q_tail do local item = self.queue[i] if item and co == item.co then self.queue[i] = nil self.to_flags[co] = true --print("marked timeout ", co) copas.wakeup(co) return end end -- nothing to do here... end -- Requests resources from the semaphore. -- Waits if there are not enough resources available before returning. -- @param requested (optional, default 1) the number of resources requested -- @param timeout (optional, defaults to semaphore timeout) timeout in -- seconds. If 0 it will either succeed or return immediately with error "timeout". -- If `math.huge` it will wait forever. -- @return true, or nil+"destroyed" function semaphore:take(requested, timeout) requested = requested or 1 if self.q_tail == 1 and self.count >= requested then -- nobody is waiting before us, and there is enough in store self.count = self.count - requested return true end if requested > self.max then return nil, "too many" end local to = timeout or self.timeout if to == 0 then return nil, "timeout" end -- get in line local co = coroutine.running() self.to_flags[co] = nil registry[co] = self copas.timeout(to, timeout_handler) self.queue[self.q_tail] = { co = co, requested = requested, --timeout = nil, -- flag indicating timeout } self.q_tail = self.q_tail + 1 copas.pauseforever() -- block until woken registry[co] = nil if self.to_flags[co] then -- a timeout happened self.to_flags[co] = nil return nil, "timeout" end copas.timeout(0) if self.destroyed then return nil, "destroyed" end return true end -- returns current available resources function semaphore:get_count() return self.count end -- returns total shortage for requested resources function semaphore:get_wait() local wait = 0 for i = self.q_tip, self.q_tail - 1 do wait = wait + ((self.queue[i] or {}).requested or 0) end return wait - self.count end return semaphore copas-4.8.0/src/copas/smtp.lua000066400000000000000000000017341476464242400162340ustar00rootroot00000000000000------------------------------------------------------------------- -- identical to the socket.smtp module except that it uses -- async wrapped Copas sockets local copas = require("copas") local smtp = require("socket.smtp") local socket = require("socket") local create = function() return copas.wrap(socket.tcp()) end local forwards = { -- setting these will be forwarded to the original smtp module PORT = true, SERVER = true, TIMEOUT = true, DOMAIN = true, TIMEZONE = true } copas.smtp = setmetatable({}, { -- use original module as metatable, to lookup constants like socket.SERVER, etc. __index = smtp, -- Setting constants is forwarded to the luasocket.smtp module. __newindex = function(self, key, value) if forwards[key] then smtp[key] = value return end return rawset(self, key, value) end, }) local _M = copas.smtp _M.send = function(mailt) mailt.create = mailt.create or create return smtp.send(mailt) end return _Mcopas-4.8.0/src/copas/timer.lua000066400000000000000000000064141476464242400163710ustar00rootroot00000000000000local copas = require("copas") local xpcall = xpcall local coroutine_running = coroutine.running if _VERSION=="Lua 5.1" and not jit then -- obsolete: only for Lua 5.1 compatibility xpcall = require("coxpcall").xpcall coroutine_running = require("coxpcall").running end local timer = {} timer.__index = timer local new_name do local count = 0 function new_name() count = count + 1 return "copas_timer_" .. count end end do local function expire_func(self, initial_delay) if self.errorhandler then copas.seterrorhandler(self.errorhandler) end copas.pause(initial_delay) while true do if not self.cancelled then if not self.recurring then -- non-recurring timer self.cancelled = true self.co = nil self:callback(self.params) return else -- recurring timer self:callback(self.params) end end if self.cancelled then -- clean up and exit the thread self.co = nil self.cancelled = true return end copas.pause(self.delay) end end --- Arms the timer object. -- @param initial_delay (optional) the first delay to use, if not provided uses the timer delay -- @return timer object, nil+error, or throws an error on bad input function timer:arm(initial_delay) assert(initial_delay == nil or initial_delay >= 0, "delay must be greater than or equal to 0") if self.co then return nil, "already armed" end self.cancelled = false self.co = copas.addnamedthread(self.name, expire_func, self, initial_delay or self.delay) return self end end --- Cancels a running timer. -- @return timer object, or nil+error function timer:cancel() if not self.co then return nil, "not armed" end if self.cancelled then return nil, "already cancelled" end self.cancelled = true copas.wakeup(self.co) -- resume asap copas.removethread(self.co) -- will immediately drop the thread upon resuming self.co = nil return self end do -- xpcall error handler that forwards to the copas errorhandler local ehandler = function(err_obj) return copas.geterrorhandler()(err_obj, coroutine_running(), nil) end --- Creates a new timer object. -- Note: the callback signature is: `function(timer_obj, params)`. -- @param opts (table) `opts.delay` timer delay in seconds, `opts.callback` function to execute, `opts.recurring` boolean -- `opts.params` (optional) this value will be passed to the timer callback, `opts.initial_delay` (optional) the first delay to use, defaults to `delay`. -- @return timer object, or throws an error on bad input function timer.new(opts) assert((opts.delay or -1) >= 0, "delay must be greater than or equal to 0") assert(type(opts.callback) == "function", "expected callback to be a function") local callback = function(timer_obj, params) xpcall(opts.callback, ehandler, timer_obj, params) end return setmetatable({ name = opts.name or new_name(), delay = opts.delay, callback = callback, recurring = not not opts.recurring, params = opts.params, cancelled = false, errorhandler = opts.errorhandler, }, timer):arm(opts.initial_delay) end end return timer copas-4.8.0/tests/000077500000000000000000000000001476464242400140075ustar00rootroot00000000000000copas-4.8.0/tests/certs/000077500000000000000000000000001476464242400151275ustar00rootroot00000000000000copas-4.8.0/tests/certs/_readme.md000066400000000000000000000001011476464242400170350ustar00rootroot00000000000000The certificate generation scripts here are copied from LuaSec copas-4.8.0/tests/certs/all.bat000066400000000000000000000004571476464242400163750ustar00rootroot00000000000000REM make sure the 'openssl.exe' commandline tool is in your path before starting! REM set the path below; set opensslpath=c:\program files (x86)\openssl-win32\bin setlocal set path=%opensslpath%;%path% call roota.bat call rootb.bat call servera.bat call serverb.bat call clienta.bat call clientb.bat copas-4.8.0/tests/certs/all.sh000077500000000000000000000002371476464242400162400ustar00rootroot00000000000000#!/bin/sh CWD=$(PWD) cd $( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) ./rootA.sh ./rootB.sh ./serverA.sh ./serverB.sh ./clientA.sh ./clientB.sh cd $CWD copas-4.8.0/tests/certs/clientA.bat000066400000000000000000000006341476464242400172010ustar00rootroot00000000000000rem #!/bin/sh openssl req -newkey rsa:2048 -sha256 -keyout clientAkey.pem -out clientAreq.pem -nodes -config ./clientA.cnf -days 365 -batch 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 copy clientAcert.pem + rootA.pem clientA.pem openssl x509 -subject -issuer -noout -in clientA.pem copas-4.8.0/tests/certs/clientA.cnf000066400000000000000000000224571476464242400172100ustar00rootroot00000000000000# # OpenSSL example configuration file. # This is mostly being used for generation of certificate requests. # # This definition stops the following lines choking if HOME isn't # defined. HOME = . RANDFILE = $ENV::HOME/.rnd # Extra OBJECT IDENTIFIER info: #oid_file = $ENV::HOME/.oid oid_section = new_oids # To use this configuration file with the "-extfile" option of the # "openssl x509" utility, name here the section containing the # X.509v3 extensions to use: # extensions = # (Alternatively, use a configuration file that has only # X.509v3 extensions in its main [= default] section.) [ new_oids ] # We can add new OIDs in here for use by 'ca' and 'req'. # Add a simple OID like this: # testoid1=1.2.3.4 # Or use config file substitution like this: # testoid2=${testoid1}.5.6 #################################################################### [ ca ] default_ca = CA_default # The default ca section #################################################################### [ CA_default ] dir = ./demoCA # Where everything is kept certs = $dir/certs # Where the issued certs are kept crl_dir = $dir/crl # Where the issued crl are kept database = $dir/index.txt # database index file. #unique_subject = no # Set to 'no' to allow creation of # several ctificates with same subject. new_certs_dir = $dir/newcerts # default place for new certs. certificate = $dir/cacert.pem # The CA certificate serial = $dir/serial # The current serial number crlnumber = $dir/crlnumber # the current crl number # must be commented out to leave a V1 CRL crl = $dir/crl.pem # The current CRL private_key = $dir/private/cakey.pem # The private key RANDFILE = $dir/private/.rand # private random number file x509_extensions = usr_cert # The extensions to add to the cert # Comment out the following two lines for the "traditional" # (and highly broken) format. name_opt = ca_default # Subject Name options cert_opt = ca_default # Certificate field options # Extension copying option: use with caution. # copy_extensions = copy # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs # so this is commented out by default to leave a V1 CRL. # crlnumber must also be commented out to leave a V1 CRL. # crl_extensions = crl_ext default_days = 365 # how long to certify for default_crl_days= 30 # how long before next CRL default_md = sha1 # which md to use. preserve = no # keep passed DN ordering # A few difference way of specifying how similar the request should look # For type CA, the listed attributes must be the same, and the optional # and supplied fields are just that :-) policy = policy_match # For the CA policy [ policy_match ] countryName = match stateOrProvinceName = match organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional # For the 'anything' policy # At this point in time, you must list all acceptable 'object' # types. [ policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional #################################################################### [ req ] default_bits = 1024 default_keyfile = privkey.pem distinguished_name = req_distinguished_name attributes = req_attributes x509_extensions = v3_ca # The extensions to add to the self signed cert # Passwords for private keys if not present they will be prompted for # input_password = secret # output_password = secret # This sets a mask for permitted string types. There are several options. # default: PrintableString, T61String, BMPString. # pkix : PrintableString, BMPString. # utf8only: only UTF8Strings. # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). # MASK:XXXX a literal mask value. # WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings # so use this option with caution! string_mask = nombstr # req_extensions = v3_req # The extensions to add to a certificate request [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = BR countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Some-State stateOrProvinceName_default = Espirito Santo localityName = Locality Name (eg, city) localityName_default = Santo Antonio do Canaa 0.organizationName = Organization Name (eg, company) 0.organizationName_default = Sao Tonico Ltda # we can do this but it is not needed normally :-) #1.organizationName = Second Organization Name (eg, company) #1.organizationName_default = World Wide Web Pty Ltd organizationalUnitName = Organizational Unit Name (eg, section) organizationalUnitName_default = Department of Computer Science commonName = Common Name (eg, YOUR name) commonName_default = Client A commonName_max = 64 emailAddress = Email Address emailAddress_max = 64 # SET-ex3 = SET extension number 3 [ req_attributes ] challengePassword = A challenge password challengePassword_min = 4 challengePassword_max = 20 unstructuredName = An optional company name [ usr_cert ] # These extensions are added when 'ca' signs a request. # This goes against PKIX guidelines but some CAs do it and some software # requires this to avoid interpreting an end user certificate as a CA. basicConstraints=CA:FALSE # Here are some examples of the usage of nsCertType. If it is omitted # the certificate can be used for anything *except* object signing. # This is OK for an SSL server. # nsCertType = server # For an object signing certificate this would be used. # nsCertType = objsign # For normal client use this is typical # nsCertType = client, email # and for everything including object signing: # nsCertType = client, email, objsign # This is typical in keyUsage for a client certificate. # keyUsage = nonRepudiation, digitalSignature, keyEncipherment # This will be displayed in Netscape's comment listbox. nsComment = "OpenSSL Generated Certificate" # PKIX recommendations harmless if included in all certificates. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer # This stuff is for subjectAltName and issuerAltname. # Import the email address. # subjectAltName=email:copy # An alternative to produce certificates that aren't # deprecated according to PKIX. # subjectAltName=email:move # Copy subject details # issuerAltName=issuer:copy #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem #nsBaseUrl #nsRevocationUrl #nsRenewalUrl #nsCaPolicyUrl #nsSslServerName [ v3_req ] # Extensions to add to a certificate request basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment [ v3_ca ] # Extensions for a typical CA # PKIX recommendation. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid:always,issuer:always # This is what PKIX recommends but some broken software chokes on critical # extensions. #basicConstraints = critical,CA:true # So we do this instead. basicConstraints = CA:true # Key usage: this is typical for a CA certificate. However since it will # prevent it being used as an test self-signed certificate it is best # left out by default. # keyUsage = cRLSign, keyCertSign # Some might want this also # nsCertType = sslCA, emailCA # Include email address in subject alt name: another PKIX recommendation # subjectAltName=email:copy # Copy issuer details # issuerAltName=issuer:copy # DER hex encoding of an extension: beware experts only! # obj=DER:02:03 # Where 'obj' is a standard or added object # You can even override a supported extension: # basicConstraints= critical, DER:30:03:01:01:FF [ crl_ext ] # CRL extensions. # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. # issuerAltName=issuer:copy authorityKeyIdentifier=keyid:always,issuer:always [ proxy_cert_ext ] # These extensions should be added when creating a proxy certificate # This goes against PKIX guidelines but some CAs do it and some software # requires this to avoid interpreting an end user certificate as a CA. basicConstraints=CA:FALSE # Here are some examples of the usage of nsCertType. If it is omitted # the certificate can be used for anything *except* object signing. # This is OK for an SSL server. # nsCertType = server # For an object signing certificate this would be used. # nsCertType = objsign # For normal client use this is typical # nsCertType = client, email # and for everything including object signing: # nsCertType = client, email, objsign # This is typical in keyUsage for a client certificate. # keyUsage = nonRepudiation, digitalSignature, keyEncipherment # This will be displayed in Netscape's comment listbox. nsComment = "OpenSSL Generated Certificate" # PKIX recommendations harmless if included in all certificates. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer:always # This stuff is for subjectAltName and issuerAltname. # Import the email address. # subjectAltName=email:copy # An alternative to produce certificates that aren't # deprecated according to PKIX. # subjectAltName=email:move # Copy subject details # issuerAltName=issuer:copy #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem #nsBaseUrl #nsRevocationUrl #nsRenewalUrl #nsCaPolicyUrl #nsSslServerName # This really needs to be in place for it to be a proxy certificate. proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo copas-4.8.0/tests/certs/clientA.sh000077500000000000000000000006431476464242400170500ustar00rootroot00000000000000#!/bin/sh openssl req -newkey rsa:2048 -sha256 -keyout clientAkey.pem -out clientAreq.pem \ -nodes -config ./clientA.cnf -days 365 -batch 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 cat clientAcert.pem rootA.pem > clientA.pem openssl x509 -subject -issuer -noout -in clientA.pem copas-4.8.0/tests/certs/clientB.bat000066400000000000000000000006341476464242400172020ustar00rootroot00000000000000rem #!/bin/sh openssl req -newkey rsa:2048 -sha256 -keyout clientBkey.pem -out clientBreq.pem -nodes -config ./clientB.cnf -days 365 -batch 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 copy clientBcert.pem + rootB.pem clientB.pem openssl x509 -subject -issuer -noout -in clientB.pem copas-4.8.0/tests/certs/clientB.cnf000066400000000000000000000224571476464242400172110ustar00rootroot00000000000000# # OpenSSL example configuration file. # This is mostly being used for generation of certificate requests. # # This definition stops the following lines choking if HOME isn't # defined. HOME = . RANDFILE = $ENV::HOME/.rnd # Extra OBJECT IDENTIFIER info: #oid_file = $ENV::HOME/.oid oid_section = new_oids # To use this configuration file with the "-extfile" option of the # "openssl x509" utility, name here the section containing the # X.509v3 extensions to use: # extensions = # (Alternatively, use a configuration file that has only # X.509v3 extensions in its main [= default] section.) [ new_oids ] # We can add new OIDs in here for use by 'ca' and 'req'. # Add a simple OID like this: # testoid1=1.2.3.4 # Or use config file substitution like this: # testoid2=${testoid1}.5.6 #################################################################### [ ca ] default_ca = CA_default # The default ca section #################################################################### [ CA_default ] dir = ./demoCA # Where everything is kept certs = $dir/certs # Where the issued certs are kept crl_dir = $dir/crl # Where the issued crl are kept database = $dir/index.txt # database index file. #unique_subject = no # Set to 'no' to allow creation of # several ctificates with same subject. new_certs_dir = $dir/newcerts # default place for new certs. certificate = $dir/cacert.pem # The CA certificate serial = $dir/serial # The current serial number crlnumber = $dir/crlnumber # the current crl number # must be commented out to leave a V1 CRL crl = $dir/crl.pem # The current CRL private_key = $dir/private/cakey.pem # The private key RANDFILE = $dir/private/.rand # private random number file x509_extensions = usr_cert # The extensions to add to the cert # Comment out the following two lines for the "traditional" # (and highly broken) format. name_opt = ca_default # Subject Name options cert_opt = ca_default # Certificate field options # Extension copying option: use with caution. # copy_extensions = copy # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs # so this is commented out by default to leave a V1 CRL. # crlnumber must also be commented out to leave a V1 CRL. # crl_extensions = crl_ext default_days = 365 # how long to certify for default_crl_days= 30 # how long before next CRL default_md = sha1 # which md to use. preserve = no # keep passed DN ordering # A few difference way of specifying how similar the request should look # For type CA, the listed attributes must be the same, and the optional # and supplied fields are just that :-) policy = policy_match # For the CA policy [ policy_match ] countryName = match stateOrProvinceName = match organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional # For the 'anything' policy # At this point in time, you must list all acceptable 'object' # types. [ policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional #################################################################### [ req ] default_bits = 1024 default_keyfile = privkey.pem distinguished_name = req_distinguished_name attributes = req_attributes x509_extensions = v3_ca # The extensions to add to the self signed cert # Passwords for private keys if not present they will be prompted for # input_password = secret # output_password = secret # This sets a mask for permitted string types. There are several options. # default: PrintableString, T61String, BMPString. # pkix : PrintableString, BMPString. # utf8only: only UTF8Strings. # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). # MASK:XXXX a literal mask value. # WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings # so use this option with caution! string_mask = nombstr # req_extensions = v3_req # The extensions to add to a certificate request [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = BR countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Some-State stateOrProvinceName_default = Espirito Santo localityName = Locality Name (eg, city) localityName_default = Santo Antonio do Canaa 0.organizationName = Organization Name (eg, company) 0.organizationName_default = Sao Tonico Ltda # we can do this but it is not needed normally :-) #1.organizationName = Second Organization Name (eg, company) #1.organizationName_default = World Wide Web Pty Ltd organizationalUnitName = Organizational Unit Name (eg, section) organizationalUnitName_default = Department of Computer Science commonName = Common Name (eg, YOUR name) commonName_default = Client B commonName_max = 64 emailAddress = Email Address emailAddress_max = 64 # SET-ex3 = SET extension number 3 [ req_attributes ] challengePassword = A challenge password challengePassword_min = 4 challengePassword_max = 20 unstructuredName = An optional company name [ usr_cert ] # These extensions are added when 'ca' signs a request. # This goes against PKIX guidelines but some CAs do it and some software # requires this to avoid interpreting an end user certificate as a CA. basicConstraints=CA:FALSE # Here are some examples of the usage of nsCertType. If it is omitted # the certificate can be used for anything *except* object signing. # This is OK for an SSL server. # nsCertType = server # For an object signing certificate this would be used. # nsCertType = objsign # For normal client use this is typical # nsCertType = client, email # and for everything including object signing: # nsCertType = client, email, objsign # This is typical in keyUsage for a client certificate. # keyUsage = nonRepudiation, digitalSignature, keyEncipherment # This will be displayed in Netscape's comment listbox. nsComment = "OpenSSL Generated Certificate" # PKIX recommendations harmless if included in all certificates. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer # This stuff is for subjectAltName and issuerAltname. # Import the email address. # subjectAltName=email:copy # An alternative to produce certificates that aren't # deprecated according to PKIX. # subjectAltName=email:move # Copy subject details # issuerAltName=issuer:copy #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem #nsBaseUrl #nsRevocationUrl #nsRenewalUrl #nsCaPolicyUrl #nsSslServerName [ v3_req ] # Extensions to add to a certificate request basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment [ v3_ca ] # Extensions for a typical CA # PKIX recommendation. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid:always,issuer:always # This is what PKIX recommends but some broken software chokes on critical # extensions. #basicConstraints = critical,CA:true # So we do this instead. basicConstraints = CA:true # Key usage: this is typical for a CA certificate. However since it will # prevent it being used as an test self-signed certificate it is best # left out by default. # keyUsage = cRLSign, keyCertSign # Some might want this also # nsCertType = sslCA, emailCA # Include email address in subject alt name: another PKIX recommendation # subjectAltName=email:copy # Copy issuer details # issuerAltName=issuer:copy # DER hex encoding of an extension: beware experts only! # obj=DER:02:03 # Where 'obj' is a standard or added object # You can even override a supported extension: # basicConstraints= critical, DER:30:03:01:01:FF [ crl_ext ] # CRL extensions. # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. # issuerAltName=issuer:copy authorityKeyIdentifier=keyid:always,issuer:always [ proxy_cert_ext ] # These extensions should be added when creating a proxy certificate # This goes against PKIX guidelines but some CAs do it and some software # requires this to avoid interpreting an end user certificate as a CA. basicConstraints=CA:FALSE # Here are some examples of the usage of nsCertType. If it is omitted # the certificate can be used for anything *except* object signing. # This is OK for an SSL server. # nsCertType = server # For an object signing certificate this would be used. # nsCertType = objsign # For normal client use this is typical # nsCertType = client, email # and for everything including object signing: # nsCertType = client, email, objsign # This is typical in keyUsage for a client certificate. # keyUsage = nonRepudiation, digitalSignature, keyEncipherment # This will be displayed in Netscape's comment listbox. nsComment = "OpenSSL Generated Certificate" # PKIX recommendations harmless if included in all certificates. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer:always # This stuff is for subjectAltName and issuerAltname. # Import the email address. # subjectAltName=email:copy # An alternative to produce certificates that aren't # deprecated according to PKIX. # subjectAltName=email:move # Copy subject details # issuerAltName=issuer:copy #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem #nsBaseUrl #nsRevocationUrl #nsRenewalUrl #nsCaPolicyUrl #nsSslServerName # This really needs to be in place for it to be a proxy certificate. proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo copas-4.8.0/tests/certs/clientB.sh000077500000000000000000000006431476464242400170510ustar00rootroot00000000000000#!/bin/sh openssl req -newkey rsa:2048 -sha256 -keyout clientBkey.pem -out clientBreq.pem \ -nodes -config ./clientB.cnf -days 365 -batch 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 cat clientBcert.pem rootB.pem > clientB.pem openssl x509 -subject -issuer -noout -in clientB.pem copas-4.8.0/tests/certs/rootA.bat000066400000000000000000000004751476464242400167110ustar00rootroot00000000000000REM #!/bin/sh openssl req -newkey rsa:2048 -sha256 -keyout rootAkey.pem -out rootAreq.pem -nodes -config ./rootA.cnf -days 365 -batch openssl x509 -req -in rootAreq.pem -sha256 -extfile ./rootA.cnf -extensions v3_ca -signkey rootAkey.pem -out rootA.pem -days 365 openssl x509 -subject -issuer -noout -in rootA.pem copas-4.8.0/tests/certs/rootA.cnf000066400000000000000000000224061476464242400167070ustar00rootroot00000000000000# # OpenSSL example configuration file. # This is mostly being used for generation of certificate requests. # # This definition stops the following lines choking if HOME isn't # defined. HOME = . RANDFILE = $ENV::HOME/.rnd # Extra OBJECT IDENTIFIER info: #oid_file = $ENV::HOME/.oid oid_section = new_oids # To use this configuration file with the "-extfile" option of the # "openssl x509" utility, name here the section containing the # X.509v3 extensions to use: # extensions = # (Alternatively, use a configuration file that has only # X.509v3 extensions in its main [= default] section.) [ new_oids ] # We can add new OIDs in here for use by 'ca' and 'req'. # Add a simple OID like this: # testoid1=1.2.3.4 # Or use config file substitution like this: # testoid2=${testoid1}.5.6 #################################################################### [ ca ] default_ca = CA_default # The default ca section #################################################################### [ CA_default ] dir = ./demoCA # Where everything is kept certs = $dir/certs # Where the issued certs are kept crl_dir = $dir/crl # Where the issued crl are kept database = $dir/index.txt # database index file. #unique_subject = no # Set to 'no' to allow creation of # several ctificates with same subject. new_certs_dir = $dir/newcerts # default place for new certs. certificate = $dir/cacert.pem # The CA certificate serial = $dir/serial # The current serial number crlnumber = $dir/crlnumber # the current crl number # must be commented out to leave a V1 CRL crl = $dir/crl.pem # The current CRL private_key = $dir/private/cakey.pem # The private key RANDFILE = $dir/private/.rand # private random number file x509_extensions = usr_cert # The extensions to add to the cert # Comment out the following two lines for the "traditional" # (and highly broken) format. name_opt = ca_default # Subject Name options cert_opt = ca_default # Certificate field options # Extension copying option: use with caution. # copy_extensions = copy # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs # so this is commented out by default to leave a V1 CRL. # crlnumber must also be commented out to leave a V1 CRL. # crl_extensions = crl_ext default_days = 365 # how long to certify for default_crl_days= 30 # how long before next CRL default_md = sha1 # which md to use. preserve = no # keep passed DN ordering # A few difference way of specifying how similar the request should look # For type CA, the listed attributes must be the same, and the optional # and supplied fields are just that :-) policy = policy_match # For the CA policy [ policy_match ] countryName = match stateOrProvinceName = match organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional # For the 'anything' policy # At this point in time, you must list all acceptable 'object' # types. [ policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional #################################################################### [ req ] default_bits = 1024 default_keyfile = privkey.pem distinguished_name = req_distinguished_name attributes = req_attributes x509_extensions = v3_ca # The extensions to add to the self signed cert # Passwords for private keys if not present they will be prompted for # input_password = secret # output_password = secret # This sets a mask for permitted string types. There are several options. # default: PrintableString, T61String, BMPString. # pkix : PrintableString, BMPString. # utf8only: only UTF8Strings. # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). # MASK:XXXX a literal mask value. # WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings # so use this option with caution! string_mask = nombstr # req_extensions = v3_req # The extensions to add to a certificate request [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = BR countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Espirito Santo localityName = Locality Name (eg, city) localityName_default = Santo Antonio do Canaa 0.organizationName = Organization Name (eg, company) 0.organizationName_default = Santo Tonico Ltda # we can do this but it is not needed normally :-) #1.organizationName = Second Organization Name (eg, company) #1.organizationName_default = World Wide Web Pty Ltd organizationalUnitName = Organizational Unit Name (eg, section) organizationalUnitName_default = Department of Computer Science commonName = Common Name (eg, YOUR name) commonName_max = 64 commonName_default = Root A emailAddress = Email Address emailAddress_max = 64 # SET-ex3 = SET extension number 3 [ req_attributes ] challengePassword = A challenge password challengePassword_min = 4 challengePassword_max = 20 unstructuredName = An optional company name [ usr_cert ] # These extensions are added when 'ca' signs a request. # This goes against PKIX guidelines but some CAs do it and some software # requires this to avoid interpreting an end user certificate as a CA. basicConstraints=CA:FALSE # Here are some examples of the usage of nsCertType. If it is omitted # the certificate can be used for anything *except* object signing. # This is OK for an SSL server. # nsCertType = server # For an object signing certificate this would be used. # nsCertType = objsign # For normal client use this is typical # nsCertType = client, email # and for everything including object signing: # nsCertType = client, email, objsign # This is typical in keyUsage for a client certificate. # keyUsage = nonRepudiation, digitalSignature, keyEncipherment # This will be displayed in Netscape's comment listbox. nsComment = "OpenSSL Generated Certificate" # PKIX recommendations harmless if included in all certificates. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer # This stuff is for subjectAltName and issuerAltname. # Import the email address. # subjectAltName=email:copy # An alternative to produce certificates that aren't # deprecated according to PKIX. # subjectAltName=email:move # Copy subject details # issuerAltName=issuer:copy #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem #nsBaseUrl #nsRevocationUrl #nsRenewalUrl #nsCaPolicyUrl #nsSslServerName [ v3_req ] # Extensions to add to a certificate request basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment [ v3_ca ] # Extensions for a typical CA # PKIX recommendation. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid:always,issuer:always # This is what PKIX recommends but some broken software chokes on critical # extensions. #basicConstraints = critical,CA:true # So we do this instead. basicConstraints = CA:true # Key usage: this is typical for a CA certificate. However since it will # prevent it being used as an test self-signed certificate it is best # left out by default. # keyUsage = cRLSign, keyCertSign # Some might want this also # nsCertType = sslCA, emailCA # Include email address in subject alt name: another PKIX recommendation # subjectAltName=email:copy # Copy issuer details # issuerAltName=issuer:copy # DER hex encoding of an extension: beware experts only! # obj=DER:02:03 # Where 'obj' is a standard or added object # You can even override a supported extension: # basicConstraints= critical, DER:30:03:01:01:FF [ crl_ext ] # CRL extensions. # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. # issuerAltName=issuer:copy authorityKeyIdentifier=keyid:always,issuer:always [ proxy_cert_ext ] # These extensions should be added when creating a proxy certificate # This goes against PKIX guidelines but some CAs do it and some software # requires this to avoid interpreting an end user certificate as a CA. basicConstraints=CA:FALSE # Here are some examples of the usage of nsCertType. If it is omitted # the certificate can be used for anything *except* object signing. # This is OK for an SSL server. # nsCertType = server # For an object signing certificate this would be used. # nsCertType = objsign # For normal client use this is typical # nsCertType = client, email # and for everything including object signing: # nsCertType = client, email, objsign # This is typical in keyUsage for a client certificate. # keyUsage = nonRepudiation, digitalSignature, keyEncipherment # This will be displayed in Netscape's comment listbox. nsComment = "OpenSSL Generated Certificate" # PKIX recommendations harmless if included in all certificates. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer:always # This stuff is for subjectAltName and issuerAltname. # Import the email address. # subjectAltName=email:copy # An alternative to produce certificates that aren't # deprecated according to PKIX. # subjectAltName=email:move # Copy subject details # issuerAltName=issuer:copy #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem #nsBaseUrl #nsRevocationUrl #nsRenewalUrl #nsCaPolicyUrl #nsSslServerName # This really needs to be in place for it to be a proxy certificate. proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo copas-4.8.0/tests/certs/rootA.sh000077500000000000000000000004701476464242400165530ustar00rootroot00000000000000#!/bin/sh openssl req -newkey rsa:2048 -sha256 -keyout rootAkey.pem -out rootAreq.pem -nodes -config ./rootA.cnf -days 365 -batch openssl x509 -req -in rootAreq.pem -sha256 -extfile ./rootA.cnf -extensions v3_ca -signkey rootAkey.pem -out rootA.pem -days 365 openssl x509 -subject -issuer -noout -in rootA.pem copas-4.8.0/tests/certs/rootB.bat000066400000000000000000000004751476464242400167120ustar00rootroot00000000000000rem #!/bin/sh openssl req -newkey rsa:2048 -sha256 -keyout rootBkey.pem -out rootBreq.pem -nodes -config ./rootB.cnf -days 365 -batch openssl x509 -req -in rootBreq.pem -sha256 -extfile ./rootB.cnf -extensions v3_ca -signkey rootBkey.pem -out rootB.pem -days 365 openssl x509 -subject -issuer -noout -in rootB.pem copas-4.8.0/tests/certs/rootB.cnf000066400000000000000000000224041476464242400167060ustar00rootroot00000000000000# # OpenSSL example configuration file. # This is mostly being used for generation of certificate requests. # # This definition stops the following lines choking if HOME isn't # defined. HOME = . RANDFILE = $ENV::HOME/.rnd # Extra OBJECT IDENTIFIER info: #oid_file = $ENV::HOME/.oid oid_section = new_oids # To use this configuration file with the "-extfile" option of the # "openssl x509" utility, name here the section containing the # X.509v3 extensions to use: # extensions = # (Alternatively, use a configuration file that has only # X.509v3 extensions in its main [= default] section.) [ new_oids ] # We can add new OIDs in here for use by 'ca' and 'req'. # Add a simple OID like this: # testoid1=1.2.3.4 # Or use config file substitution like this: # testoid2=${testoid1}.5.6 #################################################################### [ ca ] default_ca = CA_default # The default ca section #################################################################### [ CA_default ] dir = ./demoCA # Where everything is kept certs = $dir/certs # Where the issued certs are kept crl_dir = $dir/crl # Where the issued crl are kept database = $dir/index.txt # database index file. #unique_subject = no # Set to 'no' to allow creation of # several ctificates with same subject. new_certs_dir = $dir/newcerts # default place for new certs. certificate = $dir/cacert.pem # The CA certificate serial = $dir/serial # The current serial number crlnumber = $dir/crlnumber # the current crl number # must be commented out to leave a V1 CRL crl = $dir/crl.pem # The current CRL private_key = $dir/private/cakey.pem # The private key RANDFILE = $dir/private/.rand # private random number file x509_extensions = usr_cert # The extensions to add to the cert # Comment out the following two lines for the "traditional" # (and highly broken) format. name_opt = ca_default # Subject Name options cert_opt = ca_default # Certificate field options # Extension copying option: use with caution. # copy_extensions = copy # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs # so this is commented out by default to leave a V1 CRL. # crlnumber must also be commented out to leave a V1 CRL. # crl_extensions = crl_ext default_days = 365 # how long to certify for default_crl_days= 30 # how long before next CRL default_md = sha1 # which md to use. preserve = no # keep passed DN ordering # A few difference way of specifying how similar the request should look # For type CA, the listed attributes must be the same, and the optional # and supplied fields are just that :-) policy = policy_match # For the CA policy [ policy_match ] countryName = match stateOrProvinceName = match organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional # For the 'anything' policy # At this point in time, you must list all acceptable 'object' # types. [ policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional #################################################################### [ req ] default_bits = 1024 default_keyfile = privkey.pem distinguished_name = req_distinguished_name attributes = req_attributes x509_extensions = v3_ca # The extensions to add to the self signed cert # Passwords for private keys if not present they will be prompted for # input_password = secret # output_password = secret # This sets a mask for permitted string types. There are several options. # default: PrintableString, T61String, BMPString. # pkix : PrintableString, BMPString. # utf8only: only UTF8Strings. # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). # MASK:XXXX a literal mask value. # WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings # so use this option with caution! string_mask = nombstr # req_extensions = v3_req # The extensions to add to a certificate request [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = BR countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Espirito Santo localityName = Locality Name (eg, city) localityName_default = Santo Antonio do Canaa 0.organizationName = Organization Name (eg, company) 0.organizationName_default = Sao Tonico Ltda # we can do this but it is not needed normally :-) #1.organizationName = Second Organization Name (eg, company) #1.organizationName_default = World Wide Web Pty Ltd organizationalUnitName = Organizational Unit Name (eg, section) organizationalUnitName_default = Department of Computer Science commonName = Common Name (eg, YOUR name) commonName_default = Root B commonName_max = 64 emailAddress = Email Address emailAddress_max = 64 # SET-ex3 = SET extension number 3 [ req_attributes ] challengePassword = A challenge password challengePassword_min = 4 challengePassword_max = 20 unstructuredName = An optional company name [ usr_cert ] # These extensions are added when 'ca' signs a request. # This goes against PKIX guidelines but some CAs do it and some software # requires this to avoid interpreting an end user certificate as a CA. basicConstraints=CA:FALSE # Here are some examples of the usage of nsCertType. If it is omitted # the certificate can be used for anything *except* object signing. # This is OK for an SSL server. # nsCertType = server # For an object signing certificate this would be used. # nsCertType = objsign # For normal client use this is typical # nsCertType = client, email # and for everything including object signing: # nsCertType = client, email, objsign # This is typical in keyUsage for a client certificate. # keyUsage = nonRepudiation, digitalSignature, keyEncipherment # This will be displayed in Netscape's comment listbox. nsComment = "OpenSSL Generated Certificate" # PKIX recommendations harmless if included in all certificates. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer # This stuff is for subjectAltName and issuerAltname. # Import the email address. # subjectAltName=email:copy # An alternative to produce certificates that aren't # deprecated according to PKIX. # subjectAltName=email:move # Copy subject details # issuerAltName=issuer:copy #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem #nsBaseUrl #nsRevocationUrl #nsRenewalUrl #nsCaPolicyUrl #nsSslServerName [ v3_req ] # Extensions to add to a certificate request basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment [ v3_ca ] # Extensions for a typical CA # PKIX recommendation. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid:always,issuer:always # This is what PKIX recommends but some broken software chokes on critical # extensions. #basicConstraints = critical,CA:true # So we do this instead. basicConstraints = CA:true # Key usage: this is typical for a CA certificate. However since it will # prevent it being used as an test self-signed certificate it is best # left out by default. # keyUsage = cRLSign, keyCertSign # Some might want this also # nsCertType = sslCA, emailCA # Include email address in subject alt name: another PKIX recommendation # subjectAltName=email:copy # Copy issuer details # issuerAltName=issuer:copy # DER hex encoding of an extension: beware experts only! # obj=DER:02:03 # Where 'obj' is a standard or added object # You can even override a supported extension: # basicConstraints= critical, DER:30:03:01:01:FF [ crl_ext ] # CRL extensions. # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. # issuerAltName=issuer:copy authorityKeyIdentifier=keyid:always,issuer:always [ proxy_cert_ext ] # These extensions should be added when creating a proxy certificate # This goes against PKIX guidelines but some CAs do it and some software # requires this to avoid interpreting an end user certificate as a CA. basicConstraints=CA:FALSE # Here are some examples of the usage of nsCertType. If it is omitted # the certificate can be used for anything *except* object signing. # This is OK for an SSL server. # nsCertType = server # For an object signing certificate this would be used. # nsCertType = objsign # For normal client use this is typical # nsCertType = client, email # and for everything including object signing: # nsCertType = client, email, objsign # This is typical in keyUsage for a client certificate. # keyUsage = nonRepudiation, digitalSignature, keyEncipherment # This will be displayed in Netscape's comment listbox. nsComment = "OpenSSL Generated Certificate" # PKIX recommendations harmless if included in all certificates. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer:always # This stuff is for subjectAltName and issuerAltname. # Import the email address. # subjectAltName=email:copy # An alternative to produce certificates that aren't # deprecated according to PKIX. # subjectAltName=email:move # Copy subject details # issuerAltName=issuer:copy #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem #nsBaseUrl #nsRevocationUrl #nsRenewalUrl #nsCaPolicyUrl #nsSslServerName # This really needs to be in place for it to be a proxy certificate. proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo copas-4.8.0/tests/certs/rootB.sh000077500000000000000000000004711476464242400165550ustar00rootroot00000000000000#!/bin/sh openssl req -newkey rsa:2048 -sha256 -keyout rootBkey.pem -out rootBreq.pem -nodes -config ./rootB.cnf -days 365 -batch openssl x509 -req -in rootBreq.pem -sha256 -extfile ./rootB.cnf -extensions v3_ca -signkey rootBkey.pem -out rootB.pem -days 365 openssl x509 -subject -issuer -noout -in rootB.pem copas-4.8.0/tests/certs/serverA.bat000066400000000000000000000006241476464242400172300ustar00rootroot00000000000000rem #!/bin/sh openssl req -newkey rsa:2048 -keyout serverAkey.pem -out serverAreq.pem -config ./serverA.cnf -nodes -days 365 -batch 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 copy serverAcert.pem + rootA.pem serverA.pem openssl x509 -subject -issuer -noout -in serverA.pem copas-4.8.0/tests/certs/serverA.cnf000066400000000000000000000224551476464242400172360ustar00rootroot00000000000000# # OpenSSL example configuration file. # This is mostly being used for generation of certificate requests. # # This definition stops the following lines choking if HOME isn't # defined. HOME = . RANDFILE = $ENV::HOME/.rnd # Extra OBJECT IDENTIFIER info: #oid_file = $ENV::HOME/.oid oid_section = new_oids # To use this configuration file with the "-extfile" option of the # "openssl x509" utility, name here the section containing the # X.509v3 extensions to use: # extensions = # (Alternatively, use a configuration file that has only # X.509v3 extensions in its main [= default] section.) [ new_oids ] # We can add new OIDs in here for use by 'ca' and 'req'. # Add a simple OID like this: # testoid1=1.2.3.4 # Or use config file substitution like this: # testoid2=${testoid1}.5.6 #################################################################### [ ca ] default_ca = CA_default # The default ca section #################################################################### [ CA_default ] dir = ./demoCA # Where everything is kept certs = $dir/certs # Where the issued certs are kept crl_dir = $dir/crl # Where the issued crl are kept database = $dir/index.txt # database index file. #unique_subject = no # Set to 'no' to allow creation of # several ctificates with same subject. new_certs_dir = $dir/newcerts # default place for new certs. certificate = $dir/cacert.pem # The CA certificate serial = $dir/serial # The current serial number crlnumber = $dir/crlnumber # the current crl number # must be commented out to leave a V1 CRL crl = $dir/crl.pem # The current CRL private_key = $dir/private/cakey.pem # The private key RANDFILE = $dir/private/.rand # private random number file x509_extensions = usr_cert # The extensions to add to the cert # Comment out the following two lines for the "traditional" # (and highly broken) format. name_opt = ca_default # Subject Name options cert_opt = ca_default # Certificate field options # Extension copying option: use with caution. # copy_extensions = copy # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs # so this is commented out by default to leave a V1 CRL. # crlnumber must also be commented out to leave a V1 CRL. # crl_extensions = crl_ext default_days = 365 # how long to certify for default_crl_days= 30 # how long before next CRL default_md = sha1 # which md to use. preserve = no # keep passed DN ordering # A few difference way of specifying how similar the request should look # For type CA, the listed attributes must be the same, and the optional # and supplied fields are just that :-) policy = policy_match # For the CA policy [ policy_match ] countryName = match stateOrProvinceName = match organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional # For the 'anything' policy # At this point in time, you must list all acceptable 'object' # types. [ policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional #################################################################### [ req ] default_bits = 1024 default_keyfile = privkey.pem distinguished_name = req_distinguished_name attributes = req_attributes x509_extensions = v3_ca # The extensions to add to the self signed cert # Passwords for private keys if not present they will be prompted for # input_password = secret # output_password = secret # This sets a mask for permitted string types. There are several options. # default: PrintableString, T61String, BMPString. # pkix : PrintableString, BMPString. # utf8only: only UTF8Strings. # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). # MASK:XXXX a literal mask value. # WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings # so use this option with caution! string_mask = nombstr # req_extensions = v3_req # The extensions to add to a certificate request [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = BR countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Some-State stateOrProvinceName_default = Espirito Santo localityName = Locality Name (eg, city) localityName_default = Santo Antonio do Canaa 0.organizationName = Organization Name (eg, company) 0.organizationName_default = Sao Tonico Ltda # we can do this but it is not needed normally :-) #1.organizationName = Second Organization Name (eg, company) #1.organizationName_default = World Wide Web Pty Ltd organizationalUnitName = Organizational Unit Name (eg, section) organizationalUnitName_default = Department of Computer Science commonName = Common Name (eg, YOUR name) commonName_default = Server A commonName_max = 64 emailAddress = Email Address emailAddress_max = 64 # SET-ex3 = SET extension number 3 [ req_attributes ] challengePassword = A challenge password challengePassword_min = 4 challengePassword_max = 20 unstructuredName = An optional company name [ usr_cert ] # These extensions are added when 'ca' signs a request. # This goes against PKIX guidelines but some CAs do it and some software # requires this to avoid interpreting an end user certificate as a CA. basicConstraints=CA:FALSE # Here are some examples of the usage of nsCertType. If it is omitted # the certificate can be used for anything *except* object signing. # This is OK for an SSL server. nsCertType = server # For an object signing certificate this would be used. # nsCertType = objsign # For normal client use this is typical # nsCertType = client, email # and for everything including object signing: # nsCertType = client, email, objsign # This is typical in keyUsage for a client certificate. # keyUsage = nonRepudiation, digitalSignature, keyEncipherment # This will be displayed in Netscape's comment listbox. nsComment = "OpenSSL Generated Certificate" # PKIX recommendations harmless if included in all certificates. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer # This stuff is for subjectAltName and issuerAltname. # Import the email address. # subjectAltName=email:copy # An alternative to produce certificates that aren't # deprecated according to PKIX. # subjectAltName=email:move # Copy subject details # issuerAltName=issuer:copy #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem #nsBaseUrl #nsRevocationUrl #nsRenewalUrl #nsCaPolicyUrl #nsSslServerName [ v3_req ] # Extensions to add to a certificate request basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment [ v3_ca ] # Extensions for a typical CA # PKIX recommendation. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid:always,issuer:always # This is what PKIX recommends but some broken software chokes on critical # extensions. #basicConstraints = critical,CA:true # So we do this instead. basicConstraints = CA:true # Key usage: this is typical for a CA certificate. However since it will # prevent it being used as an test self-signed certificate it is best # left out by default. # keyUsage = cRLSign, keyCertSign # Some might want this also # nsCertType = sslCA, emailCA # Include email address in subject alt name: another PKIX recommendation # subjectAltName=email:copy # Copy issuer details # issuerAltName=issuer:copy # DER hex encoding of an extension: beware experts only! # obj=DER:02:03 # Where 'obj' is a standard or added object # You can even override a supported extension: # basicConstraints= critical, DER:30:03:01:01:FF [ crl_ext ] # CRL extensions. # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. # issuerAltName=issuer:copy authorityKeyIdentifier=keyid:always,issuer:always [ proxy_cert_ext ] # These extensions should be added when creating a proxy certificate # This goes against PKIX guidelines but some CAs do it and some software # requires this to avoid interpreting an end user certificate as a CA. basicConstraints=CA:FALSE # Here are some examples of the usage of nsCertType. If it is omitted # the certificate can be used for anything *except* object signing. # This is OK for an SSL server. # nsCertType = server # For an object signing certificate this would be used. # nsCertType = objsign # For normal client use this is typical # nsCertType = client, email # and for everything including object signing: # nsCertType = client, email, objsign # This is typical in keyUsage for a client certificate. # keyUsage = nonRepudiation, digitalSignature, keyEncipherment # This will be displayed in Netscape's comment listbox. nsComment = "OpenSSL Generated Certificate" # PKIX recommendations harmless if included in all certificates. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer:always # This stuff is for subjectAltName and issuerAltname. # Import the email address. # subjectAltName=email:copy # An alternative to produce certificates that aren't # deprecated according to PKIX. # subjectAltName=email:move # Copy subject details # issuerAltName=issuer:copy #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem #nsBaseUrl #nsRevocationUrl #nsRenewalUrl #nsCaPolicyUrl #nsSslServerName # This really needs to be in place for it to be a proxy certificate. proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo copas-4.8.0/tests/certs/serverA.sh000077500000000000000000000006361476464242400171020ustar00rootroot00000000000000#!/bin/sh openssl req -newkey rsa:2048 -keyout serverAkey.pem -out serverAreq.pem \ -config ./serverA.cnf -nodes -days 365 -batch 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 cat serverAcert.pem rootA.pem > serverA.pem openssl x509 -subject -issuer -noout -in serverA.pem copas-4.8.0/tests/certs/serverB.bat000066400000000000000000000006241476464242400172310ustar00rootroot00000000000000rem #!/bin/sh openssl req -newkey rsa:2048 -keyout serverBkey.pem -out serverBreq.pem -config ./serverB.cnf -nodes -days 365 -batch 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 copy serverBcert.pem + rootB.pem serverB.pem openssl x509 -subject -issuer -noout -in serverB.pem copas-4.8.0/tests/certs/serverB.cnf000066400000000000000000000224551476464242400172370ustar00rootroot00000000000000# # OpenSSL example configuration file. # This is mostly being used for generation of certificate requests. # # This definition stops the following lines choking if HOME isn't # defined. HOME = . RANDFILE = $ENV::HOME/.rnd # Extra OBJECT IDENTIFIER info: #oid_file = $ENV::HOME/.oid oid_section = new_oids # To use this configuration file with the "-extfile" option of the # "openssl x509" utility, name here the section containing the # X.509v3 extensions to use: # extensions = # (Alternatively, use a configuration file that has only # X.509v3 extensions in its main [= default] section.) [ new_oids ] # We can add new OIDs in here for use by 'ca' and 'req'. # Add a simple OID like this: # testoid1=1.2.3.4 # Or use config file substitution like this: # testoid2=${testoid1}.5.6 #################################################################### [ ca ] default_ca = CA_default # The default ca section #################################################################### [ CA_default ] dir = ./demoCA # Where everything is kept certs = $dir/certs # Where the issued certs are kept crl_dir = $dir/crl # Where the issued crl are kept database = $dir/index.txt # database index file. #unique_subject = no # Set to 'no' to allow creation of # several ctificates with same subject. new_certs_dir = $dir/newcerts # default place for new certs. certificate = $dir/cacert.pem # The CA certificate serial = $dir/serial # The current serial number crlnumber = $dir/crlnumber # the current crl number # must be commented out to leave a V1 CRL crl = $dir/crl.pem # The current CRL private_key = $dir/private/cakey.pem # The private key RANDFILE = $dir/private/.rand # private random number file x509_extensions = usr_cert # The extensions to add to the cert # Comment out the following two lines for the "traditional" # (and highly broken) format. name_opt = ca_default # Subject Name options cert_opt = ca_default # Certificate field options # Extension copying option: use with caution. # copy_extensions = copy # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs # so this is commented out by default to leave a V1 CRL. # crlnumber must also be commented out to leave a V1 CRL. # crl_extensions = crl_ext default_days = 365 # how long to certify for default_crl_days= 30 # how long before next CRL default_md = sha1 # which md to use. preserve = no # keep passed DN ordering # A few difference way of specifying how similar the request should look # For type CA, the listed attributes must be the same, and the optional # and supplied fields are just that :-) policy = policy_match # For the CA policy [ policy_match ] countryName = match stateOrProvinceName = match organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional # For the 'anything' policy # At this point in time, you must list all acceptable 'object' # types. [ policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional #################################################################### [ req ] default_bits = 1024 default_keyfile = privkey.pem distinguished_name = req_distinguished_name attributes = req_attributes x509_extensions = v3_ca # The extensions to add to the self signed cert # Passwords for private keys if not present they will be prompted for # input_password = secret # output_password = secret # This sets a mask for permitted string types. There are several options. # default: PrintableString, T61String, BMPString. # pkix : PrintableString, BMPString. # utf8only: only UTF8Strings. # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). # MASK:XXXX a literal mask value. # WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings # so use this option with caution! string_mask = nombstr # req_extensions = v3_req # The extensions to add to a certificate request [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = BR countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Some-State stateOrProvinceName_default = Espirito Santo localityName = Locality Name (eg, city) localityName_default = Santo Antonio do Canaa 0.organizationName = Organization Name (eg, company) 0.organizationName_default = Sao Tonico Ltda # we can do this but it is not needed normally :-) #1.organizationName = Second Organization Name (eg, company) #1.organizationName_default = World Wide Web Pty Ltd organizationalUnitName = Organizational Unit Name (eg, section) organizationalUnitName_default = Department of Computer Science commonName = Common Name (eg, YOUR name) commonName_default = Server B commonName_max = 64 emailAddress = Email Address emailAddress_max = 64 # SET-ex3 = SET extension number 3 [ req_attributes ] challengePassword = A challenge password challengePassword_min = 4 challengePassword_max = 20 unstructuredName = An optional company name [ usr_cert ] # These extensions are added when 'ca' signs a request. # This goes against PKIX guidelines but some CAs do it and some software # requires this to avoid interpreting an end user certificate as a CA. basicConstraints=CA:FALSE # Here are some examples of the usage of nsCertType. If it is omitted # the certificate can be used for anything *except* object signing. # This is OK for an SSL server. nsCertType = server # For an object signing certificate this would be used. # nsCertType = objsign # For normal client use this is typical # nsCertType = client, email # and for everything including object signing: # nsCertType = client, email, objsign # This is typical in keyUsage for a client certificate. # keyUsage = nonRepudiation, digitalSignature, keyEncipherment # This will be displayed in Netscape's comment listbox. nsComment = "OpenSSL Generated Certificate" # PKIX recommendations harmless if included in all certificates. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer # This stuff is for subjectAltName and issuerAltname. # Import the email address. # subjectAltName=email:copy # An alternative to produce certificates that aren't # deprecated according to PKIX. # subjectAltName=email:move # Copy subject details # issuerAltName=issuer:copy #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem #nsBaseUrl #nsRevocationUrl #nsRenewalUrl #nsCaPolicyUrl #nsSslServerName [ v3_req ] # Extensions to add to a certificate request basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment [ v3_ca ] # Extensions for a typical CA # PKIX recommendation. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid:always,issuer:always # This is what PKIX recommends but some broken software chokes on critical # extensions. #basicConstraints = critical,CA:true # So we do this instead. basicConstraints = CA:true # Key usage: this is typical for a CA certificate. However since it will # prevent it being used as an test self-signed certificate it is best # left out by default. # keyUsage = cRLSign, keyCertSign # Some might want this also # nsCertType = sslCA, emailCA # Include email address in subject alt name: another PKIX recommendation # subjectAltName=email:copy # Copy issuer details # issuerAltName=issuer:copy # DER hex encoding of an extension: beware experts only! # obj=DER:02:03 # Where 'obj' is a standard or added object # You can even override a supported extension: # basicConstraints= critical, DER:30:03:01:01:FF [ crl_ext ] # CRL extensions. # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. # issuerAltName=issuer:copy authorityKeyIdentifier=keyid:always,issuer:always [ proxy_cert_ext ] # These extensions should be added when creating a proxy certificate # This goes against PKIX guidelines but some CAs do it and some software # requires this to avoid interpreting an end user certificate as a CA. basicConstraints=CA:FALSE # Here are some examples of the usage of nsCertType. If it is omitted # the certificate can be used for anything *except* object signing. # This is OK for an SSL server. # nsCertType = server # For an object signing certificate this would be used. # nsCertType = objsign # For normal client use this is typical # nsCertType = client, email # and for everything including object signing: # nsCertType = client, email, objsign # This is typical in keyUsage for a client certificate. # keyUsage = nonRepudiation, digitalSignature, keyEncipherment # This will be displayed in Netscape's comment listbox. nsComment = "OpenSSL Generated Certificate" # PKIX recommendations harmless if included in all certificates. subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer:always # This stuff is for subjectAltName and issuerAltname. # Import the email address. # subjectAltName=email:copy # An alternative to produce certificates that aren't # deprecated according to PKIX. # subjectAltName=email:move # Copy subject details # issuerAltName=issuer:copy #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem #nsBaseUrl #nsRevocationUrl #nsRenewalUrl #nsCaPolicyUrl #nsSslServerName # This really needs to be in place for it to be a proxy certificate. proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo copas-4.8.0/tests/certs/serverB.sh000077500000000000000000000006361476464242400171030ustar00rootroot00000000000000#!/bin/sh openssl req -newkey rsa:2048 -keyout serverBkey.pem -out serverBreq.pem \ -config ./serverB.cnf -nodes -days 365 -batch 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 cat serverBcert.pem rootB.pem > serverB.pem openssl x509 -subject -issuer -noout -in serverB.pem copas-4.8.0/tests/close.lua000066400000000000000000000043431476464242400156230ustar00rootroot00000000000000-- when a socket is being closed, while another coroutine -- is reading on it, the `select` call does not return an event on that -- socket. Hence the loop keeps running until the watch-dog kicks-in, reads -- on the socket, and that returns the final "closed" error to the reading -- coroutine. -- -- when a socket is closed, any read/write operation should return immediately -- with a "closed" error. local copas = require "copas" local socket = require "socket" copas.loop(function() local client_socket local close_time local send_end_time local receive_end_time print "------------- starting close test ---------------" local function check_exit() if receive_end_time and send_end_time then -- both set, so we're done print "success!" os.exit(0) end end do -- set up a server that accepts but doesn't read or write anything local server = socket.bind("localhost", 20000) copas.addserver(server, copas.handler(function(conn_skt) -- client connected, we're not doing anything, let the client -- wait in the read/write queues copas.pause(2) -- now we're closing the connecting_socket close_time = copas.gettime() print("closing client socket now, client receive and send operation should immediately error out now") client_socket:close() copas.pause(10) conn_skt:close() copas.removeserver(server) print "timeout, test failed" os.exit(1) end)) end do -- create a client that connect to the server client_socket = copas.wrap(socket.connect("localhost", 20000)) copas.addthread(function() local data, err = client_socket:receive(1) print("receive result: ", tostring(data), tostring(err)) receive_end_time = copas.gettime() print("receive took: ", receive_end_time - close_time) check_exit() end) copas.addthread(function() local ok, err = true, nil while ok do -- loop to fill any buffers, until we get stuck ok, err = client_socket:send(("hello world"):rep(100)) end print("send result: ", tostring(ok), tostring(err)) send_end_time = copas.gettime() print("send took: ", send_end_time - close_time) check_exit() end) end end) copas-4.8.0/tests/connecttwice.lua000066400000000000000000000014531476464242400172020ustar00rootroot00000000000000-- test reconnecting a socket, should return an error "already connected" -- test based on Windows behaviour, see comments in `copas.connect()` function local copas = require("copas") local socket = require("socket") local skt = copas.wrap(socket.tcp()) local done = false copas.addthread(function() print("First try... (should succeed)") local ok, err = skt:connect("google.com", 80) if ok then print("Success") else print("Failed: "..err) os.exit(1) end print("\nSecond try... (should error as already connected)") ok, err = skt:connect("thijsschreijer.nl", 80) if ok then print("Unexpected success") os.exit(1) else print("Failed: "..err) end done = true end) copas.loop() if not done then print("Loop completed with test not finished") os.exit(1) end copas-4.8.0/tests/errhandlers.lua000066400000000000000000000075451476464242400170360ustar00rootroot00000000000000-- Tests Copas socket timeouts -- -- Run the test file, it should exit successfully without hanging. -- make sure we are pointing to the local copas first package.path = string.format("../src/?.lua;%s", package.path) --local platform = "unix" --if package.config:sub(1,1) == "\\" then -- platform = "windows" --elseif io.popen("uname", "r"):read("*a"):find("Darwin") then -- platform = "mac" --end --print("Testing platform: " .. platform) local copas = require("copas") local tests = {} if _VERSION ~= "Lua 5.1" then -- do not run these for Lua 5.1 since it has a different stacktrace tests.default_properly_formats_coro_errors = function() local old_print = print local msg print = function(errmsg) --luacheck: ignore msg = errmsg --old_print(msg) end copas.loop(function() local f = function() error("hi there!") end f() end) print = old_print --luacheck: ignore assert(msg:find("errhandlers%.lua:%d-: hi there! %(coroutine: copas_initializer, socket: nil%)"), "got:\n"..msg) assert(msg:find("stack traceback:.+errhandlers%.lua"), "got:\n"..msg) end tests.default_properly_formats_timerwheel_errors = function() local old_print = print local msg print = function(errmsg) --luacheck: ignore msg = errmsg --old_print(msg) end copas.loop(function() copas.timeout(0.01, function(co) local f = function() error("hi there!") end f() end) copas.pause(1) end) print = old_print --luacheck: ignore assert(msg:find("errhandlers%.lua:%d-: hi there! %(coroutine: copas_core_timer, socket: nil%)"), "got:\n"..msg) assert(msg:find("stack traceback:.+errhandlers%.lua"), "got:\n"..msg) end end tests.yielding_from_user_code_fails = function() local old_print = print local msg print = function(errmsg) --luacheck: ignore msg = errmsg --old_print(msg) end copas.loop(function() copas.pause(1) coroutine.yield() -- directly yield to Copas end) print = old_print --luacheck: ignore assert(msg:find("coroutine.yield was called without a resume first, user-code cannot yield to Copas", 1, true), "got:\n"..msg) end tests.handler_gets_called_if_set = function() local call_count = 0 copas.loop(function() copas.setErrorHandler(function() call_count = call_count + 1 end) error("end of the world!") end) assert(call_count == 1, "expected callcount 1, got: " .. tostring(call_count)) end tests.default_handler_gets_called_if_set = function() local call_count = 0 copas.setErrorHandler(function() call_count = call_count + 10 end, true) copas.loop(function() error("end of the world!") end) assert(call_count == 10, "expected callcount 10, got: " .. tostring(call_count)) end tests.default_handler_doesnt_get_called_if_overridden = function() local call_count = 0 copas.setErrorHandler(function() call_count = call_count + 10 end, true) copas.loop(function() copas.setErrorHandler(function() call_count = call_count + 1 end) error("end of the world!") end) assert(call_count == 1, "expected callcount 1, got: " .. tostring(call_count)) end tests.timerwheel_callbacks_call_the_default_error_handler = function() local call_count = 0 copas.setErrorHandler(function() call_count = call_count - 10 end, true) copas.loop(function() copas.timeout(0.01, function(co) error("hi there!") end) copas.pause(1) end) assert(call_count == -10, "expected callcount -10, got: " .. tostring(call_count)) end -- test "framework" for name, test in pairs(tests) do -- reload copas, to clear default err handlers package.loaded.copas = nil copas = require "copas" print("testing: "..tostring(name)) local status, err = pcall(test) if not status then error(err) end end print("[✓] all tests completed successuly") copas-4.8.0/tests/exit.lua000066400000000000000000000023711476464242400154660ustar00rootroot00000000000000-- tests whether the main loop automatically exits when all work is done local copas = require("copas") local socket = require("socket") local done = false copas.addthread(function() for i = 1, 5 do copas.pause() print(i) end done = true end) print("Test 1 count to 5... then exit") copas.loop() if done then print("Test 1 done") else print("Loop completed with test 1 not finished") os.exit(1) end done = false local server = socket.bind("*", 20000) local message = "Hello world!" copas.addserver(server, function(skt) local received = copas.receive(skt) if received ~= message then print("Incorrect return from copas.receive: "..tostring(received)) os.exit(1) else print("Received "..message) end copas.removeserver(server) done = true end) copas.addthread(function() copas.pause(1) local skt = socket.connect("localhost", 20000) print("Sending "..message.."\\n") local bytes = copas.send(skt, message.."\n") if bytes ~= #message + 1 then print("Incorrect return from copas.send: "..tostring(bytes)) os.exit(1) end end) print("Test 2 send and receive some... then exit") copas.loop() if done then print("Test 2 done") else print("Loop completed with test 2 not finished") os.exit(1) end copas-4.8.0/tests/exittest.lua000066400000000000000000000064601476464242400163710ustar00rootroot00000000000000print([[ Testing to automatically exit the copas loop when nothing remains to be done. So none of the tests below should hang, as that means it did not exit... ============================================================================= ]]) local copas = require("copas") local testran print("1) Testing exiting when a task finishes before the loop even starts") copas.addthread(function() print("","1 running...") testran = 1 end) copas.loop() assert(testran == 1, "Test 1 was not executed!") print("1) success") print("2) Testing exiting when a task finishes within the loop") copas.addthread(function() copas.pause(0.1) -- wait until loop is running copas.pause(0.1) -- wait again to make sure its not the initial step in the loop print("","2 running...") testran = 2 end) copas.loop() assert(testran == 2, "Test 2 was not executed!") print("2) success") print("3) Testing exiting when a task fails before the loop even starts") copas.addthread(function() print("","3 running...") testran = 3 error("error on purpose") end) copas.loop() assert(testran == 3, "Test 3 was not executed!") print("3) success") print("4) Testing exiting when a task fails in the loop") copas.addthread(function() copas.pause(0.1) -- wait until loop is running copas.pause(0.1) -- wait again to make sure its not the initial step in the loop print("","4 running...") testran = 4 error("error on purpose") end) copas.loop() assert(testran == 4, "Test 4 was not executed!") print("4) success") print("5) Testing exiting when a task permanently sleeps before the loop") copas.addthread(function() print("","5 running...") testran = 5 copas.pauseforever() -- sleep until explicitly woken up end) copas.loop() assert(testran == 5, "Test 5 was not executed!") print("5) success") print("6) Testing exiting when a task permanently sleeps in the loop") copas.addthread(function() copas.pause(0.1) -- wait until loop is running copas.pause(0.1) -- wait again to make sure its not the initial step in the loop print("","6 running...") testran = 6 copas.pauseforever() -- sleep until explicitly woken up end) copas.loop() assert(testran == 6, "Test 6 was not executed!") print("6) success") print("7) Testing exiting releasing the exitsemaphore (implicit, no call to copas.exit)") copas.addthread(function() print("","7 running...") copas.addthread(function() copas.waitforexit() testran = 7 end) end) copas.loop() assert(testran == 7, "Test 7 was not executed!") print("7) success") print("8) Testing schduling new tasks while exiting (explicit exit by calling copas.exit)") testran = 0 copas.addthread(function() print("","8 running...") copas.addthread(function() while true do copas.pause(0.1) testran = testran + 1 print("count...") if testran == 3 then -- testran == 3 print("initiating exit...") copas.exit() break end end end) copas.addthread(function() copas.waitforexit() print("exit signal received...") testran = testran + 1 -- testran == 4 copas.addthread(function() print("running new task from exit handler...") copas.pause(1) testran = testran + 1 -- testran == 5 print("new task from exit handler done!") end) end) end) copas.loop() assert(testran == 5, "Test 8 was not executed!") print("8) success") copas-4.8.0/tests/http-timeout.lua000066400000000000000000000261211476464242400171570ustar00rootroot00000000000000-- tests timeouts with http requests. -- -- in sending/receiving headers/body -- local copas = require 'copas' local socket = require 'socket' local ltn12 = require 'ltn12' local request = copas.http.request -- copas.debug.start() local response_body = ("A"):rep(1023).."x" -- 1 kb string local response_headers = [[HTTP/1.1 200 OK Date: Mon, 27 Jul 2009 12:28:53 GMT Server: Apache/2.2.14 (Win32) Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT Content-Type: text/html Content-Length: ]] local response = response_headers .. tostring(#response_body) .. "\n\n" .. response_body response = response:gsub("\13?\10", "\13\10") -- update lineendings local request_headers = { Header1 = "header value 1 which is really a dummy value", Header2 = "header value 2 which is really a dummy value", Header3 = "header value 3 which is really a dummy value", Header4 = "header value 4 which is really a dummy value", Header5 = "header value 5 which is really a dummy value", Header6 = "header value 6 which is really a dummy value", } local request_body = response_body request_headers["Content-Length"] = #request_body local request_size -- will be set on first test local timeout = 5 local timeout_bytes_request local timeout_bytes_response copas.setErrorHandler(function(msg, co, skt) -- on any error we want to exit forcefully print(copas.gettraceback(msg, co, skt)) os.exit(1) end, true) -- true: make it the default for all threads/coros local function runtest() local s1 = socket.bind('*', 49500) copas.addserver(s1, copas.handler(function(skt) -- HTTP server that will optionally do a timeout on the request, or on the response copas.setsocketname("Server 49500", skt) copas.setthreadname("Server 49500") -- read bytes up to where we're supposed to do the timeout (if at all) print("Server reading port 49500: incoming connection") skt:settimeout(1) if timeout_bytes_request then print("Server reading port 49500: generating request-timeout at byte: ", timeout_bytes_request) local res, err, part = skt:receive(timeout_bytes_request) if not res then print("Server reading port 49500: full request:", err, "received:", part) os.exit(1) end -- we timeout on the request, so sleep here and exit copas.pause(timeout + 1) -- sleep 1 second more than the requester timeout, to force a timeout on the request print("Server reading port 49500: request-timeout complete") return skt:close() end -- receive full request print("Server reading port 49500: reading full request") local res, err, part = skt:receive("*a") -- print("res",res) -- print("err",err) -- print("part",part) if not res and err == "timeout" then res = part end request_size = #res print("request size: ", #res) if not res then print("Server reading port 49500: first chunk:", err, "received:", part) os.exit(1) end -- request was read, now send the reposnse if not timeout_bytes_response then -- just send full response, not timeout generation local ok, err = skt:send(response) if not ok then print("Server reading port 49500: failed sending full response:", err) os.exit(1) end print("Server reading port 49500: send full response, done!") return skt:close() end -- we send a partial response and timeout print("Server reading port 49500: generating response-timeout at byte: ", timeout_bytes_response) local ok, err = skt:send(response:sub(1, timeout_bytes_response)) if not ok then print("Server reading port 49500: failed sending partial response:", err) os.exit(1) end copas.pause(timeout + 1) -- sleep 1 second more than the requester timeout, to force a timeout on the response print("Server reading port 49500: response-timeout complete") return skt:close() end)) copas.addnamedthread("test request", function() print "Waiting a bit for server to start..." copas.pause(1) -- give server some time to start do print("first test: succesfull round trip") timeout_bytes_request = nil timeout_bytes_response = nil -- make request local ok, rstatus, rheaders, rstatus_line = request { url = "http://localhost:49500/some/path", method = "POST", headers = request_headers, timeout = timeout, source = ltn12.source.string(("B"):rep(1023).."x"), -- request body sink = ltn12.sink.table({}), -- response body } print("Client received response: ") print(" ok = "..tostring(ok)) print(" status = "..tostring(rstatus)) if not rheaders then print(" headers = "..tostring(rheaders)) else print(" headers = {") for k, v in pairs(rheaders) do print(" "..tostring(k)..": "..tostring(v)) end print(" }") end print(" status_line = "..tostring(rstatus_line)) if ok and rstatus == 200 then print("Client: received a '200 OK', as expected!") else print("Client: error when requesting:", rstatus) os.exit(1) end -- cleanup; sleep 2 secs to wait for closing server socket -- to ensure any error messages do not get intermixed with the next tests output copas.pause(2) print(("="):rep(80)) end do print("second test: server generates time-out while receiving the headers") timeout_bytes_request = 100 -- 100 bytes is still headers timeout_bytes_response = nil -- make request local ok, rstatus, rheaders, rstatus_line = request { url = "http://localhost:49500/some/path", method = "POST", headers = request_headers, timeout = timeout, source = ltn12.source.string(("B"):rep(1023).."x"), -- request body sink = ltn12.sink.table({}), -- response body } print("Client received response: ") print(" ok = "..tostring(ok)) print(" status = "..tostring(rstatus)) if not rheaders then print(" headers = "..tostring(rheaders)) else print(" headers = {") for k, v in pairs(rheaders) do print(" "..tostring(k)..": "..tostring(v)) end print(" }") end print(" status_line = "..tostring(rstatus_line)) if not ok and rstatus == "timeout" then print("Client: received a timeout error, as expected!") else print("Client: error when requesting:", rstatus) os.exit(1) end -- cleanup; sleep 2 secs to wait for closing server socket -- to ensure any error messages do not get intermixed with the next tests output copas.pause(2) print(("="):rep(80)) end do print("third test: server generates time-out while receiving the body") timeout_bytes_request = request_size - 500 -- body = 1k, so 500 before end is right in the middle of the body timeout_bytes_response = nil -- make request local ok, rstatus, rheaders, rstatus_line = request { url = "http://localhost:49500/some/path", method = "POST", headers = request_headers, timeout = timeout, source = ltn12.source.string(("B"):rep(1023).."x"), -- request body sink = ltn12.sink.table({}), -- response body } print("Client received response: ") print(" ok = "..tostring(ok)) print(" status = "..tostring(rstatus)) if not rheaders then print(" headers = "..tostring(rheaders)) else print(" headers = {") for k, v in pairs(rheaders) do print(" "..tostring(k)..": "..tostring(v)) end print(" }") end print(" status_line = "..tostring(rstatus_line)) if not ok and rstatus == "timeout" then print("Client: received a timeout error, as expected!") else print("Client: error when requesting:", rstatus) os.exit(1) end -- cleanup; sleep 2 secs to wait for closing server socket -- to ensure any error messages do not get intermixed with the next tests output copas.pause(2) print(("="):rep(80)) end do print("fourth test: server generates time-out while sending the headers") timeout_bytes_request = nil timeout_bytes_response = 100 -- after 100 bytes, is still in the headers -- make request local ok, rstatus, rheaders, rstatus_line = request { url = "http://localhost:49500/some/path", method = "POST", headers = request_headers, timeout = timeout, source = ltn12.source.string(("B"):rep(1023).."x"), -- request body sink = ltn12.sink.table({}), -- response body } print("Client received response: ") print(" ok = "..tostring(ok)) print(" status = "..tostring(rstatus)) if not rheaders then print(" headers = "..tostring(rheaders)) else print(" headers = {") for k, v in pairs(rheaders) do print(" "..tostring(k)..": "..tostring(v)) end print(" }") end print(" status_line = "..tostring(rstatus_line)) if not ok and rstatus == "timeout" then print("Client: received a timeout error, as expected!") else print("Client: error when requesting:", rstatus) os.exit(1) end -- cleanup; sleep 2 secs to wait for closing server socket -- to ensure any error messages do not get intermixed with the next tests output copas.pause(2) print(("="):rep(80)) end do print("fifth test: server generates time-out while sending the body") timeout_bytes_request = nil timeout_bytes_response = #response - 500 -- body = 1024, so 500 before end is right in the middle of the body -- make request local ok, rstatus, rheaders, rstatus_line = request { url = "http://localhost:49500/some/path", method = "POST", headers = request_headers, timeout = timeout, source = ltn12.source.string(("B"):rep(1023).."x"), -- request body sink = ltn12.sink.table({}), -- response body } print("Client received response: ") print(" ok = "..tostring(ok)) print(" status = "..tostring(rstatus)) if not rheaders then print(" headers = "..tostring(rheaders)) else print(" headers = {") for k, v in pairs(rheaders) do print(" "..tostring(k)..": "..tostring(v)) end print(" }") end print(" status_line = "..tostring(rstatus_line)) if not ok and rstatus == "timeout" then print("Client: received a timeout error, as expected!") else print("Client: error when requesting:", rstatus) os.exit(1) end -- cleanup; sleep 2 secs to wait for closing server socket -- to ensure any error messages do not get intermixed with the next tests output copas.pause(2) print(("="):rep(80)) end -- close server and exit print("closing server and exiting...") copas.removeserver(s1) end) print("starting loop") copas.loop() print("Loop done") end runtest() print "test success!" copas-4.8.0/tests/httpredirect.lua000066400000000000000000000067571476464242400172320ustar00rootroot00000000000000-- test redirecting http <-> https combinations local copas = require("copas") local http = copas.http local ltn12 = require("ltn12") local dump_all_headers = false local redirect local socket = require "socket" local function doreq(url) local reqt = { url = url, redirect = redirect, --> allows https-> http redirect target = {}, } reqt.sink = ltn12.sink.table(reqt.target) local result, code, headers, status = http.request(reqt) print(string.rep("-",70)) print("Fetching:",url,"==>",code, status) if dump_all_headers then if headers then print("HEADERS") for k,v in pairs(headers) do print("",k,v) end end else print(" at:", (headers or {}).location) end --print(string.rep("=",70)) return result, code, headers, status end local done = false copas.addthread(function() local _, code, headers = doreq("https://goo.gl/UBCUc5") -- https --> https redirect assert(tonumber(code)==200, "unexpected status code: "..tostring(code)) assert(headers.location == "https://github.com/lunarmodules/luasec", "unexpected location header: "..tostring(headers.location)) print("https -> https redirect OK!") copas.addthread(function() local _, code, headers = doreq("http://goo.gl/UBCUc5") -- http --> https redirect assert(tonumber(code)==200, "unexpected status code: "..tostring(code)) assert(headers.location == "https://github.com/lunarmodules/luasec", "unexpected location header: "..tostring(headers.location)) print("http -> https redirect OK!") copas.addthread(function() --local result, code, headers, status = doreq("http://goo.gl/tBfqNu") -- http --> http redirect -- the above no longer works for testing, since Google auto-inserts a -- initial redirect to the same url, over https, hence the final -- redirect is a downgrade which then errors out -- so we set up a local http-server to deal with this local server = assert(socket.bind("127.0.0.1", 9876)) local crlf = string.char(13)..string.char(10) copas.addserver(server, function(skt) skt = copas.wrap(skt) assert(skt:receive()) local response = "HTTP/1.1 302 Found" .. crlf .. "Location: http://www.httpvshttps.com" .. crlf .. crlf assert(skt:send(response)) skt:close() end) -- execute test request local _, code, headers = doreq("http://localhost:9876/") -- http --> http redirect copas.removeserver(server) -- immediately close server again assert(tonumber(code)==200, "unexpected status code: "..tostring(code)) assert(headers.location == "http://www.httpvshttps.com") print("http -> http redirect OK!") copas.addthread(function() local result, code = doreq("https://bit.ly/3vmhXhW") -- https --> http security test case assert(result==nil and code == "Unallowed insecure redirect https to http") print("https -> http redirect, while not allowed OK!:", code) copas.addthread(function() redirect = "all" local _, code, headers = doreq("https://bit.ly/3vmhXhW") -- https --> http security test case assert(tonumber(code)==200, "unexpected status code: "..tostring(code)) assert(headers.location == "http://www.httpvshttps.com/") print("https -> http redirect, while allowed OK!") done = true end) end) end) end) end) copas.loop() if not done then print("Some checks above failed") os.exit(1) end copas-4.8.0/tests/largetransfer.lua000066400000000000000000000076111476464242400173560ustar00rootroot00000000000000-- tests large transmissions, sending and receiving -- uses `receive` and `receivepartial` -- Does send the same string twice simultaneously -- -- Test should; -- * show timer output, once per second, and actual time should be 1 second increments -- * both transmissions should take appr. equal time, then they we're nicely cooperative local copas = require 'copas' local socket = require 'socket' -- copas.debug.start() local body = ("A"):rep(1024*1024*50) -- 50 mb string local start = copas.gettime() local done = 0 local sparams, cparams local function runtest() local s1 = socket.bind('*', 49500) copas.addserver(s1, copas.handler(function(skt) copas.setsocketname("Server 49500", skt) copas.setthreadname("Server 49500") --skt:settimeout(0) -- don't set, uses `receive` method local res, err, part = skt:receive('*a') res = res or part if res ~= body then print("Received doesn't match send") end print("Server reading port 49500... Done!", copas.gettime()-start, err, #res) copas.removeserver(s1) done = done + 1 end, sparams)) local s2 = socket.bind('*', 49501) copas.addserver(s2, copas.handler(function(skt) skt:settimeout(0) -- set, uses the `receivepartial` method copas.setsocketname("Server 49501", skt) copas.setthreadname("Server 49501") local res, err, part = skt:receive('*a') res = res or part if res ~= body then print("Received doesn't match send") end print("Server reading port 49501... Done!", copas.gettime()-start, err, #res) copas.removeserver(s2) done = done + 1 end, sparams)) copas.addnamedthread("Client 49500", function() local skt = socket.tcp() skt = copas.wrap(skt, cparams) copas.setsocketname("Client 49500", skt) skt:connect("localhost", 49500) local last_byte_sent, err repeat last_byte_sent, err = skt:send(body, last_byte_sent or 1, -1) until last_byte_sent == nil or last_byte_sent == #body print("Client writing port 49500... Done!", copas.gettime()-start, err, #body) -- we're not closing the socket, so the Copas GC-when-idle can kick-in to clean up skt = nil -- luacheck: ignore done = done + 1 end) copas.addnamedthread("Client 49501", function() local skt = socket.tcp() skt = copas.wrap(skt, cparams) copas.setsocketname("Client 49501", skt) skt:connect("localhost", 49501) local last_byte_sent, err repeat last_byte_sent, err = skt:send(body, last_byte_sent or 1, -1) until last_byte_sent == nil or last_byte_sent == #body print("Client writing port 49501... Done!", copas.gettime()-start, err, #body) -- we're not closing the socket, so the Copas GC-when-idle can kick-in to clean up skt = nil -- luacheck: ignore done = done + 1 end) copas.addnamedthread("test timeout thread", function() local i = 1 while done ~= 4 do copas.pause(1) print(i, "seconds:", copas.gettime()-start) i = i + 1 if i > 60 then print"timeout" os.exit(1) end end print "success!" end) print("starting loop") copas.loop() print("Loop done") end runtest() -- run test using regular connection (s/cparams == nil) -- set ssl parameters and do it again sparams = { mode = "server", protocol = "any", key = "tests/certs/serverAkey.pem", certificate = "tests/certs/serverA.pem", cafile = "tests/certs/rootA.pem", verify = {"peer", "fail_if_no_peer_cert"}, options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"}, } cparams = { mode = "client", protocol = "any", key = "tests/certs/clientAkey.pem", certificate = "tests/certs/clientA.pem", cafile = "tests/certs/rootA.pem", verify = {"peer", "fail_if_no_peer_cert"}, options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"}, } done = 0 start = copas.gettime() runtest() copas-4.8.0/tests/lock.lua000066400000000000000000000062771476464242400154560ustar00rootroot00000000000000-- make sure we are pointing to the local copas first package.path = string.format("../src/?.lua;%s", package.path) local copas = require "copas" local Lock = copas.lock local gettime = copas.gettime local test_complete = false copas.loop(function() local lock1 = Lock.new(nil, true) -- not re-entrant assert(lock1:get()) local s = gettime() local _, err = lock1:get(1) local duration = gettime() - s assert(err == "timeout", "got errror: "..tostring(err)) assert(duration > 1 and duration < 1.2, string.format("expected timeout of 1 second, but took: %f",duration)) -- let go and reacquire assert(lock1:release()) local _, err = lock1:release() assert(err == "cannot release a lock not owned", "got error: "..tostring(err)) assert(lock1:get()) lock1:destroy() local _, err = lock1:release() assert(err == "destroyed", "got errror: "..tostring(err)) -- let's scale, go grab a lock lock1 = assert(Lock.new(10)) assert(lock1:get()) local success_count = 0 local timeout_count = 0 local destroyed_count = 0 -- now add another bunch of threads for the same lock local size = 750 -- must be multiple of 3 !! print("creating "..size.." threads hitting the lock...", gettime()) local tracker = {} for i = 1, size do tracker[i] = true copas.addthread(function() local timeout if i > (size*2)/3 then timeout = 60 -- the ones to hit "destroyed" elseif i > size/3 and i <= (size*2)/3 then timeout = 2 -- the ones to hit "timeout" else timeout = 1 -- the ones to succeed end --print(i, "waiting...") local ok, err = lock1:get(timeout) if ok then --print(i, "got it!") success_count = success_count + 1 if i == size/3 then copas.pause(3) -- keep it long enough for the next 500 to timeout --print(i, "releasing ") assert(lock1:release()) -- by now the 2nd 500 timed out --print(i, "destroying ") assert(lock1:destroy()) -- make the last 500 fail on "destroyed" else --print(i, "releasing ") assert(lock1:release()) end tracker[i] = nil elseif err == "timeout" then --print(i, "timed out!") timeout_count = timeout_count + 1 --if i == (size*2)/3 then -- copas.pause(2) -- to ensure thread 500 finished its sleep above --end tracker[i] = nil elseif err == "destroyed" then --print(i, "destroyed!") destroyed_count = destroyed_count + 1 tracker[i] = nil else tracker[i] = nil error("didn't expect error: '"..tostring(err).."' thread "..i) end end) -- added thread function end -- for loop print("releasing "..size.." threads...", gettime()) assert(lock1:release()) print("waiting to finish...") while next(tracker) do copas.pause(0.1) end -- check results print("success: ", success_count) print("timeout: ", timeout_count) print("destroyed: ", destroyed_count) assert(success_count == size/3) assert(timeout_count == size/3) assert(destroyed_count == size/3) test_complete = true end) assert(test_complete, "test did not complete!") print("test success!") copas-4.8.0/tests/loop_starter.lua000066400000000000000000000004431476464242400172300ustar00rootroot00000000000000-- make sure we are pointing to the local copas first package.path = string.format("../src/?.lua;%s", package.path) local copas = require "copas" local x copas.loop(function() -- Copas initialization function x = true end) assert(x, "expected 'x' to be truthy") print "test success!" copas-4.8.0/tests/no_luasocket.lua000066400000000000000000000017041476464242400172020ustar00rootroot00000000000000-- make sure we are pointing to the local copas first package.path = string.format("../src/?.lua;%s", package.path) print([[ Testing to run Copas without LuaSocket, just LuaSystem ============================================================================= ]]) -- patch require to no longer load luasocket local _require = require _G.require = function(name) if name == "socket" then error("luasocket is not allowed in this test") end return _require(name) end local copas = require "copas" local timer = copas.timer local successes = 0 local t1 -- luacheck: ignore copas.loop(function() t1 = timer.new({ delay = 0.1, recurring = true, callback = function(timer_obj, params) successes = successes + 1 -- 6 to come if successes == 6 then timer_obj:cancel() end end, }) -- succes count = 6 end) assert(successes == 6, "number of successes didn't match! got: "..successes) print("test success!") copas-4.8.0/tests/pause.lua000066400000000000000000000011331476464242400156250ustar00rootroot00000000000000-- check no memory leaks when sleeping -- make sure we are pointing to the local copas first package.path = string.format("../src/?.lua;%s", package.path) local copas = require("copas") local t1 = copas.addthread( function() copas.pauseforever() -- sleep until woken up end ) -- prepare GC test local validate_gc = setmetatable({ [t1] = true, },{ __mode = "k" }) -- start test copas.loop() t1 = nil -- luacheck: ignore collectgarbage() collectgarbage() --check GC assert(next(validate_gc) == nil, "the 'validate_gc' table should have been empty!") print "test success!" copas-4.8.0/tests/queue.lua000066400000000000000000000114171476464242400156420ustar00rootroot00000000000000-- make sure we are pointing to the local copas first package.path = string.format("../src/?.lua;%s", package.path) local copas = require "copas" local now = copas.gettime local Queue = copas.queue local test_complete = false copas.loop(function() -- basic push/pop local q = Queue:new() q:push "hello" assert(q:pop() == "hello", "expected the input to be returned") -- yielding on pop when queue is empty local s = now() copas.addthread(function() copas.pause(0.5) q:push("delayed") end) assert(q:pop() == "delayed", "expected a delayed result") assert(now() - s >= 0.5, "result was not delayed!") -- pop times out local ok, err = q:pop(0.5) assert(err == "timeout", "expected a timeout") assert(ok == nil) -- get_size returns queue size assert(q:get_size() == 0) q:push(1) assert(q:get_size() == 1) q:push(2) assert(q:get_size() == 2) q:push(3) assert(q:get_size() == 3) -- queue behaves as fifo assert(q:pop() == 1) assert(q:pop() == 2) assert(q:pop() == 3) -- handles nil values q:push(1) q:push(nil) q:push(3) assert(q:pop() == 1) local val, err = q:pop() assert(val == nil) assert(err == nil) assert(q:pop() == 3) -- stopping q:push(1) q:push(2) q:push(3) assert(q:stop()) local count = 0 local coro = q:add_worker(function(item) count = count + 1 end) copas.pause(0.1) assert(count == 3, "expected all 3 items handled") assert(coroutine.status(coro) == "dead", "expected thread to be gone") -- coro should be GC'able local weak = setmetatable({}, {__mode="v"}) weak[{}] = coro coro = nil -- luacheck: ignore collectgarbage() collectgarbage() assert(not next(weak)) -- worker exited, so queue is destroyed now? ok, err = q:push() assert(err == "destroyed", "expected queue to be destroyed") assert(ok == nil) ok, err = q:pop() assert(err == "destroyed", "expected queue to be destroyed") assert(ok == nil) test_complete = true end) -- copas loop exited when here assert(test_complete, "test did not complete!") print("test 1 success!") -- a worker handling nil values local count = 0 copas.loop(function() local q = Queue:new() q:push(1) q:push(nil) q:push(3) q:add_worker(function() count = count + 1 end) copas.pause(0.5) -- to activate the worker, which will now be blocked on the q semaphore assert(q:finish(5)) end) assert(count == 3, "expected count to be 3, got "..tostring(count)) print("test 2 success!") -- finish blocks for a timeout local passed = false copas.loop(function() local q = Queue:new() q:push(1) -- no workers, so this one will not be handled local s = now() local ok, err = q:finish(1) local duration = now() - s assert(not ok, "expected a falsy value, got: "..tostring(ok)) assert(err == "timeout", "expected error 'timeout', got: "..tostring(err)) assert(duration > 1 and duration < 1.2, string.format("expected timeout of 1 second, but took: %f",duration)) passed = true end) assert(passed, "test failed!") print("test 3 success!") -- destroying a queue while workers are idle copas.loop(function() local q = Queue:new() q:add_worker(function() end) copas.pause(0.5) -- to activate the worker, which will now be blocked on the q semaphore q:stop() -- this should exit the idle workers and exit the copas loop end) print("test 4 success!") -- finish a queue while workers are idle copas.loop(function() local q = Queue:new() q:add_worker(function() end) copas.pause(0.5) -- to activate the worker, which will now be blocked on the q semaphore q:finish() -- this should exit the idle workers and exit the copas loop end) print("test 5 success!") -- finish doesn't return until all workers are done (finished handling the last queue item) local result = {} local passed = true copas.loop(function() local q = Queue:new() q:push(1) q:push(2) q:push(3) for i = 1,2 do -- add 2 workers q:add_worker(function(n) table.insert(result, "start item " .. n) copas.pause(0.5) table.insert(result, "end item " .. n) end) end -- local s = now() table.insert(result, "start queue") copas.pause(0.75) table.insert(result, "start finish") local ok, err = q:finish() table.insert(result, "finished "..tostring(ok).." "..tostring(err)) copas.pause(1) local expected = { "start queue", "start item 1", "start item 2", "end item 1", "start item 3", "end item 2", "start finish", "end item 3", "finished true nil", } for i = 1, math.max(#result, #expected) do if result[i] ~= expected[i] then for n = 1, math.max(#result, #expected) do print(n, result[n], expected[n], result[n] == expected[n] and "" or " <--- failed") end passed = false break end end end) assert(passed, "test 6 failed!") print("test 6 success!") copas-4.8.0/tests/removeserver.lua000066400000000000000000000022021476464242400172320ustar00rootroot00000000000000 --- Test for removeserver(skt, true) -- that keeps the socket open after removal. local copas = require("copas") local socket = require("socket") local wskt = socket.bind("*", 0) local whost, wport = wskt:getsockname() wport = tonumber(wport) -- set up a timeout to not hang on failure local timeout_timer = copas.timer.new { delay = 10, callback = function() print("timeout!") os.exit(1) end } local connection_handler = function(cskt) print(tostring(cskt).." ("..type(cskt)..") received a connection") local data, _, partial = cskt:receive() if partial and not data then data = partial end print("triggered", data) copas.removeserver(wskt, true) end local function wait_for_trigger() copas.addserver(wskt, copas.handler(connection_handler), "my_TCP_server") end local function trigger_it(n) local cskt = copas.wrap(socket.tcp()) local ok = cskt:connect(whost, wport) if ok then cskt:send("hi "..n) end cskt:close() end copas.addthread(function() for i = 1, 3 do wait_for_trigger() trigger_it(i) copas.pause(0.1) end timeout_timer:cancel() end) copas.loop() copas-4.8.0/tests/removethread.lua000066400000000000000000000025401476464242400172000ustar00rootroot00000000000000--- Test for removethread(thread) -- make sure we are pointing to the local copas first package.path = string.format("../src/?.lua;%s", package.path) local copas = require("copas") local now = copas.gettime -- Test 1: basic test local t1 = copas.addthread( function() print("endless thread start") local n = 0 while true do n = n + 1 print("endless thread:",n) copas.pause(0.5) end end) local t2 = copas.addthread(function() for i = 1, 5 do copas.pause(0.6) end print("stopping endless thread externally") copas.removethread(t1) end) -- prepare GC test local validate_gc = setmetatable({ [t1] = true, [t2] = true, },{ __mode = "k" }) -- start test copas.loop() t1 = nil -- luacheck: ignore t2 = nil -- luacheck: ignore collectgarbage() collectgarbage() --check GC assert(next(validate_gc) == nil, "the 'validate_gc' table should have been empty!") print "test 1: success!" -- Test 2: ensure a waiting thread leaves in a timely manner local coro = copas.addthread(function() copas.pause(2) end) local start_time = now() copas(function() copas.pause(0.5) copas.removethread(coro) end) local duration = now() - start_time assert(duration < 0.6, ("Expected immediate exit, after removal of thread, took: %.2f"):format(duration)) print "test 2: success!" copas-4.8.0/tests/request.lua000066400000000000000000000030611476464242400162020ustar00rootroot00000000000000local copas = require("copas") local http = copas.http local url = assert(arg[1], "missing url argument") local debug_mode = not not arg[2] print("Testing copas.http.request with url " .. url .. (debug_mode and "(in debug mode)" or "")) local switches, max_switches = 0, 10000000 local done = false if debug_mode then copas.debug.start() local socket = require "socket" local old_tcp = socket.tcp socket.tcp = function(...) local sock, err = old_tcp(...) if not sock then return sock, err end return copas.debug.socket(sock) end end copas.addthread(function() while switches < max_switches do copas.pause() switches = switches + 1 end if not done then print(("Error: Request not finished after %d thread switches"):format(switches)) os.exit(1) end end) copas.addthread(function() print("Starting request") local content, code, headers, status = http.request(url) print(("Finished request after %d thread switches"):format(switches)) if type(content) ~= "string" or type(code) ~= "number" or type(headers) ~= "table" or type(status) ~= "string" then print("Error: incorrect return values:") print(content) print(code) print(headers) print(status) os.exit(1) end print(("Status: %s, content: %d bytes"):format(status, #content)) done = true max_switches = switches + 10 -- just do a few more and finish the test end) print("Starting loop") copas.loop() if done then print("Finished loop") else print("Error: Finished loop but request is not complete") os.exit(1) end copas-4.8.0/tests/semaphore.lua000066400000000000000000000106221476464242400164760ustar00rootroot00000000000000-- make sure we are pointing to the local copas first package.path = string.format("../src/?.lua;%s", package.path) local copas = require "copas" local now = copas.gettime local semaphore = copas.semaphore local test_complete = false copas.loop(function() local sema = semaphore.new(10, 5, 1) assert(sema:get_count() == 5) assert(sema:take(3)) assert(sema:get_count() == 2) local ok, _, err local start = now() _, err = sema:take(3, 0) -- 1 too many, immediate timeout assert(err == "timeout", "expected a timeout") assert(now() - start < 0.001, "expected it not to block with timeout = 0") start = now() _, err = sema:take(10, 0) -- way too many, immediate timeout assert(err == "timeout", "expected a timeout") assert(now() - start < 0.001, "expected it not to block with timeout = 0") start = now() _, err = sema:take(11) -- more than 'max'; "too many" error assert(err == "too many", "expected a 'too many' error") assert(now() - start < 0.001, "expected it not to block") start = now() _, err = sema:take(10) -- not too many, let's timeout assert(err == "timeout", "expected a 'timeout' error") assert(now() - start > 1, "expected it to block for 1s") assert(sema:get_count() == 2) --validate async threads local state = 0 copas.addthread(function() assert(sema:take(5)) print("got the first 5!") state = state + 1 end) copas.addthread(function() assert(sema:take(5)) print("got the next 5!") state = state + 2 end) copas.pause(0.1) assert(state == 0, "expected state to still be 0") assert(sema:get_count() == 2, "expected count to still have 2 resources") assert(sema:give(4)) assert(sema:get_count() == 1, "expected count to now have 1 resource") copas.pause(0.1) assert(state == 1, "expected 1 from the first thread to be added to state") assert(sema:give(4)) assert(sema:get_count() == 0, "gave 4 more, so 5 in total, releasing 5, leaves 0 as expected") copas.pause(0.1) assert(state == 3, "expected 2 from the 2nd thread to be added to state") ok, err = sema:give(100) assert(not ok) assert(err == "too many") assert(sema:get_count() == 10) -- validate destroying assert(sema:take(sema:get_count())) -- empty the semaphore assert(sema:get_count() == 0, "should be empty now") local state = 0 copas.addthread(function() local ok, err = sema:take(5) if ok then print("got 5, this is unexpected") elseif err == "destroyed" then state = state + 1 end end) copas.addthread(function() local ok, err = sema:take(5) if ok then print("got 5, this is unexpected") elseif err == "destroyed" then state = state + 1 end end) copas.pause(0.1) assert(sema:destroy()) copas.pause(0.1) assert(state == 2, "expected 2 threads to error with 'destroyed'") -- only returns errors from now on, on all methods ok, err = sema:destroy(); assert(ok == nil and err == "destroyed", "expected an error") ok, err = sema:give(1); assert(ok == nil and err == "destroyed", "expected an error") ok, err = sema:take(1); assert(ok == nil and err == "destroyed", "expected an error") ok, err = sema:get_count(); assert(ok == nil and err == "destroyed", "expected an error") -- timeouts get cancelled upon destruction -- we set a timeout to 0.5 seconds, then destroy the semaphore -- the timeout should not execute -- Reproduce https://github.com/lunarmodules/copas/issues/118 local track_table = setmetatable({}, { __mode = "v" }) local sema = semaphore.new(10, 0, 0.5) track_table.sema = sema local state = 0 track_table.coro = copas.addthread(function() local ok, err = sema:take(1) if ok then print("got one, this is unexpected") elseif err == "destroyed" then state = state + 1 end end) copas.pause(0.1) assert(sema:destroy()) copas.pause(0.1) assert(state == 1, "expected 1 thread to error with 'destroyed'") sema = nil local errors = 0 copas.setErrorHandler(function(msg) print("got error: "..tostring(msg)) print("--------------------------------------") errors = errors + 1 end, true) collectgarbage() -- collect garbage to force eviction from the semaphore registry collectgarbage() copas.pause(0.5) -- wait for the timeout to expire if it is still set assert(errors == 0, "expected no errors") test_complete = true end) assert(test_complete, "test did not complete!") print("test success!") copas-4.8.0/tests/starve.lua000066400000000000000000000051101476464242400160130ustar00rootroot00000000000000-- tests looping 100% in receive/send -- Should not prevent other threads from running -- -- Test should; -- * sleep incremental, not on absolute time so it slowly diverges if the timer -- thread is being starved -- * seconds printed and elapsed should stay very close local copas = require 'copas' local socket = require 'socket' --copas.debug.start() local body = ("A"):rep(1024*1024*50) -- 50 mb string local done = 0 local function runtest() local s1 = socket.bind('*', 49500) copas.addserver(s1, copas.handler(function(skt) copas.setsocketname("Server 49500", skt) copas.setthreadname("Server 49500") print "Server 49500 accepted incoming connection" local end_time = copas.gettime() + 30 -- we run for 30 seconds while end_time > copas.gettime() do local res, err, _ = skt:receive(1) -- single byte from 50mb chunks if res == nil and err ~= "timeout" then print("Server 49500 returned: " .. err) os.exit(1) end end done = done + 1 print("Server reading port 49500... Done!") skt:close() copas.removeserver(s1) end)) copas.addnamedthread("Client 49500", function() local skt = socket.tcp() skt = copas.wrap(skt) copas.setsocketname("Client 49500", skt) skt:connect("localhost", 49500) local last_byte_sent, err, complete while not complete do repeat last_byte_sent, err = skt:send(body, last_byte_sent or 1, -1) if err == "closed" then -- server closed connection, so exit, test is finished complete = true break end if last_byte_sent == nil and err ~= "timeout" then print("client 49500 returned: " .. err) os.exit(1) end until last_byte_sent == nil or last_byte_sent == #body end print("Client writing port 49500... Done!") skt:close() done = done + 1 end) copas.addnamedthread("test timeout thread", function() local i = 0 local start = copas.gettime() while done ~= 2 do copas.pause(1) -- delta sleep, so it slowly diverges if starved i = i + 1 local time_passed = copas.gettime()-start print("slept "..i.." seconds, time passed: ".. time_passed.." seconds") if math.abs(i - time_passed) > 2 then print("timer diverged by more than 2 seconds: failed!") os.exit(1) end if i > 60 then print"timeout" os.exit(1) end end print "success!" end) print("starting loop") copas.loop() print("Loop done") end runtest() copas-4.8.0/tests/tcptimeout.lua000066400000000000000000000105511476464242400167110ustar00rootroot00000000000000-- Tests Copas socket timeouts -- -- Run the test file, it should exit successfully without hanging. -- make sure we are pointing to the local copas first package.path = string.format("../src/?.lua;%s", package.path) local platform = "unix" if package.config:sub(1,1) == "\\" then platform = "windows" elseif io.popen("uname", "r"):read("*a"):find("Darwin") then platform = "mac" end print("Testing platform: " .. platform) local copas = require("copas") local socket = require("socket") -- hack; no way to kill copas.loop from thread local function error(err) print(debug.traceback(err, 2)) os.exit(-1) end local function assert(truthy, err) if not truthy then print(debug.traceback(err, 2)) os.exit(-1) end end -- tcp echo server for testing against, returns `ip, port` to connect to -- send `quit\n` to cause server to disconnect client -- stops listen server after first connection local function singleuseechoserver() local server = socket.bind("127.0.0.1", 0) -- "localhost" fails because of IPv6 error local ip, port = server:getsockname() local function echoHandler(skt) -- remove server after first connection copas.removeserver(server) skt = copas.wrap(skt) while true do local data = skt:receive() if not data or data == "quit" then break end skt:send(data..'\n') end end copas.addserver(server, echoHandler) return ip, port end local tests = {} function tests.just_exit() copas.loop() end function tests.connect_and_exit() local ip, port = singleuseechoserver() copas.addthread(function() local client = socket.connect(ip, port) client = copas.wrap(client) client:close() end) copas.loop() end if platform == "mac" then -- this test fails on a Mac, looks like the 'listen(0)' isn't being honoured print("\nSkipping test on Mac!\n") else function tests.connect_timeout_copas() local server = socket.tcp() server:bind("localhost", 0) server:listen(0) -- zero backlog, single connection will block further connections -- note: not servicing connections local ip, port = server:getsockname() copas.addthread(function() -- fill server's implicit connection backlog socket.connect(ip,port) local client = socket.tcp() client = copas.wrap(client) client:settimeout(0.01) local status, err = client:connect(ip, port) assert(status == nil, "connect somehow succeeded") assert(err == "timeout", "connect failed with non-timeout error: "..tostring(err)) client:close() end) copas.loop() end function tests.connect_timeout_socket() local server = socket.tcp() server:bind("localhost", 0) server:listen(0) -- zero backlog, single connection will block further connections -- note: not servicing connections local ip, port = server:getsockname() copas.addthread(function() copas.useSocketTimeoutErrors(true) -- fill server's implicit connection backlog socket.connect(ip,port) local client = socket.tcp() client = copas.wrap(client) client:settimeout(0.01) local status, err = client:connect(ip, port) assert(status == nil, "connect somehow succeeded") -- we test for a different error message becasue we expect socket errors, not copas ones assert(err == "Operation already in progress", "connect failed with non-timeout error: "..tostring(err)) client:close() end) copas.loop() end end function tests.receive_timeout() local ip, port = singleuseechoserver() copas.addthread(function() local client = socket.tcp() client = copas.wrap(client) client:settimeout(0.01) local status, err = client:connect(ip, port) assert(status, "failed to connect: "..tostring(err)) client:send("foo\n") local data, err = client:receive() assert(data, "failed to recieve: "..tostring(err)) assert(data == "foo", "recieved wrong echo: "..tostring(data)) local data, err = client:receive() assert(data == nil, "somehow recieved echo without sending") assert(err == "timeout", "failed with non-timeout error: "..tostring(err)) client:close() end) copas.loop() end -- test "framework" for name, test in pairs(tests) do print("testing: "..tostring(name)) local status, err = pcall(test) if not status then error(err) end end print("[✓] all tests completed successuly") copas-4.8.0/tests/timeout_errors.lua000066400000000000000000000021451476464242400175760ustar00rootroot00000000000000-- Tests Copas timeout mnechanism, when a timeout handler errors out -- -- Run the test file, it should exit successfully without hanging. -- make sure we are pointing to the local copas first package.path = string.format("../src/?.lua;%s", package.path) local copas = require("copas") local tests = {} function tests.error_on_timeout() local err_received copas.addthread(function() copas.setErrorHandler(function(err, coro, skt) err_received = err end, true) print "setting timeout in 0.1 seconds" copas.timeout(0.1, function() print "throwing an error now..." error("oops...") end) print "going to sleep for 1 second" copas.pause(1) if not (err_received or ""):find("oops...", 1, true) then print("expected to find the error string 'oops...', but got: " .. tostring(err_received)) os.exit(1) end end) copas.loop() end -- test "framework" for name, test in pairs(tests) do print("testing: "..tostring(name)) local status, err = pcall(test) if not status then error(err) end end print("[✓] all tests completed successuly") copas-4.8.0/tests/timer.lua000066400000000000000000000075441476464242400156440ustar00rootroot00000000000000-- make sure we are pointing to the local copas first package.path = string.format("../src/?.lua;%s", package.path) local copas = require "copas" local gettime = copas.gettime local timer = copas.timer local successes = 0 copas.loop(function() local count_t1 = 0 local t1 = timer.new({ delay = 0.5, recurring = true, params = "hello world", callback = function(timer_obj, params) -- let's ensure parameters get passed assert(params == "hello world", "expected: hello world") successes = successes + 1 -- 6 to come count_t1 = count_t1 + 1 print(params .. " " .. count_t1) end, }) -- succes count = 6 local t2 = timer.new({ delay = 0.2, -- we'll override this with 0.1 below recurring = false, params = { start_time = gettime() }, initial_delay = 0.1, -- initial delay, only 0.1 callback = function(timer_obj, params) assert(gettime() - params.start_time < 0.11, "didn't honour initial delay, or recurred") print("this seems to go well, and should print only once") successes = successes + 1 -- 1 to come end, }) -- succes count = 7 timer.new({ delay = 3.3, --> allows T1 to run 6 times callback = function(timer_obj, params) t1:cancel() local _, err = t2:cancel() assert(err == "not armed", "expected t2 to already be stopped") successes = successes + 1 -- 1 to come assert(count_t1 == 6, "expected t1 to run 6 times!") successes = successes + 1 -- 1 to come timer_obj:cancel() -- cancel myself end, }) -- succes count = 9 timer.new({ delay = 0.1, recurring = true, callback = function(timer_obj, params) -- re-arm myself (recurring), should not be possible local ok, err = timer_obj:arm(1) assert(err == "already armed", "expected myself to be already armed") assert(ok == nil, "expected 'ok' to be nil") print("failed to re-arm a recurring timer, so that's ok") successes = successes + 1 -- 1 to come assert(timer_obj:cancel()) -- cancel myself end, }) -- succes count = 10 local touched = false timer.new({ delay = 0.1, recurring = false, callback = function(timer_obj, params) if touched == false then -- re-arm myself (non-recurring), should be possible local ok, err = timer_obj:arm(3) assert(ok == timer_obj) assert(err == nil, "expected 'err' to be nil") touched = gettime() print("re-armed a non-recurring timer, so that's ok") successes = successes + 1 -- 1 to come else print("a re-armed non-recurring timer executed, so that's ok") successes = successes + 1 -- 1 to come local t = math.abs(gettime() - touched - 3) assert(t < 0.01, "expected a 3 second delay for the rearmed timer. Got: "..(gettime() - touched)) successes = successes + 1 -- 1 to come end end, }) -- succes count = 13 local count = 0 local params_in = {} -- timer shouldn't be cancelled if its handler errors timer.new({ name = "error-test", delay = 0.1, recurring = true, params = params_in, errorhandler = function(msg, co, skt) local errmsg = copas.gettraceback(msg, co, skt) assert(errmsg:find("error%-test"), "the threadname wasn't found") assert(errmsg:find("error 1!") or errmsg:find("error 2!"), "the error message wasn't found") --print(errmsg) successes = successes + 1 end, callback = function(timer_obj, params) assert(params == params_in, "Params table wasn't passed along") count = count + 1 if count == 2 then -- 2nd call, so we're done timer_obj:cancel() end error("error "..count.."!") end, }) -- succes count = 15 end) assert(successes == 15, "number of successes didn't match! got: "..successes) print("test success!") copas-4.8.0/tests/tls-sni.lua000066400000000000000000000057761476464242400161220ustar00rootroot00000000000000-- Tests Copas with a simple Echo server -- -- Run the test file and the connect to the server using telnet on the used port. -- The server should be able to echo any input, to stop the test just send the command "quit" local port = 20000 local copas = require("copas") local socket = require("socket") local ssl = require("ssl") local server if _VERSION=="Lua 5.1" and not jit then -- obsolete: only for Lua 5.1 compatibility pcall = require("coxpcall").pcall -- luacheck: ignore end local server_params = { wrap = { mode = "server", protocol = "any", key = "tests/certs/serverAkey.pem", certificate = "tests/certs/serverA.pem", cafile = "tests/certs/rootA.pem", verify = {"peer", "fail_if_no_peer_cert"}, options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"}, }, sni = { strict = true, -- only allow connection 'myhost.com' names = {} } } server_params.sni.names["myhost.com"] = ssl.newcontext(server_params.wrap) local client_params = { wrap = { mode = "client", protocol = "any", key = "tests/certs/clientAkey.pem", certificate = "tests/certs/clientA.pem", cafile = "tests/certs/rootA.pem", verify = {"peer", "fail_if_no_peer_cert"}, options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"}, }, sni = { names = "" -- will be added in test below } } local function echoHandler(skt) while true do local data, err = skt:receive() if not data then if err ~= "closed" then return error("client connection error: "..tostring(err)) else return -- client closed the connection end elseif data == "quit" then return -- close this client connection elseif data == "exit" then copas.removeserver(server) return -- close this client connection, after stopping the server end skt:send(data.."\n") end end server = assert(socket.bind("*", port)) copas.addserver(server, copas.handler(echoHandler, server_params)) copas.addthread(function() copas.pause(0.5) -- allow server socket to be ready ---------------------- -- Tests start here -- ---------------------- -- try with a bad SNI (non matching) client_params.sni.names = "badhost.com" local skt = copas.wrap(socket.tcp(), client_params) local _, err = pcall(skt.connect, skt, "localhost", port) if not tostring(err):match("TLS/SSL handshake failed:") then print "expected handshake to fail" os.exit(1) end -- try again with a proper SNI (matching) client_params.sni.names = "myhost.com" local skt = copas.wrap(socket.tcp(), client_params) local success, ok = pcall(skt.connect, skt, "localhost", port) if not (success and ok) then print("expected connection to be completed", success, ok) os.exit(1) end assert(skt:send("hello world\n")) assert(skt:receive() == "hello world") print "succesfully completed test" -- send exit signal to server skt:send("exit\n") end) -- no ugly errors please, comment out when debugging copas.setErrorHandler(function() end, true) copas.loop() copas-4.8.0/tests/udptimeout.lua000066400000000000000000000053651476464242400167220ustar00rootroot00000000000000-- Tests Copas socket timeouts -- -- Run the test file, it should exit successfully without hanging. -- make sure we are pointing to the local copas first package.path = string.format("../src/?.lua;%s", package.path) local copas = require("copas") local socket = require("socket") -- hack; no way to kill copas.loop from thread local function error(err) print(debug.traceback(err, 2)) os.exit(-1) end local function assert(truthy, err) if not truthy then print(debug.traceback(err, 2)) os.exit(-1) end end -- udp echo server for testing against, returns `ip, port` to connect to -- send `quit\n` to cause server to disconnect client -- stops listen server after provided number of echos local function singleuseechoserver(die_after) die_after = die_after or 1 local server = socket.udp() server:setsockname("127.0.0.1", 0) -- "localhost" fails because of IPv6 error local ip, port = server:getsockname() copas.addthread(function() local skt = copas.wrap(server) while die_after > 0 do local data, ip, port = skt:receivefrom() if not data or data == "quit" then break end skt:sendto(data, ip, port) die_after = die_after - 1 end end) return ip, port end local tests = {} function tests.receive_timeout() local ip, port = singleuseechoserver(1) copas.addthread(function() local client = socket.udp() client = copas.wrap(client) client:settimeout(0.01) local status, err = client:setpeername(ip, port) assert(status, "failed to connect: "..tostring(err)) client:send("foo") local data, err = client:receive() assert(data, "failed to recieve: "..tostring(err)) assert(data == "foo", "recieved wrong echo: "..tostring(data)) local data, err = client:receive() assert(data == nil, "somehow recieved echo without sending") assert(err == "timeout", "failed with non-timeout error") client:close() end) copas.loop() end function tests.receivefrom_timeout() local ip, port = singleuseechoserver(1) copas.addthread(function() local client = socket.udp() client = copas.wrap(client) client:settimeout(0.01) client:sendto("foo", ip, port) local data, err = client:receivefrom() assert(data, "failed to recieve: "..tostring(err)) assert(data == "foo", "recieved wrong echo: "..tostring(data)) local data, err = client:receivefrom() assert(data == nil, "somehow recieved echo without sending") assert(err == "timeout", "failed with non-timeout error") client:close() end) copas.loop() end -- test "framework" for name, test in pairs(tests) do print("testing: "..tostring(name)) local status, err = pcall(test) if not status then error(err) end end print("[✓] all tests completed successuly")