From 9db0a1b8d4539fe5f7aa678e0ef8fb634fb48bf7 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sun, 15 Jul 2012 19:58:28 +0200 Subject: add more libraries --- .gitignore | 9 +- Makefile.in | 112 +++---- README.markdown | 226 ++++++++++++++ bin/lem-repl | 29 ++ bin/lem.c | 389 ++++++++++++++++++++++++ bin/libev.c | 20 ++ bin/lua.c | 52 ++++ configure.ac | 17 +- include/lem.h | 63 ++++ include/streams.h | 52 ++++ lem-repl | 29 -- lem.c | 390 ------------------------ lem.h | 63 ---- lem/hathaway.lua | 385 ++++++++++++++++++++++++ lem/http.lua | 93 ++++++ lem/http/core.c | 366 ++++++++++++++++++++++ lem/repl.lua | 117 ++++++++ lem/streams.lua | 50 +++ lem/streams/core.c | 258 ++++++++++++++++ lem/streams/parsers.c | 175 +++++++++++ lem/streams/queue.lua | 78 +++++ lem/streams/sendfile.c | 120 ++++++++ lem/streams/server.c | 298 ++++++++++++++++++ lem/streams/stream.c | 802 +++++++++++++++++++++++++++++++++++++++++++++++++ lem/streams/tcp.c | 251 ++++++++++++++++ lem/utils.c | 330 ++++++++++++++++++++ libev.c | 20 -- lua.c | 52 ---- lua/luaconf.h.in | 4 +- repl.lua | 117 -------- test.lua | 51 ---- test/ctest.lua | 48 +++ test/htest.lua | 148 +++++++++ test/httptest.lua | 58 ++++ test/multiplexing.lua | 79 +++++ test/sleep.lua | 46 +++ test/stest.lua | 64 ++++ test/test.lua | 48 +++ test/test2.lua | 40 +++ test2.lua | 43 --- utils.c | 330 -------------------- 41 files changed, 4759 insertions(+), 1163 deletions(-) create mode 100755 bin/lem-repl create mode 100644 bin/lem.c create mode 100644 bin/libev.c create mode 100644 bin/lua.c create mode 100644 include/lem.h create mode 100644 include/streams.h delete mode 100755 lem-repl delete mode 100644 lem.c delete mode 100644 lem.h create mode 100644 lem/hathaway.lua create mode 100644 lem/http.lua create mode 100644 lem/http/core.c create mode 100644 lem/repl.lua create mode 100644 lem/streams.lua create mode 100644 lem/streams/core.c create mode 100644 lem/streams/parsers.c create mode 100644 lem/streams/queue.lua create mode 100644 lem/streams/sendfile.c create mode 100644 lem/streams/server.c create mode 100644 lem/streams/stream.c create mode 100644 lem/streams/tcp.c create mode 100644 lem/utils.c delete mode 100644 libev.c delete mode 100644 lua.c delete mode 100644 repl.lua delete mode 100755 test.lua create mode 100755 test/ctest.lua create mode 100755 test/htest.lua create mode 100755 test/httptest.lua create mode 100755 test/multiplexing.lua create mode 100755 test/sleep.lua create mode 100755 test/stest.lua create mode 100755 test/test.lua create mode 100755 test/test2.lua delete mode 100755 test2.lua delete mode 100644 utils.c diff --git a/.gitignore b/.gitignore index 13aceab..eb799ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,12 @@ *.o -*.lo *.so -config.status -config.log -Makefile +bin/lem lua/luaconf.h libev/ev-config.h lem.pc -lem +config.status +config.log +Makefile aclocal.m4 autom4te.cache *.pkg.tar.* diff --git a/Makefile.in b/Makefile.in index e374abe..6caee46 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,6 +1,6 @@ CC = @CC@ CFLAGS ?= @CFLAGS@ -CFLAGS += -I. @CPPFLAGS@ +CFLAGS += -Iinclude @CPPFLAGS@ SHARED = @SHARED@ PKG_CONFIG = @PKG_CONFIG@ @@ -8,14 +8,6 @@ STRIP = @STRIP@ INSTALL = @INSTALL@ SED = @SED@ -headers = @headers@ -programs = lem utils.so -scripts = repl.lua lem-repl - -objects = @objects@ - -LIBS = @LIBS@ - prefix = @prefix@ exec_prefix = @exec_prefix@ bindir = @bindir@ @@ -26,8 +18,22 @@ pkgconfigdir = @pkgconfigdir@ lmoddir = @lmoddir@ cmoddir = @cmoddir@ -installdirs = $(bindir) $(includedir)/lem \ - $(lmoddir)/lem $(cmoddir)/lem $(pkgconfigdir) +objects = @objects@ +LIBS = @LIBS@ + +headers = @headers@ + +llibs = \ + lem/repl.lua \ + lem/streams.lua \ + lem/streams/queue.lua \ + lem/http.lua \ + lem/hathaway.lua + +clibs = \ + lem/utils.so \ + lem/streams/core.so \ + lem/http/core.so ifdef V E=@\# @@ -37,19 +43,18 @@ E=@echo Q=@ endif -.PHONY: all strip install clean $(installdirs) -.PRECIOUS: %.lo +.PHONY: all strip install clean -all: $(programs) lem.pc +all: bin/lem lem.pc $(clibs) -libev.o: CFLAGS += -w -lua.o: lua/luaconf.h +bin/libev.o: CFLAGS += -w +bin/lua.o: lua/luaconf.h %.o: %.c $E ' CC $@' $Q$(CC) $(CFLAGS) -c $< -o $@ -lem: $(objects) +bin/lem: $(objects) $E ' LD $@' $Q$(CC) $^ -o $@ -rdynamic $(LDFLAGS) $(LIBS) @@ -60,11 +65,11 @@ lem: $(objects) lua/luaconf.h: lua/luaconf.h.in $E ' SED > $@' $Q$(SED) \ - -e 's|@path@|$(lua_path)|' \ - -e 's|@cpath@|$(lua_cpath)|' \ + -e 's|@lmoddir[@]|$(lmoddir)|' \ + -e 's|@cmoddir[@]|$(cmoddir)|' \ $< > $@ -%.pc: %.pc.in +lem.pc: lem.pc.in $E ' SED > $@' $Q$(SED) \ -e 's|@lmoddir[@]|$(lmoddir)|' \ @@ -76,37 +81,40 @@ lua/luaconf.h: lua/luaconf.h.in $E ' STRIP $<' $Q$(STRIP) $(STRIP_ARGS) $< -strip: $(programs:%=%-strip) - -$(installdirs): - $E ' INSTALL -d $@' - $Q$(INSTALL) -dm755 $(DESTDIR)$@ - -lem-install: lem | $(bindir) - $E ' INSTALL $<' - $Q$(INSTALL) $< $(DESTDIR)$(bindir)/$< - -lem-repl-install: lem-repl | $(bindir) - $E ' INSTALL $<' - $Q$(INSTALL) $< $(DESTDIR)$(bindir)/$< - -%.h-install: %.h | $(includedir)/lem - $E ' INSTALL $(notdir $<)' - $Q$(INSTALL) -m644 $< $(DESTDIR)$(includedir)/lem/$(notdir $<) - -%.lua-install: %.lua | $(lmoddir)/lem - $E ' INSTALL $<' - $Q$(INSTALL) -m644 $< $(DESTDIR)$(lmoddir)/lem/$< - -%.so-install: %.so | $(cmoddir)/lem - $E ' INSTALL $<' - $Q$(INSTALL) $< $(DESTDIR)$(cmoddir)/lem/$< - -%.pc-install: %.pc | $(pkgconfigdir) - $E ' INSTALL $<' - $Q$(INSTALL) -m644 $< $(DESTDIR)$(pkgconfigdir) - -install: lem.pc-install $(headers:%=%-install) $(programs:%=%-install) $(scripts:%=%-install) +strip: bin/lem-strip $(clibs:%=%-strip) + +$(DESTDIR)$(bindir)/%: bin/% + $E ' INSTALL $@' + $Q$(INSTALL) -d $(dir $@) + $Q$(INSTALL) -m 755 $< $@ + +$(DESTDIR)$(includedir)/lem/%: lua/% + $E ' INSTALL $@' + $Q$(INSTALL) -d $(dir $@) + $Q$(INSTALL) -m 644 $< $@ + +$(DESTDIR)$(includedir)/lem/%: libev/% + $E ' INSTALL $@' + $Q$(INSTALL) -d $(dir $@) + $Q$(INSTALL) -m 644 $< $@ + +$(DESTDIR)$(includedir)/lem/%: include/% + $E ' INSTALL $@' + $Q$(INSTALL) -d $(dir $@) + $Q$(INSTALL) -m 644 $< $@ + +$(DESTDIR)$(lmoddir)/% $(DESTDIR)$(cmoddir)/% $(DESTDIR)$(pkgconfigdir)/%: % + $E ' INSTALL $@' + $Q$(INSTALL) -d $(dir $@) + $Q$(INSTALL) -m 644 $< $@ + +install: \ + $(DESTDIR)$(pkgconfigdir)/lem.pc \ + $(DESTDIR)$(bindir)/lem \ + $(DESTDIR)$(bindir)/lem-repl \ + $(headers:%=$(DESTDIR)$(includedir)/lem/%) \ + $(llibs:%=$(DESTDIR)$(lmoddir)/%) \ + $(clibs:%=$(DESTDIR)$(cmoddir)/%) clean: - rm -f lem lua/luaconf.h lem.pc *.o *.so + rm -f bin/lem bin/*.o $(clibs) lua/luaconf.h lem.pc diff --git a/README.markdown b/README.markdown index f01fa6e..a7488a0 100644 --- a/README.markdown +++ b/README.markdown @@ -158,6 +158,232 @@ This sets `utils` to a table with the following functions. been scheduled to run. +The Stream Library +------------------ + +Import the module using something like + + local streams = require 'lem.streams' + +This sets `streams` to a table with the following functions. + +* __streams.open(path, [mode])__ + + Opens a given `path` and returns a new stream object. + This should be a path to a device, named pipe or named socket. + The function does work on regular files, but non-blocking IO doesn't, + so you might as well use `io.open()`. + This is a property of POSIX, sorry. + + The `mode` argument supports the same strings as `io.open()`. + If `mode` is not specified it defaults to `"r"`. + +* __streams.popen(cmd, [mode])__ + + This function works just like `io.popen()` except it returns a + stream object (which can be read or written to with blocking the + main loop). + +* __streams.tcp_connect(address, port)__ + + This function connects to the specified address and port + over TCP and if successful returns a new stream object. + +* __streams.tcp_listen(address, port)__ + + This function creates a new server object which can be used to receive + incoming TCP connections on the specified address and port. + +* __streams.sendfile(path)__ + + This function opens a file and returns a new object to be used by the + `streams:sendfile()` method. The path should point to a regular file or + `streams:sendfile()` will fail. + +The above functions return either a new stream object, a new server object, +a new sendfile object or `nil` and an error message indicating what went wrong. +The metatable of those objects can be found under __streams.Stream__, +__streams.Server__ and __streams.SendFile__ respectively. + +The following methods are available on streams. + +* __stream:closed()__ + + Returns `true` when the stream is closed, `false` otherwise. + +* __stream:busy()__ + + Returns `true` when another coroutine is waiting for IO on this stream, + `false` otherwise. + +* __stream:close()__ + + Closes the stream. If the stream is busy, this also interrupts the IO + action on the stream. + + Returns `true` on succes or otherwise `nil` followed by an error message. + If the stream is already closed the error message will be `'already closed'`. + +* __stream:interrupt()__ + + Interrupt any coroutine waiting for IO on the stream. + + Returns `true` on success and `nil, 'not busy'` if no coroutine is waiting + for connections on the server object. + +* __stream:read([mode])__ + + Read data from the stream. The `mode` argument can be one of the following: + + - a number: read the given number of bytes from the stream + - "\*a": read all data from stream until the stream is closed + - "\*l": read a line (read up to and including the next '\n' character) + + If there is not enough data immediately available the current coroutine will + be suspended until there is. + + However if the method is called without the mode argument, it will return + what is immediately available on the stream (up to a certain size limit). + Only if there is no data immediately available will the current coroutine + be suspended until there is. + + On success this method will return the data read from stream in a Lua string. + Otherwise it will return `nil` followed by an error message. + If another coroutine is waiting for IO on the stream the error message + will be `'busy'`. + If the stream was interrupted (eg. by another coroutine calling + `stream:interrupt()`, or `stream:close()`) the error message will be + `'interrupted'`. + If the stream is closed either before calling the method or closed + from the other end during the read the error message will be `'closed'`. + +* __stream:write(data)__ + + Write the given data, which must be a Lua string, to the stream. + If the data cannot be immediately written to the stream the current + coroutine will be suspended until all data is written. + + Returns `true` on success or otherwise `nil` followed by an error message. + If another coroutine is waiting for IO on the stream the error message + will be `'busy'`. + If the stream was interrupted (eg. by another coroutine calling + `stream:interrupt()`, or `stream:close()`) the error message will be + `'interrupted'`. + If the stream is closed either before calling the method or closed + from the other end during the write the error message will be `'closed'`. + +* __stream:sendfile(file, [offset])__ + + Write the given file to the stream. This is more effektive than reading + from a file and writing to the socket since the data doesn't have to go + through userspace. It only works on socket streams though. + + The file must be a sendfile object as returned by `streams.sendfile()`. + + If the offset argument is given the transfer will begin at the given + offset into the file. + + Returns `true` on success or otherwise `nil` followed by an error message. + If another coroutine is waiting for IO on the stream the error message + will be `'busy'`. + If the stream was interrupted (eg. by another coroutine calling + `stream:interrupt()`, or `stream:close()`) the error message will be + `'interrupted'`. + If the stream is closed either before calling the method or closed + from the other end during the write the error message will be `'closed'`. + If the file is closed the error message will be `'file closed'`. + +* __stream:cork()__ + + Sets the `TCP_CORK` attribute on the stream. This will of course fail + on streams which are not TCP connections. + + Returns `true` on success or otherwise `nil` followed by an error message. + If another coroutine is waiting for IO on the stream the error message + will be `'busy'`. + If the stream is closed the error message will be `'closed'`. + +* __stream:uncork()__ + + Removes the `TCP_CORK` attribute on the stream. This will of course fail + on streams which are not TCP connections. + + Returns `true` on success or otherwise `nil` followed by an error message. + If another coroutine is waiting for IO on the stream the error message + will be `'busy'`. + If the stream is closed the error message will be `'closed'`. + + +The following methods are available on server objects. + +* __server:closed()__ + + Returns `true` if the server is closed, `false` otherwise. + +* __server:busy()__ + + Returns `true` if another coroutine is listening on the server object, + `false` otherwise. + +* __server:close()__ + + Closes the server object. If another coroutine is waiting for connections + on the object it will be interrupted. + + Returns `true` on succes or otherwise `nil` followed by an error message. + If the server is already closed the error message will be `'already closed'`. + +* __server:interrupt()__ + + Interrupt any coroutine waiting for new connections on the server object. + + Returns `true` on success and `nil, 'not busy'` if no coroutine is waiting + for connections on the server object. + +* __server:accept()__ + + This method will get a stream object of a new incoming connection. + If there are no incoming connections immediately available, + the current coroutine will be suspended until there is. + + Returns a new stream object on succes or otherwise `nil` followed by an + error message. + If another coroutine is already waiting for new connections on the server + object the error message will be `'busy'`. + If the server was interrupted (eg. by another coroutine calling + `server:interrupt()`, or `server:close()`) the error message will be + `'interrupted'`. + If the server object is closed the error message will be `'closed'`. + +* __server:autospawn(handler)__ + + This method will suspend the currently running coroutine while + listening for new connections to the server object. + When a new client connects it will automatically spawn a new + coroutine running the `handler` function with a stream object + for the new connection as first argument. + + If an error occurs the method will return `nil` followed by an error message. + If another coroutine is already waiting for new connections on the server + object the error message will be `'busy'`. + If the server was interrupted (eg. by another coroutine calling + `server:interrupt()`, or `server:close()`) the error message will be + `'interrupted'`. + If the server object is closed the error message will be `'closed'`. + +The following methods are available on sendfile objects. + +* __file:close()__ + + Closes the gives file. + + Returns `true` on success or otherwise `nil` followed by an error message. + +* __file:size()__ + + Returns the size of the file. + + License ------- diff --git a/bin/lem-repl b/bin/lem-repl new file mode 100755 index 0000000..bf53113 --- /dev/null +++ b/bin/lem-repl @@ -0,0 +1,29 @@ +#!/usr/bin/env lem +-- +-- This file is part of LEM, a Lua Event Machine. +-- Copyright 2011-2012 Emil Renner Berthing +-- +-- LEM is free software: you can redistribute it and/or +-- modify it under the terms of the GNU General Public License as +-- published by the Free Software Foundation, either version 3 of +-- the License, or (at your option) any later version. +-- +-- LEM is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with LEM. If not, see . +-- + +local streams = require 'lem.streams' +local repl = require 'lem.repl' + +streams.stdout:write([[ +A Lua Event Machine 0.3 Copyright 2011-2012 Emil Renner Berthing +]]) + +repl.go('stdin', streams.stdin, streams.stdout) + +-- vim: syntax=lua ts=2 sw=2 noet: diff --git a/bin/lem.c b/bin/lem.c new file mode 100644 index 0000000..441a576 --- /dev/null +++ b/bin/lem.c @@ -0,0 +1,389 @@ +/* + * This file is part of LEM, a Lua Event Machine. + * Copyright 2011-2012 Emil Renner Berthing + * + * LEM is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * LEM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LEM. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#if EV_USE_KQUEUE +#define LEM_LOOPFLAGS EVBACKEND_KQUEUE +#else +#define LEM_LOOPFLAGS 0 +#endif + +#ifdef NDEBUG +#define lem_log_error(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__) +#else +#define lem_log_error lem_debug +#endif + +#ifndef LUA_OK +#define LUA_OK 0 +#endif + +#define LEM_INITIAL_QUEUESIZE 8 /* this must be a power of 2 */ +#define LEM_THREADTABLE 1 + +struct lem_runqueue_slot { + lua_State *T; + int nargs; +}; + +struct lem_runqueue { + struct ev_idle w; + unsigned long first; + unsigned long last; + unsigned long mask; + struct lem_runqueue_slot *queue; +}; + +#if EV_MULTIPLICITY +struct ev_loop *lem_loop; +#endif +static lua_State *L; +static struct lem_runqueue rq; +static int exit_status = EXIT_SUCCESS; + +static void +oom(void) +{ + static const char e[] = "out of memory\n"; + + fprintf(stderr, e); +#ifdef SIGQUIT + raise(SIGQUIT); +#endif + _Exit(EXIT_FAILURE); +} + +void * +lem_xmalloc(size_t size) +{ + void *p; + + p = malloc(size); + if (p == NULL) + oom(); + + return p; +} + +static int +ignore_sigpipe(void) +{ + struct sigaction act; + + if (sigaction(SIGPIPE, NULL, &act)) { + lem_log_error("error getting signal action: %s", + strerror(errno)); + return -1; + } + + act.sa_handler = SIG_IGN; + + if (sigaction(SIGPIPE, &act, NULL)) { + lem_log_error("error setting signal action: %s", + strerror(errno)); + return -1; + } + + return 0; +} + +lua_State * +lem_newthread(void) +{ + lua_State *T = lua_newthread(L); + + if (T == NULL) + oom(); + + /* set thread_table[T] = true */ + lua_pushboolean(L, 1); + lua_rawset(L, LEM_THREADTABLE); + + return T; +} + +void +lem_forgetthread(lua_State *T) +{ + /* set thread_table[T] = nil */ + lua_pushthread(T); + lua_xmove(T, L, 1); + lua_pushnil(L); + lua_rawset(L, LEM_THREADTABLE); +} + +void +lem_sethandler(lua_State *T) +{ + /* push T to L */ + lua_pushthread(T); + lua_xmove(T, L, 1); + /* move handler to L */ + lua_xmove(T, L, 1); + lua_rawset(L, LEM_THREADTABLE); +} + +void +lem_exit(int status) +{ + exit_status = status; + ev_unloop(LEM_ EVUNLOOP_ALL); +} + +void +lem_queue(lua_State *T, int nargs) +{ + struct lem_runqueue_slot *slot; + + assert(T != NULL); + lem_debug("enqueueing thread with %d argument%s", + nargs, nargs == 1 ? "" : "s"); + + if (rq.first == rq.last) + ev_idle_start(LEM_ &rq.w); + + slot = &rq.queue[rq.last]; + slot->T = T; + slot->nargs = nargs; + + rq.last++; + rq.last &= rq.mask; + if (rq.first == rq.last) { + unsigned long i; + unsigned long j; + struct lem_runqueue_slot *new_queue; + + lem_debug("expanding queue to %lu slots", 2*(rq.mask + 1)); + new_queue = lem_xmalloc(2*(rq.mask + 1) + * sizeof(struct lem_runqueue_slot)); + + i = 0; + j = rq.first; + do { + new_queue[i] = rq.queue[j]; + + i++; + j++; + j &= rq.mask; + } while (j != rq.first); + + free(rq.queue); + rq.queue = new_queue; + rq.first = 0; + rq.last = i; + rq.mask = 2*rq.mask + 1; + } +} + +static void +runqueue_pop(EV_P_ struct ev_idle *w, int revents) +{ + struct lem_runqueue_slot *slot; + lua_State *T; + int nargs; + + (void)revents; + + if (rq.first == rq.last) { /* queue is empty */ + lem_debug("runqueue is empty"); + ev_idle_stop(EV_A_ w); + return; + } + + lem_debug("running thread..."); + + slot = &rq.queue[rq.first]; + T = slot->T; + nargs = slot->nargs; + + rq.first++; + rq.first &= rq.mask; + + /* run Lua thread */ +#if LUA_VERSION_NUM >= 502 + switch (lua_resume(T, NULL, nargs)) { +#else + switch (lua_resume(T, nargs)) { +#endif + case LUA_OK: /* thread finished successfully */ + lem_debug("thread finished successfully"); + lem_forgetthread(T); + return; + + case LUA_YIELD: /* thread yielded */ + lem_debug("thread yielded"); + return; + + case LUA_ERRERR: /* error running error handler */ + lem_debug("thread errored while running error handler"); +#if LUA_VERSION_NUM >= 502 + case LUA_ERRGCMM: + lem_debug("error in __gc metamethod"); +#endif + case LUA_ERRRUN: /* runtime error */ + lem_debug("thread errored"); + + /* push T to L */ + lua_pushthread(T); + lua_xmove(T, L, 1); + + /* push thread_table[T] */ + lua_pushvalue(L, -1); + lua_rawget(L, LEM_THREADTABLE); + if (lua_type(L, -1) == LUA_TFUNCTION) { + lua_State *S = lem_newthread(); + + /* move error handler to S */ + lua_xmove(L, S, 1); + /* move error message to S */ + lua_xmove(T, S, 1); + /* queue thread */ + lem_debug("queueing error handler: %s", + lua_tostring(S, -1)); + lem_queue(S, 1); + + /* thread_table[T] = nil */ + lua_pushnil(L); + lua_rawset(L, LEM_THREADTABLE); + return; + } + lem_debug("no error handler"); + /* move error message to L */ + lua_xmove(T, L, 1); + break; + + case LUA_ERRMEM: /* out of memory */ + oom(); + + default: /* this shouldn't happen */ + lem_debug("lua_resume: unknown error"); + lua_pushliteral(L, "unknown error"); + break; + } + lem_exit(EXIT_FAILURE); +} + +static int +queue_file(int argc, char *argv[], int fidx) +{ + lua_State *T = lem_newthread(); + int i; + + switch (luaL_loadfile(T, argv[fidx])) { + case LUA_OK: /* success */ + break; + + case LUA_ERRMEM: + oom(); + + default: + lem_log_error("%s", lua_tostring(T, 1)); + return -1; + } + + lua_createtable(T, argc, 0); + for (i = 0; i < argc; i++) { + lua_pushstring(T, argv[i]); + lua_rawseti(T, -2, i - fidx); + } + lua_setglobal(T, "arg"); + + lem_queue(T, 0); + return 0; +} + +int +main(int argc, char *argv[]) +{ + if (argc < 2) { + lem_log_error("I need a file.."); + return EXIT_FAILURE; + } + +#if EV_MULTIPLICITY + lem_loop = ev_default_loop(LEM_LOOPFLAGS); + if (lem_loop == NULL) { +#else + if (!ev_default_loop(LEM_LOOPFLAGS)) { +#endif + lem_log_error("Error initializing event loop"); + return EXIT_FAILURE; + } + + if (ignore_sigpipe()) + goto error; + + /* create main Lua state */ + L = luaL_newstate(); + if (L == NULL) { + lem_log_error("Error initializing Lua state"); + goto error; + } + luaL_openlibs(L); + + /* push thread table */ + lua_newtable(L); + + /* initialize runqueue */ + ev_idle_init(&rq.w, runqueue_pop); + ev_idle_start(LEM_ &rq.w); + rq.queue = lem_xmalloc(LEM_INITIAL_QUEUESIZE + * sizeof(struct lem_runqueue_slot)); + rq.first = rq.last = 0; + rq.mask = LEM_INITIAL_QUEUESIZE - 1; + + /* load file */ + if (queue_file(argc, argv, 1)) + goto error; + + /* start the mainloop */ + ev_loop(LEM_ 0); + lem_debug("event loop exited"); + + /* if there is an error message left on L print it */ + if (lua_type(L, -1) == LUA_TSTRING) + lem_log_error("%s", lua_tostring(L, -1)); + + /* shutdown Lua */ + lua_close(L); + + /* free runqueue */ + free(rq.queue); + + /* close default loop */ + ev_default_destroy(); + lem_debug("Bye %s", exit_status == EXIT_SUCCESS ? "o/" : ":("); + return exit_status; + +error: + if (L) + lua_close(L); + if (rq.queue) + free(rq.queue); + ev_default_destroy(); + return EXIT_FAILURE; +} diff --git a/bin/libev.c b/bin/libev.c new file mode 100644 index 0000000..84ecca9 --- /dev/null +++ b/bin/libev.c @@ -0,0 +1,20 @@ +/* + * This file is part of LEM, a Lua Event Machine. + * Copyright 2011-2012 Emil Renner Berthing + * + * LEM is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * LEM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LEM. If not, see . + */ + +#include "../libev/ev-config.h" +#include "../libev/ev.c" diff --git a/bin/lua.c b/bin/lua.c new file mode 100644 index 0000000..ec5c936 --- /dev/null +++ b/bin/lua.c @@ -0,0 +1,52 @@ +/* setup for luaconf.h */ +#define LUA_CORE +#define LUA_LIB +#define ltable_c +#define lvm_c +#include + +/* do not export internal symbols */ +#undef LUAI_FUNC +#undef LUAI_DDEC +#undef LUAI_DDEF +#define LUAI_FUNC static +#define LUAI_DDEC static +#define LUAI_DDEF static + +/* core */ +#include "../lua/lapi.c" +#include "../lua/lcode.c" +#include "../lua/lctype.c" +#include "../lua/ldebug.c" +#include "../lua/ldo.c" +#include "../lua/ldump.c" +#include "../lua/lfunc.c" +#include "../lua/lgc.c" +#include "../lua/llex.c" +#include "../lua/lmem.c" +#include "../lua/lobject.c" +#include "../lua/lopcodes.c" +#include "../lua/lparser.c" +#include "../lua/lstate.c" +#include "../lua/lstring.c" +#include "../lua/ltable.c" +#include "../lua/ltm.c" +#include "../lua/lundump.c" +#include "../lua/lvm.c" +#include "../lua/lzio.c" + +/* auxiliary library */ +#include "../lua/lauxlib.c" + +/* standard library */ +#include "../lua/lbaselib.c" +#include "../lua/lbitlib.c" +#include "../lua/lcorolib.c" +#include "../lua/ldblib.c" +#include "../lua/liolib.c" +#include "../lua/lmathlib.c" +#include "../lua/loadlib.c" +#include "../lua/loslib.c" +#include "../lua/lstrlib.c" +#include "../lua/ltablib.c" +#include "../lua/linit.c" diff --git a/configure.ac b/configure.ac index a62d657..7bd1c9f 100644 --- a/configure.ac +++ b/configure.ac @@ -5,9 +5,9 @@ builtin_lua_version='5.2' AC_LANG(C) AC_CONFIG_HEADERS([libev/ev-config.h:ev-config.h.in]) -AC_SUBST([headers], ['lem.h']) -AC_SUBST([objects], ['lem.o']) -AC_SUBST([SHARED]) +AC_SUBST([headers], ['lem.h streams.h']) +AC_SUBST([objects], ['bin/lem.o']) +AC_SUBST([SHARED], ['-shared']) AC_ARG_WITH([debug], [AS_HELP_STRING([--with-debug], @@ -49,8 +49,7 @@ AS_CASE(["x$target_os"], [xdarwin*], [ac_cv_func_kqueue=no] # kqueue seems to be broken on OSX [SHARED='-dynamiclib -Wl,-undefined,dynamic_lookup'] - [STRIP="$STRIP -x"], - [SHARED='-shared']) + [STRIP="$STRIP -x"]) # Checks for Lua. AS_CASE(["x$with_lua"], @@ -90,8 +89,8 @@ AS_CASE(["x$with_lua"], AS_IF([test "x$with_lua" = 'xbuiltin'], [AC_CHECK_LIB([m], [sin])] [AC_SEARCH_LIBS([dlopen], [dl])] - [objects="lua.o $objects"] - [headers="lua/luaconf.h lua/lua.h lua/lauxlib.h $headers"] + [objects="bin/lua.o $objects"] + [headers="luaconf.h lua.h lauxlib.h $headers"] [CPPFLAGS="$CPPFLAGS -Ilua"] [AS_IF([test "x$lmoddir" = 'x'], [lmoddir="\${datarootdir}/lua/$builtin_lua_version"])] [AS_IF([test "x$cmoddir" = 'x'], [cmoddir="\${libdir}/lua/$builtin_lua_version"])]) @@ -109,8 +108,8 @@ AC_CHECK_HEADERS([sys/eventfd.h sys/epoll.h sys/event.h]) #])], # [echo "conftest returned: `cat conftest.out`"],[])]) -objects="libev.o $objects" -headers="libev/ev-config.h libev/ev.h $headers" +objects="bin/libev.o $objects" +headers="ev-config.h ev.h $headers" CPPFLAGS="$CPPFLAGS -Ilibev" AS_IF([test "x$ac_cv_header_sys_eventfd_h" = 'xyes'], diff --git a/include/lem.h b/include/lem.h new file mode 100644 index 0000000..9607fe6 --- /dev/null +++ b/include/lem.h @@ -0,0 +1,63 @@ +/* + * This file is part of LEM, a Lua Event Machine. + * Copyright 2011 Emil Renner Berthing + * + * LEM is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * LEM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LEM. If not, see . + */ + +#ifndef _LEM_H +#define _LEM_H + +#include +#include +#include + +/* Support gcc's __FUNCTION__ for people using other compilers */ +#if !defined(__GNUC__) && !defined(__FUNCTION__) +# define __FUNCTION__ __func__ /* C99 */ +#endif + +#ifdef NDEBUG +#define lem_debug(...) +#else +#define lem_debug(fmt, ...) do { \ + printf("%s (%s:%u): " fmt "\n", \ + __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__); \ + fflush(stdout); } while (0) +#endif + +#if EV_MINPRI == EV_MAXPRI +# undef ev_priority +# undef ev_set_priority +# define ev_priority(pri) +# define ev_set_priority(ev, pri) +#endif + +#if EV_MULTIPLICITY +extern struct ev_loop *lem_loop; +# define LEM lem_loop +# define LEM_ LEM, +#else +# define LEM +# define LEM_ +#endif + +void *lem_xmalloc(size_t size); +lua_State *lem_newthread(void); +void lem_forgetthread(lua_State *T); +void lem_sethandler(lua_State *T); +void lem_queue(lua_State *T, int nargs); +void lem_exit(int status); + +#endif diff --git a/include/streams.h b/include/streams.h new file mode 100644 index 0000000..e139138 --- /dev/null +++ b/include/streams.h @@ -0,0 +1,52 @@ +/* + * This file is part of LEM, a Lua Event Machine. + * Copyright 2011-2012 Emil Renner Berthing + * + * LEM is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * LEM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LEM. If not, see . + */ + +#ifndef _LEM_STREAMS_H +#define _LEM_STREAMS_H + +#include + +#define LEM_INPUTBUF_SIZE 1024 + +struct lem_inputbuf { + unsigned int start; + unsigned int end; + union { + void *p; + unsigned long u; + }; + int parts; + char buf[LEM_INPUTBUF_SIZE]; +}; + +enum lem_presult { + LEM_PMORE = 0, +}; + +enum lem_preason { + LEM_PCLOSED, + LEM_PERROR, +}; + +struct lem_parser { + void (*init)(lua_State *T, struct lem_inputbuf *b); + int (*process)(lua_State *T, struct lem_inputbuf *b); + int (*destroy)(lua_State *T, struct lem_inputbuf *b, enum lem_preason reason); +}; + +#endif diff --git a/lem-repl b/lem-repl deleted file mode 100755 index bf53113..0000000 --- a/lem-repl +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env lem --- --- This file is part of LEM, a Lua Event Machine. --- Copyright 2011-2012 Emil Renner Berthing --- --- LEM is free software: you can redistribute it and/or --- modify it under the terms of the GNU General Public License as --- published by the Free Software Foundation, either version 3 of --- the License, or (at your option) any later version. --- --- LEM is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with LEM. If not, see . --- - -local streams = require 'lem.streams' -local repl = require 'lem.repl' - -streams.stdout:write([[ -A Lua Event Machine 0.3 Copyright 2011-2012 Emil Renner Berthing -]]) - -repl.go('stdin', streams.stdin, streams.stdout) - --- vim: syntax=lua ts=2 sw=2 noet: diff --git a/lem.c b/lem.c deleted file mode 100644 index 13a9eda..0000000 --- a/lem.c +++ /dev/null @@ -1,390 +0,0 @@ -/* - * This file is part of LEM, a Lua Event Machine. - * Copyright 2011-2012 Emil Renner Berthing - * - * LEM is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * LEM is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LEM. If not, see . - */ - -#include -#include -#include -#include -#include -#include - -#include "lem.h" - -#include - -#if EV_USE_KQUEUE -#define LEM_LOOPFLAGS EVBACKEND_KQUEUE -#else -#define LEM_LOOPFLAGS 0 -#endif - -#ifdef NDEBUG -#define lem_log_error(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__) -#else -#define lem_log_error lem_debug -#endif - -#ifndef LUA_OK -#define LUA_OK 0 -#endif - -#define LEM_INITIAL_QUEUESIZE 8 /* this must be a power of 2 */ -#define LEM_THREADTABLE 1 - -struct lem_runqueue_slot { - lua_State *T; - int nargs; -}; - -struct lem_runqueue { - struct ev_idle w; - unsigned long first; - unsigned long last; - unsigned long mask; - struct lem_runqueue_slot *queue; -}; - -#if EV_MULTIPLICITY -struct ev_loop *lem_loop; -#endif -static lua_State *L; -static struct lem_runqueue rq; -static int exit_status = EXIT_SUCCESS; - -static void -oom(void) -{ - static const char e[] = "out of memory\n"; - - fprintf(stderr, e); -#ifdef SIGQUIT - raise(SIGQUIT); -#endif - _Exit(EXIT_FAILURE); -} - -void * -lem_xmalloc(size_t size) -{ - void *p; - - p = malloc(size); - if (p == NULL) - oom(); - - return p; -} - -static int -ignore_sigpipe(void) -{ - struct sigaction act; - - if (sigaction(SIGPIPE, NULL, &act)) { - lem_log_error("error getting signal action: %s", - strerror(errno)); - return -1; - } - - act.sa_handler = SIG_IGN; - - if (sigaction(SIGPIPE, &act, NULL)) { - lem_log_error("error setting signal action: %s", - strerror(errno)); - return -1; - } - - return 0; -} - -lua_State * -lem_newthread(void) -{ - lua_State *T = lua_newthread(L); - - if (T == NULL) - oom(); - - /* set thread_table[T] = true */ - lua_pushboolean(L, 1); - lua_rawset(L, LEM_THREADTABLE); - - return T; -} - -void -lem_forgetthread(lua_State *T) -{ - /* set thread_table[T] = nil */ - lua_pushthread(T); - lua_xmove(T, L, 1); - lua_pushnil(L); - lua_rawset(L, LEM_THREADTABLE); -} - -void -lem_sethandler(lua_State *T) -{ - /* push T to L */ - lua_pushthread(T); - lua_xmove(T, L, 1); - /* move handler to L */ - lua_xmove(T, L, 1); - lua_rawset(L, LEM_THREADTABLE); -} - -void -lem_exit(int status) -{ - exit_status = status; - ev_unloop(LEM_ EVUNLOOP_ALL); -} - -void -lem_queue(lua_State *T, int nargs) -{ - struct lem_runqueue_slot *slot; - - assert(T != NULL); - lem_debug("enqueueing thread with %d argument%s", - nargs, nargs == 1 ? "" : "s"); - - if (rq.first == rq.last) - ev_idle_start(LEM_ &rq.w); - - slot = &rq.queue[rq.last]; - slot->T = T; - slot->nargs = nargs; - - rq.last++; - rq.last &= rq.mask; - if (rq.first == rq.last) { - unsigned long i; - unsigned long j; - struct lem_runqueue_slot *new_queue; - - lem_debug("expanding queue to %lu slots", 2*(rq.mask + 1)); - new_queue = lem_xmalloc(2*(rq.mask + 1) - * sizeof(struct lem_runqueue_slot)); - - i = 0; - j = rq.first; - do { - new_queue[i] = rq.queue[j]; - - i++; - j++; - j &= rq.mask; - } while (j != rq.first); - - free(rq.queue); - rq.queue = new_queue; - rq.first = 0; - rq.last = i; - rq.mask = 2*rq.mask + 1; - } -} - -static void -runqueue_pop(EV_P_ struct ev_idle *w, int revents) -{ - struct lem_runqueue_slot *slot; - lua_State *T; - int nargs; - - (void)revents; - - if (rq.first == rq.last) { /* queue is empty */ - lem_debug("runqueue is empty"); - ev_idle_stop(EV_A_ w); - return; - } - - lem_debug("running thread..."); - - slot = &rq.queue[rq.first]; - T = slot->T; - nargs = slot->nargs; - - rq.first++; - rq.first &= rq.mask; - - /* run Lua thread */ -#if LUA_VERSION_NUM >= 502 - switch (lua_resume(T, NULL, nargs)) { -#else - switch (lua_resume(T, nargs)) { -#endif - case LUA_OK: /* thread finished successfully */ - lem_debug("thread finished successfully"); - lem_forgetthread(T); - return; - - case LUA_YIELD: /* thread yielded */ - lem_debug("thread yielded"); - return; - - case LUA_ERRERR: /* error running error handler */ - lem_debug("thread errored while running error handler"); -#if LUA_VERSION_NUM >= 502 - case LUA_ERRGCMM: - lem_debug("error in __gc metamethod"); -#endif - case LUA_ERRRUN: /* runtime error */ - lem_debug("thread errored"); - - /* push T to L */ - lua_pushthread(T); - lua_xmove(T, L, 1); - - /* push thread_table[T] */ - lua_pushvalue(L, -1); - lua_rawget(L, LEM_THREADTABLE); - if (lua_type(L, -1) == LUA_TFUNCTION) { - lua_State *S = lem_newthread(); - - /* move error handler to S */ - lua_xmove(L, S, 1); - /* move error message to S */ - lua_xmove(T, S, 1); - /* queue thread */ - lem_debug("queueing error handler: %s", - lua_tostring(S, -1)); - lem_queue(S, 1); - - /* thread_table[T] = nil */ - lua_pushnil(L); - lua_rawset(L, LEM_THREADTABLE); - return; - } - lem_debug("no error handler"); - /* move error message to L */ - lua_xmove(T, L, 1); - break; - - case LUA_ERRMEM: /* out of memory */ - oom(); - - default: /* this shouldn't happen */ - lem_debug("lua_resume: unknown error"); - lua_pushliteral(L, "unknown error"); - break; - } - lem_exit(EXIT_FAILURE); -} - -static int -queue_file(int argc, char *argv[], int fidx) -{ - lua_State *T = lem_newthread(); - int i; - - switch (luaL_loadfile(T, argv[fidx])) { - case LUA_OK: /* success */ - break; - - case LUA_ERRMEM: - oom(); - - default: - lem_log_error("%s", lua_tostring(T, 1)); - return -1; - } - - lua_createtable(T, argc, 0); - for (i = 0; i < argc; i++) { - lua_pushstring(T, argv[i]); - lua_rawseti(T, -2, i - fidx); - } - lua_setglobal(T, "arg"); - - lem_queue(T, 0); - return 0; -} - -int -main(int argc, char *argv[]) -{ - if (argc < 2) { - lem_log_error("I need a file.."); - return EXIT_FAILURE; - } - -#if EV_MULTIPLICITY - lem_loop = ev_default_loop(LEM_LOOPFLAGS); - if (lem_loop == NULL) { -#else - if (!ev_default_loop(LEM_LOOPFLAGS)) { -#endif - lem_log_error("Error initializing event loop"); - return EXIT_FAILURE; - } - - if (ignore_sigpipe()) - goto error; - - /* create main Lua state */ - L = luaL_newstate(); - if (L == NULL) { - lem_log_error("Error initializing Lua state"); - goto error; - } - luaL_openlibs(L); - - /* push thread table */ - lua_newtable(L); - - /* initialize runqueue */ - ev_idle_init(&rq.w, runqueue_pop); - ev_idle_start(LEM_ &rq.w); - rq.queue = lem_xmalloc(LEM_INITIAL_QUEUESIZE - * sizeof(struct lem_runqueue_slot)); - rq.first = rq.last = 0; - rq.mask = LEM_INITIAL_QUEUESIZE - 1; - - /* load file */ - if (queue_file(argc, argv, 1)) - goto error; - - /* start the mainloop */ - ev_loop(LEM_ 0); - lem_debug("event loop exited"); - - /* if there is an error message left on L print it */ - if (lua_type(L, -1) == LUA_TSTRING) - lem_log_error("%s", lua_tostring(L, -1)); - - /* shutdown Lua */ - lua_close(L); - - /* free runqueue */ - free(rq.queue); - - /* close default loop */ - ev_default_destroy(); - lem_debug("Bye %s", exit_status == EXIT_SUCCESS ? "o/" : ":("); - return exit_status; - -error: - if (L) - lua_close(L); - if (rq.queue) - free(rq.queue); - ev_default_destroy(); - return EXIT_FAILURE; -} diff --git a/lem.h b/lem.h deleted file mode 100644 index 9607fe6..0000000 --- a/lem.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * This file is part of LEM, a Lua Event Machine. - * Copyright 2011 Emil Renner Berthing - * - * LEM is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * LEM is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LEM. If not, see . - */ - -#ifndef _LEM_H -#define _LEM_H - -#include -#include -#include - -/* Support gcc's __FUNCTION__ for people using other compilers */ -#if !defined(__GNUC__) && !defined(__FUNCTION__) -# define __FUNCTION__ __func__ /* C99 */ -#endif - -#ifdef NDEBUG -#define lem_debug(...) -#else -#define lem_debug(fmt, ...) do { \ - printf("%s (%s:%u): " fmt "\n", \ - __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__); \ - fflush(stdout); } while (0) -#endif - -#if EV_MINPRI == EV_MAXPRI -# undef ev_priority -# undef ev_set_priority -# define ev_priority(pri) -# define ev_set_priority(ev, pri) -#endif - -#if EV_MULTIPLICITY -extern struct ev_loop *lem_loop; -# define LEM lem_loop -# define LEM_ LEM, -#else -# define LEM -# define LEM_ -#endif - -void *lem_xmalloc(size_t size); -lua_State *lem_newthread(void); -void lem_forgetthread(lua_State *T); -void lem_sethandler(lua_State *T); -void lem_queue(lua_State *T, int nargs); -void lem_exit(int status); - -#endif diff --git a/lem/hathaway.lua b/lem/hathaway.lua new file mode 100644 index 0000000..2497232 --- /dev/null +++ b/lem/hathaway.lua @@ -0,0 +1,385 @@ +-- +-- This file is part of LEM, a Lua Event Machine. +-- Copyright 2011-2012 Emil Renner Berthing +-- +-- LEM is free software: you can redistribute it and/or +-- modify it under the terms of the GNU General Public License as +-- published by the Free Software Foundation, either version 3 of +-- the License, or (at your option) any later version. +-- +-- LEM is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with LEM. If not, see . +-- + +local setmetatable = setmetatable +local tostring = tostring +local tonumber = tonumber +local pairs = pairs +local type = type +local date = os.date +local format = string.format +local concat = table.concat +local remove = table.remove + +local streams = require 'lem.streams' +require 'lem.http' + +local M = {} + +local status_string = { + [100] = '100 Continue', + [101] = '101 Switching Protocols', + [102] = '102 Processing', -- WebDAV + + [200] = '200 OK', + [201] = '201 Created', + [202] = '202 Accepted', + [203] = '203 Non-Authoritative Information', + [204] = '204 No Content', + [205] = '205 Reset Content', + [206] = '206 Partial Content', + [207] = '207 Multi-Status', -- WebDAV + + [300] = '300 Multiple Choices', + [301] = '301 Moved Permanently', + [302] = '302 Found', + [303] = '303 See Other', + [304] = '304 Not Modified', + [305] = '305 Use Proxy', + [306] = '306 Switch Proxy', + [307] = '307 Temporary Redirect', + + [400] = '400 Bad Request', + [401] = '401 Unauthorized', + [402] = '402 Payment Required', + [403] = '403 Forbidden', + [404] = '404 Not Found', + [405] = '405 Method Not Allowed', + -- ... + [417] = '417 Expectation Failed', + + [500] = '500 Internal Server Error', + [501] = '501 Not Implemented', + -- ... + [505] = '505 HTTP Version Not Supported', + -- ... +} +M.status_string = status_string + +function M.not_found(req, res) + if req.headers['Expect'] ~= '100-continue' then + req:body() + end + + res.status = 404 + res.headers['Content-Type'] = 'text/html; charset=UTF-8' + res:add([[ + + + +Not Found + + +

Not found

+ + +]]) +end + +do + local function htmlerror(num, text) + local str = format([[ + + + +%s + + +

%s

+ + +]], text, text) + return function(req, res) + res.status = num + res.headers['Content-Type'] = 'text/html; charset=UTF-8' + res.headers['Connection'] = 'close' + res:add(str) + end + end + + M.method_not_allowed = htmlerror(405, 'Method Not Allowed') + M.expectation_failed = htmlerror(417, 'Expectation Failed') + M.version_not_supported = htmlerror(505, 'HTTP Version Not Supported') +end + +function M.debug() end + +do + local lookup = {} + M.lookup = lookup + + function M.GET(uri, handler) + local path = lookup[uri] + if path then + path['HEAD'] = handler + path['GET'] = handler + else + path = { + ['HEAD'] = handler, + ['GET'] = handler, + } + lookup[uri] = path + end + end + + do + local function static_setter(method) + return function(uri, handler) + local path = lookup[uri] + if path then + path[method] = handler + else + lookup[uri] = { [method] = handler } + end + end + end + + M.POST = static_setter('POST') + M.PUT = static_setter('PUT') + M.DELETE = static_setter('DELETE') + end + + function M.GETM(pattern, handler) + local i = 1 + while true do + local entry = lookup[i] + if entry == nil then + lookup[i] = { pattern, + ['GET'] = handler, + ['HEAD'] = handler + } + break + end + if entry[1] == pattern then + entry['GET'] = handler + entry['HEAD'] = handler + break + end + i = i + 1 + end + end + + do + local function match_setter(method) + return function(pattern, handler) + local i = 1 + while true do + local entry = lookup[i] + if entry == nil then + lookup[i] = { pattern, [method] = handler } + break + end + if entry[1] == pattern then + entry[method] = handler + break + end + i = i + 1 + end + end + end + + M.POSTM = match_setter('POST') + M.PUTM = match_setter('PUT') + M.DELETEM = match_setter('DELETE') + end + + local Response = {} + Response.__index = Response + M.Response = Response + + function new_response(req) + local n = 0 + return setmetatable({ + headers = {}, + status = 200, + version = req.version, + add = function(self, ...) + n = n + 1 + self[n] = format(...) + end + }, Response) + end + + local function check_match(entry, req, res, ok, ...) + if not ok then return false end + local handler = entry[req.method] + if handler then + handler(req, res, ok, ...) + else + M.method_not_allowed(req, res) + end + return true + end + + local function handler(istream, ostream) + repeat + local req, err = istream:read('HTTPRequest') + if not req then M.debug(err) break end + local method, uri, version = req.method, req.uri, req.version + M.debug(format("%s %s HTTP/%s", method, uri, version)) + + req.ostream = ostream + local res = new_response(req) + + if version ~= '1.0' and version ~= '1.1' then + M.version_not_supported(req, res) + version = '1.1' + else + local expect = req.headers['Expect'] + if expect and expect ~= '100-continue' then + M.expectation_failed(req, res) + else + local path = lookup[uri] + if path then + local handler = path[method] + if handler then + handler(req, res) + else + M.method_not_allowed(req, res) + end + else + local i = 0 + repeat + i = i + 1 + local entry = lookup[i] + if not entry then + M.not_found(req, res) + break + end + until check_match(entry, req, res, uri:match(entry[1])) + end + end + end + + local headers = res.headers + local file, close = res.file, false + if type(file) == 'string' then + file, err = streams.sendfile(file) + if file then + close = true + else + M.debug(err) + res = new_response(req) + headers = res.headers + M.not_found(req, res) + end + end + + if res.status == 200 and #res == 0 and res.file == nil then + res.status = 204 + elseif headers['Content-Length'] == nil then + local len + if file then + len = file:size() + else + len = 0 + for i = 1, #res do + len = len + #res[i] + end + end + + headers['Content-Length'] = len + end + + if headers['Date'] == nil then + headers['Date'] = date('!%a, %d %b %Y %T GMT') + end + + if headers['Server'] == nil then + headers['Server'] = 'Hathaway/0.1 LEM/0.1' + end + + local robe, i = {}, 1 + do + local status = res.status + if type(status) == 'number' then + status = status_string[status] + end + + robe[1] = format('HTTP/%s %s\r\n', version, status) + end + + for k, v in pairs(headers) do + i = i + 1 + robe[i] = format('%s: %s\r\n', k, tostring(v)) + end + + i = i + 1 + robe[i] = '\r\n' + + local ok, err = ostream:cork() + if not ok then M.debug(err) break end + + local ok, err = ostream:write(concat(robe)) + if not ok then M.debug(err) break end + + if method ~= 'HEAD' then + if file then + ok, err = ostream:sendfile(file) + if close then file:close() end + else + ok, err = ostream:write(concat(res)) + end + if not ok then M.debug(err) break end + end + + local ok, err = ostream:uncork() + if not ok then M.debug(err) break end + + until version == '1.0' + or req.headers['Connection'] == 'close' + or headers['Connection'] == 'close' + + istream:close() + ostream:close() + end + + function M.Hathaway(address, port) + local server, err = streams.tcp4_listen(address, port) + if not server then M.debug(err) return nil, err end + + M.server = server + + local ok, err = server:autospawn(handler) + if not ok and err ~= 'interrupted' then + M.debug(err) + return nil, err + end + return true + end +end + +function M.import(env) + if not env then + env = _G + end + + env.GET = M.GET + env.POST = M.POST + env.PUT = M.PUT + env.DELETE = M.DELETE + env.GETM = M.GETM + env.POSTM = M.POSTM + env.PUTM = M.PUTM + env.DELETEM = M.DELETEM + env.Hathaway = M.Hathaway +end + +return M + +-- vim: ts=2 sw=2 noet: diff --git a/lem/http.lua b/lem/http.lua new file mode 100644 index 0000000..15f4125 --- /dev/null +++ b/lem/http.lua @@ -0,0 +1,93 @@ +-- +-- This file is part of LEM, a Lua Event Machine. +-- Copyright 2011-2012 Emil Renner Berthing +-- +-- LEM is free software: you can redistribute it and/or +-- modify it under the terms of the GNU General Public License as +-- published by the Free Software Foundation, either version 3 of +-- the License, or (at your option) any later version. +-- +-- LEM is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with LEM. If not, see . +-- + +local streams = require 'lem.streams' +local M = require 'lem.http.core' + +streams.parsers['HTTPRequest'] = M.HTTPRequest +M.HTTPRequest = nil +streams.parsers['HTTPResponse'] = M.HTTPResponse +M.HTTPResponse = nil + +local tonumber = tonumber +local concat = table.concat + +function M.Request:body() + local len, body = self.headers['Content-Length'], '' + if not len then return body end + + len = tonumber(len) + if len <= 0 then return body end + + if self.headers['Expect'] == '100-continue' then + local ok, err = self.ostream:send('HTTP/1.1 100 Continue\r\n\r\n') + if not ok then return nil, err end + end + + local err + body, err = self.istream:read(len) + if not body then return nil, err end + + return body +end + +function M.Response:body_chunked() + local istream = self.istream + local t, n = {}, 0 + local line, err + while true do + line, err = istream:read('*l') + if not line then return nil, err end + + local num = tonumber(line, 16) + if not num then return nil, 'expectation failed' end + if num == 0 then break end + + local data, err = istream:read(num) + if not data then return nil, err end + + n = n + 1 + t[n] = data + + line, err = istream:read('*l') + if not line then return nil, err end + end + + line, err = istream:read('*l') + if not line then return nil, err end + + return t +end + +function M.Response:body() + if self.headers['Transfer-Encoding'] == 'chunked' then + return concat(self:body_chunked()) + end + + local num = self.headers['Content-Length'] + if not num then return nil, 'no content length specified' end + + num = tonumber(num) + if not num then return nil, 'invalid content length' end + + return self.istream:read(num) +end + +return M + +-- vim: ts=2 sw=2 noet: diff --git a/lem/http/core.c b/lem/http/core.c new file mode 100644 index 0000000..ed1dce0 --- /dev/null +++ b/lem/http/core.c @@ -0,0 +1,366 @@ +/* + * This file is part of LEM, a Lua Event Machine. + * Copyright 2011-2012 Emil Renner Berthing + * + * LEM is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * LEM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LEM. If not, see . + */ + +#include + +#if !(LUA_VERSION_NUM >= 502) +#define lua_getuservalue lua_getfenv +#define lua_setuservalue lua_setfenv +#endif + +enum classes { + C_CTL, /* control characters */ + C_LF, /* \n */ + C_CR, /* \r */ + C_LWS, /* space or \t */ + C_TSPCL, /* tspecials */ + C_SLASH, /* / */ + C_COLON, /* : */ + C_DOT, /* . */ + C_NUM, /* 0-9 */ + C_H, /* H */ + C_T, /* T */ + C_P, /* P */ + C_ETC, /* the rest */ + C_MAX +}; + +/* + * This array maps the first 128 ASCII characters into character classes + * The remaining characters should be mapped to C_ETC + */ +static const unsigned char ascii_class[128] = { + C_CTL, C_CTL, C_CTL, C_CTL, C_CTL, C_CTL, C_CTL, C_CTL, + C_CTL, C_LWS, C_LF, C_CTL, C_CTL, C_CR, C_CTL, C_CTL, + C_CTL, C_CTL, C_CTL, C_CTL, C_CTL, C_CTL, C_CTL, C_CTL, + C_CTL, C_CTL, C_CTL, C_CTL, C_CTL, C_CTL, C_CTL, C_CTL, + + C_LWS, C_ETC, C_TSPCL, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, + C_TSPCL, C_TSPCL, C_ETC, C_ETC, C_TSPCL, C_ETC, C_DOT, C_SLASH, + C_NUM, C_NUM, C_NUM, C_NUM, C_NUM, C_NUM, C_NUM, C_NUM, + C_NUM, C_NUM, C_COLON, C_TSPCL, C_TSPCL, C_TSPCL, C_TSPCL, C_TSPCL, + + C_TSPCL, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, + C_H, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, + C_P, C_ETC, C_ETC, C_ETC, C_T, C_ETC, C_ETC, C_ETC, + C_ETC, C_ETC, C_ETC, C_TSPCL, C_TSPCL, C_TSPCL, C_ETC, C_ETC, + + C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, + C_H, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, + C_P, C_ETC, C_ETC, C_ETC, C_T, C_ETC, C_ETC, C_ETC, + C_ETC, C_ETC, C_ETC, C_TSPCL, C_ETC, C_TSPCL, C_ETC, C_CTL +}; + +enum states { + S_GO, + SMTD, + SMUS, + SURI, + SUHS, + SH__, + SHT1, + SHT2, + SHTP, + SSLH, + SMAV, + SDOT, + SMIV, + C_GO, + CH__, + CHT1, + CHT2, + CHTP, + CSLH, + CMAV, + CDOT, + CMIV, + CVNS, + CNUM, + CNTS, + CTXT, + SRE1, + SRE2, + SKEY, + SCOL, + SVAL, + SLWS, + SCN1, + SCN2, + SNL1, + SNL2, + SEND, + SMAX, + X___ = SMAX, + XMUS, + XUHS, + XVNS, + XNTS, + XRE1, + XKEY, + XCOL, + XVAL, + XEND +}; + +static const unsigned char state_table[SMAX][C_MAX] = { +/* ctl \n \r lws tsp / : . num H T P etc */ +/* S_GO*/ { X___,X___,X___,X___,X___,X___,X___,SMTD,SMTD,SMTD,SMTD,SMTD,SMTD }, +/* SMTD*/ { X___,X___,X___,XMUS,X___,X___,X___,SMTD,SMTD,SMTD,SMTD,SMTD,SMTD }, +/* SMUS*/ { X___,X___,X___,SMUS,SURI,SURI,SURI,SURI,SURI,SURI,SURI,SURI,SURI }, +/* SURI*/ { X___,X___,X___,XUHS,SURI,SURI,SURI,SURI,SURI,SURI,SURI,SURI,SURI }, +/* SUHS*/ { X___,X___,X___,SUHS,X___,X___,X___,X___,X___,SH__,X___,X___,X___ }, +/* SH__*/ { X___,X___,X___,X___,X___,X___,X___,X___,X___,X___,SHT1,X___,X___ }, +/* SHT1*/ { X___,X___,X___,X___,X___,X___,X___,X___,X___,X___,SHT2,X___,X___ }, +/* SHT2*/ { X___,X___,X___,X___,X___,X___,X___,X___,X___,X___,X___,SHTP,X___ }, +/* SHTP*/ { X___,X___,X___,X___,X___,SSLH,X___,X___,X___,X___,X___,X___,X___ }, +/* SSLH*/ { X___,X___,X___,X___,X___,X___,X___,X___,SMAV,X___,X___,X___,X___ }, +/* SMAV*/ { X___,X___,X___,X___,X___,X___,X___,SDOT,SMAV,X___,X___,X___,X___ }, +/* SDOT*/ { X___,X___,X___,X___,X___,X___,X___,X___,SMIV,X___,X___,X___,X___ }, +/* SMIV*/ { X___,X___,SRE1,X___,X___,X___,X___,X___,SMIV,X___,X___,X___,X___ }, +/* C_GO*/ { X___,X___,X___,X___,X___,X___,X___,X___,X___,CH__,X___,X___,X___ }, +/* CH__*/ { X___,X___,X___,X___,X___,X___,X___,X___,X___,X___,CHT1,X___,X___ }, +/* CHT1*/ { X___,X___,X___,X___,X___,X___,X___,X___,X___,X___,CHT2,X___,X___ }, +/* CHT2*/ { X___,X___,X___,X___,X___,X___,X___,X___,X___,X___,X___,CHTP,X___ }, +/* CHTP*/ { X___,X___,X___,X___,X___,CSLH,X___,X___,X___,X___,X___,X___,X___ }, +/* CSLH*/ { X___,X___,X___,X___,X___,X___,X___,X___,CMAV,X___,X___,X___,X___ }, +/* CMAV*/ { X___,X___,X___,X___,X___,X___,X___,CDOT,CMAV,X___,X___,X___,X___ }, +/* CDOT*/ { X___,X___,X___,X___,X___,X___,X___,X___,CMIV,X___,X___,X___,X___ }, +/* CMIV*/ { X___,X___,X___,XVNS,X___,X___,X___,X___,CMIV,X___,X___,X___,X___ }, +/* CVNS*/ { X___,X___,X___,CVNS,X___,X___,X___,X___,CNUM,X___,X___,X___,X___ }, +/* CNUM*/ { X___,X___,X___,XNTS,X___,X___,X___,X___,CNUM,X___,X___,X___,X___ }, +/* CNTS*/ { X___,X___,X___,CNTS,CTXT,X___,X___,X___,CTXT,CTXT,CTXT,CTXT,CTXT }, +/* CTXT*/ { X___,X___,XRE1,CTXT,CTXT,X___,X___,X___,CTXT,CTXT,CTXT,CTXT,CTXT }, +/* SRE1*/ { X___,SRE2,X___,X___,X___,X___,X___,X___,X___,X___,X___,X___,X___ }, +/* SRE2*/ { X___,X___,SEND,X___,X___,X___,X___,SKEY,SKEY,SKEY,SKEY,SKEY,SKEY }, +/* SKEY*/ { X___,X___,X___,X___,X___,X___,XCOL,SKEY,SKEY,SKEY,SKEY,SKEY,SKEY }, +/* SCOL*/ { X___,X___,SCN1,SCOL,SVAL,SVAL,SVAL,SVAL,SVAL,SVAL,SVAL,SVAL,SVAL }, +/* SVAL*/ { X___,X___,SNL1,SLWS,SVAL,SVAL,SVAL,SVAL,SVAL,SVAL,SVAL,SVAL,SVAL }, +/* SLWS*/ { X___,X___,SNL1,SLWS,XVAL,XVAL,XVAL,XVAL,XVAL,XVAL,XVAL,XVAL,XVAL }, +/* SCN1*/ { X___,SCN2,X___,X___,X___,X___,X___,X___,X___,X___,X___,X___,X___ }, +/* SCN2*/ { X___,X___,SEND,SCOL,X___,X___,X___,XKEY,XKEY,XKEY,XKEY,XKEY,XKEY }, +/* SNL1*/ { X___,SNL2,X___,X___,X___,X___,X___,X___,X___,X___,X___,X___,X___ }, +/* SNL2*/ { X___,X___,SEND,SLWS,X___,X___,X___,XKEY,XKEY,XKEY,XKEY,XKEY,XKEY }, +/* SEND*/ { X___,XEND,X___,X___,X___,X___,X___,X___,X___,X___,X___,X___,X___ }, +}; + +static void +parse_http_init(lua_State *T) +{ + /* create result table */ + lua_settop(T, 2); + lua_createtable(T, 0, 5); + lua_pushvalue(T, 1); + lua_setfield(T, -2, "istream"); +} + +static void +parse_http_req_init(lua_State *T, struct lem_inputbuf *b) +{ + b->u = S_GO; + parse_http_init(T); +} + +static void +parse_http_res_init(lua_State *T, struct lem_inputbuf *b) +{ + b->u = C_GO; + parse_http_init(T); +} + +static int +parse_http_process(lua_State *T, struct lem_inputbuf *b) +{ + unsigned char state = b->u & 0xFF; + unsigned int w = b->u >> 8; + unsigned int r = b->start; + unsigned int end = b->end; + + while (r < end) { + unsigned char ch = b->buf[r++]; + + state = state_table[state][ch > 127 ? C_ETC : ascii_class[ch]]; + /*lem_debug("char = %c (%hhu), state = %hhu", ch, ch, state);*/ + switch (state) { + case SMTD: + case SURI: + case SMAV: + case SDOT: + case SMIV: + case CMAV: + case CDOT: + case CMIV: + case CNUM: + case CTXT: + case SKEY: + case SVAL: + b->buf[w++] = ch; + break; + + case SRE1: + lua_pushlstring(T, b->buf, w); + lua_setfield(T, -2, "version"); + w = 0; + lua_newtable(T); + break; + + case X___: + lem_debug("HTTP parse error"); + lua_settop(T, 0); + lua_pushnil(T); + lua_pushliteral(T, "parse error"); + return 2; + + case XMUS: + state = SMUS; + lua_pushlstring(T, b->buf, w); + lua_setfield(T, -2, "method"); + w = 0; + break; + + case XUHS: + state = SUHS; + lua_pushlstring(T, b->buf, w); + lua_setfield(T, -2, "uri"); + w = 0; + break; + + case XVNS: + state = CVNS; + lua_pushlstring(T, b->buf, w); + lua_setfield(T, -2, "version"); + w = 0; + break; + + case XNTS: + state = CNTS; + { + unsigned int n = 0; + unsigned int k; + + for (k = 0; k < w; k++) { + n *= 10; + n += b->buf[k] - '0'; + } + + lua_pushnumber(T, n); + } + lua_setfield(T, -2, "status"); + w = 0; + break; + + case XRE1: + state = SRE1; + lua_pushlstring(T, b->buf, w); + lua_setfield(T, -2, "text"); + w = 0; + lua_newtable(T); + break; + + case XCOL: + state = SCOL; + lua_pushlstring(T, b->buf, w); + w = 0; + break; + + case XVAL: + state = SVAL; + b->buf[w++] = ' '; + b->buf[w++] = ch; + break; + + case XKEY: + state = SKEY; + lua_pushlstring(T, b->buf, w); + lua_rawset(T, -3); + w = 0; + b->buf[w++] = ch; + break; + + case XEND: + /* in case there are no headers this is false */ + if (lua_type(T, -1) == LUA_TSTRING) { + lua_pushlstring(T, b->buf, w); + lua_rawset(T, -3); + } + lua_setfield(T, -2, "headers"); + + /* set metatable */ + lua_getuservalue(T, 2); + lua_setmetatable(T, -2); + + if (r == end) + b->start = b->end = 0; + else + b->start = r; + return 1; + } + } + + if (w == LEM_INPUTBUF_SIZE - 1) { + b->start = b->end = 0; + lua_settop(T, 0); + lua_pushnil(T); + lua_pushliteral(T, "out of buffer space"); + return 2; + } + + b->start = b->end = w + 1; + b->u = (w << 8) | state; + return LEM_PMORE; +} + +int +luaopen_lem_http_core(lua_State *L) +{ + struct lem_parser *p; + + /* create module table M */ + lua_newtable(L); + + /* create Request metatable */ + lua_newtable(L); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + /* insert Request metatable */ + lua_setfield(L, -2, "Request"); + + /* create Response metatable */ + lua_newtable(L); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + /* insert Request metatable */ + lua_setfield(L, -2, "Response"); + + p = lua_newuserdata(L, sizeof(struct lem_parser)); + p->init = parse_http_req_init; + p->process = parse_http_process; + p->destroy = NULL; + lua_getfield(L, -2, "Request"); + lua_setuservalue(L, -2); + lua_setfield(L, -2, "HTTPRequest"); + + p = lua_newuserdata(L, sizeof(struct lem_parser)); + p->init = parse_http_res_init; + p->process = parse_http_process; + p->destroy = NULL; + lua_getfield(L, -2, "Response"); + lua_setuservalue(L, -2); + lua_setfield(L, -2, "HTTPResponse"); + + return 1; +} diff --git a/lem/repl.lua b/lem/repl.lua new file mode 100644 index 0000000..2e4fa69 --- /dev/null +++ b/lem/repl.lua @@ -0,0 +1,117 @@ +-- +-- This file is part of LEM, a Lua Event Machine. +-- Copyright 2011-2012 Emil Renner Berthing +-- +-- LEM is free software: you can redistribute it and/or +-- modify it under the terms of the GNU General Public License as +-- published by the Free Software Foundation, either version 3 of +-- the License, or (at your option) any later version. +-- +-- LEM is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with LEM. If not, see . +-- + +local utils = require 'lem.utils' + +local load = load +local format = string.format +local concat = table.concat +local tostring = tostring +local select = select + +local function repl(done, name, ins, outs) + if not outs then outs = ins end + + local getcode, onreturn, onerror + + name = '=' .. name + + function getcode() + local res, err = outs:write('> ') + if not res then return done(nil, err) end + + local line + line, err = ins:read('*l') + if not line then return done(nil, err) end + + line = line:gsub('^=', 'return ') + + while true do + res, err = load(line, name) + if res then break end + + if not err:match("''") then + return onerror(err) + end + + res, err = outs:write('>> ') + if not res then return done(nil, err) end + + res, err = ins:read('*l') + if not res then return done(nil, err) end + + line = line .. res + end + + utils.sethandler(onerror) + return onreturn(res()) + end + + function onreturn(...) + utils.sethandler() + local args = select('#', ...) + if args == 0 then return getcode() end + + local rstr + do + local t, ti = { ... }, nil + for i = 1, args - 1 do + t[i] = tostring(t[i]) + end + t[args] = tostring(t[args])..'\n' + + rstr = concat(t, '\t') + end + + local ok, err = outs:write(rstr) + if not ok then return done(nil, err) end + + return getcode() + end + + function onerror(err) + local ok, err = outs:write(format("%s\n", err)) + if not ok then return done(nil, err) end + + return getcode() + end + + return getcode() +end + +return { + wait = function(name, ins, outs) + local sleeper = utils.sleeper() + local function done(...) + return sleeper:wakeup(...) + end + + utils.spawn(repl, done, name, ins, outs) + + return sleeper:sleep() + end, + + go = function(name, ins, outs) + local function done() + return outs:write('\n') + end + return repl(done, name, ins, outs) + end, +} + +-- vim: ts=2 sw=2 noet: diff --git a/lem/streams.lua b/lem/streams.lua new file mode 100644 index 0000000..f4f9d4f --- /dev/null +++ b/lem/streams.lua @@ -0,0 +1,50 @@ +-- +-- This file is part of LEM, a Lua Event Machine. +-- Copyright 2011-2012 Emil Renner Berthing +-- +-- LEM is free software: you can redistribute it and/or +-- modify it under the terms of the GNU General Public License as +-- published by the Free Software Foundation, either version 3 of +-- the License, or (at your option) any later version. +-- +-- LEM is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with LEM. If not, see . +-- + +local M = require 'lem.streams.core' + +do + local type = type + local parsers = M.parsers + local parser_available = parsers.available + parsers.available = nil + local parser_target = parsers.target + parsers.target = nil + + function M.reader(readp) + return function(self, fmt, ...) + if fmt == nil then + return readp(self, parser_available) + end + if type(fmt) == 'number' then + return readp(self, parser_target, fmt) + end + local parser = parsers[fmt] + if parser == nil then + error('invalid format', 2) + end + return readp(self, parser, ...) + end + end + + M.IStream.read = M.reader(M.IStream.readp) +end + +return M + +-- vim: ts=2 sw=2 noet: diff --git a/lem/streams/core.c b/lem/streams/core.c new file mode 100644 index 0000000..e9ed9f4 --- /dev/null +++ b/lem/streams/core.c @@ -0,0 +1,258 @@ +/* + * This file is part of LEM, a Lua Event Machine. + * Copyright 2011-2012 Emil Renner Berthing + * + * LEM is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * LEM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LEM. If not, see . + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#if defined(__FreeBSD__) || defined(__APPLE__) +#include +#else +#include +#endif + +#include + +#include "sendfile.c" +#include "stream.c" +#include "server.c" +#include "tcp.c" +#include "parsers.c" + +static int +module_index(lua_State *T) +{ + const char *key = lua_tostring(T, 2); + int fd; + + if (strcmp(key, "stdin") == 0) + fd = 0; + else if (strcmp(key, "stdout") == 0) + fd = 1; + else if (strcmp(key, "stderr") == 0) + fd = 2; + else + return 0; + + /* make the socket non-blocking */ + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) { + lua_pushnil(T); + lua_pushfstring(T, "error making filedescriptor non-blocking: %s", + strerror(errno)); + return 2; + } + + if (fd == 0) + (void)istream_new(T, fd, lua_upvalueindex(1)); + else + (void)ostream_new(T, fd, lua_upvalueindex(2)); + + /* save this object so we don't initialize it again */ + lua_pushvalue(T, 2); + lua_pushvalue(T, -2); + lua_rawset(T, 1); + + return 1; +} + +int +luaopen_lem_streams_core(lua_State *L) +{ + /* create module table */ + lua_newtable(L); + + /* create metatable for sendfile objects */ + lua_newtable(L); + /* mt.__index = mt */ + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + /* mt.__gc = */ + lua_pushcfunction(L, sendfile_gc); + lua_setfield(L, -2, "__gc"); + /* mt.close = */ + lua_pushcfunction(L, sendfile_close); + lua_setfield(L, -2, "close"); + /* mt.size = */ + lua_pushcfunction(L, sendfile_size); + lua_setfield(L, -2, "size"); + /* insert table */ + lua_setfield(L, -2, "SendFile"); + + /* insert sendfile function */ + lua_getfield(L, -1, "SendFile"); /* upvalue 1 = SendFile */ + lua_pushcclosure(L, sendfile_open, 1); + lua_setfield(L, -2, "sendfile"); + + /* create metatable for input stream objects */ + lua_newtable(L); + /* mt.__index = mt */ + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + /* mt.__gc = */ + lua_pushcfunction(L, istream_close); + lua_setfield(L, -2, "__gc"); + /* mt.closed = */ + lua_pushcfunction(L, stream_closed); + lua_setfield(L, -2, "closed"); + /* mt.busy = */ + lua_pushcfunction(L, stream_busy); + lua_setfield(L, -2, "busy"); + /* mt.interrupt = */ + lua_pushcfunction(L, stream_interrupt); + lua_setfield(L, -2, "interrupt"); + /* mt.close = */ + lua_pushcfunction(L, istream_close); + lua_setfield(L, -2, "close"); + /* mt.readp = */ + lua_pushcfunction(L, stream_readp); + lua_setfield(L, -2, "readp"); + /* insert table */ + lua_setfield(L, -2, "IStream"); + + /* create metatable for output stream objects */ + lua_newtable(L); + /* mt.__index = mt */ + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + /* mt.__gc = */ + lua_pushcfunction(L, ostream_close); + lua_setfield(L, -2, "__gc"); + /* mt.closed = */ + lua_pushcfunction(L, stream_closed); + lua_setfield(L, -2, "closed"); + /* mt.busy = */ + lua_pushcfunction(L, stream_busy); + lua_setfield(L, -2, "busy"); + /* mt.interrupt = */ + lua_pushcfunction(L, stream_interrupt); + lua_setfield(L, -2, "interrupt"); + /* mt.close = */ + lua_pushcfunction(L, ostream_close); + lua_setfield(L, -2, "close"); + /* mt.write = */ + lua_pushcfunction(L, stream_write); + lua_setfield(L, -2, "write"); + /* mt.cork = */ + lua_pushcfunction(L, stream_cork); + lua_setfield(L, -2, "cork"); + /* mt.uncork = */ + lua_pushcfunction(L, stream_uncork); + lua_setfield(L, -2, "uncork"); + /* mt.sendfile = */ + lua_pushcfunction(L, stream_sendfile); + lua_setfield(L, -2, "sendfile"); + /* insert table */ + lua_setfield(L, -2, "OStream"); + + /* create metatable for server objects */ + lua_newtable(L); + /* mt.__index = mt */ + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + /* mt.__gc = */ + lua_pushcfunction(L, server_close); + lua_setfield(L, -2, "__gc"); + /* mt.closed = */ + lua_pushcfunction(L, server_closed); + lua_setfield(L, -2, "closed"); + /* mt.busy = */ + lua_pushcfunction(L, server_busy); + lua_setfield(L, -2, "busy"); + /* mt.close = */ + lua_pushcfunction(L, server_close); + lua_setfield(L, -2, "close"); + /* mt.interrupt = */ + lua_pushcfunction(L, server_interrupt); + lua_setfield(L, -2, "interrupt"); + /* mt.accept = */ + lua_getfield(L, -2, "IStream"); /* upvalue 1 = IStream */ + lua_getfield(L, -3, "OStream"); /* upvalue 2 = OStream */ + lua_pushcclosure(L, server_accept, 2); + lua_setfield(L, -2, "accept"); + /* mt.autospawn = */ + lua_getfield(L, -2, "IStream"); /* upvalue 1 = IStream */ + lua_getfield(L, -3, "OStream"); /* upvalue 2 = OStream */ + lua_pushcclosure(L, server_autospawn, 2); + lua_setfield(L, -2, "autospawn"); + /* insert table */ + lua_setfield(L, -2, "Server"); + + /* insert open function */ + lua_getfield(L, -1, "IStream"); /* upvalue 1 = IStream */ + lua_getfield(L, -2, "OStream"); /* upvalue 2 = OStream */ + lua_pushcclosure(L, stream_open, 2); + lua_setfield(L, -2, "open"); + /* insert popen function */ + lua_getfield(L, -1, "IStream"); /* upvalue 1 = IStream */ + lua_getfield(L, -2, "OStream"); /* upvalue 2 = OStream */ + lua_pushcclosure(L, stream_popen, 2); + lua_setfield(L, -2, "popen"); + + /* insert the connect function */ + lua_getfield(L, -1, "IStream"); /* upvalue 1 = IStream */ + lua_getfield(L, -2, "OStream"); /* upvalue 2 = OStream */ + lua_pushcclosure(L, tcp_connect, 2); + lua_setfield(L, -2, "tcp_connect"); + /* insert the tcp4_listen function */ + lua_getfield(L, -1, "Server"); /* upvalue 1 = Server */ + lua_pushcclosure(L, tcp4_listen, 1); + lua_setfield(L, -2, "tcp4_listen"); + /* insert the tcp6_listen function */ + lua_getfield(L, -1, "Server"); /* upvalue 1 = Server */ + lua_pushcclosure(L, tcp6_listen, 1); + lua_setfield(L, -2, "tcp6_listen"); + + /* create parser table */ + lua_createtable(L, 0, 4); + /* push parser_line */ + lua_pushlightuserdata(L, (void *)&parser_available); + lua_setfield(L, -2, "available"); + /* push parser_target */ + lua_pushlightuserdata(L, (void *)&parser_target); + lua_setfield(L, -2, "target"); + /* push parser_all */ + lua_pushlightuserdata(L, (void *)&parser_all); + lua_setfield(L, -2, "*a"); + /* push parser_line */ + lua_pushlightuserdata(L, (void *)&parser_line); + lua_setfield(L, -2, "*l"); + /* insert parser table */ + lua_setfield(L, -2, "parsers"); + + /* create metatable for the module */ + lua_newtable(L); + /* insert the index function */ + lua_getfield(L, -2, "IStream"); /* upvalue 1 = IStream */ + lua_getfield(L, -3, "OStream"); /* upvalue 2 = OStream */ + lua_pushcclosure(L, module_index, 2); + lua_setfield(L, -2, "__index"); + + /* set the metatable */ + lua_setmetatable(L, -2); + + return 1; +} diff --git a/lem/streams/parsers.c b/lem/streams/parsers.c new file mode 100644 index 0000000..72cd671 --- /dev/null +++ b/lem/streams/parsers.c @@ -0,0 +1,175 @@ +/* + * This file is part of LEM, a Lua Event Machine. + * Copyright 2011-2012 Emil Renner Berthing + * + * LEM is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * LEM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LEM. If not, see . + */ + +/* + * read available data + */ +static int +parse_available_process(lua_State *T, struct lem_inputbuf *b) +{ + size_t size = b->end - b->start; + + if (size == 0) + return LEM_PMORE; + + lua_pushlstring(T, b->buf + b->start, size); + b->start = b->end = 0; + return 1; +} + +static const struct lem_parser parser_available = { + .process = parse_available_process, +}; + +/* + * read a specified number of bytes + */ +static void +parse_target_init(lua_State *T, struct lem_inputbuf *b) +{ + b->u = luaL_checknumber(T, 3); + b->parts = 1; + lua_settop(T, 2); +} + +static int +parse_target_process(lua_State *T, struct lem_inputbuf *b) +{ + unsigned int target = b->u; + unsigned int size = b->end - b->start; + + if (size >= target) { + lua_pushlstring(T, b->buf + b->start, target); + lua_concat(T, b->parts); + b->start += target; + if (b->start == b->end) + b->start = b->end = 0; + + return 1; + } + + if (b->end == LEM_INPUTBUF_SIZE) { + lua_pushlstring(T, b->buf + b->start, size); + b->parts++; + b->start = b->end = 0; + b->u = target - size; + } + + return LEM_PMORE; +} + +static const struct lem_parser parser_target = { + .init = parse_target_init, + .process = parse_target_process, +}; + +/* + * read all data until stream closes + */ +static void +parse_all_init(lua_State *T, struct lem_inputbuf *b) +{ + b->parts = 0; + lua_settop(T, 2); +} + +static int +parse_all_process(lua_State *T, struct lem_inputbuf *b) +{ + if (b->end == LEM_INPUTBUF_SIZE) { + lua_pushlstring(T, b->buf + b->start, + LEM_INPUTBUF_SIZE - b->start); + b->start = b->end = 0; + b->parts++; + } + + return LEM_PMORE; +} + +static int +parse_all_destroy(lua_State *T, struct lem_inputbuf *b, enum lem_preason reason) +{ + unsigned int size; + + if (reason != LEM_PCLOSED) + return LEM_PMORE; + + size = b->end - b->start; + if (size > 0) { + lua_pushlstring(T, b->buf + b->start, size); + b->start = b->end = 0; + b->parts++; + } + + lua_concat(T, b->parts); + return 1; +} + +static const struct lem_parser parser_all = { + .init = parse_all_init, + .process = parse_all_process, + .destroy = parse_all_destroy, +}; + +/* + * read a line + */ +static void +parse_line_init(lua_State *T, struct lem_inputbuf *b) +{ + const char *stopbyte = luaL_optstring(T, 3, "\n"); + + b->u = (b->start << 8) | stopbyte[0]; + b->parts = 1; + lua_settop(T, 2); +} + +static int +parse_line_process(lua_State *T, struct lem_inputbuf *b) +{ + unsigned int i; + unsigned char stopbyte = b->u & 0xFF; + + for (i = b->u >> 8; i < b->end; i++) { + if (b->buf[i] == stopbyte) { + lua_pushlstring(T, b->buf + b->start, i - b->start); + lua_concat(T, b->parts); + i++; + if (i == b->end) + b->start = b->end = 0; + else + b->start = i; + return 1; + } + } + + if (b->end == LEM_INPUTBUF_SIZE) { + lua_pushlstring(T, b->buf + b->start, b->end - b->start); + b->parts++; + b->start = b->end = 0; + b->u = stopbyte; + } else + b->u = (i << 8) | stopbyte; + + return LEM_PMORE; +} + +static const struct lem_parser parser_line = { + .init = parse_line_init, + .process = parse_line_process, +}; diff --git a/lem/streams/queue.lua b/lem/streams/queue.lua new file mode 100644 index 0000000..7672798 --- /dev/null +++ b/lem/streams/queue.lua @@ -0,0 +1,78 @@ +-- +-- This file is part of lem-streams. +-- Copyright 2011 Emil Renner Berthing +-- +-- lem-streams is free software: you can redistribute it and/or +-- modify it under the terms of the GNU General Public License as +-- published by the Free Software Foundation, either version 3 of +-- the License, or (at your option) any later version. +-- +-- lem-streams is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with lem-streams. If not, see . +-- + +local utils = require 'lem.utils' + +local setmetatable = setmetatable +local thisthread, suspend, resume + = utils.thisthread, utils.suspend, utils.resume + +local QOStream = {} +QOStream.__index = QOStream + +function QOStream:closed(...) + return self.stream:closed(...) +end + +function QOStream:interrupt(...) + return self.stream:interrupt(...) +end + +function QOStream:close(...) + return self.stream:close(...) +end + +function QOStream:write(...) + local nxt = self.next + if nxt == 0 then + nxt = 1 + self.next = 1 + else + local me = nxt + + self[me] = thisthread() + nxt = #self+1 + self.next = nxt + suspend() + self[me] = nil + end + + local ok, err = self.stream:write(...) + + nxt = self[nxt] + if nxt then + resume(nxt) + else + self.next = 0 + end + + if not ok then return nil, err end + return ok +end + +local function wrap(stream, ...) + if not stream then return stream, ... end + return setmetatable({ stream = stream, next = 0 }, QOStream) +end + +return { + QOStream = QOStream, + wrap = wrap, +} + +-- vim: set ts=2 sw=2 noet: diff --git a/lem/streams/sendfile.c b/lem/streams/sendfile.c new file mode 100644 index 0000000..c3395e3 --- /dev/null +++ b/lem/streams/sendfile.c @@ -0,0 +1,120 @@ +/* + * This file is part of LEM, a Lua Event Machine. + * Copyright 2011-2012 Emil Renner Berthing + * + * LEM is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * LEM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LEM. If not, see . + */ + +struct lem_sendfile { + int fd; + off_t size; +}; + +static int +sendfile_open(lua_State *T) +{ + const char *path = luaL_checkstring(T, 1); + int fd; + struct stat buf; + struct lem_sendfile *f; + + fd = open(path, O_RDONLY | O_NONBLOCK); + if (fd < 0) { + int err = errno; + + lua_pushnil(T); + switch (err) { + case ENOENT: + lua_pushliteral(T, "not found"); + break; + case EACCES: + lua_pushliteral(T, "permission denied"); + break; + default: + lua_pushstring(T, strerror(err)); + } + return 2; + } + + if (fstat(fd, &buf)) { + lua_pushnil(T); + lua_pushstring(T, strerror(errno)); + (void)close(fd); + return 2; + } + + /* create userdata and set the metatable */ + f = lua_newuserdata(T, sizeof(struct lem_sendfile)); + lua_pushvalue(T, lua_upvalueindex(1)); + lua_setmetatable(T, -2); + + /* initialize userdata */ + f->fd = fd; + f->size = buf.st_size; + + return 1; +} + +static int +sendfile_gc(lua_State *T) +{ + struct lem_sendfile *f = lua_touserdata(T, 1); + + if (f->fd < 0) + return 0; + + (void)close(f->fd); + return 0; +} + +static int +sendfile_close(lua_State *T) +{ + struct lem_sendfile *f; + + luaL_checktype(T, 1, LUA_TUSERDATA); + f = lua_touserdata(T, 1); + if (f->fd < 0) { + lua_pushnil(T); + lua_pushliteral(T, "already closed"); + return 2; + } + + if (close(f->fd)) { + lua_pushnil(T); + lua_pushstring(T, strerror(errno)); + return 2; + } + + f->fd = -1; + lua_pushboolean(T, 1); + return 1; +} + +static int +sendfile_size(lua_State *T) +{ + struct lem_sendfile *f; + + luaL_checktype(T, 1, LUA_TUSERDATA); + f = lua_touserdata(T, 1); + if (f->fd < 0) { + lua_pushnil(T); + lua_pushliteral(T, "closed"); + return 2; + } + + lua_pushnumber(T, (lua_Number)f->size); + return 1; +} diff --git a/lem/streams/server.c b/lem/streams/server.c new file mode 100644 index 0000000..c2c03cf --- /dev/null +++ b/lem/streams/server.c @@ -0,0 +1,298 @@ +/* + * This file is part of LEM, a Lua Event Machine. + * Copyright 2011-2012 Emil Renner Berthing + * + * LEM is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * LEM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LEM. If not, see . + */ + +#ifndef MAXPENDING +#define MAXPENDING 50 +#endif + +static int +server_closed(lua_State *T) +{ + struct ev_io *w; + + luaL_checktype(T, 1, LUA_TUSERDATA); + w = lua_touserdata(T, 1); + lua_pushboolean(T, w->fd < 0); + return 1; +} + +static int +server_busy(lua_State *T) +{ + struct ev_io *w; + + luaL_checktype(T, 1, LUA_TUSERDATA); + w = lua_touserdata(T, 1); + lua_pushboolean(T, w->data != NULL); + return 1; +} + +static int +server_close(lua_State *T) +{ + struct ev_io *w; + int ret; + + luaL_checktype(T, 1, LUA_TUSERDATA); + w = lua_touserdata(T, 1); + if (w->fd < 0) { + lua_pushnil(T); + lua_pushliteral(T, "already closed"); + return 2; + } + + if (w->data != NULL) { + lem_debug("interrupting listen"); + ev_io_stop(LEM_ w); + lua_pushnil(w->data); + lua_pushliteral(w->data, "interrupted"); + lem_queue(w->data, 2); + w->data = NULL; + } + + lem_debug("closing server.."); + + if (close(w->fd)) { + lua_pushnil(T); + lua_pushstring(T, strerror(errno)); + ret = 2; + } else { + lua_pushboolean(T, 1); + ret = 1; + } + + w->fd = -1; + return ret; +} + +static int +server_interrupt(lua_State *T) +{ + struct ev_io *w; + + luaL_checktype(T, 1, LUA_TUSERDATA); + w = lua_touserdata(T, 1); + if (w->data == NULL) { + lua_pushnil(T); + lua_pushliteral(T, "not busy"); + return 2; + } + + lem_debug("interrupting listening"); + ev_io_stop(LEM_ w); + lua_pushnil(w->data); + lua_pushliteral(w->data, "interrupted"); + lem_queue(w->data, 2); + w->data = NULL; + + lua_pushboolean(T, 1); + return 1; +} + +static int +try_accept(lua_State *T, struct ev_io *w) +{ + struct sockaddr client_addr; + unsigned int client_addrlen; + int sock; + struct istream *is; + struct ostream *os; + + sock = accept(w->fd, &client_addr, &client_addrlen); + if (sock < 0) { + if (errno == EAGAIN || errno == ECONNABORTED) + return 0; + lua_pushnil(T); + lua_pushfstring(T, "error accepting connection: %s", + strerror(errno)); + return -1; + } + + /* make the socket non-blocking */ + if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) { + close(sock); + lua_pushnil(T); + lua_pushfstring(T, "error making socket non-blocking: %s", + strerror(errno)); + return -1; + } + + is = istream_new(T, sock, lua_upvalueindex(1)); + os = ostream_new(T, sock, lua_upvalueindex(2)); + is->twin = os; + os->twin = is; + + return 1; +} + +static void +server_accept_handler(EV_P_ struct ev_io *w, int revents) +{ + (void)revents; + + switch (try_accept(w->data, w)) { + case 0: + return; + + case 1: + break; + + default: + close(w->fd); + w->fd = -1; + } + + ev_io_stop(EV_A_ w); + lem_queue(w->data, 2); + w->data = NULL; +} + +static int +server_accept(lua_State *T) +{ + struct ev_io *w; + + luaL_checktype(T, 1, LUA_TUSERDATA); + w = lua_touserdata(T, 1); + if (w->fd < 0) { + lua_pushnil(T); + lua_pushliteral(T, "closed"); + return 2; + } + + if (w->data != NULL) { + lua_pushnil(T); + lua_pushliteral(T, "busy"); + return 2; + } + + switch (try_accept(T, w)) { + case 0: + w->cb = server_accept_handler; + w->data = T; + ev_io_start(LEM_ w); + + /* yield server object */ + lua_settop(T, 1); + return lua_yield(T, 1); + + case 1: + break; + + + default: + close(w->fd); + w->fd = -1; + } + + return 2; +} + +static void +server_autospawn_handler(EV_P_ struct ev_io *w, int revents) +{ + lua_State *T = w->data; + struct sockaddr client_addr; + unsigned int client_addrlen; + int sock; + lua_State *S; + struct istream *is; + struct ostream *os; + + (void)revents; + + /* dequeue the incoming connection */ + client_addrlen = sizeof(struct sockaddr); + sock = accept(w->fd, &client_addr, &client_addrlen); + if (sock < 0) { + if (errno == EAGAIN || errno == ECONNABORTED) + return; + lua_pushnil(T); + lua_pushfstring(T, "error accepting connection: %s", + strerror(errno)); + goto error; + } + + /* make the socket non-blocking */ + if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) { + close(sock); + lua_pushnil(T); + lua_pushfstring(T, "error making socket non-blocking: %s", + strerror(errno)); + goto error; + } + + S = lem_newthread(); + + /* copy handler function to thread */ + lua_pushvalue(T, 2); + lua_xmove(T, S, 1); + + /* create streams */ + is = istream_new(T, sock, lua_upvalueindex(1)); + os = ostream_new(T, sock, lua_upvalueindex(2)); + is->twin = os; + os->twin = is; + + /* move streams to new thread */ + lua_xmove(T, S, 2); + + lem_queue(S, 2); + return; + +error: + ev_io_stop(EV_A_ w); + close(w->fd); + w->fd = -1; + + lem_queue(T, 2); + w->data = NULL; +} + +static int +server_autospawn(lua_State *T) +{ + struct ev_io *w; + + luaL_checktype(T, 1, LUA_TUSERDATA); + luaL_checktype(T, 2, LUA_TFUNCTION); + + w = lua_touserdata(T, 1); + if (w->fd < 0) { + lua_pushnil(T); + lua_pushliteral(T, "closed"); + return 2; + } + + if (w->data != NULL) { + lua_pushnil(T); + lua_pushliteral(T, "busy"); + return 2; + } + + w->cb = server_autospawn_handler; + w->data = T; + ev_io_start(LEM_ w); + + lem_debug("yielding"); + + /* yield server object, function and metatable*/ + lua_settop(T, 2); + lua_pushvalue(T, lua_upvalueindex(1)); + return lua_yield(T, 3); +} diff --git a/lem/streams/stream.c b/lem/streams/stream.c new file mode 100644 index 0000000..50ffc50 --- /dev/null +++ b/lem/streams/stream.c @@ -0,0 +1,802 @@ +/* + * This file is part of LEM, a Lua Event Machine. + * Copyright 2011-2012 Emil Renner Berthing + * + * LEM is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * LEM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LEM. If not, see . + */ + +struct ostream; + +struct istream { + ev_io w; + struct ostream *twin; + struct lem_parser *p; + struct lem_inputbuf buf; +}; + +struct ostream { + ev_io w; + struct istream *twin; + const char *data; + size_t len; +}; + +static struct istream * +istream_new(lua_State *T, int fd, int mt) +{ + struct istream *s; + + /* create userdata and set the metatable */ + s = lua_newuserdata(T, sizeof(struct istream)); + lua_pushvalue(T, mt); + lua_setmetatable(T, -2); + + /* initialize userdata */ + ev_io_init(&s->w, NULL, fd, EV_READ); + s->w.data = NULL; + s->twin = NULL; + s->buf.start = s->buf.end = 0; + + return s; +} + +static int +stream_closed(lua_State *T) +{ + struct ev_io *w; + + luaL_checktype(T, 1, LUA_TUSERDATA); + w = lua_touserdata(T, 1); + lua_pushboolean(T, w->fd < 0); + return 1; +} + +static int +stream_busy(lua_State *T) +{ + struct ev_io *w; + + luaL_checktype(T, 1, LUA_TUSERDATA); + w = lua_touserdata(T, 1); + lua_pushboolean(T, w->data != NULL); + return 1; +} + +static int +stream_interrupt(lua_State *T) +{ + struct ev_io *w; + lua_State *S; + + luaL_checktype(T, 1, LUA_TUSERDATA); + w = lua_touserdata(T, 1); + S = w->data; + if (S == NULL) { + lua_pushnil(T); + lua_pushliteral(T, "not busy"); + return 2; + } + + lem_debug("interrupting io action"); + ev_io_stop(LEM_ w); + w->data = NULL; + lua_settop(S, 0); + lua_pushnil(S); + lua_pushliteral(S, "interrupted"); + lem_queue(S, 2); + + lua_pushboolean(T, 1); + return 1; +} + +static int +stream_close_check(lua_State *T, struct ev_io *w) +{ + if (w->fd < 0) { + lua_pushnil(T); + lua_pushliteral(T, "already closed"); + return 2; + } + + if (w->data != NULL) { + lua_State *S = w->data; + + lem_debug("interrupting io action"); + ev_io_stop(LEM_ w); + w->data = NULL; + lua_settop(S, 0); + lua_pushnil(S); + lua_pushliteral(S, "interrupted"); + lem_queue(S, 2); + } + + return 0; +} + +static int +istream_close(lua_State *T) +{ + struct istream *s; + + luaL_checktype(T, 1, LUA_TUSERDATA); + s = lua_touserdata(T, 1); + + lem_debug("collecting %d", s->w.fd); + if (stream_close_check(T, &s->w)) + return 2; + + if (s->twin) + s->twin->twin = NULL; + else if (close(s->w.fd)) { + s->w.fd = -1; + lua_pushnil(T); + lua_pushstring(T, strerror(errno)); + return 2; + } + + s->w.fd = -1; + lua_pushboolean(T, 1); + return 1; +} + +static int +ostream_close(lua_State *T) +{ + struct ostream *s; + + luaL_checktype(T, 1, LUA_TUSERDATA); + s = lua_touserdata(T, 1); + + lem_debug("collecting %d", s->w.fd); + if (stream_close_check(T, &s->w)) + return 2; + + if (s->twin) + s->twin->twin = NULL; + else if (close(s->w.fd)) { + s->w.fd = -1; + lua_pushnil(T); + lua_pushstring(T, strerror(errno)); + return 2; + } + + s->w.fd = -1; + lua_pushboolean(T, 1); + return 1; +} + +/* + * istream:readp() method + */ + +static void +stream_readp_handler(EV_P_ ev_io *w, int revents) +{ + struct istream *s = (struct istream *)w; + lua_State *T = s->w.data; + ssize_t bytes; + int ret; + enum lem_preason reason; + const char *msg; + + (void)revents; + + while ((bytes = read(s->w.fd, s->buf.buf + s->buf.end, + LEM_INPUTBUF_SIZE - s->buf.end)) > 0) { + lem_debug("read %ld bytes from %d", bytes, s->w.fd); + + s->buf.end += bytes; + + ret = s->p->process(T, &s->buf); + if (ret > 0) { + ev_io_stop(EV_A_ &s->w); + s->w.data = NULL; + lem_queue(T, ret); + return; + } + } + lem_debug("read %ld bytes from %d", bytes, s->w.fd); + + if (bytes < 0 && errno == EAGAIN) + return; + + ev_io_stop(EV_A_ &s->w); + s->w.data = NULL; + + if (s->twin) + s->twin->twin = NULL; + else + (void)close(s->w.fd); + s->w.fd = -1; + + if (bytes == 0 || errno == ECONNRESET || errno == EPIPE) { + reason = LEM_PCLOSED; + msg = "closed"; + } else { + reason = LEM_PERROR; + msg = strerror(errno); + } + + if (s->p->destroy && (ret = s->p->destroy(T, &s->buf, reason)) > 0) { + lem_queue(T, ret); + return; + } + + lua_settop(T, 0); + lua_pushnil(T); + lua_pushstring(T, msg); + lem_queue(T, 2); +} + +static int +stream_readp(lua_State *T) +{ + struct istream *s; + struct lem_parser *p; + int ret; + ssize_t bytes; + enum lem_preason reason; + const char *msg; + + luaL_checktype(T, 1, LUA_TUSERDATA); + ret = lua_type(T, 2); + if (ret != LUA_TUSERDATA && ret != LUA_TLIGHTUSERDATA) + return luaL_argerror(T, 2, "expected userdata"); + + s = lua_touserdata(T, 1); + if (s->w.fd < 0) { + lua_pushnil(T); + lua_pushliteral(T, "closed"); + return 2; + } + + if (s->w.data != NULL) { + lua_pushnil(T); + lua_pushliteral(T, "busy"); + return 2; + } + + p = lua_touserdata(T, 2); + if (p->init) + p->init(T, &s->buf); + +again: + ret = p->process(T, &s->buf); + if (ret > 0) + return ret; + + bytes = read(s->w.fd, s->buf.buf + s->buf.end, LEM_INPUTBUF_SIZE - s->buf.end); + lem_debug("read %ld bytes from %d", bytes, s->w.fd); + if (bytes > 0) { + s->buf.end += bytes; + goto again; + } + + if (bytes < 0 && errno == EAGAIN) { + s->p = p; + s->w.data = T; + s->w.cb = stream_readp_handler; + ev_io_start(LEM_ &s->w); + return lua_yield(T, lua_gettop(T)); + } + + if (s->twin) + s->twin->twin = NULL; + else + (void)close(s->w.fd); + s->w.fd = -1; + + if (bytes == 0 || errno == ECONNRESET || errno == EPIPE) { + reason = LEM_PCLOSED; + msg = "closed"; + } else { + reason = LEM_PERROR; + msg = strerror(errno); + } + + if (p->destroy && (ret = p->destroy(T, &s->buf, reason)) > 0) + return ret; + + lua_settop(T, 0); + lua_pushnil(T); + lua_pushstring(T, msg); + return 2; +} + +static struct ostream * +ostream_new(lua_State *T, int fd, int mt) +{ + struct ostream *s; + + /* create userdata and set the metatable */ + s = lua_newuserdata(T, sizeof(struct ostream)); + lua_pushvalue(T, mt); + lua_setmetatable(T, -2); + + /* initialize userdata */ + ev_io_init(&s->w, NULL, fd, EV_WRITE); + s->w.data = NULL; + s->twin = NULL; + + return s; +} + +static void +stream_write_handler(EV_P_ struct ev_io *w, int revents) +{ + struct ostream *s = (struct ostream *)w; + lua_State *T = s->w.data; + ssize_t bytes; + + (void)revents; + +again: + bytes = write(s->w.fd, s->data, s->len); + if (bytes > 0) { + s->len -= bytes; + if (s->len > 0) { + s->data += bytes; + goto again; + } + + ev_io_stop(EV_A_ &s->w); + s->w.data = NULL; + + lua_pushboolean(T, 1); + lem_queue(T, 1); + return; + } + + if (bytes < 0 && errno == EAGAIN) + return; + + ev_io_stop(EV_A_ &s->w); + s->w.data = NULL; + + lua_pushnil(T); + if (bytes == 0 || errno == ECONNRESET || errno == EPIPE) + lua_pushliteral(T, "closed"); + else + lua_pushstring(T, strerror(errno)); + + if (s->twin) + s->twin->twin = NULL; + else + (void)close(s->w.fd); + s->w.fd = -1; + lem_queue(T, 2); +} + +static int +stream_write(lua_State *T) +{ + struct ostream *s; + ssize_t bytes; + + luaL_checktype(T, 1, LUA_TUSERDATA); + luaL_checktype(T, 2, LUA_TSTRING); + + s = lua_touserdata(T, 1); + if (s->w.fd < 0) { + lua_pushnil(T); + lua_pushliteral(T, "closed"); + return 2; + } + + if (s->w.data != NULL) { + lua_pushnil(T); + lua_pushliteral(T, "busy"); + return 2; + } + + s->data = lua_tolstring(T, 2, &s->len); + if (s->len == 0) { + lua_pushboolean(T, 1); + return 1; + } + +again: + bytes = write(s->w.fd, s->data, s->len); + if (bytes > 0) { + s->len -= bytes; + if (s->len > 0) { + s->data += bytes; + goto again; + } + + lua_pushboolean(T, 1); + return 1; + } + + if (bytes < 0 && errno == EAGAIN) { + lua_settop(T, 1); + s->w.data = T; + s->w.cb = stream_write_handler; + ev_io_start(LEM_ &s->w); + return lua_yield(T, 1); + } + + lua_pushnil(T); + if (bytes == 0 || errno == ECONNRESET || errno == EPIPE) + lua_pushliteral(T, "closed"); + else + lua_pushstring(T, strerror(errno)); + + if (s->twin) + s->twin->twin = NULL; + else + (void)close(s->w.fd); + s->w.fd = -1; + return 2; +} + + +#ifndef TCP_CORK +#define TCP_CORK TCP_NOPUSH +#endif + +static int +stream_setcork(lua_State *T, int state) +{ + struct ostream *s; + + luaL_checktype(T, 1, LUA_TUSERDATA); + s = lua_touserdata(T, 1); + if (s->w.fd < 0) { + lua_pushnil(T); + lua_pushliteral(T, "closed"); + return 2; + } + + if (s->w.data != NULL) { + lua_pushnil(T); + lua_pushliteral(T, "busy"); + return 2; + } + + if (setsockopt(s->w.fd, IPPROTO_TCP, TCP_CORK, &state, sizeof(int))) { + lua_pushnil(T); + lua_pushstring(T, strerror(errno)); + return 2; + } + + lua_pushboolean(T, 1); + return 1; +} + +static int +stream_cork(lua_State *T) +{ + return stream_setcork(T, 1); +} + +static int +stream_uncork(lua_State *T) +{ + return stream_setcork(T, 0); +} + +struct sendfile { + struct lem_sendfile *file; + off_t offset; +}; + +static int +try_sendfile(lua_State *T, struct ostream *s, struct sendfile *sf) +{ +#ifdef __FreeBSD__ + int ret; + + do { + size_t count; + off_t written = 0; + + count = sf->file->size - sf->offset; + + if (count == 0) { + lua_settop(T, 0); + lua_pushboolean(T, 1); + return 1; + } + + ret = sendfile(sf->file->fd, s->w.fd, + sf->offset, count, + NULL, &written, 0); + lem_debug("wrote = %ld bytes", written); + sf->offset += written; + } while (ret >= 0); +#else +#ifdef __APPLE__ + int ret; + + do { + off_t count = sf->file->size - sf->offset; + + if (count == 0) { + lua_settop(T, 0); + lua_pushboolean(T, 1); + return 1; + } + + ret = sendfile(sf->file->fd, s->w.fd, + sf->offset, &count, NULL, 0); + lem_debug("wrote = %lld bytes", count); + sf->offset += count; + } while (ret >= 0); +#else + ssize_t ret; + + do { + size_t count = sf->file->size - sf->offset; + + if (count == 0) { + lua_settop(T, 0); + lua_pushboolean(T, 1); + return 1; + } + + ret = sendfile(s->w.fd, sf->file->fd, + &sf->offset, + count); + lem_debug("wrote = %ld bytes", ret); + } while (ret >= 0); +#endif +#endif + + if (errno == EAGAIN) + return 0; + + lua_pushnil(T); + lua_pushstring(T, strerror(errno)); + return 2; +} + +static void +sendfile_handler(EV_P_ struct ev_io *w, int revents) +{ + struct ostream *s = (struct ostream *)w; + lua_State *T = s->w.data; + struct sendfile *sf = lua_touserdata(T, 3); + int ret; + + (void)revents; + + ret = try_sendfile(T, s, sf); + if (ret == 0) + return; + + ev_io_stop(EV_A_ &s->w); + if (ret == 2) { + if (s->twin) + s->twin->twin = NULL; + else + (void)close(s->w.fd); + s->w.fd = -1; + } + + lem_queue(T, ret); + s->w.data = NULL; +} + +static int +stream_sendfile(lua_State *T) +{ + struct ostream *s; + struct lem_sendfile *f; + struct sendfile *sf; + off_t offset; + int ret; + + luaL_checktype(T, 1, LUA_TUSERDATA); + luaL_checktype(T, 2, LUA_TUSERDATA); + offset = (off_t)luaL_optnumber(T, 3, 0); + + s = lua_touserdata(T, 1); + if (s->w.fd < 0) { + lua_pushnil(T); + lua_pushliteral(T, "closed"); + return 2; + } + + if (s->w.data != NULL) { + lua_pushnil(T); + lua_pushliteral(T, "busy"); + return 2; + } + + f = lua_touserdata(T, 2); + if (f->fd < 0) { + lua_pushnil(T); + lua_pushliteral(T, "file closed"); + return 2; + } + + if (offset > f->size) + return luaL_error(T, "offset too big"); + + lua_settop(T, 2); + sf = lua_newuserdata(T, sizeof(struct sendfile)); + sf->file = f; + sf->offset = offset; + + ret = try_sendfile(T, s, sf); + if (ret > 0) { + if (ret == 2) { + if (s->twin) + s->twin->twin = NULL; + else + (void)close(s->w.fd); + s->w.fd = -1; + } + return ret; + } + + lem_debug("yielding"); + s->w.data = T; + s->w.cb = sendfile_handler; + ev_io_start(LEM_ &s->w); + return lua_yield(T, 3); +} + +static int +mode_to_flags(const char *mode) +{ + int omode; + int oflags; + + switch (*mode++) { + case 'r': + omode = O_RDONLY; + oflags = 0; + break; + case 'w': + omode = O_WRONLY; + oflags = O_CREAT | O_TRUNC; + break; + case 'a': + omode = O_WRONLY; + oflags = O_CREAT | O_APPEND; + break; + default: + return -1; + } + +next: + switch (*mode++) { + case '\0': + break; + case '+': + omode = O_RDWR; + goto next; + case 'b': + /* this does nothing on *nix, but + * don't treat it as an error */ + goto next; + case 'x': + oflags |= O_EXCL; + goto next; + default: + return -1; + } + + return omode | oflags; +} + +static int +stream_open(lua_State *T) +{ + const char *path = luaL_checkstring(T, 1); + int flags = mode_to_flags(luaL_optstring(T, 2, "r")); + int fd; + struct istream *is; + struct ostream *os; + + if (flags < 0) + return luaL_error(T, "invalid mode string"); + + fd = open(path, flags | O_NONBLOCK); + if (fd < 0) { + lua_pushnil(T); + switch (errno) { + case ENOENT: + lua_pushliteral(T, "not found"); + break; + case EACCES: + lua_pushliteral(T, "permission denied"); + break; + default: + lua_pushstring(T, strerror(errno)); + } + return 2; + } + + if ((flags & O_WRONLY) == 0) + is = istream_new(T, fd, lua_upvalueindex(1)); + else + is = NULL; + + if (flags & (O_RDWR | O_WRONLY)) + os = ostream_new(T, fd, lua_upvalueindex(2)); + else + os = NULL; + + if (is && os) { + is->twin = os; + os->twin = is; + return 2; + } + + return 1; +} + +static int +stream_popen(lua_State *T) +{ + const char *cmd = luaL_checkstring(T, 1); + const char *mode = luaL_optstring(T, 2, "r"); + int fd[2]; + + if (mode[0] != 'r' && mode[0] != 'w') + return luaL_error(T, "invalid mode string"); + + if (pipe(fd)) + goto error; + + switch (fork()) { + case -1: /* error */ + (void)close(fd[0]); + (void)close(fd[1]); + goto error; + case 0: /* child */ + if (mode[0] == 'r') { + (void)close(fd[0]); + (void)dup2(fd[1], 1); + } else { + (void)close(fd[1]); + (void)dup2(fd[0], 0); + } + + (void)execl("/bin/sh", "/bin/sh", "-c", cmd, NULL); + exit(EXIT_FAILURE); + } + + if (mode[0] == 'r') { + if (close(fd[1])) { + (void)close(fd[0]); + goto error; + } + } else { + if (close(fd[0])) { + (void)close(fd[1]); + goto error; + } + fd[0] = fd[1]; + } + + /* make the pipe non-blocking */ + if (fcntl(fd[0], F_SETFL, O_NONBLOCK) < 0) { + (void)close(fd[0]); + goto error; + } + + if (mode[0] == 'r') + (void)istream_new(T, fd[0], lua_upvalueindex(1)); + else + (void)ostream_new(T, fd[0], lua_upvalueindex(2)); + return 1; +error: + lua_pushnil(T); + lua_pushstring(T, strerror(errno)); + return 2; +} diff --git a/lem/streams/tcp.c b/lem/streams/tcp.c new file mode 100644 index 0000000..fd13760 --- /dev/null +++ b/lem/streams/tcp.c @@ -0,0 +1,251 @@ +/* + * This file is part of LEM, a Lua Event Machine. + * Copyright 2011-2012 Emil Renner Berthing + * + * LEM is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * LEM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LEM. If not, see . + */ + +static void +connect_handler(EV_P_ struct ev_io *w, int revents) +{ + (void)revents; + + lem_debug("connection established"); + ev_io_stop(EV_A_ w); + lem_queue(w->data, 2); + w->data = NULL; +} + +static int +tcp_connect(lua_State *T) +{ + const char *addr = luaL_checkstring(T, 1); + uint16_t port = (uint16_t)luaL_checknumber(T, 2); + struct addrinfo hints = { + .ai_flags = 0, + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + .ai_addrlen = 0, + .ai_addr = NULL, + .ai_canonname = NULL, + .ai_next = NULL + }; + struct addrinfo *ainfo; + int sock; + int ret; + struct istream *is; + struct ostream *os; + + /* lookup name */ + ret = getaddrinfo(addr, NULL, &hints, &ainfo); + if (ret != 0) { + lua_pushnil(T); + lua_pushfstring(T, "error looking up \"%s\": %s", + addr, gai_strerror(ret)); + return 2; + } + + /* create the TCP socket */ + switch (ainfo->ai_family) { + case AF_INET: + ((struct sockaddr_in *)ainfo->ai_addr)->sin_port = htons(port); + sock = socket(PF_INET, ainfo->ai_socktype, ainfo->ai_protocol); + break; + + case AF_INET6: + ((struct sockaddr_in6 *)ainfo->ai_addr)->sin6_port = htons(port); + sock = socket(PF_INET6, ainfo->ai_socktype, ainfo->ai_protocol); + break; + + default: + freeaddrinfo(ainfo); + lua_pushnil(T); + lua_pushfstring(T, "getaddrinfo() returned neither " + "IPv4 or IPv6 address for \"%s\"", + addr); + return 2; + } + + lem_debug("sock = %d", sock); + + if (sock < 0) { + freeaddrinfo(ainfo); + lua_pushnil(T); + lua_pushfstring(T, "error creating TCP socket: %s", + strerror(errno)); + return 2; + } + + /* make the socket non-blocking */ + if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) { + close(sock); + freeaddrinfo(ainfo); + lua_pushnil(T); + lua_pushfstring(T, "error making socket non-blocking: %s", + strerror(errno)); + return 2; + } + + lua_settop(T, 1); + is = istream_new(T, sock, lua_upvalueindex(1)); + os = ostream_new(T, sock, lua_upvalueindex(2)); + is->twin = os; + os->twin = is; + + /* connect */ + ret = connect(sock, ainfo->ai_addr, ainfo->ai_addrlen); + freeaddrinfo(ainfo); + if (ret == 0) { + lem_debug("connection established"); + return 2; + } + + if (errno == EINPROGRESS) { + lem_debug("EINPROGRESS"); + os->w.data = T; + os->w.cb = connect_handler; + ev_io_start(LEM_ &os->w); + return lua_yield(T, 3); + } + + close(sock); + lua_pushnil(T); + lua_pushfstring(T, "error connecting to %s:%d: %s", + addr, (int)port, strerror(errno)); + return 2; +} + +static int +common_listen(lua_State *T, struct sockaddr *address, socklen_t alen, + int sock, int backlog) +{ + struct ev_io *w; + int optval = 1; + + /* set SO_REUSEADDR option */ + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + &optval, sizeof(int))) { + close(sock); + lua_pushnil(T); + lua_pushfstring(T, "error setting SO_REUSEADDR on socket: %s", + strerror(errno)); + return 2; + } + + /* make the socket non-blocking */ + if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) { + close(sock); + lua_pushnil(T); + lua_pushfstring(T, "error making socket non-blocking: %s", + strerror(errno)); + return 2; + } + + /* bind */ + if (bind(sock, address, alen)) { + close(sock); + lua_pushnil(T); + lua_pushfstring(T, "error binding socket: %s", + strerror(errno)); + return 2; + } + + /* listen to the socket */ + if (listen(sock, backlog) < 0) { + lua_pushnil(T); + lua_pushfstring(T, "error listening to the socket: %s", + strerror(errno)); + return 2; + } + + /* create userdata and set the metatable */ + w = lua_newuserdata(T, sizeof(struct ev_io)); + lua_pushvalue(T, lua_upvalueindex(1)); + lua_setmetatable(T, -2); + + /* initialize userdata */ + ev_io_init(w, NULL, sock, EV_READ); + w->data = NULL; + + return 1; +} + +static int +tcp4_listen(lua_State *T) +{ + const char *addr = luaL_checkstring(T, 1); + uint16_t port = (uint16_t)luaL_checknumber(T, 2); + int backlog = (int)luaL_optnumber(T, 3, MAXPENDING); + int sock; + struct sockaddr_in address; + + /* initialise the socketadr_in structure */ + memset(&address, 0, sizeof(struct sockaddr_in)); + address.sin_family = AF_INET; + if (addr[0] == '*' && addr[1] == '\0') + address.sin_addr.s_addr = INADDR_ANY; + else if (!inet_pton(AF_INET, addr, &address.sin_addr)) { + lua_pushnil(T); + lua_pushfstring(T, "cannot bind to '%s'", addr); + return 2; + } + address.sin_port = htons(port); + + /* create the TCP socket */ + sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock < 0) { + lua_pushnil(T); + lua_pushfstring(T, "error creating TCP socket: %s", + strerror(errno)); + return 2; + } + + return common_listen(T, (struct sockaddr *)&address, + sizeof(struct sockaddr_in), sock, backlog); +} + +static int +tcp6_listen(lua_State *T) +{ + const char *addr = luaL_checkstring(T, 1); + uint16_t port = (uint16_t)luaL_checknumber(T, 2); + int backlog = (int)luaL_optnumber(T, 3, MAXPENDING); + int sock; + struct sockaddr_in6 address; + + /* initialise the socketadr_in structure */ + memset(&address, 0, sizeof(struct sockaddr_in6)); + address.sin6_family = AF_INET6; + if (addr[0] == '*' && addr[1] == '\0') + address.sin6_addr = in6addr_any; + else if (!inet_pton(AF_INET6, addr, &address.sin6_addr)) { + lua_pushnil(T); + lua_pushfstring(T, "cannot bind to '%s'", addr); + return 2; + } + address.sin6_port = htons(port); + + /* create the TCP socket */ + sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); + if (sock < 0) { + lua_pushnil(T); + lua_pushfstring(T, "error creating TCP socket: %s", + strerror(errno)); + return 2; + } + + return common_listen(T, (struct sockaddr *)&address, + sizeof(struct sockaddr_in6), sock, backlog); +} diff --git a/lem/utils.c b/lem/utils.c new file mode 100644 index 0000000..17f8fe5 --- /dev/null +++ b/lem/utils.c @@ -0,0 +1,330 @@ +/* + * This file is part of LEM, a Lua Event Machine. + * Copyright 2011-2012 Emil Renner Berthing + * + * LEM is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * LEM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LEM. If not, see . + */ + +#include + +static int +sleeper_wakeup(lua_State *T) +{ + struct ev_timer *w; + lua_State *S; + int nargs; + + luaL_checktype(T, 1, LUA_TUSERDATA); + w = lua_touserdata(T, 1); + S = w->data; + if (S == NULL) { + lua_pushnil(T); + lua_pushliteral(T, "not sleeping"); + return 2; + } + + ev_timer_stop(LEM_ w); + + nargs = lua_gettop(T) - 1; + lua_settop(S, 0); + lua_xmove(T, S, nargs); + lem_queue(S, nargs); + w->data = NULL; + + /* return true */ + lua_pushboolean(T, 1); + return 1; +} + +static void +sleep_handler(EV_P_ struct ev_timer *w, int revents) +{ + lua_State *T = w->data; + + (void)revents; + + /* return nil, "timeout" */ + lem_queue(T, 2); + w->data = NULL; +} + +static int +sleeper_sleep(lua_State *T) +{ + struct ev_timer *w; + + luaL_checktype(T, 1, LUA_TUSERDATA); + w = lua_touserdata(T, 1); + if (w->data != NULL) { + lua_pushnil(T); + lua_pushliteral(T, "busy"); + return 2; + } + + if (lua_gettop(T) > 1 && !lua_isnil(T, 2)) { + ev_tstamp delay = (ev_tstamp)luaL_checknumber(T, 2); + + if (delay <= 0) { + /* return nil, "timeout" + * ..but yield the thread */ + lua_pushnil(T); + lua_pushvalue(T, lua_upvalueindex(1)); + lem_queue(T, 2); + + return lua_yield(T, 2); + } + + ev_timer_set(w, delay, 0); + ev_timer_start(LEM_ w); + } + + w->data = T; + + /* yield sleeper, nil, "timeout" */ + lua_settop(T, 1); + lua_pushnil(T); + lua_pushvalue(T, lua_upvalueindex(1)); + return lua_yield(T, 3); +} + +static int +sleeper_new(lua_State *T) +{ + struct ev_timer *w; + + /* create new sleeper object and set metatable */ + w = lua_newuserdata(T, sizeof(struct ev_timer)); + lua_pushvalue(T, lua_upvalueindex(1)); + lua_setmetatable(T, -2); + + ev_init(w, sleep_handler); + w->data = NULL; + + return 1; +} + +static int +timer_cancel(lua_State *T) +{ + struct ev_timer *w; + lua_State *S; + + luaL_checktype(T, 1, LUA_TUSERDATA); + w = lua_touserdata(T, 1); + S = w->data; + if (S == NULL) { + lua_pushnil(T); + lua_pushliteral(T, "expired"); + return 2; + } + + ev_timer_stop(LEM_ w); + lem_forgetthread(S); + + /* return true */ + lua_pushboolean(T, 1); + return 1; +} + +static void +timer_handler(EV_P_ struct ev_timer *w, int revents) +{ + lua_State *T = w->data; + + (void)revents; + + lua_settop(T, 1); + lem_queue(T, 0); + + /* mark this timer as expired */ + w->data = NULL; +} + +static int +timer_new(lua_State *T) +{ + ev_tstamp delay = (ev_tstamp)luaL_checknumber(T, 1); + struct ev_timer *w; + lua_State *S; + + luaL_checktype(T, 2, LUA_TFUNCTION); + + S = lem_newthread(); + lua_settop(T, 2); + lua_xmove(T, S, 1); + + /* create new timer object and set metatable */ + w = lua_newuserdata(T, sizeof(struct ev_timer)); + lua_pushvalue(T, lua_upvalueindex(1)); + lua_setmetatable(T, -2); + + if (delay > 0) { + w->data = S; + + /* push a reference of w to S */ + lua_pushvalue(T, -1); + lua_xmove(T, S, 1); + + ev_timer_init(w, timer_handler, delay, 0); + ev_timer_start(LEM_ w); + } else { + w->data = NULL; + + lem_queue(S, 0); + } + + return 1; +} + +static int +spawn(lua_State *T) +{ + lua_State *S; + int nargs; + + luaL_checktype(T, 1, LUA_TFUNCTION); + + S = lem_newthread(); + nargs = lua_gettop(T); + + lua_xmove(T, S, nargs); + + lem_queue(S, nargs - 1); + + lua_pushboolean(T, 1); + return 1; +} + +static int +yield_lua(lua_State *T) +{ + lem_queue(T, 0); + return lua_yield(T, 0); +} + +static int +sethandler_lua(lua_State *T) +{ + if (lua_gettop(T) > 0) { + luaL_checktype(T, 1, LUA_TFUNCTION); + lua_settop(T, 1); + } else + lua_pushboolean(T, 1); + + lem_sethandler(T); + + return 0; +} + +static int +exit_lua(lua_State *T) +{ + int status = (int)luaL_checknumber(T, 1); + + lem_exit(status); + + return 0; +} + +static int +thisthread(lua_State *T) +{ + lua_pushthread(T); + return 1; +} + +static int +suspend(lua_State *T) +{ + return lua_yield(T, 0); +} + +static int +resume(lua_State *T) +{ + int args; + lua_State *S; + + luaL_checktype(T, 1, LUA_TTHREAD); + S = lua_tothread(T, 1); + + args = lua_gettop(T) - 1; + lua_xmove(T, S, args); + lem_queue(S, args); + + return 0; +} + +int +luaopen_lem_utils(lua_State *L) +{ + /* create module table */ + lua_newtable(L); + + /* create new sleeper metatable */ + lua_newtable(L); + /* mt.__index = mt */ + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + /* mt.wakeup = */ + lua_pushcfunction(L, sleeper_wakeup); + lua_setfield(L, -2, "wakeup"); + /* mt.sleep = */ + lua_pushliteral(L, "timeout"); /* upvalue 1: "timeout" */ + lua_pushcclosure(L, sleeper_sleep, 1); + lua_setfield(L, -2, "sleep"); + /* set sleeper function */ + lua_pushcclosure(L, sleeper_new, 1); + lua_setfield(L, -2, "sleeper"); + + /* create new timer metatable */ + lua_newtable(L); + /* mt.__index = mt */ + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + /* mt.cancel = */ + lua_pushcfunction(L, timer_cancel); + lua_setfield(L, -2, "cancel"); + /* set timer function */ + lua_pushcclosure(L, timer_new, 1); + lua_setfield(L, -2, "timer"); + + /* set spawn function */ + lua_pushcfunction(L, spawn); + lua_setfield(L, -2, "spawn"); + + /* set yield function */ + lua_pushcfunction(L, yield_lua); + lua_setfield(L, -2, "yield"); + + /* set sethandler function */ + lua_pushcfunction(L, sethandler_lua); + lua_setfield(L, -2, "sethandler"); + + /* set exit function */ + lua_pushcfunction(L, exit_lua); + lua_setfield(L, -2, "exit"); + + /* set thisthread function */ + lua_pushcfunction(L, thisthread); + lua_setfield(L, -2, "thisthread"); + /* set suspend function */ + lua_pushcfunction(L, suspend); + lua_setfield(L, -2, "suspend"); + /* set resume function */ + lua_pushcfunction(L, resume); + lua_setfield(L, -2, "resume"); + + return 1; +} diff --git a/libev.c b/libev.c deleted file mode 100644 index 45bfc4f..0000000 --- a/libev.c +++ /dev/null @@ -1,20 +0,0 @@ -/* - * This file is part of LEM, a Lua Event Machine. - * Copyright 2011-2012 Emil Renner Berthing - * - * LEM is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * LEM is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LEM. If not, see . - */ - -#include "libev/ev-config.h" -#include "libev/ev.c" diff --git a/lua.c b/lua.c deleted file mode 100644 index 1e4ddc5..0000000 --- a/lua.c +++ /dev/null @@ -1,52 +0,0 @@ -/* setup for luaconf.h */ -#define LUA_CORE -#define LUA_LIB -#define ltable_c -#define lvm_c -#include "luaconf.h" - -/* do not export internal symbols */ -#undef LUAI_FUNC -#undef LUAI_DDEC -#undef LUAI_DDEF -#define LUAI_FUNC static -#define LUAI_DDEC static -#define LUAI_DDEF static - -/* core */ -#include "lua/lapi.c" -#include "lua/lcode.c" -#include "lua/lctype.c" -#include "lua/ldebug.c" -#include "lua/ldo.c" -#include "lua/ldump.c" -#include "lua/lfunc.c" -#include "lua/lgc.c" -#include "lua/llex.c" -#include "lua/lmem.c" -#include "lua/lobject.c" -#include "lua/lopcodes.c" -#include "lua/lparser.c" -#include "lua/lstate.c" -#include "lua/lstring.c" -#include "lua/ltable.c" -#include "lua/ltm.c" -#include "lua/lundump.c" -#include "lua/lvm.c" -#include "lua/lzio.c" - -/* auxiliary library */ -#include "lua/lauxlib.c" - -/* standard library */ -#include "lua/lbaselib.c" -#include "lua/lbitlib.c" -#include "lua/lcorolib.c" -#include "lua/ldblib.c" -#include "lua/liolib.c" -#include "lua/lmathlib.c" -#include "lua/loadlib.c" -#include "lua/loslib.c" -#include "lua/lstrlib.c" -#include "lua/ltablib.c" -#include "lua/linit.c" diff --git a/lua/luaconf.h.in b/lua/luaconf.h.in index b0a75e8..6f4be5b 100644 --- a/lua/luaconf.h.in +++ b/lua/luaconf.h.in @@ -100,8 +100,8 @@ #else /* }{ */ -#define LUA_LDIR "@path@/" -#define LUA_CDIR "@cpath@/" +#define LUA_LDIR "@lmoddir@/" +#define LUA_CDIR "@cmoddir@/" #define LUA_PATH_DEFAULT \ LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \ LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua;" "./?.lua" diff --git a/repl.lua b/repl.lua deleted file mode 100644 index 2e4fa69..0000000 --- a/repl.lua +++ /dev/null @@ -1,117 +0,0 @@ --- --- This file is part of LEM, a Lua Event Machine. --- Copyright 2011-2012 Emil Renner Berthing --- --- LEM is free software: you can redistribute it and/or --- modify it under the terms of the GNU General Public License as --- published by the Free Software Foundation, either version 3 of --- the License, or (at your option) any later version. --- --- LEM is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with LEM. If not, see . --- - -local utils = require 'lem.utils' - -local load = load -local format = string.format -local concat = table.concat -local tostring = tostring -local select = select - -local function repl(done, name, ins, outs) - if not outs then outs = ins end - - local getcode, onreturn, onerror - - name = '=' .. name - - function getcode() - local res, err = outs:write('> ') - if not res then return done(nil, err) end - - local line - line, err = ins:read('*l') - if not line then return done(nil, err) end - - line = line:gsub('^=', 'return ') - - while true do - res, err = load(line, name) - if res then break end - - if not err:match("''") then - return onerror(err) - end - - res, err = outs:write('>> ') - if not res then return done(nil, err) end - - res, err = ins:read('*l') - if not res then return done(nil, err) end - - line = line .. res - end - - utils.sethandler(onerror) - return onreturn(res()) - end - - function onreturn(...) - utils.sethandler() - local args = select('#', ...) - if args == 0 then return getcode() end - - local rstr - do - local t, ti = { ... }, nil - for i = 1, args - 1 do - t[i] = tostring(t[i]) - end - t[args] = tostring(t[args])..'\n' - - rstr = concat(t, '\t') - end - - local ok, err = outs:write(rstr) - if not ok then return done(nil, err) end - - return getcode() - end - - function onerror(err) - local ok, err = outs:write(format("%s\n", err)) - if not ok then return done(nil, err) end - - return getcode() - end - - return getcode() -end - -return { - wait = function(name, ins, outs) - local sleeper = utils.sleeper() - local function done(...) - return sleeper:wakeup(...) - end - - utils.spawn(repl, done, name, ins, outs) - - return sleeper:sleep() - end, - - go = function(name, ins, outs) - local function done() - return outs:write('\n') - end - return repl(done, name, ins, outs) - end, -} - --- vim: ts=2 sw=2 noet: diff --git a/test.lua b/test.lua deleted file mode 100755 index bde0dc4..0000000 --- a/test.lua +++ /dev/null @@ -1,51 +0,0 @@ -#!./lem --- --- This file is part of LEM, a Lua Event Machine. --- Copyright 2011-2012 Emil Renner Berthing --- --- LEM is free software: you can redistribute it and/or --- modify it under the terms of the GNU General Public License as --- published by the Free Software Foundation, either version 3 of --- the License, or (at your option) any later version. --- --- LEM is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with LEM. If not, see . --- - -package.path = package.path .. ';../?.lua' -package.cpath = package.cpath .. ';../?.so' - -local utils = require 'lem.utils' - -local function sleep(n) - utils.sleeper():sleep(n) -end - ---print('package.cpath = ', package.cpath) - -print 'Saying booh in 2.5 seconds' - -utils.timer(2.5, function() print 'Booh!' end) - -print 'Sleeping 5 seconds' - -sleep(5) - -print 'Done sleeping' - -print 'Spawning new thread..' -utils.spawn(function(s) print(s) end, "I'm the new thread!") - -print 'Yielding..' -utils.yield() - -print 'Back.' - -print 'Bye!' - --- vim: syntax=lua ts=3 sw=3 et: diff --git a/test/ctest.lua b/test/ctest.lua new file mode 100755 index 0000000..e0bb6c0 --- /dev/null +++ b/test/ctest.lua @@ -0,0 +1,48 @@ +#!bin/lem +-- +-- This file is part of LEM, a Lua Event Machine. +-- Copyright 2011-2012 Emil Renner Berthing +-- +-- LEM is free software: you can redistribute it and/or +-- modify it under the terms of the GNU General Public License as +-- published by the Free Software Foundation, either version 3 of +-- the License, or (at your option) any later version. +-- +-- LEM is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with LEM. If not, see . +-- + +print('Entered ' .. arg[0]) + +local utils = require 'lem.utils' +local streams = require 'lem.streams' + +local sleeper = utils.sleeper() +local iconn, oconn = assert(streams.tcp_connect('127.0.0.1', arg[1] or 8080)) + +for i = 1, 10 do + assert(oconn:write('ping\n')) + + local line, err = iconn:read('*l') + if not line then + if err == 'closed' then + print("Server closed connection") + return + end + + error(err) + end + + print("Server answered: '" .. line .. "'") +end + +oconn:write('quit\n') + +print('Exiting ' .. arg[0]) + +-- vim: syntax=lua ts=2 sw=2 noet: diff --git a/test/htest.lua b/test/htest.lua new file mode 100755 index 0000000..4272118 --- /dev/null +++ b/test/htest.lua @@ -0,0 +1,148 @@ +#!bin/lem +-- +-- This file is part of LEM, a Lua Event Machine. +-- Copyright 2011-2012 Emil Renner Berthing +-- +-- LEM is free software: you can redistribute it and/or +-- modify it under the terms of the GNU General Public License as +-- published by the Free Software Foundation, either version 3 of +-- the License, or (at your option) any later version. +-- +-- LEM is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with LEM. If not, see . +-- + +local utils = require 'lem.utils' +local hathaway = require 'lem.hathaway' +hathaway.import() + +GET('/', function(req, res) + res.status = 302 + res.headers['Location'] = '/dump' +end) + +GET('/hello', function(req, res) + res.headers['Content-Type'] = 'text/plain' + res:add('Hello, World!\n') +end) + +GET('/self', function(req, res) + res.headers['Content-Type'] = 'text/plain' + res.file = arg[0] +end) + +GET('/dump', function(req, res) + local accept = req.headers['Accept'] + if accept and accept:match('application/xhtml%+xml') then + res.headers['Content-Type'] = 'application/xhtml+xml' + else + res.headers['Content-Type'] = 'text/html' + end + res:add([[ + + + + Hathaway HTTP dump + + + + +

Request

+ + + + +
Method:%s
Uri:%s
Version:%s
+ +

Headers

+ +]], req.method or '', req.uri or '', req.version) + + for k, v in pairs(req.headers) do + res:add(' \n', k, v) + end + + res:add([[ +
%s%s
+ +

Body

+
+

+
+ +

+
+ +
+

+ + +

+
+ + + +]]) +end) + +local function urldecode(str) + return str:gsub('+', ' '):gsub('%%(%x%x)', function (str) + return string.char(tonumber(str, 16)) + end) +end + +local function parseform(str) + local t = {} + for k, v in str:gmatch('([^&]+)=([^&]*)') do + t[urldecode(k)] = urldecode(v) + end + return t +end + +POST('/form', function(req, res) + res.headers['Content-Type'] = 'text/plain' + local body =req:body() + res:add("You sent:\n%s\n", body) + res:add('{\n') + for k, v in pairs(parseform(body)) do + res:add(" ['%s'] = '%s'\n", k, v) + end + res:add('}\n') +end) + +GET('/close', function(req, res) + res.headers['Content-Type'] = 'text/plain' + res.headers['Connection'] = 'close' + res:add('This connection should close\n') +end) + +POST('/quit', function(req, res) + local body = req:body() + + req.headers['Content-Type'] = 'text/plain' + + if body == 'quit=secret' then + res:add("Bye o/\n") + hathaway.server:close() + else + res:add("You didn't supply the right value...\n") + end +end) + +GETM('^/hello/([^/]+)$', function(req, res, name) + res.headers['Content-Type'] = 'text/plain' + res:add('Hello, %s!\n', name) +end) + +hathaway.debug = print +Hathaway('*', arg[1] or 8080) +utils.exit(0) -- otherwise open connections will keep us running + +-- vim: syntax=lua ts=2 sw=2 noet: diff --git a/test/httptest.lua b/test/httptest.lua new file mode 100755 index 0000000..7bc3b93 --- /dev/null +++ b/test/httptest.lua @@ -0,0 +1,58 @@ +#!bin/lem +-- +-- This file is part of LEM, a Lua Event Machine. +-- Copyright 2011-2012 Emil Renner Berthing +-- +-- LEM is free software: you can redistribute it and/or +-- modify it under the terms of the GNU General Public License as +-- published by the Free Software Foundation, either version 3 of +-- the License, or (at your option) any later version. +-- +-- LEM is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with LEM. If not, see . +-- + +local utils = require 'lem.utils' +local streams = require 'lem.streams' +local http = require 'lem.http' + +local format = string.format +local concat = table.concat + +local done = false + +utils.spawn(function() + --local istream, ostream = assert(streams.tcp_connect('www.google.dk', 80)) + local istream, ostream = assert(streams.tcp_connect('127.0.0.1', 8080)) + + print('\nConnected.') + + for i = 1, 2 do + --assert(ostream:write('GET / HTTP/1.1\r\nHost: www.google.dk\r\nConnection: close\r\n\r\n')) + assert(ostream:write('GET / HTTP/1.1\r\nHost: www.google.dk\r\n\r\n')) + + local res = assert(istream:read('HTTPResponse')) + + print(format('\nHTTP/%s %d %s', res.version, res.status, res.text)) + for k, v in pairs(res.headers) do + print(format('%s: %s', k, v)) + end + + print(format('\n#body = %d', #assert(res:body()))) + end + + done = true +end) + +local write, yield = io.write, utils.yield +repeat + write('.') + yield() +until done + +-- vim: set ts=2 sw=2 noet: diff --git a/test/multiplexing.lua b/test/multiplexing.lua new file mode 100755 index 0000000..8265e4c --- /dev/null +++ b/test/multiplexing.lua @@ -0,0 +1,79 @@ +#!bin/lem +-- +-- This file is part of LEM, a Lua Event Machine. +-- Copyright 2011-2012 Emil Renner Berthing +-- +-- LEM is free software: you can redistribute it and/or +-- modify it under the terms of the GNU General Public License as +-- published by the Free Software Foundation, either version 3 of +-- the License, or (at your option) any later version. +-- +-- LEM is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with LEM. If not, see . +-- + +local utils = require 'lem.utils' +local streams = require 'lem.streams' +local queue = require 'lem.streams.queue' + +local exit = false +local ticker = utils.sleeper() +local stdout = queue.wrap(streams.stdout) + +do + local format = string.format + function queue.QOStream:printf(...) + return self:write(format(...)) + end +end + +-- this function just reads lines from a +-- stream and prints them to stdout +local function poll(stream, name) + repeat + local line, err = stream:read('*l') + if not line then + stdout:printf('%s: %s\n', name, err) + break + end + + stdout:printf('%s: %s\n', name, line) + until line == 'quit' + + exit = true + ticker:wakeup() +end + +-- type 'mkfifo pipe' to create a named pipe for this script +-- and do 'cat > pipe' (in another terminal) to write to it +local pipe = assert(streams.open(arg[1] or 'pipe', 'r')) + +-- spawn coroutines to read from stdin and the pipe +utils.spawn(poll, streams.stdin, 'stdin') +utils.spawn(poll, pipe, 'pipe') + +do + --local out = streams.stderr + local out = stdout + local sound + + repeat + if sound == 'tick\n' then + sound = 'tock\n' + else + sound = 'tick\n' + end + out:write(sound) + ticker:sleep(1.0) + until exit +end + +streams.stdin:close() +pipe:close() + +-- vim: syntax=lua ts=2 sw=2 noet: diff --git a/test/sleep.lua b/test/sleep.lua new file mode 100755 index 0000000..41f7fe5 --- /dev/null +++ b/test/sleep.lua @@ -0,0 +1,46 @@ +#!bin/lem +-- +-- This file is part of LEM, a Lua Event Machine. +-- Copyright 2011-2012 Emil Renner Berthing +-- +-- LEM is free software: you can redistribute it and/or +-- modify it under the terms of the GNU General Public License as +-- published by the Free Software Foundation, either version 3 of +-- the License, or (at your option) any later version. +-- +-- LEM is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with LEM. If not, see . +-- + +local utils = require 'lem.utils' + +local sum, coros, n = 0, 0, 0 +local done = utils.sleeper() + +local function test(t) + local sleeper = utils.sleeper() + local diff = utils.now() + sleeper:sleep(t) + diff = utils.now() - diff + + print(string.format('%fs, %fms off', diff, 1000*(diff - t))) + sum = sum + math.abs(diff - t) + n = n + 1 + if n == coros then done:wakeup() end +end + +for t = 0, 3, 0.1 do + coros = coros + 1 + utils.spawn(test, t) +end + +done:sleep() + +print(string.format('%fms off on average', 1000*sum / n)) + +-- vim: syntax=lua ts=2 sw=2 noet: diff --git a/test/stest.lua b/test/stest.lua new file mode 100755 index 0000000..f54a415 --- /dev/null +++ b/test/stest.lua @@ -0,0 +1,64 @@ +#!bin/lem +-- +-- This file is part of LEM, a Lua Event Machine. +-- Copyright 2011-2012 Emil Renner Berthing +-- +-- LEM is free software: you can redistribute it and/or +-- modify it under the terms of the GNU General Public License as +-- published by the Free Software Foundation, either version 3 of +-- the License, or (at your option) any later version. +-- +-- LEM is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with LEM. If not, see . +-- + +print('Entered ' .. arg[0]) + +local utils = require 'lem.utils' +local streams = require 'lem.streams' + +local server = assert(streams.tcp4_listen('*', arg[1] or 8080)) + +--timer(10, function() exit(0) end) +utils.timer(10, function() + print 'Closing server' + server:close() +end) + +local ok, err = server:autospawn(function(i, o) + print 'Accepted a connection' + local sleeper = utils.sleeper() + + while true do + local line, err = i:read('*l') + if not line then + if err == 'closed' then + print("Client closed connection") + return + end + + error(err) + end + + print("Client sent: '" .. line .. "'") + if line ~= 'ping' then break end + + sleeper:sleep(0.4) + assert(o:write('pong\n')) + end + + print "Ok, I'm out" + assert(i:close()) + assert(o:close()) +end) + +if not ok and err ~= 'interrupted' then error(err) end + +print('Exiting ' .. arg[0]) + +-- vim: syntax=lua ts=2 sw=2 noet: diff --git a/test/test.lua b/test/test.lua new file mode 100755 index 0000000..57d76f2 --- /dev/null +++ b/test/test.lua @@ -0,0 +1,48 @@ +#!bin/lem +-- +-- This file is part of LEM, a Lua Event Machine. +-- Copyright 2011-2012 Emil Renner Berthing +-- +-- LEM is free software: you can redistribute it and/or +-- modify it under the terms of the GNU General Public License as +-- published by the Free Software Foundation, either version 3 of +-- the License, or (at your option) any later version. +-- +-- LEM is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with LEM. If not, see . +-- + +local utils = require 'lem.utils' + +local function sleep(n) + utils.sleeper():sleep(n) +end + +--print('package.cpath = ', package.cpath) + +print 'Saying booh in 2.5 seconds' + +utils.timer(2.5, function() print 'Booh!' end) + +print 'Sleeping 5 seconds' + +sleep(5) + +print 'Done sleeping' + +print 'Spawning new thread..' +utils.spawn(function(s) print(s) end, "I'm the new thread!") + +print 'Yielding..' +utils.yield() + +print 'Back.' + +print 'Bye!' + +-- vim: syntax=lua ts=3 sw=3 et: diff --git a/test/test2.lua b/test/test2.lua new file mode 100755 index 0000000..b64eedf --- /dev/null +++ b/test/test2.lua @@ -0,0 +1,40 @@ +#!bin/lem +-- +-- This file is part of LEM, a Lua Event Machine. +-- Copyright 2011-2012 Emil Renner Berthing +-- +-- LEM is free software: you can redistribute it and/or +-- modify it under the terms of the GNU General Public License as +-- published by the Free Software Foundation, either version 3 of +-- the License, or (at your option) any later version. +-- +-- LEM is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with LEM. If not, see . +-- + +print 'Entered test.lua' + +local utils = require 'lem.utils' + +local function sleep(n) + utils.sleeper():sleep(n) +end + +print 'Saying "Fee!" in 1 second' +utils.timer(1, function() print 'Fee!' end) + +print 'Saying "Fo!" in 3 seconds' +utils.timer(3, function() print 'Fo!' end) + +utils.spawn(function() + print 'Sleeping for 2 seconds, then saying "Fi!" before the script ends' + sleep(2) + print 'Fi!' +end) + +-- vim: syntax=lua ts=3 sw=3 et: diff --git a/test2.lua b/test2.lua deleted file mode 100755 index 06ca7c9..0000000 --- a/test2.lua +++ /dev/null @@ -1,43 +0,0 @@ -#!./lem --- --- This file is part of LEM, a Lua Event Machine. --- Copyright 2011-2012 Emil Renner Berthing --- --- LEM is free software: you can redistribute it and/or --- modify it under the terms of the GNU General Public License as --- published by the Free Software Foundation, either version 3 of --- the License, or (at your option) any later version. --- --- LEM is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with LEM. If not, see . --- - -package.path = package.path .. ';../?.lua' -package.cpath = package.cpath .. ';../?.so' - -print 'Entered test.lua' - -local utils = require 'lem.utils' - -local function sleep(n) - utils.sleeper():sleep(n) -end - -print 'Saying "Fee!" in 1 second' -utils.timer(1, function() print 'Fee!' end) - -print 'Saying "Fo!" in 3 seconds' -utils.timer(3, function() print 'Fo!' end) - -utils.spawn(function() - print 'Sleeping for 2 seconds, then saying "Fi!" before the script ends' - sleep(2) - print 'Fi!' -end) - --- vim: syntax=lua ts=3 sw=3 et: diff --git a/utils.c b/utils.c deleted file mode 100644 index 17f8fe5..0000000 --- a/utils.c +++ /dev/null @@ -1,330 +0,0 @@ -/* - * This file is part of LEM, a Lua Event Machine. - * Copyright 2011-2012 Emil Renner Berthing - * - * LEM is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * LEM is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LEM. If not, see . - */ - -#include - -static int -sleeper_wakeup(lua_State *T) -{ - struct ev_timer *w; - lua_State *S; - int nargs; - - luaL_checktype(T, 1, LUA_TUSERDATA); - w = lua_touserdata(T, 1); - S = w->data; - if (S == NULL) { - lua_pushnil(T); - lua_pushliteral(T, "not sleeping"); - return 2; - } - - ev_timer_stop(LEM_ w); - - nargs = lua_gettop(T) - 1; - lua_settop(S, 0); - lua_xmove(T, S, nargs); - lem_queue(S, nargs); - w->data = NULL; - - /* return true */ - lua_pushboolean(T, 1); - return 1; -} - -static void -sleep_handler(EV_P_ struct ev_timer *w, int revents) -{ - lua_State *T = w->data; - - (void)revents; - - /* return nil, "timeout" */ - lem_queue(T, 2); - w->data = NULL; -} - -static int -sleeper_sleep(lua_State *T) -{ - struct ev_timer *w; - - luaL_checktype(T, 1, LUA_TUSERDATA); - w = lua_touserdata(T, 1); - if (w->data != NULL) { - lua_pushnil(T); - lua_pushliteral(T, "busy"); - return 2; - } - - if (lua_gettop(T) > 1 && !lua_isnil(T, 2)) { - ev_tstamp delay = (ev_tstamp)luaL_checknumber(T, 2); - - if (delay <= 0) { - /* return nil, "timeout" - * ..but yield the thread */ - lua_pushnil(T); - lua_pushvalue(T, lua_upvalueindex(1)); - lem_queue(T, 2); - - return lua_yield(T, 2); - } - - ev_timer_set(w, delay, 0); - ev_timer_start(LEM_ w); - } - - w->data = T; - - /* yield sleeper, nil, "timeout" */ - lua_settop(T, 1); - lua_pushnil(T); - lua_pushvalue(T, lua_upvalueindex(1)); - return lua_yield(T, 3); -} - -static int -sleeper_new(lua_State *T) -{ - struct ev_timer *w; - - /* create new sleeper object and set metatable */ - w = lua_newuserdata(T, sizeof(struct ev_timer)); - lua_pushvalue(T, lua_upvalueindex(1)); - lua_setmetatable(T, -2); - - ev_init(w, sleep_handler); - w->data = NULL; - - return 1; -} - -static int -timer_cancel(lua_State *T) -{ - struct ev_timer *w; - lua_State *S; - - luaL_checktype(T, 1, LUA_TUSERDATA); - w = lua_touserdata(T, 1); - S = w->data; - if (S == NULL) { - lua_pushnil(T); - lua_pushliteral(T, "expired"); - return 2; - } - - ev_timer_stop(LEM_ w); - lem_forgetthread(S); - - /* return true */ - lua_pushboolean(T, 1); - return 1; -} - -static void -timer_handler(EV_P_ struct ev_timer *w, int revents) -{ - lua_State *T = w->data; - - (void)revents; - - lua_settop(T, 1); - lem_queue(T, 0); - - /* mark this timer as expired */ - w->data = NULL; -} - -static int -timer_new(lua_State *T) -{ - ev_tstamp delay = (ev_tstamp)luaL_checknumber(T, 1); - struct ev_timer *w; - lua_State *S; - - luaL_checktype(T, 2, LUA_TFUNCTION); - - S = lem_newthread(); - lua_settop(T, 2); - lua_xmove(T, S, 1); - - /* create new timer object and set metatable */ - w = lua_newuserdata(T, sizeof(struct ev_timer)); - lua_pushvalue(T, lua_upvalueindex(1)); - lua_setmetatable(T, -2); - - if (delay > 0) { - w->data = S; - - /* push a reference of w to S */ - lua_pushvalue(T, -1); - lua_xmove(T, S, 1); - - ev_timer_init(w, timer_handler, delay, 0); - ev_timer_start(LEM_ w); - } else { - w->data = NULL; - - lem_queue(S, 0); - } - - return 1; -} - -static int -spawn(lua_State *T) -{ - lua_State *S; - int nargs; - - luaL_checktype(T, 1, LUA_TFUNCTION); - - S = lem_newthread(); - nargs = lua_gettop(T); - - lua_xmove(T, S, nargs); - - lem_queue(S, nargs - 1); - - lua_pushboolean(T, 1); - return 1; -} - -static int -yield_lua(lua_State *T) -{ - lem_queue(T, 0); - return lua_yield(T, 0); -} - -static int -sethandler_lua(lua_State *T) -{ - if (lua_gettop(T) > 0) { - luaL_checktype(T, 1, LUA_TFUNCTION); - lua_settop(T, 1); - } else - lua_pushboolean(T, 1); - - lem_sethandler(T); - - return 0; -} - -static int -exit_lua(lua_State *T) -{ - int status = (int)luaL_checknumber(T, 1); - - lem_exit(status); - - return 0; -} - -static int -thisthread(lua_State *T) -{ - lua_pushthread(T); - return 1; -} - -static int -suspend(lua_State *T) -{ - return lua_yield(T, 0); -} - -static int -resume(lua_State *T) -{ - int args; - lua_State *S; - - luaL_checktype(T, 1, LUA_TTHREAD); - S = lua_tothread(T, 1); - - args = lua_gettop(T) - 1; - lua_xmove(T, S, args); - lem_queue(S, args); - - return 0; -} - -int -luaopen_lem_utils(lua_State *L) -{ - /* create module table */ - lua_newtable(L); - - /* create new sleeper metatable */ - lua_newtable(L); - /* mt.__index = mt */ - lua_pushvalue(L, -1); - lua_setfield(L, -2, "__index"); - /* mt.wakeup = */ - lua_pushcfunction(L, sleeper_wakeup); - lua_setfield(L, -2, "wakeup"); - /* mt.sleep = */ - lua_pushliteral(L, "timeout"); /* upvalue 1: "timeout" */ - lua_pushcclosure(L, sleeper_sleep, 1); - lua_setfield(L, -2, "sleep"); - /* set sleeper function */ - lua_pushcclosure(L, sleeper_new, 1); - lua_setfield(L, -2, "sleeper"); - - /* create new timer metatable */ - lua_newtable(L); - /* mt.__index = mt */ - lua_pushvalue(L, -1); - lua_setfield(L, -2, "__index"); - /* mt.cancel = */ - lua_pushcfunction(L, timer_cancel); - lua_setfield(L, -2, "cancel"); - /* set timer function */ - lua_pushcclosure(L, timer_new, 1); - lua_setfield(L, -2, "timer"); - - /* set spawn function */ - lua_pushcfunction(L, spawn); - lua_setfield(L, -2, "spawn"); - - /* set yield function */ - lua_pushcfunction(L, yield_lua); - lua_setfield(L, -2, "yield"); - - /* set sethandler function */ - lua_pushcfunction(L, sethandler_lua); - lua_setfield(L, -2, "sethandler"); - - /* set exit function */ - lua_pushcfunction(L, exit_lua); - lua_setfield(L, -2, "exit"); - - /* set thisthread function */ - lua_pushcfunction(L, thisthread); - lua_setfield(L, -2, "thisthread"); - /* set suspend function */ - lua_pushcfunction(L, suspend); - lua_setfield(L, -2, "suspend"); - /* set resume function */ - lua_pushcfunction(L, resume); - lua_setfield(L, -2, "resume"); - - return 1; -} -- cgit v1.2.1