LuaSearch - Navigate Lua Module Documentation


NAME

copas - dispatcher based on coroutines for TCP/IP servers (Copas)


OVERVIEW

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.


DOWNLOAD

Copas can be downloaded from its LuaForge (http://luaforge.net/frs/) page.

CVS access is also available from LuaForge.


DEPENDENCIES

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).


INSTALLING

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.


INTRODUCTION

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.

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
    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.

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)
    -- 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.

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


REFERENCE

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
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.

timeout
is the timeout for blocking I/O in seconds.

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.

Data Exchange Functions

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.


VERSION

Current version is 1.1. It was developed for Lua 5.0.


CREDITS

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.


HISTORY

``1.1'' [20/Sep/2006]
copas.addthread added

``1.0'' [17/May/2005] (http://www.keplerproject.org/copas/1.0)
copas.step added

``1.0'' Beta [17/Feb/2005]
First public version


CONTACT

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/).


LICENSE

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.

~~~~~