Showing preview only (369K chars total). Download the full file or copy to clipboard to get everything.
Repository: raksoras/luaw
Branch: master
Commit: 036601d6a76b
Files: 62
Total size: 349.9 KB
Directory structure:
gitextract_wtxp3faz/
├── .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
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
**/*.o
**/*.so
**/*.log
================================================
FILE: .gitmodules
================================================
[submodule "deps/libuv"]
path = deps/libuv
url = https://github.com/libuv/libuv.git
[submodule "deps/luajit-2.0"]
path = deps/luajit-2.0
url = http://luajit.org/git/luajit-2.0.git
[submodule "deps/lua-PUC-Rio"]
path = deps/lua-PUC-Rio
url = https://github.com/lua/lua.git
================================================
FILE: LICENSE
================================================
Copyright (c) 2015 raksoras
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
# Makefile for building Luaw
# == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT =======================
export UVDIR= deps/libuv
export UVLIB= deps/libuv/.libs/libuv.a
ifeq ($(LUAVM),luajit)
export LUADIR= deps/luajit-2.0
export LUALIB= deps/luajit-2.0/src/libluajit.a
export OSXLDFLAGS= "-Wl,-pagezero_size,10000 -Wl,-image_base,100000000"
else
export LUADIR= deps/lua-PUC-Rio
export LUALIB= deps/lua-PUC-Rio/src/liblua.a
export OSXLDFLAGS=
endif
# == END OF USER SETTINGS -- NO NEED TO CHANGE ANYTHING BELOW THIS LINE =======
# Supported platforms
PLATS= aix ansi bsd freebsd generic linux macosx mingw posix solaris
ALL= all
# Targets start here.
all:
@echo "Please do 'make PLATFORM' where PLATFORM is one of these:"
@echo " $(PLATS)"
$(UVLIB): $(UVDIR)/Makefile
$(MAKE) -C $(UVDIR)
$(UVDIR)/Makefile: $(UVDIR)/configure
cd $(UVDIR) && ./configure
$(UVDIR)/configure: $(UVDIR)/autogen.sh
cd $(UVDIR) && sh autogen.sh
$(LUADIR)/src/libluajit.a:
$(MAKE) -C $(LUADIR)
$(LUADIR)/src/liblua.a:
$(MAKE) -C $(LUADIR) $(TARGET)
luaw:
$(MAKE) -C src $(ALL) SYSLDFLAGS=$(SYSLDFLAGS) CC=$(CC)
# Convenience targets for popular platforms
aix: TARGET= aix
aix: $(UVLIB) $(LUALIB)
$(MAKE) -C src $(ALL) CC="xlc" CFLAGS="-O2" SYSLIBS="-ldl" SYSLDFLAGS="-brtl -bexpall"
ansi: TARGET= ansi
ansi: $(UVLIB) $(LUALIB)
$(MAKE) -C src $(ALL)
bsd: TARGET= bsd
bsd: $(UVLIB) $(LUALIB)
$(MAKE) -C src $(ALL) SYSLIBS="-Wl,-E"
freebsd: TARGET= freebsd
freebsd: $(UVLIB) $(LUALIB)
$(MAKE) -C src $(ALL) SYSLIBS="-Wl,-E"
linux: TARGET= linux
linux: SYSLIBS= -Wl,-E -ldl
linux: $(UVLIB) $(LUALIB)
$(MAKE) -C src $(ALL) SYSLIBS="-lrt -Wl,-E -ldl"
macosx: TARGET= macosx
macosx: $(UVLIB) $(LUALIB)
$(MAKE) -C src $(ALL) CC="cc" SYSLDFLAGS=$(OSXLDFLAGS)
posix: TARGET= posix
posix: $(UVLIB) $(LUALIB)
$(MAKE) -C src $(ALL)
solaris: TARGET= solaris
solaris: $(UVLIB) $(LUALIB)
$(MAKE) -C src $(ALL) SYSLIBS="-ldl"
#build objects management
install:
$(MAKE) -C src install
install-sample:
$(MAKE) -C src install-sample
uninstall:
$(MAKE) -C src uninstall
clean: $(UVDIR)/Makefile
$(MAKE) -C deps/luajit-2.0 clean
$(MAKE) -C deps/lua-PUC-Rio clean
$(MAKE) -C $(UVDIR) distclean
$(MAKE) -C src clean
# list targets that do not create files (but not all makes understand .PHONY)
.PHONY: all check_plat $(LUALIB) $(PLATS) luaw install uninstall clean $(LUADIR)/src/libluajit.a $(LUADIR)/src/liblua.a
================================================
FILE: README.md
================================================
Luaw - Lua meets Node.js
========================
***
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 :)
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.
##Features
1. Full HTTP 1.1 support with persistent/keep-alive connections and HTTP pipelining with configurable connect and read timeouts.
2. Two ways to read and parse incoming HTTP requests,
- Reading whole request in memory and then parsing it which is suitable for majority of applications.
- 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.
3. Similarly on the output side it supports,
- Buffering entire content in memory before writing it out to client with the correct "Content-Length" header, or
- Streaming response continuously as it is generated using HTTP 1.1 chunked encoding to keep server memory pressure minimum.
4. Fully asynchronous DNS and HTTP client for making remote HTTP calls from server
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.
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.
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.
8. Support for user timers for implementing periodic cron like jobs inside Luaw server.
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.
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.
11. Built in multipart file upload support.
##How To Build
***
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.
sudo apt-get install make
sudo apt-get install autotools-dev
sudo apt-get install autoconf
sudo apt-get install build-essential libtool
2. Clone Luaw repository
git clone --recursive https://github.com/raksoras/luaw.git
3. Build Luaw to use standard [PUC-Rio Lua VM](http://www.lua.org/)
cd luaw
make linux
or, build Luaw to use [LuaJit VM](http://luajit.org/)
cd luaw
make LUAVM=luajit linux
While building on mac OS use target "macosx". For example
cd luaw
make LUAVM=luajit macosx
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
make INSTALL_ROOT=~/luawsample install
To install binaries along with the sample app provided
make INSTALL_ROOT=~/luawsample install-sample
##Luaw directory structure
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`
```
~/luawsample
|
|
+--- bin Directory that holds Luaw server binary we built
| along with all necessary Lua libraries
|
+--- conf Directory for server configuration
| |
│ +--- server.cfg Sample server configuration file, to be used as a starting point
|
+--- lib Directory to install any third party or user supplied Lua
| libraries that application may depend on.
|
+--- logs Directory for server logs
|
+--- webapps Directory to install Luaw webapps
```
##License
Luaw uses [MIT license](http://opensource.org/licenses/mit-license.html) for all parts of Luaw that are not externally
maintained libraries like libuv.
## Documentation
Please refer to the [project wiki](https://github.com/raksoras/luaw/wiki) for documentation regarding how to build, configure, start and program using Luaw.
## Getting in touch
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
================================================
FILE: conf/server.cfg
================================================
luaw_server_config = {
server_ip = "0.0.0.0",
server_port = 7001,
connect_timeout = 4000,
read_timeout = 8000,
write_timeout = 8000
}
luaw_log_config = {
log_dir = "./logs",
log_file_basename = "luaw-log",
log_file_size_limit = 1024*1024,
log_file_count_limit = 9,
log_filename_timestamp_format = '%Y%m%d',
log_lines_buffer_count = 16,
syslog_server = "127.0.0.1",
syslog_port = 514,
}
luaw_webapp_config = {
base_dir = "./webapps"
}
================================================
FILE: docs/Contents.md
================================================
#Contents
1. Introduction
2. Getting started: building and installing Luaw
3. Luaw configuration
4. Your first Luaw webapp - "Hello world!"
5. Your second Luaw webapp - "Hello `<user name>`!"
6. Luaw template views
7. Your third webapp - with template views
8. Using Luaw logging framework
9. Using Luaw response object
10. Using Luaw async HTTP Client
11. Using Luaw threads and background jobs
12. Using Luaw timers
13. Using custom HTTP handler for HTTP stream parsing
14. Using custom scripts on command line at Luaw start up
================================================
FILE: docs/Introduction.md
================================================
#Introduction
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 :)
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.
##Features
1. Full HTTP 1.1 support with persistent/keep-alive connections and HTTP pipelining with configurable connect and read timeouts.
2. Two ways to read and parse incoming HTTP requests,
- Reading whole request in memory and then parsing it which is suitable for majority of applications.
- 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.
3. Similarly on the output side it supports,
- Buffering entire content in memory before writing it out to client with the correct "Content-Length" header.
- Streaming response continuously as it is generated using HTTP 1.1 chunked encoding to keep server memory pressure minimum.
4. Fully asynchronous DNS and HTTP client for making remote HTTP calls from server
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.
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.
7. Support for spawning user threads that can hook into Luaw's async IO machinery for calling multiple backend services from Luaw in parallel.
8. Support for user timers for implementing periodic cron like jobs inside Luaw server.
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.
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.
================================================
FILE: docs/cmd-line.md
================================================
#14. Advanced Topic II - Using custom scripts on command line at start up
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,
1. Loading any in memory caches required by application in Luaw's memory
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
3. Set up sandboxes in which to execute certain request or resource handlers.
================================================
FILE: docs/configuration.md
================================================
#3. Configuring Luaw
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.
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.
`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.
##Server configuration
Here is a sample server.cfg
```lua
luaw_server_config = {
server_ip = "0.0.0.0",
server_port = 7001,
connect_timeout = 4000,
read_timeout = 8000,
write_timeout = 8000
}
luaw_log_config = {
log_dir = "/apps/luaw/sample/log",
log_file_basename = "luaw-log",
log_file_size_limit = 1024*1024,
log_file_count_limit = 9,
log_filename_timestamp_format = '%Y%m%d',
log_lines_buffer_count = 16,
syslog_server = "127.0.0.1",
syslog_port = 514,
}
luaw_webapp_config = {
base_dir = "/apps/luaw/sample/webapps"
}
```
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.
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.
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.
## webapp configuration
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:
```
luaw_root_dir
|
+--- bin
|
+--- conf
| |
│ +--- server.cfg
|
+--- lib
|
+--- logs
|
+--- webapps
|
+--- myapp1
| |
| +---web.lua
|
+--- myapp2
|
+---web.lua
```
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.
##Sample web.lua:
```lua
luaw_webapp = {
resourcePattern = "handler%-.*%.lua",
views = {
"user/address-view.lua",
"account/account-view.lua"
}
}
Luaw.logging.file {
name = "root",
level = Luaw.logging.ERROR,
}
Luaw.logging.file {
name = "com.luaw",
level = Luaw.logging.INFO,
}
Luaw.logging.syslog {
name = "com.luaw",
level = Luaw.logging.WARNING,
}
```
luaw_webapp section specifies resources (request handlers) and views (templates) that make up the web application. These can be specified in two different ways:
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.
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.
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.
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.
================================================
FILE: docs/first-webapp.md
================================================
#4. Your First Luaw Webapp - "Hello world!"
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.
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.
##Writing Luaw Request Handler
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.
cd luaw_root_dir/webapps
mkdir myapp
cd myapp
2. create a filed called web.lua in `luaw_root_dir/webapps/myapp` and put following content in it
```lua
luaw_webapp = {
resourcePattern = "handler%-.*%.lua",
}
```
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.
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.
Next put following code in "handler-helloworld.lua":
```lua
GET 'helloworld' {
function(req, resp, pathParams)
return "Hello World!"
end
}
```
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.
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.
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.
export LD_LIBRARY_PATH=/usr/local/lib/
4. Now we are ready to start the server. To do this switch to `luaw_root_dir` and run Luaw server like this:
cd luaw_root_dir
./bin/luaw_server ./conf/server.cfg
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:
```
******************** Starting webapp myapp *******************************
.Loading resource ./webapps/myapp/handlers/handler-helloworld.lua
#Loaded total 1 resources
#Compiled total 0 views
*********************** Webapp myapp started ****************************
starting server on port 7001 ...
```
Now point your browser to http://localhost:7001/myapp/helloworld and greet the brave new world!
Congratulations, you have successfully written your first Luaw webapp!
================================================
FILE: docs/getting-started.md
================================================
#2. Getting started
Let's build Luaw from its sources. Luaw depends on,
1. Lua 5.2(+)
2. libuv (v1.0.0 )
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.
##Building Lua 5.2
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:
curl -R -O http://www.lua.org/ftp/lua-5.2.3.tar.gz
tar zxf lua-5.2.3.tar.gz
cd lua-5.2.3
make linux test
sudo make linux install
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,
make macosx test
sudo make linux install
To see what OSes are supported run ./lua-5.2.3/src/Makefile targets
##Building libuv
Luaw uses node.js library libuv to do asynchronous, event based IO in a portable, cross platform manner. To build libuv:
1. first clone libuv repository
git clone https://github.com/joyent/libuv.git
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.
cd libuv
git checkout tags/v1.0.0
3. Build libuv. This may require you to install autotools. Detailed instructions are [here](https://github.com/joyent/libuv#build-instructions)
sh autogen.sh
./configure
make
make check
sudo make install
## Building Luaw
With all dependencies built, now we are ready to build Luaw itself.
1. Clone Luaw repository
git clone https://github.com/raksoras/luaw.git
2. Build Luaw
cd luaw/src
make linux
3. Install Luaw binary - luaw_server - in directory of your choice
make INSTALL_ROOT=<luaw_root_dir> install
4. Note: On Mac running Yosemite version of Mac OS you may have to run,
make SYSCFLAGS=-I/usr/local/include SYSLDFLAGS=-L/usr/local/lib macosx
make INSTALL_ROOT=<luaw_root_dir> install
##Luaw directory structure
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`
```
luaw_root_dir
|
|
+--- bin Directory that holds Luaw server binary we built
| along with all necessary Lua libraries
|
+--- conf Directory for server configuration
| |
│ +--- server.cfg Sample server configuration file, to be used as a starting point
|
+--- lib Directory to install any third party or user supplied Lua
| libraries that application may depend on.
|
+--- logs Directory for server logs
|
+--- webapps Directory to install Luaw webapps
```
This directory structure and its usage is further explained in the next chapter "Configuring Luaw"
================================================
FILE: docs/http-client.md
================================================
#10. Async HTTP Client
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.
The Luaw HTTP client is modeled by two objects: clientHttpRequest and clientHttpResponse. Here is a small example of Luaw's HTTP client's usage:
```lua
-- set up HTTP request
local clientReq = Luaw.newClientHttpRequest()
clientReq.hostName = "www.google.com"
-- OR alternatively,
clientReq.hostIP = "74.125.25.106"
clientReq.method = 'GET'
clientReq.url = "/"
clientReq.headers = { Host = "www.google.com" }
-- OR alternatively
clientReq:addHeader("Host", "www.google.com")
-- execute the HTTP request and read the response back.
local clientResp = clientReq:execute()
-- Get the respone headers and body from the client response object returned
local respBody = clientResp.body
local respHeaders = clientResp.headers
```
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.
================================================
FILE: docs/logging.md
================================================
#8. Luaw logging framework
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
runtime properties
Luaw's logging is configured at two levels
##Logging configuration for the whole server
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
```lua
luaw_log_config = {
log_dir = "/apps/luaw/sample/log",
log_file_basename = "luaw-log",
log_filename_timestamp_format = '%Y%m%d',
log_file_size_limit = 1024*1024,
log_file_count_limit = 9,
log_lines_buffer_count = 16,
syslog_server = "127.0.0.1",
syslog_port = 514
}
```
Example above configures,
1. Log directory where the files should go: /apps/luaw/sample/log
2. Log file base name: luaw-log
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
4. File size limit for an individual log file: 1MB
5. Total number of log files to keep: 9
6. How many log lines to buffer in memory before flushing them to log file: 16
7. syslog server address: 127.0.0.1
8. syslog server port: 514
You could omit either file system related configuration or syslog configuration but at least one must be present to use Luaw logging.
##Logging configuration per webapp
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":
```lua
Luaw.logging.file {
name = "root",
level = Luaw.logging.ERROR
}
```
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.
```lua
Luaw.logging.file {
name = "com.myapp",
level = Luaw.logging.INFO
}
```
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.
To actually log a line you use code like following:
```lua
local log = require("luaw_logging")
local logger = log.getLogger("com.myapp")
logger.error("some error occurred")
logger.warning("this is a warning")
logger.info("some information")
logger.debug("lowest level debug")
```
Here are different log levels and Luaw logger functions corresponding to them in a decreasing order of severity:
|Log Level | Logging function |
|--------------------------------
| EMERGENCY | logger.emergency() |
| ALERT | logger.alert() |
| CRITICAL | logger.critical() |
| ERROR | logger.error() |
| WARNING | logger.warning() |
| NOTICE | logger.notice() |
| INFO | logger.info() |
| DEBUG | logger.debug() |
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
```lua
logger.infof("User name %s, User Id %d", name, id)
```
This form is useful to avoid unnecessary string concatenation beforehand when the configured logger level may actually end up filtering the log line anyways.
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:
```lua
Luaw.logging.syslog {
name = "com.myapp.system",
level = Luaw.logging.WARNING
}
```
syslog sever and port to use is specified in server.cfg
================================================
FILE: docs/proxy-http.md
================================================
#13. custom HTTP handler for proxying request
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.
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.
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.
```
+------+ ----HTTP request---> +-------------+ --- Proxy HTTP request ---> +-------+
|Client| |Reverse Proxy| |Backend|
+------+ <---HTTP response--- +-------------+ <-- Proxy HTTP response --- +-------+
```
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.
```lua
--[[
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.
]]
Luaw.request_handler = function(conn)
conn:startReading()
-- loop to support HTTP 1.1 persistent (keep-alive) connections
while true do
local req = Luaw.newServerHttpRequest(conn)
local resp = Luaw.newServerHttpResponse(conn)
-- read and parse full request
local eof = req:readFull()
if (eof) then
conn:close()
return "connection reset by peer"
end
local reqHeaders = req.headers
local beHost = reqHeaders['backend-host']
local beURL = reqHeaders['backend-url']
if (beHost and beURL) then
local backendReq = Luaw.newClientHttpRequest()
backendReq.hostName = beHost
backendReq.url = beURL
backendReq.method = 'GET'
backendReq.headers = { Host = beHost }
local status, backendResp = pcall(backendReq.execute, backendReq)
if (status) then
resp:setStatus(backendResp:getStatus())
resp:appendBody(backendResp:getBody())
local beHeaders = backendResp.headers
for k,v in pairs(beHeaders) do
if ((k ~= 'Transfer-Encoding')and(k ~= 'Content-Length')) then
resp:addHeader(k,v)
end
end
backendResp:close()
else
resp:setStatus(500)
resp:appendBody("connection to backend server failed")
end
else
resp:setStatus(400)
resp:appendBody("Request must contain headers backend-host and backend-url")
end
local status, mesg = pcall(resp.flush, resp)
if (not status) then
conn:close()
return error(mesg)
end
if (req:shouldCloseConnection() or resp:shouldCloseConnection()) then
conn:close()
return "connection reset by peer"
end
end
end
```
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:
```
./bin/luaw_server ./conf/server.cfg
```
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.
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:
./bin/luaw_server ./conf/server.cfg ./bin/proxy-handler.lua
`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.
Now test your shiny new proxy server by running following tests:
## Test 1 - Missing required headers
$ curl -v http://127.0.0.1:7001/
* Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 7001 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.37.1
> Host: 127.0.0.1:7001
> Accept: */*
>
< HTTP/1.1 400 Bad Request
< Content-Length: 50
<
Headers proxy-host and proxy-url must be present
## Test 2 - With correct headers
$ curl -v -H"proxy-host: www.google.com" -H"proxy-url: /" http://127.0.0.1:7001/
* Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 7001 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.37.1
> Host: 127.0.0.1:7001
> Accept: */*
> proxy-host: www.google.com
> proxy-url: /
>
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=ISO-8859-1
< Transfer-Encoding: chunked
* Server gws is not blacklisted
< Server: gws
(.. followed by the body of the home page at www.google.com)
================================================
FILE: docs/resp-obj.md
================================================
#9. Response Object
So far we have seen two ways to return response body from resource handler:
1. Returning a string from resource handler function which Luaw returns in turn as a whole response body to the client, and
2. Using Luaw template views that generate response body programmatically in a fashion similar to JSP or ASP
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:
```lua
GET '/user/:username' {
function(req, resp, pathParams)
return "Hello "..pathParams.username.."!"
end
}
```
Finer grained control over response generation is achieved by invoking various methods on the response object as described below.
1. `resp:setStatus(status)`: You can set HTTP status code to be returned to the client - 200, 404 etc. - using this method
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.
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.
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.
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.
5. `resp:close()`: Finally, call this method to actually close underlying connection to client and release all the associated resources.
================================================
FILE: docs/second-webapp.md
================================================
#5. Your Second Luaw webapp - "Hello `username`!"
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']`
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:
```lua
GET '/user/:username' {
function(req, resp, pathParams)
return "Hello "..pathParams.username.."!"
end
}
```
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']`
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!
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,
```lua
GET '/user/:username/#count' {
function(req, resp, pathParams)
return "Hello "..pathParams.username.."! You are user number "..pathParams.count.." to visit this site."
end
}
```
and then point your browser to "http://localhost:7001/myapp/user/raksoras/9" to get
```
Hello raksoras! You are user number 9 to visit this site.
```
in your browser window.
================================================
FILE: docs/template-view.md
================================================
#6. Luaw Template Views
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.
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!
Without further ado, here is a sample Luaw template view file - view-address.lua:
```lua
BEGIN 'html'
BEGIN 'head'
BEGIN 'title'
TEXT 'Address'
END 'title'
END 'head'
BEGIN 'body'
BEGIN 'div' {class='address'}
BEGIN 'h1'
TEXT(model.title)
END 'h1'
BEGIN 'table' {border="1", margin="1px"}
BEGIN 'tr'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT 'City'
END 'td'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT(model.city)
END 'td'
END 'tr'
if (model.zip == 94086) then
BEGIN 'tr'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT 'County'
END 'td'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT 'Santa Clara'
END 'td'
END 'tr'
end
BEGIN 'tr'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT 'Zip'
END 'td'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT(model.zip)
END 'td'
END 'tr'
END 'table'
END 'div'
END 'body'
END 'html'
```
Each Luaw template view has access to following implicitly defined variables: `req`, `resp`,
`pathParams` and `model` passed from resource handler that invoked the view
It also defines three syntax sugar extentions to generate markeup `BEGIN`, `TEXT` and `END`.
1. You use `BEGIN tag_name` to open any HTML or XML tag of type tag_name
2. If the tag has any attributes you follow the `BEGIN tag_name` with set of attributes defined like this `{name1=value1, name2=value2 ...}`
3. You close opne tag with `END tag_name`
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.
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.
In the next chapter we will see how to use Luaw template views with resource handlers.
**_NOTE:_**
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:
```lua
-- Reusable markup generating function AKA tag library
function generateHeader(model, BEGIN, TEXT, END)
BEGIN 'div' {class='header')
--- generate HTML here using BEGIN, TEXT and END
END 'div'
end
```
```lua
-- Using common markup generating function from Luaw template view
local ssi = require("common_markup.lua")
-- Just call function to include the markup at write place
ssi.generateHeader(model, BEGIN, TEXT, END)
BEGIN 'div' {class="body"}
--- generate page specific page markup here
END 'div'
ssi.generateFooter(model, BEGIN, TEXT, END)
```
================================================
FILE: docs/third-webapp.md
================================================
#7. Your third webapp - with Luaw template view
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.
##Luaw template view
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:
```lua
BEGIN 'html'
BEGIN 'head'
BEGIN 'title'
TEXT 'Address'
END 'title'
END 'head'
BEGIN 'body'
BEGIN 'div' {class='address'}
BEGIN 'h1'
TEXT(model.title)
END 'h1'
BEGIN 'table' {border="1", margin="1px"}
BEGIN 'tr'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT 'City'
END 'td'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT(model.city)
END 'td'
END 'tr'
if (model.zip == 94086) then
BEGIN 'tr'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT 'County'
END 'td'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT 'Santa Clara'
END 'td'
END 'tr'
end
BEGIN 'tr'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT 'Zip'
END 'td'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT(model.zip)
END 'td'
END 'tr'
END 'table'
END 'div'
END 'body'
END 'html'
```
##REST resource handler
Add a file `handler-address.lua` to `luaw_root_dir/webapps/myapp/handlers` containing following code:
```lua
GET 'address/:city/#zip' {
function(req, resp, pathParams)
address = {
city = pathParams.city,
zip = pathParams.zip
}
return '/views/view-address.lua', address
end
}
```
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 '#')
Most interesting line in the handler above is the following return statement from the function handler:
return '/views/view-address.lua', address
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`
##Modified web.lua
Modify *<luaw_root_dir>*/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
luaw_webapp = {
resourcePattern = "handler%-.*%.lua",
viewPattern = "view%-.*%.lua",
}
##Test your work
Finally, restart your luaw server by running
cd luaw_root_dir
./bin/luaw_server ./conf/server.cfg
You should see console output similar to this:
```
********************* Starting webapp myapp ****************************
.Loading resource ./webapps/myapp/handlers/handler-address.lua
.Loading resource ./webapps/myapp/handlers/handler-hellouser.lua
.Loading resource ./webapps/myapp/handlers/handler-helloworld.lua
#Loaded total 3 resources
.Loading view /views/view-address.lua
#Compiled total 1 views
*********** ********* Webapp myapp started ****************************
```
Note the "loading view" part.
Now point your browser to http://127.0.0.1:7001/myapp/address/Sunnyvale/94085 and see the output in your browser.
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.
================================================
FILE: docs/user-threads.md
================================================
#11. User Threads
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.
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
```lua
local function parallelHttpRequest(host, url)
local clientReq = Luaw.newClientHttpRequest()
clientReq.hostName = host
clientReq.method = 'GET'
clientReq.url = url
clientReq:addHeader("Host", host)
local clientResp = clientReq:execute()
return clientResp
end
local scheduler = Luaw.scheduler
-- do two HTTP request in parallel
local threadCtx1 = scheduler.startUserThread(parallelHttpRequest, "www.google.com", "/")
local threadCtx2 = scheduler.startUserThread(parallelHttpRequest, "www.facebook.com", "/")
-- wait on both threads to be donw
scheduler.join(threadCtx1, threadCtx2)
-- Retrieve the responses received
local clientResp1 = threadCtx1.result
local clientResp2 = threadCtx2.result
```
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.
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.
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.
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.
================================================
FILE: docs/user-timers.md
================================================
#12. Luaw Timers
Luaw supports user defined timers. Here is an example:
```lua
local timer = Luaw.newTimer()
timer:start(1000)
doSomeStuff()
print("waiting till it's time...")
timer:wait()
print('done waiting!')
-- call timer: delete() to free timer resources immediately. If not delete() is not called
-- timer resources hang around till Lua's VM garbage collects them
timer:delete()
```
1. You create a new timer using Luaw.newTimer()
2. You start it with some timeout - specified in milliseconds using timer:start(timeout)
3. and finally you wait on it using timer:wait()
That's basically it!
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:
```lua
local timer = Luaw.newTimer()
timer:sleep(1000)
print('done waiting!')
```
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.
================================================
FILE: lib/luapack.lua
================================================
--[[
Copyright (c) 2015 raksoras
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
local luaw_constants = require("luaw_constants")
local lpackMT = getmetatable(luaw_lpack_lib.newLPackParser())
lpackMT.INT_RANGES = {
lpackMT.UINT_8,
lpackMT.UINT_16,
lpackMT.UINT_32,
lpackMT.INT_8,
lpackMT.INT_16,
lpackMT.INT_32,
lpackMT.INT_64,
lpackMT.FLOAT,
lpackMT.DOUBLE
}
lpackMT.FLOAT_RANGES = {
lpackMT.FLOAT,
lpackMT.DOUBLE
}
lpackMT.STRING_RANGES = {
lpackMT.STRING,
lpackMT.BIG_STRING,
lpackMT.HUGE_STRING
}
lpackMT.DICT_ENTRY_RANGES = {
lpackMT.DICT_ENTRY,
lpackMT.BIG_DICT_ENTRY
}
local function findMinRange(num, ranges)
for i, range in ipairs(ranges) do
if ((num >= range[4])and(num <= range[5])) then
return range
end
end
error("Number "..num.." outside supported max range")
end
-- read functions
local function readNextBuffer(lpack)
if not lpack.EOF then
local newBuffer = lpack:readFn()
if not newBuffer then
lpack.EOF = true
else
local buffer = lpack.buffer
local offset = lpack.offset
if ((buffer)and(#buffer > offset)) then
lpack.buffer = string.sub(buffer, offset)..newBuffer
else
lpack.buffer = newBuffer
end
lpack.offset = 0
end
end
end
local function done(lpack)
return ((lpack.EOF)and(lpack.offset >= #lpack.buffer))
end
local function readNumber(lpack, numType)
while (not lpack:done()) do
local offset = lpack.offset
local readLen, value = lpack.read_number(numType, lpack.buffer, offset)
if (readLen < 0) then
error("Error while reading number at byte# "..tostring(offset).." in buffer: "..tostring(lpack.buffer))
end
if (readLen > 0) then
lpack.offset = offset + readLen
return value
end
readNextBuffer(lpack);
end
end
local function readMarker(lpack)
return readNumber(lpack, lpack.TYPE_MARKER[2])
end
local function readString(lpack, desiredLen)
local accm
while ((desiredLen > 0)and(not lpack:done())) do
local offset = lpack.offset
local buffer = lpack.buffer
local readLen, value = lpack.read_string(desiredLen, buffer, offset)
if (readLen > 0) then
lpack.offset = offset + readLen
desiredLen = desiredLen - readLen
if (desiredLen == 0) then
if accm then
table.insert(accm, value)
return table.concat(accm)
end
return value
end
if not accm then accm = {} end
table.insert(accm, value)
end
readNextBuffer(lpack);
end
end
local function deserialize(lpack, container, isMap)
local key, val, len, t
local isKey = true
local dictionary = lpack.dictionary
while not lpack:done() do
t = readMarker(lpack)
if t == lpack.NIL[2] then
val = nil
elseif t == lpack.BOOL_TRUE[2] then
val = true
elseif t == lpack.BOOL_FALSE[2] then
val = false
elseif t == lpack.STRING[2] then
len = readNumber(lpack, lpack.UINT_8[2])
val = readString(lpack, len)
elseif t == lpack.BIG_STRING[2] then
len = readNumber(lpack, lpack.UINT_16[2])
val = readString(lpack, len)
elseif t == lpack.HUGE_STRING[2] then
len = readNumber(lpack, lpack.UINT_32[2])
val = readString(lpack, len)
elseif t == lpack.MAP_START[2] then
val = deserialize(lpack, luaw_lpack_lib.createDict(0, 16), true)
elseif t == lpack.ARRAY_START[2] then
val = deserialize(lpack, luaw_lpack_lib.createDict(16, 0), false)
elseif t == lpack.RECORD_END[2] then
if ((isMap)and(not isKey)) then
error("Unbalanced table, key without corresponding value found")
end
return container
elseif t == lpack.DICT_ENTRY[2] then
local dw = readNumber(lpack, lpack.UINT_8[2])
assert(dictionary, "Missing dictionary")
val = assert(dictionary[dw], "Entry missing in dictionary: "..dw)
elseif t == lpack.BIG_DICT_ENTRY[2] then
local dw = readNumber(lpack, lpack.UINT_16[2])
assert(dictionary, "Missing dictionary")
val = assert(dictionary[dw], "Entry missing in dictionary")
elseif t == lpack.DICT_START[2] then
dictionary = deserialize(lpack, luaw_lpack_lib.createDict(64, 0), false)
lpack.dictionary = dictionary
debugDump(dictionary)
else
-- everything else is a number
val = readNumber(lpack, t)
end
if container then
if isMap then
if isKey then
key = val
isKey = false
else
container[key] = val
isKey = true
end
else
-- is array
table.insert(container, val)
end
else
if (t ~= lpack.DICT_START[2]) then
-- single, standalone value
return val
end
end
end
return val
end
local function read(lpack)
readNextBuffer(lpack)
val = deserialize(lpack, nil, false)
return val
end
local function newLPackReader()
local lpackReader = luaw_lpack_lib.newLPackParser();
lpackReader.EOF = false
lpackReader.buffer = ''
lpackReader.offset = 0
lpackReader.done = done
lpackReader.read = read
return lpackReader
end
local function newLPackStringReader(str)
assert(str, "String cannot be null")
local lpackReader = newLPackReader()
local eof = false
lpackReader.readFn = function()
if (not eof) then
eof = true
return str
end
end
return lpackReader
end
local function newLPackFileReader(file)
assert(file, "File cannot be null")
local lpackReader = newLPackReader()
lpackReader.readFn = function()
return file:read(1024)
end
return lpackReader
end
local function newLPackReqReader(req)
assert(req, "Request cannot be null")
local lpackReader = newLPackReader()
lpackReader.readFn = function()
if ((not req.EOF)and(not req.luaw_mesg_done)) then
req:readAndParse()
local str = req:consumeBodyChunkParsed()
if (not str) then
debugDump(req)
end
return str
end
end
return lpackReader
end
-- Write functions
local function flush(lpack)
local writeQ = lpack.writeQ
local count = #writeQ
if count then
local str = lpack.serialize_write_Q(writeQ, lpack.writeQsize)
if str then
lpack:writeFn(str)
lpack.writeQsize = 0
for i=1,count do
writeQ[i] = nil
end
end
end
end
local function qstore(lpack, val, size)
if not val then
error("nil string passed to write(), use writeNil() instead")
end
local writeQ = lpack.writeQ
table.insert(writeQ, val);
lpack.writeQsize = lpack.writeQsize + size;
if (lpack.writeQsize >= lpack.flushLimit) then
flush(lpack)
end
end
local function writeMarker(lpack, marker)
if ((marker[2] < lpack.TYPE_MARKER[2])or(marker[2] > lpack.HUGE_STRING[2])) then
error("Invalid marker "..marker.." specified")
end
qstore(lpack, marker[2], 1)
end
local function startMap(lpack)
writeMarker(lpack, lpack.MAP_START)
end
local function startArray(lpack)
writeMarker(lpack, lpack.ARRAY_START)
end
local function startDict(lpack)
writeMarker(lpack, lpack.DICT_START)
end
local function endCollection(lpack)
writeMarker(lpack, lpack.RECORD_END)
end
local function writeBoolean(lpack, value)
if (value) then
writeMarker(lpack, lpack.BOOL_TRUE)
else
writeMarker(lpack, lpack.BOOL_FALSE)
end
end
local function writeNil(lpack)
writeMarker(lpack, lpack.NIL)
end
local function writeNumber(lpack, num)
local range
if (num % 1 == 0) then
-- integer
range = findMinRange(num, lpack.INT_RANGES)
else
-- float
range = findMinRange(num, lpack.FLOAT_RANGES)
end
qstore(lpack, range[2], 1);
qstore(lpack, num, range[3]);
end
local function writeString(lpack, str)
local dw, len, range
local dict = lpack.dictionary
if dict then
dw = dict[str]
end
if dw then
range = findMinRange(dw, lpack.DICT_ENTRY_RANGES)
str = dw
len = range[3]
else
len = #str
range = findMinRange(len, lpack.STRING_RANGES)
end
qstore(lpack, range[2], 1) -- marker
qstore(lpack, str, len) -- actual string value/dictionary entry
end
local function serialize(lpack, val)
local t = type(val)
if t == 'nil' then
writeNil(lpack)
return;
end
if t == 'boolean' then
writeBoolean(lpack, val)
return
end
if t == 'number' then
writeNumber(lpack, val)
return
end
if t == 'string' then
writeString(lpack, val)
return
end
if t == 'table' then
if (#val > 0) then
writeMarker(lpack, lpack.ARRAY_START)
for i, v in ipairs(val) do
serialize(lpack, v)
end
endCollection(lpack)
else
writeMarker(lpack, lpack.MAP_START)
for k, v in pairs(val) do
serialize(lpack, k)
serialize(lpack, v)
end
endCollection(lpack)
end
end
end
local function write(lpack, val)
serialize(lpack, val)
flush(lpack)
end
local function setDictionaryForWrite(lpack, dict)
assert((type(dict) == 'table'), "Please provide valid dictionary table")
local dictionary = luaw_lpack_lib.createDict(#dict, 0)
writeMarker(lpack, lpack.DICT_START)
for i, dw in ipairs(dict) do
writeString(lpack, dw)
dictionary[dw] = i
end
writeMarker(lpack, lpack.RECORD_END)
lpack.dictionary = dictionary
end
local function newLPackWriter(limit)
local lpackWriter = luaw_lpack_lib.newLPackParser()
lpackWriter.writeQ = {}
lpackWriter.writeQsize = 0
lpackWriter.flushLimit = limit or luaw_constants.CONN_BUFFER_SIZE
lpackWriter.useDictionary = setDictionaryForWrite
lpackWriter.write = write
return lpackWriter
end
local function newLPackFileWriter(file, limit)
assert(file, "File can not be nil")
local lpackWriter = newLPackWriter(limit)
lpackWriter.writeFn = function(lpack, str)
file:write(str)
end
return lpackWriter
end
local function newLPackBufferWriter(buff, limit)
assert(buff, "buffer can not be nil")
local lpackWriter = newLPackWriter(limit)
lpackWriter.writeFn = function(lpack, str)
table.insert(buff, str)
end
return lpackWriter
end
local function newLPackRespWriter(resp, limit)
assert(resp, "response can not be nil")
local lpackWriter = newLPackWriter(limit)
resp.headers['Content-Type'] = 'application/luapack'
resp:startStreaming()
lpackWriter.writeFn = function(lpack, str)
resp:appendBody(str)
end
return lpackWriter
end
luaw_lpack_lib.newLPackFileReader = newLPackFileReader
luaw_lpack_lib.newLPackStringReader = newLPackStringReader
luaw_lpack_lib.newLPackReqReader = newLPackReqReader
luaw_lpack_lib.newLPackFileWriter = newLPackFileWriter
luaw_lpack_lib.newLPackBufferWriter = newLPackBufferWriter
luaw_lpack_lib.newLPackRespWriter = newLPackRespWriter
return luaw_lpack_lib
================================================
FILE: lib/luaw_constants.lua
================================================
local constMT = {
__newindex = function(table, key, value)
error("constant "..table.name.." cannot be changed")
end,
__tostring = function(table)
return table.name
end,
__concat = function(op1, op2)
return tostring(op1)..tostring(op2)
end,
__metatable = "Luaw constant"
}
local function luaw_constant(value)
local c = {name = value}
setmetatable(c, constMT)
return c
end
return {
-- scheduler constants
TS_RUNNABLE = luaw_constant("RUNNABLE"),
TS_DONE = luaw_constant("DONE"),
TS_BLOCKED_EVENT = luaw_constant("BLOCKED_ON_EVENT"),
TS_BLOCKED_THREAD = luaw_constant("BLOCKED_ON_THREAD"),
END_OF_CALL = luaw_constant("END_OF_CALL"),
END_OF_THREAD = luaw_constant("END_OF_THREAD"),
-- TCP constants
DEFAULT_CONNECT_TIMEOUT = luaw_server_config.connect_timeout or 8000,
DEFAULT_READ_TIMEOUT = luaw_server_config.read_timeout or 3000,
DEFAULT_WRITE_TIMEOUT = luaw_server_config.write_timeout or 3000,
CONN_BUFFER_SIZE = luaw_server_config.connection_buffer_size or 4096,
-- HTTP parser constants
EOF = 0,
CRLF = '\r\n',
MULTIPART_BEGIN = luaw_constant("MULTIPART_BEGIN"),
PART_BEGIN = luaw_constant("PART_BEGIN"),
PART_DATA = luaw_constant("PART_DATA"),
PART_END = luaw_constant("PART_END"),
MULTIPART_END = luaw_constant("MULTIPART_END")
}
================================================
FILE: lib/luaw_data_structs_lib.lua
================================================
--[[
Copyright (c) 2015 raksoras
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
local module = {}
--
-- Registry
---
-- reg[0] stores head of the free list
local function ref(reg, obj)
if not obj then return -1 end
local ref = reg[0];
if ref then
reg[0] = reg[ref]
else
ref = #reg + 1
end
reg[ref] = obj
reg.size = reg.size + 1
return ref
end
local function unref(reg, ref)
if ref >= 0 then
reg[ref] = reg[0]
reg[0] = ref
reg.size = reg.size -1
end
end
function module.newRegistry(size)
local reg = {}
reg.ref = ref;
reg.unref = unref;
reg.size = 0
return reg;
end
--
-- Ring buffers
--
local function offer(rb, obj)
local size = rb.size
local filled = rb.filled
local writer = rb.writer
if (filled < size) then
rb[writer] = obj
rb.filled = filled + 1
if (writer == size) then
rb.writer = 1
else
rb.writer = writer + 1
end
return true;
end
return false;
end
local function offerWithWait(rb, obj)
local added = offer(rb, obj)
while not added do
coroutine.yield()
added = offer(rb, obj)
end
return added
end
local function take(rb)
local size = rb.size
local filled = rb.filled
local reader = rb.reader
local obj = nil
if (filled > 0) then
obj = rb[reader];
rb[reader] = nil;
rb.filled = filled - 1
if (reader == size) then
rb.reader = 1
else
rb.reader = reader + 1
end
end
return obj
end
local function takeWithWait(rb)
local obj = take(rb)
while not obj do
coroutine.yield()
obj = take(rb)
end
return obj
end
local function offerWithOverwrite(rb, obj)
local added = offer(rb, obj)
if added then return true end
-- overwrite oldest item
local overwrittenObj = take(rb)
offer(rb, obj)
return false, overwrittenObj
end
function module.newRingBuffer(size)
local rb = {}
rb.reader = 1
rb.writer = 1
rb.filled = 0
rb.size = size
rb.offer = offer
rb.take = take
return rb
end
function module.newOverwrittingRingBuffer(size)
local rb = {}
rb.reader = 1
rb.writer = 1
rb.filled = 0
rb.size = size
rb.offer = offerWithOverwrite
rb.take = take
return rb
end
function module.newBlockingRingBuffer(size)
local rb = {}
rb.reader = 1
rb.writer = 1
rb.filled = 0
rb.size = size
rb.offer = offerWithWait
rb.take = takeWithWait
return rb
end
return module
================================================
FILE: lib/luaw_http.lua
================================================
--[[
Copyright (c) 2015 raksoras
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
local constants = require('luaw_constants')
local luaw_tcp_lib = require('luaw_tcp')
local TS_BLOCKED_EVENT = constants.TS_BLOCKED_EVENT
local TS_RUNNABLE = constants.TS_RUNNABLE
local CONN_BUFFER_SIZE = constants.CONN_BUFFER_SIZE
local EOF = constants.EOF
local CRLF = constants.CRLF
local MULTIPART_BEGIN = constants.MULTIPART_BEGIN
local PART_BEGIN = constants.PART_BEGIN
local PART_DATA = constants.PART_DATA
local PART_END = constants.PART_END
local MULTIPART_END = constants.MULTIPART_END
local http_status_codes = {
[100] = "Continue",
[101] = "Switching Protocols",
[200] = "OK",
[201] = "Created",
[202] = "Accepted",
[203] = "Non-Authoritative Information",
[204] = "No Content",
[205] = "Reset Content",
[206] = "Partial Content",
[300] = "Multiple Choices",
[301] = "Moved Permanently",
[302] = "Found",
[303] = "See Other",
[304] = "Not Modified",
[305] = "Use Proxy",
[307] = "Temporary Redirect",
[400] = "Bad Request",
[401] = "Unauthorized",
[402] = "Payment Required",
[403] = "Forbidden",
[404] = "Not Found",
[405] = "Method Not Allowed",
[406] = "Not Acceptable",
[407] = "Proxy Authentication Required",
[408] = "Request Timeout",
[409] = "Conflict",
[410] = "Gone",
[411] = "Length Required",
[412] = "Precondition Failed",
[413] = "Request Entity Too Large",
[414] = "Request-URI Too Long",
[416] = "Requested Range Not Satisfiable",
[417] = "Expectation Failed",
[500] = "Internal Server Error",
[501] = "Not Implemented",
[502] = "Bad Gateway",
[503] = "Service Unavailable",
[504] = "Gateway Timeout",
[505] = "HTTP Version Not Supported"
}
setmetatable(http_status_codes, {
__index = function(status)
return "User Defined Status"
end
})
-- write buffer implementation
local function clearArrayPart(t)
local len = #t
for i=1,len do
t[i] = nil
end
end
local function reset(buffer)
clearArrayPart(buffer)
buffer.len = 0
end
local function concat(buffer)
return table.concat(buffer)
end
local function append(buffer, str)
local len = buffer.len
if (str) then
table.insert(buffer, str)
len = len + #str
buffer.len = len
end
return len
end
local function newBuffer()
return {
len = 0,
reset = reset,
concat = concat,
append = append
}
end
function luaw_http_lib.storeHttpParam(params, name , value)
oldValue = params[name]
if (oldValue) then
-- handle multi-valued param names
if (type(oldValue) == 'table') then
table.insert(oldValue, value)
else
-- single param value already stored against the same param name
-- convert it to table and store multiple values in it
params[name] = {oldValue, value}
end
else
params[name] = value
end
end
local parserMT = getmetatable(luaw_http_lib.newHttpRequestParser())
--[[ HTTP parser we use can invoke callback for the same HTTP field (status, URL, header
name/value etc.) multiple times, each time passing only few characters for the current
ongoing field. This can happen because we are reading HTTP request or response body in
multiple chuncks - either of a fixed byte buffer size of by new lines. For this reason we
"accumulate" http header name and value in a hidden request table fields (_acc_header_name_
/_acc_header_value_) and then store full header value against full header name when the
parser issues callback for a next HTTP field. Other fields like URL, status etc. are
accumulated by concatenating them "in place".
]]
local function accumulateChunkedValue(req, name, chunk)
local accValue = rawget(req, name)
if accValue then
rawset(req, name, accValue .. chunk)
else
rawset(req, name, chunk)
end
end
local function addHeader(req, hName, hValue)
if (hName and hValue) then
local headers = req.headers
local currValues = headers[hName]
if currValues then
-- handle multi-valued headers
if (type(currValues) == 'table') then
table.insert(currValues, hValue)
else
-- single string header value already stored against the same header name
-- convert it to table and store multiple values in it
headers[hName] = {currValues, hValue}
end
else
headers[hName] = hValue
end
return true
end
return false
end
local function handleAccHttpHeader(req)
local hName = rawget(req, '_acc_header_name_')
local hValue = rawget(req, '_acc_header_value_')
local added = addHeader(req, hName, hValue)
if (added) then
rawset(req, '_acc_header_name_', nil)
rawset(req, '_acc_header_value_', nil)
end
end
local function handleKeepAlive(req, keepAlive)
if not keepAlive then
req.headers['Connection'] = 'close'
req.EOF = true
end
end
local function onNone(req, cbType)
end
local function onMesgBegin(req, cbtype, remaining)
req:reset()
end
local function onStatus(req, cbtype, remaining ,status)
accumulateChunkedValue(req, 'statusMesg', status)
end
local function onURL(req, cbtype, remaining, url)
accumulateChunkedValue(req, 'url', url)
end
local function onHeaderName(req, cbtype, remaining, hName)
handleAccHttpHeader(req)
accumulateChunkedValue(req, '_acc_header_name_', hName)
end
local function onHeaderValue(req, cbtype, remaining, hValue)
if not hValue then hValue = '' end -- empty header value
accumulateChunkedValue(req, '_acc_header_value_', hValue)
end
local function onHeadersComplete(req, cbtype, remaining, keepAlive, httpMajor, httpMinor, method, status)
handleAccHttpHeader(req)
handleKeepAlive(req, keepAlive)
req.major_version = httpMajor
req.minor_version = httpMinor
req.method = method
req.status = status
-- parse URL
local url = req.url
local parsedURL
if url then
local method = req.method
parsedURL = luaw_http_lib.parseURL(url, ((method) and (string.upper(method) == "CONNECT")))
else
parsedURL = {}
end
req.parsedURL = parsedURL
-- GET query params
local params = {}
local queryString = parsedURL.queryString
if queryString then
assert(luaw_http_lib:urlDecode(queryString, params))
end
req.params = params
req.luaw_headers_done = true
end
local function onBody(req, cbtype, remaining, chunk)
req.bodyParts:append(chunk)
end
local function onMesgComplete(req, cbtype, remaining, keepAlive)
-- for the rare boundary case of chunked transfer encoding, where headers may continue
-- after the last body chunk
handleAccHttpHeader(req)
handleKeepAlive(req, keepAlive)
local luaw_parser = req.luaw_parser
if (luaw_parser) then
luaw_parser:initHttpParser()
end
-- store body
local bodyParts = req.bodyParts
req.body = bodyParts:concat()
bodyParts:reset()
-- POST form params
local params = req.params
local contentType = req.headers['Content-Type']
if ((contentType) and (contentType:lower() == 'application/x-www-form-urlencoded')) then
assert(luaw_http_lib:urlDecode(req.body, params))
end
req.luaw_mesg_done = true
end
-- Order is important and must match C enum http_parser_cb_type
local http_callbacks_lua = {
onNone,
onMesgBegin,
onStatus,
onURL,
onHeaderName,
onHeaderValue,
onHeadersComplete,
onBody,
onMesgComplete
}
local function parseHttpFragment(req, conn, parser, content, offset)
-- matched against most number of return results possible. Actual variable names
-- are meaningless without the context of correct callback, misleading even!
local cbtype, offset, keepAlive, httpMajor, httpMinor, method, status = parser:parseHttp(content, offset)
if (not cbtype) then
conn:close()
return error(offset) -- offset carries error message in this case
end
local callback = http_callbacks_lua[cbtype]
if (not callback) then
conn:close()
return error("Invalid HTTP parser callback# "..tostring(cbtype).." requested")
end
callback(req, cbtype, remaining, keepAlive, httpMajor, httpMinor, method, status)
return cbtype, offset
end
local function hasContent(content, offset)
return (content)and(offset)and(offset < #content)
end
local function readAndParse(req)
local conn = req.luaw_conn
local parser = req.luaw_parser
local content = req.luaw_read_content
local offset = req.luaw_read_offset
local httpcb, status
if (not hasContent(content, offset)) then
-- read new content from socket
status, content = conn:read(req.readTimeout)
if (not status) then
if (content == 'EOF') then
req:addHeader('Connection', 'close')
req.luaw_read_content = nil
req.luaw_read_offset = nil
req.luaw_headers_done = true
req.luaw_mesg_done = true
req.EOF = true
return
else
return error(content)
end
end
offset = 0 -- offset is for C, therefore zero based
end
httpcb, offset = parseHttpFragment(req, conn, parser, content, offset)
if (hasContent(content,offset)) then
-- store back remaining content in request object for next HTTP request parsing
req.luaw_read_content = content
req.luaw_read_offset = offset
else
req.luaw_read_content = nil
req.luaw_read_offset = nil
end
end
local function consumeTill(input, search, offset)
local start, stop = string.find(input, search, offset, true)
if (start and stop) then
return stop+1, string.sub(input, offset, start-1)
end
end
local function getMultipartBoundary(req)
local header = req.headers['Content-Type']
if (header) then
local offset, contentType = consumeTill(header, ";", 1)
if (contentType == "multipart/form-data") then
local boundary
offset, boundary = consumeTill(header, "=", offset)
if ((boundary)and(string.find(boundary, "boundary", 1, true))) then
return '--'..string.sub(header, offset)
end
end
end
end
local function isMultipart(req)
if (req.luaw_multipart_boundary) then
return true
end
local boundary = getMultipartBoundary(req)
if (boundary) then
req.luaw_multipart_boundary = boundary
req.luaw_multipart_end = boundary .. '--'
return true
end
end
local function isLuaPackMesg(req)
local contentType = req.headers['Content-Type']
if ('application/luapack' == contentType) then
return true
end
end
local function readFull(req)
-- first parse till headers are done
while (not req.luaw_headers_done) do
req:readAndParse()
end
if ((isMultipart(req))or(isLuaPackMesg(req))) then
-- multipart (file upload) HTTP requests and LuaPack requests are forced to be streaming to conserve memory
return
end
while (not req.luaw_mesg_done) do
req:readAndParse()
end
end
local function consumeBodyChunkParsed(req)
local bodyChunk = req.body
if (bodyChunk) then
req.body = nil
else
local bodyParts = req.bodyParts
if (bodyParts.len > 0) then
bodyChunk = bodyParts:concat()
bodyParts:reset()
end
end
return bodyChunk
end
local function bufferedConsume(req, search, content, offset)
assert(search, "search pattern cannot be nil")
while (true) do
if ((content)and(#content > offset)) then
local matchPos, matchStr = consumeTill(content, search, offset)
if (matchPos) then
-- found match
if (matchPos < #content) then
-- there is content remaining
return matchStr, content, matchPos
end
-- content fully consumed
return matchStr, nil, nil
end
if (offset > 1) then
content = string.sub(content, offset)
offset = 1
end
end
-- match not found, read more
req:readAndParse()
if (req.luaw_mesg_done) then
error("premature HTTP message end")
end
local readStr = req:consumeBodyChunkParsed()
if (readStr) then
if (content) then
content = content..readStr
else
content = readStr
offset = 1
end
end
end
end
local function saveState(req, content, offset)
req.luaw_multipart_content = content
req.luaw_multipart_offset = offset
end
function fetchNextPart(req, state)
local content = req.luaw_multipart_content
local offset = req.luaw_multipart_offset or 1
local boundary = req.luaw_multipart_boundary
local matchStr
if (state == MULTIPART_BEGIN) then
-- read beginning boundary
matchedStr, content, offset = bufferedConsume(req, CRLF, content, offset)
assert(matchedStr == boundary, "Missing multi-part boundary at the beginning of the part")
state = PART_END
end
if (state == PART_END) then
-- read "Content-Disposition" line
matchedStr, content, offset = bufferedConsume(req, ": ", content, offset)
assert(matchedStr == 'Content-Disposition',"Missing 'Content-Disposition' header")
matchedStr, content, offset = bufferedConsume(req, "; ", content, offset)
assert(string.find(matchedStr, 'form-data', 1, true), "Wrong Content-Disposition")
matchedStr, content, offset = bufferedConsume(req, '"', content, offset)
assert(string.find(matchedStr, "name", 1, true), "form field name missing")
local fieldName
fieldName, content, offset = bufferedConsume(req, '"', content, offset)
assert(#fieldName > 0, "form field name missing")
matchedStr, content, offset = bufferedConsume(req, CRLF, content, offset)
local _, filenamePos = string.find(matchedStr, 'filename="', 1, true)
local fileName
if (filenamePos) then
fileName = string.sub(matchedStr, filenamePos+1, #matchedStr-1)
end
local contentType
if (fileName) then
-- read "Content-Type" line
matchedStr, content, offset = bufferedConsume(req, ": ", content, offset)
assert(matchedStr == 'Content-Type', "Missing 'Content-Type' header")
contentType, content, offset = bufferedConsume(req, CRLF, content, offset)
assert(#contentType, "Content-Type value missing")
end
-- read next blank line
matchedStr, content, offset = bufferedConsume(req, CRLF, content, offset)
assert(#matchedStr == 0, "Missing line separating content headers and actual content")
saveState(req, content, offset)
return PART_BEGIN, fieldName, fileName, contentType
end
while ((state == PART_BEGIN)or(state == PART_DATA)) do
local matchedStr, content, offset = bufferedConsume(req, CRLF, content, offset)
if (matchedStr) then
if (matchedStr == boundary) then
saveState(req, content, offset)
return PART_END
end
local lastBoundary = req.luaw_multipart_end
if (matchedStr == lastBoundary) then
saveState(req, content, offset)
return MULTIPART_END
end
saveState(req, content, offset)
return PART_DATA, matchedStr
else
local content = req.luaw_multipart_content
local offset = req.luaw_consumed_till or 0
if ((content)and(#content-offset) > #boundary+3) then
saveState(req, nil, 1)
return PART_DATA, content
end
end
end
end
local function multiPartIterator(req)
if (isMultipart(req)) then
return fetchNextPart, req, MULTIPART_BEGIN
end
end
local function shouldCloseConnection(req)
if req and req.EOF then
return true
end
end
local function toURLSafeChar(ch)
if (ch == " ") then return "+" end
return string.format("%%%02X", string.byte(ch))
end
local function urlEncode(str)
str = string.gsub(str, "([^a-zA-Z0-9%.%*%-_])", toURLSafeChar)
return str
end
local function urlEncodeParams(params)
if params then
local encodedParams = {}
local contentLength = 0
for key, val in pairs(params) do
local ueKey = urlEncode(key)
table.insert(encodedParams, ueKey)
contentLength = contentLength + #ueKey
table.insert(encodedParams, "=")
contentLength = contentLength + 1
local ueVal = urlEncode(val)
table.insert(encodedParams, ueVal)
contentLength = contentLength + #ueVal
table.insert(encodedParams, "&")
contentLength = contentLength + 1
end
if (#encodedParams > 0) then
table.remove(encodedParams) -- remove the last extra "&"
contentLength = contentLength - 1
return encodedParams, contentLength
end
end
return nil, 0
end
function buildURL(req)
if (req.method == 'GET') then
local encodedParams = urlEncodeParams(req.params)
if encodedParams then
table.insert(encodedParams, 1, "?")
table.insert(encodedParams, 1, req.url)
return table.concat(encodedParams)
end
end
return req.url
end
local function setStatus(resp, status)
resp.status = status
resp.statusMesg = http_status_codes[status]
end
local function getStatus(resp)
return resp.status
end
local function getBody(resp)
return resp.body
end
local function firstResponseLine(resp)
local line = {"HTTP/", resp.major_version, ".", resp.minor_version,
" ", resp.status, " ", resp.statusMesg, CRLF}
return table.concat(line)
end
local function firstRequestLine(req)
local line = {req.method, " ", req:buildURL(), " HTTP/", req.major_version,
".", req.minor_version, CRLF}
return table.concat(line)
end
local function sendBuffer(buffer, conn, writeTimeout, isChunked)
local chunk = table.concat(buffer)
buffer:reset()
if (isChunked) then
chunk = string.format("%x\r\n", #chunk)..chunk..CRLF
end
conn:write(chunk, writeTimeout)
end
local function bufferHeader(buffer, name, value)
buffer:append(tostring(name))
buffer:append(": ")
buffer:append(tostring(value))
buffer:append(CRLF)
end
local function bufferHeaders(headers, buffer)
if (headers) then
for name,value in pairs(headers) do
if (type(value) == 'table') then
for i,v in ipairs(value) do
bufferHeader(buffer, name, v)
end
else
bufferHeader(buffer, name, value)
end
headers[name] = nil
end
end
buffer:append(CRLF)
end
local function startStreaming(resp)
resp.luaw_is_chunked = true
resp:addHeader('Transfer-Encoding', 'chunked')
local headers = resp.headers
local conn = resp.luaw_conn
local writeTimeout = resp.writeTimeout
-- use separate buffer from "bodyParts" to serialize headers
local headersBuffer = newBuffer()
headersBuffer:append(resp:firstLine())
bufferHeaders(headers, headersBuffer)
-- flush up to HTTP headers end without chunked encoding before actual body starts
sendBuffer(headersBuffer, conn, writeTimeout, false)
end
local function appendBody(resp, bodyPart)
if not bodyPart then
return
end
local bodyBuffer = resp.bodyParts
local len = bodyBuffer:append(bodyPart)
if ((resp.luaw_is_chunked)and(len >= CONN_BUFFER_SIZE)) then
local conn = resp.luaw_conn
local writeTimeout = resp.writeTimeout
sendBuffer(bodyBuffer, conn, writeTimeout, true)
end
end
local function writeFullBody(resp)
local conn = resp.luaw_conn
local writeTimeout = resp.writeTimeout
local bodyBuffer = resp.bodyParts
if (resp.method == 'POST') then
local encodedParams, contentLength = urlEncodeParams(resp.params)
if encodedParams then
resp:addHeader('Content-Type', 'application/x-www-form-urlencoded')
bodyBuffer:append(encodedParams)
end
end
resp:addHeader('Content-Length', bodyBuffer.len)
-- first write up to HTTP headers end
local headersBuffer = newBuffer()
headersBuffer:append(resp:firstLine())
bufferHeaders(resp.headers, headersBuffer)
sendBuffer(headersBuffer, conn, writeTimeout, false)
-- now write body
sendBuffer(bodyBuffer, conn, writeTimeout, false)
end
local function endStreaming(resp)
local conn = resp.luaw_conn
local writeTimeout = resp.writeTimeout
local bodyBuffer = resp.bodyParts
-- flush whatever is remaining in write buffer
sendBuffer(bodyBuffer, conn, writeTimeout, true)
-- add last chunk encoding trailer
conn:write("0\r\n\r\n", writeTimeout)
end
local function flush(resp)
if resp.luaw_is_chunked then
endStreaming(resp)
else
writeFullBody(resp)
end
end
local function close(req)
local conn = req.luaw_conn;
if conn then
conn:close()
req.luaw_conn = nil
end
end
local function reset(req)
req.headers = {}
req["_acc_header_name_"] = nil
req["_acc_header_value_"] = nil
req.url = nil
req.bodyParts:reset()
req.body = nil
req.luaw_mesg_done = nil
req.luaw_headers_done = nil
req.params = nil
req.parsedURL = nil
req.status = nil
req.statusMesg = nil
end
luaw_http_lib.newServerHttpRequest = function(conn)
local req = {
luaw_mesg_type = 'sreq',
luaw_conn = conn,
headers = {},
bodyParts = newBuffer(),
luaw_parser = luaw_http_lib:newHttpRequestParser(),
addHeader = addHeader,
shouldCloseConnection = shouldCloseConnection,
isComplete = isComplete,
readAndParse = readAndParse,
readFull = readFull,
isMultipart = isMultipart,
multiPartIterator = multiPartIterator,
getBody = getBody,
reset = reset,
consumeBodyChunkParsed = consumeBodyChunkParsed,
close = close
}
return req;
end
luaw_http_lib.newServerHttpResponse = function(conn)
local resp = {
luaw_mesg_type = 'sresp',
luaw_conn = conn,
major_version = 1,
minor_version = 1,
contentLength = 0,
headers = {},
bodyParts = newBuffer(),
addHeader = addHeader,
shouldCloseConnection = shouldCloseConnection,
setStatus = setStatus,
firstLine = firstResponseLine,
startStreaming = startStreaming,
appendBody = appendBody,
flush = flush,
reset = reset,
close = close
}
return resp;
end
local function newClientHttpResponse(conn)
local resp = {
luaw_mesg_type = 'cresp',
luaw_conn = conn,
headers = {},
bodyParts = newBuffer(),
luaw_parser = luaw_http_lib:newHttpResponseParser(),
addHeader = addHeader,
shouldCloseConnection = shouldCloseConnection,
readAndParse = readAndParse,
getBody = getBody,
getStatus = getStatus,
readFull = readFull,
consumeBodyChunkParsed = consumeBodyChunkParsed,
reset = reset,
close = close
}
return resp;
end
local function connect(req)
local conn = assert(luaw_tcp_lib.connect(req.hostIP, req.hostName, req.port, req.connectTimeout))
return conn
end
local function connectReq(req)
conn = connect(req)
conn:startReading()
req.luaw_conn = conn
local resp = newClientHttpResponse(conn)
resp.readTimeout = req.readTimeout
resp.writeTimeout = req.writeTimeout
return resp
end
local function execute(req)
local resp = req:connect()
req:flush()
resp:readFull()
if resp:shouldCloseConnection() then
resp:close()
end
return resp
end
luaw_http_lib.newClientHttpRequest = function()
local req = {
luaw_mesg_type = 'creq',
port = 80,
major_version = 1,
minor_version = 1,
method = 'GET',
contentLength = 0,
headers = {},
bodyParts = newBuffer(),
addHeader = addHeader,
connect = connectReq,
execute = execute,
shouldCloseConnection = shouldCloseConnection,
buildURL = buildURL,
firstLine = firstRequestLine,
startStreaming = startStreaming,
appendBody = appendBody,
flush = flush,
reset = reset,
close = close
}
return req;
end
return luaw_http_lib
================================================
FILE: lib/luaw_init.lua
================================================
--[[
Copyright (c) 2015 raksoras
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
luaw_utils = require("luaw_utils")
luaw_logging = require("luaw_logging")
luaw_lpack = require("luapack")
luaw_scheduler = require("luaw_scheduler")
luaw_tcp = require("luaw_tcp")
luaw_timer = require("luaw_timer")
luaw_http = require("luaw_http")
luaw_webapp = require("luaw_webapp")
luaw_webapp.init()
================================================
FILE: lib/luaw_logging.lua
================================================
--[[
Copyright (c) 2015 raksoras
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
local ds_lib = require('luaw_data_structs_lib')
local luaw_utils_lib = require("luaw_utils")
local luapack_lib = require('luapack')
local log_module = {}
local PATH_SEPARATOR = string.match (package.config, "[^\n]+")
-- Log file states
local LOG_NOT_OPEN = 0
local OPENING_LOG = 1
local LOG_IS_OPEN = 2
-- Log levels
local EMERGENCY = 0
local ALERT = 1
local CRITICAL = 2
local ERROR = 3
local WARNING = 4
local NOTICE = 5
local INFO = 6
local DEBUG = 7
log_module.EMERGENCY = EMERGENCY
log_module.ALERT = ALERT
log_module.CRITICAL = CRITICAL
log_module.ERROR = ERROR
log_module.WARNING = WARNING
log_module.NOTICE = NOTICE
log_module.INFO = INFO
log_module.DEBUG = DEBUG
-- Log appender types
local FILE_LOG = "FILE"
local SYS_LOG = "SYSLOG"
log_module.SYSLOG_FACILITY_USER = 1
log_module.SYSLOG_FACILITY_AUTH = 10
log_module.SYSLOG_FACILITY_AUDIT = 13
log_module.SYSLOG_FACILITY_ALERT = 14
log_module.SYSLOG_FACILITY_LOCAL0 = 16
log_module.SYSLOG_FACILITY_LOCAL1 = 17
log_module.SYSLOG_FACILITY_LOCAL2 = 18
log_module.SYSLOG_FACILITY_LOCAL3 = 19
log_module.SYSLOG_FACILITY_LOCAL4 = 20
log_module.SYSLOG_FACILITY_LOCAL5 = 21
log_module.SYSLOG_FACILITY_LOCAL6 = 22
log_module.SYSLOG_FACILITY_LOCAL7 = 23
local logRoot = { }
local logDir = assert(luaw_log_config.log_dir, "Invalid log directory specified")
local noOfLogLinesToBuffer = luaw_log_config.log_lines_buffer_count or 100
local logfileBaseName = luaw_log_config.log_file_basename or "luaw-log"
local logfileSizeLimit = luaw_log_config.log_file_size_limit or (1024 * 1024 * 10) -- 10MB
local logfileCountLimit = luaw_log_config.log_file_count_limit or 99
local logLineTimeFormat = luaw_log_config.log_line_timestamp_format or "%x %X"
local logFileNameTimeFormat = luaw_log_config.log_filename_timestamp_format or '%Y%m%d-%H%M%S'
local syslogTag = luaw_log_config.syslog_tag or 'luaw'
local syslogPresent = luaw_logging_lib.syslogConnect(luaw_log_config.syslog_server, luaw_log_config.syslog_port)
logRoot.facility = luaw_log_config.syslog_facility or log_module.SYSLOG_FACILITY_LOCAL7
local hostname = luaw_logging_lib.hostname()
local logSequenceNum = 0
local logSize = 0
local logBuffer = ds_lib.newOverwrittingRingBuffer(noOfLogLinesToBuffer + 32)
local noOfLogLinesDropped = 0
local currentTimeStr
local syslogTimeStr
log_module.updateCurrentTime = function(currentTime)
currentTimeStr = os.date(logLineTimeFormat, currentTime)
if syslogPresent then
syslogTimeStr = os.date("%b %d %X", currentTime)
end
end
local function nextLogSequenceNum()
if logSequenceNum > logfileCountLimit then logSequenceNum = 0 end
logSequenceNum = logSequenceNum + 1
return logSequenceNum
end
local function concatLogLines()
local temp = luapack_lib.createDict(logBuffer.filled+1, 0)
local i = 1
local logLine = logBuffer:take()
while logLine do
temp[i] = logLine
i = i+1
logLine = logBuffer:take()
end
temp[i] = '' -- for the last newline
return table.concat(temp, '\n')
end
local function logToFile(logLine)
local added = logBuffer:offer(currentTimeStr..' '..logLine)
if not added then noOfLogLinesDropped = noOfLogLinesDropped +1 end
local state = luaw_logging_lib.logState()
if ((state == LOG_IS_OPEN)and(logBuffer.filled >= noOfLogLinesToBuffer)) then
local logBatch = concatLogLines()
logSize = logSize + string.len(logBatch)
local rotateLog = (logSize >= logfileSizeLimit)
state = luaw_logging_lib.writeLog(logBatch, rotateLog)
end
if (state == LOG_NOT_OPEN) then
logSize = 0
local ts = os.date(logFileNameTimeFormat, os.time())
local fileName = logDir..PATH_SEPARATOR..logfileBaseName..'-'..ts..'-'..nextLogSequenceNum()..'.log'
luaw_logging_lib.openLog(fileName)
end
end
local function syslog(priority, facility, mesg)
local pri = priority + (facility * 8)
local logLine = string.format("<%d>%s %s %s: %s", pri, syslogTimeStr, hostname, syslogTag, mesg)
luaw_logging_lib.syslogSend(logLine);
end
local nameIterator = luaw_utils_lib.splitter('.')
local function splitName(name)
if not name then return luaw_utils_lib.nilFn end
return nameIterator, name, 0
end
local function logInternal(logLevel, fileLevel, syslogLevel, syslogFacility, mesg)
if (logLevel <= fileLevel) then
logToFile(mesg)
end
if ((syslogPresent)and(logLevel <= syslogLevel)) then
syslog(logLevel, syslogFacility, mesg)
end
end
local function log(logger, logLevel, mesg)
local fileLevel = logger[FILE_LOG] or ERROR
local syslogLevel = logger[SYS_LOG] or ERROR
logInternal(logLevel, fileLevel, syslogLevel, logger.facility, mesg)
end
local function logf(logger, logLevel, mesgFormat, ...)
local fileLevel = logger[FILE_LOG] or ERROR
local syslogLevel = logger[SYS_LOG] or ERROR
if ((logLevel <= fileLevel)or(logLevel <= syslogLevel)) then
local mesg = string.format(mesgFormat, ...)
logInternal(logLevel, fileLevel, syslogLevel, logger.facility, mesg)
end
end
logRoot.log = log
logRoot.logf = logf
logRoot.emergency = function(logger, mesg)
log(logger, EMERGENCY, mesg)
end
logRoot.alert = function(logger, mesg)
log(logger, ALERT, mesg)
end
logRoot.critical = function(logger, mesg)
log(logger, CRITICAL, mesg)
end
logRoot.error = function(logger, mesg)
log(logger, ERROR, mesg)
end
logRoot.warning = function(logger, mesg)
log(logger, WARNING, mesg)
end
logRoot.notice = function(logger, mesg)
log(logger, NOTICE, mesg)
end
logRoot.info = function(logger, mesg)
log(logger, INFO, mesg)
end
logRoot.debug = function(logger, mesg)
log(logger, DEBUG, mesg)
end
logRoot.emergencyf = function(logger, mesgFormat, ...)
logf(logger, EMERGENCY, mesgFormat, ...)
end
logRoot.alertf = function(logger, mesgFormat, ...)
logf(logger, ALERT, mesgFormat, ...)
end
logRoot.criticalf = function(logger, mesgFormat, ...)
logf(logger, CRITICAL, mesgFormat, ...)
end
logRoot.errorf = function(logger, mesgFormat, ...)
logf(logger, ERROR, mesgFormat, ...)
end
logRoot.warningf = function(logger, mesgFormat, ...)
logf(logger, WARNING, mesgFormat, ...)
end
logRoot.noticef = function(logger, mesgFormat, ...)
logf(logger, NOTICE, mesgFormat, ...)
end
logRoot.infof = function(logger, mesgFormat, ...)
logf(logger, INFO, mesgFormat, ...)
end
logRoot.debugf = function(logger, mesgFormat, ...)
logf(logger, DEBUG, mesgFormat, ...)
end
local function getLogger(name)
local logger = logRoot
if (name == 'root') then return logger end
for idx, namePart in splitName(name) do
local child = logger[namePart]
if not child then
child = {}
setmetatable(child, {__index = logger})
logger[namePart] = child
end
logger = child
end
return logger
end
log_module.getLogger = getLogger
local function configureLogger(logCfg, logType)
local loggerName = assert(logCfg.name, "Logger name missing")
local logLevel = assert(logCfg.level, "Logger level missing")
local logger = assert(getLogger(loggerName), "Could not find logger "..loggerName)
logger[logType] = logLevel
return logger
end
log_module.file = function(logCfg)
configureLogger(logCfg, FILE_LOG)
end
log_module.syslog = function(logCfg)
local logger = configureLogger(logCfg, SYS_LOG)
logger.facility = logCfg.facility
end
return log_module
================================================
FILE: lib/luaw_scheduler.lua
================================================
--[[
Copyright (c) 2015 raksoras
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
local constants = require('luaw_constants')
local ds_lib = require('luaw_data_structs_lib')
local logging = require('luaw_logging')
-- Scheduler object
local scheduler = {}
-- Constants
local TS_RUNNABLE = constants.RUNNABLE
local TS_DONE = constants.DONE
local TS_BLOCKED_EVENT = constants.BLOCKED_ON_EVENT
local TS_BLOCKED_THREAD = constants.BLOCKED_ON_THREAD
local END_OF_CALL = constants.END_OF_CALL
local END_OF_THREAD = constants.END_OF_THREAD
local UPDATE_TIME_COUNTER_LIMIT = 10
-- scheduler state
local threadRegistry = ds_lib.newRegistry(luaw_server_config.thread_pool_size or 1024)
local threadPool = ds_lib.newRingBuffer(luaw_server_config.thread_pool_size or 1024)
local timesReuseThread = luaw_server_config.thread_reuse_limit or 1024
local runQueueLen = 0
local runQueueHead = nil
local runQueueTail = nil
local currentRunningThreadCtx = nil
local updateTimeCyclingCounter = 0
local currentTime
scheduler.updateCurrentTime = function ()
updateTimeCyclingCounter = updateTimeCyclingCounter + 1
if (updateTimeCyclingCounter >= UPDATE_TIME_COUNTER_LIMIT) then
currentTime = os.time()
logging.updateCurrentTime(currentTime)
updateTimeCyclingCounter = 0
end
end
scheduler.time = function()
return currentTime
end
-- returns current running thread's id
scheduler.tid = function()
if currentRunningThreadCtx then
return currentRunningThreadCtx.tid
end
return nil
end
local function threadRunLoop(fn, arg1, arg2, arg3, arg4)
local i = 0
while i <= timesReuseThread do
fn, arg1, arg2, arg3, arg4 = coroutine.yield(END_OF_CALL, fn(arg1, arg2, arg3, arg4))
i = i+1
end
return END_OF_THREAD, fn(arg1, arg2, arg3, arg4)
end
local function userThreadRunner(userThreadFn, ...)
-- We have captured user thread function along with its arguments on a coroutine stack.
-- Yield now so that scheduler can add this thread in run queue for "bottom half"
-- processing later and original calling thread can resume.
coroutine.yield(TS_RUNNABLE)
-- At this point we have been resumed by thread scheduler during the "bottom half" run
-- queue processing bu the scheduler so run the actual user thread function.
return userThreadFn(...)
end
local function addToRunQueue(threadCtx)
if not runQueueTail then
runQueueHead = threadCtx
runQueueTail = threadCtx
else
runQueueTail.nextThread = threadCtx
runQueueTail = threadCtx
end
runQueueLen = runQueueLen + 1
threadCtx.state = TS_RUNNABLE
end
local function newThread()
local t = threadPool:take()
if not t then
t = coroutine.create(threadRunLoop)
end
local threadCtx = { thread = t, requestCtx = {} }
-- anchor thread in registry to prevent GC
local ref = threadRegistry:ref(threadCtx)
threadCtx.tid = ref
return threadCtx
end
local function unblockJoinedThreadIfAny(threadCtx, status, retVal)
local joinedTC = threadCtx.joinedBy
if joinedTC then
local count = joinedTC.joinCount
count = count -1
joinedTC.joinCount = count
if (count <= 0) then
addToRunQueue(joinedTC)
end
end
end
local function afterResume(threadCtx, state, retVal)
threadCtx.state, threadCtx.result = state, retVal
currentRunningThreadCtx = nil
if (state == TS_DONE) then
return true, retVal
end
return false, retVal
end
local function resumeThread(threadCtx, ...)
currentRunningThreadCtx = threadCtx
local t = threadCtx.thread
local tid = threadCtx.tid
scheduler.updateCurrentTime()
context = threadCtx.requestCtx -- TLS, per thread context
local status, state, retVal = coroutine.resume(t, ...)
context = nil -- reset TLS context
if not status then
-- thread ran into error
print("Error: "..tostring(state))
state = END_OF_THREAD
-- thread has blown its stack so let it get garbage collected
t = nil
end
if ((state == END_OF_THREAD)or(state == END_OF_CALL)) then
threadRegistry:unref(tid)
threadCtx.thread = nil
threadCtx.requestCtx = nil
if ((state == END_OF_CALL) and (t)) then
-- thread is still alive, return it to free pool if possible
threadPool:offer(t)
end
unblockJoinedThreadIfAny(threadCtx, status, retVal)
return afterResume(threadCtx, TS_DONE, retVal)
end
if ((state == TS_BLOCKED_EVENT)or(state == TS_BLOCKED_THREAD)) then
-- thread will later be resumed by libuv call back
return afterResume(threadCtx, state, retVal)
end
-- Thread yielded, but is still runnable. Add it back to the run queue
addToRunQueue(threadCtx)
return afterResume(threadCtx, TS_RUNNABLE, retVal)
end
function resumeThreadId(tid, ...)
local threadCtx = threadRegistry[tid]
if not threadCtx then error("Invalid thread Id "..tostring(tid)) end
return resumeThread(threadCtx, ...)
end
scheduler.resumeThreadId = resumeThreadId
function startSystemThread(serviceFn, conn, ...)
local threadCtx = newThread()
threadCtx.state = TS_RUNNABLE
local isDone = resumeThread(threadCtx, serviceFn, conn, ...)
return isDone, threadCtx.tid
end
scheduler.startSystemThread = startSystemThread
-- Scheduler object methods
scheduler.startUserThread = function(userThreadFn, ...)
local backgroundThreadCtx = newThread()
coroutine.resume(backgroundThreadCtx.thread, userThreadRunner, userThreadFn, ...)
addToRunQueue(backgroundThreadCtx)
return backgroundThreadCtx;
end
scheduler.join = function(...)
local joiningTC = currentRunningThreadCtx
if (joininTC) then
local joinedThreads = table.pack(...)
local numOfThreads = #joinedThreads
local count = 0
for i, joinedTC in ipairs(joinedThreads) do
if ((joinedTC)and(joinedTC.state)and(joinedTC.state ~= TS_DONE)) then
count = count + 1
joinedTC.joinedBy = joiningTC
end
end
joiningTC.joinCount = count
while (joiningTC.joinCount > 0) do
coroutine.yield(TS_BLOCKED_THREAD)
end
end
end
scheduler.runQueueSize = function()
return runQueueLen
end
local runNextFromRunQueue = function()
local threadCtx = runQueueHead
if threadCtx then
runQueueHead = threadCtx.nextThread
if not runQueueHead then
runQueueTail = nil
end
threadCtx.nextThread = nil
runQueueLen = runQueueLen -1
if (runQueueLen < 0) then
runQueueLen = 0
end
if (threadCtx.state == TS_DONE) then
-- This can happen when thread is added to the run queue but is woken up by libuv
-- event and then runs to completion before the run queue scheduler gets chance
-- to resume it
return
end
return resumeThread(threadCtx)
end
end
scheduler.runReadyThreads = function(limit)
local runnableCount = runQueueLen
if ((limit)and(limit < runnableCount)) then
runnableCount = limit
end
for i=1, runnableCount do
runNextFromRunQueue()
end
-- about to block on libuv event loop, next resumeThread should update current time
-- as it may have spent significant time blocked on a event loop.
updateTimeCyclingCounter = UPDATE_TIME_COUNTER_LIMIT
return runnableCount
end
scheduler.updateCurrentTime()
return scheduler
================================================
FILE: lib/luaw_tcp.lua
================================================
--[[
Copyright (c) 2015 raksoras
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
local constants = require('luaw_constants')
local scheduler = require('luaw_scheduler')
local DEFAULT_CONNECT_TIMEOUT = constants.DEFAULT_CONNECT_TIMEOUT
local DEFAULT_READ_TIMEOUT = constants.DEFAULT_READ_TIMEOUT
local DEFAULT_WRITE_TIMEOUT = constants.DEFAULT_WRITE_TIMEOUT
local CONN_BUFFER_SIZE = constants.CONN_BUFFER_SIZE
local conn = luaw_tcp_lib.newConnection();
local connMT = getmetatable(conn)
conn:close()
local startReadingInternal = connMT.startReading
local readInternal = connMT.read
local writeInternal = connMT.write
connMT.startReading = function(self)
local status, mesg = startReadingInternal(self)
assert(status, mesg)
end
connMT.read = function(self, readTimeout)
local status, str = readInternal(self, scheduler.tid(), readTimeout or DEFAULT_READ_TIMEOUT)
if ((status)and(not str)) then
-- nothing in buffer, wait for libuv on_read callback
status, str = coroutine.yield(TS_BLOCKED_EVENT)
end
return status, str
end
connMT.write = function(self, str, writeTimeout)
local status, nwritten = writeInternal(self, scheduler.tid(), str, writeTimeout or DEFAULT_WRITE_TIMEOUT)
if ((status)and(nwritten > 0)) then
-- there is something to write, yield for libuv callback
status, nwritten = coroutine.yield(TS_BLOCKED_EVENT)
end
assert(status, nwritten)
return nwritten
end
local connectInternal = luaw_tcp_lib.connect
local function connect(hostIP, hostName, port, connectTimeout)
assert((hostName or hostIP), "Either hostName or hostIP must be specified in request")
local threadId = scheduler.tid()
if not hostIP then
local status, mesg = luaw_tcp_lib.resolveDNS(hostName, threadId)
assert(status, mesg)
status, mesg = coroutine.yield(TS_BLOCKED_EVENT)
assert(status, mesg)
hostIP = mesg
end
local connectTimeout = connectTimeout or DEFAULT_CONNECT_TIMEOUT
local conn, mesg = connectInternal(hostIP, port, threadId, connectTimeout)
-- initial connect_req succeeded, block for libuv callback
assert(coroutine.yield(TS_BLOCKED_EVENT))
return conn, mesg
end
luaw_tcp_lib.connect = connect
return luaw_tcp_lib
================================================
FILE: lib/luaw_timer.lua
================================================
--[[
Copyright (c) 2015 raksoras
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
local constants = require('luaw_constants')
local TS_BLOCKED_EVENT = constants.TS_BLOCKED_EVENT
local timerMT = getmetatable(luaw_timer_lib.newTimer())
local waitInternal = timerMT.wait
timerMT.wait = function(timer)
local status, elapsed = waitInternal(timer, scheduler.tid())
if ((status) and (not elapsed)) then
-- timer not yet elapsed, wait for libuv on_timeout callback
status, elapsed = coroutine.yield(TS_BLOCKED_EVENT)
end
return status, elapsed
end
timerMT.sleep = function(timer, timeout)
assert(timer:start(timeout))
timer:wait()
end
return luaw_timer_lib
================================================
FILE: lib/luaw_utils.lua
================================================
--[[
Copyright (c) 2015 raksoras
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
local luaw_util_lib = {}
local function tprint(tbl, indent, tab)
for k, v in pairs(tbl) do
if type(v) == "table" then
print(string.rep(tab, indent) .. tostring(k) .. ": {")
tprint(v, indent+1, tab)
print(string.rep(tab, indent) .. "}")
else
print(string.rep(tab, indent) .. tostring(k) .. ": " .. tostring(v))
end
end
end
-- Print contents of `tbl`, with indentation.
-- `indent` sets the initial level of indentation.
luaw_util_lib.debugDump = function(tbl, indent, tab)
indent = indent or 0
tab = tab or " "
print(string.rep(tab, indent) .. "{")
tprint(tbl, indent+1, tab)
print(string.rep(tab, indent) .. "}")
end
luaw_util_lib.steplight = function(mesg)
local tid = tostring(Luaw.scheduler.tid())
print("Thread-"..tid.."> "..tostring(mesg))
end
luaw_util_lib.step = function(mesg, level)
local tid = tostring(Luaw.scheduler.tid())
local lvl = level or 2
if (lvl < 0) then lvl = lvl * -1 end
local dc = debug.getinfo(lvl, "nSl")
local str = ""
if type(mesg) == 'table' then
for k,v in pairs(mesg) do
str = str..", "..tostring(k).."="..tostring(v)
end
else
str = tostring(mesg)
end
print('Thread '..tid..'> line# '..tostring(dc.linedefined)..' in function '..tostring(dc.name)..' in file '..tostring(dc.source)..': '..str)
if ((level)and(level < 0)) then
print(debug.traceback())
end
end
luaw_util_lib.run = function(codeblock)
if (codeblock) then
local try = codeblock.try
if (try) then
local catch = codeblock.catch
local finally = codeblock.finally
local status, err = pcall(try, codeblock)
if ((not status)and(catch)) then
status, err = pcall(catch, codeblock, err)
end
if (finally) then
finally(codeblock)
end
if (not status) then
error(err)
end
end
end
end
luaw_util_lib.clearArrayPart = function(t)
local len = #t
for i=1,len do
t[i] = nil
end
end
luaw_util_lib.splitter = function(splitCh)
local separator = string.byte(splitCh, 1, 1)
local byte = string.byte
return function (str, pos)
pos = pos + 1
local start = pos
local len = #str
while pos <= len do
local ch = byte(str, pos, pos)
if (ch == separator) then
if (pos > start) then
return pos, string.sub(str, start, pos-1)
end
start = pos + 1
end
pos = pos + 1
end
if (pos > start) then return pos, string.sub(str, start, pos) end
end
end
luaw_util_lib.nilFn = function()
return nil
end
luaw_util_lib.formattedLine = function(str, lineSize, paddingCh, beginCh, endCh)
lineSize = lineSize or 0
paddingCh = paddingCh or ''
beginCh = beginCh or ''
endCh = endCh or ''
paddingWidth = (lineSize - #str -2)/2
local padding = ''
if paddingWidth > 0 then
padding = string.rep(paddingCh, paddingWidth)
end
print(string.format("%s %s %s %s %s", beginCh, padding, str, padding, endCh))
end
return luaw_util_lib
================================================
FILE: lib/luaw_webapp.lua
================================================
--[[
Copyright (c) 2015 raksoras
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
local luaw_utils_lib = require("luaw_utils")
local luaw_http_lib = require("luaw_http")
local HTTP_METHODS = {
GET = "GET",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE",
HEAD = "HEAD",
OPTIONS = "OPTIONS",
TRACE = "TRACE",
CONNECT = "CONNECT",
SERVICE = "SERVICE"
}
local registeredWebApps = {}
local DIR_SEPARATOR = string.match (package.config, "[^\n]+")
local STRING_PATH_PARAM = { start = string.byte(":"), valueOf = tostring }
local NUM_PATH_PARAM = { start = string.byte("#"), valueOf = tonumber }
TAB = ' '
local function findFiles(path, pattern, matches)
if (path and pattern) then
for file in lfs.dir(path) do
if (file ~= '.' and file ~= '..') then
local f = path..DIR_SEPARATOR..file
local attrs = lfs.attributes(f)
if attrs then
local mode = attrs.mode
if mode == 'file' then
if (string.match(f, pattern)) then
table.insert(matches, f)
end
elseif mode == 'directory' then
findFiles(f, pattern, matches)
end
end
end
end
end
return matches
end
local pathIterator = luaw_utils_lib.splitter('/')
local function splitPath(path)
if not path then return luaw_util_lib.nilFn end
return pathIterator, path, 0
end
local function findAction(method, path)
assert(method, "HTTP method may not be nil")
assert(method, "HTTP request path may not be nil")
local webApp = nil
local route = nil
local pathParams = {}
for idx, pseg in splitPath(path) do
-- first iteration only
if not webApp then
webApp = registeredWebApps[pseg]
if not webApp then
-- fallback to root path
webApp = registeredWebApps['/']
end
if not webApp then
return nil
end
route = webApp.root
else
local nextRoute = route.childRoutes[pseg]
if not nextRoute then
-- may be it's a path param
nextRoute = route.childRoutes["_path_param_"]
if nextRoute then
pathParams[nextRoute.pathParam] = nextRoute.pathParamType.valueOf(pseg)
end
end
if not nextRoute then return nil end
route = nextRoute
end
end
if (route) then
local action = route[method]
if not action then
-- try catch call action as a fallback
action = route['SERVICE']
end
return webApp, action, pathParams
end
end
local function renderView(req, resp, pathParams, model, view)
local isTagOpen = false
local indent = 0
local attributes = function(attrs)
if attrs then
for k,v in pairs(attrs) do
resp:appendBody(' ')
resp:appendBody(k)
resp:appendBody('="')
resp:appendBody(tostring(v))
resp:appendBody('"')
end
end
end
local BEGIN = function(tag)
if (isTagOpen) then
resp:appendBody('>\n')
end
for i=1, indent do
resp:appendBody(TAB)
end
resp:appendBody('<')
resp:appendBody(tag)
isTagOpen = true;
indent = indent+1
return attributes
end
local END = function(tag)
if (isTagOpen) then
resp:appendBody('>\n')
isTagOpen = false;
end
indent = indent - 1
if indent > 0 then
indent = indent
else
indent = 0
end
for i=1, indent do
resp:appendBody(TAB)
end
resp:appendBody('</')
resp:appendBody(tag)
resp:appendBody('>\n')
end
local TEXT = function(...)
if (isTagOpen) then
resp:appendBody('>\n')
isTagOpen = false;
end
for i=1, indent do
resp:appendBody(TAB)
end
local values = {...}
for i,v in ipairs(values) do
resp:appendBody(tostring(v))
resp:appendBody(' ')
end
resp:appendBody('\n')
end
-- render view
if ((req.major_version >= 1)and(req.minor_version >= 1)) then
resp:startStreaming()
end
view(req, resp, pathParams, model, BEGIN, TEXT, END)
end
local function dispatchAction(req, resp)
assert(req, "HTTP request may not be nil")
local parsedURL = req.parsedURL
if not(req.method and parsedURL) then
-- EOF in case of persistent connections
return
end
local webApp, action, pathParams = findAction(req.method, parsedURL.path)
assert(action, "No action found for path "..parsedURL.path.." for method "..req.method)
if req:shouldCloseConnection() then
resp.headers['Connection'] = 'close'
else
resp.headers['Connection'] = 'Keep-Alive'
end
v1, v2 = action.handler(req, resp, pathParams)
-- handle action returned response, if any and if resp is not closed
if v1 and resp.luaw_conn then
if (type(v1) == 'number') then
-- v1 is HTTP status
resp:setStatus(v1)
--resp:startStreaming()
if v2 then
-- v2 is body content
resp:appendBody(tostring(v2))
end
else
if not resp.statusCode then
resp:setStatus(200)
end
--resp:startStreaming()
if ((type(v1) == 'string')and(v2)) then
-- v1 is view path, v2 is view model
local compiledView = webApp.compiledViews[v1]
if not compiledView then
error("View '"..tostring(v1).."' is not defined")
end
renderView(req, resp, pathParams, v2, compiledView)
else
-- v1 is the body content itself
resp:appendBody(tostring(v1))
end
end
end
-- flush in case resp is not closed
if resp.luaw_conn then resp:flush() end
end
local function registerResource(resource)
local route = assert(webapp.root, "webapp root not defined")
local path = assert(resource.path , "Handler definition is missing value for 'path'")
local handlerFn = assert(resource.handler, "Handler definition is missing 'handler' function")
local method = resource.method or 'SERVICE'
if(not HTTP_METHODS[method]) then
error(method.." is not a valid HTTP method")
end
for idx, pseg in splitPath(path) do
local firstChar = string.byte(pseg)
local pathParam = nil
if ((firstChar == STRING_PATH_PARAM.start)or(firstChar == NUM_PATH_PARAM.start)) then
pathParam = pseg:sub(2)
pseg = "_path_param_"
end
local nextRoute = route.childRoutes[pseg]
if not nextRoute then
nextRoute = { childRoutes = {} }
if pathParam then
nextRoute.pathParam = pathParam
if (firstChar == NUM_PATH_PARAM.start) then
nextRoute.pathParamType = NUM_PATH_PARAM
else
nextRoute.pathParamType = STRING_PATH_PARAM
end
end
route.childRoutes[pseg] = nextRoute
end
route = nextRoute
end
assert(route, "Could not register handler for path "..path)
assert((not route[method]), 'Handler already registered for '..method..' for path "/'..webapp.path..'/'..path..'"')
route[method] = {handler = handlerFn}
end
local function serviceHTTP(conn)
conn:startReading()
-- loop to support HTTP 1.1 persistent (keep-alive) connections
while true do
local req = luaw_http_lib.newServerHttpRequest(conn)
-- read and parse full request
local status, errmesg = pcall(req.readFull, req)
if ((not status)or(req.EOF == true)) then
conn:close()
if (status) then
return "read time out"
end
print("Error: ", errmesg, debug.traceback())
return "connection reset by peer"
end
local resp = luaw_http_lib.newServerHttpResponse(conn)
local status, errMesg = pcall(dispatchAction, req, resp)
if (not status) then
-- send HTTP error response
resp:setStatus(500)
resp:addHeader('Connection', 'close')
pcall(resp.appendBody, resp, errMesg)
pcall(resp.flush, resp)
conn:close()
error(errMesg)
end
if (req:shouldCloseConnection() or resp:shouldCloseConnection()) then
conn:close()
return "connection reset by peer"
end
end
end
local function toFullPath(appRoot, files)
local fullPaths = {}
if files then
for i, file in ipairs(files) do
table.insert(fullPaths, appRoot..'/'..file)
end
end
return fullPaths
end
local function loadWebApp(appName, appDir)
local app = {}
app.path = assert(appName, "Missing mandatory configuration property 'path'")
app.appRoot = assert(appDir, "Missing mandatory configuration property 'root_dir'")
if (registeredWebApps[app.path]) then
error('Anothe web app is already registered for path '..app.path)
end
registeredWebApps[app.path] = app
app.root = { childRoutes = {} }
-- Load resource handlers
local resources = toFullPath(app.appRoot, luaw_webapp.resources)
if luaw_webapp.resourcePattern then
resources = findFiles(app.appRoot, luaw_webapp.resourcePattern, resources)
end
assert((resources and (#resources > 0)), "Either 'resources' or 'resourcePattern' must be specified in a web app configuration")
app.resources = resources
-- Load view files if any
local views = toFullPath(app.appRoot, luaw_webapp.views)
if luaw_webapp.viewPattern then
views = findFiles(app.appRoot, luaw_webapp.viewPattern, views)
end
app.views = views
return app
end
local function loadView(viewPath, buff)
local firstLine = true
local lastLine = false
local viewLines = io.lines(viewPath)
return function()
local line = nil
if firstLine then
firstLine = false
line = "return function(req, resp, pathParams, model, BEGIN, TEXT, END)"
else
if (not lastLine) then
local vl = viewLines()
if (not vl) then
lastLine = true
line = "end"
else
line = vl
end
end
end
if line then
table.insert(buff, line)
return (line .. '\n')
end
end
end
local function startWebApp(app)
-- register resources
local resources = app.resources
for i,resource in ipairs(resources) do
luaw_utils_lib.formattedLine(".Loading resource "..resource)
-- declare globals (registerHandler and webapp) for the duration of the loadfile(resource)
registerHandler = registerResource
webapp = app
local routeDefn = assert(loadfile(resource), string.format("Could not load resource %s", resource))
routeDefn()
end
luaw_utils_lib.formattedLine("#Loaded total "..#resources.." resources\n")
-- compile views
local views = app.views
local compiledViews = {}
local appRootLen = string.len(app.appRoot) + 1
for i,view in ipairs(views) do
local relativeViewPath = string.sub(view, appRootLen)
luaw_utils_lib.formattedLine(".Loading view "..relativeViewPath)
local viewBuff = {}
local viewDefn = loadView(view, viewBuff)
local compiledView, errMesg = load(viewDefn, relativeViewPath)
if (not compiledView) then
luaw_utils_lib.formattedLine("\nError while compiling view: "..view)
luaw_utils_lib.formattedLine("<SOURCE>")
for i, line in ipairs(viewBuff) do
print(tostring(i)..":\t"..tostring(line))
end
luaw_utils_lib.formattedLine("<SOURCE>")
error(errMesg)
end
compiledViews[relativeViewPath] = compiledView()
end
app.views = nil
app.compiledViews = compiledViews
luaw_utils_lib.formattedLine("#Compiled total "..#views.." views")
end
local function init()
if ((luaw_webapp_config)and(luaw_webapp_config.base_dir)) then
local root = luaw_webapp_config.base_dir
for webappName in lfs.dir(root) do
if (webappName ~= '.' and webappName ~= '..') then
local webappDir = root..DIR_SEPARATOR..webappName
local attrs = lfs.attributes(webappDir)
if ((attrs)and(attrs.mode == 'directory')) then
local webappCfgFile = webappDir..DIR_SEPARATOR..'web.lua'
if (lfs.attributes(webappCfgFile, 'mode') == 'file') then
luaw_utils_lib.formattedLine('Starting webapp '..webappName, 120, '*', '\n')
dofile(webappCfgFile) -- defines global variable luaw_webapp
local app = loadWebApp(webappName, webappDir)
startWebApp(app)
luaw_utils_lib.formattedLine('Webapp '..webappName..' started', 120, '*')
webapp = nil -- reset global variable
end
end
end
end
end
end
-- install REST HTTP app handler as a default request handler
luaw_http_lib.request_handler = serviceHTTP
return {
init = init,
dispatchAction = dispatchAction,
serviceHTTP = serviceHTTP
}
================================================
FILE: lib/unit_testing.lua
================================================
--[[
Copyright (c) 2015 raksoras
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
local module = {total_run = 0, total_failed = 0}
function module.assertTrue(expr)
if not expr then
error("Assert true failed!", 2)
end
end
function module.assertFalse(expr)
if expr then
error("Assert false failed!", 2)
end
end
function module.assertNotNil(expr)
if not expr then
error("Assert not nil failed!", 2)
end
end
function module.assertNil(expr)
if expr then
error("Assert nil failed!", 2)
end
end
function module.assertEqual(actual, expected)
if (actual ~= expected) then
error(string.format("Assert equal failed! Actual: [%s], Expected: [%s]", actual, expected), 2)
end
end
function module.assertNotEqual(actual, expected)
if (actual == expected) then
error(string.format("Assert not equal failed! Actual: [%s], Expected: [%s]", actual, expected), 2)
end
end
local function tprint(tbl, indent, tab)
for k, v in pairs(tbl) do
if type(v) == "table" then
print(string.rep(tab, indent) .. tostring(k) .. ": {")
tprint(v, indent+1, tab)
print(string.rep(tab, indent) .. "}")
else
print(string.rep(tab, indent) .. tostring(k) .. ": " .. tostring(v))
end
end
end
-- Print contents of `tbl`, with indentation.
-- `indent` sets the initial level of indentation.
function module.printTable (tbl, indent, tab)
indent = indent or 0
tab = tab or " "
print(string.rep(tab, indent) .. "{")
tprint(tbl, indent+1, tab)
print(string.rep(tab, indent) .. "}")
end
function module:runTests()
number_run = 0
number_failed = 0
for name, func in pairs(self) do
if (string.find(name, "test") == 1) then
result, mesg = pcall(func)
number_run = number_run + 1
if (string.find(name, "testError") == 1) then
-- negative test
if not result then
print(string.format(" %-40s [OK]", name));
else
number_failed = number_failed + 1
print(string.format(" %-30s [FAILED! Expected to throw error]", name));
end
else
if result then
print(string.format(" %-40s [OK]", name));
else
number_failed = number_failed + 1
print(string.format(" %-30s [FAILED! %s]", name, mesg));
end
end
self[name] = nil
end
end
self.total_run = self.total_run + number_run
self.total_failed = self.total_failed + number_failed
local info = debug.getinfo(2, "S")
print('----------------------------------------------------------------------------')
print(string.format("%s: Total# %d, Failed# %d", info.source, number_run, number_failed));
print('----------------------------------------------------------------------------')
end
function printOverallSummary()
print('\n*****************************************************************************')
print(string.format("Overall Summary: Total# %d, Failed# %d", module.total_run, module.total_failed));
print('*****************************************************************************\n')
end
return module;
================================================
FILE: sample/conf/server.cfg
================================================
luaw_server_config = {
server_ip = "0.0.0.0",
server_port = 7001,
connect_timeout = 4000,
read_timeout = 8000,
write_timeout = 8000
}
luaw_log_config = {
log_dir = "./logs",
log_file_basename = "luaw-log",
log_file_size_limit = 1024*1024,
log_file_count_limit = 9,
log_filename_timestamp_format = '%Y%m%d',
log_lines_buffer_count = 16,
syslog_server = "127.0.0.1",
syslog_port = 514,
}
luaw_webapp_config = {
base_dir = "./webapps"
}
================================================
FILE: sample/proxy_handler.lua
================================================
local http_lib = require('luaw_http')
--[[
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 passed to REST handler.
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 shared state inside the function.
]]
http_lib.request_handler = function(conn)
conn:startReading()
-- loop to support HTTP 1.1 persistent (keep-alive) connections
while true do
local req = http_lib.newServerHttpRequest(conn)
local resp = http_lib.newServerHttpResponse(conn)
-- read and parse full request
req:readFull()
if (req.EOF) then
conn:close()
return "connection reset by peer"
end
local reqHeaders = req.headers
local beHost = reqHeaders['backend-host']
local beURL = reqHeaders['backend-url']
if (beHost and beURL) then
local backendReq = http_lib.newClientHttpRequest()
backendReq.hostName = beHost
backendReq.url = beURL
backendReq.method = 'GET'
backendReq.headers = { Host = beHost }
local status, backendResp = pcall(backendReq.execute, backendReq)
if (status) then
resp:setStatus(backendResp:getStatus())
resp:appendBody(backendResp:getBody())
local beHeaders = backendResp.headers
for k,v in pairs(beHeaders) do
if ((k ~= 'Transfer-Encoding')and(k ~= 'Content-Length')) then
resp:addHeader(k,v)
end
end
backendResp:close()
else
resp:setStatus(500)
resp:appendBody("connection to backend server failed")
end
else
resp:setStatus(400)
resp:appendBody("Request must contain headers backend-host and backend-url\n")
end
local status, mesg = pcall(resp.flush, resp)
if (not status) then
conn:close()
return error(mesg)
end
if (req:shouldCloseConnection() or resp:shouldCloseConnection()) then
conn:close()
return "connection reset by peer"
end
end
end
================================================
FILE: sample/webapps/myapp/handlers/handler-address.lua
================================================
registerHandler {
method = 'GET',
path = 'address/:city/#zip',
handler = function(req, resp, pathParams)
address = {
city = pathParams.city,
zip = pathParams.zip
}
return '/views/view-address.lua', address
end
}
================================================
FILE: sample/webapps/myapp/handlers/handler-fileupload-display.lua
================================================
registerHandler {
method = 'GET',
path = 'showform',
handler = function(req, resp, pathParams)
return [[
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>upload</title>
</head>
<body>
<form action="/myapp/filesupload" method="post" enctype="multipart/form-data">
<p><input type="text" name="text1" value="text default">
<p><input type="text" name="text2" value="ABCD">
<p><input type="file" name="file1">
<p><input type="file" name="file2">
<p><button type="submit">Submit</button>
</form>
</body>
</html>
]]
end
}
================================================
FILE: sample/webapps/myapp/handlers/handler-fileupload-process.lua
================================================
local function append(buffer, str)
if (str) then
table.insert(buffer, str)
table.insert(buffer, ", ")
end
end
registerHandler {
method = 'POST',
path = 'filesupload',
handler = function(req, resp, pathParams)
if (req:isMultipart()) then
local token, fieldName, fileName, contentType
local buffer = {}
for token, fieldName, fileName, contentType in req:multiPartIterator() do
append(buffer, tostring(token))
append(buffer, fieldName)
append(buffer, fileName)
append(buffer, contentType)
table.insert(buffer,"\n")
end
return table.concat(buffer)
end
return "Not a multi-part file upload"
end
}
================================================
FILE: sample/webapps/myapp/handlers/handler-hellouser.lua
================================================
registerHandler {
method = 'GET',
path = '/user/:username/#count',
handler = function(req, resp, pathParams)
return "Hello "..pathParams.username.."! You are user number "..pathParams.count.." to visit this site."
end
}
================================================
FILE: sample/webapps/myapp/handlers/handler-helloworld.lua
================================================
registerHandler {
method = 'GET',
path = 'helloworld',
handler = function(req, resp, pathParams)
return "Hello World!"
end
}
================================================
FILE: sample/webapps/myapp/handlers/handler-read-lpack.lua
================================================
local utils_lib = require('luaw_utils')
local http_lib = require('luaw_http')
local lpack = require('luapack')
registerHandler {
method = 'GET',
path = 'readlpack',
handler = function(req, resp, pathParams)
local backendReq = http_lib.newClientHttpRequest()
backendReq.hostName = 'localhost'
backendReq.port = 7001
backendReq.url = '/myapp/genlpack'
backendReq.method = 'GET'
backendReq.headers = { Host = 'localhost' }
local backendResp = backendReq:execute()
local lpackReader = lpack.newLPackReqReader(backendResp)
local mesg = lpackReader:read()
print('\n================================\n')
utils_lib.debugDump(backendResp.headers)
print('\n================================\n')
utils_lib.debugDump(mesg)
print('\n================================\n')
return "OK"
end
}
================================================
FILE: sample/webapps/myapp/handlers/handler-write-lpack.lua
================================================
local utils_lib = require('luaw_utils')
local http_lib = require('luaw_http')
local lpack = require('luapack')
local big_str = "abcdefghijklmnopqrstuvwxyz0123456789"
while (#big_str < 4096) do
big_str = big_str ..'-'..big_str
end
local mesg = {
name = "Homer Simpson",
gender = "M",
uint8 = 255,
uint16 = 256,
uint32 = 4294967295,
int8_neg = -128,
int8 = 127,
int16_neg = -1000,
int16 = 20000,
int32 = 32456,
int64= 17179869184,
int64_neg= -17179869184,
float = 0.0012,
float_neg = - 112.8,
double = 8589934592.13,
double_neg = -8589934592.28,
str = "ABCD",
bigstr = big_str,
positive = true,
negative = false,
kids = {
{
name = "Lisa",
gender = "F",
uint8 = 255,
uint16 = 256,
uint32 = 4294967295,
int8_neg = -128,
int8 = 127,
int16_neg = -1000,
int16 = 20000,
int32 = 32456,
int64= 17179869184,
int64_neg= -17179869184,
float = 0.0012,
float_neg = - 112.8,
double = 8589934592.13,
double_neg = -8589934592.28,
str = "ABCD",
bigstr = big_str,
positive = true,
negative = false
},
{
name = "Bart",
gender = "M",
uint8 = 255,
uint16 = 256,
uint32 = 4294967295,
int8_neg = -128,
int8 = 127,
int16_neg = -1000,
int16 = 20000,
int32 = 32456,
int64= 17179869184,
int64_neg= -17179869184,
float = 0.0012,
float_neg = - 112.8,
double = 8589934592.13,
double_neg = -8589934592.28,
str = "ABCD",
bigstr = big_str,
positive = true,
negative = false
},
{
name = "Maggy",
gender = "?"
}
}
}
local dict = {
"name",
"gender",
"uint8",
"uint16",
"uint32",
"int8_neg",
"int8",
"int16_neg",
"int16",
"int32",
"int64",
"int64_neg",
"float",
"float_neg",
"double",
"double_neg",
"str",
"bigstr",
"positive",
"negative",
big_str
}
registerHandler {
method = 'GET',
path = 'genlpack',
handler = function(req, resp, pathParams)
resp:setStatus(200)
local lpackWriter = lpack.newLPackRespWriter(resp)
if (req.params['dict'] == 'true') then
lpackWriter:useDictionary(dict)
end
lpackWriter:write(mesg)
end
}
================================================
FILE: sample/webapps/myapp/views/view-address.lua
================================================
BEGIN 'html'
BEGIN 'head'
BEGIN 'title'
TEXT 'Address'
END 'title'
END 'head'
BEGIN 'body'
BEGIN 'div' {class='address'}
BEGIN 'h1'
TEXT(model.title)
END 'h1'
BEGIN 'table' {border="1", margin="1px"}
BEGIN 'tr'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT 'City'
END 'td'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT(model.city)
END 'td'
END 'tr'
if (model.zip == 94086) then
BEGIN 'tr'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT 'County'
END 'td'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT 'Santa Clara'
END 'td'
END 'tr'
end
BEGIN 'tr'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT 'Zip'
END 'td'
BEGIN 'td' {style="padding: 3px 3px 3px 3px"}
TEXT(model.zip)
END 'td'
END 'tr'
END 'table'
END 'div'
END 'body'
END 'html'
================================================
FILE: sample/webapps/myapp/web.lua
================================================
luaw_webapp = {
resourcePattern = "handler%-.*%.lua",
viewPattern = "view%-.*%.lua",
}
================================================
FILE: src/Makefile
================================================
# Makefile for building Luaw
# == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT =======================
CC?= gcc
CFLAGS?= -O2 -g -Wall
CFLAGS+= $(SYSCFLAGS) $(MYCFLAGS) -I../$(UVDIR)/include -I../$(LUADIR)/src
LDFLAGS= $(SYSLDFLAGS) $(MYLDFLAGS)
LIBS= ../$(UVLIB) -lpthread ../$(LUALIB) -lm $(SYSLIBS) $(MYLIBS)
# == END OF USER SETTINGS -- NO NEED TO CHANGE ANYTHING BELOW THIS LINE =======
# Build artifacts
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
LUAW_BIN= luaw_server
LUAW_CONF= server.cfg
LUAW_SCRIPTS= luapack.lua luaw_init.lua luaw_logging.lua luaw_data_structs_lib.lua luaw_utils.lua \
luaw_scheduler.lua luaw_webapp.lua luaw_timer.lua luaw_tcp.lua luaw_http.lua luaw_constants.lua
# How to install. If your install program does not support "-p", then
# you may have to run ranlib on the installed liblua.a.
INSTALL= install
INSTALL_EXEC= $(INSTALL) -p -m 0755
INSTALL_DATA= $(INSTALL) -p -m 0644
#
# If you don't have "install" you can use "cp" instead.
# INSTALL= cp -p
# INSTALL_EXEC= $(INSTALL)
# INSTALL_DATA= $(INSTALL)
# Other utilities.
MKDIR= mkdir -p
RM= rm -f
RMDIR= rm -rf
#where to install
INSTALL_BIN=$(INSTALL_ROOT)/bin
INSTALL_LIB=$(INSTALL_ROOT)/lib
INSTALL_CONF=$(INSTALL_ROOT)/conf
INSTALL_LOGS=$(INSTALL_ROOT)/logs
INSTALL_WEBAPP=$(INSTALL_ROOT)/webapps
# Targets start here.
all: $(LUAW_BIN)
$(LUAW_BIN): $(LUAW_OBJS)
$(CC) -o $@ $(LDFLAGS) $(LUAW_OBJS) $(LIBS)
install: check_install_root
$(MKDIR) $(INSTALL_BIN)
$(MKDIR) $(INSTALL_LIB)
$(MKDIR) $(INSTALL_CONF)
$(MKDIR) $(INSTALL_LOGS)
$(MKDIR) $(INSTALL_WEBAPP)
$(INSTALL_EXEC) $(LUAW_BIN) $(INSTALL_BIN)
cd ../lib && $(INSTALL_DATA) $(LUAW_SCRIPTS) $(INSTALL_BIN)
cd ../conf && $(INSTALL_DATA) $(LUAW_CONF) $(INSTALL_CONF)
install-sample: install
cd ../sample && cp -r * $(INSTALL_ROOT)
uninstall: check_install_root
$(RMDIR) $(INSTALL_ROOT)/*
check_install_root:
ifndef INSTALL_ROOT
$(error INSTALL_ROOT is undefined)
endif
clean:
$(RM) $(LUAW_BIN) $(LUAW_OBJS)
echo:
@echo "CC= $(CC)"
@echo "CFLAGS= $(CFLAGS)"
@echo "LDFLAGS= $(SYSLDFLAGS)"
@echo "LIBS= $(LIBS)"
@echo "RM= $(RM)"
# list targets that do not create files (but not all makes understand .PHONY)
.PHONY: default install install-sample uninstall check_install_root clean echo
# Luaw object files
http_parser.o: http_parser.c http_parser.h
lua_lpack.o: lua_lpack.c lua_lpack.h luaw_common.h
luaw_logging.o: luaw_logging.c luaw_logging.h luaw_common.h
luaw_common.o: luaw_common.c luaw_common.h luaw_tcp.h luaw_http_parser.h luaw_timer.h lua_lpack.h
luaw_http_parser.o: luaw_http_parser.c luaw_http_parser.h luaw_common.h luaw_tcp.h lfs.h
luaw_server.o: luaw_server.c luaw_common.h luaw_tcp.h luaw_logging.h
luaw_tcp.o: luaw_tcp.c luaw_tcp.h luaw_common.h http_parser.h luaw_http_parser.h luaw_tcp.h
luaw_timer.o: luaw_timer.c luaw_timer.h luaw_common.h
lfs.o: lfs.c lfs.h
================================================
FILE: src/http_parser.c
================================================
/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev
*
* Additional changes are licensed under the same terms as NGINX and
* copyright Joyent, Inc. and other Node contributors. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "http_parser.h"
#include <assert.h>
#include <stddef.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#ifndef ULLONG_MAX
# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */
#endif
#ifndef MIN
# define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif
#ifndef ARRAY_SIZE
# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#endif
#ifndef BIT_AT
# define BIT_AT(a, i) \
(!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \
(1 << ((unsigned int) (i) & 7))))
#endif
#ifndef ELEM_AT
# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v))
#endif
#define SET_ERRNO(e) \
do { \
parser->http_errno = (e); \
} while(0)
/* Run the notify callback FOR, returning ER if it fails */
#define CALLBACK_NOTIFY_(FOR, ER) \
do { \
assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \
\
if (settings->on_##FOR) { \
if (0 != settings->on_##FOR(parser)) { \
SET_ERRNO(HPE_CB_##FOR); \
} \
\
/* We either errored above or got paused; get out */ \
if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \
return (ER); \
} \
} \
} while (0)
/* Run the notify callback FOR and consume the current byte */
#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1)
/* Run the notify callback FOR and don't consume the current byte */
#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data)
/* Run data callback FOR with LEN bytes, returning ER if it fails */
#define CALLBACK_DATA_(FOR, LEN, ER) \
do { \
assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \
\
if (FOR##_mark) { \
if (settings->on_##FOR) { \
if (0 != settings->on_##FOR(parser, FOR##_mark, (LEN))) { \
SET_ERRNO(HPE_CB_##FOR); \
} \
\
/* We either errored above or got paused; get out */ \
if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \
return (ER); \
} \
} \
FOR##_mark = NULL; \
} \
} while (0)
/* Run the data callback FOR and consume the current byte */
#define CALLBACK_DATA(FOR) \
CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1)
/* Run the data callback FOR and don't consume the current byte */
#define CALLBACK_DATA_NOADVANCE(FOR) \
CALLBACK_DATA_(FOR, p - FOR##_mark, p - data)
/* Set the mark FOR; non-destructive if mark is already set */
#define MARK(FOR) \
do { \
if (!FOR##_mark) { \
FOR##_mark = p; \
} \
} while (0)
#define PROXY_CONNECTION "proxy-connection"
#define CONNECTION "connection"
#define CONTENT_LENGTH "content-length"
#define TRANSFER_ENCODING "transfer-encoding"
#define UPGRADE "upgrade"
#define CHUNKED "chunked"
#define KEEP_ALIVE "keep-alive"
#define CLOSE "close"
static const char *method_strings[] =
{
#define XX(num, name, string) #string,
HTTP_METHOD_MAP(XX)
#undef XX
};
/* Tokens as defined by rfc 2616. Also lowercases them.
* token = 1*<any CHAR except CTLs or separators>
* separators = "(" | ")" | "<" | ">" | "@"
* | "," | ";" | ":" | "\" | <">
* | "/" | "[" | "]" | "?" | "="
* | "{" | "}" | SP | HT
*/
static const char tokens[256] = {
/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
0, 0, 0, 0, 0, 0, 0, 0,
/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
0, 0, 0, 0, 0, 0, 0, 0,
/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
0, 0, 0, 0, 0, 0, 0, 0,
/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
0, 0, 0, 0, 0, 0, 0, 0,
/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
0, '!', 0, '#', '$', '%', '&', '\'',
/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
0, 0, '*', '+', 0, '-', '.', 0,
/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
'0', '1', '2', '3', '4', '5', '6', '7',
/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
'8', '9', 0, 0, 0, 0, 0, 0,
/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
0, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
'x', 'y', 'z', 0, 0, 0, '^', '_',
/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
'x', 'y', 'z', 0, '|', 0, '~', 0 };
static const int8_t unhex[256] =
{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1
,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
};
#if HTTP_PARSER_STRICT
# define T(v) 0
#else
# define T(v) v
#endif
static const uint8_t normal_url_char[32] = {
/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0,
/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
0 | 2 | 4 | 0 | 16 | 32 | 64 | 128,
/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 0,
/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, };
#undef T
enum state
{ s_dead = 1 /* important that this is > 0 */
, s_start_req_or_res
, s_res_or_resp_H
, s_start_res
, s_res_H
, s_res_HT
, s_res_HTT
, s_res_HTTP
, s_res_first_http_major
, s_res_http_major
, s_res_first_http_minor
, s_res_http_minor
, s_res_first_status_code
, s_res_status_code
, s_res_status_start
, s_res_status
, s_res_line_almost_done
, s_start_req
, s_req_method
, s_req_spaces_before_url
, s_req_schema
, s_req_schema_slash
, s_req_schema_slash_slash
, s_req_server_start
, s_req_server
, s_req_server_with_at
, s_req_path
, s_req_query_string_start
, s_req_query_string
, s_req_fragment_start
, s_req_fragment
, s_req_http_start
, s_req_http_H
, s_req_http_HT
, s_req_http_HTT
, s_req_http_HTTP
, s_req_first_http_major
, s_req_http_major
, s_req_first_http_minor
, s_req_http_minor
, s_req_line_almost_done
, s_header_field_start
, s_header_field
, s_header_value_start
, s_header_value
, s_header_value_lws
, s_header_almost_done
, s_chunk_size_start
, s_chunk_size
, s_chunk_parameters
, s_chunk_size_almost_done
, s_headers_almost_done
, s_headers_done
/* Important: 's_headers_done' must be the last 'header' state. All
* states beyond this must be 'body' states. It is used for overflow
* checking. See the PARSING_HEADER() macro.
*/
, s_chunk_data
, s_chunk_data_almost_done
, s_chunk_data_done
, s_body_identity
, s_body_identity_eof
, s_message_done
};
#define PARSING_HEADER(state) (state <= s_headers_done)
enum header_states
{ h_general = 0
, h_C
, h_CO
, h_CON
, h_matching_connection
, h_matching_proxy_connection
, h_matching_content_length
, h_matching_transfer_encoding
, h_matching_upgrade
, h_connection
, h_content_length
, h_transfer_encoding
, h_upgrade
, h_matching_transfer_encoding_chunked
, h_matching_connection_keep_alive
, h_matching_connection_close
, h_transfer_encoding_chunked
, h_connection_keep_alive
, h_connection_close
};
enum http_host_state
{
s_http_host_dead = 1
, s_http_userinfo_start
, s_http_userinfo
, s_http_host_start
, s_http_host_v6_start
, s_http_host
, s_http_host_v6
, s_http_host_v6_end
, s_http_host_port_start
, s_http_host_port
};
/* Macros for character classes; depends on strict-mode */
#define CR '\r'
#define LF '\n'
#define LOWER(c) (unsigned char)(c | 0x20)
#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z')
#define IS_NUM(c) ((c) >= '0' && (c) <= '9')
#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c))
#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f'))
#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \
(c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \
(c) == ')')
#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \
(c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \
(c) == '$' || (c) == ',')
#if HTTP_PARSER_STRICT
#define TOKEN(c) (tokens[(unsigned char)c])
#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c))
#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')
#else
#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c])
#define IS_URL_CHAR(c) \
(BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80))
#define IS_HOST_CHAR(c) \
(IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
#endif
#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)
#if HTTP_PARSER_STRICT
# define STRICT_CHECK(cond) \
do { \
if (cond) { \
SET_ERRNO(HPE_STRICT); \
goto error; \
} \
} while (0)
# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead)
#else
# define STRICT_CHECK(cond)
# define NEW_MESSAGE() start_state
#endif
/* Map errno values to strings for human-readable output */
#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s },
static struct {
const char *name;
const char *description;
} http_strerror_tab[] = {
HTTP_ERRNO_MAP(HTTP_STRERROR_GEN)
};
#undef HTTP_STRERROR_GEN
int http_message_needs_eof(const http_parser *parser);
/* Our URL parser.
*
* This is designed to be shared by http_parser_execute() for URL validation,
* hence it has a state transition + byte-for-byte interface. In addition, it
* is meant to be embedded in http_parser_parse_url(), which does the dirty
* work of turning state transitions URL components for its API.
*
* This function should only be invoked with non-space characters. It is
* assumed that the caller cares about (and can detect) the transition between
* URL and non-URL states by looking for these.
*/
static enum state
parse_url_char(enum state s, const char ch)
{
if (ch == ' ' || ch == '\r' || ch == '\n') {
return s_dead;
}
#if HTTP_PARSER_STRICT
if (ch == '\t' || ch == '\f') {
return s_dead;
}
#endif
switch (s) {
case s_req_spaces_before_url:
/* Proxied requests are followed by scheme of an absolute URI (alpha).
* All methods except CONNECT are followed by '/' or '*'.
*/
if (ch == '/' || ch == '*') {
return s_req_path;
}
if (IS_ALPHA(ch)) {
return s_req_schema;
}
break;
case s_req_schema:
if (IS_ALPHA(ch)) {
return s;
}
if (ch == ':') {
return s_req_schema_slash;
}
break;
case s_req_schema_slash:
if (ch == '/') {
return s_req_schema_slash_slash;
}
break;
case s_req_schema_slash_slash:
if (ch == '/') {
return s_req_server_start;
}
break;
case s_req_server_with_at:
if (ch == '@') {
return s_dead;
}
/* FALLTHROUGH */
case s_req_server_start:
case s_req_server:
if (ch == '/') {
return s_req_path;
}
if (ch == '?') {
return s_req_query_string_start;
}
if (ch == '@') {
return s_req_server_with_at;
}
if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') {
return s_req_server;
}
break;
case s_req_path:
if (IS_URL_CHAR(ch)) {
return s;
}
switch (ch) {
case '?':
return s_req_query_string_start;
case '#':
return s_req_fragment_start;
}
break;
case s_req_query_string_start:
case s_req_query_string:
if (IS_URL_CHAR(ch)) {
return s_req_query_string;
}
switch (ch) {
case '?':
/* allow extra '?' in query string */
return s_req_query_string;
case '#':
return s_req_fragment_start;
}
break;
case s_req_fragment_start:
if (IS_URL_CHAR(ch)) {
return s_req_fragment;
}
switch (ch) {
case '?':
return s_req_fragment;
case '#':
return s;
}
break;
case s_req_fragment:
if (IS_URL_CHAR(ch)) {
return s;
}
switch (ch) {
case '?':
case '#':
return s;
}
break;
default:
break;
}
/* We should never fall out of the switch above unless there's an error */
return s_dead;
}
size_t http_parser_execute (http_parser *parser,
const http_parser_settings *settings,
const char *data,
size_t len)
{
char c, ch;
int8_t unhex_val;
const char *p = data;
const char *header_field_mark = 0;
const char *header_value_mark = 0;
const char *url_mark = 0;
const char *body_mark = 0;
const char *status_mark = 0;
/* We're in an error state. Don't bother doing anything. */
if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
return 0;
}
if (len == 0) {
switch (parser->state) {
case s_body_identity_eof:
/* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if
* we got paused.
*/
CALLBACK_NOTIFY_NOADVANCE(message_complete);
return 0;
case s_dead:
case s_start_req_or_res:
case s_start_res:
case s_start_req:
return 0;
default:
SET_ERRNO(HPE_INVALID_EOF_STATE);
return 1;
}
}
if (parser->state == s_header_field)
header_field_mark = data;
if (parser->state == s_header_value)
header_value_mark = data;
switch (parser->state) {
case s_req_path:
case s_req_schema:
case s_req_schema_slash:
case s_req_schema_slash_slash:
case s_req_server_start:
case s_req_server:
case s_req_server_with_at:
case s_req_query_string_start:
case s_req_query_string:
case s_req_fragment_start:
case s_req_fragment:
url_mark = data;
break;
case s_res_status:
status_mark = data;
break;
}
for (p=data; p != data + len; p++) {
ch = *p;
if (PARSING_HEADER(parser->state)) {
++parser->nread;
/* Don't allow the total size of the HTTP headers (including the status
* line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect
* embedders against denial-of-service attacks where the attacker feeds
* us a never-ending header that the embedder keeps buffering.
*
* This check is arguably the responsibility of embedders but we're doing
* it on the embedder's behalf because most won't bother and this way we
* make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger
* than any reasonable request or response so this should never affect
* day-to-day operation.
*/
if (parser->nread > HTTP_MAX_HEADER_SIZE) {
SET_ERRNO(HPE_HEADER_OVERFLOW);
goto error;
}
}
reexecute_byte:
switch (parser->state) {
case s_dead:
/* this state is used after a 'Connection: close' message
* the parser will error out if it reads another message
*/
if (ch == CR || ch == LF)
break;
SET_ERRNO(HPE_CLOSED_CONNECTION);
goto error;
case s_start_req_or_res:
{
if (ch == CR || ch == LF)
break;
parser->flags = 0;
parser->content_length = ULLONG_MAX;
if (ch == 'H') {
parser->state = s_res_or_resp_H;
CALLBACK_NOTIFY(message_begin);
} else {
parser->type = HTTP_REQUEST;
parser->state = s_start_req;
goto reexecute_byte;
}
break;
}
case s_res_or_resp_H:
if (ch == 'T') {
parser->type = HTTP_RESPONSE;
parser->state = s_res_HT;
} else {
if (ch != 'E') {
SET_ERRNO(HPE_INVALID_CONSTANT);
goto error;
}
parser->type = HTTP_REQUEST;
parser->method = HTTP_HEAD;
parser->index = 2;
parser->state = s_req_method;
}
break;
case s_start_res:
{
parser->flags = 0;
parser->content_length = ULLONG_MAX;
switch (ch) {
case 'H':
parser->state = s_res_H;
break;
case CR:
case LF:
break;
default:
SET_ERRNO(HPE_INVALID_CONSTANT);
goto error;
}
CALLBACK_NOTIFY(message_begin);
break;
}
case s_res_H:
STRICT_CHECK(ch != 'T');
parser->state = s_res_HT;
break;
case s_res_HT:
STRICT_CHECK(ch != 'T');
parser->state = s_res_HTT;
break;
case s_res_HTT:
STRICT_CHECK(ch != 'P');
parser->state = s_res_HTTP;
break;
case s_res_HTTP:
STRICT_CHECK(ch != '/');
parser->state = s_res_first_http_major;
break;
case s_res_first_http_major:
if (ch < '0' || ch > '9') {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
parser->http_major = ch - '0';
parser->state = s_res_http_major;
break;
/* major HTTP version or dot */
case s_res_http_major:
{
if (ch == '.') {
parser->state = s_res_first_http_minor;
break;
}
if (!IS_NUM(ch)) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
parser->http_major *= 10;
parser->http_major += ch - '0';
if (parser->http_major > 999) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
break;
}
/* first digit of minor HTTP version */
case s_res_first_http_minor:
if (!IS_NUM(ch)) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
parser->http_minor = ch - '0';
parser->state = s_res_http_minor;
break;
/* minor HTTP version or end of request line */
case s_res_http_minor:
{
if (ch == ' ') {
parser->state = s_res_first_status_code;
break;
}
if (!IS_NUM(ch)) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
parser->http_minor *= 10;
parser->http_minor += ch - '0';
if (parser->http_minor > 999) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
break;
}
case s_res_first_status_code:
{
if (!IS_NUM(ch)) {
if (ch == ' ') {
break;
}
SET_ERRNO(HPE_INVALID_STATUS);
goto error;
}
parser->status_code = ch - '0';
parser->state = s_res_status_code;
break;
}
case s_res_status_code:
{
if (!IS_NUM(ch)) {
switch (ch) {
case ' ':
parser->state = s_res_status_start;
break;
case CR:
parser->state = s_res_line_almost_done;
break;
case LF:
parser->state = s_header_field_start;
break;
default:
SET_ERRNO(HPE_INVALID_STATUS);
goto error;
}
break;
}
parser->status_code *= 10;
parser->status_code += ch - '0';
if (parser->status_code > 999) {
SET_ERRNO(HPE_INVALID_STATUS);
goto error;
}
break;
}
case s_res_status_start:
{
if (ch == CR) {
parser->state = s_res_line_almost_done;
break;
}
if (ch == LF) {
parser->state =
gitextract_wtxp3faz/
├── .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
SYMBOL INDEX (204 symbols across 15 files)
FILE: src/http_parser.c
type state (line 235) | enum state
type header_states (line 316) | enum header_states
type http_host_state (line 342) | enum http_host_state
function parse_url_char (line 425) | static enum state
function http_parser_execute (line 573) | size_t http_parser_execute (http_parser *parser,
function http_message_needs_eof (line 1901) | int
function http_should_keep_alive (line 1924) | int
type http_method (line 1944) | enum http_method
function http_parser_init (line 1950) | void
type http_errno (line 1962) | enum http_errno
type http_errno (line 1968) | enum http_errno
function http_parse_host_char (line 1973) | static enum http_host_state
function http_parse_host (line 2038) | static int
function http_parser_parse_url (line 2111) | int
function http_parser_pause (line 2210) | void
function http_body_is_final (line 2224) | int
function http_parser_version (line 2229) | unsigned long
FILE: src/http_parser.h
type __int8 (line 36) | typedef __int8 int8_t;
type __int16 (line 38) | typedef __int16 int16_t;
type __int32 (line 40) | typedef __int32 int32_t;
type __int64 (line 42) | typedef __int64 int64_t;
type http_parser (line 59) | typedef struct http_parser http_parser;
type http_parser_settings (line 60) | typedef struct http_parser_settings http_parser_settings;
type http_method (line 114) | enum http_method
type http_parser_type (line 122) | enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }
type flags (line 126) | enum flags
type http_errno (line 184) | enum http_errno {
type http_parser (line 194) | struct http_parser {
type http_parser_settings (line 224) | struct http_parser_settings {
type http_parser_url_fields (line 236) | enum http_parser_url_fields
type http_parser_url (line 255) | struct http_parser_url {
type http_parser_type (line 278) | enum http_parser_type
type http_method (line 296) | enum http_method
type http_errno (line 299) | enum http_errno
type http_errno (line 302) | enum http_errno
type http_parser_url (line 307) | struct http_parser_url
FILE: src/lfs.c
type dir_data (line 98) | typedef struct dir_data {
function pusherror (line 132) | static int pusherror(lua_State *L, const char *info)
function pushresult (line 143) | static int pushresult(lua_State *L, int i, const char *info)
function change_dir (line 155) | static int change_dir (lua_State *L) {
function get_dir (line 173) | static int get_dir (lua_State *L) {
function FILE (line 191) | static FILE *check_file (lua_State *L, int idx, const char *funcname) {
function _file_lock (line 207) | static int _file_lock (lua_State *L, FILE *fh, const char *mode, const l...
type lfs_Lock (line 255) | typedef struct lfs_Lock {
function lfs_lock_dir (line 258) | static int lfs_lock_dir(lua_State *L) {
function lfs_unlock_dir (line 286) | static int lfs_unlock_dir(lua_State *L) {
type lfs_Lock (line 295) | typedef struct lfs_Lock {
function lfs_lock_dir (line 298) | static int lfs_lock_dir(lua_State *L) {
function lfs_unlock_dir (line 319) | static int lfs_unlock_dir(lua_State *L) {
function lfs_g_setmode (line 330) | static int lfs_g_setmode (lua_State *L, FILE *f, int arg) {
function lfs_f_setmode (line 356) | static int lfs_f_setmode(lua_State *L) {
function file_lock (line 367) | static int file_lock (lua_State *L) {
function file_unlock (line 389) | static int file_unlock (lua_State *L) {
function make_link (line 410) | static int make_link(lua_State *L)
function make_dir (line 427) | static int make_dir (lua_State *L) {
function remove_dir (line 450) | static int remove_dir (lua_State *L) {
function dir_iter (line 469) | static int dir_iter (lua_State *L) {
function dir_close (line 516) | static int dir_close (lua_State *L) {
function dir_iter_factory (line 535) | static int dir_iter_factory (lua_State *L) {
function dir_create_meta (line 561) | static int dir_create_meta (lua_State *L) {
function lock_create_meta (line 582) | static int lock_create_meta (lua_State *L) {
function file_utime (line 651) | static int file_utime (lua_State *L) {
function push_st_mode (line 673) | static void push_st_mode (lua_State *L, STAT_STRUCT *info) {
function push_st_dev (line 677) | static void push_st_dev (lua_State *L, STAT_STRUCT *info) {
function push_st_ino (line 681) | static void push_st_ino (lua_State *L, STAT_STRUCT *info) {
function push_st_nlink (line 685) | static void push_st_nlink (lua_State *L, STAT_STRUCT *info) {
function push_st_uid (line 689) | static void push_st_uid (lua_State *L, STAT_STRUCT *info) {
function push_st_gid (line 693) | static void push_st_gid (lua_State *L, STAT_STRUCT *info) {
function push_st_rdev (line 697) | static void push_st_rdev (lua_State *L, STAT_STRUCT *info) {
function push_st_atime (line 701) | static void push_st_atime (lua_State *L, STAT_STRUCT *info) {
function push_st_mtime (line 705) | static void push_st_mtime (lua_State *L, STAT_STRUCT *info) {
function push_st_ctime (line 709) | static void push_st_ctime (lua_State *L, STAT_STRUCT *info) {
function push_st_size (line 713) | static void push_st_size (lua_State *L, STAT_STRUCT *info) {
function push_st_blocks (line 718) | static void push_st_blocks (lua_State *L, STAT_STRUCT *info) {
function push_st_blksize (line 722) | static void push_st_blksize (lua_State *L, STAT_STRUCT *info) {
function push_st_perm (line 763) | static void push_st_perm (lua_State *L, STAT_STRUCT *info) {
type _stat_members (line 769) | struct _stat_members {
type _stat_members (line 774) | struct _stat_members
function _file_info_ (line 797) | static int _file_info_ (lua_State *L, int (*st)(const char*, STAT_STRUCT...
function file_info (line 836) | static int file_info (lua_State *L) {
function link_info (line 844) | static int link_info (lua_State *L) {
function set_info (line 852) | static void set_info (lua_State *L) {
type luaL_Reg (line 865) | struct luaL_Reg
function luaopen_lfs (line 882) | int luaopen_lfs (lua_State *L) {
FILE: src/lua_lpack.c
function be16 (line 36) | static void be16(const char* in, char * out) {
function be32 (line 46) | static void be32(const char* in, char* out) {
function be64 (line 60) | static void be64(const char* in, char* out) {
function number_to_lua (line 88) | static int number_to_lua(lua_State* L, int read_len, double value) {
function LUA_LIB_METHOD (line 94) | LUA_LIB_METHOD int read_number(lua_State* L) {
function string_to_lua (line 173) | static int string_to_lua(lua_State* L, const char* buff, size_t len) {
function LUA_LIB_METHOD (line 179) | LUA_LIB_METHOD int read_string(lua_State* L) {
function fetch_next_as_integer (line 198) | static double fetch_next_as_integer(lua_State* L, int* idx, const char* ...
function fetch_next_as_double (line 210) | static double fetch_next_as_double(lua_State* L, int* idx, const char* s) {
function LUA_LIB_METHOD (line 238) | LUA_LIB_METHOD int serialize_write_Q(lua_State* L) {
function define_lpack_enum (line 406) | static void define_lpack_enum(lua_State* L, const char* name, type_tag t...
function define_lapck_types (line 430) | static void define_lapck_types(lua_State* L) {
function LUA_LIB_METHOD (line 459) | LUA_LIB_METHOD static int new_lpack_parser(lua_State* L) {
function LUA_LIB_METHOD (line 465) | LUA_LIB_METHOD static int create_dict(lua_State* L) {
function LUA_LIB_METHOD (line 472) | LUA_LIB_METHOD static int to_hex(lua_State* L) {
type luaL_Reg (line 484) | struct luaL_Reg
type luaL_Reg (line 491) | struct luaL_Reg
function luaw_init_lpack_lib (line 498) | void luaw_init_lpack_lib (lua_State *L) {
FILE: src/lua_lpack.h
type type_tag (line 29) | typedef enum {
FILE: src/luaw_common.c
function resume_lua_thread (line 45) | void resume_lua_thread(lua_State* L, int nargs, int nresults, int errHan...
function error_to_lua (line 57) | int error_to_lua(lua_State* L, const char* fmt, ...) {
function raise_lua_error (line 67) | int raise_lua_error (lua_State *L, const char *fmt, ...) {
function close_if_active (line 75) | void close_if_active(uv_handle_t* handle, uv_close_cb close_cb) {
function luaL_setfuncs (line 84) | void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) {
function luaL_setmetatable (line 98) | void luaL_setmetatable (lua_State *L, const char *tname) {
function make_metatable (line 106) | void make_metatable(lua_State *L, const char* mt_name, const luaL_Reg* m...
function LUA_LIB_METHOD (line 115) | LUA_LIB_METHOD void luaw_init_libs (lua_State *L) {
FILE: src/luaw_http_parser.c
type parse_http_lua_stack_index (line 35) | typedef enum {
function decode_hex_str (line 44) | static int decode_hex_str(const char* str, int len) {
function handle_name_value_pair (line 67) | static int handle_name_value_pair(lua_State* L, const char* name, int na...
function LUA_LIB_METHOD (line 96) | LUA_LIB_METHOD static int luaw_url_decode(lua_State *L) {
function new_lhttp_parser (line 180) | static int new_lhttp_parser(lua_State *L, enum http_parser_type parser_t...
function LUA_LIB_METHOD (line 191) | LUA_LIB_METHOD static int luaw_new_http_request_parser(lua_State* L) {
function LUA_LIB_METHOD (line 195) | LUA_LIB_METHOD static int luaw_new_http_response_parser(lua_State* L) {
function LUA_LIB_METHOD (line 199) | LUA_LIB_METHOD static int luaw_init_http_parser(lua_State* L) {
function handle_http_callback (line 206) | static int handle_http_callback(http_parser *parser, http_parser_cb_type...
function HTTP_PARSER_CALLBACK (line 215) | HTTP_PARSER_CALLBACK static int http_parser_on_message_begin(http_parser...
function HTTP_PARSER_CALLBACK (line 219) | HTTP_PARSER_CALLBACK static int http_parser_on_url(http_parser *parser, ...
function HTTP_PARSER_CALLBACK (line 223) | HTTP_PARSER_CALLBACK static int http_parser_on_status(http_parser *parse...
function HTTP_PARSER_CALLBACK (line 227) | HTTP_PARSER_CALLBACK static int http_parser_on_header_name(http_parser *...
function HTTP_PARSER_CALLBACK (line 231) | HTTP_PARSER_CALLBACK static int http_parser_on_header_value(http_parser ...
function HTTP_PARSER_CALLBACK (line 235) | HTTP_PARSER_CALLBACK static int http_parser_on_headers_complete(http_par...
function HTTP_PARSER_CALLBACK (line 239) | HTTP_PARSER_CALLBACK static int http_parser_on_body(http_parser *parser,...
function HTTP_PARSER_CALLBACK (line 243) | HTTP_PARSER_CALLBACK static int http_parser_on_message_complete(http_par...
function parse_http (line 274) | static int parse_http(lua_State *L) {
function push_url_part (line 342) | static void push_url_part(lua_State *L, const char* buff, struct http_pa...
function LUA_LIB_METHOD (line 353) | LUA_LIB_METHOD static int luaw_parse_url(lua_State *L) {
type luaL_Reg (line 382) | struct luaL_Reg
type luaL_Reg (line 390) | struct luaL_Reg
function luaw_init_http_lib (line 396) | void luaw_init_http_lib (lua_State *L) {
FILE: src/luaw_http_parser.h
type decoder_state (line 31) | typedef enum {
type http_parser_cb_type (line 39) | typedef enum {
type luaw_http_parser_t (line 60) | typedef struct {
FILE: src/luaw_logging.c
type addrinfo (line 39) | struct addrinfo
function LUA_LIB_METHOD (line 53) | LUA_LIB_METHOD static int get_hostname_lua(lua_State *L) {
function LUA_LIB_METHOD (line 58) | LUA_LIB_METHOD static int get_logging_state(lua_State* l_thread) {
function LIBUV_CALLBACK (line 63) | LIBUV_CALLBACK static void on_log_open(uv_fs_t* req) {
function LUA_LIB_METHOD (line 70) | LUA_LIB_METHOD static int open_log_file(lua_State* l_thread) {
function LIBUV_CALLBACK (line 87) | LIBUV_CALLBACK static void on_log_close(uv_fs_t* req) {
function LIBUV_CALLBACK (line 92) | LIBUV_CALLBACK static void close_log(uv_fs_t* req) {
function LIBUV_CALLBACK (line 98) | LIBUV_CALLBACK static void on_log_write(uv_fs_t* req) {
function LUA_LIB_METHOD (line 108) | LUA_LIB_METHOD static int write_log(lua_State* l_thread) {
function close_syslog (line 146) | void close_syslog() {
function LUA_LIB_METHOD (line 150) | LUA_LIB_METHOD static int connect_to_syslog(lua_State *L) {
function LUA_LIB_METHOD (line 191) | LUA_LIB_METHOD static int send_to_syslog(lua_State *L) {
type luaL_Reg (line 204) | struct luaL_Reg
function luaw_init_logging_lib (line 214) | int luaw_init_logging_lib (lua_State *L) {
FILE: src/luaw_logging.h
type logfile_sate (line 27) | typedef enum {
FILE: src/luaw_server.c
type lua_load_buffer_t (line 55) | typedef struct {
function handle_shutdown_req (line 79) | static void handle_shutdown_req(uv_signal_t* handle, int signum) {
function init_luaw_server (line 87) | void init_luaw_server(lua_State* L) {
function LIBUV_CALLBACK (line 159) | LIBUV_CALLBACK static void on_server_connect(uv_stream_t* server, int st...
function start_server (line 185) | void start_server(lua_State *L) {
function run_user_threads (line 213) | static void run_user_threads(uv_prepare_t* handle) {
function close_walk_cb (line 226) | static void close_walk_cb(uv_handle_t* handle, void* arg) {
function server_loop (line 232) | static int server_loop(lua_State *L) {
function run_lua_file (line 248) | static void run_lua_file(const char* filename, char* epilogue) {
function set_lua_path (line 280) | static void set_lua_path(lua_State* L) {
function main (line 287) | int main (int argc, char* argv[]) {
FILE: src/luaw_tcp.c
function connection_t (line 46) | connection_t* new_connection(lua_State* L) {
function LUA_LIB_METHOD (line 85) | LUA_LIB_METHOD static int new_connection_lua(lua_State* L) {
function free_timer (line 90) | static void free_timer(uv_handle_t* handle) {
function free_tcp_handle (line 96) | static void free_tcp_handle(uv_handle_t* handle) {
function close_connection (line 102) | void close_connection(connection_t* conn, const int status) {
function LIBUV_CALLBACK (line 154) | LIBUV_CALLBACK static void on_conn_timeout(uv_timer_t* timer) {
function start_timer (line 160) | static int start_timer(uv_timer_t* timer, int timeout) {
function stop_timer (line 167) | static void stop_timer(uv_timer_t* timer) {
function LUA_OBJ_METHOD (line 173) | LUA_OBJ_METHOD static int close_connection_lua(lua_State* l_thread) {
function LUA_OBJ_METHOD (line 183) | LUA_OBJ_METHOD static int connection_gc(lua_State *L) {
function LIBUV_CALLBACK (line 201) | LIBUV_CALLBACK static void on_alloc(uv_handle_t* handle, size_t suggeste...
function LIBUV_API (line 220) | LIBUV_API static void on_read(uv_stream_t* stream, ssize_t nread, const ...
function LUA_OBJ_METHOD (line 251) | LUA_OBJ_METHOD static int start_reading(lua_State *l_thread) {
function LUA_OBJ_METHOD (line 271) | LUA_OBJ_METHOD static int read_check(lua_State* l_thread) {
function LIBUV_API (line 301) | LIBUV_API static void on_write(uv_write_t* req, int status) {
function LUA_OBJ_METHOD (line 326) | LUA_OBJ_METHOD static int write_buffer(lua_State* l_thread) {
function LIBUV_CALLBACK (line 364) | LIBUV_CALLBACK static void on_client_connect(uv_connect_t* connect_req, ...
function LUA_LIB_METHOD (line 389) | LUA_LIB_METHOD static int client_connect(lua_State* l_thread) {
function LIBUV_CALLBACK (line 430) | LIBUV_CALLBACK static void on_resolved(uv_getaddrinfo_t *resolver, int s...
function LUA_LIB_METHOD (line 455) | LUA_LIB_METHOD static int dns_resolve(lua_State* l_thread) {
type luaL_Reg (line 495) | struct luaL_Reg
type luaL_Reg (line 504) | struct luaL_Reg
function luaw_init_tcp_lib (line 512) | void luaw_init_tcp_lib (lua_State *L) {
FILE: src/luaw_tcp.h
type connection_t (line 30) | typedef struct connection_s connection_t;
type connection_s (line 34) | struct connection_s {
FILE: src/luaw_timer.c
function clear_user_timer (line 41) | static void clear_user_timer(luaw_timer_t* timer) {
function free_user_timer (line 46) | static void free_user_timer(uv_handle_t* handle) {
function LUA_OBJ_METHOD (line 52) | LUA_OBJ_METHOD static void close_timer(luaw_timer_t* timer) {
function LUA_OBJ_METHOD (line 73) | LUA_OBJ_METHOD static int close_timer_lua(lua_State* l_thread) {
function LUA_OBJ_METHOD (line 79) | LUA_OBJ_METHOD static int timer_gc(lua_State *L) {
function LUA_LIB_METHOD (line 85) | LUA_LIB_METHOD static int new_user_timer(lua_State* l_thread) {
function LIBUV_CALLBACK (line 117) | LIBUV_CALLBACK static void on_user_timer_timeout(uv_timer_t* handle) {
function start_user_timer (line 135) | static int start_user_timer(lua_State* l_thread) {
function LUA_OBJ_METHOD (line 165) | LUA_OBJ_METHOD static int wait_user_timer(lua_State* l_thread) {
function LUA_OBJ_METHOD (line 191) | LUA_OBJ_METHOD static int stop_user_timer(lua_State* l_thread) {
type luaL_Reg (line 208) | struct luaL_Reg
type luaL_Reg (line 217) | struct luaL_Reg
function luaw_init_timer_lib (line 223) | void luaw_init_timer_lib (lua_State *L) {
FILE: src/luaw_timer.h
type timer_state (line 29) | typedef enum {
type luaw_timer_t (line 36) | typedef struct luaw_timer_s luaw_timer_t;
type luaw_timer_s (line 38) | struct luaw_timer_s {
Condensed preview — 62 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (375K chars).
[
{
"path": ".gitignore",
"chars": 24,
"preview": "**/*.o\n**/*.so\n**/*.log\n"
},
{
"path": ".gitmodules",
"chars": 278,
"preview": "[submodule \"deps/libuv\"]\n\tpath = deps/libuv\n\turl = https://github.com/libuv/libuv.git\n[submodule \"deps/luajit-2.0\"]\n\tpat"
},
{
"path": "LICENSE",
"chars": 1052,
"preview": "Copyright (c) 2015 raksoras\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this softwa"
},
{
"path": "Makefile",
"chars": 2447,
"preview": "# Makefile for building Luaw\n# == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT =======================\n\nexport UVD"
},
{
"path": "README.md",
"chars": 5551,
"preview": "Luaw - Lua meets Node.js\n========================\n***\n\nLuaw stands for \"Lua web server\". It also matches abbreviation fo"
},
{
"path": "conf/server.cfg",
"chars": 493,
"preview": "luaw_server_config = {\n server_ip = \"0.0.0.0\",\n server_port = 7001,\n connect_timeout = 4000,\n read_timeout ="
},
{
"path": "docs/Contents.md",
"chars": 533,
"preview": "#Contents\n\n1. Introduction\n2. Getting started: building and installing Luaw\n3. Luaw configuration\n4. Your first Luaw web"
},
{
"path": "docs/Introduction.md",
"chars": 2859,
"preview": "#Introduction\n\nLuaw stands for \"Lua web server\". It also matches abbreviation for an air traffic controller command \"lin"
},
{
"path": "docs/cmd-line.md",
"chars": 1372,
"preview": "#14. Advanced Topic II - Using custom scripts on command line at start up\n\nIn the last topic we saw that we can specify "
},
{
"path": "docs/configuration.md",
"chars": 6289,
"preview": "#3. Configuring Luaw\n\nBefore we can write our first Luaw webapp we need to configure our Luaw server with some basic set"
},
{
"path": "docs/first-webapp.md",
"chars": 3999,
"preview": "#4. Your First Luaw Webapp - \"Hello world!\"\n\nNow that we are familiar with Luaw's directory layout and configuration, we"
},
{
"path": "docs/getting-started.md",
"chars": 3252,
"preview": "#2. Getting started\n\nLet's build Luaw from its sources. Luaw depends on,\n\n1. Lua 5.2(+)\n2. libuv (v1.0.0 )\n\nWe will firs"
},
{
"path": "docs/http-client.md",
"chars": 1757,
"preview": "#10. Async HTTP Client\n\nLuaw comes equipped with curl like async HTTP client. It is fully async, in that both DNS lookup"
},
{
"path": "docs/logging.md",
"chars": 4668,
"preview": "#8. Luaw logging framework\n\nLuaw comes equipped with logging framework that's modeled after Java's popular log4j logging"
},
{
"path": "docs/proxy-http.md",
"chars": 7813,
"preview": "#13. custom HTTP handler for proxying request\n\nSo far we have used default HTTP requests handler that ships with Luaw in"
},
{
"path": "docs/resp-obj.md",
"chars": 3252,
"preview": "#9. Response Object\n\nSo far we have seen two ways to return response body from resource handler:\n\n1. Returning a string "
},
{
"path": "docs/second-webapp.md",
"chars": 2162,
"preview": "#5. Your Second Luaw webapp - \"Hello `username`!\"\n\nLuaw handlers can accept and process HTTP request parameters (query p"
},
{
"path": "docs/template-view.md",
"chars": 5239,
"preview": "#6. Luaw Template Views\n\nLuaw comes equipped with a Luaw template views for generating server-side dynamic content. Luaw"
},
{
"path": "docs/third-webapp.md",
"chars": 5229,
"preview": "#7. Your third webapp - with Luaw template view\n\nIn this chapter we will put together all the pieces we have learned abo"
},
{
"path": "docs/user-threads.md",
"chars": 3582,
"preview": "#11. User Threads\n\nLuaw allows developer to spawn user threads to execute multiple tasks in parallel. Luaw user threads "
},
{
"path": "docs/user-timers.md",
"chars": 1125,
"preview": "#12. Luaw Timers\n\nLuaw supports user defined timers. Here is an example:\n\n```lua\nlocal timer = Luaw.newTimer()\ntimer:sta"
},
{
"path": "lib/luapack.lua",
"chars": 12925,
"preview": "--[[\nCopyright (c) 2015 raksoras\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this s"
},
{
"path": "lib/luaw_constants.lua",
"chars": 1387,
"preview": "local constMT = {\n __newindex = function(table, key, value)\n error(\"constant \"..table.name..\" cannot be change"
},
{
"path": "lib/luaw_data_structs_lib.lua",
"chars": 3619,
"preview": "--[[\nCopyright (c) 2015 raksoras\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this s"
},
{
"path": "lib/luaw_http.lua",
"chars": 26003,
"preview": "--[[\nCopyright (c) 2015 raksoras\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this s"
},
{
"path": "lib/luaw_init.lua",
"chars": 1365,
"preview": "--[[\nCopyright (c) 2015 raksoras\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this s"
},
{
"path": "lib/luaw_logging.lua",
"chars": 8545,
"preview": "--[[\nCopyright (c) 2015 raksoras\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this s"
},
{
"path": "lib/luaw_scheduler.lua",
"chars": 8598,
"preview": "--[[\nCopyright (c) 2015 raksoras\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this s"
},
{
"path": "lib/luaw_tcp.lua",
"chars": 3249,
"preview": "--[[\nCopyright (c) 2015 raksoras\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this s"
},
{
"path": "lib/luaw_timer.lua",
"chars": 1673,
"preview": "--[[\nCopyright (c) 2015 raksoras\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this s"
},
{
"path": "lib/luaw_utils.lua",
"chars": 4314,
"preview": "--[[\nCopyright (c) 2015 raksoras\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this s"
},
{
"path": "lib/luaw_webapp.lua",
"chars": 15028,
"preview": "--[[\nCopyright (c) 2015 raksoras\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this s"
},
{
"path": "lib/unit_testing.lua",
"chars": 3951,
"preview": "--[[\nCopyright (c) 2015 raksoras\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this s"
},
{
"path": "sample/conf/server.cfg",
"chars": 493,
"preview": "luaw_server_config = {\n server_ip = \"0.0.0.0\",\n server_port = 7001,\n connect_timeout = 4000,\n read_timeout ="
},
{
"path": "sample/proxy_handler.lua",
"chars": 2589,
"preview": "local http_lib = require('luaw_http')\n--[[\n Luaw allows you to replace it's default MVC/REST request handler with you"
},
{
"path": "sample/webapps/myapp/handlers/handler-address.lua",
"chars": 277,
"preview": "registerHandler {\n method = 'GET',\n path = 'address/:city/#zip',\n\n handler = function(req, resp, pathParams)\n "
},
{
"path": "sample/webapps/myapp/handlers/handler-fileupload-display.lua",
"chars": 861,
"preview": "registerHandler {\n method = 'GET',\n path = 'showform',\n\n\thandler = function(req, resp, pathParams)\n\t return [[\n"
},
{
"path": "sample/webapps/myapp/handlers/handler-fileupload-process.lua",
"chars": 784,
"preview": "local function append(buffer, str)\n if (str) then\n table.insert(buffer, str)\n table.insert(buffer, \", \""
},
{
"path": "sample/webapps/myapp/handlers/handler-hellouser.lua",
"chars": 234,
"preview": "registerHandler {\n method = 'GET',\n path = '/user/:username/#count',\n\n\thandler = function(req, resp, pathParams)\n\t"
},
{
"path": "sample/webapps/myapp/handlers/handler-helloworld.lua",
"chars": 151,
"preview": "registerHandler {\n method = 'GET',\n path = 'helloworld',\n\n handler = function(req, resp, pathParams)\n re"
},
{
"path": "sample/webapps/myapp/handlers/handler-read-lpack.lua",
"chars": 906,
"preview": "local utils_lib = require('luaw_utils')\nlocal http_lib = require('luaw_http')\nlocal lpack = require('luapack')\n\nregister"
},
{
"path": "sample/webapps/myapp/handlers/handler-write-lpack.lua",
"chars": 2678,
"preview": "local utils_lib = require('luaw_utils')\nlocal http_lib = require('luaw_http')\nlocal lpack = require('luapack')\n\nlocal bi"
},
{
"path": "sample/webapps/myapp/views/view-address.lua",
"chars": 1425,
"preview": "BEGIN 'html'\n BEGIN 'head'\n BEGIN 'title'\n TEXT 'Address'\n END 'title'\n END 'head'\n BE"
},
{
"path": "sample/webapps/myapp/web.lua",
"chars": 95,
"preview": "luaw_webapp = {\n resourcePattern = \"handler%-.*%.lua\",\n viewPattern = \"view%-.*%.lua\",\n}\n"
},
{
"path": "src/Makefile",
"chars": 2950,
"preview": "# Makefile for building Luaw\n# == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT =======================\n\nCC?= gcc\nC"
},
{
"path": "src/http_parser.c",
"chars": 63117,
"preview": "/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev\n *\n * Additional changes are licensed under the s"
},
{
"path": "src/http_parser.h",
"chars": 11955,
"preview": "/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.\n *\n * Permission is hereby granted, free of "
},
{
"path": "src/lfs.c",
"chars": 24691,
"preview": "/*\n** LuaFileSystem\n** Copyright Kepler Project 2003 (http://www.keplerproject.org/luafilesystem)\n**\n** File system mani"
},
{
"path": "src/lfs.h",
"chars": 410,
"preview": "/*\n** LuaFileSystem\n** Copyright Kepler Project 2003 (http://www.keplerproject.org/luafilesystem)\n**\n** $Id: lfs.h,v 1.5"
},
{
"path": "src/lua_lpack.c",
"chars": 15598,
"preview": "/*\n* Copyright (c) 2015 raksoras\n*\n* Permission is hereby granted, free of charge, to any person obtaining a copy\n* of t"
},
{
"path": "src/lua_lpack.h",
"chars": 1821,
"preview": "/*\n* Copyright (c) 2015 raksoras\n*\n* Permission is hereby granted, free of charge, to any person obtaining a copy\n* of t"
},
{
"path": "src/luaw_common.c",
"chars": 5414,
"preview": "/*\n* Copyright (c) 2015 raksoras\n*\n* Permission is hereby granted, free of charge, to any person obtaining a copy\n* of t"
},
{
"path": "src/luaw_common.h",
"chars": 4303,
"preview": "/*\n* Copyright (c) 2015 raksoras\n*\n* Permission is hereby granted, free of charge, to any person obtaining a copy\n* of t"
},
{
"path": "src/luaw_http_parser.c",
"chars": 12950,
"preview": "/*\n* Copyright (c) 2015 raksoras\n*\n* Permission is hereby granted, free of charge, to any person obtaining a copy\n* of t"
},
{
"path": "src/luaw_http_parser.h",
"chars": 2035,
"preview": "/*\n* Copyright (c) 2015 raksoras\n*\n* Permission is hereby granted, free of charge, to any person obtaining a copy\n* of t"
},
{
"path": "src/luaw_logging.c",
"chars": 6891,
"preview": "/*\n* Copyright (c) 2015 raksoras\n*\n* Permission is hereby granted, free of charge, to any person obtaining a copy\n* of t"
},
{
"path": "src/luaw_logging.h",
"chars": 1315,
"preview": "/*\n* Copyright (c) 2015 raksoras\n*\n* Permission is hereby granted, free of charge, to any person obtaining a copy\n* of t"
},
{
"path": "src/luaw_server.c",
"chars": 10165,
"preview": "/*\n* Copyright (c) 2015 raksoras\n*\n* Permission is hereby granted, free of charge, to any person obtaining a copy\n* of t"
},
{
"path": "src/luaw_tcp.c",
"chars": 16922,
"preview": "/*\n* Copyright (c) 2015 raksoras\n*\n* Permission is hereby granted, free of charge, to any person obtaining a copy\n* of t"
},
{
"path": "src/luaw_tcp.h",
"chars": 3407,
"preview": "/*\n* Copyright (c) 2015 raksoras\n*\n* Permission is hereby granted, free of charge, to any person obtaining a copy\n* of t"
},
{
"path": "src/luaw_timer.c",
"chars": 7582,
"preview": "/*\n* Copyright (c) 2015 raksoras\n*\n* Permission is hereby granted, free of charge, to any person obtaining a copy\n* of t"
},
{
"path": "src/luaw_timer.h",
"chars": 2615,
"preview": "/*\n* Copyright (c) 2015 raksoras\n*\n* Permission is hereby granted, free of charge, to any person obtaining a copy\n* of t"
}
]
About this extraction
This page contains the full source code of the raksoras/luaw GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 62 files (349.9 KB), approximately 91.7k tokens, and a symbol index with 204 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.