├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── conf └── server.cfg ├── docs ├── Contents.md ├── Introduction.md ├── cmd-line.md ├── configuration.md ├── first-webapp.md ├── getting-started.md ├── http-client.md ├── logging.md ├── proxy-http.md ├── resp-obj.md ├── second-webapp.md ├── template-view.md ├── third-webapp.md ├── user-threads.md └── user-timers.md ├── lib ├── luapack.lua ├── luaw_constants.lua ├── luaw_data_structs_lib.lua ├── luaw_http.lua ├── luaw_init.lua ├── luaw_logging.lua ├── luaw_scheduler.lua ├── luaw_tcp.lua ├── luaw_timer.lua ├── luaw_utils.lua ├── luaw_webapp.lua └── unit_testing.lua ├── sample ├── conf │ └── server.cfg ├── proxy_handler.lua └── webapps │ └── myapp │ ├── handlers │ ├── handler-address.lua │ ├── handler-fileupload-display.lua │ ├── handler-fileupload-process.lua │ ├── handler-hellouser.lua │ ├── handler-helloworld.lua │ ├── handler-read-lpack.lua │ └── handler-write-lpack.lua │ ├── views │ └── view-address.lua │ └── web.lua └── src ├── Makefile ├── http_parser.c ├── http_parser.h ├── lfs.c ├── lfs.h ├── lua_lpack.c ├── lua_lpack.h ├── luaw_common.c ├── luaw_common.h ├── luaw_http_parser.c ├── luaw_http_parser.h ├── luaw_logging.c ├── luaw_logging.h ├── luaw_server.c ├── luaw_tcp.c ├── luaw_tcp.h ├── luaw_timer.c └── luaw_timer.h /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.o 2 | **/*.so 3 | **/*.log 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/libuv"] 2 | path = deps/libuv 3 | url = https://github.com/libuv/libuv.git 4 | [submodule "deps/luajit-2.0"] 5 | path = deps/luajit-2.0 6 | url = http://luajit.org/git/luajit-2.0.git 7 | [submodule "deps/lua-PUC-Rio"] 8 | path = deps/lua-PUC-Rio 9 | url = https://github.com/lua/lua.git 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 raksoras 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for building Luaw 2 | # == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT ======================= 3 | 4 | export UVDIR= deps/libuv 5 | export UVLIB= deps/libuv/.libs/libuv.a 6 | 7 | ifeq ($(LUAVM),luajit) 8 | export LUADIR= deps/luajit-2.0 9 | export LUALIB= deps/luajit-2.0/src/libluajit.a 10 | export OSXLDFLAGS= "-Wl,-pagezero_size,10000 -Wl,-image_base,100000000" 11 | else 12 | export LUADIR= deps/lua-PUC-Rio 13 | export LUALIB= deps/lua-PUC-Rio/src/liblua.a 14 | export OSXLDFLAGS= 15 | endif 16 | 17 | # == END OF USER SETTINGS -- NO NEED TO CHANGE ANYTHING BELOW THIS LINE ======= 18 | 19 | # Supported platforms 20 | PLATS= aix ansi bsd freebsd generic linux macosx mingw posix solaris 21 | ALL= all 22 | 23 | # Targets start here. 24 | 25 | all: 26 | @echo "Please do 'make PLATFORM' where PLATFORM is one of these:" 27 | @echo " $(PLATS)" 28 | 29 | $(UVLIB): $(UVDIR)/Makefile 30 | $(MAKE) -C $(UVDIR) 31 | 32 | $(UVDIR)/Makefile: $(UVDIR)/configure 33 | cd $(UVDIR) && ./configure 34 | 35 | $(UVDIR)/configure: $(UVDIR)/autogen.sh 36 | cd $(UVDIR) && sh autogen.sh 37 | 38 | $(LUADIR)/src/libluajit.a: 39 | $(MAKE) -C $(LUADIR) 40 | 41 | $(LUADIR)/src/liblua.a: 42 | $(MAKE) -C $(LUADIR) $(TARGET) 43 | 44 | luaw: 45 | $(MAKE) -C src $(ALL) SYSLDFLAGS=$(SYSLDFLAGS) CC=$(CC) 46 | 47 | # Convenience targets for popular platforms 48 | 49 | aix: TARGET= aix 50 | aix: $(UVLIB) $(LUALIB) 51 | $(MAKE) -C src $(ALL) CC="xlc" CFLAGS="-O2" SYSLIBS="-ldl" SYSLDFLAGS="-brtl -bexpall" 52 | 53 | ansi: TARGET= ansi 54 | ansi: $(UVLIB) $(LUALIB) 55 | $(MAKE) -C src $(ALL) 56 | 57 | bsd: TARGET= bsd 58 | bsd: $(UVLIB) $(LUALIB) 59 | $(MAKE) -C src $(ALL) SYSLIBS="-Wl,-E" 60 | 61 | freebsd: TARGET= freebsd 62 | freebsd: $(UVLIB) $(LUALIB) 63 | $(MAKE) -C src $(ALL) SYSLIBS="-Wl,-E" 64 | 65 | linux: TARGET= linux 66 | linux: SYSLIBS= -Wl,-E -ldl 67 | linux: $(UVLIB) $(LUALIB) 68 | $(MAKE) -C src $(ALL) SYSLIBS="-lrt -Wl,-E -ldl" 69 | 70 | macosx: TARGET= macosx 71 | macosx: $(UVLIB) $(LUALIB) 72 | $(MAKE) -C src $(ALL) CC="cc" SYSLDFLAGS=$(OSXLDFLAGS) 73 | 74 | posix: TARGET= posix 75 | posix: $(UVLIB) $(LUALIB) 76 | $(MAKE) -C src $(ALL) 77 | 78 | solaris: TARGET= solaris 79 | solaris: $(UVLIB) $(LUALIB) 80 | $(MAKE) -C src $(ALL) SYSLIBS="-ldl" 81 | 82 | #build objects management 83 | 84 | install: 85 | $(MAKE) -C src install 86 | 87 | install-sample: 88 | $(MAKE) -C src install-sample 89 | 90 | uninstall: 91 | $(MAKE) -C src uninstall 92 | 93 | clean: $(UVDIR)/Makefile 94 | $(MAKE) -C deps/luajit-2.0 clean 95 | $(MAKE) -C deps/lua-PUC-Rio clean 96 | $(MAKE) -C $(UVDIR) distclean 97 | $(MAKE) -C src clean 98 | 99 | # list targets that do not create files (but not all makes understand .PHONY) 100 | .PHONY: all check_plat $(LUALIB) $(PLATS) luaw install uninstall clean $(LUADIR)/src/libluajit.a $(LUADIR)/src/liblua.a 101 | 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Luaw - Lua meets Node.js 2 | ======================== 3 | *** 4 | 5 | Luaw stands for "Lua web server". It also matches abbreviation for an air traffic controller command "Line Up And Wait" that closely resembles the way it handles multiple requests using event loop :) 6 | 7 | Luaw is an event driven, non blocking IO based HTTP application server inspired by Node.js. It uses Node.js's excellent async library libuv to do non-blocking IO but instead of Javascript it uses [Lua](http://www.lua.org/) as its primary language for application development. Luaw takes advantage of Lua's first class coroutine support to avoid [callback spaghetti problem](http://callbackhell.com/). This makes writing async IO code as straight forward as writing sequential code while all the heavy-lifting of application state management is transparently handled by Lua coroutines. This mean a Luaw application developer gets best of both worlds - [scale](http://www.kegel.com/c10k.html) of event driven IO and code simplicity of blocking IO. 8 | 9 | 10 | ##Features 11 | 12 | 1. Full HTTP 1.1 support with persistent/keep-alive connections and HTTP pipelining with configurable connect and read timeouts. 13 | 2. Two ways to read and parse incoming HTTP requests, 14 | - Reading whole request in memory and then parsing it which is suitable for majority of applications. 15 | - Reading request as a stream and parsing it as it arrives in parts to minimize latency and server memory footprint for high traffic, high performance applications like HTTP proxies or HTTP load balancers. 16 | 3. Similarly on the output side it supports, 17 | - Buffering entire content in memory before writing it out to client with the correct "Content-Length" header, or 18 | - Streaming response continuously as it is generated using HTTP 1.1 chunked encoding to keep server memory pressure minimum. 19 | 4. Fully asynchronous DNS and HTTP client for making remote HTTP calls from server 20 | 5. [Sinatra](http://www.sinatrarb.com/) like web application framework that supports mapping URLs to REST resources with full support for path and query parameters. 21 | 6. Luaw template views for server side dynamic content generation . Luaw template views use streaming output with chunked encoding described above to eliminate need for huge server side memory buffers to buffer output generated by the templates. 22 | 7. Support for spawning user threads that can hook into Luaw's async IO machinery for calling multiple backend services from Luaw server in parallel. 23 | 8. Support for user timers for implementing periodic cron like jobs inside Luaw server. 24 | 9. Log4j like logging framework with configurable log levels, log file size limits and automatic log rotation that integrates with syslog out of the box. 25 | 10. [MessagePack](http://msgpack.org/) like library to efficiently serialize/deserialize arbitrarily complex data into compact and portable binary format for remote web service calls. 26 | 11. Built in multipart file upload support. 27 | 28 | 29 | ##How To Build 30 | *** 31 | 1. Get necessary build tools. libuv, one of the Luaw dependencies uses autotools, autoconf and libtoolize in its build system. If your machine is not already setup with these, you can use following steps to get these tools. 32 | 33 | sudo apt-get install make 34 | sudo apt-get install autotools-dev 35 | sudo apt-get install autoconf 36 | sudo apt-get install build-essential libtool 37 | 38 | 39 | 2. Clone Luaw repository 40 | 41 | git clone --recursive https://github.com/raksoras/luaw.git 42 | 43 | 3. Build Luaw to use standard [PUC-Rio Lua VM](http://www.lua.org/) 44 | 45 | cd luaw 46 | make linux 47 | 48 | or, build Luaw to use [LuaJit VM](http://luajit.org/) 49 | 50 | cd luaw 51 | make LUAVM=luajit linux 52 | 53 | While building on mac OS use target "macosx". For example 54 | 55 | cd luaw 56 | make LUAVM=luajit macosx 57 | 58 | 4. Install Luaw binary - luaw_server - in directory of your choice. We will use `~/luawsample` in all our examples going forward as a directory of choice for Luaw installation 59 | 60 | make INSTALL_ROOT=~/luawsample install 61 | 62 | To install binaries along with the sample app provided 63 | 64 | make INSTALL_ROOT=~/luawsample install-sample 65 | 66 | 67 | 68 | ##Luaw directory structure 69 | 70 | In the tree diagram below `~/luawsample` is a directory that you chose in the `make intsall` step above. It will act as a root for Luaw server's directory structure. The `make install` step will create following directory structure under `~/luawsample` 71 | 72 | ``` 73 | ~/luawsample 74 | | 75 | | 76 | +--- bin Directory that holds Luaw server binary we built 77 | | along with all necessary Lua libraries 78 | | 79 | +--- conf Directory for server configuration 80 | | | 81 | │   +--- server.cfg Sample server configuration file, to be used as a starting point 82 | | 83 | +--- lib Directory to install any third party or user supplied Lua 84 | | libraries that application may depend on. 85 | | 86 | +--- logs Directory for server logs 87 | | 88 | +--- webapps Directory to install Luaw webapps 89 | ``` 90 | 91 | ##License 92 | 93 | Luaw uses [MIT license](http://opensource.org/licenses/mit-license.html) for all parts of Luaw that are not externally 94 | maintained libraries like libuv. 95 | 96 | ## Documentation 97 | 98 | Please refer to the [project wiki](https://github.com/raksoras/luaw/wiki) for documentation regarding how to build, configure, start and program using Luaw. 99 | 100 | ## Getting in touch 101 | 102 | Please post your questions/comments on Google Group [Luaw](https://groups.google.com/forum/#!forum/luaw) and follow [@raksoras](https://twitter.com/raksoras) on Twitter 103 | -------------------------------------------------------------------------------- /conf/server.cfg: -------------------------------------------------------------------------------- 1 | luaw_server_config = { 2 | server_ip = "0.0.0.0", 3 | server_port = 7001, 4 | connect_timeout = 4000, 5 | read_timeout = 8000, 6 | write_timeout = 8000 7 | } 8 | 9 | luaw_log_config = { 10 | log_dir = "./logs", 11 | log_file_basename = "luaw-log", 12 | log_file_size_limit = 1024*1024, 13 | log_file_count_limit = 9, 14 | log_filename_timestamp_format = '%Y%m%d', 15 | log_lines_buffer_count = 16, 16 | syslog_server = "127.0.0.1", 17 | syslog_port = 514, 18 | } 19 | 20 | luaw_webapp_config = { 21 | base_dir = "./webapps" 22 | } 23 | 24 | -------------------------------------------------------------------------------- /docs/Contents.md: -------------------------------------------------------------------------------- 1 | #Contents 2 | 3 | 1. Introduction 4 | 2. Getting started: building and installing Luaw 5 | 3. Luaw configuration 6 | 4. Your first Luaw webapp - "Hello world!" 7 | 5. Your second Luaw webapp - "Hello ``!" 8 | 6. Luaw template views 9 | 7. Your third webapp - with template views 10 | 8. Using Luaw logging framework 11 | 9. Using Luaw response object 12 | 10. Using Luaw async HTTP Client 13 | 11. Using Luaw threads and background jobs 14 | 12. Using Luaw timers 15 | 13. Using custom HTTP handler for HTTP stream parsing 16 | 14. Using custom scripts on command line at Luaw start up 17 | -------------------------------------------------------------------------------- /docs/Introduction.md: -------------------------------------------------------------------------------- 1 | #Introduction 2 | 3 | Luaw stands for "Lua web server". It also matches abbreviation for an air traffic controller command "line up and wait" that closely resembles the way it handles multiple requests using event loop :) 4 | 5 | Luaw is an event driven, non blocking IO based HTTP application server inspired by node.js. It uses node.js's excellent async library libuv to do non-blocking IO but instead of Javascript it uses [Lua](http://www.lua.org/) as its language for application development. Luaw takes advantage of Lua's first class coroutine support to avoid [callback spaghetti problem](http://callbackhell.com/). This makes writing async IO code as simple as writing sequential code while all the heavy-lifting of application state machine management that is usually associated with async IO is transparently handled by Lua coroutines. This mean a Luaw application developer gets best of both worlds - [scale](http://www.kegel.com/c10k.html) of event driven IO and simplicity of blocking IO like code. 6 | 7 | 8 | ##Features 9 | 10 | 1. Full HTTP 1.1 support with persistent/keep-alive connections and HTTP pipelining with configurable connect and read timeouts. 11 | 2. Two ways to read and parse incoming HTTP requests, 12 | - Reading whole request in memory and then parsing it which is suitable for majority of applications. 13 | - Reading request as a stream and parsing it as it arrives in parts to minimize latency and server memory footprint for high traffic, high performance applications like HTTP proxies or HTTP load balancers. 14 | 3. Similarly on the output side it supports, 15 | - Buffering entire content in memory before writing it out to client with the correct "Content-Length" header. 16 | - Streaming response continuously as it is generated using HTTP 1.1 chunked encoding to keep server memory pressure minimum. 17 | 4. Fully asynchronous DNS and HTTP client for making remote HTTP calls from server 18 | 5. [Sinatra](http://www.sinatrarb.com/) like web application framework that supports mapping URLs to REST resources with full support for path and query parameters. 19 | 6. Luaw template views for server side dynamic content generation . Luaw template views use streaming output with chunked encoding described above to eliminate need for huge server side memory buffers to buffer output generated by templates. 20 | 7. Support for spawning user threads that can hook into Luaw's async IO machinery for calling multiple backend services from Luaw in parallel. 21 | 8. Support for user timers for implementing periodic cron like jobs inside Luaw server. 22 | 9. Log4j like logging framework with configurable log levels, log file size limits and automatic log rotation that integrates with syslog out of the box. 23 | 10. [MessagePack](http://msgpack.org/) like library to efficiently serialize/deserialize arbitrarily complex data into compact and portable binary format for remote web service calls. 24 | -------------------------------------------------------------------------------- /docs/cmd-line.md: -------------------------------------------------------------------------------- 1 | #14. Advanced Topic II - Using custom scripts on command line at start up 2 | 3 | In the last topic we saw that we can specify any custom Lua script(s) on the command line after the server configuration file at start up. All these script are executed sequentially in order by Luaw using the same Lua VM and global environment. These scripts are executed after all the core infrastructure machinery of Luaw is initialized. This means these custom scripts have full access to initialized Luaw container before it starts to server any traffic. This is a very flexible and powerful trick to customize Luaw server and modify the global environment in which all subsequent request handlers will run. We have already used this trick to override Luaw's default HTTP request handler with our own custom request handler optimized for proxying network traffic. You can use this trick to do many essential, common tasks at server start up time. For example, 4 | 5 | 1. Loading any in memory caches required by application in Luaw's memory 6 | 2. Start any crontab like tasks that you would like to be run periodically inside your Luaw server. To do this you would typically spawn a new user thread per task and use a timer:sleep() method to sleep for desired time and then execute whichever action you want in a forever loop 7 | 3. Set up sandboxes in which to execute certain request or resource handlers. -------------------------------------------------------------------------------- /docs/configuration.md: -------------------------------------------------------------------------------- 1 | #3. Configuring Luaw 2 | 3 | Before we can write our first Luaw webapp we need to configure our Luaw server with some basic settings -- TCP port on which Luaw server will listen for incoming HTTP connections, for example. These settings are specified in a text file which is named server.cfg by convention and is put under `luaw_root_dir/conf` directory. This file is Luaw's counterpart of Apache web server's httpd.conf or Tomcat's server.xml file. All Luaw configuration files use [Lua object notation syntax](http://www.lua.org/pil/10.1.html#DataDesc) and server.cfg is no exception to this rule. 4 | 5 | Like Tomcat, Luaw allows deploying multiple web apps in a single Luaw server. Settings configured in server.cfg apply to the whole server, that is, they apply to all webapps that are deployed in that server. Settings specific to each webapps are configured using "web.lua" file per webapp. 6 | 7 | `make INSTALL_ROOT=luaw_root_dir install` step from the last chapter should have created the standard directory structure for you and put sample server.cfg file under `luaw_root_dir/conf` directory. You can use this server.cfg as a starting point for defining your configuration. Open your conf/server.cfg and take a look. 8 | 9 | ##Server configuration 10 | 11 | Here is a sample server.cfg 12 | 13 | ```lua 14 | luaw_server_config = { 15 | server_ip = "0.0.0.0", 16 | server_port = 7001, 17 | connect_timeout = 4000, 18 | read_timeout = 8000, 19 | write_timeout = 8000 20 | } 21 | 22 | luaw_log_config = { 23 | log_dir = "/apps/luaw/sample/log", 24 | log_file_basename = "luaw-log", 25 | log_file_size_limit = 1024*1024, 26 | log_file_count_limit = 9, 27 | log_filename_timestamp_format = '%Y%m%d', 28 | log_lines_buffer_count = 16, 29 | syslog_server = "127.0.0.1", 30 | syslog_port = 514, 31 | } 32 | 33 | luaw_webapp_config = { 34 | base_dir = "/apps/luaw/sample/webapps" 35 | } 36 | ``` 37 | luaw_server_config section specifies listening port and read/connection timeout defaults for TCP socket connections. "server_ip" setting's value "0.0.0.0" tells server to accept connections coming in on any of the host's ip addresses. Some hosts have more than one IP address assigned to them. In such case "server_ip" can be used to restrict Luaw server to accept incoming connections on only one of the multiple IP addresses of the host. 38 | 39 | luaw_log_config section sets up parameters for Luaw's log4j like logging subsystem - log file name pattern, size limit for a single log file after which Luaw should open new log file, how many of such past log files to keep around (log rotation) etc. Luaw logging framework can send messages to syslog daemon as well and this section can be used to specify target syslog server's ip address and port. 40 | 41 | Finally, luaw_webapp_config section specifies location of directory that houses all the webapps that this Luaw server will load and run. By convention this directory is named "webapps" and is placed directly under Luaw server's root folder but you can place it anywhere you like using this section, should your build/deploy procedure requires you to choose another location. 42 | 43 | ## webapp configuration 44 | 45 | Like Tomcat, Luaw allows deploying multiple webapps in a single Luaw server. These webapps are deployed under `luaw_root_dir/webapps`. Here is a sample layout for a Luaw server that has two webapps - `myapp1` and `myapp2` - deployed in it: 46 | 47 | ``` 48 | luaw_root_dir 49 | | 50 | +--- bin 51 | | 52 | +--- conf 53 | | | 54 | │   +--- server.cfg 55 | | 56 | +--- lib 57 | | 58 | +--- logs 59 | | 60 | +--- webapps 61 | | 62 | +--- myapp1 63 | | | 64 | | +---web.lua 65 | | 66 | +--- myapp2 67 | | 68 | +---web.lua 69 | ``` 70 | 71 | Each webapp contains file named web.lua that specifies settings specific to that particular webapp. The same directory (`/myapp1` and `/myapp2` in the example above) also contains Lua code for the webapp - REST handlers, views etc. We will visit application code in the next chapter. In this chapter we will focus on just the configuration piece. 72 | 73 | ##Sample web.lua: 74 | 75 | ```lua 76 | luaw_webapp = { 77 | resourcePattern = "handler%-.*%.lua", 78 | views = { 79 | "user/address-view.lua", 80 | "account/account-view.lua" 81 | } 82 | } 83 | 84 | Luaw.logging.file { 85 | name = "root", 86 | level = Luaw.logging.ERROR, 87 | } 88 | 89 | Luaw.logging.file { 90 | name = "com.luaw", 91 | level = Luaw.logging.INFO, 92 | } 93 | 94 | Luaw.logging.syslog { 95 | name = "com.luaw", 96 | level = Luaw.logging.WARNING, 97 | } 98 | ``` 99 | 100 | luaw_webapp section specifies resources (request handlers) and views (templates) that make up the web application. These can be specified in two different ways: 101 | 102 | 1. **Using pattern **: You can use configuration elements *resourcePattern* and *viewPattern* to specify name pattern for request handlers and views. Luaw will traverse all directories under current webapp recursively to load all files that match these patterns. The patterns are specified using standard [Lua regular expressions](http://www.lua.org/pil/20.2.html). These are somewhat different than usual POSIX or PERL regular expressions so be sure to read the documentation linked above before you use them. 103 | 2. **Listing them one by one by exact name and path **: You can also use configuration elements *resources* and *views* to list exact resources and views by name to load them. Each entry should contain a path that is relative to the root webapp directory (**myapp1** and **myapp2** in the example above) ending in the filename of the file to load. 104 | 105 | You can mix and match both these approach. For example you can use *resourcePattern* to specify resources and *views* for specifying exact list of views. You can even use both the ways together. That is, you can use both *resourcePattern* and *resources* in the same section and Luaw will load all the files under the given webapp's folder that either match the *resourcePattern* or match the path and file name from the *resources* list (union operation). This could be useful if most of your resource files follow certain naming convention or a pattern but you also have few one off files that don't follow those conventions that you'd like to load nonetheless. 106 | 107 | Rest of the file specifies logging level and logging target (file vs syslog) for different Luaw packages. Logging settings in web.lua specify log levels that are specific to that webapp while overall logging settings like log file name and size limit are determined by server.cfg settings at a global server level. 108 | -------------------------------------------------------------------------------- /docs/first-webapp.md: -------------------------------------------------------------------------------- 1 | #4. Your First Luaw Webapp - "Hello world!" 2 | 3 | Now that we are familiar with Luaw's directory layout and configuration, we are ready to write our first webapp - "Hello world" but of course. 4 | 5 | Luaw comes equipped with [Ruby's Sinatra](http://www.sinatrarb.com/) like web framework that allows mapping URLs to request handlers (routes in Sinatra's terminology). It has full support for REST path and query parameters. 6 | 7 | ##Writing Luaw Request Handler 8 | 9 | 1. Switch to directory `luaw_root_dir/webapps` that was created in chapter "Getting started" and create a directory called `myapp` under it. This is our first webapp. 10 | 11 | cd luaw_root_dir/webapps 12 | mkdir myapp 13 | cd myapp 14 | 15 | 2. create a filed called web.lua in `luaw_root_dir/webapps/myapp` and put following content in it 16 | ```lua 17 | luaw_webapp = { 18 | resourcePattern = "handler%-.*%.lua", 19 | } 20 | ``` 21 | This is a bare minimum webapp configuration that basically tells Luaw to load any file matching Lua regex pattern "handler%-.*%.lua" as a URL (or in REST terminology Resource) handler. 22 | 23 | 3. Now we will write our first resource handler. create a directory `handlers` under `luaw_root_dir/webapps/myapp` and create a file named `handler-helloworld.lua` under it. The choice of the directory name "handlers" is purely arbitrary. All that matters is that handler's file name matches the pattern `handler%-.*%.lua` that we have specified in web.lua. Luaw will traverse all folders under `luaw_root_dir/webapps/myapp` looking for handler files to load that match the pattern. This means we could have placed handler-helloworld.lua directly under luaw_root_dir/webapps/myapp and it would have still worked. It's probably a better practice to put them in their own directory like "handlers" from the point of view of clean code organization though. 24 | Next put following code in "handler-helloworld.lua": 25 | ```lua 26 | GET 'helloworld' { 27 | function(req, resp, pathParams) 28 | return "Hello World!" 29 | end 30 | } 31 | ``` 32 | 33 | In the code above GET identifies the HTTP method this handler will service. Other methods available are POST, PUT, DELETE, HEAD, OPTIONS, TRACE and CONNECT corresponding to respective HTTP methods. There is also a catch-all, uber method called SERVICE that you can use in case you want to handle any HTTP request irrespective of its method. 34 | the string 'helloworld' specifies the URL path this handler will match. It is analogus to Sinatra's route. It means this handler will be invoked for all GET methods made on `/myapp/helloworld` URL. 35 | 36 | Finally, the function in the above example is the actual code run whenever `/myapp/helloworld` is requested. The function is passed incoming HTTP request, HTTP response to write to and any REST path parameters defined on the resources. We will see how to used all three of these objects in subsequent chapters. For now, we just want to follow the age old rite of passage and say "Hello World!" to the browser. Simply returning the string "Hello World!" from our function is sufficient to achieve this. Later we will see more sophisticated ways of forming and returning response using response object and Luaw template views. 37 | 38 | export LD_LIBRARY_PATH=/usr/local/lib/ 39 | 40 | 4. Now we are ready to start the server. To do this switch to `luaw_root_dir` and run Luaw server like this: 41 | cd luaw_root_dir 42 | ./bin/luaw_server ./conf/server.cfg 43 | 44 | First argument to the luaw_server is always the server configuration file. If you have followed all the steps so far correctly, you should see following output in your console window: 45 | 46 | ``` 47 | ******************** Starting webapp myapp ******************************* 48 | .Loading resource ./webapps/myapp/handlers/handler-helloworld.lua 49 | #Loaded total 1 resources 50 | 51 | #Compiled total 0 views 52 | *********************** Webapp myapp started **************************** 53 | starting server on port 7001 ... 54 | ``` 55 | 56 | Now point your browser to http://localhost:7001/myapp/helloworld and greet the brave new world! 57 | 58 | Congratulations, you have successfully written your first Luaw webapp! -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | #2. Getting started 2 | 3 | Let's build Luaw from its sources. Luaw depends on, 4 | 5 | 1. Lua 5.2(+) 6 | 2. libuv (v1.0.0 ) 7 | 8 | We will first build these dependencies from their sources and then finally build Luaw itself. To build these artifacts you would need [Git](http://git-scm.com/), [Make](http://www.gnu.org/software/make/) and [autotools](http://www.gnu.org/software/automake/manual/html_node/Autotools-Introduction.html) setup on your machine. 9 | 10 | ##Building Lua 5.2 11 | Lua sources can be downloaded from [here](http://www.lua.org/download.html). Here are the steps to download Lua 5.2 sources and build it for Linux: 12 | 13 | curl -R -O http://www.lua.org/ftp/lua-5.2.3.tar.gz 14 | tar zxf lua-5.2.3.tar.gz 15 | cd lua-5.2.3 16 | make linux test 17 | sudo make linux install 18 | 19 | To build for other OSes replace "linux" from the last two make commands with the OS you are building for. For example for when building for Mac OS run, 20 | 21 | make macosx test 22 | sudo make linux install 23 | 24 | To see what OSes are supported run ./lua-5.2.3/src/Makefile targets 25 | 26 | 27 | ##Building libuv 28 | Luaw uses node.js library libuv to do asynchronous, event based IO in a portable, cross platform manner. To build libuv: 29 | 30 | 1. first clone libuv repository 31 | git clone https://github.com/joyent/libuv.git 32 | 2. Checkout latest stable release of libuv from the cloned local repository. As of this writing the latest stable release is v1.0.0 and Luaw is verified to compile and run successfully with this release of libuv. 33 | cd libuv 34 | git checkout tags/v1.0.0 35 | 3. Build libuv. This may require you to install autotools. Detailed instructions are [here](https://github.com/joyent/libuv#build-instructions) 36 | sh autogen.sh 37 | ./configure 38 | make 39 | make check 40 | sudo make install 41 | 42 | ## Building Luaw 43 | With all dependencies built, now we are ready to build Luaw itself. 44 | 45 | 1. Clone Luaw repository 46 | git clone https://github.com/raksoras/luaw.git 47 | 2. Build Luaw 48 | cd luaw/src 49 | make linux 50 | 3. Install Luaw binary - luaw_server - in directory of your choice 51 | make INSTALL_ROOT= install 52 | 4. Note: On Mac running Yosemite version of Mac OS you may have to run, 53 | make SYSCFLAGS=-I/usr/local/include SYSLDFLAGS=-L/usr/local/lib macosx 54 | make INSTALL_ROOT= install 55 | 56 | 57 | ##Luaw directory structure 58 | 59 | In the tree diagram below `luaw_root_dir` is a directory that you chose in the `make intsall` step above. It will act as a root for Luaw server's directory structure. The `make install` step will create following directory structure under `luaw_root_dir` 60 | 61 | ``` 62 | luaw_root_dir 63 | | 64 | | 65 | +--- bin Directory that holds Luaw server binary we built 66 | | along with all necessary Lua libraries 67 | | 68 | +--- conf Directory for server configuration 69 | | | 70 | │   +--- server.cfg Sample server configuration file, to be used as a starting point 71 | | 72 | +--- lib Directory to install any third party or user supplied Lua 73 | | libraries that application may depend on. 74 | | 75 | +--- logs Directory for server logs 76 | | 77 | +--- webapps Directory to install Luaw webapps 78 | ``` 79 | 80 | This directory structure and its usage is further explained in the next chapter "Configuring Luaw" 81 | 82 | -------------------------------------------------------------------------------- /docs/http-client.md: -------------------------------------------------------------------------------- 1 | #10. Async HTTP Client 2 | 3 | Luaw comes equipped with curl like async HTTP client. It is fully async, in that both DNS lookup for the hostname as well as actual connect/read/write on the socket are done in a non blocking fashion. Due to this you can use this client safely in your Luaw webapp, from your request thread to make HTTP requests to other backend servers. Luaw will transparently suspend your running request thread (Lua coroutine) when the HTTP client is waiting on DNS lookup, connect, read or write. 4 | 5 | The Luaw HTTP client is modeled by two objects: clientHttpRequest and clientHttpResponse. Here is a small example of Luaw's HTTP client's usage: 6 | 7 | ```lua 8 | -- set up HTTP request 9 | local clientReq = Luaw.newClientHttpRequest() 10 | clientReq.hostName = "www.google.com" 11 | -- OR alternatively, 12 | clientReq.hostIP = "74.125.25.106" 13 | clientReq.method = 'GET' 14 | clientReq.url = "/" 15 | 16 | clientReq.headers = { Host = "www.google.com" } 17 | -- OR alternatively 18 | clientReq:addHeader("Host", "www.google.com") 19 | 20 | -- execute the HTTP request and read the response back. 21 | local clientResp = clientReq:execute() 22 | 23 | -- Get the respone headers and body from the client response object returned 24 | local respBody = clientResp.body 25 | local respHeaders = clientResp.headers 26 | ``` 27 | 28 | In fact, Luaw's built in HTTP client allows even more fine grained control over various stages of HTTP request execution and parsing of the HTTP response received from the server, similar to what we saw in the chapter "Advanced Topic I - Using Response Object" which was about server's HTTP response. We learn will how to use some of these methods in the last chapter where we put together all the things we have learned so far to develop a streaming request/response handler for a high performance proxy web server. 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /docs/logging.md: -------------------------------------------------------------------------------- 1 | #8. Luaw logging framework 2 | 3 | Luaw comes equipped with logging framework that's modeled after Java's popular log4j logging framework. Like log4j, it allows different destinations for logs (either file system or syslog) and different log levels per package to allow enabling/disabling logging by setting 4 | runtime properties 5 | 6 | Luaw's logging is configured at two levels 7 | 8 | ##Logging configuration for the whole server 9 | Configuration related to log destinations is configured in `conf/server.cfg` and applies to all web applications deployed in that server. luaw_log_config section contains this configuration in server.cfg 10 | ```lua 11 | luaw_log_config = { 12 | log_dir = "/apps/luaw/sample/log", 13 | log_file_basename = "luaw-log", 14 | log_filename_timestamp_format = '%Y%m%d', 15 | log_file_size_limit = 1024*1024, 16 | log_file_count_limit = 9, 17 | log_lines_buffer_count = 16, 18 | syslog_server = "127.0.0.1", 19 | syslog_port = 514 20 | } 21 | ``` 22 | 23 | Example above configures, 24 | 25 | 1. Log directory where the files should go: /apps/luaw/sample/log 26 | 2. Log file base name: luaw-log 27 | 3. [Timestamp format](http://www.lua.org/pil/22.1.html) used along with log file base name to generate full log file name for versioned log files: %Y%m%d 28 | 4. File size limit for an individual log file: 1MB 29 | 5. Total number of log files to keep: 9 30 | 6. How many log lines to buffer in memory before flushing them to log file: 16 31 | 7. syslog server address: 127.0.0.1 32 | 8. syslog server port: 514 33 | 34 | You could omit either file system related configuration or syslog configuration but at least one must be present to use Luaw logging. 35 | 36 | 37 | ##Logging configuration per webapp 38 | 39 | Configuration related to individual webapps' logging is configured in `web.lua` config file of each webapp. Here are some example entries from sample "web.lua": 40 | ```lua 41 | Luaw.logging.file { 42 | name = "root", 43 | level = Luaw.logging.ERROR 44 | } 45 | ``` 46 | 47 | Above entry configures root level logging ("root" is a special word) for the entire webapp that applies to all loggers used by the webapp by default unless overridden by more specific logger. It configures all logging to go to log file - whose destination and name is configured in server.cfg - and states only logs with log level ERROR and above should be logged. 48 | 49 | ```lua 50 | Luaw.logging.file { 51 | name = "com.myapp", 52 | level = Luaw.logging.INFO 53 | } 54 | ``` 55 | 56 | Overrides default ("root") logging for the logger "com.myapp" and sets its level to INFO which means for any logger with name starting with "com.myapp", log lines with the log level INFO and above will get logged to the file instead of just ERROR as dictated by the default (root) configuration above. "com.myapp" is a logger name. You can use any name separated by periods. Each part of the name delimited by a period forms logger hierarchy just like log4j. For example, the logger named "com" is a parent to logger name "com.myapp" and any logging configuration defined for "com" is inherited by "com.myapp" unless specififcally overriden on "com.myapp". This makes it easy to fine tune logging levels of different parts of code using loggers arranged in a logical hierarchy. 57 | 58 | To actually log a line you use code like following: 59 | 60 | ```lua 61 | local log = require("luaw_logging") 62 | 63 | local logger = log.getLogger("com.myapp") 64 | logger.error("some error occurred") 65 | logger.warning("this is a warning") 66 | logger.info("some information") 67 | logger.debug("lowest level debug") 68 | ``` 69 | 70 | Here are different log levels and Luaw logger functions corresponding to them in a decreasing order of severity: 71 | 72 | |Log Level | Logging function | 73 | |-------------------------------- 74 | | EMERGENCY | logger.emergency() | 75 | | ALERT | logger.alert() | 76 | | CRITICAL | logger.critical() | 77 | | ERROR | logger.error() | 78 | | WARNING | logger.warning() | 79 | | NOTICE | logger.notice() | 80 | | INFO | logger.info() | 81 | | DEBUG | logger.debug() | 82 | 83 | 84 | All these functions also have a counterpart ending in "f" - `logger.errorf()`, `logger.warningf()`, `logger.debugf()` etc. - that take a [lua format string](http://lua-users.org/wiki/StringLibraryTutorial) followed by variable number of arguments that are used to substitute place holderes in the format string like below 85 | 86 | ```lua 87 | logger.infof("User name %s, User Id %d", name, id) 88 | ``` 89 | 90 | This form is useful to avoid unnecessary string concatenation beforehand when the configured logger level may actually end up filtering the log line anyways. 91 | 92 | Finally, here is an example of configuring syslog as a log destination for logger "com.myapp.system" and setting it to the log level WARNING in web.lua: 93 | 94 | ```lua 95 | Luaw.logging.syslog { 96 | name = "com.myapp.system", 97 | level = Luaw.logging.WARNING 98 | } 99 | ``` 100 | 101 | syslog sever and port to use is specified in server.cfg -------------------------------------------------------------------------------- /docs/proxy-http.md: -------------------------------------------------------------------------------- 1 | #13. custom HTTP handler for proxying request 2 | 3 | So far we have used default HTTP requests handler that ships with Luaw in all our examples. For most purposes it is indeed sufficient, even ideal HTTP handler to use. It handles all low level details of HTTP protocol parsing and routes incoming requests to REST resource by matching URL to user specified matching rules. 4 | 5 | However, if you need it, Luaw also allows you to specify your own custom HTTP request handler - which is top level HTTP request router really comparable to Strut's main servlet or Spring MVC servlet counterpart in Java world - which will replace Luaw's default request handler. We will use this Luaw facility to develop a toy HTTP buffering reverse proxy server in this section. 6 | 7 | A typical reverse proxy server accepts a HTTP request from a client, inspects its contents and then forwards it to one of many backend HTTP servers it is balancing load for depending upon the results of the request content inspection. On the return path it receives the response generated by the backend server and forwards it to the connected client. Buffering reverse proxy adds, well, buffering to the standard reverse proxy functionality. It buffers incoming HTTP client request till it is complete before it sends it to one of the backend servers. Similarly on the return path it buffers full response generated by the backend server before it starts returning it back to the original client. This buffering is important in order to defend backend servers' limited resoucres against slow client. Without buffering both consumption of input HTTP request and sending of HTTP response will proceed at "line speed" dictated by the slow client which will tie backend server's resources for a long time. 8 | 9 | ``` 10 | +------+ ----HTTP request---> +-------------+ --- Proxy HTTP request ---> +-------+ 11 | |Client| |Reverse Proxy| |Backend| 12 | +------+ <---HTTP response--- +-------------+ <-- Proxy HTTP response --- +-------+ 13 | ``` 14 | 15 | Below is the code for a toy buffering reverse proxy with inline comments explaining what's going on. Our toy proxy server uses a protocol where by client sends the backend host and URL it wants to connect through the proxy server in its HTTP request's "proxy-host" and "proxy-url" headers respectively. If any of these headers are missing our proxy server responds with 400 error. It also proxies only HTTP GET requests but its easy to see how it can be modified to proxy other HTTP methods too. 16 | 17 | ```lua 18 | --[[ 19 | Luaw allows you to replace it's default MVC/REST request handler with your own custom HTTP request handler implementation. To override the default HTTP request handler just set Luaw object's request_handler property to your custom Lua function. This function is passed in a low level connection object for each incoming request instead of the normal request and response objects. The function is called on its own separate Luaw coroutine for each HTTP request so you don't have to worry about multithreaded access to same state inside the function. 20 | ]] 21 | 22 | Luaw.request_handler = function(conn) 23 | conn:startReading() 24 | 25 | -- loop to support HTTP 1.1 persistent (keep-alive) connections 26 | while true do 27 | local req = Luaw.newServerHttpRequest(conn) 28 | local resp = Luaw.newServerHttpResponse(conn) 29 | 30 | -- read and parse full request 31 | local eof = req:readFull() 32 | if (eof) then 33 | conn:close() 34 | return "connection reset by peer" 35 | end 36 | 37 | local reqHeaders = req.headers 38 | local beHost = reqHeaders['backend-host'] 39 | local beURL = reqHeaders['backend-url'] 40 | 41 | if (beHost and beURL) then 42 | local backendReq = Luaw.newClientHttpRequest() 43 | backendReq.hostName = beHost 44 | backendReq.url = beURL 45 | backendReq.method = 'GET' 46 | backendReq.headers = { Host = beHost } 47 | 48 | local status, backendResp = pcall(backendReq.execute, backendReq) 49 | if (status) then 50 | resp:setStatus(backendResp:getStatus()) 51 | resp:appendBody(backendResp:getBody()) 52 | local beHeaders = backendResp.headers 53 | for k,v in pairs(beHeaders) do 54 | if ((k ~= 'Transfer-Encoding')and(k ~= 'Content-Length')) then 55 | resp:addHeader(k,v) 56 | end 57 | end 58 | backendResp:close() 59 | else 60 | resp:setStatus(500) 61 | resp:appendBody("connection to backend server failed") 62 | end 63 | else 64 | resp:setStatus(400) 65 | resp:appendBody("Request must contain headers backend-host and backend-url") 66 | end 67 | 68 | local status, mesg = pcall(resp.flush, resp) 69 | if (not status) then 70 | conn:close() 71 | return error(mesg) 72 | end 73 | 74 | if (req:shouldCloseConnection() or resp:shouldCloseConnection()) then 75 | conn:close() 76 | return "connection reset by peer" 77 | end 78 | end 79 | end 80 | ``` 81 | 82 | The last bit of puzzle remaining is how do we actually load this new, shiny custom HTTP handler of ours into Luaw server? To this we use a simple trick that is quite flexible and powerful in practice. So far we have been starting our Luaw server with following command in luaw_roo_dir: 83 | ``` 84 | ./bin/luaw_server ./conf/server.cfg 85 | ``` 86 | Where `server.cfg` is a Luaw server configuration file. In reality `luaw_server` binary will read and execute any number of Lua script files specified as series of command line arguments. The very first one is assumed to be server configuration file and is mandatory. Any number - and any kind - of Lua script files can follow the configuration file and are executed by `luaw_server` in the same order as they are specified on the start up commmand line using the same Lua VM and global environment. We can use this handy trick to load any functions we want into Luaw as well as hook up into Luaw's internal machinery using any of the public hook up points that Luaw advertises. 87 | 88 | So for our case, just create a file called proxy-handler.lua under `luaw_roo_dir/bin` folder and put the above code in it. Then from your command prompt run Luaw like this: 89 | 90 | ./bin/luaw_server ./conf/server.cfg ./bin/proxy-handler.lua 91 | 92 | `luaw_server` will run your proxy-handler.lua after it has initialized itself. The script in proxy-handler.lua then takes care of replacing Luaw's default HTTP request handler with the custom one by assigning the custom handler function to `Luaw.request_handler` property. 93 | 94 | Now test your shiny new proxy server by running following tests: 95 | 96 | ## Test 1 - Missing required headers 97 | 98 | $ curl -v http://127.0.0.1:7001/ 99 | * Trying 127.0.0.1... 100 | * Connected to 127.0.0.1 (127.0.0.1) port 7001 (#0) 101 | > GET / HTTP/1.1 102 | > User-Agent: curl/7.37.1 103 | > Host: 127.0.0.1:7001 104 | > Accept: */* 105 | > 106 | < HTTP/1.1 400 Bad Request 107 | < Content-Length: 50 108 | < 109 | Headers proxy-host and proxy-url must be present 110 | 111 | ## Test 2 - With correct headers 112 | $ curl -v -H"proxy-host: www.google.com" -H"proxy-url: /" http://127.0.0.1:7001/ 113 | * Trying 127.0.0.1... 114 | * Connected to 127.0.0.1 (127.0.0.1) port 7001 (#0) 115 | > GET / HTTP/1.1 116 | > User-Agent: curl/7.37.1 117 | > Host: 127.0.0.1:7001 118 | > Accept: */* 119 | > proxy-host: www.google.com 120 | > proxy-url: / 121 | > 122 | < HTTP/1.1 200 OK 123 | < Content-Type: text/html; charset=ISO-8859-1 124 | < Transfer-Encoding: chunked 125 | * Server gws is not blacklisted 126 | < Server: gws 127 | 128 | (.. followed by the body of the home page at www.google.com) 129 | -------------------------------------------------------------------------------- /docs/resp-obj.md: -------------------------------------------------------------------------------- 1 | #9. Response Object 2 | 3 | So far we have seen two ways to return response body from resource handler: 4 | 5 | 1. Returning a string from resource handler function which Luaw returns in turn as a whole response body to the client, and 6 | 2. Using Luaw template views that generate response body programmatically in a fashion similar to JSP or ASP 7 | 8 | However, Luaw does offer finer grain control over response generation from resource handler should you need it. Remember the resource handler function is passed in both request and response object like this: 9 | 10 | ```lua 11 | GET '/user/:username' { 12 | function(req, resp, pathParams) 13 | return "Hello "..pathParams.username.."!" 14 | end 15 | } 16 | ``` 17 | 18 | Finer grained control over response generation is achieved by invoking various methods on the response object as described below. 19 | 20 | 1. `resp:setStatus(status)`: You can set HTTP status code to be returned to the client - 200, 404 etc. - using this method 21 | 22 | 2. `resp:addHeader(name, value)`: You can add arbitrary HTTP headers to the response using this method. All the headers must be added before you start adding body content. 23 | 24 | 3. `resp:startStreaming()`: Calling this method activates a special [HTTP 1.1 chunked transfer mode](http://en.wikipedia.org/wiki/Chunked_transfer_encoding) which causes Luaw to stream response to the connected client instead of buffering it in memory till end and then sending it in a single shot. In this mode, any body content added to the response is buffered till it reaches a certain , relatively small buffer size threshold - 2K by default whihch is configurable using property "connection_buffer_size" in server.cfg's luaw_server_config section - and then is sent to the client as a HTTP 1.1 compliant body chunk. This means server does not have to buffer the entire response body in its memory to calculate "Content-Length" header value before it can send it to the client. Thus, this mode improves overall server memory footprint and also client's response time to the first byte received. Luaw template views use this mode by default to generate content. HTTP status and all the HTTP headers must be added to response before resp:startStreaming() is called. 25 | 26 | 3. `resp:appendBody(content)`: You can use this method to add content to the response body in piecemeal fashion. Depending upon whether the response is in default HTTP 1.1 mode or put in HTTP 1.1 chunked transfer mode by calling resp:startStreaming(); the content is either buffered till resp:flush() is called or streamed to the client in HTTP 1.1 chunks whenever buffered content reaches size limit specified by connection_buffer_size. 27 | 28 | 4. `resp:flush()`: Causes the response to be flushed to the client. In default (HTTP 1.1) mode this causes Luaw to calculate correct "Content-Length" header value for the whole response buffered so far in the memory and then send it to the client along with the "Content-Length" header. In case the response object was put in the HTTP 1.1 chunked transfer mode by calling resp:startStreaming() this causes Luaw to send the last HTTP chunk followed by the terminating chunk as required by the HTTP 1.1 specification. 29 | 30 | 5. `resp:close()`: Finally, call this method to actually close underlying connection to client and release all the associated resources. 31 | -------------------------------------------------------------------------------- /docs/second-webapp.md: -------------------------------------------------------------------------------- 1 | #5. Your Second Luaw webapp - "Hello `username`!" 2 | 3 | Luaw handlers can accept and process HTTP request parameters (query parameters as well as form parameters) using the request object that is passed to the resource handler function. These parameters are available as `req.params`. For example to access HTTP parameter 'username' - either passed as a query parameter like `?username=raksoras` or POSTed as a form field - you can do either `req.params.username` or `req.params['username']` 4 | 5 | Luaw also supports mapping parts of URL paths to REST path parameters. We will use this method to receive username. Let's say we want to use URL format '/user/raksoras' where raksoras is the input user name. To do this create a new handler named "handler-hellouser.lua" under `luaw_root_dir/webapps/myapp/handlers` that we created in previous chapter and put following code in it: 6 | ```lua 7 | GET '/user/:username' { 8 | function(req, resp, pathParams) 9 | return "Hello "..pathParams.username.."!" 10 | end 11 | } 12 | ``` 13 | Here colon in `:username` in the URL path `user/:username` identifies it as a REST path parameter. Luaw will parse it from the URL path at runtime and will make it available on the third paramter - pathParams - passed to the handler function. Inside the function you may refer to it as either `pathParams.username` or `pathParams['username']` 14 | 15 | Now restart your Luaw server and then point your browser to http://localhost:7001/myapp/user/your_name and Luaw should greet you this time in a more personal manner! 16 | 17 | In fact, the preceding ":" identifies the path parameter as a string path parameter. You can use preceding "#" to identify a path parameter as a numerical path parameter instead and Luaw will automatically parse it as a number. For example, you can change the handler-hellouser.lua code as follows, 18 | 19 | ```lua 20 | GET '/user/:username/#count' { 21 | function(req, resp, pathParams) 22 | return "Hello "..pathParams.username.."! You are user number "..pathParams.count.." to visit this site." 23 | end 24 | } 25 | ``` 26 | 27 | and then point your browser to "http://localhost:7001/myapp/user/raksoras/9" to get 28 | ``` 29 | Hello raksoras! You are user number 9 to visit this site. 30 | ``` 31 | in your browser window. -------------------------------------------------------------------------------- /docs/template-view.md: -------------------------------------------------------------------------------- 1 | #6. Luaw Template Views 2 | 3 | Luaw comes equipped with a Luaw template views for generating server-side dynamic content. Luaw template views serve the same need as served by solutions like JSP, ASP and PHP. 4 | 5 | In reality, a Luaw template view is a normal, plain Lua code with little bit of syntactical sugar added for generating HTML/XHTML/XML markup. This allows developer to use full power of Lua - including if-then-else conditional checks, for/while loops, local variables and functions etc. - within the template view while still using concise, readable notation for generating markup in output. Of course this power can be abused by writing complex logic - which should belong to a well defined, separate business logic tier - inside a template view but we trust that you will do no such thing :) Remember with great power comes great responsibility! 6 | 7 | Without further ado, here is a sample Luaw template view file - view-address.lua: 8 | ```lua 9 | BEGIN 'html' 10 | BEGIN 'head' 11 | BEGIN 'title' 12 | TEXT 'Address' 13 | END 'title' 14 | END 'head' 15 | BEGIN 'body' 16 | BEGIN 'div' {class='address'} 17 | BEGIN 'h1' 18 | TEXT(model.title) 19 | END 'h1' 20 | BEGIN 'table' {border="1", margin="1px"} 21 | BEGIN 'tr' 22 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 23 | TEXT 'City' 24 | END 'td' 25 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 26 | TEXT(model.city) 27 | END 'td' 28 | END 'tr' 29 | if (model.zip == 94086) then 30 | BEGIN 'tr' 31 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 32 | TEXT 'County' 33 | END 'td' 34 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 35 | TEXT 'Santa Clara' 36 | END 'td' 37 | END 'tr' 38 | end 39 | BEGIN 'tr' 40 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 41 | TEXT 'Zip' 42 | END 'td' 43 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 44 | TEXT(model.zip) 45 | END 'td' 46 | END 'tr' 47 | END 'table' 48 | END 'div' 49 | END 'body' 50 | END 'html' 51 | ``` 52 | 53 | Each Luaw template view has access to following implicitly defined variables: `req`, `resp`, 54 | `pathParams` and `model` passed from resource handler that invoked the view 55 | 56 | It also defines three syntax sugar extentions to generate markeup `BEGIN`, `TEXT` and `END`. 57 | 58 | 1. You use `BEGIN tag_name` to open any HTML or XML tag of type tag_name 59 | 2. If the tag has any attributes you follow the `BEGIN tag_name` with set of attributes defined like this `{name1=value1, name2=value2 ...}` 60 | 3. You close opne tag with `END tag_name` 61 | 4. If you want to emit any content in the body of the response you are generating you can use `TEXT()` to do so. `TEXT()` can take multiple, variable number of arguments of different types. It will include them in the output response in the same order by calling tostring() on each of the arguments. Consequtive arguments are separated by a single space in the response. As a special case if you want to emit a literal string ('Hello world!' for example) you can do so without using parenthesis like this: `TEXT 'Hello world!'`. There is nothing special about this syntax. Lua as a language offers this convenient notation for calling any Lua function with a single, literal string argument. 62 | 63 | That's it! You can mix your normal Lua code - if/else, loops etc. - along with the markup to be genertated easily. Take a look at the check for zipcode in the sample code above to see an example of this. 64 | 65 | In the next chapter we will see how to use Luaw template views with resource handlers. 66 | 67 | **_NOTE:_** 68 | 69 | You can write resusable templates to generate markup that is common across many pages - web site header and footer, for example - by writing Lua functions that generate this common markup and then simply invoking these where you want to include the markup. This is very similar to JSP tag libraries or other server side include technologies. The only tricky part is making syntax sugar extentions like `BEGIN`, `TEXT` and `END` available to normal Lua functions outside Lua template views. This is actually very easy. Behind the scene these three extentions are really closures bound to current request/response scope. This means you can pass them to any normal Lua functions - even functions defined in separate .lua files that themselves are not Lua template views - like this: 70 | 71 | ```lua 72 | -- Reusable markup generating function AKA tag library 73 | function generateHeader(model, BEGIN, TEXT, END) 74 | BEGIN 'div' {class='header') 75 | --- generate HTML here using BEGIN, TEXT and END 76 | END 'div' 77 | end 78 | ``` 79 | 80 | ```lua 81 | -- Using common markup generating function from Luaw template view 82 | local ssi = require("common_markup.lua") 83 | 84 | -- Just call function to include the markup at write place 85 | ssi.generateHeader(model, BEGIN, TEXT, END) 86 | 87 | BEGIN 'div' {class="body"} 88 | --- generate page specific page markup here 89 | END 'div' 90 | 91 | ssi.generateFooter(model, BEGIN, TEXT, END) 92 | ``` -------------------------------------------------------------------------------- /docs/third-webapp.md: -------------------------------------------------------------------------------- 1 | #7. Your third webapp - with Luaw template view 2 | 3 | In this chapter we will put together all the pieces we have learned about so far - resource handler reading REST path parameters + Luaw template view - to build a toy but nevertheless complete MVC solution with Luaw. 4 | 5 | ##Luaw template view 6 | Create a new directory `views` under `luaw_root_dir/webapps/myapp` and add a file named "view-address.lua" to the `views` directory, containing following piece of Luaw template view code from the last chapter: 7 | 8 | ```lua 9 | BEGIN 'html' 10 | BEGIN 'head' 11 | BEGIN 'title' 12 | TEXT 'Address' 13 | END 'title' 14 | END 'head' 15 | BEGIN 'body' 16 | BEGIN 'div' {class='address'} 17 | BEGIN 'h1' 18 | TEXT(model.title) 19 | END 'h1' 20 | BEGIN 'table' {border="1", margin="1px"} 21 | BEGIN 'tr' 22 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 23 | TEXT 'City' 24 | END 'td' 25 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 26 | TEXT(model.city) 27 | END 'td' 28 | END 'tr' 29 | if (model.zip == 94086) then 30 | BEGIN 'tr' 31 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 32 | TEXT 'County' 33 | END 'td' 34 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 35 | TEXT 'Santa Clara' 36 | END 'td' 37 | END 'tr' 38 | end 39 | BEGIN 'tr' 40 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 41 | TEXT 'Zip' 42 | END 'td' 43 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 44 | TEXT(model.zip) 45 | END 'td' 46 | END 'tr' 47 | END 'table' 48 | END 'div' 49 | END 'body' 50 | END 'html' 51 | ``` 52 | 53 | ##REST resource handler 54 | Add a file `handler-address.lua` to `luaw_root_dir/webapps/myapp/handlers` containing following code: 55 | ```lua 56 | GET 'address/:city/#zip' { 57 | function(req, resp, pathParams) 58 | address = { 59 | city = pathParams.city, 60 | zip = pathParams.zip 61 | } 62 | return '/views/view-address.lua', address 63 | end 64 | } 65 | ``` 66 | 67 | This resource handler handles GET request made to URL path "address/_city_/_zip_" with two path parameters - "city" defined as string parameter (denoted by preceding ':') and "zip" defined as numeric (denoted by preceding '#') 68 | 69 | Most interesting line in the handler above is the following return statement from the function handler: 70 | 71 | return '/views/view-address.lua', address 72 | 73 | So far we have been returning a single string from resource handler function (return "Hello World", for example) which Luaw took as a whole response body. This is a second, alternative form. In this form we return two values from the handler function - Lua as a language allows returning multiple values from a function which is very handy - a string and any other value. Whenever this form is used Luaw automatically interpretes first string returned as a relative path to a Luaw template view and the second value to be a "model" that is to be passed to the "view" defined by the Luaw template view. The Luaw template view path is always relative to the application root (`luaw_root_dir/webapps/myapp` in case of our example here) and always starts with a "/". The second value returned - the "model" - can be of any type - number, string, boolean or a Lua table. Our example resource handler above reads values for city and zip code from its REST path parameters and puts them in a single Lua table which it then returns as a model. Our Luaw template view in the step 1 above - view-address.lua - gets access to this model passed from the resource handler using variable `model` 74 | 75 | ##Modified web.lua 76 | Modify **/webapps/myapp/web.lua to include the "viewPattern" element that defines a view pattern so Luaw can load any Luaw template view definitions found under "myapp" directory 77 | 78 | luaw_webapp = { 79 | resourcePattern = "handler%-.*%.lua", 80 | viewPattern = "view%-.*%.lua", 81 | } 82 | 83 | ##Test your work 84 | Finally, restart your luaw server by running 85 | 86 | cd luaw_root_dir 87 | ./bin/luaw_server ./conf/server.cfg 88 | 89 | You should see console output similar to this: 90 | 91 | ``` 92 | ********************* Starting webapp myapp **************************** 93 | .Loading resource ./webapps/myapp/handlers/handler-address.lua 94 | .Loading resource ./webapps/myapp/handlers/handler-hellouser.lua 95 | .Loading resource ./webapps/myapp/handlers/handler-helloworld.lua 96 | #Loaded total 3 resources 97 | 98 | .Loading view /views/view-address.lua 99 | #Compiled total 1 views 100 | *********** ********* Webapp myapp started **************************** 101 | ``` 102 | 103 | Note the "loading view" part. 104 | 105 | Now point your browser to http://127.0.0.1:7001/myapp/address/Sunnyvale/94085 and see the output in your browser. 106 | 107 | To verify that the Lua conditional logic embedded in view-address.lua is working properly, point your browser to http://127.0.0.1:7001/myapp/address/Sunnyvale/94086 and see the output. It should include one additional row for the county now. 108 | -------------------------------------------------------------------------------- /docs/user-threads.md: -------------------------------------------------------------------------------- 1 | #11. User Threads 2 | 3 | Luaw allows developer to spawn user threads to execute multiple tasks in parallel. Luaw user threads are implemented using Lua's coroutines and hence are very lightweight compared to real OS threads. In addition to this Luaw also pools and reuses Lua coroutines underlying user threads to make spawning user threads even cheaper. As a result you should be able to spawn thousands of them without having to worry about using up all your system resources. 4 | 5 | Here is a small example of user threads' usage. It uses async HTTP client introduced in last chapter to do two HTTP calls in parallel using user threads, waits till both of them return and then processes both the responses received. It is an example of common scatter/gather pattern frequently encountered in service oriented architecture and illustrates how Luaw's user threads nicely complement Luaw's async HTTP client's functionality 6 | 7 | ```lua 8 | local function parallelHttpRequest(host, url) 9 | local clientReq = Luaw.newClientHttpRequest() 10 | clientReq.hostName = host 11 | clientReq.method = 'GET' 12 | clientReq.url = url 13 | clientReq:addHeader("Host", host) 14 | local clientResp = clientReq:execute() 15 | return clientResp 16 | end 17 | 18 | local scheduler = Luaw.scheduler 19 | -- do two HTTP request in parallel 20 | local threadCtx1 = scheduler.startUserThread(parallelHttpRequest, "www.google.com", "/") 21 | local threadCtx2 = scheduler.startUserThread(parallelHttpRequest, "www.facebook.com", "/") 22 | 23 | -- wait on both threads to be donw 24 | scheduler.join(threadCtx1, threadCtx2) 25 | 26 | -- Retrieve the responses received 27 | local clientResp1 = threadCtx1.result 28 | local clientResp2 = threadCtx2.result 29 | ``` 30 | 31 | 1. You use Luaw.scheduler.startUserThread(function, ...) to start a new user thread. First argument to this method must be a "thread function" that is to be run by the thread being spawn. This function argument may be followed by variable number of arguments which are passed to the thread function as its argument. In case of the example above our thread function is "parallelHttpRequest" which takes two arguments - a hostname and a URL. These two arguments are passed in Luaw.scheduler.startUserThread() after the thread function in the same order. Luaw.scheduler.startUserThread() re-uses internally pooled coroutine - if one is available - to run the thread function provided so this call is really cheap. 32 | 33 | 2. Luaw.scheduler.startUserThread() returns a thread context which you can pass to scheduler.join() to wait on the thread to complete. scheduler.join() accepts variable number of thread contexts so you can wait on more than one thread in a single call. scheduler.join() doesn't return till all the threads represented by thread contexts passed into it have finished executing. 34 | 35 | 3. Value returned by the thread function ("parallelHttpRequest" in our case) can be retrieved as threadCtx.result. Thread function should return only single value (Lua allows functions to return multiple values). If there is a need to return multiple values from the thread function, the function can stuff them all inside a single Lua table with different keys (i.e. property names) and return the Lua table instead. 36 | 37 | 4. Finally, in all other aspects user threads are semantically similar to system threads spawned by Luaw server itself to server incoming HTTP requests. That is, they can use async calls like HTTP client's execute or Timer methods (explained in the next chapter) and Luaw will automatically suspend them when they are waiting for the async calls to return. They are fully hooked into Luaw's internal async callback mechanism. 38 | -------------------------------------------------------------------------------- /docs/user-timers.md: -------------------------------------------------------------------------------- 1 | #12. Luaw Timers 2 | 3 | Luaw supports user defined timers. Here is an example: 4 | 5 | ```lua 6 | local timer = Luaw.newTimer() 7 | timer:start(1000) 8 | doSomeStuff() 9 | print("waiting till it's time...") 10 | timer:wait() 11 | print('done waiting!') 12 | -- call timer: delete() to free timer resources immediately. If not delete() is not called 13 | -- timer resources hang around till Lua's VM garbage collects them 14 | timer:delete() 15 | ``` 16 | 17 | 1. You create a new timer using Luaw.newTimer() 18 | 2. You start it with some timeout - specified in milliseconds using timer:start(timeout) 19 | 3. and finally you wait on it using timer:wait() 20 | 21 | That's basically it! 22 | 23 | There is one more call - `timer:sleep(timeout)` - that combines `timer:start()` and `timer:wait()` in a single function call. The example above can be rewritten as follows provided we did not have to call doSomeStuff() in between: 24 | 25 | ```lua 26 | local timer = Luaw.newTimer() 27 | timer:sleep(1000) 28 | print('done waiting!') 29 | ``` 30 | 31 | Luaw timers are fully hooked into Luaw's async machinert. Just like any other async call - HTTP client's execute(), for example - timer:wait() or timer:sleep() suspend current Luaw thread till the time is up. -------------------------------------------------------------------------------- /lib/luapack.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2015 raksoras 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ]] 22 | 23 | local luaw_constants = require("luaw_constants") 24 | 25 | local lpackMT = getmetatable(luaw_lpack_lib.newLPackParser()) 26 | 27 | lpackMT.INT_RANGES = { 28 | lpackMT.UINT_8, 29 | lpackMT.UINT_16, 30 | lpackMT.UINT_32, 31 | lpackMT.INT_8, 32 | lpackMT.INT_16, 33 | lpackMT.INT_32, 34 | lpackMT.INT_64, 35 | lpackMT.FLOAT, 36 | lpackMT.DOUBLE 37 | } 38 | 39 | lpackMT.FLOAT_RANGES = { 40 | lpackMT.FLOAT, 41 | lpackMT.DOUBLE 42 | } 43 | 44 | lpackMT.STRING_RANGES = { 45 | lpackMT.STRING, 46 | lpackMT.BIG_STRING, 47 | lpackMT.HUGE_STRING 48 | } 49 | 50 | lpackMT.DICT_ENTRY_RANGES = { 51 | lpackMT.DICT_ENTRY, 52 | lpackMT.BIG_DICT_ENTRY 53 | } 54 | 55 | local function findMinRange(num, ranges) 56 | for i, range in ipairs(ranges) do 57 | if ((num >= range[4])and(num <= range[5])) then 58 | return range 59 | end 60 | end 61 | error("Number "..num.." outside supported max range") 62 | end 63 | 64 | -- read functions 65 | 66 | local function readNextBuffer(lpack) 67 | if not lpack.EOF then 68 | local newBuffer = lpack:readFn() 69 | if not newBuffer then 70 | lpack.EOF = true 71 | else 72 | local buffer = lpack.buffer 73 | local offset = lpack.offset 74 | if ((buffer)and(#buffer > offset)) then 75 | lpack.buffer = string.sub(buffer, offset)..newBuffer 76 | else 77 | lpack.buffer = newBuffer 78 | end 79 | lpack.offset = 0 80 | end 81 | end 82 | end 83 | 84 | local function done(lpack) 85 | return ((lpack.EOF)and(lpack.offset >= #lpack.buffer)) 86 | end 87 | 88 | local function readNumber(lpack, numType) 89 | while (not lpack:done()) do 90 | local offset = lpack.offset 91 | local readLen, value = lpack.read_number(numType, lpack.buffer, offset) 92 | if (readLen < 0) then 93 | error("Error while reading number at byte# "..tostring(offset).." in buffer: "..tostring(lpack.buffer)) 94 | end 95 | if (readLen > 0) then 96 | lpack.offset = offset + readLen 97 | return value 98 | end 99 | readNextBuffer(lpack); 100 | end 101 | end 102 | 103 | local function readMarker(lpack) 104 | return readNumber(lpack, lpack.TYPE_MARKER[2]) 105 | end 106 | 107 | local function readString(lpack, desiredLen) 108 | local accm 109 | while ((desiredLen > 0)and(not lpack:done())) do 110 | local offset = lpack.offset 111 | local buffer = lpack.buffer 112 | local readLen, value = lpack.read_string(desiredLen, buffer, offset) 113 | if (readLen > 0) then 114 | lpack.offset = offset + readLen 115 | desiredLen = desiredLen - readLen 116 | 117 | if (desiredLen == 0) then 118 | if accm then 119 | table.insert(accm, value) 120 | return table.concat(accm) 121 | end 122 | return value 123 | end 124 | 125 | if not accm then accm = {} end 126 | table.insert(accm, value) 127 | end 128 | readNextBuffer(lpack); 129 | end 130 | end 131 | 132 | local function deserialize(lpack, container, isMap) 133 | local key, val, len, t 134 | local isKey = true 135 | local dictionary = lpack.dictionary 136 | 137 | while not lpack:done() do 138 | t = readMarker(lpack) 139 | 140 | if t == lpack.NIL[2] then 141 | val = nil 142 | 143 | elseif t == lpack.BOOL_TRUE[2] then 144 | val = true 145 | 146 | elseif t == lpack.BOOL_FALSE[2] then 147 | val = false 148 | 149 | elseif t == lpack.STRING[2] then 150 | len = readNumber(lpack, lpack.UINT_8[2]) 151 | val = readString(lpack, len) 152 | 153 | elseif t == lpack.BIG_STRING[2] then 154 | len = readNumber(lpack, lpack.UINT_16[2]) 155 | val = readString(lpack, len) 156 | 157 | elseif t == lpack.HUGE_STRING[2] then 158 | len = readNumber(lpack, lpack.UINT_32[2]) 159 | val = readString(lpack, len) 160 | 161 | elseif t == lpack.MAP_START[2] then 162 | val = deserialize(lpack, luaw_lpack_lib.createDict(0, 16), true) 163 | 164 | elseif t == lpack.ARRAY_START[2] then 165 | val = deserialize(lpack, luaw_lpack_lib.createDict(16, 0), false) 166 | 167 | elseif t == lpack.RECORD_END[2] then 168 | if ((isMap)and(not isKey)) then 169 | error("Unbalanced table, key without corresponding value found") 170 | end 171 | return container 172 | 173 | elseif t == lpack.DICT_ENTRY[2] then 174 | local dw = readNumber(lpack, lpack.UINT_8[2]) 175 | assert(dictionary, "Missing dictionary") 176 | val = assert(dictionary[dw], "Entry missing in dictionary: "..dw) 177 | 178 | elseif t == lpack.BIG_DICT_ENTRY[2] then 179 | local dw = readNumber(lpack, lpack.UINT_16[2]) 180 | assert(dictionary, "Missing dictionary") 181 | val = assert(dictionary[dw], "Entry missing in dictionary") 182 | 183 | elseif t == lpack.DICT_START[2] then 184 | dictionary = deserialize(lpack, luaw_lpack_lib.createDict(64, 0), false) 185 | lpack.dictionary = dictionary 186 | debugDump(dictionary) 187 | 188 | else 189 | -- everything else is a number 190 | val = readNumber(lpack, t) 191 | end 192 | 193 | if container then 194 | if isMap then 195 | if isKey then 196 | key = val 197 | isKey = false 198 | else 199 | container[key] = val 200 | isKey = true 201 | end 202 | else 203 | -- is array 204 | table.insert(container, val) 205 | end 206 | else 207 | if (t ~= lpack.DICT_START[2]) then 208 | -- single, standalone value 209 | return val 210 | end 211 | end 212 | end 213 | 214 | return val 215 | end 216 | 217 | local function read(lpack) 218 | readNextBuffer(lpack) 219 | val = deserialize(lpack, nil, false) 220 | return val 221 | end 222 | 223 | local function newLPackReader() 224 | local lpackReader = luaw_lpack_lib.newLPackParser(); 225 | lpackReader.EOF = false 226 | lpackReader.buffer = '' 227 | lpackReader.offset = 0 228 | lpackReader.done = done 229 | lpackReader.read = read 230 | return lpackReader 231 | end 232 | 233 | local function newLPackStringReader(str) 234 | assert(str, "String cannot be null") 235 | local lpackReader = newLPackReader() 236 | local eof = false 237 | lpackReader.readFn = function() 238 | if (not eof) then 239 | eof = true 240 | return str 241 | end 242 | end 243 | return lpackReader 244 | end 245 | 246 | local function newLPackFileReader(file) 247 | assert(file, "File cannot be null") 248 | local lpackReader = newLPackReader() 249 | lpackReader.readFn = function() 250 | return file:read(1024) 251 | end 252 | return lpackReader 253 | end 254 | 255 | local function newLPackReqReader(req) 256 | assert(req, "Request cannot be null") 257 | local lpackReader = newLPackReader() 258 | lpackReader.readFn = function() 259 | if ((not req.EOF)and(not req.luaw_mesg_done)) then 260 | req:readAndParse() 261 | local str = req:consumeBodyChunkParsed() 262 | if (not str) then 263 | debugDump(req) 264 | end 265 | return str 266 | end 267 | end 268 | return lpackReader 269 | end 270 | 271 | -- Write functions 272 | 273 | local function flush(lpack) 274 | local writeQ = lpack.writeQ 275 | local count = #writeQ 276 | if count then 277 | local str = lpack.serialize_write_Q(writeQ, lpack.writeQsize) 278 | if str then 279 | lpack:writeFn(str) 280 | lpack.writeQsize = 0 281 | for i=1,count do 282 | writeQ[i] = nil 283 | end 284 | end 285 | end 286 | end 287 | 288 | local function qstore(lpack, val, size) 289 | if not val then 290 | error("nil string passed to write(), use writeNil() instead") 291 | end 292 | 293 | local writeQ = lpack.writeQ 294 | table.insert(writeQ, val); 295 | lpack.writeQsize = lpack.writeQsize + size; 296 | 297 | if (lpack.writeQsize >= lpack.flushLimit) then 298 | flush(lpack) 299 | end 300 | end 301 | 302 | local function writeMarker(lpack, marker) 303 | if ((marker[2] < lpack.TYPE_MARKER[2])or(marker[2] > lpack.HUGE_STRING[2])) then 304 | error("Invalid marker "..marker.." specified") 305 | end 306 | qstore(lpack, marker[2], 1) 307 | end 308 | 309 | local function startMap(lpack) 310 | writeMarker(lpack, lpack.MAP_START) 311 | end 312 | 313 | local function startArray(lpack) 314 | writeMarker(lpack, lpack.ARRAY_START) 315 | end 316 | 317 | local function startDict(lpack) 318 | writeMarker(lpack, lpack.DICT_START) 319 | end 320 | 321 | local function endCollection(lpack) 322 | writeMarker(lpack, lpack.RECORD_END) 323 | end 324 | 325 | local function writeBoolean(lpack, value) 326 | if (value) then 327 | writeMarker(lpack, lpack.BOOL_TRUE) 328 | else 329 | writeMarker(lpack, lpack.BOOL_FALSE) 330 | end 331 | end 332 | 333 | local function writeNil(lpack) 334 | writeMarker(lpack, lpack.NIL) 335 | end 336 | 337 | local function writeNumber(lpack, num) 338 | local range 339 | 340 | if (num % 1 == 0) then 341 | -- integer 342 | range = findMinRange(num, lpack.INT_RANGES) 343 | else 344 | -- float 345 | range = findMinRange(num, lpack.FLOAT_RANGES) 346 | end 347 | qstore(lpack, range[2], 1); 348 | qstore(lpack, num, range[3]); 349 | end 350 | 351 | local function writeString(lpack, str) 352 | local dw, len, range 353 | local dict = lpack.dictionary 354 | if dict then 355 | dw = dict[str] 356 | end 357 | 358 | if dw then 359 | range = findMinRange(dw, lpack.DICT_ENTRY_RANGES) 360 | str = dw 361 | len = range[3] 362 | else 363 | len = #str 364 | range = findMinRange(len, lpack.STRING_RANGES) 365 | end 366 | 367 | qstore(lpack, range[2], 1) -- marker 368 | qstore(lpack, str, len) -- actual string value/dictionary entry 369 | end 370 | 371 | local function serialize(lpack, val) 372 | local t = type(val) 373 | 374 | if t == 'nil' then 375 | writeNil(lpack) 376 | return; 377 | end 378 | 379 | if t == 'boolean' then 380 | writeBoolean(lpack, val) 381 | return 382 | end 383 | 384 | if t == 'number' then 385 | writeNumber(lpack, val) 386 | return 387 | end 388 | 389 | if t == 'string' then 390 | writeString(lpack, val) 391 | return 392 | end 393 | 394 | if t == 'table' then 395 | if (#val > 0) then 396 | writeMarker(lpack, lpack.ARRAY_START) 397 | for i, v in ipairs(val) do 398 | serialize(lpack, v) 399 | end 400 | endCollection(lpack) 401 | else 402 | writeMarker(lpack, lpack.MAP_START) 403 | for k, v in pairs(val) do 404 | serialize(lpack, k) 405 | serialize(lpack, v) 406 | end 407 | endCollection(lpack) 408 | end 409 | end 410 | end 411 | 412 | local function write(lpack, val) 413 | serialize(lpack, val) 414 | flush(lpack) 415 | end 416 | 417 | local function setDictionaryForWrite(lpack, dict) 418 | assert((type(dict) == 'table'), "Please provide valid dictionary table") 419 | local dictionary = luaw_lpack_lib.createDict(#dict, 0) 420 | writeMarker(lpack, lpack.DICT_START) 421 | for i, dw in ipairs(dict) do 422 | writeString(lpack, dw) 423 | dictionary[dw] = i 424 | end 425 | writeMarker(lpack, lpack.RECORD_END) 426 | lpack.dictionary = dictionary 427 | end 428 | 429 | local function newLPackWriter(limit) 430 | local lpackWriter = luaw_lpack_lib.newLPackParser() 431 | lpackWriter.writeQ = {} 432 | lpackWriter.writeQsize = 0 433 | lpackWriter.flushLimit = limit or luaw_constants.CONN_BUFFER_SIZE 434 | lpackWriter.useDictionary = setDictionaryForWrite 435 | lpackWriter.write = write 436 | return lpackWriter 437 | end 438 | 439 | local function newLPackFileWriter(file, limit) 440 | assert(file, "File can not be nil") 441 | local lpackWriter = newLPackWriter(limit) 442 | lpackWriter.writeFn = function(lpack, str) 443 | file:write(str) 444 | end 445 | return lpackWriter 446 | end 447 | 448 | local function newLPackBufferWriter(buff, limit) 449 | assert(buff, "buffer can not be nil") 450 | local lpackWriter = newLPackWriter(limit) 451 | lpackWriter.writeFn = function(lpack, str) 452 | table.insert(buff, str) 453 | end 454 | return lpackWriter 455 | end 456 | 457 | local function newLPackRespWriter(resp, limit) 458 | assert(resp, "response can not be nil") 459 | local lpackWriter = newLPackWriter(limit) 460 | resp.headers['Content-Type'] = 'application/luapack' 461 | resp:startStreaming() 462 | lpackWriter.writeFn = function(lpack, str) 463 | resp:appendBody(str) 464 | end 465 | return lpackWriter 466 | end 467 | 468 | luaw_lpack_lib.newLPackFileReader = newLPackFileReader 469 | luaw_lpack_lib.newLPackStringReader = newLPackStringReader 470 | luaw_lpack_lib.newLPackReqReader = newLPackReqReader 471 | luaw_lpack_lib.newLPackFileWriter = newLPackFileWriter 472 | luaw_lpack_lib.newLPackBufferWriter = newLPackBufferWriter 473 | luaw_lpack_lib.newLPackRespWriter = newLPackRespWriter 474 | 475 | return luaw_lpack_lib 476 | 477 | 478 | -------------------------------------------------------------------------------- /lib/luaw_constants.lua: -------------------------------------------------------------------------------- 1 | local constMT = { 2 | __newindex = function(table, key, value) 3 | error("constant "..table.name.." cannot be changed") 4 | end, 5 | 6 | __tostring = function(table) 7 | return table.name 8 | end, 9 | 10 | __concat = function(op1, op2) 11 | return tostring(op1)..tostring(op2) 12 | end, 13 | 14 | __metatable = "Luaw constant" 15 | } 16 | 17 | local function luaw_constant(value) 18 | local c = {name = value} 19 | setmetatable(c, constMT) 20 | return c 21 | end 22 | 23 | return { 24 | -- scheduler constants 25 | TS_RUNNABLE = luaw_constant("RUNNABLE"), 26 | TS_DONE = luaw_constant("DONE"), 27 | TS_BLOCKED_EVENT = luaw_constant("BLOCKED_ON_EVENT"), 28 | TS_BLOCKED_THREAD = luaw_constant("BLOCKED_ON_THREAD"), 29 | END_OF_CALL = luaw_constant("END_OF_CALL"), 30 | END_OF_THREAD = luaw_constant("END_OF_THREAD"), 31 | 32 | -- TCP constants 33 | DEFAULT_CONNECT_TIMEOUT = luaw_server_config.connect_timeout or 8000, 34 | DEFAULT_READ_TIMEOUT = luaw_server_config.read_timeout or 3000, 35 | DEFAULT_WRITE_TIMEOUT = luaw_server_config.write_timeout or 3000, 36 | CONN_BUFFER_SIZE = luaw_server_config.connection_buffer_size or 4096, 37 | 38 | -- HTTP parser constants 39 | EOF = 0, 40 | CRLF = '\r\n', 41 | MULTIPART_BEGIN = luaw_constant("MULTIPART_BEGIN"), 42 | PART_BEGIN = luaw_constant("PART_BEGIN"), 43 | PART_DATA = luaw_constant("PART_DATA"), 44 | PART_END = luaw_constant("PART_END"), 45 | MULTIPART_END = luaw_constant("MULTIPART_END") 46 | 47 | } -------------------------------------------------------------------------------- /lib/luaw_data_structs_lib.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2015 raksoras 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ]] 22 | 23 | local module = {} 24 | 25 | -- 26 | -- Registry 27 | --- 28 | 29 | -- reg[0] stores head of the free list 30 | local function ref(reg, obj) 31 | if not obj then return -1 end 32 | 33 | local ref = reg[0]; 34 | if ref then 35 | reg[0] = reg[ref] 36 | else 37 | ref = #reg + 1 38 | end 39 | 40 | reg[ref] = obj 41 | reg.size = reg.size + 1 42 | return ref 43 | end 44 | 45 | local function unref(reg, ref) 46 | if ref >= 0 then 47 | reg[ref] = reg[0] 48 | reg[0] = ref 49 | reg.size = reg.size -1 50 | end 51 | end 52 | 53 | function module.newRegistry(size) 54 | local reg = {} 55 | reg.ref = ref; 56 | reg.unref = unref; 57 | reg.size = 0 58 | return reg; 59 | end 60 | 61 | -- 62 | -- Ring buffers 63 | -- 64 | 65 | local function offer(rb, obj) 66 | local size = rb.size 67 | local filled = rb.filled 68 | local writer = rb.writer 69 | 70 | if (filled < size) then 71 | rb[writer] = obj 72 | rb.filled = filled + 1 73 | if (writer == size) then 74 | rb.writer = 1 75 | else 76 | rb.writer = writer + 1 77 | end 78 | return true; 79 | end 80 | return false; 81 | end 82 | 83 | local function offerWithWait(rb, obj) 84 | local added = offer(rb, obj) 85 | while not added do 86 | coroutine.yield() 87 | added = offer(rb, obj) 88 | end 89 | return added 90 | end 91 | 92 | local function take(rb) 93 | local size = rb.size 94 | local filled = rb.filled 95 | local reader = rb.reader 96 | local obj = nil 97 | 98 | if (filled > 0) then 99 | obj = rb[reader]; 100 | rb[reader] = nil; 101 | rb.filled = filled - 1 102 | if (reader == size) then 103 | rb.reader = 1 104 | else 105 | rb.reader = reader + 1 106 | end 107 | end 108 | return obj 109 | end 110 | 111 | local function takeWithWait(rb) 112 | local obj = take(rb) 113 | while not obj do 114 | coroutine.yield() 115 | obj = take(rb) 116 | end 117 | return obj 118 | end 119 | 120 | local function offerWithOverwrite(rb, obj) 121 | local added = offer(rb, obj) 122 | if added then return true end 123 | 124 | -- overwrite oldest item 125 | local overwrittenObj = take(rb) 126 | offer(rb, obj) 127 | return false, overwrittenObj 128 | end 129 | 130 | 131 | function module.newRingBuffer(size) 132 | local rb = {} 133 | rb.reader = 1 134 | rb.writer = 1 135 | rb.filled = 0 136 | rb.size = size 137 | rb.offer = offer 138 | rb.take = take 139 | return rb 140 | end 141 | 142 | function module.newOverwrittingRingBuffer(size) 143 | local rb = {} 144 | rb.reader = 1 145 | rb.writer = 1 146 | rb.filled = 0 147 | rb.size = size 148 | rb.offer = offerWithOverwrite 149 | rb.take = take 150 | return rb 151 | end 152 | 153 | function module.newBlockingRingBuffer(size) 154 | local rb = {} 155 | rb.reader = 1 156 | rb.writer = 1 157 | rb.filled = 0 158 | rb.size = size 159 | rb.offer = offerWithWait 160 | rb.take = takeWithWait 161 | return rb 162 | end 163 | 164 | return module 165 | -------------------------------------------------------------------------------- /lib/luaw_init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2015 raksoras 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ]] 22 | 23 | luaw_utils = require("luaw_utils") 24 | luaw_logging = require("luaw_logging") 25 | luaw_lpack = require("luapack") 26 | luaw_scheduler = require("luaw_scheduler") 27 | luaw_tcp = require("luaw_tcp") 28 | luaw_timer = require("luaw_timer") 29 | luaw_http = require("luaw_http") 30 | luaw_webapp = require("luaw_webapp") 31 | 32 | luaw_webapp.init() -------------------------------------------------------------------------------- /lib/luaw_logging.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2015 raksoras 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ]] 22 | 23 | local ds_lib = require('luaw_data_structs_lib') 24 | local luaw_utils_lib = require("luaw_utils") 25 | local luapack_lib = require('luapack') 26 | 27 | local log_module = {} 28 | 29 | local PATH_SEPARATOR = string.match (package.config, "[^\n]+") 30 | 31 | -- Log file states 32 | local LOG_NOT_OPEN = 0 33 | local OPENING_LOG = 1 34 | local LOG_IS_OPEN = 2 35 | 36 | -- Log levels 37 | local EMERGENCY = 0 38 | local ALERT = 1 39 | local CRITICAL = 2 40 | local ERROR = 3 41 | local WARNING = 4 42 | local NOTICE = 5 43 | local INFO = 6 44 | local DEBUG = 7 45 | 46 | log_module.EMERGENCY = EMERGENCY 47 | log_module.ALERT = ALERT 48 | log_module.CRITICAL = CRITICAL 49 | log_module.ERROR = ERROR 50 | log_module.WARNING = WARNING 51 | log_module.NOTICE = NOTICE 52 | log_module.INFO = INFO 53 | log_module.DEBUG = DEBUG 54 | 55 | -- Log appender types 56 | local FILE_LOG = "FILE" 57 | local SYS_LOG = "SYSLOG" 58 | 59 | log_module.SYSLOG_FACILITY_USER = 1 60 | log_module.SYSLOG_FACILITY_AUTH = 10 61 | log_module.SYSLOG_FACILITY_AUDIT = 13 62 | log_module.SYSLOG_FACILITY_ALERT = 14 63 | log_module.SYSLOG_FACILITY_LOCAL0 = 16 64 | log_module.SYSLOG_FACILITY_LOCAL1 = 17 65 | log_module.SYSLOG_FACILITY_LOCAL2 = 18 66 | log_module.SYSLOG_FACILITY_LOCAL3 = 19 67 | log_module.SYSLOG_FACILITY_LOCAL4 = 20 68 | log_module.SYSLOG_FACILITY_LOCAL5 = 21 69 | log_module.SYSLOG_FACILITY_LOCAL6 = 22 70 | log_module.SYSLOG_FACILITY_LOCAL7 = 23 71 | 72 | local logRoot = { } 73 | 74 | local logDir = assert(luaw_log_config.log_dir, "Invalid log directory specified") 75 | local noOfLogLinesToBuffer = luaw_log_config.log_lines_buffer_count or 100 76 | local logfileBaseName = luaw_log_config.log_file_basename or "luaw-log" 77 | local logfileSizeLimit = luaw_log_config.log_file_size_limit or (1024 * 1024 * 10) -- 10MB 78 | local logfileCountLimit = luaw_log_config.log_file_count_limit or 99 79 | local logLineTimeFormat = luaw_log_config.log_line_timestamp_format or "%x %X" 80 | local logFileNameTimeFormat = luaw_log_config.log_filename_timestamp_format or '%Y%m%d-%H%M%S' 81 | 82 | local syslogTag = luaw_log_config.syslog_tag or 'luaw' 83 | local syslogPresent = luaw_logging_lib.syslogConnect(luaw_log_config.syslog_server, luaw_log_config.syslog_port) 84 | logRoot.facility = luaw_log_config.syslog_facility or log_module.SYSLOG_FACILITY_LOCAL7 85 | local hostname = luaw_logging_lib.hostname() 86 | 87 | 88 | local logSequenceNum = 0 89 | local logSize = 0 90 | local logBuffer = ds_lib.newOverwrittingRingBuffer(noOfLogLinesToBuffer + 32) 91 | local noOfLogLinesDropped = 0 92 | 93 | local currentTimeStr 94 | local syslogTimeStr 95 | 96 | log_module.updateCurrentTime = function(currentTime) 97 | currentTimeStr = os.date(logLineTimeFormat, currentTime) 98 | if syslogPresent then 99 | syslogTimeStr = os.date("%b %d %X", currentTime) 100 | end 101 | end 102 | 103 | local function nextLogSequenceNum() 104 | if logSequenceNum > logfileCountLimit then logSequenceNum = 0 end 105 | logSequenceNum = logSequenceNum + 1 106 | return logSequenceNum 107 | end 108 | 109 | local function concatLogLines() 110 | local temp = luapack_lib.createDict(logBuffer.filled+1, 0) 111 | local i = 1 112 | local logLine = logBuffer:take() 113 | while logLine do 114 | temp[i] = logLine 115 | i = i+1 116 | logLine = logBuffer:take() 117 | end 118 | temp[i] = '' -- for the last newline 119 | return table.concat(temp, '\n') 120 | end 121 | 122 | local function logToFile(logLine) 123 | local added = logBuffer:offer(currentTimeStr..' '..logLine) 124 | if not added then noOfLogLinesDropped = noOfLogLinesDropped +1 end 125 | 126 | local state = luaw_logging_lib.logState() 127 | 128 | if ((state == LOG_IS_OPEN)and(logBuffer.filled >= noOfLogLinesToBuffer)) then 129 | local logBatch = concatLogLines() 130 | logSize = logSize + string.len(logBatch) 131 | local rotateLog = (logSize >= logfileSizeLimit) 132 | state = luaw_logging_lib.writeLog(logBatch, rotateLog) 133 | end 134 | 135 | if (state == LOG_NOT_OPEN) then 136 | logSize = 0 137 | local ts = os.date(logFileNameTimeFormat, os.time()) 138 | local fileName = logDir..PATH_SEPARATOR..logfileBaseName..'-'..ts..'-'..nextLogSequenceNum()..'.log' 139 | luaw_logging_lib.openLog(fileName) 140 | end 141 | end 142 | 143 | local function syslog(priority, facility, mesg) 144 | local pri = priority + (facility * 8) 145 | local logLine = string.format("<%d>%s %s %s: %s", pri, syslogTimeStr, hostname, syslogTag, mesg) 146 | luaw_logging_lib.syslogSend(logLine); 147 | end 148 | 149 | local nameIterator = luaw_utils_lib.splitter('.') 150 | local function splitName(name) 151 | if not name then return luaw_utils_lib.nilFn end 152 | return nameIterator, name, 0 153 | end 154 | 155 | local function logInternal(logLevel, fileLevel, syslogLevel, syslogFacility, mesg) 156 | if (logLevel <= fileLevel) then 157 | logToFile(mesg) 158 | end 159 | if ((syslogPresent)and(logLevel <= syslogLevel)) then 160 | syslog(logLevel, syslogFacility, mesg) 161 | end 162 | end 163 | 164 | local function log(logger, logLevel, mesg) 165 | local fileLevel = logger[FILE_LOG] or ERROR 166 | local syslogLevel = logger[SYS_LOG] or ERROR 167 | logInternal(logLevel, fileLevel, syslogLevel, logger.facility, mesg) 168 | end 169 | 170 | local function logf(logger, logLevel, mesgFormat, ...) 171 | local fileLevel = logger[FILE_LOG] or ERROR 172 | local syslogLevel = logger[SYS_LOG] or ERROR 173 | if ((logLevel <= fileLevel)or(logLevel <= syslogLevel)) then 174 | local mesg = string.format(mesgFormat, ...) 175 | logInternal(logLevel, fileLevel, syslogLevel, logger.facility, mesg) 176 | end 177 | end 178 | 179 | logRoot.log = log 180 | 181 | logRoot.logf = logf 182 | 183 | logRoot.emergency = function(logger, mesg) 184 | log(logger, EMERGENCY, mesg) 185 | end 186 | 187 | logRoot.alert = function(logger, mesg) 188 | log(logger, ALERT, mesg) 189 | end 190 | 191 | logRoot.critical = function(logger, mesg) 192 | log(logger, CRITICAL, mesg) 193 | end 194 | 195 | logRoot.error = function(logger, mesg) 196 | log(logger, ERROR, mesg) 197 | end 198 | 199 | logRoot.warning = function(logger, mesg) 200 | log(logger, WARNING, mesg) 201 | end 202 | 203 | logRoot.notice = function(logger, mesg) 204 | log(logger, NOTICE, mesg) 205 | end 206 | 207 | logRoot.info = function(logger, mesg) 208 | log(logger, INFO, mesg) 209 | end 210 | 211 | logRoot.debug = function(logger, mesg) 212 | log(logger, DEBUG, mesg) 213 | end 214 | 215 | logRoot.emergencyf = function(logger, mesgFormat, ...) 216 | logf(logger, EMERGENCY, mesgFormat, ...) 217 | end 218 | 219 | logRoot.alertf = function(logger, mesgFormat, ...) 220 | logf(logger, ALERT, mesgFormat, ...) 221 | end 222 | 223 | logRoot.criticalf = function(logger, mesgFormat, ...) 224 | logf(logger, CRITICAL, mesgFormat, ...) 225 | end 226 | 227 | logRoot.errorf = function(logger, mesgFormat, ...) 228 | logf(logger, ERROR, mesgFormat, ...) 229 | end 230 | 231 | logRoot.warningf = function(logger, mesgFormat, ...) 232 | logf(logger, WARNING, mesgFormat, ...) 233 | end 234 | 235 | logRoot.noticef = function(logger, mesgFormat, ...) 236 | logf(logger, NOTICE, mesgFormat, ...) 237 | end 238 | 239 | logRoot.infof = function(logger, mesgFormat, ...) 240 | logf(logger, INFO, mesgFormat, ...) 241 | end 242 | 243 | logRoot.debugf = function(logger, mesgFormat, ...) 244 | logf(logger, DEBUG, mesgFormat, ...) 245 | end 246 | 247 | local function getLogger(name) 248 | local logger = logRoot 249 | if (name == 'root') then return logger end 250 | 251 | for idx, namePart in splitName(name) do 252 | local child = logger[namePart] 253 | if not child then 254 | child = {} 255 | setmetatable(child, {__index = logger}) 256 | logger[namePart] = child 257 | end 258 | logger = child 259 | end 260 | return logger 261 | end 262 | 263 | log_module.getLogger = getLogger 264 | 265 | local function configureLogger(logCfg, logType) 266 | local loggerName = assert(logCfg.name, "Logger name missing") 267 | local logLevel = assert(logCfg.level, "Logger level missing") 268 | local logger = assert(getLogger(loggerName), "Could not find logger "..loggerName) 269 | logger[logType] = logLevel 270 | return logger 271 | end 272 | 273 | log_module.file = function(logCfg) 274 | configureLogger(logCfg, FILE_LOG) 275 | end 276 | 277 | log_module.syslog = function(logCfg) 278 | local logger = configureLogger(logCfg, SYS_LOG) 279 | logger.facility = logCfg.facility 280 | end 281 | 282 | return log_module -------------------------------------------------------------------------------- /lib/luaw_scheduler.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2015 raksoras 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ]] 22 | 23 | local constants = require('luaw_constants') 24 | local ds_lib = require('luaw_data_structs_lib') 25 | local logging = require('luaw_logging') 26 | 27 | -- Scheduler object 28 | local scheduler = {} 29 | 30 | -- Constants 31 | local TS_RUNNABLE = constants.RUNNABLE 32 | local TS_DONE = constants.DONE 33 | local TS_BLOCKED_EVENT = constants.BLOCKED_ON_EVENT 34 | local TS_BLOCKED_THREAD = constants.BLOCKED_ON_THREAD 35 | local END_OF_CALL = constants.END_OF_CALL 36 | local END_OF_THREAD = constants.END_OF_THREAD 37 | 38 | local UPDATE_TIME_COUNTER_LIMIT = 10 39 | 40 | -- scheduler state 41 | local threadRegistry = ds_lib.newRegistry(luaw_server_config.thread_pool_size or 1024) 42 | local threadPool = ds_lib.newRingBuffer(luaw_server_config.thread_pool_size or 1024) 43 | local timesReuseThread = luaw_server_config.thread_reuse_limit or 1024 44 | local runQueueLen = 0 45 | local runQueueHead = nil 46 | local runQueueTail = nil 47 | local currentRunningThreadCtx = nil 48 | local updateTimeCyclingCounter = 0 49 | local currentTime 50 | 51 | 52 | scheduler.updateCurrentTime = function () 53 | updateTimeCyclingCounter = updateTimeCyclingCounter + 1 54 | if (updateTimeCyclingCounter >= UPDATE_TIME_COUNTER_LIMIT) then 55 | currentTime = os.time() 56 | logging.updateCurrentTime(currentTime) 57 | updateTimeCyclingCounter = 0 58 | end 59 | end 60 | 61 | scheduler.time = function() 62 | return currentTime 63 | end 64 | 65 | -- returns current running thread's id 66 | scheduler.tid = function() 67 | if currentRunningThreadCtx then 68 | return currentRunningThreadCtx.tid 69 | end 70 | return nil 71 | end 72 | 73 | local function threadRunLoop(fn, arg1, arg2, arg3, arg4) 74 | local i = 0 75 | while i <= timesReuseThread do 76 | fn, arg1, arg2, arg3, arg4 = coroutine.yield(END_OF_CALL, fn(arg1, arg2, arg3, arg4)) 77 | i = i+1 78 | end 79 | return END_OF_THREAD, fn(arg1, arg2, arg3, arg4) 80 | end 81 | 82 | local function userThreadRunner(userThreadFn, ...) 83 | -- We have captured user thread function along with its arguments on a coroutine stack. 84 | -- Yield now so that scheduler can add this thread in run queue for "bottom half" 85 | -- processing later and original calling thread can resume. 86 | coroutine.yield(TS_RUNNABLE) 87 | -- At this point we have been resumed by thread scheduler during the "bottom half" run 88 | -- queue processing bu the scheduler so run the actual user thread function. 89 | return userThreadFn(...) 90 | end 91 | 92 | local function addToRunQueue(threadCtx) 93 | if not runQueueTail then 94 | runQueueHead = threadCtx 95 | runQueueTail = threadCtx 96 | else 97 | runQueueTail.nextThread = threadCtx 98 | runQueueTail = threadCtx 99 | end 100 | runQueueLen = runQueueLen + 1 101 | threadCtx.state = TS_RUNNABLE 102 | end 103 | 104 | local function newThread() 105 | local t = threadPool:take() 106 | if not t then 107 | t = coroutine.create(threadRunLoop) 108 | end 109 | 110 | local threadCtx = { thread = t, requestCtx = {} } 111 | -- anchor thread in registry to prevent GC 112 | local ref = threadRegistry:ref(threadCtx) 113 | threadCtx.tid = ref 114 | return threadCtx 115 | end 116 | 117 | local function unblockJoinedThreadIfAny(threadCtx, status, retVal) 118 | local joinedTC = threadCtx.joinedBy 119 | if joinedTC then 120 | local count = joinedTC.joinCount 121 | count = count -1 122 | joinedTC.joinCount = count 123 | if (count <= 0) then 124 | addToRunQueue(joinedTC) 125 | end 126 | end 127 | end 128 | 129 | local function afterResume(threadCtx, state, retVal) 130 | threadCtx.state, threadCtx.result = state, retVal 131 | currentRunningThreadCtx = nil 132 | if (state == TS_DONE) then 133 | return true, retVal 134 | end 135 | return false, retVal 136 | end 137 | 138 | local function resumeThread(threadCtx, ...) 139 | currentRunningThreadCtx = threadCtx 140 | local t = threadCtx.thread 141 | local tid = threadCtx.tid 142 | 143 | scheduler.updateCurrentTime() 144 | 145 | context = threadCtx.requestCtx -- TLS, per thread context 146 | local status, state, retVal = coroutine.resume(t, ...) 147 | context = nil -- reset TLS context 148 | 149 | if not status then 150 | -- thread ran into error 151 | print("Error: "..tostring(state)) 152 | state = END_OF_THREAD 153 | -- thread has blown its stack so let it get garbage collected 154 | t = nil 155 | end 156 | 157 | if ((state == END_OF_THREAD)or(state == END_OF_CALL)) then 158 | threadRegistry:unref(tid) 159 | threadCtx.thread = nil 160 | threadCtx.requestCtx = nil 161 | if ((state == END_OF_CALL) and (t)) then 162 | -- thread is still alive, return it to free pool if possible 163 | threadPool:offer(t) 164 | end 165 | unblockJoinedThreadIfAny(threadCtx, status, retVal) 166 | return afterResume(threadCtx, TS_DONE, retVal) 167 | end 168 | 169 | if ((state == TS_BLOCKED_EVENT)or(state == TS_BLOCKED_THREAD)) then 170 | -- thread will later be resumed by libuv call back 171 | return afterResume(threadCtx, state, retVal) 172 | end 173 | 174 | -- Thread yielded, but is still runnable. Add it back to the run queue 175 | addToRunQueue(threadCtx) 176 | return afterResume(threadCtx, TS_RUNNABLE, retVal) 177 | end 178 | 179 | function resumeThreadId(tid, ...) 180 | local threadCtx = threadRegistry[tid] 181 | if not threadCtx then error("Invalid thread Id "..tostring(tid)) end 182 | return resumeThread(threadCtx, ...) 183 | end 184 | 185 | scheduler.resumeThreadId = resumeThreadId 186 | 187 | function startSystemThread(serviceFn, conn, ...) 188 | local threadCtx = newThread() 189 | threadCtx.state = TS_RUNNABLE 190 | local isDone = resumeThread(threadCtx, serviceFn, conn, ...) 191 | return isDone, threadCtx.tid 192 | end 193 | 194 | scheduler.startSystemThread = startSystemThread 195 | 196 | -- Scheduler object methods 197 | 198 | scheduler.startUserThread = function(userThreadFn, ...) 199 | local backgroundThreadCtx = newThread() 200 | coroutine.resume(backgroundThreadCtx.thread, userThreadRunner, userThreadFn, ...) 201 | addToRunQueue(backgroundThreadCtx) 202 | return backgroundThreadCtx; 203 | end 204 | 205 | scheduler.join = function(...) 206 | local joiningTC = currentRunningThreadCtx 207 | if (joininTC) then 208 | local joinedThreads = table.pack(...) 209 | local numOfThreads = #joinedThreads 210 | 211 | local count = 0 212 | 213 | for i, joinedTC in ipairs(joinedThreads) do 214 | if ((joinedTC)and(joinedTC.state)and(joinedTC.state ~= TS_DONE)) then 215 | count = count + 1 216 | joinedTC.joinedBy = joiningTC 217 | end 218 | end 219 | 220 | joiningTC.joinCount = count 221 | while (joiningTC.joinCount > 0) do 222 | coroutine.yield(TS_BLOCKED_THREAD) 223 | end 224 | end 225 | end 226 | 227 | scheduler.runQueueSize = function() 228 | return runQueueLen 229 | end 230 | 231 | local runNextFromRunQueue = function() 232 | local threadCtx = runQueueHead 233 | if threadCtx then 234 | runQueueHead = threadCtx.nextThread 235 | if not runQueueHead then 236 | runQueueTail = nil 237 | end 238 | 239 | threadCtx.nextThread = nil 240 | 241 | runQueueLen = runQueueLen -1 242 | if (runQueueLen < 0) then 243 | runQueueLen = 0 244 | end 245 | 246 | if (threadCtx.state == TS_DONE) then 247 | -- This can happen when thread is added to the run queue but is woken up by libuv 248 | -- event and then runs to completion before the run queue scheduler gets chance 249 | -- to resume it 250 | return 251 | end 252 | 253 | return resumeThread(threadCtx) 254 | end 255 | end 256 | 257 | scheduler.runReadyThreads = function(limit) 258 | local runnableCount = runQueueLen 259 | if ((limit)and(limit < runnableCount)) then 260 | runnableCount = limit 261 | end 262 | 263 | for i=1, runnableCount do 264 | runNextFromRunQueue() 265 | end 266 | 267 | -- about to block on libuv event loop, next resumeThread should update current time 268 | -- as it may have spent significant time blocked on a event loop. 269 | updateTimeCyclingCounter = UPDATE_TIME_COUNTER_LIMIT 270 | 271 | return runnableCount 272 | end 273 | 274 | scheduler.updateCurrentTime() 275 | 276 | return scheduler 277 | -------------------------------------------------------------------------------- /lib/luaw_tcp.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2015 raksoras 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ]] 22 | 23 | local constants = require('luaw_constants') 24 | local scheduler = require('luaw_scheduler') 25 | 26 | local DEFAULT_CONNECT_TIMEOUT = constants.DEFAULT_CONNECT_TIMEOUT 27 | local DEFAULT_READ_TIMEOUT = constants.DEFAULT_READ_TIMEOUT 28 | local DEFAULT_WRITE_TIMEOUT = constants.DEFAULT_WRITE_TIMEOUT 29 | local CONN_BUFFER_SIZE = constants.CONN_BUFFER_SIZE 30 | 31 | local conn = luaw_tcp_lib.newConnection(); 32 | local connMT = getmetatable(conn) 33 | conn:close() 34 | local startReadingInternal = connMT.startReading 35 | local readInternal = connMT.read 36 | local writeInternal = connMT.write 37 | 38 | connMT.startReading = function(self) 39 | local status, mesg = startReadingInternal(self) 40 | assert(status, mesg) 41 | end 42 | 43 | connMT.read = function(self, readTimeout) 44 | local status, str = readInternal(self, scheduler.tid(), readTimeout or DEFAULT_READ_TIMEOUT) 45 | if ((status)and(not str)) then 46 | -- nothing in buffer, wait for libuv on_read callback 47 | status, str = coroutine.yield(TS_BLOCKED_EVENT) 48 | end 49 | return status, str 50 | end 51 | 52 | connMT.write = function(self, str, writeTimeout) 53 | local status, nwritten = writeInternal(self, scheduler.tid(), str, writeTimeout or DEFAULT_WRITE_TIMEOUT) 54 | if ((status)and(nwritten > 0)) then 55 | -- there is something to write, yield for libuv callback 56 | status, nwritten = coroutine.yield(TS_BLOCKED_EVENT) 57 | end 58 | assert(status, nwritten) 59 | return nwritten 60 | end 61 | 62 | local connectInternal = luaw_tcp_lib.connect 63 | 64 | local function connect(hostIP, hostName, port, connectTimeout) 65 | assert((hostName or hostIP), "Either hostName or hostIP must be specified in request") 66 | local threadId = scheduler.tid() 67 | if not hostIP then 68 | local status, mesg = luaw_tcp_lib.resolveDNS(hostName, threadId) 69 | assert(status, mesg) 70 | status, mesg = coroutine.yield(TS_BLOCKED_EVENT) 71 | assert(status, mesg) 72 | hostIP = mesg 73 | end 74 | 75 | local connectTimeout = connectTimeout or DEFAULT_CONNECT_TIMEOUT 76 | local conn, mesg = connectInternal(hostIP, port, threadId, connectTimeout) 77 | 78 | -- initial connect_req succeeded, block for libuv callback 79 | assert(coroutine.yield(TS_BLOCKED_EVENT)) 80 | return conn, mesg 81 | end 82 | 83 | luaw_tcp_lib.connect = connect 84 | 85 | return luaw_tcp_lib -------------------------------------------------------------------------------- /lib/luaw_timer.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2015 raksoras 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ]] 22 | 23 | local constants = require('luaw_constants') 24 | local TS_BLOCKED_EVENT = constants.TS_BLOCKED_EVENT 25 | 26 | 27 | local timerMT = getmetatable(luaw_timer_lib.newTimer()) 28 | local waitInternal = timerMT.wait 29 | 30 | timerMT.wait = function(timer) 31 | local status, elapsed = waitInternal(timer, scheduler.tid()) 32 | if ((status) and (not elapsed)) then 33 | -- timer not yet elapsed, wait for libuv on_timeout callback 34 | status, elapsed = coroutine.yield(TS_BLOCKED_EVENT) 35 | end 36 | return status, elapsed 37 | end 38 | 39 | timerMT.sleep = function(timer, timeout) 40 | assert(timer:start(timeout)) 41 | timer:wait() 42 | end 43 | 44 | return luaw_timer_lib -------------------------------------------------------------------------------- /lib/luaw_utils.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2015 raksoras 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ]] 22 | 23 | local luaw_util_lib = {} 24 | 25 | local function tprint(tbl, indent, tab) 26 | for k, v in pairs(tbl) do 27 | if type(v) == "table" then 28 | print(string.rep(tab, indent) .. tostring(k) .. ": {") 29 | tprint(v, indent+1, tab) 30 | print(string.rep(tab, indent) .. "}") 31 | else 32 | print(string.rep(tab, indent) .. tostring(k) .. ": " .. tostring(v)) 33 | end 34 | end 35 | end 36 | 37 | -- Print contents of `tbl`, with indentation. 38 | -- `indent` sets the initial level of indentation. 39 | luaw_util_lib.debugDump = function(tbl, indent, tab) 40 | indent = indent or 0 41 | tab = tab or " " 42 | print(string.rep(tab, indent) .. "{") 43 | tprint(tbl, indent+1, tab) 44 | print(string.rep(tab, indent) .. "}") 45 | end 46 | 47 | luaw_util_lib.steplight = function(mesg) 48 | local tid = tostring(Luaw.scheduler.tid()) 49 | print("Thread-"..tid.."> "..tostring(mesg)) 50 | end 51 | 52 | luaw_util_lib.step = function(mesg, level) 53 | local tid = tostring(Luaw.scheduler.tid()) 54 | 55 | local lvl = level or 2 56 | if (lvl < 0) then lvl = lvl * -1 end 57 | 58 | local dc = debug.getinfo(lvl, "nSl") 59 | 60 | local str = "" 61 | if type(mesg) == 'table' then 62 | for k,v in pairs(mesg) do 63 | str = str..", "..tostring(k).."="..tostring(v) 64 | end 65 | else 66 | str = tostring(mesg) 67 | end 68 | 69 | print('Thread '..tid..'> line# '..tostring(dc.linedefined)..' in function '..tostring(dc.name)..' in file '..tostring(dc.source)..': '..str) 70 | 71 | if ((level)and(level < 0)) then 72 | print(debug.traceback()) 73 | end 74 | end 75 | 76 | luaw_util_lib.run = function(codeblock) 77 | if (codeblock) then 78 | local try = codeblock.try 79 | if (try) then 80 | local catch = codeblock.catch 81 | local finally = codeblock.finally 82 | 83 | local status, err = pcall(try, codeblock) 84 | if ((not status)and(catch)) then 85 | status, err = pcall(catch, codeblock, err) 86 | end 87 | 88 | if (finally) then 89 | finally(codeblock) 90 | end 91 | 92 | if (not status) then 93 | error(err) 94 | end 95 | end 96 | end 97 | end 98 | 99 | luaw_util_lib.clearArrayPart = function(t) 100 | local len = #t 101 | for i=1,len do 102 | t[i] = nil 103 | end 104 | end 105 | 106 | luaw_util_lib.splitter = function(splitCh) 107 | local separator = string.byte(splitCh, 1, 1) 108 | local byte = string.byte 109 | 110 | return function (str, pos) 111 | pos = pos + 1 112 | local start = pos 113 | local len = #str 114 | while pos <= len do 115 | local ch = byte(str, pos, pos) 116 | if (ch == separator) then 117 | if (pos > start) then 118 | return pos, string.sub(str, start, pos-1) 119 | end 120 | start = pos + 1 121 | end 122 | pos = pos + 1 123 | end 124 | if (pos > start) then return pos, string.sub(str, start, pos) end 125 | end 126 | end 127 | 128 | luaw_util_lib.nilFn = function() 129 | return nil 130 | end 131 | 132 | luaw_util_lib.formattedLine = function(str, lineSize, paddingCh, beginCh, endCh) 133 | lineSize = lineSize or 0 134 | paddingCh = paddingCh or '' 135 | beginCh = beginCh or '' 136 | endCh = endCh or '' 137 | paddingWidth = (lineSize - #str -2)/2 138 | local padding = '' 139 | if paddingWidth > 0 then 140 | padding = string.rep(paddingCh, paddingWidth) 141 | end 142 | print(string.format("%s %s %s %s %s", beginCh, padding, str, padding, endCh)) 143 | end 144 | 145 | return luaw_util_lib 146 | -------------------------------------------------------------------------------- /lib/unit_testing.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2015 raksoras 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ]] 22 | 23 | local module = {total_run = 0, total_failed = 0} 24 | 25 | function module.assertTrue(expr) 26 | if not expr then 27 | error("Assert true failed!", 2) 28 | end 29 | end 30 | 31 | function module.assertFalse(expr) 32 | if expr then 33 | error("Assert false failed!", 2) 34 | end 35 | end 36 | 37 | function module.assertNotNil(expr) 38 | if not expr then 39 | error("Assert not nil failed!", 2) 40 | end 41 | end 42 | 43 | function module.assertNil(expr) 44 | if expr then 45 | error("Assert nil failed!", 2) 46 | end 47 | end 48 | 49 | function module.assertEqual(actual, expected) 50 | if (actual ~= expected) then 51 | error(string.format("Assert equal failed! Actual: [%s], Expected: [%s]", actual, expected), 2) 52 | end 53 | end 54 | 55 | function module.assertNotEqual(actual, expected) 56 | if (actual == expected) then 57 | error(string.format("Assert not equal failed! Actual: [%s], Expected: [%s]", actual, expected), 2) 58 | end 59 | end 60 | 61 | local function tprint(tbl, indent, tab) 62 | for k, v in pairs(tbl) do 63 | if type(v) == "table" then 64 | print(string.rep(tab, indent) .. tostring(k) .. ": {") 65 | tprint(v, indent+1, tab) 66 | print(string.rep(tab, indent) .. "}") 67 | else 68 | print(string.rep(tab, indent) .. tostring(k) .. ": " .. tostring(v)) 69 | end 70 | end 71 | end 72 | 73 | -- Print contents of `tbl`, with indentation. 74 | -- `indent` sets the initial level of indentation. 75 | function module.printTable (tbl, indent, tab) 76 | indent = indent or 0 77 | tab = tab or " " 78 | print(string.rep(tab, indent) .. "{") 79 | tprint(tbl, indent+1, tab) 80 | print(string.rep(tab, indent) .. "}") 81 | end 82 | 83 | function module:runTests() 84 | number_run = 0 85 | number_failed = 0 86 | 87 | for name, func in pairs(self) do 88 | if (string.find(name, "test") == 1) then 89 | result, mesg = pcall(func) 90 | number_run = number_run + 1 91 | if (string.find(name, "testError") == 1) then 92 | -- negative test 93 | if not result then 94 | print(string.format(" %-40s [OK]", name)); 95 | else 96 | number_failed = number_failed + 1 97 | print(string.format(" %-30s [FAILED! Expected to throw error]", name)); 98 | end 99 | else 100 | if result then 101 | print(string.format(" %-40s [OK]", name)); 102 | else 103 | number_failed = number_failed + 1 104 | print(string.format(" %-30s [FAILED! %s]", name, mesg)); 105 | end 106 | end 107 | self[name] = nil 108 | end 109 | end 110 | 111 | self.total_run = self.total_run + number_run 112 | self.total_failed = self.total_failed + number_failed 113 | 114 | local info = debug.getinfo(2, "S") 115 | print('----------------------------------------------------------------------------') 116 | print(string.format("%s: Total# %d, Failed# %d", info.source, number_run, number_failed)); 117 | print('----------------------------------------------------------------------------') 118 | end 119 | 120 | function printOverallSummary() 121 | print('\n*****************************************************************************') 122 | print(string.format("Overall Summary: Total# %d, Failed# %d", module.total_run, module.total_failed)); 123 | print('*****************************************************************************\n') 124 | 125 | end 126 | 127 | return module; -------------------------------------------------------------------------------- /sample/conf/server.cfg: -------------------------------------------------------------------------------- 1 | luaw_server_config = { 2 | server_ip = "0.0.0.0", 3 | server_port = 7001, 4 | connect_timeout = 4000, 5 | read_timeout = 8000, 6 | write_timeout = 8000 7 | } 8 | 9 | luaw_log_config = { 10 | log_dir = "./logs", 11 | log_file_basename = "luaw-log", 12 | log_file_size_limit = 1024*1024, 13 | log_file_count_limit = 9, 14 | log_filename_timestamp_format = '%Y%m%d', 15 | log_lines_buffer_count = 16, 16 | syslog_server = "127.0.0.1", 17 | syslog_port = 514, 18 | } 19 | 20 | luaw_webapp_config = { 21 | base_dir = "./webapps" 22 | } 23 | 24 | -------------------------------------------------------------------------------- /sample/proxy_handler.lua: -------------------------------------------------------------------------------- 1 | local http_lib = require('luaw_http') 2 | --[[ 3 | Luaw allows you to replace it's default MVC/REST request handler with your own custom HTTP 4 | request handler implementation. To override the default HTTP request handler just set Luaw object's 5 | request_handler property to your custom Lua function. This function is passed in a low level connection 6 | object for each incoming request instead of the normal request and response objects passed to REST handler. 7 | The function is called on its own separate Luaw coroutine for each HTTP request so you don't have to worry 8 | about multithreaded access to shared state inside the function. 9 | ]] 10 | 11 | http_lib.request_handler = function(conn) 12 | conn:startReading() 13 | 14 | -- loop to support HTTP 1.1 persistent (keep-alive) connections 15 | while true do 16 | local req = http_lib.newServerHttpRequest(conn) 17 | local resp = http_lib.newServerHttpResponse(conn) 18 | 19 | -- read and parse full request 20 | req:readFull() 21 | if (req.EOF) then 22 | conn:close() 23 | return "connection reset by peer" 24 | end 25 | 26 | local reqHeaders = req.headers 27 | local beHost = reqHeaders['backend-host'] 28 | local beURL = reqHeaders['backend-url'] 29 | 30 | if (beHost and beURL) then 31 | local backendReq = http_lib.newClientHttpRequest() 32 | backendReq.hostName = beHost 33 | backendReq.url = beURL 34 | backendReq.method = 'GET' 35 | backendReq.headers = { Host = beHost } 36 | 37 | local status, backendResp = pcall(backendReq.execute, backendReq) 38 | if (status) then 39 | resp:setStatus(backendResp:getStatus()) 40 | resp:appendBody(backendResp:getBody()) 41 | local beHeaders = backendResp.headers 42 | for k,v in pairs(beHeaders) do 43 | if ((k ~= 'Transfer-Encoding')and(k ~= 'Content-Length')) then 44 | resp:addHeader(k,v) 45 | end 46 | end 47 | backendResp:close() 48 | else 49 | resp:setStatus(500) 50 | resp:appendBody("connection to backend server failed") 51 | end 52 | else 53 | resp:setStatus(400) 54 | resp:appendBody("Request must contain headers backend-host and backend-url\n") 55 | end 56 | 57 | local status, mesg = pcall(resp.flush, resp) 58 | if (not status) then 59 | conn:close() 60 | return error(mesg) 61 | end 62 | 63 | if (req:shouldCloseConnection() or resp:shouldCloseConnection()) then 64 | conn:close() 65 | return "connection reset by peer" 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /sample/webapps/myapp/handlers/handler-address.lua: -------------------------------------------------------------------------------- 1 | registerHandler { 2 | method = 'GET', 3 | path = 'address/:city/#zip', 4 | 5 | handler = function(req, resp, pathParams) 6 | address = { 7 | city = pathParams.city, 8 | zip = pathParams.zip 9 | } 10 | return '/views/view-address.lua', address 11 | end 12 | } 13 | -------------------------------------------------------------------------------- /sample/webapps/myapp/handlers/handler-fileupload-display.lua: -------------------------------------------------------------------------------- 1 | registerHandler { 2 | method = 'GET', 3 | path = 'showform', 4 | 5 | handler = function(req, resp, pathParams) 6 | return [[ 7 | 8 | 9 | 10 | 11 | upload 12 | 13 | 14 |
15 |

16 |

17 |

18 |

19 |

20 |

21 | 22 | 23 | ]] 24 | end 25 | } 26 | -------------------------------------------------------------------------------- /sample/webapps/myapp/handlers/handler-fileupload-process.lua: -------------------------------------------------------------------------------- 1 | local function append(buffer, str) 2 | if (str) then 3 | table.insert(buffer, str) 4 | table.insert(buffer, ", ") 5 | end 6 | end 7 | 8 | 9 | registerHandler { 10 | method = 'POST', 11 | path = 'filesupload', 12 | 13 | handler = function(req, resp, pathParams) 14 | if (req:isMultipart()) then 15 | local token, fieldName, fileName, contentType 16 | local buffer = {} 17 | for token, fieldName, fileName, contentType in req:multiPartIterator() do 18 | append(buffer, tostring(token)) 19 | append(buffer, fieldName) 20 | append(buffer, fileName) 21 | append(buffer, contentType) 22 | table.insert(buffer,"\n") 23 | end 24 | return table.concat(buffer) 25 | end 26 | return "Not a multi-part file upload" 27 | end 28 | } 29 | -------------------------------------------------------------------------------- /sample/webapps/myapp/handlers/handler-hellouser.lua: -------------------------------------------------------------------------------- 1 | registerHandler { 2 | method = 'GET', 3 | path = '/user/:username/#count', 4 | 5 | handler = function(req, resp, pathParams) 6 | return "Hello "..pathParams.username.."! You are user number "..pathParams.count.." to visit this site." 7 | end 8 | } 9 | 10 | -------------------------------------------------------------------------------- /sample/webapps/myapp/handlers/handler-helloworld.lua: -------------------------------------------------------------------------------- 1 | registerHandler { 2 | method = 'GET', 3 | path = 'helloworld', 4 | 5 | handler = function(req, resp, pathParams) 6 | return "Hello World!" 7 | end 8 | } 9 | 10 | -------------------------------------------------------------------------------- /sample/webapps/myapp/handlers/handler-read-lpack.lua: -------------------------------------------------------------------------------- 1 | local utils_lib = require('luaw_utils') 2 | local http_lib = require('luaw_http') 3 | local lpack = require('luapack') 4 | 5 | registerHandler { 6 | method = 'GET', 7 | path = 'readlpack', 8 | 9 | handler = function(req, resp, pathParams) 10 | local backendReq = http_lib.newClientHttpRequest() 11 | backendReq.hostName = 'localhost' 12 | backendReq.port = 7001 13 | backendReq.url = '/myapp/genlpack' 14 | backendReq.method = 'GET' 15 | backendReq.headers = { Host = 'localhost' } 16 | local backendResp = backendReq:execute() 17 | local lpackReader = lpack.newLPackReqReader(backendResp) 18 | local mesg = lpackReader:read() 19 | 20 | print('\n================================\n') 21 | utils_lib.debugDump(backendResp.headers) 22 | print('\n================================\n') 23 | utils_lib.debugDump(mesg) 24 | print('\n================================\n') 25 | return "OK" 26 | end 27 | } 28 | -------------------------------------------------------------------------------- /sample/webapps/myapp/handlers/handler-write-lpack.lua: -------------------------------------------------------------------------------- 1 | local utils_lib = require('luaw_utils') 2 | local http_lib = require('luaw_http') 3 | local lpack = require('luapack') 4 | 5 | local big_str = "abcdefghijklmnopqrstuvwxyz0123456789" 6 | while (#big_str < 4096) do 7 | big_str = big_str ..'-'..big_str 8 | end 9 | 10 | local mesg = { 11 | name = "Homer Simpson", 12 | gender = "M", 13 | uint8 = 255, 14 | uint16 = 256, 15 | uint32 = 4294967295, 16 | int8_neg = -128, 17 | int8 = 127, 18 | int16_neg = -1000, 19 | int16 = 20000, 20 | int32 = 32456, 21 | int64= 17179869184, 22 | int64_neg= -17179869184, 23 | float = 0.0012, 24 | float_neg = - 112.8, 25 | double = 8589934592.13, 26 | double_neg = -8589934592.28, 27 | str = "ABCD", 28 | bigstr = big_str, 29 | positive = true, 30 | negative = false, 31 | kids = { 32 | { 33 | name = "Lisa", 34 | gender = "F", 35 | uint8 = 255, 36 | uint16 = 256, 37 | uint32 = 4294967295, 38 | int8_neg = -128, 39 | int8 = 127, 40 | int16_neg = -1000, 41 | int16 = 20000, 42 | int32 = 32456, 43 | int64= 17179869184, 44 | int64_neg= -17179869184, 45 | float = 0.0012, 46 | float_neg = - 112.8, 47 | double = 8589934592.13, 48 | double_neg = -8589934592.28, 49 | str = "ABCD", 50 | bigstr = big_str, 51 | positive = true, 52 | negative = false 53 | }, 54 | { 55 | name = "Bart", 56 | gender = "M", 57 | uint8 = 255, 58 | uint16 = 256, 59 | uint32 = 4294967295, 60 | int8_neg = -128, 61 | int8 = 127, 62 | int16_neg = -1000, 63 | int16 = 20000, 64 | int32 = 32456, 65 | int64= 17179869184, 66 | int64_neg= -17179869184, 67 | float = 0.0012, 68 | float_neg = - 112.8, 69 | double = 8589934592.13, 70 | double_neg = -8589934592.28, 71 | str = "ABCD", 72 | bigstr = big_str, 73 | positive = true, 74 | negative = false 75 | }, 76 | { 77 | name = "Maggy", 78 | gender = "?" 79 | } 80 | } 81 | } 82 | 83 | local dict = { 84 | "name", 85 | "gender", 86 | "uint8", 87 | "uint16", 88 | "uint32", 89 | "int8_neg", 90 | "int8", 91 | "int16_neg", 92 | "int16", 93 | "int32", 94 | "int64", 95 | "int64_neg", 96 | "float", 97 | "float_neg", 98 | "double", 99 | "double_neg", 100 | "str", 101 | "bigstr", 102 | "positive", 103 | "negative", 104 | big_str 105 | } 106 | 107 | registerHandler { 108 | method = 'GET', 109 | path = 'genlpack', 110 | 111 | handler = function(req, resp, pathParams) 112 | resp:setStatus(200) 113 | local lpackWriter = lpack.newLPackRespWriter(resp) 114 | if (req.params['dict'] == 'true') then 115 | lpackWriter:useDictionary(dict) 116 | end 117 | lpackWriter:write(mesg) 118 | end 119 | } 120 | -------------------------------------------------------------------------------- /sample/webapps/myapp/views/view-address.lua: -------------------------------------------------------------------------------- 1 | BEGIN 'html' 2 | BEGIN 'head' 3 | BEGIN 'title' 4 | TEXT 'Address' 5 | END 'title' 6 | END 'head' 7 | BEGIN 'body' 8 | BEGIN 'div' {class='address'} 9 | BEGIN 'h1' 10 | TEXT(model.title) 11 | END 'h1' 12 | BEGIN 'table' {border="1", margin="1px"} 13 | BEGIN 'tr' 14 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 15 | TEXT 'City' 16 | END 'td' 17 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 18 | TEXT(model.city) 19 | END 'td' 20 | END 'tr' 21 | if (model.zip == 94086) then 22 | BEGIN 'tr' 23 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 24 | TEXT 'County' 25 | END 'td' 26 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 27 | TEXT 'Santa Clara' 28 | END 'td' 29 | END 'tr' 30 | end 31 | BEGIN 'tr' 32 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 33 | TEXT 'Zip' 34 | END 'td' 35 | BEGIN 'td' {style="padding: 3px 3px 3px 3px"} 36 | TEXT(model.zip) 37 | END 'td' 38 | END 'tr' 39 | END 'table' 40 | END 'div' 41 | END 'body' 42 | END 'html' 43 | 44 | -------------------------------------------------------------------------------- /sample/webapps/myapp/web.lua: -------------------------------------------------------------------------------- 1 | luaw_webapp = { 2 | resourcePattern = "handler%-.*%.lua", 3 | viewPattern = "view%-.*%.lua", 4 | } 5 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for building Luaw 2 | # == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT ======================= 3 | 4 | CC?= gcc 5 | CFLAGS?= -O2 -g -Wall 6 | CFLAGS+= $(SYSCFLAGS) $(MYCFLAGS) -I../$(UVDIR)/include -I../$(LUADIR)/src 7 | LDFLAGS= $(SYSLDFLAGS) $(MYLDFLAGS) 8 | LIBS= ../$(UVLIB) -lpthread ../$(LUALIB) -lm $(SYSLIBS) $(MYLIBS) 9 | 10 | # == END OF USER SETTINGS -- NO NEED TO CHANGE ANYTHING BELOW THIS LINE ======= 11 | 12 | # Build artifacts 13 | LUAW_OBJS= http_parser.o lua_lpack.o luaw_common.o luaw_logging.o luaw_http_parser.o luaw_server.o luaw_tcp.o luaw_timer.o lfs.o 14 | LUAW_BIN= luaw_server 15 | LUAW_CONF= server.cfg 16 | LUAW_SCRIPTS= luapack.lua luaw_init.lua luaw_logging.lua luaw_data_structs_lib.lua luaw_utils.lua \ 17 | luaw_scheduler.lua luaw_webapp.lua luaw_timer.lua luaw_tcp.lua luaw_http.lua luaw_constants.lua 18 | 19 | # How to install. If your install program does not support "-p", then 20 | # you may have to run ranlib on the installed liblua.a. 21 | INSTALL= install 22 | INSTALL_EXEC= $(INSTALL) -p -m 0755 23 | INSTALL_DATA= $(INSTALL) -p -m 0644 24 | 25 | # 26 | # If you don't have "install" you can use "cp" instead. 27 | # INSTALL= cp -p 28 | # INSTALL_EXEC= $(INSTALL) 29 | # INSTALL_DATA= $(INSTALL) 30 | 31 | # Other utilities. 32 | MKDIR= mkdir -p 33 | RM= rm -f 34 | RMDIR= rm -rf 35 | 36 | #where to install 37 | INSTALL_BIN=$(INSTALL_ROOT)/bin 38 | INSTALL_LIB=$(INSTALL_ROOT)/lib 39 | INSTALL_CONF=$(INSTALL_ROOT)/conf 40 | INSTALL_LOGS=$(INSTALL_ROOT)/logs 41 | INSTALL_WEBAPP=$(INSTALL_ROOT)/webapps 42 | 43 | # Targets start here. 44 | 45 | all: $(LUAW_BIN) 46 | 47 | $(LUAW_BIN): $(LUAW_OBJS) 48 | $(CC) -o $@ $(LDFLAGS) $(LUAW_OBJS) $(LIBS) 49 | 50 | install: check_install_root 51 | $(MKDIR) $(INSTALL_BIN) 52 | $(MKDIR) $(INSTALL_LIB) 53 | $(MKDIR) $(INSTALL_CONF) 54 | $(MKDIR) $(INSTALL_LOGS) 55 | $(MKDIR) $(INSTALL_WEBAPP) 56 | $(INSTALL_EXEC) $(LUAW_BIN) $(INSTALL_BIN) 57 | cd ../lib && $(INSTALL_DATA) $(LUAW_SCRIPTS) $(INSTALL_BIN) 58 | cd ../conf && $(INSTALL_DATA) $(LUAW_CONF) $(INSTALL_CONF) 59 | 60 | install-sample: install 61 | cd ../sample && cp -r * $(INSTALL_ROOT) 62 | 63 | uninstall: check_install_root 64 | $(RMDIR) $(INSTALL_ROOT)/* 65 | 66 | check_install_root: 67 | ifndef INSTALL_ROOT 68 | $(error INSTALL_ROOT is undefined) 69 | endif 70 | 71 | clean: 72 | $(RM) $(LUAW_BIN) $(LUAW_OBJS) 73 | 74 | echo: 75 | @echo "CC= $(CC)" 76 | @echo "CFLAGS= $(CFLAGS)" 77 | @echo "LDFLAGS= $(SYSLDFLAGS)" 78 | @echo "LIBS= $(LIBS)" 79 | @echo "RM= $(RM)" 80 | 81 | # list targets that do not create files (but not all makes understand .PHONY) 82 | .PHONY: default install install-sample uninstall check_install_root clean echo 83 | 84 | # Luaw object files 85 | http_parser.o: http_parser.c http_parser.h 86 | lua_lpack.o: lua_lpack.c lua_lpack.h luaw_common.h 87 | luaw_logging.o: luaw_logging.c luaw_logging.h luaw_common.h 88 | luaw_common.o: luaw_common.c luaw_common.h luaw_tcp.h luaw_http_parser.h luaw_timer.h lua_lpack.h 89 | luaw_http_parser.o: luaw_http_parser.c luaw_http_parser.h luaw_common.h luaw_tcp.h lfs.h 90 | luaw_server.o: luaw_server.c luaw_common.h luaw_tcp.h luaw_logging.h 91 | luaw_tcp.o: luaw_tcp.c luaw_tcp.h luaw_common.h http_parser.h luaw_http_parser.h luaw_tcp.h 92 | luaw_timer.o: luaw_timer.c luaw_timer.h luaw_common.h 93 | lfs.o: lfs.c lfs.h 94 | 95 | -------------------------------------------------------------------------------- /src/http_parser.h: -------------------------------------------------------------------------------- 1 | /* Copyright Joyent, Inc. and other Node contributors. All rights reserved. 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to 5 | * deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | * sell copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | * IN THE SOFTWARE. 20 | */ 21 | #ifndef http_parser_h 22 | #define http_parser_h 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | /* Also update SONAME in the Makefile whenever you change these. */ 28 | #define HTTP_PARSER_VERSION_MAJOR 2 29 | #define HTTP_PARSER_VERSION_MINOR 2 30 | #define HTTP_PARSER_VERSION_PATCH 0 31 | 32 | #include 33 | #if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600) 34 | #include 35 | #include 36 | typedef __int8 int8_t; 37 | typedef unsigned __int8 uint8_t; 38 | typedef __int16 int16_t; 39 | typedef unsigned __int16 uint16_t; 40 | typedef __int32 int32_t; 41 | typedef unsigned __int32 uint32_t; 42 | typedef __int64 int64_t; 43 | typedef unsigned __int64 uint64_t; 44 | #else 45 | #include 46 | #endif 47 | 48 | /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run 49 | * faster 50 | */ 51 | #ifndef HTTP_PARSER_STRICT 52 | # define HTTP_PARSER_STRICT 1 53 | #endif 54 | 55 | /* Maximium header size allowed */ 56 | #define HTTP_MAX_HEADER_SIZE (80*1024) 57 | 58 | 59 | typedef struct http_parser http_parser; 60 | typedef struct http_parser_settings http_parser_settings; 61 | 62 | 63 | /* Callbacks should return non-zero to indicate an error. The parser will 64 | * then halt execution. 65 | * 66 | * The one exception is on_headers_complete. In a HTTP_RESPONSE parser 67 | * returning '1' from on_headers_complete will tell the parser that it 68 | * should not expect a body. This is used when receiving a response to a 69 | * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: 70 | * chunked' headers that indicate the presence of a body. 71 | * 72 | * http_data_cb does not return data chunks. It will be call arbitrarally 73 | * many times for each string. E.G. you might get 10 callbacks for "on_url" 74 | * each providing just a few characters more data. 75 | */ 76 | typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); 77 | typedef int (*http_cb) (http_parser*); 78 | 79 | 80 | /* Request Methods */ 81 | #define HTTP_METHOD_MAP(XX) \ 82 | XX(0, DELETE, DELETE) \ 83 | XX(1, GET, GET) \ 84 | XX(2, HEAD, HEAD) \ 85 | XX(3, POST, POST) \ 86 | XX(4, PUT, PUT) \ 87 | /* pathological */ \ 88 | XX(5, CONNECT, CONNECT) \ 89 | XX(6, OPTIONS, OPTIONS) \ 90 | XX(7, TRACE, TRACE) \ 91 | /* webdav */ \ 92 | XX(8, COPY, COPY) \ 93 | XX(9, LOCK, LOCK) \ 94 | XX(10, MKCOL, MKCOL) \ 95 | XX(11, MOVE, MOVE) \ 96 | XX(12, PROPFIND, PROPFIND) \ 97 | XX(13, PROPPATCH, PROPPATCH) \ 98 | XX(14, SEARCH, SEARCH) \ 99 | XX(15, UNLOCK, UNLOCK) \ 100 | /* subversion */ \ 101 | XX(16, REPORT, REPORT) \ 102 | XX(17, MKACTIVITY, MKACTIVITY) \ 103 | XX(18, CHECKOUT, CHECKOUT) \ 104 | XX(19, MERGE, MERGE) \ 105 | /* upnp */ \ 106 | XX(20, MSEARCH, M-SEARCH) \ 107 | XX(21, NOTIFY, NOTIFY) \ 108 | XX(22, SUBSCRIBE, SUBSCRIBE) \ 109 | XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \ 110 | /* RFC-5789 */ \ 111 | XX(24, PATCH, PATCH) \ 112 | XX(25, PURGE, PURGE) \ 113 | 114 | enum http_method 115 | { 116 | #define XX(num, name, string) HTTP_##name = num, 117 | HTTP_METHOD_MAP(XX) 118 | #undef XX 119 | }; 120 | 121 | 122 | enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; 123 | 124 | 125 | /* Flag values for http_parser.flags field */ 126 | enum flags 127 | { F_CHUNKED = 1 << 0 128 | , F_CONNECTION_KEEP_ALIVE = 1 << 1 129 | , F_CONNECTION_CLOSE = 1 << 2 130 | , F_TRAILING = 1 << 3 131 | , F_UPGRADE = 1 << 4 132 | , F_SKIPBODY = 1 << 5 133 | }; 134 | 135 | 136 | /* Map for errno-related constants 137 | * 138 | * The provided argument should be a macro that takes 2 arguments. 139 | */ 140 | #define HTTP_ERRNO_MAP(XX) \ 141 | /* No error */ \ 142 | XX(OK, "success") \ 143 | \ 144 | /* Callback-related errors */ \ 145 | XX(CB_message_begin, "the on_message_begin callback failed") \ 146 | XX(CB_url, "the on_url callback failed") \ 147 | XX(CB_header_field, "the on_header_field callback failed") \ 148 | XX(CB_header_value, "the on_header_value callback failed") \ 149 | XX(CB_headers_complete, "the on_headers_complete callback failed") \ 150 | XX(CB_body, "the on_body callback failed") \ 151 | XX(CB_message_complete, "the on_message_complete callback failed") \ 152 | XX(CB_status, "the on_status callback failed") \ 153 | \ 154 | /* Parsing-related errors */ \ 155 | XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ 156 | XX(HEADER_OVERFLOW, \ 157 | "too many header bytes seen; overflow detected") \ 158 | XX(CLOSED_CONNECTION, \ 159 | "data received after completed connection: close message") \ 160 | XX(INVALID_VERSION, "invalid HTTP version") \ 161 | XX(INVALID_STATUS, "invalid HTTP status code") \ 162 | XX(INVALID_METHOD, "invalid HTTP method") \ 163 | XX(INVALID_URL, "invalid URL") \ 164 | XX(INVALID_HOST, "invalid host") \ 165 | XX(INVALID_PORT, "invalid port") \ 166 | XX(INVALID_PATH, "invalid path") \ 167 | XX(INVALID_QUERY_STRING, "invalid query string") \ 168 | XX(INVALID_FRAGMENT, "invalid fragment") \ 169 | XX(LF_EXPECTED, "LF character expected") \ 170 | XX(INVALID_HEADER_TOKEN, "invalid character in header") \ 171 | XX(INVALID_CONTENT_LENGTH, \ 172 | "invalid character in content-length header") \ 173 | XX(INVALID_CHUNK_SIZE, \ 174 | "invalid character in chunk size header") \ 175 | XX(INVALID_CONSTANT, "invalid constant string") \ 176 | XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ 177 | XX(STRICT, "strict mode assertion failed") \ 178 | XX(PAUSED, "parser is paused") \ 179 | XX(UNKNOWN, "an unknown error occurred") 180 | 181 | 182 | /* Define HPE_* values for each errno value above */ 183 | #define HTTP_ERRNO_GEN(n, s) HPE_##n, 184 | enum http_errno { 185 | HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) 186 | }; 187 | #undef HTTP_ERRNO_GEN 188 | 189 | 190 | /* Get an http_errno value from an http_parser */ 191 | #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) 192 | 193 | 194 | struct http_parser { 195 | /** PRIVATE **/ 196 | unsigned int type : 2; /* enum http_parser_type */ 197 | unsigned int flags : 6; /* F_* values from 'flags' enum; semi-public */ 198 | unsigned int state : 8; /* enum state from http_parser.c */ 199 | unsigned int header_state : 8; /* enum header_state from http_parser.c */ 200 | unsigned int index : 8; /* index into current matcher */ 201 | 202 | uint32_t nread; /* # bytes read in various scenarios */ 203 | uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ 204 | 205 | /** READ-ONLY **/ 206 | unsigned short http_major; 207 | unsigned short http_minor; 208 | unsigned int status_code : 16; /* responses only */ 209 | unsigned int method : 8; /* requests only */ 210 | unsigned int http_errno : 7; 211 | 212 | /* 1 = Upgrade header was present and the parser has exited because of that. 213 | * 0 = No upgrade header present. 214 | * Should be checked when http_parser_execute() returns in addition to 215 | * error checking. 216 | */ 217 | unsigned int upgrade : 1; 218 | 219 | /** PUBLIC **/ 220 | void *data; /* A pointer to get hook to the "connection" or "socket" object */ 221 | }; 222 | 223 | 224 | struct http_parser_settings { 225 | http_cb on_message_begin; 226 | http_data_cb on_url; 227 | http_data_cb on_status; 228 | http_data_cb on_header_field; 229 | http_data_cb on_header_value; 230 | http_cb on_headers_complete; 231 | http_data_cb on_body; 232 | http_cb on_message_complete; 233 | }; 234 | 235 | 236 | enum http_parser_url_fields 237 | { UF_SCHEMA = 0 238 | , UF_HOST = 1 239 | , UF_PORT = 2 240 | , UF_PATH = 3 241 | , UF_QUERY = 4 242 | , UF_FRAGMENT = 5 243 | , UF_USERINFO = 6 244 | , UF_MAX = 7 245 | }; 246 | 247 | 248 | /* Result structure for http_parser_parse_url(). 249 | * 250 | * Callers should index into field_data[] with UF_* values iff field_set 251 | * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and 252 | * because we probably have padding left over), we convert any port to 253 | * a uint16_t. 254 | */ 255 | struct http_parser_url { 256 | uint16_t field_set; /* Bitmask of (1 << UF_*) values */ 257 | uint16_t port; /* Converted UF_PORT string */ 258 | 259 | struct { 260 | uint16_t off; /* Offset into buffer in which field starts */ 261 | uint16_t len; /* Length of run in buffer */ 262 | } field_data[UF_MAX]; 263 | }; 264 | 265 | 266 | /* Returns the library version. Bits 16-23 contain the major version number, 267 | * bits 8-15 the minor version number and bits 0-7 the patch level. 268 | * Usage example: 269 | * 270 | * unsigned long version = http_parser_version(); 271 | * unsigned major = (version >> 16) & 255; 272 | * unsigned minor = (version >> 8) & 255; 273 | * unsigned patch = version & 255; 274 | * printf("http_parser v%u.%u.%u\n", major, minor, version); 275 | */ 276 | unsigned long http_parser_version(void); 277 | 278 | void http_parser_init(http_parser *parser, enum http_parser_type type); 279 | 280 | 281 | size_t http_parser_execute(http_parser *parser, 282 | const http_parser_settings *settings, 283 | const char *data, 284 | size_t len); 285 | 286 | 287 | /* If http_should_keep_alive() in the on_headers_complete or 288 | * on_message_complete callback returns 0, then this should be 289 | * the last message on the connection. 290 | * If you are the server, respond with the "Connection: close" header. 291 | * If you are the client, close the connection. 292 | */ 293 | int http_should_keep_alive(const http_parser *parser); 294 | 295 | /* Returns a string version of the HTTP method. */ 296 | const char *http_method_str(enum http_method m); 297 | 298 | /* Return a string name of the given error */ 299 | const char *http_errno_name(enum http_errno err); 300 | 301 | /* Return a string description of the given error */ 302 | const char *http_errno_description(enum http_errno err); 303 | 304 | /* Parse a URL; return nonzero on failure */ 305 | int http_parser_parse_url(const char *buf, size_t buflen, 306 | int is_connect, 307 | struct http_parser_url *u); 308 | 309 | /* Pause or un-pause the parser; a nonzero value pauses */ 310 | void http_parser_pause(http_parser *parser, int paused); 311 | 312 | /* Checks if this is the final chunk of the body. */ 313 | int http_body_is_final(const http_parser *parser); 314 | 315 | #ifdef __cplusplus 316 | } 317 | #endif 318 | #endif 319 | -------------------------------------------------------------------------------- /src/lfs.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** LuaFileSystem 3 | ** Copyright Kepler Project 2003 (http://www.keplerproject.org/luafilesystem) 4 | ** 5 | ** $Id: lfs.h,v 1.5 2008/02/19 20:08:23 mascarenhas Exp $ 6 | */ 7 | 8 | /* Define 'chdir' for systems that do not implement it */ 9 | #ifdef NO_CHDIR 10 | #define chdir(p) (-1) 11 | #define chdir_error "Function 'chdir' not provided by system" 12 | #else 13 | #define chdir_error strerror(errno) 14 | #endif 15 | 16 | 17 | extern int luaopen_lfs (lua_State *L); -------------------------------------------------------------------------------- /src/lua_lpack.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 raksoras 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #define LUA_LPACK_META_TABLE "_luaw_lpack_MT_" 24 | 25 | static const int32_t _i = 1; 26 | 27 | #define is_bigendian() ((*(char *)&_i) == 0) 28 | 29 | typedef enum { 30 | /* meta type for all marker tags, should never itself occur in read stream */ 31 | TYPE_MARKER = 0, 32 | 33 | /* single byte marker tags */ 34 | MAP_START, 35 | ARRAY_START, 36 | DICT_START, 37 | RECORD_END, 38 | 39 | /* single byte value markers */ 40 | NIL, 41 | BOOL_TRUE, 42 | BOOL_FALSE, 43 | 44 | /* fix width types */ 45 | UINT_8, 46 | DICT_ENTRY, 47 | UINT_16, 48 | BIG_DICT_ENTRY, 49 | UINT_32, 50 | INT_8, 51 | INT_16, 52 | INT_32, 53 | INT_64, 54 | FLOAT, 55 | DOUBLE, 56 | 57 | /* variable length types */ 58 | STRING, 59 | BIG_STRING, 60 | HUGE_STRING 61 | } 62 | type_tag; 63 | 64 | extern void luaw_init_lpack_lib (lua_State *L); 65 | -------------------------------------------------------------------------------- /src/luaw_common.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 raksoras 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | 32 | #include "uv.h" 33 | #include "luaw_common.h" 34 | #include "luaw_logging.h" 35 | #include "luaw_tcp.h" 36 | #include "luaw_http_parser.h" 37 | #include "luaw_timer.h" 38 | #include "lua_lpack.h" 39 | 40 | /* globals */ 41 | lua_State* l_global = NULL; //main global Lua state that spawns all other coroutines 42 | int resume_thread_fn_ref; //Resume thread lua function 43 | 44 | 45 | void resume_lua_thread(lua_State* L, int nargs, int nresults, int errHandler) { 46 | int tid = lua_tointeger(L, (0 - nargs)); 47 | if (tid) { 48 | int rc = lua_pcall(L, nargs, nresults, errHandler); 49 | if (rc != 0) { 50 | fprintf(stderr, "******** Error resuming Lua thread# %d: %s (%d) *********\n", tid, lua_tostring(L, -1), rc); 51 | } 52 | } 53 | } 54 | 55 | /* sets up error code and error message on the Lua stack to be returned by the original C 56 | function called from Lua. */ 57 | int error_to_lua(lua_State* L, const char* fmt, ...) { 58 | lua_settop(L, 0); //remove success status and params table from stack 59 | lua_pushboolean(L, 0); //set status to false in case of error 60 | va_list argp; 61 | va_start(argp, fmt); 62 | lua_pushvfstring(L, fmt, argp); 63 | va_end(argp); 64 | return 2; //two values status and err_mesg pushed onto stack. 65 | } 66 | 67 | int raise_lua_error (lua_State *L, const char *fmt, ...) { 68 | va_list argp; 69 | va_start(argp, fmt); 70 | lua_pushvfstring(L, fmt, argp); 71 | va_end(argp); 72 | return lua_error(L); 73 | } 74 | 75 | void close_if_active(uv_handle_t* handle, uv_close_cb close_cb) { 76 | if ((handle != NULL)&&(!uv_is_closing(handle))) { 77 | uv_close(handle, close_cb); 78 | } 79 | } 80 | 81 | /* Minimal LuaJIT compatibility layer adopted from https://github.com/keplerproject/lua-compat-5.2 */ 82 | #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM == 501 83 | 84 | void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { 85 | luaL_checkstack(L, nup+1, "too many upvalues"); 86 | for (; l->name != NULL; l++) { /* fill the table with given functions */ 87 | int i; 88 | lua_pushstring(L, l->name); 89 | for (i = 0; i < nup; i++) /* copy upvalues to the top */ 90 | lua_pushvalue(L, -(nup + 1)); 91 | lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ 92 | lua_settable(L, -(nup + 3)); /* table must be below the upvalues, the name and the closure */ 93 | } 94 | lua_pop(L, nup); /* remove upvalues */ 95 | } 96 | 97 | 98 | void luaL_setmetatable (lua_State *L, const char *tname) { 99 | luaL_checkstack(L, 1, "not enough stack slots"); 100 | luaL_getmetatable(L, tname); 101 | lua_setmetatable(L, -2); 102 | } 103 | 104 | #endif 105 | 106 | void make_metatable(lua_State *L, const char* mt_name, const luaL_Reg* mt_funcs) { 107 | luaL_newmetatable(L, mt_name); 108 | luaL_setfuncs(L, mt_funcs, 0); 109 | /* client metatable.__index = client metatable */ 110 | lua_pushstring(L, "__index"); 111 | lua_pushvalue(L,-2); 112 | lua_rawset(L, -3); 113 | } 114 | 115 | LUA_LIB_METHOD void luaw_init_libs (lua_State *L) { 116 | luaw_init_logging_lib(L); 117 | luaw_init_tcp_lib(L); 118 | luaw_init_http_lib(L); 119 | luaw_init_timer_lib(L); 120 | luaw_init_lpack_lib(L); 121 | } 122 | 123 | /********************************************************************* 124 | * This file contains parts of Lua 5.2's source code: 125 | * 126 | * Copyright (C) 1994-2013 Lua.org, PUC-Rio. 127 | * 128 | * Permission is hereby granted, free of charge, to any person obtaining 129 | * a copy of this software and associated documentation files (the 130 | * "Software"), to deal in the Software without restriction, including 131 | * without limitation the rights to use, copy, modify, merge, publish, 132 | * distribute, sublicense, and/or sell copies of the Software, and to 133 | * permit persons to whom the Software is furnished to do so, subject to 134 | * the following conditions: 135 | * 136 | * The above copyright notice and this permission notice shall be 137 | * included in all copies or substantial portions of the Software. 138 | * 139 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 140 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 141 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 142 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 143 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 144 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 145 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 146 | *********************************************************************/ 147 | -------------------------------------------------------------------------------- /src/luaw_common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 raksoras 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef LUAW_COMMON_H 24 | 25 | #define LUAW_COMMON_H 26 | 27 | typedef enum { 28 | false = 0, 29 | true 30 | } 31 | bool; 32 | 33 | /* marker macros for documenting code */ 34 | #define LIBUV_CALLBACK 35 | #define LIBUV_API 36 | #define LUA_OBJ_METHOD 37 | #define LUA_LIB_METHOD 38 | #define HTTP_PARSER_CALLBACK 39 | 40 | #define Q(x) #x 41 | #define QUOTE(x) Q(x) 42 | 43 | #define INCR_REF_COUNT(s) if (s != NULL) s->ref_count++; 44 | 45 | #define DECR_REF_COUNT(s) if (s != NULL) s->ref_count--; 46 | 47 | #define GC_REF(s) \ 48 | if (s != NULL) { \ 49 | s->ref_count--; \ 50 | if (s->ref_count <= 0) free(s); \ 51 | } 52 | 53 | 54 | #define step fprintf(stdout, "At line# %d function(%s) in file %s\n", __LINE__, __FUNCTION__, __FILE__); 55 | #define debug_i(s) fprintf(stdout, #s "= %d at line# %d function(%s) in file %s\n", s, __LINE__, __FUNCTION__, __FILE__); 56 | #define debug_l(s) fprintf(stdout, #s "= %ld at line# %d function(%s) in file %s\n", s, __LINE__, __FUNCTION__, __FILE__); 57 | #define debug_s(s) fprintf(stdout, #s "= %s at line# %d function(%s) in file %s\n", s, __LINE__, __FUNCTION__, __FILE__); 58 | #define debug_sl(s, l) fprintf(stdout, #s "= %.*s at line# %d function(%s) in file %s\n", l, s, __LINE__, __FUNCTION__, __FILE__); 59 | #define debug_p(s) fprintf(stdout, #s "= %p at line# %d function(%s) in file %s\n", s, __LINE__, __FUNCTION__, __FILE__); 60 | #define debug_break(s) fprintf(stdout, "%s\n", s); 61 | 62 | /* global state */ 63 | extern lua_State* l_global; 64 | extern int resume_thread_fn_ref; 65 | 66 | extern int error_to_lua(lua_State* L, const char* fmt, ...); 67 | extern int raise_lua_error (lua_State *L, const char *fmt, ...); 68 | extern void make_metatable(lua_State *L, const char* mt_name, const luaL_Reg* mt_funcs); 69 | extern void luaw_init_libs(lua_State *L); 70 | extern void close_if_active(uv_handle_t* handle, uv_close_cb close_cb); 71 | extern void resume_lua_thread(lua_State* L, int nargs, int nresults, int errHandler); 72 | 73 | 74 | 75 | /* Minimal LuaJIT compatibility layer adopted from https://github.com/keplerproject/lua-compat-5.2 */ 76 | 77 | #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM == 501 78 | /* Lua 5.1 */ 79 | 80 | /* PUC-Rio Lua uses lconfig_h as include guard for luaconf.h, 81 | * LuaJIT uses luaconf_h. If you use PUC-Rio's include files 82 | * but LuaJIT's library, you will need to define the macro 83 | * COMPAT52_IS_LUAJIT yourself! */ 84 | #if !defined(COMPAT52_IS_LUAJIT) && defined(luaconf_h) 85 | #define COMPAT52_IS_LUAJIT 86 | #endif 87 | 88 | /* LuaJIT doesn't define these unofficial macros ... */ 89 | #if !defined(LUAI_INT32) 90 | #include 91 | #if INT_MAX-20 < 32760 92 | #define LUAI_INT32 long 93 | #define LUAI_UINT32 unsigned long 94 | #elif INT_MAX > 2147483640L 95 | #define LUAI_INT32 int 96 | #define LUAI_UINT32 unsigned int 97 | #else 98 | #error "could not detect suitable lua_Unsigned datatype" 99 | #endif 100 | #endif 101 | 102 | #define lua_rawlen(L, i) lua_objlen(L, i) 103 | 104 | #endif /* Lua 5.1 */ 105 | 106 | #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM == 501 107 | /* Lua 5.0 *or* 5.1 */ 108 | 109 | #define LUA_OK 0 110 | 111 | #define lua_pushglobaltable(L) \ 112 | lua_pushvalue(L, LUA_GLOBALSINDEX) 113 | 114 | #define luaL_newlib(L, l) \ 115 | (lua_newtable((L)),luaL_setfuncs((L), (l), 0)) 116 | 117 | void luaL_checkversion (lua_State *L); 118 | 119 | #endif /* Lua 5.0 *or* 5.1 */ 120 | 121 | void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup); 122 | void luaL_setmetatable (lua_State *L, const char *tname); 123 | 124 | 125 | #endif 126 | -------------------------------------------------------------------------------- /src/luaw_http_parser.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 raksoras 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | #include "uv.h" 31 | #include "luaw_common.h" 32 | #include "luaw_http_parser.h" 33 | #include "luaw_tcp.h" 34 | 35 | typedef enum { 36 | PARSE_HTTP_PARSER_IDX = 1, 37 | PARSE_HTTP_BUFF_IDX, 38 | PARSE_HTTP_REQ_IDX, 39 | PARSE_HTTP_LENGTH_IDX, 40 | PARSE_HTTP_CONN_IDX 41 | } 42 | parse_http_lua_stack_index; 43 | 44 | static int decode_hex_str(const char* str, int len) { 45 | char *read_ptr = (char *)str; 46 | char *write_ptr = (char *)str; 47 | int hex_ch; 48 | char ch; 49 | 50 | while (len) { 51 | ch = *read_ptr; 52 | if ((ch == '%')&&(len > 2)) { 53 | sscanf((read_ptr+1),"%2x", &hex_ch); 54 | ch = (char)hex_ch; 55 | read_ptr += 3; len -= 3; 56 | } 57 | else { 58 | read_ptr++; len--; 59 | } 60 | 61 | *write_ptr = ch; 62 | write_ptr++; 63 | } 64 | return ((const unsigned char*)write_ptr - (const unsigned char*)str); 65 | } 66 | 67 | static int handle_name_value_pair(lua_State* L, const char* name, int name_len, bool hex_name, const char* value, int value_len, bool hex_value) { 68 | if ((name != NULL)&&(name_len > 0)&&(value != NULL)&&(value_len > 0)) { 69 | if (hex_name) { 70 | name_len = decode_hex_str(name, name_len); 71 | } 72 | if (hex_value) { 73 | value_len = decode_hex_str(value, value_len); 74 | } 75 | 76 | lua_getfield(L, 1, "storeHttpParam"); 77 | if (lua_isfunction(L, -1) != 1) { 78 | raise_lua_error(L, "Missing lua function storeHttpParam()"); 79 | } 80 | lua_pushvalue(L, 3); 81 | lua_pushlstring(L, name, name_len); 82 | lua_pushlstring(L, value, value_len) ; 83 | lua_call(L, 3, 0); 84 | } 85 | else if((name_len == 0)&&(value_len > 0)) { 86 | error_to_lua(L, "400 Bad URL encoding: empty parameter name, non empty parameter value: %s ", value); 87 | return false; 88 | } 89 | return true; //param name without value is ok, e.g. &foo= 90 | } 91 | 92 | /* Lua call spec: 93 | success: params table, nil = http_lib:url_decode(url encoded string, params) 94 | success: status(false), error message = http_lib:url_decode(url encoded string, params) 95 | */ 96 | LUA_LIB_METHOD static int luaw_url_decode(lua_State *L) { 97 | if (!lua_istable(L, 1)) { 98 | return raise_lua_error(L, "Luaw HTTP lib table is missing"); 99 | } 100 | size_t length = 0; 101 | const char* data = lua_tolstring(L, 2, &length); 102 | char *read_ptr = (char *)data; 103 | char *name = NULL; 104 | char *value = NULL; 105 | bool hex_name = false, hex_value = false; 106 | int name_len = 0, value_len = 0; 107 | decoder_state ds = starting_name; 108 | 109 | while (length--) { 110 | char ch = *read_ptr; 111 | 112 | switch(ds) { 113 | case starting_name: 114 | if (!handle_name_value_pair(L, name, name_len, hex_name, value, value_len, hex_value)) return 2; //err_code, err_mesg 115 | name_len = 0; 116 | hex_name = false; 117 | value_len = 0; 118 | hex_value = false; 119 | 120 | switch(ch) { 121 | case '&': 122 | case '=': 123 | return error_to_lua(L, "400 Bad URL encoding: Error while expecting start of param name at: %s\n", read_ptr); 124 | case '%': 125 | hex_name = true; 126 | } 127 | ds = in_name; 128 | name = read_ptr; 129 | name_len = 1; 130 | break; 131 | 132 | case in_name: 133 | switch(ch) { 134 | case '&': 135 | return error_to_lua(L, "400 Bad URL encoding: Error while parsing param name at: %s\n", read_ptr); 136 | case '=': 137 | ds = starting_value; 138 | break; 139 | case '%': 140 | hex_name = true; 141 | default: 142 | name_len++; 143 | } 144 | break; 145 | 146 | case starting_value: 147 | switch(ch) { 148 | case '&': 149 | case '=': 150 | return error_to_lua(L, "400 Bad URL encoding: Error while expecting start of param value at: %s\n", read_ptr); 151 | case '%': 152 | hex_value = true; 153 | } 154 | ds = in_value; 155 | value = read_ptr; 156 | value_len = 1; 157 | break; 158 | 159 | case in_value: 160 | switch(ch) { 161 | case '&': 162 | ds = starting_name; 163 | break; 164 | case '=': 165 | return error_to_lua(L, "400 Bad URL encoding: Error while parsing param value at: %s\n", read_ptr); 166 | case '%': 167 | hex_value = true; 168 | default: 169 | value_len++; 170 | } 171 | break; 172 | 173 | } 174 | if (ch == '+') *read_ptr =' '; 175 | read_ptr++; 176 | } 177 | return handle_name_value_pair(L, name, name_len, hex_name, value, value_len, hex_value) ? 1 : 2; 178 | } 179 | 180 | static int new_lhttp_parser(lua_State *L, enum http_parser_type parser_type) { 181 | luaw_http_parser_t* lhttp_parser = lua_newuserdata(L, sizeof(luaw_http_parser_t)); 182 | if (lhttp_parser == NULL) { 183 | return raise_lua_error(L, "Failed to allocate memory for new http_parser."); 184 | } 185 | luaL_setmetatable(L, LUA_HTTP_PARSER_META_TABLE); 186 | http_parser_init(&lhttp_parser->parser, parser_type); 187 | lhttp_parser->parser.data = lhttp_parser; 188 | return 1; 189 | } 190 | 191 | LUA_LIB_METHOD static int luaw_new_http_request_parser(lua_State* L) { 192 | return new_lhttp_parser(L, HTTP_REQUEST); 193 | } 194 | 195 | LUA_LIB_METHOD static int luaw_new_http_response_parser(lua_State* L) { 196 | return new_lhttp_parser(L, HTTP_RESPONSE); 197 | } 198 | 199 | LUA_LIB_METHOD static int luaw_init_http_parser(lua_State* L) { 200 | luaw_http_parser_t* lhttp_parser = luaL_checkudata(L, 1, LUA_HTTP_PARSER_META_TABLE); 201 | http_parser* parser = &lhttp_parser->parser; 202 | http_parser_init(parser, parser->type); 203 | return 0; 204 | } 205 | 206 | static int handle_http_callback(http_parser *parser, http_parser_cb_type cb, const char* start, size_t len) { 207 | luaw_http_parser_t* lhttp_parser = (luaw_http_parser_t*) parser->data; 208 | lhttp_parser->http_cb = cb; 209 | lhttp_parser->start = (char*)start; 210 | lhttp_parser->len = len; 211 | http_parser_pause(parser, 1); 212 | return 0; 213 | } 214 | 215 | HTTP_PARSER_CALLBACK static int http_parser_on_message_begin(http_parser *parser) { 216 | return handle_http_callback(parser, http_cb_on_message_begin, NULL, 0); 217 | } 218 | 219 | HTTP_PARSER_CALLBACK static int http_parser_on_url(http_parser *parser, const char* start, size_t len) { 220 | return handle_http_callback(parser, http_cb_on_url, start, len); 221 | } 222 | 223 | HTTP_PARSER_CALLBACK static int http_parser_on_status(http_parser *parser, const char* start, size_t len) { 224 | return handle_http_callback(parser, http_cb_on_status, start, len); 225 | } 226 | 227 | HTTP_PARSER_CALLBACK static int http_parser_on_header_name(http_parser *parser, const char* start, size_t len) { 228 | return handle_http_callback(parser, http_cb_on_header_field, start, len); 229 | } 230 | 231 | HTTP_PARSER_CALLBACK static int http_parser_on_header_value(http_parser *parser, const char* start, size_t len) { 232 | return handle_http_callback(parser, http_cb_on_header_value, start, len); 233 | } 234 | 235 | HTTP_PARSER_CALLBACK static int http_parser_on_headers_complete(http_parser *parser) { 236 | return handle_http_callback(parser, http_cb_on_headers_complete, NULL, 0); 237 | } 238 | 239 | HTTP_PARSER_CALLBACK static int http_parser_on_body(http_parser *parser, const char* start, size_t len) { 240 | return handle_http_callback(parser, http_cb_on_body, start, len); 241 | } 242 | 243 | HTTP_PARSER_CALLBACK static int http_parser_on_message_complete(http_parser *parser) { 244 | return handle_http_callback(parser, http_cb_on_mesg_complete, NULL, 0); 245 | } 246 | 247 | static const http_parser_settings parser_settings = { 248 | .on_message_begin = http_parser_on_message_begin, 249 | .on_status = http_parser_on_status, 250 | .on_url = http_parser_on_url, 251 | .on_header_field = http_parser_on_header_name, 252 | .on_header_value = http_parser_on_header_value, 253 | .on_headers_complete = http_parser_on_headers_complete, 254 | .on_body = http_parser_on_body, 255 | .on_message_complete = http_parser_on_message_complete 256 | }; 257 | 258 | /* Lua call spec: 259 | * All failures: 260 | * false, error message = parser:parseHttp(str, offset) 261 | * 262 | * Successes: 263 | * 264 | * http_parser_on_headers_complete: 265 | * http_cb_type, new offset, http_should_keep_alive, major_version, minor_version, http method, http status code = parseHttp(conn) 266 | * 267 | * http_parser_on_message_complete: 268 | * http_cb_type, new offset, http_should_keep_alive = parser:parseHttp(str, offset) 269 | * 270 | * All other callbacks: 271 | * http_cb_type, new offset, parsed value = parser:parseHttp(conn, str, offset) 272 | * 273 | */ 274 | static int parse_http(lua_State *L) { 275 | lua_settop(L, 3); 276 | 277 | luaw_http_parser_t* lhttp_parser = luaL_checkudata(L, 1, LUA_HTTP_PARSER_META_TABLE); 278 | http_parser* parser = &lhttp_parser->parser; 279 | 280 | size_t len = 0; 281 | const char* buff = lua_tolstring(L, 2, &len); 282 | 283 | int offset = lua_tointeger(L, 3); 284 | if (offset > len) { 285 | lua_pushboolean(L, 0); 286 | lua_pushfstring(L, "Wrong offset into HTTP string: len=%d, offset=%d, content=%s\n", len, offset, buff); 287 | return 2; 288 | } 289 | 290 | /* every http_parser_execute() does not necessarily cause callback to be invoked, we need to know if it 291 | did call the callback */ 292 | lhttp_parser->http_cb = http_cb_none; 293 | const int nparsed = http_parser_execute(parser, &parser_settings, (buff+offset), (len-offset)); 294 | offset += nparsed; 295 | const int remaining = len - offset; 296 | 297 | if ((remaining > 0)&&(parser->http_errno != HPE_PAUSED)) { 298 | lua_pushboolean(L, 0); 299 | lua_pushfstring(L, "Error parsing HTTP fragment: errorCode=%d, total=%d, parsed=%d, content=%s\n", parser->http_errno, len, nparsed, buff); 300 | return 2; 301 | } 302 | 303 | lua_pushinteger(L, lhttp_parser->http_cb); 304 | lua_pushinteger(L, offset); 305 | int nresults = 3; 306 | 307 | switch(lhttp_parser->http_cb) { 308 | case http_cb_on_headers_complete: 309 | lua_pushboolean(L, http_should_keep_alive(parser)); 310 | lua_pushinteger(L, parser->http_major); 311 | lua_pushinteger(L, parser->http_minor); 312 | if (parser->type == HTTP_REQUEST) { 313 | lua_pushstring(L, http_methods[parser->method]); 314 | } else { 315 | lua_pushnil(L); 316 | } 317 | if (parser->type == HTTP_RESPONSE) { 318 | lua_pushinteger(L, parser->status_code); 319 | } else { 320 | lua_pushnil(L); 321 | } 322 | nresults = 7; 323 | break; 324 | 325 | case http_cb_none: 326 | case http_cb_on_mesg_complete: 327 | lua_pushboolean(L, http_should_keep_alive(parser)); 328 | break; 329 | 330 | default: 331 | lua_pushlstring(L, lhttp_parser->start, lhttp_parser->len); 332 | } 333 | 334 | /* un-pause parser */ 335 | http_parser_pause(parser, 0); 336 | return nresults; 337 | } 338 | 339 | 340 | const char* url_field_names[] = { "schema", "host", "port", "path", "queryString", "fragment", "userInfo" }; 341 | 342 | static void push_url_part(lua_State *L, const char* buff, struct http_parser_url* parsed_url, enum http_parser_url_fields url_field) { 343 | if ((parsed_url->field_set) & (1 << url_field)) { 344 | lua_pushlstring(L, (buff + parsed_url->field_data[url_field].off), parsed_url->field_data[url_field].len); 345 | lua_setfield(L, -2, url_field_names[url_field]); 346 | } 347 | } 348 | 349 | /* Lua call spec: 350 | * Success: url parts table = http_lib.parseURL(url_string, is_connect) 351 | * Failure: nil = http_lib.parseURL(url_string, is_connect) 352 | */ 353 | LUA_LIB_METHOD static int luaw_parse_url(lua_State *L) { 354 | size_t len = 0; 355 | const char* buff = luaL_checklstring(L, 1, &len); 356 | int is_connect = lua_toboolean(L, 2); 357 | 358 | struct http_parser_url* parsed_url = (struct http_parser_url*) malloc(sizeof(struct http_parser_url)); 359 | if (parsed_url == NULL) { 360 | return raise_lua_error(L, "Could not allocate memory for URL struct"); 361 | } 362 | 363 | int result = http_parser_parse_url(buff, len, is_connect, parsed_url); 364 | if (result) { 365 | lua_pushnil(L); 366 | return 1; 367 | } 368 | 369 | lua_createtable(L, 0 , 7); 370 | push_url_part(L, buff, parsed_url, UF_SCHEMA); 371 | push_url_part(L, buff, parsed_url, UF_HOST); 372 | push_url_part(L, buff, parsed_url, UF_PORT); 373 | push_url_part(L, buff, parsed_url, UF_PATH); 374 | push_url_part(L, buff, parsed_url, UF_QUERY); 375 | push_url_part(L, buff, parsed_url, UF_FRAGMENT); 376 | push_url_part(L, buff, parsed_url, UF_USERINFO); 377 | 378 | free(parsed_url); 379 | return 1; 380 | } 381 | 382 | static const struct luaL_Reg luaw_http_lib[] = { 383 | {"urlDecode", luaw_url_decode}, 384 | {"newHttpRequestParser", luaw_new_http_request_parser}, 385 | {"newHttpResponseParser", luaw_new_http_response_parser}, 386 | {"parseURL", luaw_parse_url}, 387 | {NULL, NULL} /* sentinel */ 388 | }; 389 | 390 | static const struct luaL_Reg http_parser_methods[] = { 391 | {"parseHttp", parse_http}, 392 | {"initHttpParser", luaw_init_http_parser}, 393 | {NULL, NULL} /* sentinel */ 394 | }; 395 | 396 | void luaw_init_http_lib (lua_State *L) { 397 | make_metatable(L, LUA_HTTP_PARSER_META_TABLE, http_parser_methods); 398 | luaL_newlib(L, luaw_http_lib); 399 | lua_setglobal(L, "luaw_http_lib"); 400 | } 401 | -------------------------------------------------------------------------------- /src/luaw_http_parser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 raksoras 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include "http_parser.h" 24 | #include "uv.h" 25 | 26 | #ifndef LUAW_HTTP_PARSER_H 27 | #define LUAW_HTTP_PARSER_H 28 | 29 | #define LUA_HTTP_PARSER_META_TABLE "__luaw_HTTP_parser_MT__" 30 | 31 | typedef enum { 32 | starting_name = 0, 33 | in_name, 34 | starting_value, 35 | in_value 36 | } 37 | decoder_state; 38 | 39 | typedef enum { 40 | http_cb_none = 1, //Used to denote invocations of http_parser_execute() that don't cause callback 41 | http_cb_on_message_begin, 42 | http_cb_on_status, 43 | http_cb_on_url, 44 | http_cb_on_header_field, 45 | http_cb_on_header_value, 46 | http_cb_on_headers_complete, 47 | http_cb_on_body, 48 | http_cb_on_mesg_complete 49 | } http_parser_cb_type; 50 | 51 | /* imported from http_parser.h */ 52 | static const char *http_methods[] = 53 | { 54 | #define XX(num, name, string) #string, 55 | HTTP_METHOD_MAP(XX) 56 | #undef XX 57 | }; 58 | 59 | 60 | typedef struct { 61 | http_parser parser; 62 | http_parser_cb_type http_cb; 63 | char* start; 64 | size_t len; 65 | } 66 | luaw_http_parser_t; 67 | 68 | extern void luaw_init_http_lib(lua_State *L); 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /src/luaw_logging.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 raksoras 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | 32 | #include "uv.h" 33 | #include "luaw_common.h" 34 | #include "luaw_logging.h" 35 | 36 | static char hostname[512] = {'\0'}; 37 | static logfile_sate log_state = LOG_NOT_OPEN; 38 | static uv_file logfile; 39 | static struct addrinfo *log_server_addr = NULL; 40 | static int log_sock_fd = -1; 41 | 42 | 43 | static const char* get_hostname() { 44 | if (hostname[0] == '\0') { 45 | int rc = gethostname(hostname, 511); 46 | if (rc < 0) { 47 | strcpy(hostname, "localhost"); 48 | } 49 | } 50 | return hostname; 51 | } 52 | 53 | LUA_LIB_METHOD static int get_hostname_lua(lua_State *L) { 54 | lua_pushstring(L, get_hostname()); 55 | return 1; 56 | } 57 | 58 | LUA_LIB_METHOD static int get_logging_state(lua_State* l_thread) { 59 | lua_pushinteger(l_thread, log_state); 60 | return 1; 61 | } 62 | 63 | LIBUV_CALLBACK static void on_log_open(uv_fs_t* req) { 64 | logfile = req->result; 65 | log_state = LOG_IS_OPEN; 66 | uv_fs_req_cleanup(req); 67 | free(req); 68 | } 69 | 70 | LUA_LIB_METHOD static int open_log_file(lua_State* l_thread) { 71 | if (log_state == LOG_NOT_OPEN) { 72 | const char* filename = lua_tostring(l_thread, 1); 73 | if (filename) { 74 | uv_fs_t* open_req = (uv_fs_t*)malloc(sizeof(uv_fs_t)); 75 | if (open_req) { 76 | uv_loop_t* loop = uv_default_loop(); 77 | int rc = uv_fs_open(loop, open_req, filename, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP, on_log_open); 78 | if (rc == 0) { 79 | log_state = OPENING_LOG; 80 | } 81 | } 82 | } 83 | } 84 | return 0; 85 | } 86 | 87 | LIBUV_CALLBACK static void on_log_close(uv_fs_t* req) { 88 | uv_fs_req_cleanup(req); 89 | free(req); 90 | } 91 | 92 | LIBUV_CALLBACK static void close_log(uv_fs_t* req) { 93 | uv_file f = *((int*)req->data); 94 | uv_fs_req_cleanup(req); 95 | uv_fs_close(uv_default_loop(), req, f, on_log_close); 96 | } 97 | 98 | LIBUV_CALLBACK static void on_log_write(uv_fs_t* req) { 99 | if (req->result >= 0) { 100 | uv_fs_req_cleanup(req); 101 | free(req); 102 | } else { /* error */ 103 | log_state = LOG_NOT_OPEN; 104 | close_log(req); 105 | } 106 | } 107 | 108 | LUA_LIB_METHOD static int write_log(lua_State* l_thread) { 109 | if (log_state == LOG_IS_OPEN) { 110 | size_t len = 0; 111 | const char* str = lua_tolstring(l_thread, 1, &len); 112 | 113 | if ((str != NULL)||(len > 0)) { 114 | char* log_mesg = malloc(len * sizeof(char)); 115 | if (log_mesg != NULL) { 116 | memcpy(log_mesg, str, len); 117 | uv_fs_t* write_req = (uv_fs_t*)malloc(sizeof(uv_fs_t)); 118 | 119 | if (write_req) { 120 | write_req->data = &logfile; 121 | uv_buf_t buff = uv_buf_init(log_mesg, len); 122 | uv_loop_t* loop = uv_default_loop(); 123 | 124 | int rotate_log = lua_toboolean(l_thread, 2); 125 | if (rotate_log == 0) { 126 | int rc = uv_fs_write(loop, write_req, logfile, &buff, 1, -1, on_log_write); 127 | if (rc != 0) { 128 | log_state = LOG_NOT_OPEN; 129 | close_log(write_req); 130 | } 131 | } else { 132 | log_state = LOG_NOT_OPEN; 133 | uv_fs_write(loop, write_req, logfile, &buff, 1, -1, close_log); 134 | } 135 | } else { 136 | free(log_mesg); 137 | } 138 | } 139 | } 140 | } 141 | 142 | lua_pushinteger(l_thread, log_state); 143 | return 1; 144 | } 145 | 146 | void close_syslog() { 147 | uv_freeaddrinfo(log_server_addr); 148 | } 149 | 150 | LUA_LIB_METHOD static int connect_to_syslog(lua_State *L) { 151 | int rc = -1; 152 | int flags = 1; 153 | const char* log_server_ip = lua_tostring(L, 1); 154 | const char* log_server_port = lua_tostring(L, 2); 155 | 156 | if (log_server_ip && log_server_port) { 157 | struct addrinfo hints; 158 | 159 | memset(&hints, 0, sizeof(struct addrinfo)); 160 | hints.ai_family = AF_INET; 161 | hints.ai_socktype = SOCK_DGRAM; 162 | hints.ai_flags = 0; 163 | hints.ai_protocol = 0; 164 | 165 | rc = getaddrinfo(log_server_ip, log_server_port, &hints, &log_server_addr); 166 | if (rc != 0) { 167 | fprintf(stderr,"failed to get address for sys log server :%s\n",gai_strerror(rc)); 168 | } else { 169 | rc = socket(log_server_addr->ai_family, log_server_addr->ai_socktype, log_server_addr->ai_protocol); 170 | if (rc < 0) { 171 | fprintf(stderr, "could not connect to sys log server\n"); 172 | } else { 173 | log_sock_fd = rc; 174 | #if defined(O_NONBLOCK) 175 | if (-1 == (flags = fcntl(log_sock_fd, F_GETFL, 0))) flags = 0; 176 | rc = fcntl(log_sock_fd, F_SETFL, flags | O_NONBLOCK); 177 | #else 178 | rc = ioctl(log_sock_fd, FIONBIO, &flags); 179 | #endif 180 | if (rc < 0) { 181 | fprintf(stderr, "Error putting syslog connection in non blocking mode\n"); 182 | } 183 | } 184 | } 185 | } 186 | 187 | lua_pushboolean(L, (rc < 0) ? 0 : 1); 188 | return 1; 189 | } 190 | 191 | LUA_LIB_METHOD static int send_to_syslog(lua_State *L) { 192 | if (log_sock_fd > 0) { 193 | size_t len = 0; 194 | const char* mesg = lua_tolstring(L, 1, &len); 195 | if ((mesg)&&(len > 0)) { 196 | sendto(log_sock_fd, mesg, len, MSG_DONTWAIT,log_server_addr->ai_addr, log_server_addr->ai_addrlen); 197 | } 198 | } 199 | return 0; 200 | } 201 | 202 | 203 | 204 | static const struct luaL_Reg luaw_logging_lib[] = { 205 | {"logState", get_logging_state}, 206 | {"openLog", open_log_file}, 207 | {"writeLog", write_log}, 208 | {"syslogConnect", connect_to_syslog}, 209 | {"syslogSend", send_to_syslog}, 210 | {"hostname", get_hostname_lua}, 211 | {NULL, NULL} /* sentinel */ 212 | }; 213 | 214 | int luaw_init_logging_lib (lua_State *L) { 215 | luaL_newlib(L, luaw_logging_lib); 216 | lua_setglobal(L, "luaw_logging_lib"); 217 | return 1; 218 | } 219 | -------------------------------------------------------------------------------- /src/luaw_logging.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 raksoras 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef LUAW_LOGGING_H 24 | 25 | #define LUAW_LOGGING_H 26 | 27 | typedef enum { 28 | LOG_NOT_OPEN = 0, 29 | OPENING_LOG, 30 | LOG_IS_OPEN, 31 | } logfile_sate; 32 | 33 | extern void close_syslog(); 34 | extern int luaw_init_logging_lib (lua_State *L); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/luaw_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 raksoras 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | #include "uv.h" 38 | #include "luaw_common.h" 39 | #include "luaw_logging.h" 40 | #include "luaw_tcp.h" 41 | #include "lfs.h" 42 | 43 | static char* server_ip = "0.0.0.0"; 44 | static int server_port = 80; 45 | static uv_tcp_t server; 46 | static uv_loop_t* event_loop; 47 | static uv_signal_t shutdown_signal; 48 | 49 | static int service_http_fn_ref; 50 | static int start_thread_fn_ref; 51 | static int run_ready_threads_fn_ref; 52 | 53 | #define LUA_LOAD_FILE_BUFF_SIZE 1024 54 | 55 | typedef struct { 56 | FILE *file; /* file being read */ 57 | char buff[LUA_LOAD_FILE_BUFF_SIZE]; /* area for reading file */ 58 | char* epilogue; 59 | } lua_load_buffer_t; 60 | 61 | uv_prepare_t user_thread_runner; 62 | 63 | static const char* lua_file_reader(lua_State* L, void* data, size_t* size) { 64 | lua_load_buffer_t *lb = (lua_load_buffer_t*)data; 65 | 66 | if (lb->file == NULL) return NULL; 67 | 68 | if (feof(lb->file)) { 69 | fclose(lb->file); 70 | lb->file = NULL; 71 | *size = (lb->epilogue != NULL) ? strlen(lb->epilogue) : 0; 72 | return lb->epilogue; 73 | } 74 | 75 | *size = fread(lb->buff, 1, sizeof(lb->buff), lb->file); /* read block */ 76 | return lb->buff; 77 | } 78 | 79 | static void handle_shutdown_req(uv_signal_t* handle, int signum) { 80 | if (signum == SIGHUP) { 81 | fprintf(stderr, "shutdown request received\n"); 82 | uv_signal_stop(handle); 83 | uv_stop(event_loop); 84 | } 85 | } 86 | 87 | void init_luaw_server(lua_State* L) { 88 | lua_getglobal(L, "luaw_http_lib"); 89 | if (!lua_istable(L, -1)) { 90 | fprintf(stderr, "Luaw HTTP library not initialized\n"); 91 | exit(EXIT_FAILURE); 92 | } 93 | 94 | lua_getfield(L, -1, "request_handler"); 95 | if (!lua_isfunction(L, -1)) { 96 | fprintf(stderr, "Main HTTP request handler function (Luaw.request_handler) not set\n"); 97 | exit(EXIT_FAILURE); 98 | } 99 | service_http_fn_ref = luaL_ref(L, LUA_REGISTRYINDEX); 100 | lua_pop(L, 1); 101 | 102 | lua_getglobal(L, "luaw_scheduler"); 103 | if (!lua_istable(L, -1)) { 104 | fprintf(stderr, "Luaw scheduler not initialized\n"); 105 | exit(EXIT_FAILURE); 106 | } 107 | 108 | lua_getfield(L, -1, "resumeThreadId"); 109 | if (lua_isfunction(L, -1)) { 110 | resume_thread_fn_ref = luaL_ref(L, LUA_REGISTRYINDEX); 111 | } else { 112 | fprintf(stderr, "resumeThreadId function not found in luaw scheduler\n"); 113 | exit(EXIT_FAILURE); 114 | } 115 | 116 | lua_getfield(L, -1, "startSystemThread"); 117 | if (lua_isfunction(L, -1)) { 118 | start_thread_fn_ref = luaL_ref(L, LUA_REGISTRYINDEX); 119 | } else { 120 | fprintf(stderr, "startSystemThread function not found in luaw scheduler\n"); 121 | exit(EXIT_FAILURE); 122 | } 123 | 124 | lua_getfield(L, -1, "runReadyThreads"); 125 | if (lua_isfunction(L, -1)) { 126 | run_ready_threads_fn_ref = luaL_ref(L, LUA_REGISTRYINDEX); 127 | } else { 128 | fprintf(stderr, "runReadyThreads function not found in luaw scheduler\n"); 129 | exit(EXIT_FAILURE); 130 | } 131 | lua_pop(L, 1); 132 | 133 | lua_getglobal(L, "luaw_server_config"); 134 | if (lua_istable(L, -1)) { 135 | lua_getfield(L, -1, "server_ip"); 136 | if (lua_isstring(L, -1)) { 137 | server_ip = (char *)lua_tostring(L, -1); 138 | lua_pop(L, 1); 139 | } 140 | 141 | lua_getfield(L, -1, "server_port"); 142 | if (lua_isnumber(L, -1)) { 143 | server_port = lua_tointeger(L, -1); 144 | lua_pop(L, 1); 145 | } 146 | 147 | lua_pop(L, 1); //pop luaw_server_config object 148 | } 149 | 150 | lua_pushnumber(L, CONN_BUFFER_SIZE); 151 | lua_setglobal(L, "CONN_BUFFER_SIZE"); 152 | 153 | event_loop = uv_default_loop(); 154 | } 155 | 156 | /* create a new lua coroutine to service this conn, anchor it in "all active coroutines" 157 | * global table to prevent it from being garbage collected and start the coroutine 158 | */ 159 | LIBUV_CALLBACK static void on_server_connect(uv_stream_t* server, int status) { 160 | if (status) { 161 | raise_lua_error(l_global, "Error in on_server_connect callback: %s\n", uv_strerror(status)); 162 | return; 163 | } 164 | 165 | lua_rawgeti(l_global, LUA_REGISTRYINDEX, start_thread_fn_ref); 166 | assert(lua_isfunction(l_global, -1)); 167 | 168 | lua_rawgeti(l_global, LUA_REGISTRYINDEX, service_http_fn_ref); 169 | assert(lua_isfunction(l_global, -1)); 170 | 171 | connection_t * conn = new_connection(l_global); 172 | status = uv_accept(server, (uv_stream_t*)&conn->handle); 173 | if (status) { 174 | close_connection(conn, status); 175 | fprintf(stderr, "Error accepting incoming conn: %s\n", uv_strerror(status)); 176 | return; 177 | } 178 | 179 | status = lua_pcall(l_global, 2, 2, 0); 180 | if (status) { 181 | fprintf(stderr, "**** Error starting new client connect thread: %s (%d) ****\n", lua_tostring(l_global, -1), status); 182 | } 183 | } 184 | 185 | void start_server(lua_State *L) { 186 | fprintf(stderr, "starting server on port %d ...\n", server_port); 187 | 188 | struct sockaddr_in addr; 189 | int err_code = uv_ip4_addr(server_ip, server_port, &addr); 190 | if (err_code) { 191 | fprintf(stderr, "Error initializing socket address: %s\n", uv_strerror(err_code)); 192 | exit(EXIT_FAILURE); 193 | } 194 | 195 | uv_tcp_init(event_loop, &server); 196 | 197 | err_code = uv_tcp_bind(&server, (const struct sockaddr*) &addr, 0); 198 | if (err_code) { 199 | fprintf(stderr, "Error binding to port %d : %s\n", server_port, uv_strerror(err_code)); 200 | exit(EXIT_FAILURE); 201 | } 202 | 203 | err_code = uv_listen((uv_stream_t*)&server, 128, on_server_connect); 204 | if (err_code) { 205 | fprintf(stderr, "Error listening on port %d : %s\n", server_port, uv_strerror(err_code)); 206 | exit(EXIT_FAILURE); 207 | } 208 | 209 | uv_signal_init(event_loop, &shutdown_signal); 210 | uv_signal_start(&shutdown_signal, handle_shutdown_req, SIGHUP); 211 | } 212 | 213 | static void run_user_threads(uv_prepare_t* handle) { 214 | /* do the bottom half processing, run ready user threads that are waiting */ 215 | lua_rawgeti(l_global, LUA_REGISTRYINDEX, run_ready_threads_fn_ref); 216 | int status = lua_pcall(l_global, 0, 1, 0); 217 | 218 | if (status != LUA_OK) { 219 | fprintf(stderr,"Error while running user threads for bottom half processing: %s\n", lua_tostring(l_global, -1)); 220 | uv_stop(event_loop); 221 | } 222 | 223 | lua_settop(l_global, 0); 224 | } 225 | 226 | static void close_walk_cb(uv_handle_t* handle, void* arg) { 227 | if (!uv_is_closing(handle)) { 228 | uv_close(handle, NULL); 229 | } 230 | } 231 | 232 | static int server_loop(lua_State *L) { 233 | uv_prepare_init(event_loop, &user_thread_runner); 234 | uv_prepare_start(&user_thread_runner, run_user_threads); 235 | 236 | int status = uv_run(event_loop, UV_RUN_DEFAULT); 237 | 238 | /* clean up resources used by the event loop and Lua */ 239 | uv_walk(event_loop, close_walk_cb, NULL); 240 | uv_run(event_loop, UV_RUN_ONCE); 241 | uv_loop_delete(event_loop); 242 | lua_close(L); 243 | close_syslog(); 244 | 245 | return status; 246 | } 247 | 248 | static void run_lua_file(const char* filename, char* epilogue) { 249 | lua_load_buffer_t lb; 250 | 251 | lb.file = fopen(filename, "r"); 252 | if (lb.file == NULL) { 253 | fprintf(stderr, "Could not open file %s for reading\n", filename); 254 | exit(EXIT_FAILURE); 255 | } 256 | lb.epilogue = epilogue; 257 | 258 | #ifdef COMPAT52_IS_LUAJIT 259 | int status = lua_load(l_global, lua_file_reader, &lb, filename); 260 | #else 261 | int status = lua_load(l_global, lua_file_reader, &lb, filename, "t"); 262 | #endif 263 | 264 | if (status != LUA_OK) { 265 | fprintf(stderr, "Error while loading file: %s\n", filename); 266 | fprintf(stderr, "%s\n", lua_tostring(l_global, -1)); 267 | exit(EXIT_FAILURE); 268 | } 269 | 270 | status = lua_pcall(l_global, 0, 0, 0); 271 | if (status != LUA_OK) { 272 | fprintf(stderr, "Error while executing file: %s\n", filename); 273 | fprintf(stderr, "%s\n", lua_tostring(l_global, -1)); 274 | fprintf(stderr, "\n\t* Try running luaw_server from directory that contains \"bin\" directory containing the binary \"luaw_server\"\n"); 275 | fprintf(stderr, "\t* For example: ./bin/luaw_server \n\n"); 276 | exit(EXIT_FAILURE); 277 | } 278 | } 279 | 280 | static void set_lua_path(lua_State* L) { 281 | lua_getglobal( L, "package" ); 282 | lua_pushliteral(L, "?;?.lua;./bin/?;./bin/?.lua;./lib/?;./lib/?.lua"); 283 | lua_setfield( L, -2, "path" ); 284 | lua_pop(L, 1); 285 | } 286 | 287 | int main (int argc, char* argv[]) { 288 | if (argc < 2) { 289 | fprintf(stderr, "Usage: %s \n", argv[0]); 290 | exit(EXIT_FAILURE); 291 | } 292 | 293 | l_global = luaL_newstate(); 294 | if (!l_global) { 295 | fprintf(stderr, "Could not create new Lua state\n"); 296 | exit(EXIT_FAILURE); 297 | } 298 | 299 | lua_gc(l_global, LUA_GCSTOP, 0); /* stop collector during initialization */ 300 | luaL_openlibs(l_global); /* open libraries */ 301 | luaw_init_libs(l_global); 302 | luaopen_lfs(l_global); 303 | lua_gc(l_global, LUA_GCRESTART, 0); 304 | 305 | /* load config file, mandatory */ 306 | set_lua_path(l_global); 307 | run_lua_file(argv[1], "\ninit = require(\"luaw_init\")\n"); 308 | 309 | /* run other lua on startup script passed on the command line, if any */ 310 | int i = 2; 311 | for (; i < argc; i++) { 312 | fprintf(stderr, "## Running %s \n", argv[i]); 313 | run_lua_file(argv[i], NULL); 314 | } 315 | 316 | init_luaw_server(l_global); 317 | start_server(l_global); 318 | int status = server_loop(l_global); 319 | 320 | exit(status); 321 | } 322 | -------------------------------------------------------------------------------- /src/luaw_tcp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 raksoras 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef LUAW_TCP_H 24 | 25 | #define LUAW_TCP_H 26 | 27 | #define LUA_CONNECTION_META_TABLE "_luaw_connection_MT_" 28 | #define CONN_BUFFER_SIZE 4096 29 | 30 | typedef struct connection_s connection_t; 31 | 32 | /* client connection's state: socket connection, coroutines servicing the connection 33 | and read/write buffers for the connection */ 34 | struct connection_s { 35 | uv_tcp_t handle; /* connected socket */ 36 | 37 | /* read section */ 38 | int lua_reader_tid; /* ID of the reading coroutine */ 39 | size_t read_len; /* read length */ 40 | uv_timer_t read_timer; /* for read timeout */ 41 | 42 | /* write section */ 43 | int lua_writer_tid; /* ID of the writing coroutine */ 44 | uv_timer_t write_timer; /* for write/connect timeout */ 45 | uv_write_t write_req; /* write request */ 46 | 47 | /* memory management */ 48 | int ref_count; /* reference count */ 49 | connection_t** lua_ref; /* back reference to Lua's full userdata pointing to this conn */ 50 | 51 | /* read buffer */ 52 | char read_buffer[CONN_BUFFER_SIZE]; /* buffer to read into */ 53 | }; 54 | 55 | #define MAX_CONNECTION_BUFF_SIZE 65536 //16^4 56 | 57 | #define TO_CONN(h) (connection_t*)h->data 58 | 59 | #define GET_CONN_OR_RETURN(h) \ 60 | (connection_t*)h->data; \ 61 | if(!h->data) return 62 | 63 | #define LUA_GET_CONN_OR_RETURN(L, i, c) \ 64 | connection_t** cr = luaL_checkudata(L, i, LUA_CONNECTION_META_TABLE); \ 65 | if (cr == NULL) return 0; \ 66 | connection_t* c = *cr; \ 67 | if (c == NULL) return 0; 68 | 69 | #define LUA_GET_CONN_OR_ERROR(L, i, c) \ 70 | connection_t** cr = luaL_checkudata(L, i, LUA_CONNECTION_META_TABLE); \ 71 | if (cr == NULL) return error_to_lua(L, "Connection missing"); \ 72 | connection_t* c = *cr; \ 73 | if (c == NULL) return error_to_lua(L, "Connection closed"); 74 | 75 | #define TO_TIMER(h) (luaw_timer_t*)h->data 76 | 77 | 78 | /* TCP lib methods to be exported */ 79 | extern connection_t* new_connection(lua_State* L); 80 | extern void close_connection(connection_t* conn, const int status); 81 | extern void luaw_init_tcp_lib (lua_State *L); 82 | 83 | #endif 84 | -------------------------------------------------------------------------------- /src/luaw_timer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 raksoras 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | 36 | #include "uv.h" 37 | #include "luaw_common.h" 38 | #include "luaw_timer.h" 39 | 40 | 41 | static void clear_user_timer(luaw_timer_t* timer) { 42 | timer->state = INIT; 43 | timer->lua_tid = 0; 44 | } 45 | 46 | static void free_user_timer(uv_handle_t* handle) { 47 | luaw_timer_t* timer = GET_TIMER_OR_RETURN(handle); 48 | handle->data = NULL; 49 | GC_REF(timer) 50 | } 51 | 52 | LUA_OBJ_METHOD static void close_timer(luaw_timer_t* timer) { 53 | /* timer->lua_ref == NULL also acts as a flag to mark that this timer has been closed */ 54 | if ((timer == NULL)||(timer->lua_ref == NULL)) return; 55 | 56 | *(timer->lua_ref) = NULL; //delink from Lua's userdata 57 | timer->lua_ref = NULL; 58 | DECR_REF_COUNT(timer); 59 | 60 | /* unblock waiting thread */ 61 | if (timer->lua_tid) { 62 | lua_rawgeti(l_global, LUA_REGISTRYINDEX, resume_thread_fn_ref); 63 | lua_pushinteger(l_global, timer->lua_tid); 64 | lua_pushboolean(l_global, 0); //status 65 | lua_pushstring(l_global, uv_strerror(UV_ECANCELED)); //error message 66 | timer->lua_tid = 0; 67 | resume_lua_thread(l_global, 3, 2, 0); 68 | } 69 | 70 | close_if_active((uv_handle_t*)&timer->handle, free_user_timer); 71 | } 72 | 73 | LUA_OBJ_METHOD static int close_timer_lua(lua_State* l_thread) { 74 | LUA_GET_TIMER_OR_RETURN(l_thread, 1, timer); 75 | close_timer(timer); 76 | return 0; 77 | } 78 | 79 | LUA_OBJ_METHOD static int timer_gc(lua_State *L) { 80 | LUA_GET_TIMER_OR_RETURN(L, 1, timer); 81 | close_timer(timer); 82 | return 0; 83 | } 84 | 85 | LUA_LIB_METHOD static int new_user_timer(lua_State* l_thread) { 86 | luaw_timer_t* timer = (luaw_timer_t*)calloc(1, sizeof(luaw_timer_t)); 87 | if (timer == NULL) { 88 | return raise_lua_error(l_thread, "Could not allocate memory for user timer"); 89 | } 90 | 91 | luaw_timer_t** lua_ref = lua_newuserdata(l_thread, sizeof(luaw_timer_t*)); 92 | if (lua_ref == NULL) { 93 | free(timer); 94 | return raise_lua_error(l_thread, "Could not allocate memory for user timer Lua reference"); 95 | } 96 | 97 | /* link C side connection reference and Lua's full userdata that represents it to each other */ 98 | luaL_setmetatable(l_thread, LUA_USER_TIMER_META_TABLE); 99 | *lua_ref = timer; 100 | INCR_REF_COUNT(timer) 101 | timer->lua_ref = lua_ref; 102 | 103 | /* init libuv artifacts */ 104 | uv_timer_init(uv_default_loop(), &timer->handle); 105 | timer->handle.data = timer; 106 | INCR_REF_COUNT(timer) 107 | clear_user_timer(timer); 108 | 109 | return 1; 110 | } 111 | 112 | /* lua call spec: status, timer_elapsed = timer:wait(tid) 113 | Success, timer elapsed: status = true, timer_elapsed = true 114 | Success, timer not elapsed: status = true, timer_elapsed = false 115 | Failure: status = false, error message 116 | */ 117 | LIBUV_CALLBACK static void on_user_timer_timeout(uv_timer_t* handle) { 118 | luaw_timer_t* timer = GET_TIMER_OR_RETURN(handle); 119 | if(timer->lua_tid) { 120 | lua_rawgeti(l_global, LUA_REGISTRYINDEX, resume_thread_fn_ref); 121 | lua_pushinteger(l_global, timer->lua_tid); 122 | clear_user_timer(timer); 123 | lua_pushboolean(l_global, 1); //status 124 | lua_pushboolean(l_global, 1); //elapsed 125 | resume_lua_thread(l_global, 3, 2, 0); 126 | } else { 127 | timer->state = ELAPSED; 128 | } 129 | } 130 | 131 | /* lua call spec: 132 | Success: status(true), nil = timer:start(timeout) 133 | Failure: status(false), error message = timer:start(timeout) 134 | */ 135 | static int start_user_timer(lua_State* l_thread) { 136 | LUA_GET_TIMER_OR_ERROR(l_thread, 1, timer); 137 | 138 | if (timer->state != INIT) { 139 | /* Timer is already started by another lua thread */ 140 | return error_to_lua(l_thread, "Timer already in use by another thread"); 141 | } 142 | 143 | int timeout = lua_tointeger(l_thread, 2); 144 | if (timeout <= 0) { 145 | return error_to_lua(l_thread, "Invalid timeout value %d specified", timeout); 146 | } 147 | 148 | int rc = uv_timer_start(&timer->handle, on_user_timer_timeout, timeout, 0); 149 | if (rc) { 150 | close_timer(timer); 151 | return error_to_lua(l_thread, "Error starting timer: %s", uv_strerror(rc)); 152 | } 153 | 154 | timer->state = TICKING; 155 | lua_pushboolean(l_thread, 1); 156 | return 1; 157 | } 158 | 159 | 160 | /* lua call spec: status, timer_elapsed = timer:wait(tid) 161 | Success, timer elapsed: status = true, timer_elapsed = true 162 | Success, timer not elapsed: status = true, timer_elapsed = false 163 | Failure: status = false, error message 164 | */ 165 | LUA_OBJ_METHOD static int wait_user_timer(lua_State* l_thread) { 166 | LUA_GET_TIMER_OR_ERROR(l_thread, 1, timer); 167 | 168 | if (timer->state == ELAPSED) { 169 | clear_user_timer(timer); 170 | lua_pushboolean(l_thread, 1); //status 171 | lua_pushboolean(l_thread, 1); //elasped 172 | } else { 173 | if (timer->state != TICKING) { 174 | return error_to_lua(l_thread, "Attempt to wait on timer that is not started, timer state: %d", timer->state); 175 | } 176 | if (timer->lua_tid) { 177 | return error_to_lua(l_thread, "Timer already is in use by thread %d", timer->lua_tid); 178 | } 179 | int tid = lua_tointeger(l_thread, 2); 180 | if (tid <= 0) { 181 | return error_to_lua(l_thread, "Invalid thread id %d specified", tid); 182 | } 183 | timer->lua_tid = tid; 184 | lua_pushboolean(l_thread, 1); 185 | lua_pushboolean(l_thread, 0); 186 | } 187 | return 2; 188 | } 189 | 190 | /* lua call spec: timer:stop() */ 191 | LUA_OBJ_METHOD static int stop_user_timer(lua_State* l_thread) { 192 | LUA_GET_TIMER_OR_ERROR(l_thread, 1, timer); 193 | if (timer->state == TICKING) { 194 | if (timer->lua_tid) { 195 | lua_rawgeti(l_thread, LUA_REGISTRYINDEX, resume_thread_fn_ref); 196 | lua_pushinteger(l_thread, timer->lua_tid); 197 | lua_pushboolean(l_thread, 0); //status 198 | lua_pushstring(l_thread, uv_strerror(UV_ECANCELED)); //error message 199 | resume_lua_thread(l_thread, 3, 2, 0); 200 | } 201 | clear_user_timer(timer); 202 | uv_timer_stop(&timer->handle); 203 | } 204 | return 0; 205 | } 206 | 207 | 208 | static const struct luaL_Reg luaw_user_timer_methods[] = { 209 | {"start", start_user_timer}, 210 | {"stop", stop_user_timer}, 211 | {"wait", wait_user_timer}, 212 | {"delete", close_timer_lua}, 213 | {"__gc", timer_gc}, 214 | {NULL, NULL} /* sentinel */ 215 | }; 216 | 217 | static const struct luaL_Reg luaw_timer_lib[] = { 218 | {"newTimer", new_user_timer}, 219 | {NULL, NULL} /* sentinel */ 220 | }; 221 | 222 | 223 | void luaw_init_timer_lib (lua_State *L) { 224 | make_metatable(L, LUA_USER_TIMER_META_TABLE, luaw_user_timer_methods); 225 | luaL_newlib(L, luaw_timer_lib); 226 | lua_setglobal(L, "luaw_timer_lib"); 227 | } 228 | -------------------------------------------------------------------------------- /src/luaw_timer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 raksoras 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef LUAW_TIMER_H 24 | 25 | #define LUAW_TIMER_H 26 | 27 | #define LUA_USER_TIMER_META_TABLE "_luaw_user_timer_MT_" 28 | 29 | typedef enum { 30 | INIT = 0, 31 | TICKING, 32 | ELAPSED 33 | } 34 | timer_state; 35 | 36 | typedef struct luaw_timer_s luaw_timer_t; 37 | 38 | struct luaw_timer_s { 39 | uv_timer_t handle; /* timer handler */ 40 | timer_state state; /* timer state */ 41 | int lua_tid; /* id of a lua thread waiting on this timer */ 42 | 43 | /* memory management */ 44 | int ref_count; /* reference count */ 45 | luaw_timer_t** lua_ref; /* back reference to Lua's full userdata pointing to this conn */ 46 | }; 47 | 48 | 49 | #define TO_TIMER(h) (luaw_timer_t*)h->data 50 | 51 | #define GET_TIMER_OR_RETURN(h) \ 52 | (luaw_timer_t*)h->data; \ 53 | if(!h->data) return 54 | 55 | #define LUA_GET_TIMER_OR_RETURN(L, i, t) \ 56 | luaw_timer_t** tr = luaL_checkudata(L, i, LUA_USER_TIMER_META_TABLE); \ 57 | if (tr == NULL) return 0; \ 58 | luaw_timer_t* t = *tr; \ 59 | if (t == NULL) return 0; 60 | 61 | #define LUA_GET_TIMER_OR_ERROR(L, i, t) \ 62 | luaw_timer_t** tr = luaL_checkudata(L, i, LUA_USER_TIMER_META_TABLE); \ 63 | if (tr == NULL) return error_to_lua(L, "Timer missing"); \ 64 | luaw_timer_t* t = *tr; \ 65 | if (t == NULL) return error_to_lua(L, "Timer closed"); 66 | 67 | 68 | extern void luaw_init_timer_lib(lua_State *L); 69 | 70 | #endif --------------------------------------------------------------------------------