copas.addserver
copas.addthread
copas.loop
copas.step
copas.flush
copas.receive
copas.send
copas.wrap
copas - dispatcher based on coroutines for TCP/IP servers (Copas)
Copas is a dispatcher based on coroutines that can be used by TCP/IP servers. It uses LuaSocket (http://www.cs.princeton.edu/~diego/professional/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 (http://www.keplerproject.org/xavante) as an example.
Copas can be downloaded from its LuaForge (http://luaforge.net/frs/) page.
CVS access is also available from LuaForge.
Copas 1.1 depends only on LuaSocket 2.0 (http://www.cs.princeton.edu/~diego/professional/luasocket/) and Compat-5.1 Release 5 (http://www.keplerproject.org/compat) (if you are using Lua 5.0).
Copas follows the package model (http://www.inf.puc-rio.br/~roberto/pil2/chapter15.pdf) for Lua 5.1, therefore it should be ``installed''. Refer to Compat-5.1 configuration (http://www.keplerproject.org/compat/manual.html#configuration) section about how to install the modules properly in a Lua 5.0 environment.
If you are using Kepler Copas is already installed by it, but you may want to upgrade manually your Copas version. Be aware that this may cause problems with other Kepler modules such as Xavante that depends on Copas.
Copas is a dispatcher that can help a lot in the creation of servers based on LuaSocket (http://www.cs.princeton.edu/~diego/professional/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
If all you will do with the socket is send and receive data, 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.loop()
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 yielding. If you need to
use any of those functions in your handler we strongly suggest using
Xavante's coxpcall (http://luaforge.net/frs/), a
coroutine safe version of the Lua 5 protected calls. For an example of
this usage please check Xavante.
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 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 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 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.
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) -- 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
Finally, to run the dispatcher infinite loop you just call:
copas.loop()
During the loop Copas' dispatcher accepts connections from clients and automatically calls the corresponding handler functions.
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
Copas functions are separated in two groups.
The first group is relative to the use of the dispatcher itself and are used to register servers and to execute the main loop of Copas:
copas.addserver
copas.addserver(server, handler[, timeout])
Adds a new server
and its handler
to the dispatcher using an
optional timeout
.
server
socket.bind()
.
handler
timeout
The handler will be executed in parallel with other threads and the registered handlers as long as it uses the Copas socket functions.
copas.addthread
copas.addthread(thrd[, ...])
Adds a new thread to the dispatcher using optional parameters.
The thread will be executed in parallel with other threads and the registered handlers as long as it uses the Copas socket functions.
copas.loop
copas.loop(timeout)
Starts the Copas infinite loop accepting client connections for the
registered servers and handling those connections with the
corresponding handlers. Every time a server accepts a connection,
Copas calls the associated handler passing the client socket returned
by accept()
. The timeout
parameter is optional.
copas.step
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
accept()
. The timeout
parameter is optional.
The second group is used by the handler functions to exchange data
with the clients, and by threads registered with addthread
to
exchange data with other services.
copas.flush
copas.flush(skt)
Flushes a client write buffer. copas.flush()
is called from time to
time by copas.loop()
but it may be necessary to call it from the
handler function or one of the threads.
copas.receive
copas.receive(skt, pattern)
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.
copas.send
copas.send(skt, data)
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.
copas.wrap
copas.wrap(skt)
Wraps a LuaSocket socket and returns a Copas socket that implements
LuaSocket's API but use Copas' methods copas.send()
and
copas.receive()
automatically.
Current version is 1.1. It was developed for Lua 5.0.
Copas was designed and implemented by André Carregal and Javier Guerra as part of the Kepler Project (http://www.keplerproject.org) which holds its copyright. Copas development had significative contributions from Diego Nehab, Mike Pall, David Burgess and Leonardo Godinho.
copas.addthread
added
copas.step
added
For more information please contact us (info-NO-SPAM-THANKS@keplerproject.org) Comments are welcome!
You can also reach other Kepler developers and users on the Kepler Project mailing list (http://luaforge.net/mail/).
Copas is free software and uses the same license as Lua 5.
Copas is free software: it can be used for both academic and commercial purposes at absolutely no cost. There are no royalties or GNU-like ``copyleft'' restrictions. Copas qualifies as Open Source (http://www.opensource.org/docs/definition.html) software. Its licenses are compatible with GPL (http://www.gnu.org/licenses/gpl.html). Copas is not in the public domain and the Kepler Project (http://www.keplerproject.org) keep its copyright. The legal details are below.
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 © 2006 The Kepler Project.
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.
~~~~~