├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.markdown ├── config ├── src ├── ngx_http_lua_io.c ├── ngx_http_lua_io.h ├── ngx_http_lua_io_input_filter.c ├── ngx_http_lua_io_input_filter.h └── ngx_http_lua_io_module.c ├── t ├── 00-open-close.t ├── 01-thread-pool.t ├── 02-file-write.t ├── 03-file-flush.t ├── 04-file-write-back-cache.t ├── 05-file-seek.t ├── 06-file-read.t └── 07-file-lines.t ├── util └── build.sh └── valgrind.suppress /.gitattributes: -------------------------------------------------------------------------------- 1 | *.t linguist-language=Text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | t/servroot 55 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | os: linux 5 | 6 | language: c 7 | 8 | cache: 9 | apt: true 10 | directories: 11 | - download-cache 12 | 13 | addons: 14 | apt: 15 | packages: 16 | - axel 17 | - cpanminus 18 | - libtest-base-perl 19 | - libtext-diff-perl 20 | - liburi-perl 21 | - libwww-perl 22 | - libtest-longstring-perl 23 | - liblist-moreutils-perl 24 | 25 | compiler: 26 | - gcc 27 | - clang 28 | 29 | env: 30 | global: 31 | - LUAJIT_PREFIX=/opt/luajit21 32 | - LUAJIT_LIB=$LUAJIT_PREFIX/lib 33 | - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH 34 | - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1 35 | - LUA_INCLUDE_DIR=$LUAJIT_INC 36 | - LUA_CMODULE_DIR=/lib 37 | - JOBS=3 38 | - NGX_BUILD_JOBS=$JOBS 39 | matrix: 40 | - NGINX_VERSION=1.11.2 41 | - NGINX_VERSION=1.13.6 42 | - NGINX_VERSION=1.15.6 43 | 44 | services: 45 | - redis-server 46 | 47 | install: 48 | - echo $HOME 49 | - if [ ! -d download-cache ]; then mkdir download-cache; fi 50 | - if [ ! -f download-cache/ngx_http_redis-0.3.7.tar.gz ]; then wget -O download-cache/ngx_http_redis-0.3.7.tar.gz http://people.freebsd.org/~osa/ngx_http_redis-0.3.7.tar.gz; fi 51 | - mkdir -p ~/work/nginx && cp download-cache/ngx_http_redis-0.3.7.tar.gz ~/work/nginx/ 52 | - git clone https://github.com/openresty/nginx-devel-utils.git 53 | - git clone https://github.com/openresty/openresty.git ../openresty 54 | - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx 55 | - git clone https://github.com/simpl/ngx_devel_kit.git ../ndk-nginx-module 56 | - git clone https://github.com/openresty/test-nginx.git 57 | - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git 58 | - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module 59 | - git clone https://github.com/openresty/nginx-eval-module.git ../eval-nginx-module 60 | - git clone https://github.com/openresty/echo-nginx-module.git ../echo-nginx-module 61 | - git clone https://github.com/openresty/set-misc-nginx-module.git ../set-misc-nginx-module 62 | - git clone https://github.com/openresty/openresty.git ../ngx_openresty 63 | 64 | script: 65 | - cd luajit2 66 | - make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT -msse4.2' > build.log 2>&1 || (cat build.log && exit 1) 67 | - sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1) 68 | - cd .. 69 | - cd test-nginx && sudo cpanm . && cd .. 70 | - export PATH=$PWD/work/nginx/sbin:$PWD/nginx-devel-utils:$PATH 71 | - export NGX_BUILD_CC=$CC 72 | - sh util/build.sh $NGINX_VERSION > build.log 2>&1 || (cat build.log && exit 1) 73 | - nginx -V 74 | - ldd `which nginx` | grep luajit 75 | - prove -r -s t 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2019, Alex Zhang 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Name 2 | 3 | lua-io-nginx-module - Nginx C module to take over the Lua file operations. It's based on Nginx's thread pool. 4 | 5 | ![Build Status](https://travis-ci.org/tokers/lua-io-nginx-module.svg?branch=master) [![License](https://img.shields.io/badge/License-BSD%202--Clause-orange.svg)](https://github.com/tokers/lua-io-nginx-module/blob/master/LICENSE) 6 | 7 | # Table of Contents 8 | 9 | * [Name](#name) 10 | * [Status](#status) 11 | * [Synopsis](#synopsis) 12 | * [Description](#description) 13 | * [Prerequisites](#prerequisites) 14 | * [Directives](#directives) 15 | * [lua_io_thread_pool](#lua_io_thread_pool) 16 | * [lua_io_log_errors](#lua_io_log_errors) 17 | * [lua_io_read_buffer_size](#lua_io_read_buffer_size) 18 | * [lua_io_write_buffer_size](#lua_io_write_buffer_size) 19 | * [APIs](#apis) 20 | * [ngx_io.open](#ngx_ioopen) 21 | * [file:read](#fileread) 22 | * [file:write](#filewrite) 23 | * [file:seek](#fileseek) 24 | * [file:flush](#fileflush) 25 | * [file:close](#fileclose) 26 | * [Author](#author) 27 | 28 | # Status 29 | 30 | This Nginx module is currently considered experimental. 31 | 32 | # Synopsis 33 | 34 | ```nginx 35 | # configure a thread pool, with 16 threads and a task queue which size is 64k. 36 | thread_pool default threads=16 max_queue=65536; 37 | 38 | http { 39 | 40 | ... 41 | 42 | server { 43 | listen *:8080; 44 | lua_io_thread_pool default; 45 | location /read_by_line { 46 | lua_io_read_buffer_size 8192; 47 | content_by_lua_block { 48 | local ngx_io = require "ngx.io" 49 | local filename = "/tmp/foo.txt" 50 | local file, err = ngx_io.open(filename, "r") 51 | assert(file and not err) 52 | 53 | -- iterates the file by reading one line every time. 54 | for line in file:lines() do 55 | ngx.say(line) 56 | end 57 | 58 | local ok, err = file:close() 59 | assert(ok and not err) 60 | } 61 | } 62 | 63 | location /read_by_bytes { 64 | content_by_lua_block { 65 | local ngx_io = require "ngx.io" 66 | local filename = "/tmp/foo.txt" 67 | 68 | local file, err = ngx_io.open(filename, "r") 69 | assert(file and not err) 70 | 71 | while true do 72 | -- iterates the file by reading 512 bytes every time. 73 | local data, err = file:read(512) 74 | if err ~= nil then 75 | ngx.log(ngx.ERR, "file:read() error: ", err) 76 | break 77 | end 78 | 79 | if data == nil then 80 | break 81 | end 82 | 83 | ngx.print(data) 84 | end 85 | 86 | local ok, err = file:close() 87 | assert(ok and not err) 88 | } 89 | } 90 | 91 | location /write { 92 | lua_io_write_buffer_size 4k; 93 | content_by_lua_block { 94 | local ngx_io = require "ngx.io" 95 | 96 | local length = tonumber(ngx.var.http_content_length) 97 | if not length then 98 | return ngx.exit(200) 99 | end 100 | 101 | local sock, err = ngx.req.socket() 102 | if not sock then 103 | ngx.log(ngx.ERR, "ngx.req.socket() failed: ", err) 104 | return ngx.exit(500) 105 | end 106 | 107 | local file, err = ngx_io.open("/tmp/foo.txt", "w") 108 | assert(file and not err) 109 | 110 | repeat 111 | local size = length > 4096 and 4096 or length 112 | length = length - size 113 | local data, err = sock:receive(size) 114 | if err then 115 | ngx.log(ngx.ERR, "sock:receive() failed: ", err) 116 | return 117 | end 118 | 119 | local bytes, err = file:write(data) 120 | assert(bytes == size) 121 | assert(not err) 122 | until length == 0 123 | 124 | local ok, err = file:close() 125 | assert(ok and not err) 126 | 127 | return ngx.exit(200) 128 | } 129 | } 130 | } 131 | ``` 132 | 133 | # Description 134 | 135 | This Nginx C module provides the basic file operations APIs with a mechanism that never block Nginx's event loop. 136 | For now, it leverages Nginx's thread pool, I/O operations might be offloaded to one of the free threads, 137 | and current Lua coroutine (Light Thread) will be yield until the I/O operations is done, in the meantime, Nginx in turn processes other events. 138 | 139 | It's worth to mention that the cost time of a single I/O operation won't be reduced, it was just transferred from the main thread (the one executes the event loop) to another exclusive thread. 140 | Indeed, the overhead might be a little higher, because of the extra tasks transferring, lock waiting, Lua coroutine resumption (and can only be resumed in the next event loop) and so forth. Nevertheless, after the offloading, the main thread doesn't block due to the I/O operation, and this is the fundamental advantage compared with the native Lua I/O library. 141 | 142 | The APIs are similar with the [Lua I/O library](https://www.lua.org/pil/21.html), but with the totally different internal implementations, it doesn't use the stream file facilities in libc (but keep trying to be consistent with it), the buffer is maintained inside this module, and follows Cosocket's internals. 143 | 144 | If you want to learn more about Nginx's thread pool, just try this [article](https://www.nginx.com/blog/thread-pools-boost-performance-9x/). 145 | 146 | [Back to TOC](#table-of-contents) 147 | 148 | # Prerequisites 149 | 150 | This Nginx C module relies on the [lua-nginx-module](https://github.com/openresty/lua-nginx-module) and the thread pool option, so configure your Nginx branch like the follow way: 151 | 152 | ```bash 153 | ./auto/configure --with-threads --add-module=/path/to/lua-nginx-module/ --add-module=/path/to/lua-io-nginx-module/ 154 | ``` 155 | 156 | Due to some existing limitations in ngx_lua, you must place the `--add-module=/path/to/lua-nginx-module/` **before** `--add-module=/path/to/lua-io-nginx-module/`. 157 | These limitations might be eliminated in the future if ngx_lua exposes more C functions and data structures. 158 | 159 | # Directives 160 | 161 | ## lua_io_thread_pool 162 | 163 | **Syntax:** *lua_io_thread_pool thread-pool-name;* 164 | **Default:** *lua_io_thread_pool default;* 165 | **Context:** *http, server, location, if in location* 166 | 167 | Specifies which thread pool should be used, note you should configure the thread pool by the `thread_pool` direction. 168 | 169 | ## lua_io_log_errors 170 | 171 | **Syntax:** *lua_io_log_errors on | off* 172 | **Default:** *lua_io_log_errors off;* 173 | **Context:** *http, server, location, if in location* 174 | 175 | Specifies whether logs the error message when failures occur. If you are already doing proper error handling and logging in your Lua code, then it is recommended to turn this directive off to prevent data flushing in your nginx error log files (which is usually rather expensive). 176 | 177 | ## lua_io_read_buffer_size 178 | 179 | **Syntax:** *lua_io_read_buffer_size * 180 | **Default:** *lua_io_read_buffer_size 4k/8k;* 181 | **Context:** *http, server, location, if in location* 182 | 183 | Specifies the buffer size used by the reading operations. 184 | 185 | ## lua_io_write_buffer_size 186 | 187 | **Syntax:** *lua_io_write_buffer_size * 188 | **Default:** *lua_io_write_buffer_size 4k/8k;* 189 | **Context:** *http, server, location, if in location* 190 | 191 | Specifies the buffer size used by the writing operations. 192 | 193 | Data will be cached in this buffer until overflow or you call these "flushable" APIs (like `file:flush`) explicitly. 194 | 195 | You can set this value to zero and always "write through the cache". 196 | 197 | # APIs 198 | 199 | To use these APIs, just import this module by: 200 | 201 | ```lua 202 | local ngx_io = require "ngx.io" 203 | ``` 204 | 205 | ## ngx_io.open 206 | 207 | **Syntax:** *local file, err = ngx_io.open(filename [, mode])* 208 | **Context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** 209 | 210 | Opens a file and returns the corresponding file object. In case of failure, `nil` and a Lua string will be given, which describes the error reason. 211 | 212 | The first parameter is the target file name that would be opened. When `filename` is a relative path, the nginx prefix will be placed in front of `filename`, for instance, if the `filename` is "foo.txt", and you start your Nginx by `nginx -p /tmp`, then file `/tmp/foo.txt` will be opened. 213 | 214 | The second optional parameter, specifes the open mode, can be any of the following: 215 | 216 | * `"r"`: read mode (the default); 217 | * `"w"`: write mode; 218 | * `"a"`: append mode; 219 | * `"r+"`: update mode, all previous data is preserved; 220 | * `"w+"`: update mode, all previous data is erased (file will be truncated); 221 | * `"a+"`: append update mode, previous data is preserved, writing is only allowed at the end of file. 222 | 223 | ## file:read 224 | 225 | **Syntax:** *local data, err = file:read([format])* 226 | **Context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** 227 | 228 | Reads some data from the file, according to the given formats, which specify what to read. 229 | 230 | The available formats are: 231 | 232 | * `"*a"`: reads the whole file, starting at the current position. On end of file, it returns `nil`. 233 | * `"*l"`: reads the next line (skipping the end of line), returning `nil` on end of file. This is the default format. 234 | * number: reads a string with up to this number of characters, returning `nil` on end of file. If number is zero, it reads nothing and returns an empty string, or `nil` on end of file. 235 | 236 | A Lua string will be returned as the expected data; In case of failure, `nil` and an error message will be given. 237 | 238 | This method is a synchronous operation and is 100% nonblocking. 239 | 240 | ## file:write 241 | 242 | **Syntax:** *local n, err = file:write(data)* 243 | **Context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** 244 | 245 | Writes data to the file. Note `data` might be cached in the write buffer if suitable. 246 | 247 | the number of wrote bytes will be returned; In case of failure, `0` and an error message will be given. 248 | 249 | This method is a synchronous operation and is 100% nonblocking. 250 | 251 | **CAUTION:** If you opened the file with the append mode, then writing is only allowed at the end of file. The adjustment of the file offset and the write operation are performed as an atomic step, which is guaranteed by the `write` and `writev` system calls. 252 | 253 | ## file:seek 254 | 255 | **Syntax:** *local offset, err = file:seek([whence] [, offset])* 256 | **Context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** 257 | 258 | 259 | Sets and gets the file position, measured from the beginning of the file, to the position given by `offset` plus a base specified by the string `whence`, as follows: 260 | 261 | * "set": base is position 0 (beginning of the file); 262 | * "cur": base is current position; 263 | * "end": base is end of file; 264 | 265 | In case of success, function seek returns the final file position, measured in bytes from the beginning of the file. If this method fails, it returns nil, plus a string describing the error. 266 | 267 | The default value for `whence` is "cur", and for `offset` is `0`. Therefore, the call file:seek() returns the current file position, without changing it; the call file:seek("set") sets the position to the beginning of the file (and returns `0`); and the call file:seek("end") sets the position to the end of the file, and returns its size. 268 | 269 | Cached write buffer data will be flushed to the file and cached read buffer data will be dropped. This method is a synchronous operation and is 100% nonblocking. 270 | 271 | **CAVEAT:** You should always call this method before you switch the I/O operations from `read` to `write` and vice versa. 272 | 273 | ## file:flush 274 | 275 | **Syntax:** *local ok, err = file:flush([sync])* 276 | **Context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** 277 | 278 | 279 | Saves any written data to file. In case of success, it returns `1` and if this method fails, `nil` and a Lua string will be given (as the error message). 280 | 281 | An optional and sole parameter `sync` can be passed to specify whether this method should call `fsync` and wait until data was saved to the storage, default is `false`. 282 | 283 | This method is a synchronous operation and is 100% nonblocking. 284 | 285 | ## file:lines 286 | 287 | **Syntax:** *local iter = file:lines()* 288 | **Context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** 289 | 290 | 291 | Returns an iterator that, each time it is called, returns a new line from the file. Therefore, the construction 292 | 293 | ```lua 294 | for line in file:lines() do body end 295 | ``` 296 | 297 | will iterate over all lines of the file. 298 | 299 | The iterator is like the way `file:read("*l")`, and you can always mixed use of these read methods safely. 300 | 301 | ## file:close 302 | 303 | **Syntax:** *local ok, err = file:close()* 304 | **Context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** 305 | 306 | 307 | Closes the file. Any cached write buffer data will be flushed to the file. This method is a synchronous operation and is 100% nonblocking. 308 | 309 | In case of success, this method returns `1` while `nil` plus a Lua string will be returned if errors occurred. 310 | 311 | # Author 312 | 313 | Alex Zhang (张超) zchao1995@gmail.com, UPYUN Inc. 314 | 315 | [Back to TOC](#table-of-contents) 316 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | if [ $USE_THREADS != YES ]; then 2 | cat << END 3 | 4 | $0: lua_io_nginx_module depends on the threads support, please reconfigure with "--with-threads" option. 5 | 6 | END 7 | exit 1 8 | fi 9 | 10 | # threads support test has been done 11 | 12 | # this is a workaround, to include the private header files in ngx_lua module, 13 | # we need this to solve the context checking problem, and remember to add the 14 | # ngx_lua module prior to this module. This embarrassed situation will be 15 | # avoided if ngx_lua module can expose some APIs to allow us check the current 16 | # request context. 17 | for header in $HTTP_LUA_DEPS 18 | do 19 | has=`echo $header | grep -q "ngx_http_lua_util.h"` 20 | if [ -n $has ]; then 21 | dir=`dirname $header` 22 | CORE_INCS="$CORE_INCS $dir" 23 | break 24 | fi 25 | done 26 | 27 | ngx_addon_name=ngx_http_lua_io_module 28 | HTTP_LUA_IO_SRCS="$ngx_addon_dir/src/ngx_http_lua_io_module.c \ 29 | $ngx_addon_dir/src/ngx_http_lua_io.c \ 30 | $ngx_addon_dir/src/ngx_http_lua_io_input_filter.c" 31 | 32 | HTTP_LUA_IO_DEPS="$ngx_addon_dir/src/ngx_http_lua_io.h \ 33 | $ngx_addon_dir/src/ngx_http_lua_io_input_filter.h" 34 | 35 | if test -n "$ngx_module_link"; then 36 | ngx_module_type=HTTP 37 | ngx_module_name=$ngx_addon_name 38 | ngx_module_deps="$HTTP_LUA_IO_DEPS" 39 | ngx_module_srcs="$HTTP_LUA_IO_SRCS" 40 | 41 | . auto/module 42 | else 43 | HTTP_MODULES="$HTTP_MODULES $ngx_addon_name" 44 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $HTTP_LUA_IO_SRCS" 45 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $HTTP_LUA_IO_DEPS" 46 | 47 | CORE_INCS="$CORE_INCS $ngx_module_incs" 48 | CORE_LIBS="$CORE_LIBS $ngx_module_libs" 49 | fi 50 | -------------------------------------------------------------------------------- /src/ngx_http_lua_io.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Alex Zhang 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | 10 | #include "ngx_http_lua_io.h" 11 | 12 | 13 | static ngx_chain_t *ngx_http_lua_io_chain_to_iovec(ngx_iovec_t *vec, 14 | ngx_chain_t *cl); 15 | static ngx_int_t ngx_http_lua_io_thread_post_task(ngx_thread_task_t *task, 16 | ngx_http_lua_io_file_ctx_t *file_ctx); 17 | static void ngx_http_lua_io_thread_write_chain_to_file(void *data, 18 | ngx_log_t *log); 19 | static void ngx_http_lua_io_thread_read_file(void *data, ngx_log_t *log); 20 | 21 | 22 | static ngx_chain_t * 23 | ngx_http_lua_io_chain_to_iovec(ngx_iovec_t *vec, ngx_chain_t *cl) 24 | { 25 | size_t total, size; 26 | u_char *prev; 27 | ngx_uint_t n; 28 | struct iovec *iov; 29 | 30 | iov = NULL; 31 | prev = NULL; 32 | total = 0; 33 | n = 0; 34 | 35 | for ( /* void */ ; cl; cl = cl->next) { 36 | 37 | if (ngx_buf_special(cl->buf)) { 38 | continue; 39 | } 40 | 41 | size = cl->buf->last - cl->buf->pos; 42 | 43 | if (prev == cl->buf->pos) { 44 | iov->iov_len += size; 45 | 46 | } else { 47 | if (n == vec->nalloc) { 48 | break; 49 | } 50 | 51 | iov = &vec->iovs[n++]; 52 | 53 | iov->iov_base = (void *) cl->buf->pos; 54 | iov->iov_len = size; 55 | } 56 | 57 | prev = cl->buf->pos + size; 58 | total += size; 59 | } 60 | 61 | vec->count = n; 62 | vec->size = total; 63 | 64 | return cl; 65 | } 66 | 67 | 68 | static ngx_int_t 69 | ngx_http_lua_io_thread_post_task(ngx_thread_task_t *task, 70 | ngx_http_lua_io_file_ctx_t *file_ctx) 71 | { 72 | ngx_http_request_t *r; 73 | 74 | r = file_ctx->request; 75 | 76 | task->event.data = file_ctx; 77 | task->event.handler = file_ctx->handler; 78 | 79 | if (ngx_thread_task_post(file_ctx->thread_pool, task) != NGX_OK) { 80 | return NGX_ERROR; 81 | } 82 | 83 | r->main->blocked++; 84 | r->aio = 1; 85 | 86 | return NGX_OK; 87 | } 88 | 89 | 90 | static void 91 | ngx_http_lua_io_thread_write_chain_to_file(void *data, ngx_log_t *log) 92 | { 93 | ngx_http_lua_io_thread_ctx_t *ctx = data; 94 | 95 | ssize_t n; 96 | ngx_err_t err; 97 | ngx_chain_t *cl; 98 | ngx_iovec_t vec; 99 | struct iovec iovs[NGX_IOVS_PREALLOCATE]; 100 | 101 | vec.iovs = iovs; 102 | vec.nalloc = NGX_IOVS_PREALLOCATE; 103 | 104 | cl = ctx->chain; 105 | 106 | ctx->nbytes = 0; 107 | ctx->err = 0; 108 | 109 | if (cl == NULL && ctx->flush) { 110 | goto flush; 111 | } 112 | 113 | do { 114 | /* create the iovec and coalesce the neighbouring bufs */ 115 | cl = ngx_http_lua_io_chain_to_iovec(&vec, cl); 116 | 117 | eintr: 118 | 119 | n = writev(ctx->fd, iovs, vec.count); 120 | 121 | if (n == -1) { 122 | err = ngx_errno; 123 | 124 | if (err == NGX_EINTR) { 125 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, err, 126 | "writev() was interrupted"); 127 | goto eintr; 128 | } 129 | 130 | ctx->err = err; 131 | return; 132 | } 133 | 134 | if ((size_t) n != vec.size) { 135 | ctx->nbytes = 0; 136 | return; 137 | } 138 | 139 | ctx->nbytes += n; 140 | } while (cl); 141 | 142 | flush: 143 | 144 | if(ctx->flush && fsync(ctx->fd) < 0) { 145 | ctx->err = ngx_errno; 146 | } 147 | } 148 | 149 | 150 | static void 151 | ngx_http_lua_io_thread_read_file(void *data, ngx_log_t *log) 152 | { 153 | ngx_http_lua_io_thread_ctx_t *ctx = data; 154 | 155 | ssize_t n; 156 | size_t size; 157 | 158 | ctx->nbytes = 0; 159 | ctx->err = 0; 160 | ctx->eof = 0; 161 | 162 | size = ctx->size; 163 | if (size == 0) { 164 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, 165 | "lua io thread read zero bytes"); 166 | return; 167 | } 168 | 169 | n = read(ctx->fd, ctx->buf, size); 170 | 171 | if (n == -1) { 172 | ctx->err = ngx_errno; 173 | 174 | } else { 175 | ctx->nbytes = n; 176 | ctx->err = 0; 177 | 178 | if ((size_t) n < size) { 179 | ctx->eof = 1; 180 | } 181 | } 182 | 183 | ngx_log_debug4(NGX_LOG_DEBUG_HTTP, log, 0, 184 | "lua io thread read %z (err: %d) of %uz, eof:%d", 185 | n, ctx->err, size, ctx->eof); 186 | } 187 | 188 | 189 | ngx_int_t 190 | ngx_http_lua_io_thread_post_write_task(ngx_http_lua_io_file_ctx_t *file_ctx, 191 | ngx_chain_t *cl, ngx_int_t flush) 192 | { 193 | ngx_thread_task_t *task; 194 | ngx_http_lua_io_thread_ctx_t *thread_ctx; 195 | ngx_http_request_t *r; 196 | 197 | r = file_ctx->request; 198 | 199 | ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 200 | "lua io thread write chain: %d, %p flush:%d", 201 | file_ctx->fd, cl, flush); 202 | 203 | task = file_ctx->thread_task; 204 | 205 | if (task == NULL) { 206 | task = ngx_thread_task_alloc(r->pool, 207 | sizeof(ngx_http_lua_io_thread_ctx_t)); 208 | if (task == NULL) { 209 | file_ctx->ft_type |= NGX_HTTP_LUA_IO_FT_NO_MEMORY; 210 | return NGX_ERROR; 211 | } 212 | 213 | file_ctx->thread_task = task; 214 | } 215 | 216 | task->handler = ngx_http_lua_io_thread_write_chain_to_file; 217 | 218 | thread_ctx = task->ctx; 219 | thread_ctx->fd = file_ctx->fd; 220 | thread_ctx->chain = cl; 221 | thread_ctx->flush = flush; 222 | 223 | if (ngx_http_lua_io_thread_post_task(task, file_ctx) != NGX_OK) { 224 | file_ctx->ft_type |= NGX_HTTP_LUA_IO_FT_TASK_POST_ERROR; 225 | return NGX_ERROR; 226 | } 227 | 228 | return NGX_OK; 229 | } 230 | 231 | 232 | ngx_int_t 233 | ngx_http_lua_io_thread_post_read_task(ngx_http_lua_io_file_ctx_t *file_ctx, 234 | ngx_buf_t *buf) 235 | { 236 | ngx_thread_task_t *task; 237 | ngx_http_lua_io_thread_ctx_t *thread_ctx; 238 | ngx_http_request_t *r; 239 | 240 | r = file_ctx->request; 241 | 242 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 243 | "lua io thread read: %d", file_ctx->fd); 244 | 245 | task = file_ctx->thread_task; 246 | 247 | if (task == NULL) { 248 | task = ngx_thread_task_alloc(r->pool, 249 | sizeof(ngx_http_lua_io_thread_ctx_t)); 250 | if (task == NULL) { 251 | file_ctx->ft_type |= NGX_HTTP_LUA_IO_FT_NO_MEMORY; 252 | return NGX_ERROR; 253 | } 254 | 255 | file_ctx->thread_task = task; 256 | } 257 | 258 | task->handler = ngx_http_lua_io_thread_read_file; 259 | 260 | thread_ctx = task->ctx; 261 | thread_ctx->fd = file_ctx->fd; 262 | thread_ctx->buf = buf->last; 263 | thread_ctx->size = buf->end - buf->last; 264 | 265 | if (ngx_http_lua_io_thread_post_task(task, file_ctx) != NGX_OK) { 266 | file_ctx->ft_type |= NGX_HTTP_LUA_IO_FT_TASK_POST_ERROR; 267 | return NGX_ERROR; 268 | } 269 | 270 | return NGX_OK; 271 | } 272 | -------------------------------------------------------------------------------- /src/ngx_http_lua_io.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Alex Zhang 4 | */ 5 | 6 | 7 | #ifndef _NGX_HTTP_LUA_IO_H_INCLUDED_ 8 | #define _NGX_HTTP_LUA_IO_H_INCLUDED_ 9 | 10 | 11 | #include 12 | #include 13 | 14 | 15 | #ifdef __GNUC__ 16 | #define NGX_LIKELY(x) __builtin_expect(!!(x), 1) 17 | #define NGX_UNLIKELY(x) __builtin_expect(!!(x), 0) 18 | #else 19 | #define NGX_LIKELY(x) (x) 20 | #define NGX_UNLIKELY(x) (x) 21 | #endif 22 | 23 | #define NGX_HTTP_LUA_IO_FT_CLOSE (1 << 0) 24 | #define NGX_HTTP_LUA_IO_FT_TASK_POST_ERROR (1 << 1) 25 | #define NGX_HTTP_LUA_IO_FT_NO_MEMORY (1 << 2) 26 | 27 | 28 | typedef struct { 29 | ngx_fd_t fd; 30 | 31 | ngx_int_t (*thread_handler)(ngx_thread_task_t *task, 32 | ngx_file_t *file); 33 | ngx_thread_task_t *thread_task; 34 | ngx_thread_pool_t *thread_pool; 35 | 36 | ngx_chain_t *bufs_out; 37 | ngx_chain_t *bufs_in; 38 | ngx_chain_t *buf_in; 39 | ngx_buf_t buffer; 40 | 41 | ngx_int_t (*input_filter)(void *data, ngx_buf_t *buf, 42 | size_t size); 43 | 44 | ngx_err_t error; 45 | 46 | ngx_http_request_t *request; 47 | ngx_http_cleanup_pt *cleanup; 48 | ngx_event_handler_pt handler; 49 | 50 | ngx_http_lua_co_ctx_t *coctx; 51 | 52 | off_t offset; 53 | 54 | int whence; 55 | off_t seek_offset; 56 | 57 | size_t nbytes; 58 | size_t rest; 59 | 60 | unsigned mode; 61 | unsigned ft_type; 62 | 63 | unsigned read_waiting:1; 64 | unsigned write_waiting:1; 65 | unsigned flush_waiting:1; 66 | unsigned seeking:1; 67 | unsigned closing:1; 68 | unsigned closed:1; 69 | unsigned eof:1; 70 | } ngx_http_lua_io_file_ctx_t; 71 | 72 | 73 | typedef struct { 74 | ngx_fd_t fd; 75 | 76 | ngx_chain_t *chain; 77 | off_t offset; 78 | 79 | u_char *buf; 80 | 81 | ngx_err_t err; 82 | size_t nbytes; 83 | size_t size; 84 | 85 | unsigned flush:1; 86 | unsigned eof:1; 87 | } ngx_http_lua_io_thread_ctx_t; 88 | 89 | 90 | ngx_int_t ngx_http_lua_io_thread_post_write_task( 91 | ngx_http_lua_io_file_ctx_t *file_ctx, ngx_chain_t *cl, ngx_int_t flush); 92 | ngx_int_t ngx_http_lua_io_thread_post_read_task( 93 | ngx_http_lua_io_file_ctx_t *file_ctx, ngx_buf_t *buf); 94 | 95 | 96 | #endif /* _NGX_HTTP_LUA_IO_H_INCLUDED_ */ 97 | -------------------------------------------------------------------------------- /src/ngx_http_lua_io_input_filter.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Alex Zhang 4 | */ 5 | 6 | 7 | #include 8 | #include "ngx_http_lua_io.h" 9 | #include "ngx_http_lua_io_input_filter.h" 10 | 11 | 12 | ngx_int_t 13 | ngx_http_lua_io_read_chunk(void *data, ngx_buf_t *buf, size_t size) 14 | { 15 | ngx_http_lua_io_file_ctx_t *file_ctx = data; 16 | 17 | size_t rest; 18 | 19 | rest = file_ctx->rest; 20 | 21 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, file_ctx->request->connection->log, 0, 22 | "lua io read chunk, need:%uz got:%uz", rest, size); 23 | 24 | if (size == 0) { 25 | return NGX_OK; 26 | } 27 | 28 | if (size >= rest) { 29 | buf->pos += rest; 30 | file_ctx->buf_in->buf->last += rest; 31 | file_ctx->rest = 0; 32 | 33 | return NGX_OK; 34 | } 35 | 36 | buf->pos += size; 37 | file_ctx->buf_in->buf->last += size; 38 | file_ctx->rest -= size; 39 | 40 | return NGX_AGAIN; 41 | } 42 | 43 | 44 | ngx_int_t 45 | ngx_http_lua_io_read_line(void *data, ngx_buf_t *buf, size_t size) 46 | { 47 | ngx_http_lua_io_file_ctx_t *file_ctx = data; 48 | 49 | u_char c, *dst; 50 | 51 | #if (NGX_DEBUG) 52 | ngx_http_request_t *r; 53 | #endif 54 | 55 | 56 | dst = file_ctx->buf_in->buf->last; 57 | 58 | #if (NGX_DEBUG) 59 | 60 | r = file_ctx->request; 61 | 62 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 63 | "lua io read line"); 64 | 65 | #endif 66 | 67 | while (size--) { 68 | 69 | c = *buf->pos++; 70 | 71 | switch (c) { 72 | case '\n': 73 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 74 | "lua io read the final linefeed"); 75 | 76 | file_ctx->buf_in->buf->last = dst; 77 | 78 | return NGX_OK; 79 | 80 | case '\r': 81 | /* just ignore this CR */ 82 | break; 83 | 84 | default: 85 | *dst++ = c; 86 | break; 87 | } 88 | } 89 | 90 | file_ctx->buf_in->buf->last = dst; 91 | 92 | return NGX_AGAIN; 93 | } 94 | 95 | 96 | ngx_int_t 97 | ngx_http_lua_io_read_all(void *data, ngx_buf_t *buf, size_t size) 98 | { 99 | ngx_http_lua_io_file_ctx_t *file_ctx = data; 100 | 101 | file_ctx->buf_in->buf->last += size; 102 | buf->pos += size; 103 | 104 | return file_ctx->eof ? NGX_OK : NGX_AGAIN; 105 | } 106 | -------------------------------------------------------------------------------- /src/ngx_http_lua_io_input_filter.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Alex Zhang 4 | */ 5 | 6 | 7 | #ifndef _NGX_HTTP_LUA_IO_INPUT_FILTER_H_INCLUDED_ 8 | #define _NGX_HTTP_LUA_IO_INPUT_FILTER_H_INCLUDED_ 9 | 10 | 11 | ngx_int_t ngx_http_lua_io_read_chunk(void *data, ngx_buf_t *buf, size_t size); 12 | ngx_int_t ngx_http_lua_io_read_line(void *data, ngx_buf_t *buf, size_t size); 13 | ngx_int_t ngx_http_lua_io_read_all(void *data, ngx_buf_t *buf, size_t size); 14 | 15 | 16 | #endif /* _NGX_HTTP_LUA_IO_INPUT_FILTER_H_INCLUDED_ */ 17 | -------------------------------------------------------------------------------- /src/ngx_http_lua_io_module.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Alex Zhang 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "ngx_http_lua_io.h" 15 | #include "ngx_http_lua_io_input_filter.h" 16 | 17 | 18 | #define NGX_HTTP_LUA_IO_FILE_CTX_INDEX 1 19 | 20 | #define NGX_HTTP_LUA_IO_FILE_READ_MODE (1 << 0) 21 | #define NGX_HTTP_LUA_IO_FILE_WRITE_MODE (1 << 1) 22 | #define NGX_HTTP_LUA_IO_FILE_APPEND_MODE (1 << 2) 23 | #define NGX_HTTP_LUA_IO_FILE_CREATE_MODE (1 << 3) 24 | 25 | #define ngx_http_lua_io_check_busy_reading(r, ctx, L) \ 26 | if ((ctx)->read_waiting) { \ 27 | lua_pushnil(L); \ 28 | lua_pushliteral(L, "io busy reading"); \ 29 | return 2; \ 30 | } 31 | 32 | #define ngx_http_lua_io_check_busy_writing(r, ctx, L) \ 33 | if ((ctx)->write_waiting) { \ 34 | lua_pushnil(L); \ 35 | lua_pushliteral(L, "io busy writing"); \ 36 | return 2; \ 37 | } 38 | 39 | #define ngx_http_lua_io_check_busy_flushing(r, ctx, L) \ 40 | if ((ctx)->flush_waiting) { \ 41 | lua_pushnil(L); \ 42 | lua_pushliteral(L, "io busy flushing"); \ 43 | return 2; \ 44 | } 45 | 46 | 47 | typedef struct { 48 | ngx_flag_t log_errors; 49 | size_t read_buf_size; 50 | size_t write_buf_size; 51 | ngx_http_complex_value_t *thread_pool; 52 | } ngx_http_lua_io_loc_conf_t; 53 | 54 | 55 | typedef struct { 56 | ngx_chain_t *free_read_bufs; 57 | ngx_chain_t *free_write_bufs; 58 | } ngx_http_lua_io_ctx_t; 59 | 60 | 61 | static char ngx_http_lua_io_metatable_key; 62 | static char ngx_http_lua_io_file_ctx_metatable_key; 63 | 64 | static ngx_str_t ngx_http_lua_io_thread_pool_default = ngx_string("default"); 65 | static const char* ngx_http_lua_io_seek_list[] = { "set", "cur", "end", NULL }; 66 | static int ngx_http_lua_io_seek_enum[] = { SEEK_SET, SEEK_CUR, SEEK_END }; 67 | 68 | 69 | static int ngx_http_lua_io_open(lua_State *L); 70 | static int ngx_http_lua_io_file_close(lua_State *L); 71 | static int ngx_http_lua_io_file_read(lua_State *L); 72 | static int ngx_http_lua_io_file_write(lua_State *L); 73 | static int ngx_http_lua_io_file_flush(lua_State *L); 74 | static int ngx_http_lua_io_file_seek(lua_State *L); 75 | static int ngx_http_lua_io_file_lines(lua_State *L); 76 | static int ngx_http_lua_io_file_lines_iter(lua_State *L); 77 | static int ngx_http_lua_io_file_destory(lua_State *L); 78 | static void ngx_http_lua_io_file_cleanup(void *data); 79 | static void ngx_http_lua_io_coctx_cleanup(void *data); 80 | static void ngx_http_lua_io_file_finalize(ngx_http_request_t *r, 81 | ngx_http_lua_io_file_ctx_t *ctx); 82 | static void ngx_http_lua_io_thread_event_handler(ngx_event_t *ev); 83 | static void ngx_http_lua_io_content_wev_handler(ngx_http_request_t *r); 84 | static ngx_int_t ngx_http_lua_io_resume(ngx_http_request_t *r); 85 | static void ngx_http_lua_io_before_yield(ngx_http_request_t *r, 86 | ngx_http_lua_io_file_ctx_t *file_ctx); 87 | static ngx_int_t ngx_http_lua_io_prepare_retvals(ngx_http_request_t *r, 88 | ngx_http_lua_io_file_ctx_t *file_ctx, ngx_http_lua_co_ctx_t *coctx); 89 | static int ngx_http_lua_io_file_read_helper(ngx_http_request_t *r, 90 | ngx_http_lua_io_file_ctx_t *file_ctx, lua_State *L); 91 | static int ngx_http_lua_io_submit_input_data(ngx_http_request_t *r, 92 | ngx_http_lua_io_file_ctx_t *file_ctx, lua_State *L); 93 | static ngx_int_t ngx_http_lua_io_file_do_read(ngx_http_request_t *r, 94 | ngx_http_lua_io_file_ctx_t *file_ctx); 95 | static ngx_int_t ngx_http_lua_io_add_input_buffer(ngx_http_request_t *r, 96 | ngx_http_lua_io_file_ctx_t *file_ctx); 97 | static int ngx_http_lua_io_file_do_seek(ngx_http_lua_io_file_ctx_t *file_ctx, 98 | ngx_http_request_t *r, lua_State *L, off_t offset, int whence); 99 | static int ngx_http_lua_io_create_module(lua_State *L); 100 | static void *ngx_http_lua_io_create_loc_conf(ngx_conf_t *cf); 101 | static char *ngx_http_lua_io_merge_loc_conf(ngx_conf_t *cf, void *parent, 102 | void *child); 103 | static char *ngx_http_lua_io_thread_pool(ngx_conf_t *cf, ngx_command_t *cmd, 104 | void *conf); 105 | static ngx_int_t ngx_http_lua_io_extract_mode(ngx_http_lua_io_file_ctx_t *ctx, 106 | ngx_str_t *mode); 107 | static ngx_int_t ngx_http_lua_io_handle_error(lua_State *L, 108 | ngx_http_request_t *r, ngx_http_lua_io_file_ctx_t *ctx); 109 | static ngx_int_t ngx_http_lua_io_init(ngx_conf_t *cf); 110 | 111 | 112 | static ngx_command_t ngx_http_lua_io_commands[] = { 113 | 114 | { ngx_string("lua_io_thread_pool"), 115 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF 116 | |NGX_CONF_TAKE1, 117 | ngx_http_lua_io_thread_pool, 118 | NGX_HTTP_LOC_CONF_OFFSET, 119 | 0, 120 | NULL }, 121 | 122 | { ngx_string("lua_io_log_errors"), 123 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF 124 | |NGX_CONF_TAKE1, 125 | ngx_conf_set_flag_slot, 126 | NGX_HTTP_LOC_CONF_OFFSET, 127 | offsetof(ngx_http_lua_io_loc_conf_t, log_errors), 128 | NULL }, 129 | 130 | { ngx_string("lua_io_read_buffer_size"), 131 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF 132 | |NGX_CONF_TAKE1, 133 | ngx_conf_set_size_slot, 134 | NGX_HTTP_LOC_CONF_OFFSET, 135 | offsetof(ngx_http_lua_io_loc_conf_t, read_buf_size), 136 | NULL }, 137 | 138 | { ngx_string("lua_io_write_buffer_size"), 139 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF 140 | |NGX_CONF_TAKE1, 141 | ngx_conf_set_size_slot, 142 | NGX_HTTP_LOC_CONF_OFFSET, 143 | offsetof(ngx_http_lua_io_loc_conf_t, write_buf_size), 144 | NULL }, 145 | 146 | ngx_null_command 147 | }; 148 | 149 | 150 | static ngx_http_module_t ngx_http_lua_io_module_ctx = { 151 | NULL, /* preconfiguration */ 152 | ngx_http_lua_io_init, /* postconfiguration */ 153 | 154 | NULL, /* create main configuration */ 155 | NULL, /* init main configuration */ 156 | 157 | NULL, /* create server configuration */ 158 | NULL, /* merge server configuration */ 159 | 160 | ngx_http_lua_io_create_loc_conf, /* create location configuration */ 161 | ngx_http_lua_io_merge_loc_conf, /* merge location configuration */ 162 | }; 163 | 164 | 165 | ngx_module_t ngx_http_lua_io_module = { 166 | NGX_MODULE_V1, 167 | &ngx_http_lua_io_module_ctx, /* module context */ 168 | ngx_http_lua_io_commands, /* module directives */ 169 | NGX_HTTP_MODULE, /* module type */ 170 | NULL, /* init master */ 171 | NULL, /* init module */ 172 | NULL, /* init process */ 173 | NULL, /* init thread */ 174 | NULL, /* exit thread */ 175 | NULL, /* exit process */ 176 | NULL, /* exit master */ 177 | NGX_MODULE_V1_PADDING 178 | }; 179 | 180 | 181 | static char * 182 | ngx_http_lua_io_thread_pool(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 183 | { 184 | ngx_http_lua_io_loc_conf_t *iocf = conf; 185 | 186 | ngx_http_compile_complex_value_t ccv; 187 | ngx_str_t *value; 188 | 189 | if (iocf->thread_pool != NULL) { 190 | return "is duplicate"; 191 | } 192 | 193 | iocf->thread_pool = ngx_pcalloc(cf->pool, sizeof(ngx_http_complex_value_t)); 194 | if (iocf->thread_pool == NULL) { 195 | return NGX_CONF_ERROR; 196 | } 197 | 198 | ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); 199 | 200 | value = cf->args->elts; 201 | 202 | ccv.cf = cf; 203 | ccv.value = &value[1]; 204 | ccv.complex_value = iocf->thread_pool; 205 | 206 | if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { 207 | return NGX_CONF_ERROR; 208 | } 209 | 210 | return NGX_CONF_OK; 211 | } 212 | 213 | 214 | static void * 215 | ngx_http_lua_io_create_loc_conf(ngx_conf_t *cf) 216 | { 217 | ngx_http_lua_io_loc_conf_t *iocf; 218 | 219 | iocf = ngx_pcalloc(cf->pool, sizeof(ngx_http_lua_io_loc_conf_t)); 220 | if (iocf == NULL) { 221 | return NULL; 222 | } 223 | 224 | /* 225 | * set by ngx_pcalloc(): 226 | * 227 | * iocf->thread_pool = { 0, NULL }; 228 | * iocf->thread_pool_lengths = NULL; 229 | * iocf->thread_pool_values = NULL; 230 | */ 231 | 232 | iocf->write_buf_size = NGX_CONF_UNSET_SIZE; 233 | iocf->read_buf_size = NGX_CONF_UNSET_SIZE; 234 | iocf->log_errors = NGX_CONF_UNSET; 235 | 236 | return iocf; 237 | } 238 | 239 | 240 | static char * 241 | ngx_http_lua_io_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 242 | { 243 | ngx_http_lua_io_loc_conf_t *prev = parent; 244 | ngx_http_lua_io_loc_conf_t *conf = child; 245 | 246 | ngx_conf_merge_size_value(conf->read_buf_size, prev->read_buf_size, 247 | ngx_pagesize); 248 | ngx_conf_merge_size_value(conf->write_buf_size, prev->write_buf_size, 249 | ngx_pagesize); 250 | ngx_conf_merge_value(conf->log_errors, prev->log_errors, 0); 251 | 252 | if (conf->thread_pool == NULL) { 253 | conf->thread_pool = prev->thread_pool; 254 | } 255 | 256 | if (conf->thread_pool 257 | && conf->thread_pool->lengths == NULL 258 | && ngx_thread_pool_get(cf->cycle, &conf->thread_pool->value) == NULL) 259 | { 260 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 261 | "thread pool \"%V\" not found", 262 | &conf->thread_pool->value); 263 | 264 | return NGX_CONF_ERROR; 265 | } 266 | 267 | return NGX_CONF_OK; 268 | } 269 | 270 | 271 | static ngx_int_t 272 | ngx_http_lua_io_init(ngx_conf_t *cf) 273 | { 274 | if (ngx_http_lua_add_package_preload(cf, "ngx.io", 275 | ngx_http_lua_io_create_module) 276 | != NGX_OK) 277 | { 278 | return NGX_ERROR; 279 | } 280 | 281 | return NGX_OK; 282 | } 283 | 284 | 285 | static int 286 | ngx_http_lua_io_create_module(lua_State *L) 287 | { 288 | lua_createtable(L, 0 /* narr */, 2 /* nrec */); 289 | 290 | lua_pushcfunction(L, ngx_http_lua_io_open); 291 | lua_setfield(L, -2, "open"); 292 | 293 | /* io file object metatable */ 294 | lua_pushlightuserdata(L, &ngx_http_lua_io_metatable_key); 295 | lua_createtable(L, 0 /* narr */, 6 /* nrec */); 296 | 297 | lua_pushcfunction(L, ngx_http_lua_io_file_close); 298 | lua_setfield(L, -2, "close"); 299 | 300 | lua_pushcfunction(L, ngx_http_lua_io_file_read); 301 | lua_setfield(L, -2, "read"); 302 | 303 | lua_pushcfunction(L, ngx_http_lua_io_file_write); 304 | lua_setfield(L, -2, "write"); 305 | 306 | lua_pushcfunction(L, ngx_http_lua_io_file_flush); 307 | lua_setfield(L, -2, "flush"); 308 | 309 | lua_pushcfunction(L, ngx_http_lua_io_file_seek); 310 | lua_setfield(L, -2, "seek"); 311 | 312 | lua_pushcfunction(L, ngx_http_lua_io_file_lines); 313 | lua_setfield(L, -2, "lines"); 314 | 315 | lua_pushvalue(L, -1); 316 | lua_setfield(L, -2, "__index"); 317 | 318 | lua_rawset(L, LUA_REGISTRYINDEX); 319 | 320 | /* io file object ctx metatable */ 321 | lua_pushlightuserdata(L, &ngx_http_lua_io_file_ctx_metatable_key); 322 | lua_createtable(L, 0 /* narr */, 1 /* nrec */); 323 | 324 | lua_pushcfunction(L, ngx_http_lua_io_file_destory); 325 | lua_setfield(L, -2, "__gc"); 326 | 327 | lua_rawset(L, LUA_REGISTRYINDEX); 328 | 329 | return 1; 330 | } 331 | 332 | 333 | static ngx_int_t 334 | ngx_http_lua_io_extract_mode(ngx_http_lua_io_file_ctx_t *ctx, 335 | ngx_str_t *mode) 336 | { 337 | char ch; 338 | ngx_int_t flags, plus; 339 | 340 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0, 341 | "lua io open mode:\"%V\"", mode); 342 | 343 | plus = 0; 344 | 345 | if (mode->len > 2 || mode->len < 1) { 346 | return NGX_ERROR; 347 | 348 | } else if (mode->len == 2) { 349 | if (mode->data[1] != '+') { 350 | return NGX_ERROR; 351 | } 352 | 353 | plus = 1; 354 | } 355 | 356 | flags = 0; 357 | ch = mode->data[0]; 358 | 359 | switch (ch) { 360 | 361 | case 'r': 362 | flags = O_RDONLY; 363 | ctx->mode |= NGX_HTTP_LUA_IO_FILE_READ_MODE; 364 | break; 365 | 366 | case 'w': 367 | flags = O_WRONLY; 368 | ctx->mode |= NGX_HTTP_LUA_IO_FILE_WRITE_MODE; 369 | ctx->mode |= NGX_HTTP_LUA_IO_FILE_CREATE_MODE; 370 | break; 371 | 372 | case 'a': 373 | flags = O_WRONLY|O_APPEND; 374 | ctx->mode |= NGX_HTTP_LUA_IO_FILE_APPEND_MODE; 375 | ctx->mode |= NGX_HTTP_LUA_IO_FILE_WRITE_MODE; 376 | ctx->mode |= NGX_HTTP_LUA_IO_FILE_CREATE_MODE; 377 | break; 378 | 379 | default: 380 | return NGX_ERROR; 381 | } 382 | 383 | if (plus) { 384 | flags = O_RDWR; 385 | if (ch == 'a') { 386 | flags |= O_APPEND; 387 | } 388 | 389 | ctx->mode |= NGX_HTTP_LUA_IO_FILE_WRITE_MODE; 390 | ctx->mode |= NGX_HTTP_LUA_IO_FILE_READ_MODE; 391 | } 392 | 393 | if (ch != 'a' && (ctx->mode & NGX_HTTP_LUA_IO_FILE_WRITE_MODE)) { 394 | flags |= O_TRUNC; 395 | } 396 | 397 | return flags; 398 | } 399 | 400 | 401 | static ngx_thread_pool_t * 402 | ngx_http_lua_io_get_thread_pool(ngx_http_request_t *r) 403 | { 404 | ngx_str_t name; 405 | ngx_http_lua_io_loc_conf_t *iocf; 406 | 407 | iocf = ngx_http_get_module_loc_conf(r, ngx_http_lua_io_module); 408 | 409 | if (iocf->thread_pool == NULL) { 410 | name = ngx_http_lua_io_thread_pool_default; 411 | 412 | } else { 413 | if (ngx_http_complex_value(r, iocf->thread_pool, &name) != NGX_OK) { 414 | return NULL; 415 | } 416 | } 417 | 418 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 419 | "lua io use thread pool \"%V\"", &name); 420 | 421 | return ngx_thread_pool_get((ngx_cycle_t *) ngx_cycle, &name); 422 | } 423 | 424 | 425 | static int 426 | ngx_http_lua_io_open(lua_State *L) 427 | { 428 | off_t offset; 429 | ngx_str_t path, modestr; 430 | ngx_int_t mode, n, create; 431 | ngx_http_request_t *r; 432 | ngx_http_cleanup_t *cln; 433 | ngx_http_lua_ctx_t *ctx; 434 | ngx_http_lua_io_ctx_t *ioctx; 435 | ngx_http_lua_io_file_ctx_t *file_ctx; 436 | 437 | n = lua_gettop(L); 438 | 439 | if (NGX_UNLIKELY(n != 1 && n != 2)) { 440 | return luaL_error(L, "expecting 1 or 2 arguments, but got %s", n); 441 | } 442 | 443 | if (n == 2) { 444 | path.data = (u_char *) luaL_checklstring(L, -2, &path.len); 445 | modestr.data = (u_char *) luaL_checklstring(L, -1, &modestr.len); 446 | 447 | } else { 448 | path.data = (u_char *) luaL_checklstring(L, 1, &path.len); 449 | modestr.len = 1; 450 | modestr.data = (u_char *) "r"; 451 | } 452 | 453 | r = ngx_http_lua_get_request(L); 454 | if (NGX_UNLIKELY(r == NULL)) { 455 | return luaL_error(L, "no request found"); 456 | } 457 | 458 | ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); 459 | if (NGX_UNLIKELY(ctx == NULL)) { 460 | return luaL_error(L, "no ctx found"); 461 | } 462 | 463 | ioctx = ngx_http_get_module_ctx(r, ngx_http_lua_io_module); 464 | if (ioctx == NULL) { 465 | ioctx = ngx_pcalloc(r->pool, sizeof(ngx_http_lua_io_ctx_t)); 466 | if (ioctx == NULL) { 467 | return luaL_error(L, "no memory"); 468 | } 469 | 470 | ngx_http_set_ctx(r, ioctx, ngx_http_lua_io_module); 471 | } 472 | 473 | ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE 474 | |NGX_HTTP_LUA_CONTEXT_ACCESS 475 | |NGX_HTTP_LUA_CONTEXT_CONTENT 476 | |NGX_HTTP_LUA_CONTEXT_TIMER 477 | |NGX_HTTP_LUA_CONTEXT_SSL_CERT 478 | |NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH); 479 | 480 | if (ngx_get_full_name(r->pool, (ngx_str_t *) &ngx_cycle->prefix, &path) 481 | != NGX_OK) 482 | { 483 | return luaL_error(L, "no memory"); 484 | } 485 | 486 | lua_createtable(L, 1 /* narr */, 1 /* nrec */); 487 | 488 | lua_pushlightuserdata(L, &ngx_http_lua_io_metatable_key); 489 | lua_rawget(L, LUA_REGISTRYINDEX); 490 | lua_setmetatable(L, -2); 491 | 492 | file_ctx = lua_newuserdata(L, sizeof(ngx_http_lua_io_file_ctx_t)); 493 | if (NGX_UNLIKELY(file_ctx == NULL)) { 494 | return luaL_error(L, "no memory"); 495 | } 496 | 497 | lua_pushlightuserdata(L, &ngx_http_lua_io_file_ctx_metatable_key); 498 | lua_rawget(L, LUA_REGISTRYINDEX); 499 | lua_setmetatable(L, -2); 500 | 501 | lua_rawseti(L, -2, NGX_HTTP_LUA_IO_FILE_CTX_INDEX); 502 | 503 | ngx_memzero(file_ctx, sizeof(ngx_http_lua_io_file_ctx_t)); 504 | 505 | file_ctx->thread_pool = ngx_http_lua_io_get_thread_pool(r); 506 | if (NGX_UNLIKELY(file_ctx->thread_pool == NULL)) { 507 | return luaL_error(L, "no thread pool found"); 508 | } 509 | 510 | file_ctx->request = r; 511 | file_ctx->fd = NGX_INVALID_FILE; 512 | 513 | cln = ngx_http_lua_cleanup_add(r, 0); 514 | if (cln == NULL) { 515 | lua_pushnil(L); 516 | lua_pushliteral(L, "no memory"); 517 | return 2; 518 | } 519 | 520 | cln->handler = ngx_http_lua_io_file_cleanup; 521 | cln->data = file_ctx; 522 | 523 | file_ctx->cleanup = &cln->handler; 524 | file_ctx->handler = ngx_http_lua_io_thread_event_handler; 525 | 526 | mode = ngx_http_lua_io_extract_mode(file_ctx, &modestr); 527 | if (NGX_UNLIKELY(mode == NGX_ERROR)) { 528 | lua_pushnil(L); 529 | lua_pushliteral(L, "bad open mode"); 530 | return 2; 531 | } 532 | 533 | create = (file_ctx->mode & NGX_HTTP_LUA_IO_FILE_CREATE_MODE) ? O_CREAT : 0; 534 | 535 | ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 536 | "lua io open \"%V\" r:%d w:%d a:%d", 537 | &path, (file_ctx->mode & NGX_HTTP_LUA_IO_FILE_READ_MODE) != 0, 538 | (file_ctx->mode & NGX_HTTP_LUA_IO_FILE_WRITE_MODE) != 0, 539 | (file_ctx->mode & NGX_HTTP_LUA_IO_FILE_APPEND_MODE) != 0); 540 | 541 | file_ctx->fd = ngx_open_file(path.data, mode, create, S_IRUSR|S_IWUSR|S_IRGRP 542 | |S_IWGRP|S_IROTH|S_IWOTH); 543 | 544 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 545 | "lua io open fd:%d", file_ctx->fd); 546 | 547 | if (file_ctx->fd == NGX_INVALID_FILE) { 548 | file_ctx->error = ngx_errno; 549 | return ngx_http_lua_io_handle_error(L, r, file_ctx); 550 | } 551 | 552 | if ((file_ctx->mode & NGX_HTTP_LUA_IO_FILE_APPEND_MODE) != 0) { 553 | 554 | offset = lseek(file_ctx->fd, 0, SEEK_END); 555 | if (NGX_UNLIKELY(offset < 0)) { 556 | file_ctx->error = ngx_errno; 557 | return ngx_http_lua_io_handle_error(L, r, file_ctx); 558 | } 559 | 560 | file_ctx->offset = offset; 561 | } 562 | 563 | return 1; 564 | } 565 | 566 | 567 | static int 568 | ngx_http_lua_io_file_close(lua_State *L) 569 | { 570 | ngx_http_request_t *r; 571 | ngx_http_lua_io_file_ctx_t *ctx; 572 | 573 | if (NGX_UNLIKELY(lua_gettop(L) != 1)) { 574 | return luaL_error(L, "expecting only one argument (the object), " 575 | "but got %d", lua_gettop(L)); 576 | } 577 | 578 | r = ngx_http_lua_get_request(L); 579 | if (NGX_UNLIKELY(r == NULL)) { 580 | return luaL_error(L, "no request found"); 581 | } 582 | 583 | luaL_checktype(L, 1, LUA_TTABLE); 584 | 585 | lua_rawgeti(L, 1, NGX_HTTP_LUA_IO_FILE_CTX_INDEX); 586 | ctx = lua_touserdata(L, -1); 587 | lua_pop(L, 1); 588 | 589 | if (ctx == NULL || ctx->closed) { 590 | lua_pushnil(L); 591 | lua_pushliteral(L, "closed"); 592 | return 2; 593 | } 594 | 595 | if (ctx->request != r) { 596 | return luaL_error(L, "bad request"); 597 | } 598 | 599 | ngx_http_lua_io_check_busy_reading(r, ctx, L); 600 | ngx_http_lua_io_check_busy_writing(r, ctx, L); 601 | ngx_http_lua_io_check_busy_flushing(r, ctx, L); 602 | 603 | if (!ctx->bufs_out) { 604 | ngx_http_lua_io_file_finalize(r, ctx); 605 | 606 | if (ctx->ft_type || ctx->error) { 607 | return ngx_http_lua_io_handle_error(L, r, ctx); 608 | } 609 | 610 | lua_pushinteger(L, 1); 611 | return 1; 612 | } 613 | 614 | /* flush the legacy buffer */ 615 | 616 | if (NGX_UNLIKELY(ngx_http_lua_io_thread_post_write_task(ctx, ctx->bufs_out, 617 | 0) 618 | == NGX_ERROR)) 619 | { 620 | return ngx_http_lua_io_handle_error(L, r, ctx); 621 | } 622 | 623 | ctx->closing = 1; 624 | ctx->bufs_out = NULL; 625 | ctx->flush_waiting = 1; 626 | 627 | ngx_http_lua_io_before_yield(r, ctx); 628 | 629 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 630 | "lua io close flushing saved co ctx:%p", ctx->coctx); 631 | 632 | return lua_yield(L, 0); 633 | } 634 | 635 | 636 | static int 637 | ngx_http_lua_io_file_read(lua_State *L) 638 | { 639 | int n, type; 640 | const char *p; 641 | lua_Integer bytes; 642 | ngx_str_t pat; 643 | ngx_http_request_t *r; 644 | ngx_http_lua_io_file_ctx_t *file_ctx; 645 | ngx_http_lua_io_loc_conf_t *iocf; 646 | 647 | n = lua_gettop(L); 648 | if (NGX_UNLIKELY(n != 1 && n != 2)) { 649 | return luaL_error(L, "expecting two arguments (including the object), ", 650 | "but got %d", n); 651 | } 652 | 653 | r = ngx_http_lua_get_request(L); 654 | if (NGX_UNLIKELY(r == NULL)) { 655 | return luaL_error(L, "no request found"); 656 | } 657 | 658 | luaL_checktype(L, 1, LUA_TTABLE); 659 | lua_rawgeti(L, 1, NGX_HTTP_LUA_IO_FILE_CTX_INDEX); 660 | 661 | file_ctx = lua_touserdata(L, -1); 662 | lua_pop(L, 1); 663 | 664 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 665 | "lua io write, ctx:%p", file_ctx); 666 | 667 | iocf = ngx_http_get_module_loc_conf(r, ngx_http_lua_io_module); 668 | 669 | if (file_ctx == NULL || file_ctx->closed) { 670 | if (iocf->log_errors) { 671 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 672 | "attempt to read data from a closed file object"); 673 | } 674 | 675 | lua_pushnil(L); 676 | lua_pushliteral(L, "closed"); 677 | return 2; 678 | } 679 | 680 | if (NGX_UNLIKELY(file_ctx->request != r)) { 681 | return luaL_error(L, "bad request"); 682 | } 683 | 684 | ngx_http_lua_io_check_busy_reading(r, file_ctx, L); 685 | ngx_http_lua_io_check_busy_writing(r, file_ctx, L); 686 | ngx_http_lua_io_check_busy_flushing(r, file_ctx, L); 687 | 688 | if (NGX_UNLIKELY(!(file_ctx->mode & NGX_HTTP_LUA_IO_FILE_READ_MODE))) { 689 | /* FIXME need to be compatible with libc? */ 690 | lua_pushnil(L); 691 | lua_pushliteral(L, "operation not permitted"); 692 | return 2; 693 | } 694 | 695 | if (n > 1) { 696 | 697 | type = lua_type(L, 2); 698 | switch (type) { 699 | case LUA_TNUMBER: 700 | bytes = lua_tointeger(L, 2); 701 | if (NGX_UNLIKELY(bytes < 0)) { 702 | return luaL_argerror(L, 2, "bad pattern argument"); 703 | } 704 | 705 | if (NGX_UNLIKELY(bytes == 0)) { 706 | lua_pushliteral(L, ""); 707 | return 1; 708 | } 709 | 710 | file_ctx->input_filter = ngx_http_lua_io_read_chunk; 711 | file_ctx->rest = (size_t) bytes; 712 | 713 | break; 714 | 715 | case LUA_TSTRING: 716 | pat.data = (u_char *) luaL_checklstring(L, 2, &pat.len); 717 | if (NGX_UNLIKELY(pat.len != 2 || pat.data[0] != '*')) { 718 | p = lua_pushfstring(L, "bad pattern argument: %s", pat.data); 719 | return luaL_argerror(L, 2, p); 720 | } 721 | 722 | switch (pat.data[1]) { 723 | case 'l': 724 | file_ctx->input_filter = ngx_http_lua_io_read_line; 725 | break; 726 | 727 | case 'a': 728 | file_ctx->input_filter = ngx_http_lua_io_read_all; 729 | break; 730 | 731 | default: 732 | p = lua_pushfstring(L, "bad pattern argument: %s", pat.data); 733 | return luaL_argerror(L, 2, p); 734 | } 735 | 736 | file_ctx->rest = 0; 737 | break; 738 | 739 | default: 740 | return luaL_argerror(L, 2, "bad pattern argument"); 741 | } 742 | 743 | } else { 744 | file_ctx->input_filter = ngx_http_lua_io_read_line; 745 | file_ctx->rest = 0; 746 | } 747 | 748 | return ngx_http_lua_io_file_read_helper(r, file_ctx, L); 749 | } 750 | 751 | 752 | static int 753 | ngx_http_lua_io_file_write(lua_State *L) 754 | { 755 | int type; 756 | size_t len, size; 757 | u_char *p; 758 | const char *errmsg; 759 | ngx_chain_t *cl, *out; 760 | ngx_buf_t *b; 761 | ngx_http_lua_io_loc_conf_t *iocf; 762 | ngx_http_lua_io_ctx_t *ioctx; 763 | ngx_http_lua_io_file_ctx_t *file_ctx; 764 | ngx_http_request_t *r; 765 | 766 | if (NGX_UNLIKELY(lua_gettop(L) != 2)) { 767 | return luaL_error(L, "expecting two arguments (including the object), ", 768 | "but got %d", lua_gettop(L)); 769 | } 770 | 771 | r = ngx_http_lua_get_request(L); 772 | if (NGX_UNLIKELY(r == NULL)) { 773 | return luaL_error(L, "no request found"); 774 | } 775 | 776 | luaL_checktype(L, 1, LUA_TTABLE); 777 | lua_rawgeti(L, 1, NGX_HTTP_LUA_IO_FILE_CTX_INDEX); 778 | 779 | file_ctx = lua_touserdata(L, -1); 780 | lua_pop(L, 1); 781 | 782 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 783 | "lua io write, ctx:%p", file_ctx); 784 | 785 | iocf = ngx_http_get_module_loc_conf(r, ngx_http_lua_io_module); 786 | 787 | if (file_ctx == NULL || file_ctx->closed) { 788 | if (iocf->log_errors) { 789 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 790 | "attempt to write data on a closed file object"); 791 | } 792 | 793 | lua_pushnil(L); 794 | lua_pushliteral(L, "closed"); 795 | return 2; 796 | } 797 | 798 | if (NGX_UNLIKELY(file_ctx->request != r)) { 799 | return luaL_error(L, "bad request"); 800 | } 801 | 802 | ngx_http_lua_io_check_busy_reading(r, file_ctx, L); 803 | ngx_http_lua_io_check_busy_writing(r, file_ctx, L); 804 | ngx_http_lua_io_check_busy_flushing(r, file_ctx, L); 805 | 806 | if (NGX_UNLIKELY(!(file_ctx->mode & NGX_HTTP_LUA_IO_FILE_WRITE_MODE))) { 807 | 808 | /* FIXME need to be compatible with libc? */ 809 | lua_pushnil(L); 810 | lua_pushliteral(L, "operation not permitted"); 811 | return 2; 812 | } 813 | 814 | len = 0; 815 | 816 | type = lua_type(L, 2); 817 | switch (type) { 818 | case LUA_TNUMBER: 819 | /* fallthrough */ 820 | case LUA_TSTRING: 821 | lua_tolstring(L, 2, &len); 822 | break; 823 | 824 | case LUA_TTABLE: 825 | len = ngx_http_lua_calc_strlen_in_table(L, 2, 2, 1 /* strict */); 826 | break; 827 | 828 | case LUA_TBOOLEAN: 829 | if (lua_toboolean(L, 2)) { 830 | len = sizeof("true") - 1; 831 | 832 | } else { 833 | len = sizeof("false") - 1; 834 | } 835 | 836 | break; 837 | 838 | case LUA_TNIL: 839 | len = sizeof("nil") - 1; 840 | break; 841 | 842 | default: 843 | errmsg = lua_pushfstring(L, "string, number, boolean, nil" 844 | " or array table expected, got %s", 845 | lua_typename(L, type)); 846 | 847 | return luaL_argerror(L, 2, errmsg); 848 | } 849 | 850 | if (len == 0) { 851 | lua_pushinteger(L, 0); 852 | return 1; 853 | } 854 | 855 | out = NULL; 856 | 857 | ioctx = ngx_http_get_module_ctx(r, ngx_http_lua_io_module); 858 | size = iocf->write_buf_size; 859 | 860 | if (size == 0) { 861 | cl = ngx_http_lua_chain_get_free_buf(r->connection->log, r->pool, 862 | &ioctx->free_write_bufs, len); 863 | if (NGX_UNLIKELY(cl == NULL)) { 864 | return luaL_error(L, "no memory"); 865 | } 866 | 867 | out = cl; 868 | 869 | } else { 870 | cl = file_ctx->bufs_out; 871 | if (cl == NULL || (size_t) (cl->buf->end - cl->buf->last) < len) { 872 | 873 | cl = ngx_http_lua_chain_get_free_buf(r->connection->log, r->pool, 874 | &ioctx->free_write_bufs, 875 | size > len ? size : len); 876 | 877 | if (NGX_UNLIKELY(cl == NULL)) { 878 | return luaL_error(L, "no memory"); 879 | } 880 | 881 | if (file_ctx->bufs_out) { 882 | out = file_ctx->bufs_out; 883 | } 884 | 885 | if (len > size) { 886 | if (out) { 887 | out->next = cl; 888 | 889 | } else { 890 | out = cl; 891 | } 892 | 893 | file_ctx->bufs_out = NULL; 894 | 895 | } else { 896 | file_ctx->bufs_out = cl; 897 | } 898 | } 899 | } 900 | 901 | b = cl->buf; 902 | 903 | switch (type) { 904 | case LUA_TNUMBER: 905 | /* fallthrough */ 906 | case LUA_TSTRING: 907 | p = (u_char *) lua_tolstring(L, -1, &len); 908 | b->last = ngx_copy(b->last, (u_char *) p, len); 909 | break; 910 | 911 | case LUA_TTABLE: 912 | b->last = ngx_http_lua_copy_str_in_table(L, -1, b->last); 913 | break; 914 | 915 | case LUA_TBOOLEAN: 916 | if (lua_toboolean(L, 2)) { 917 | *b->last++ = 't'; 918 | *b->last++ = 'r'; 919 | *b->last++ = 'u'; 920 | *b->last++ = 'e'; 921 | 922 | } else { 923 | *b->last++ = 'f'; 924 | *b->last++ = 'a'; 925 | *b->last++ = 'l'; 926 | *b->last++ = 's'; 927 | *b->last++ = 'e'; 928 | } 929 | 930 | break; 931 | 932 | case LUA_TNIL: 933 | *b->last++ = 'n'; 934 | *b->last++ = 'i'; 935 | *b->last++ = 'l'; 936 | break; 937 | 938 | default: 939 | return luaL_error(L, "impossible to reach here"); 940 | } 941 | 942 | if (out == NULL) { 943 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 944 | "lua io write cache"); 945 | lua_pushinteger(L, len); 946 | return 1; 947 | } 948 | 949 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 950 | "lua io write through"); 951 | 952 | if (NGX_UNLIKELY(ngx_http_lua_io_thread_post_write_task(file_ctx, out, 0) 953 | == NGX_ERROR)) 954 | { 955 | return ngx_http_lua_io_handle_error(L, r, file_ctx); 956 | } 957 | 958 | file_ctx->nbytes = 0; 959 | for ( /* void */ ; cl; cl = cl->next) { 960 | file_ctx->nbytes += ngx_buf_size(cl->buf); 961 | } 962 | 963 | file_ctx->write_waiting = 1; 964 | 965 | ngx_http_lua_io_before_yield(r, file_ctx); 966 | 967 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 968 | "lua io write saved co ctx:%p", file_ctx->coctx); 969 | 970 | return lua_yield(L, 0); 971 | } 972 | 973 | 974 | static int 975 | ngx_http_lua_io_file_flush(lua_State *L) 976 | { 977 | int n; 978 | ngx_int_t full; 979 | ngx_http_request_t *r; 980 | ngx_http_lua_io_file_ctx_t *file_ctx; 981 | ngx_http_lua_io_loc_conf_t *iocf; 982 | 983 | n = lua_gettop(L); 984 | 985 | if (NGX_UNLIKELY(n != 1 && n != 2)) { 986 | return luaL_error(L, "expecting one or two arguments, but got %d", n); 987 | } 988 | 989 | r = ngx_http_lua_get_request(L); 990 | if (NGX_UNLIKELY(r == NULL)) { 991 | return luaL_error(L, "no request found"); 992 | } 993 | 994 | full = 0; 995 | if (n == 2) { 996 | full = lua_toboolean(L, 1); 997 | lua_pop(L, 1); 998 | } 999 | 1000 | luaL_checktype(L, 1, LUA_TTABLE); 1001 | lua_rawgeti(L, 1, NGX_HTTP_LUA_IO_FILE_CTX_INDEX); 1002 | 1003 | file_ctx = lua_touserdata(L, -1); 1004 | lua_pop(L, 1); 1005 | 1006 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1007 | "lua io flush"); 1008 | 1009 | if (file_ctx == NULL || file_ctx->closed) { 1010 | iocf = ngx_http_get_module_loc_conf(r, ngx_http_lua_io_module); 1011 | if (iocf->log_errors) { 1012 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 1013 | "attempt to flush data on a closed file object"); 1014 | } 1015 | 1016 | lua_pushnil(L); 1017 | lua_pushliteral(L, "closed"); 1018 | return 2; 1019 | } 1020 | 1021 | if (NGX_UNLIKELY(r != file_ctx->request)) { 1022 | lua_pushnil(L); 1023 | lua_pushliteral(L, "bad request"); 1024 | return 2; 1025 | } 1026 | 1027 | ngx_http_lua_io_check_busy_reading(r, file_ctx, L); 1028 | ngx_http_lua_io_check_busy_writing(r, file_ctx, L); 1029 | ngx_http_lua_io_check_busy_flushing(r, file_ctx, L); 1030 | 1031 | if (NGX_UNLIKELY(!(file_ctx->mode & NGX_HTTP_LUA_IO_FILE_WRITE_MODE))) { 1032 | 1033 | /* FIXME need to be compatible with libc? */ 1034 | lua_pushnil(L); 1035 | lua_pushliteral(L, "operation not permitted"); 1036 | return 2; 1037 | } 1038 | 1039 | if (NGX_UNLIKELY(ngx_http_lua_io_thread_post_write_task(file_ctx, 1040 | file_ctx->bufs_out, 1041 | full) 1042 | == NGX_ERROR)) 1043 | { 1044 | return ngx_http_lua_io_handle_error(L, r, file_ctx); 1045 | } 1046 | 1047 | ngx_http_lua_io_before_yield(r, file_ctx); 1048 | 1049 | file_ctx->flush_waiting = 1; 1050 | file_ctx->bufs_out = NULL; 1051 | 1052 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1053 | "lua io flush saved co ctx:%p", file_ctx->coctx); 1054 | 1055 | return lua_yield(L, 0); 1056 | } 1057 | 1058 | 1059 | static int 1060 | ngx_http_lua_io_file_seek(lua_State *L) 1061 | { 1062 | off_t offset; 1063 | int n, whence, opt; 1064 | ngx_chain_t **ll, *cl; 1065 | ngx_http_request_t *r; 1066 | ngx_http_lua_io_ctx_t *ioctx; 1067 | ngx_http_lua_io_loc_conf_t *iocf; 1068 | ngx_http_lua_io_file_ctx_t *file_ctx; 1069 | 1070 | n = lua_gettop(L); 1071 | 1072 | if (NGX_UNLIKELY(n > 3)) { 1073 | return luaL_error(L, "expecting one, two or three arguments," 1074 | " but got %d", n); 1075 | } 1076 | 1077 | r = ngx_http_lua_get_request(L); 1078 | if (NGX_UNLIKELY(r == NULL)) { 1079 | return luaL_error(L, "no request found"); 1080 | } 1081 | 1082 | luaL_checktype(L, 1, LUA_TTABLE); 1083 | lua_rawgeti(L, 1, NGX_HTTP_LUA_IO_FILE_CTX_INDEX); 1084 | 1085 | file_ctx = lua_touserdata(L, -1); 1086 | lua_pop(L, 1); 1087 | 1088 | whence = SEEK_CUR; 1089 | offset = 0; 1090 | 1091 | if (n >= 2) { 1092 | opt = luaL_checkoption(L, 2, "cur", ngx_http_lua_io_seek_list); 1093 | whence = ngx_http_lua_io_seek_enum[opt]; 1094 | 1095 | if (n >= 3) { 1096 | offset = (off_t) luaL_checkinteger(L, 3); 1097 | } 1098 | } 1099 | 1100 | if (file_ctx == NULL || file_ctx->closed) { 1101 | iocf = ngx_http_get_module_loc_conf(r, ngx_http_lua_io_module); 1102 | if (iocf->log_errors) { 1103 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 1104 | "attempt to flush data on a closed file object"); 1105 | } 1106 | 1107 | lua_pushnil(L); 1108 | lua_pushliteral(L, "closed"); 1109 | return 2; 1110 | } 1111 | 1112 | ngx_http_lua_io_check_busy_reading(r, file_ctx, L); 1113 | ngx_http_lua_io_check_busy_writing(r, file_ctx, L); 1114 | ngx_http_lua_io_check_busy_flushing(r, file_ctx, L); 1115 | 1116 | if (NGX_UNLIKELY(r != file_ctx->request)) { 1117 | return luaL_error(L, "bad request"); 1118 | } 1119 | 1120 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1121 | "lua io seek whence:%d offset:%O", whence, offset); 1122 | 1123 | if (file_ctx->bufs_in) { 1124 | 1125 | /* FIXME keep these buffers and use them in the proper timing? */ 1126 | 1127 | ioctx = ngx_http_get_module_ctx(r, ngx_http_lua_io_module); 1128 | 1129 | ll = &file_ctx->bufs_in; 1130 | for (cl = file_ctx->bufs_in; cl; cl = cl->next) { 1131 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1132 | "lua io seek drain read chain:%p next:%p", 1133 | cl, cl->next); 1134 | 1135 | cl->buf->pos = cl->buf->last; 1136 | ll = &cl->next; 1137 | } 1138 | 1139 | *ll = ioctx->free_read_bufs; 1140 | ioctx->free_read_bufs = file_ctx->bufs_in; 1141 | 1142 | file_ctx->bufs_in = NULL; 1143 | file_ctx->buf_in = NULL; 1144 | 1145 | ngx_memzero(&file_ctx->buffer, sizeof(ngx_buf_t)); 1146 | } 1147 | 1148 | if (file_ctx->bufs_out == NULL) { 1149 | goto seek; 1150 | } 1151 | 1152 | if (NGX_UNLIKELY(ngx_http_lua_io_thread_post_write_task(file_ctx, 1153 | file_ctx->bufs_out, 1154 | 0) 1155 | == NGX_ERROR)) 1156 | { 1157 | return ngx_http_lua_io_handle_error(L, r, file_ctx); 1158 | } 1159 | 1160 | file_ctx->write_waiting = 1; 1161 | file_ctx->seeking = 1; 1162 | file_ctx->bufs_out = NULL; 1163 | file_ctx->whence = whence; 1164 | file_ctx->seek_offset = offset; 1165 | 1166 | ngx_http_lua_io_before_yield(r, file_ctx); 1167 | 1168 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1169 | "lua io seek saved co ctx:%p", file_ctx->coctx); 1170 | 1171 | return lua_yield(L, 0); 1172 | 1173 | seek: 1174 | 1175 | return ngx_http_lua_io_file_do_seek(file_ctx, r, L, offset, whence); 1176 | } 1177 | 1178 | 1179 | static int 1180 | ngx_http_lua_io_file_lines(lua_State *L) 1181 | { 1182 | ngx_http_request_t *r; 1183 | 1184 | if (NGX_UNLIKELY(lua_gettop(L) > 1)) { 1185 | return luaL_error(L, "expecting one argument, but got %d", 1186 | lua_gettop(L)); 1187 | } 1188 | 1189 | r = ngx_http_lua_get_request(L); 1190 | if (NGX_UNLIKELY(r == NULL)) { 1191 | return luaL_error(L, "no request found"); 1192 | } 1193 | 1194 | luaL_checktype(L, -1, LUA_TTABLE); 1195 | 1196 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1197 | "lua io file lines created an iterator"); 1198 | 1199 | lua_pushcclosure(L, ngx_http_lua_io_file_lines_iter, 1); 1200 | return 1; 1201 | } 1202 | 1203 | 1204 | static int 1205 | ngx_http_lua_io_file_lines_iter(lua_State *L) 1206 | { 1207 | ngx_http_request_t *r; 1208 | ngx_http_lua_io_file_ctx_t *file_ctx; 1209 | ngx_http_lua_io_loc_conf_t *iocf; 1210 | 1211 | r = ngx_http_lua_get_request(L); 1212 | if (NGX_UNLIKELY(r == NULL)) { 1213 | return luaL_error(L, "no request found"); 1214 | } 1215 | 1216 | lua_rawgeti(L, lua_upvalueindex(1), NGX_HTTP_LUA_IO_FILE_CTX_INDEX); 1217 | file_ctx = lua_touserdata(L, -1); 1218 | lua_pop(L, 1); 1219 | 1220 | if (file_ctx == NULL || file_ctx->closed) { 1221 | iocf = ngx_http_get_module_loc_conf(r, ngx_http_lua_io_module); 1222 | if (iocf->log_errors) { 1223 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 1224 | "attempt to read a line from a closed file object"); 1225 | } 1226 | 1227 | lua_pushnil(L); 1228 | lua_pushliteral(L, "closed"); 1229 | return 2; 1230 | } 1231 | 1232 | if(NGX_UNLIKELY(file_ctx->request != r)) { 1233 | return luaL_error(L, "bad request"); 1234 | } 1235 | 1236 | ngx_http_lua_io_check_busy_reading(r, file_ctx, L); 1237 | ngx_http_lua_io_check_busy_writing(r, file_ctx, L); 1238 | ngx_http_lua_io_check_busy_flushing(r, file_ctx, L); 1239 | 1240 | if (NGX_UNLIKELY(!(file_ctx->mode & NGX_HTTP_LUA_IO_FILE_READ_MODE))) { 1241 | 1242 | /* FIXME need to be compatible with libc? */ 1243 | lua_pushnil(L); 1244 | lua_pushliteral(L, "operation not permitted"); 1245 | return 2; 1246 | } 1247 | 1248 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1249 | "lua io file line iterator called"); 1250 | 1251 | file_ctx->input_filter = ngx_http_lua_io_read_line; 1252 | file_ctx->rest = 0; 1253 | 1254 | return ngx_http_lua_io_file_read_helper(r, file_ctx, L); 1255 | } 1256 | 1257 | 1258 | static void 1259 | ngx_http_lua_io_coctx_cleanup(void *data) 1260 | { 1261 | ngx_http_lua_co_ctx_t *coctx = data; 1262 | 1263 | ngx_http_lua_io_file_ctx_t *file_ctx; 1264 | ngx_http_request_t *r; 1265 | 1266 | file_ctx = coctx->data; 1267 | if (file_ctx == NULL || file_ctx->closed || file_ctx->request == NULL) { 1268 | return; 1269 | } 1270 | 1271 | r = file_ctx->request; 1272 | 1273 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1274 | "lua io coctx cleanup"); 1275 | 1276 | ngx_http_lua_io_file_finalize(r, file_ctx); 1277 | } 1278 | 1279 | 1280 | static void 1281 | ngx_http_lua_io_content_wev_handler(ngx_http_request_t *r) 1282 | { 1283 | ngx_http_lua_ctx_t *ctx; 1284 | 1285 | ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); 1286 | if (ctx == NULL) { 1287 | return; 1288 | } 1289 | 1290 | (void) ctx->resume_handler(r); 1291 | } 1292 | 1293 | 1294 | static void 1295 | ngx_http_lua_io_thread_event_handler(ngx_event_t *ev) 1296 | { 1297 | ngx_http_lua_io_file_ctx_t *file_ctx = ev->data; 1298 | 1299 | ngx_connection_t *c; 1300 | ngx_http_request_t *r; 1301 | ngx_http_lua_ctx_t *lctx; 1302 | 1303 | r = file_ctx->request; 1304 | c = r->connection; 1305 | 1306 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1307 | "lua io thread event handler"); 1308 | 1309 | ev->complete = 0; 1310 | 1311 | r->main->blocked--; 1312 | r->aio = 0; 1313 | 1314 | lctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); 1315 | 1316 | lctx->resume_handler = ngx_http_lua_io_resume; 1317 | lctx->cur_co_ctx = file_ctx->coctx; 1318 | 1319 | r->write_event_handler(r); 1320 | ngx_http_run_posted_requests(c); 1321 | 1322 | return; 1323 | } 1324 | 1325 | 1326 | static ngx_int_t 1327 | ngx_http_lua_io_resume(ngx_http_request_t *r) 1328 | { 1329 | ngx_int_t rc, n; 1330 | ngx_uint_t nreqs; 1331 | lua_State *L; 1332 | ngx_connection_t *c; 1333 | ngx_http_lua_ctx_t *lctx; 1334 | ngx_http_lua_co_ctx_t *coctx; 1335 | ngx_http_lua_io_file_ctx_t *file_ctx; 1336 | 1337 | #if (NGX_DEBUG) 1338 | const char *action; 1339 | #endif 1340 | 1341 | lctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); 1342 | if (lctx == NULL) { 1343 | return NGX_ERROR; 1344 | } 1345 | 1346 | lctx->resume_handler = ngx_http_lua_wev_handler; 1347 | 1348 | coctx = lctx->cur_co_ctx; 1349 | coctx->cleanup = NULL; 1350 | 1351 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1352 | "lua io resume cur_co_ctx:%p", coctx); 1353 | 1354 | file_ctx = coctx->data; 1355 | 1356 | #if (NGX_DEBUG) 1357 | 1358 | if (file_ctx->write_waiting) { 1359 | action = "write"; 1360 | 1361 | } else if (file_ctx->read_waiting) { 1362 | action = "read"; 1363 | 1364 | } else { 1365 | action = "flush"; 1366 | } 1367 | 1368 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1369 | "lua io %s done and resume", action); 1370 | 1371 | #endif 1372 | 1373 | n = ngx_http_lua_io_prepare_retvals(r, file_ctx, coctx); 1374 | 1375 | if (n < 0) { 1376 | /* still have to stuck in here */ 1377 | return NGX_DONE; 1378 | } 1379 | 1380 | L = ngx_http_lua_get_lua_vm(r, lctx); 1381 | 1382 | c = r->connection; 1383 | nreqs = c->requests; 1384 | 1385 | rc = ngx_http_lua_run_thread(L, r, lctx, n); 1386 | 1387 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1388 | "lua run thread returned %d", rc); 1389 | 1390 | if (rc == NGX_AGAIN) { 1391 | return ngx_http_lua_run_posted_threads(c, L, r, lctx, nreqs); 1392 | } 1393 | 1394 | if (rc == NGX_DONE) { 1395 | ngx_http_lua_finalize_request(r, NGX_DONE); 1396 | return ngx_http_lua_run_posted_threads(c, L, r, lctx, nreqs); 1397 | } 1398 | 1399 | if (lctx->entered_content_phase) { 1400 | ngx_http_lua_finalize_request(r, rc); 1401 | return NGX_DONE; 1402 | } 1403 | 1404 | return rc; 1405 | } 1406 | 1407 | 1408 | static ngx_int_t 1409 | ngx_http_lua_io_handle_error(lua_State *L, ngx_http_request_t *r, 1410 | ngx_http_lua_io_file_ctx_t *ctx) 1411 | { 1412 | u_char errstr[NGX_MAX_ERROR_STR]; 1413 | u_char *p; 1414 | 1415 | lua_pop(L, lua_gettop(L)); 1416 | 1417 | lua_pushnil(L); 1418 | 1419 | if (ctx->ft_type & NGX_HTTP_LUA_IO_FT_CLOSE) { 1420 | lua_pushliteral(L, "closed"); 1421 | 1422 | } else if (ctx->ft_type & NGX_HTTP_LUA_IO_FT_TASK_POST_ERROR) { 1423 | lua_pushliteral(L, "task post failed"); 1424 | 1425 | } else if (ctx->ft_type & NGX_HTTP_LUA_IO_FT_NO_MEMORY) { 1426 | lua_pushliteral(L, "no memory"); 1427 | 1428 | } else { 1429 | if (ctx->error != 0) { 1430 | p = ngx_strerror(ctx->error, errstr, sizeof(errstr)); 1431 | ngx_strlow(errstr, errstr, p - errstr); 1432 | lua_pushlstring(L, (char *) errstr, p - errstr); 1433 | 1434 | } else { 1435 | lua_pushliteral(L, "error"); 1436 | } 1437 | } 1438 | 1439 | return 2; 1440 | } 1441 | 1442 | 1443 | static int 1444 | ngx_http_lua_io_file_destory(lua_State *L) 1445 | { 1446 | ngx_http_lua_io_file_ctx_t *ctx; 1447 | 1448 | ctx = lua_touserdata(L, 1); 1449 | if (ctx == NULL) { 1450 | return 0; 1451 | } 1452 | 1453 | if (ctx->cleanup) { 1454 | ngx_http_lua_io_file_finalize(ctx->request, ctx); 1455 | } 1456 | 1457 | return 0; 1458 | } 1459 | 1460 | 1461 | static void 1462 | ngx_http_lua_io_file_cleanup(void *data) 1463 | { 1464 | ngx_http_lua_io_file_ctx_t *ctx = data; 1465 | 1466 | ngx_http_request_t *r; 1467 | 1468 | r = ctx->request; 1469 | 1470 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1471 | "lua io file ctx cleanup"); 1472 | 1473 | ngx_http_lua_io_file_finalize(r, ctx); 1474 | } 1475 | 1476 | 1477 | static void 1478 | ngx_http_lua_io_file_finalize(ngx_http_request_t *r, 1479 | ngx_http_lua_io_file_ctx_t *ctx) 1480 | { 1481 | ngx_chain_t *cl, **ll; 1482 | ngx_http_lua_io_ctx_t *ioctx; 1483 | 1484 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1485 | "lua io file ctx finalize, r:%p", r); 1486 | 1487 | if (ctx->cleanup) { 1488 | *ctx->cleanup = NULL; 1489 | ngx_http_lua_cleanup_free(r, ctx->cleanup); 1490 | ctx->cleanup = NULL; 1491 | } 1492 | 1493 | ioctx = ngx_http_get_module_ctx(r, ngx_http_lua_io_module); 1494 | 1495 | if (ctx->bufs_in) { 1496 | 1497 | ll = &ctx->bufs_in; 1498 | for (cl = ctx->bufs_in; cl; cl = cl->next) { 1499 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1500 | "lua io file ctx finalize read chain:%p, next:%p", 1501 | cl, cl->next); 1502 | 1503 | cl->buf->pos = cl->buf->last; 1504 | ll = &cl->next; 1505 | } 1506 | 1507 | *ll = ioctx->free_read_bufs; 1508 | ioctx->free_read_bufs = ctx->bufs_in; 1509 | 1510 | ctx->bufs_in = NULL; 1511 | ctx->buf_in = NULL; 1512 | 1513 | ngx_memzero(&ctx->buffer, sizeof(ngx_buf_t)); 1514 | } 1515 | 1516 | if (ctx->bufs_out) { 1517 | 1518 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1519 | "lua io file ctx finalize write chain:%p", 1520 | ctx->bufs_out); 1521 | 1522 | ctx->bufs_out->buf->pos = ctx->bufs_out->buf->last; 1523 | 1524 | ctx->bufs_out->next = ioctx->free_write_bufs; 1525 | ioctx->free_write_bufs = ctx->bufs_out; 1526 | 1527 | ctx->bufs_out = NULL; 1528 | } 1529 | 1530 | ctx->error = 0; 1531 | ctx->ft_type = 0; 1532 | ctx->closed = 1; 1533 | 1534 | if (ctx->fd != NGX_INVALID_FILE && ngx_close_file(ctx->fd) < 0) { 1535 | ctx->error = ngx_errno; 1536 | ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno, 1537 | ngx_close_file_n " failed"); 1538 | } 1539 | } 1540 | 1541 | 1542 | static void 1543 | ngx_http_lua_io_before_yield(ngx_http_request_t *r, 1544 | ngx_http_lua_io_file_ctx_t *file_ctx) 1545 | { 1546 | ngx_http_lua_ctx_t *lctx; 1547 | ngx_http_lua_co_ctx_t *coctx; 1548 | 1549 | lctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); 1550 | coctx = lctx->cur_co_ctx; 1551 | 1552 | ngx_http_lua_cleanup_pending_operation(coctx); 1553 | coctx->cleanup = ngx_http_lua_io_coctx_cleanup; 1554 | coctx->data = file_ctx; 1555 | 1556 | file_ctx->coctx = coctx; 1557 | 1558 | if (lctx->entered_content_phase) { 1559 | r->write_event_handler = ngx_http_lua_io_content_wev_handler; 1560 | 1561 | } else { 1562 | r->write_event_handler = ngx_http_core_run_phases; 1563 | } 1564 | } 1565 | 1566 | 1567 | static ngx_int_t 1568 | ngx_http_lua_io_prepare_retvals(ngx_http_request_t *r, 1569 | ngx_http_lua_io_file_ctx_t *file_ctx, ngx_http_lua_co_ctx_t *coctx) 1570 | { 1571 | ngx_int_t rc; 1572 | ngx_buf_t *b; 1573 | ngx_http_lua_io_ctx_t *ioctx; 1574 | ngx_http_lua_io_thread_ctx_t *thread_ctx; 1575 | 1576 | thread_ctx = file_ctx->thread_task->ctx; 1577 | if (thread_ctx->err) { 1578 | file_ctx->error = thread_ctx->err; 1579 | return ngx_http_lua_io_handle_error(coctx->co, r, file_ctx); 1580 | } 1581 | 1582 | ioctx = ngx_http_get_module_ctx(r, ngx_http_lua_io_module); 1583 | 1584 | if (file_ctx->write_waiting) { 1585 | file_ctx->offset += thread_ctx->nbytes; 1586 | file_ctx->write_waiting = 0; 1587 | 1588 | thread_ctx->chain->buf->pos = thread_ctx->chain->buf->last; 1589 | 1590 | thread_ctx->chain->next = ioctx->free_write_bufs; 1591 | ioctx->free_write_bufs = thread_ctx->chain; 1592 | 1593 | thread_ctx->chain = NULL; 1594 | 1595 | if (file_ctx->seeking) { 1596 | file_ctx->seeking = 0; 1597 | 1598 | return ngx_http_lua_io_file_do_seek(file_ctx, r, coctx->co, 1599 | file_ctx->seek_offset, 1600 | file_ctx->whence); 1601 | } 1602 | 1603 | lua_pushinteger(coctx->co, file_ctx->nbytes); 1604 | return 1; 1605 | } 1606 | 1607 | if (file_ctx->flush_waiting) { 1608 | file_ctx->offset += thread_ctx->nbytes; 1609 | file_ctx->flush_waiting = 0; 1610 | 1611 | if (thread_ctx->chain) { 1612 | thread_ctx->chain->buf->pos = thread_ctx->chain->buf->last; 1613 | 1614 | thread_ctx->chain->next = ioctx->free_write_bufs; 1615 | ioctx->free_write_bufs = thread_ctx->chain; 1616 | 1617 | thread_ctx->chain = NULL; 1618 | } 1619 | 1620 | if (file_ctx->closing) { 1621 | file_ctx->closing = 0; 1622 | ngx_http_lua_io_file_finalize(r, file_ctx); 1623 | 1624 | if (file_ctx->ft_type || file_ctx->error) { 1625 | return ngx_http_lua_io_handle_error(coctx->co, r, file_ctx); 1626 | } 1627 | } 1628 | 1629 | lua_pushinteger(coctx->co, 1); 1630 | return 1; 1631 | } 1632 | 1633 | /* file_ctx->read_waiting == 1 */ 1634 | 1635 | file_ctx->eof = thread_ctx->eof; 1636 | file_ctx->read_waiting = 0; 1637 | file_ctx->buffer.last += thread_ctx->nbytes; 1638 | 1639 | if (file_ctx->eof) { 1640 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1641 | "lua io read eof"); 1642 | } 1643 | 1644 | rc = ngx_http_lua_io_file_do_read(r, file_ctx); 1645 | 1646 | if (rc == NGX_AGAIN) { 1647 | b = &file_ctx->buffer; 1648 | if (NGX_UNLIKELY(ngx_http_lua_io_thread_post_read_task(file_ctx, b) 1649 | == NGX_ERROR)) 1650 | { 1651 | return ngx_http_lua_io_handle_error(coctx->co, r, file_ctx); 1652 | } 1653 | 1654 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1655 | "lua io read need one more try"); 1656 | 1657 | file_ctx->read_waiting = 1; 1658 | return -1; 1659 | } 1660 | 1661 | if (rc == NGX_ERROR) { 1662 | return ngx_http_lua_io_handle_error(coctx->co, r, file_ctx); 1663 | } 1664 | 1665 | /* rc == NGX_OK */ 1666 | 1667 | return ngx_http_lua_io_submit_input_data(r, file_ctx, coctx->co); 1668 | } 1669 | 1670 | 1671 | static int 1672 | ngx_http_lua_io_file_do_seek(ngx_http_lua_io_file_ctx_t *file_ctx, 1673 | ngx_http_request_t *r, lua_State *L, off_t offset, int whence) 1674 | { 1675 | 1676 | if (whence == SEEK_CUR) { 1677 | 1678 | /* 1679 | * due to the buffered reading mechanism, 1680 | * we rebuild the seek offset, to the correct position. 1681 | */ 1682 | 1683 | whence = SEEK_SET; 1684 | offset = file_ctx->offset + offset; 1685 | } 1686 | 1687 | offset = lseek(file_ctx->fd, offset, whence); 1688 | 1689 | if (offset < 0) { 1690 | file_ctx->error = ngx_errno; 1691 | return ngx_http_lua_io_handle_error(L, r, file_ctx); 1692 | } 1693 | 1694 | file_ctx->eof = 0; 1695 | file_ctx->offset = offset; 1696 | 1697 | lua_pushinteger(L, offset); 1698 | return 1; 1699 | } 1700 | 1701 | 1702 | static int 1703 | ngx_http_lua_io_file_read_helper(ngx_http_request_t *r, 1704 | ngx_http_lua_io_file_ctx_t *file_ctx, lua_State *L) 1705 | { 1706 | ngx_int_t rc; 1707 | ngx_http_lua_io_loc_conf_t *iocf; 1708 | ngx_http_lua_io_ctx_t *ioctx; 1709 | 1710 | 1711 | if (file_ctx->bufs_in == NULL) { 1712 | ioctx = ngx_http_get_module_ctx(r, ngx_http_lua_io_module); 1713 | iocf = ngx_http_get_module_loc_conf(r, ngx_http_lua_io_module); 1714 | 1715 | file_ctx->bufs_in = ngx_http_lua_chain_get_free_buf(r->connection->log, 1716 | r->pool, 1717 | &ioctx->free_read_bufs, 1718 | iocf->read_buf_size); 1719 | 1720 | if (NGX_UNLIKELY(file_ctx->bufs_in == NULL)) { 1721 | return luaL_error(L, "no memory"); 1722 | } 1723 | 1724 | file_ctx->buf_in = file_ctx->bufs_in; 1725 | file_ctx->buffer = *file_ctx->buf_in->buf; 1726 | } 1727 | 1728 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1729 | "lua io read, bufs_in:%p buf_in:%p", 1730 | file_ctx->bufs_in, file_ctx->buf_in); 1731 | 1732 | file_ctx->read_waiting = 0; 1733 | file_ctx->coctx = NULL; 1734 | 1735 | rc = ngx_http_lua_io_file_do_read(r, file_ctx); 1736 | 1737 | if (rc == NGX_ERROR) { 1738 | return ngx_http_lua_io_handle_error(L, r, file_ctx); 1739 | } 1740 | 1741 | if (rc == NGX_OK) { 1742 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1743 | "lua io read done just in one round"); 1744 | 1745 | return ngx_http_lua_io_submit_input_data(r, file_ctx, L); 1746 | } 1747 | 1748 | /* rc == NGX_AGAIN */ 1749 | 1750 | if (NGX_UNLIKELY(ngx_http_lua_io_thread_post_read_task(file_ctx, 1751 | &file_ctx->buffer) 1752 | == NGX_ERROR)) 1753 | { 1754 | return ngx_http_lua_io_handle_error(L, r, file_ctx); 1755 | } 1756 | 1757 | file_ctx->read_waiting = 1; 1758 | 1759 | ngx_http_lua_io_before_yield(r, file_ctx); 1760 | 1761 | return lua_yield(L, 0); 1762 | } 1763 | 1764 | 1765 | static ngx_int_t 1766 | ngx_http_lua_io_file_do_read(ngx_http_request_t *r, 1767 | ngx_http_lua_io_file_ctx_t *file_ctx) 1768 | { 1769 | 1770 | size_t size; 1771 | ngx_buf_t *b; 1772 | ngx_int_t rc; 1773 | 1774 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1775 | "lua io do read, waiting:%d", file_ctx->read_waiting); 1776 | 1777 | b = &file_ctx->buffer; 1778 | 1779 | for ( ;; ) { 1780 | 1781 | size = b->last - b->pos; 1782 | 1783 | if (size == 0 && !file_ctx->eof) { 1784 | break; 1785 | } 1786 | 1787 | rc = file_ctx->input_filter(file_ctx, b, size); 1788 | if (rc == NGX_OK) { 1789 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1790 | "lua io read done, waiting:%d", 1791 | file_ctx->read_waiting); 1792 | 1793 | return NGX_OK; 1794 | } 1795 | 1796 | if (rc == NGX_AGAIN) { 1797 | break; 1798 | } 1799 | } 1800 | 1801 | if (file_ctx->eof) { 1802 | return NGX_OK; 1803 | } 1804 | 1805 | size = b->end - b->last; 1806 | if (size == 0) { 1807 | rc = ngx_http_lua_io_add_input_buffer(r, file_ctx); 1808 | 1809 | if (NGX_UNLIKELY(rc == NGX_ERROR)) { 1810 | file_ctx->ft_type |= NGX_HTTP_LUA_IO_FT_NO_MEMORY; 1811 | return NGX_ERROR; 1812 | } 1813 | } 1814 | 1815 | return NGX_AGAIN; 1816 | } 1817 | 1818 | 1819 | static ngx_int_t 1820 | ngx_http_lua_io_add_input_buffer(ngx_http_request_t *r, 1821 | ngx_http_lua_io_file_ctx_t *file_ctx) 1822 | { 1823 | ngx_chain_t *cl; 1824 | ngx_http_lua_io_loc_conf_t *iocf; 1825 | ngx_http_lua_io_ctx_t *ioctx; 1826 | 1827 | ioctx = ngx_http_get_module_ctx(r, ngx_http_lua_io_module); 1828 | iocf = ngx_http_get_module_loc_conf(r, ngx_http_lua_io_module); 1829 | 1830 | cl = ngx_http_lua_chain_get_free_buf(r->connection->log, r->pool, 1831 | &ioctx->free_read_bufs, 1832 | iocf->read_buf_size); 1833 | 1834 | if (cl == NULL) { 1835 | return NGX_ERROR; 1836 | } 1837 | 1838 | file_ctx->buf_in->next = cl; 1839 | file_ctx->buf_in = cl; 1840 | file_ctx->buffer = *cl->buf; 1841 | 1842 | return NGX_OK; 1843 | } 1844 | 1845 | 1846 | static int 1847 | ngx_http_lua_io_submit_input_data(ngx_http_request_t *r, 1848 | ngx_http_lua_io_file_ctx_t *file_ctx, lua_State *L) 1849 | { 1850 | luaL_Buffer luabuf; 1851 | ngx_int_t nbufs, eof; 1852 | ngx_chain_t *cl, **ll; 1853 | ngx_buf_t *b; 1854 | size_t size; 1855 | off_t offset; 1856 | ngx_http_lua_io_ctx_t *ioctx; 1857 | 1858 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1859 | "lua submit input data, bufs_in:%p buf_in: %p", 1860 | file_ctx->bufs_in, file_ctx->buf_in); 1861 | 1862 | luaL_buffinit(L, &luabuf); 1863 | 1864 | offset = 0; 1865 | 1866 | nbufs = 0; 1867 | ll = NULL; 1868 | 1869 | for (cl = file_ctx->bufs_in; cl; cl = cl->next) { 1870 | b = cl->buf; 1871 | size = b->last - b->pos; 1872 | 1873 | luaL_addlstring(&luabuf, (const char *) b->pos, size); 1874 | 1875 | offset += size; 1876 | 1877 | if (cl->next) { 1878 | ll = &cl->next; 1879 | } 1880 | 1881 | nbufs++; 1882 | } 1883 | 1884 | eof = file_ctx->buffer.last == file_ctx->buffer.pos; 1885 | 1886 | if (file_ctx->eof && eof && offset == 0) { 1887 | lua_pushnil(L); 1888 | 1889 | } else { 1890 | file_ctx->offset += offset; 1891 | luaL_pushresult(&luabuf); 1892 | } 1893 | 1894 | if (nbufs > 1 && ll) { 1895 | ioctx = ngx_http_get_module_ctx(r, ngx_http_lua_io_module); 1896 | 1897 | *ll = ioctx->free_read_bufs; 1898 | ioctx->free_read_bufs = file_ctx->bufs_in; 1899 | file_ctx->bufs_in = file_ctx->buf_in; 1900 | } 1901 | 1902 | b = &file_ctx->buffer; 1903 | 1904 | if (eof) { 1905 | b->pos = b->start; 1906 | b->last = b->start; 1907 | } 1908 | 1909 | if (file_ctx->bufs_in) { 1910 | file_ctx->bufs_in->buf->pos = b->pos; 1911 | file_ctx->bufs_in->buf->last = b->pos; 1912 | } 1913 | 1914 | return 1; 1915 | } 1916 | -------------------------------------------------------------------------------- /t/00-open-close.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | repeat_each(3); 4 | 5 | plan tests => repeat_each() * 4 * 13; 6 | 7 | log_level 'debug'; 8 | 9 | no_long_string(); 10 | run_tests(); 11 | 12 | __DATA__ 13 | 14 | === TEST 1: ngx.io.open() with nonexistent file 15 | --- main_config 16 | thread_pool default threads=2 max_queue=10; 17 | --- config 18 | server_tokens off; 19 | location /t { 20 | content_by_lua_block { 21 | local io = require "ngx.io" 22 | local file, err = io.open("foo") 23 | assert(file == nil) 24 | ngx.print(err) 25 | } 26 | } 27 | 28 | --- request 29 | GET /t 30 | --- response_body: no such file or directory 31 | --- grep_error_log: lua io open fd:-1 32 | --- grep_error_log_out 33 | lua io open fd:-1 34 | --- no_error_log 35 | [error] 36 | 37 | 38 | 39 | === TEST 2: ngx.io.open() with empty open mode 40 | --- main_config 41 | thread_pool default threads=2 max_queue=10; 42 | --- config 43 | server_tokens off; 44 | location /t { 45 | content_by_lua_block { 46 | local io = require "ngx.io" 47 | local file, err = io.open("foo", "") 48 | assert(file == nil) 49 | ngx.print(err) 50 | } 51 | } 52 | 53 | --- request 54 | GET /t 55 | --- response_body: bad open mode 56 | --- grep_error_log: lua io open mode:"" 57 | --- grep_error_log_out 58 | lua io open mode:"" 59 | --- no_error_log 60 | [error] 61 | 62 | 63 | 64 | === TEST 3: ngx.io.open() with invalid open mode 65 | --- main_config 66 | thread_pool default threads=2 max_queue=10; 67 | --- config 68 | server_tokens off; 69 | location /t { 70 | content_by_lua_block { 71 | local io = require "ngx.io" 72 | local file, err = io.open("foo", "+") 73 | assert(file == nil) 74 | ngx.say(err) 75 | 76 | local file, err = io.open("foo", "rr") 77 | assert(file == nil) 78 | ngx.say(err) 79 | 80 | local file, err = io.open("foo", "rw") 81 | assert(file == nil) 82 | ngx.say(err) 83 | 84 | local file, err = io.open("foo", "rw+") 85 | assert(file == nil) 86 | ngx.say(err) 87 | 88 | local file, err = io.open("foo", "+r") 89 | assert(file == nil) 90 | ngx.say(err) 91 | 92 | local file, err = io.open("foo", "+w") 93 | assert(file == nil) 94 | ngx.say(err) 95 | 96 | local file, err = io.open("foo", "x+") 97 | assert(file == nil) 98 | ngx.say(err) 99 | } 100 | } 101 | 102 | --- request 103 | GET /t 104 | --- response_body 105 | bad open mode 106 | bad open mode 107 | bad open mode 108 | bad open mode 109 | bad open mode 110 | bad open mode 111 | bad open mode 112 | --- grep_error_log eval 113 | qr/lua io open mode:".*"/ 114 | --- grep_error_log_out 115 | lua io open mode:"+" 116 | lua io open mode:"rr" 117 | lua io open mode:"rw" 118 | lua io open mode:"rw+" 119 | lua io open mode:"+r" 120 | lua io open mode:"+w" 121 | lua io open mode:"x+" 122 | --- no_error_log 123 | [error] 124 | 125 | 126 | 127 | === TEST 4: ngx.io.open() with default mode ("r") 128 | --- main_config 129 | thread_pool default threads=2 max_queue=10; 130 | --- config 131 | server_tokens off; 132 | location /t { 133 | content_by_lua_block { 134 | local io = require "ngx.io" 135 | local file, err = io.open("foo") 136 | assert(file == nil) 137 | ngx.say(err) 138 | } 139 | } 140 | 141 | --- request 142 | GET /t 143 | --- response_body 144 | no such file or directory 145 | --- grep_error_log: lua io open mode:"r" 146 | --- grep_error_log_out 147 | lua io open mode:"r" 148 | --- no_error_log 149 | [error] 150 | 151 | 152 | 153 | === TEST 5: ngx.io.open() is successful and file is closed by file:close() 154 | --- main_config 155 | thread_pool default threads=2 max_queue=10; 156 | --- config 157 | server_tokens off; 158 | location /t { 159 | content_by_lua_block { 160 | local io = require "ngx.io" 161 | local file, err = io.open("conf/nginx.conf") 162 | assert(file ~= nil) 163 | assert(err == nil) 164 | assert(type(file) == "table") 165 | 166 | local ok, err = file:close() 167 | assert(ok ~= nil) 168 | assert(err == nil) 169 | ngx.print("OK") 170 | } 171 | } 172 | 173 | --- request 174 | GET /t 175 | --- response_body: OK 176 | --- grep_error_log: lua io open mode:"r" 177 | --- grep_error_log_out 178 | lua io open mode:"r" 179 | --- grep_error_log: lua io file ctx finalize 180 | --- grep_error_log_out 181 | lua io file ctx finalize 182 | --- no_error_log 183 | [error] 184 | 185 | 186 | 187 | === TEST 6: ngx.io.open() is success and file is closed by nginx http request cleanup handler 188 | --- main_config 189 | thread_pool default threads=2 max_queue=10; 190 | --- config 191 | server_tokens off; 192 | location /t { 193 | content_by_lua_block { 194 | local io = require "ngx.io" 195 | local file, err = io.open("conf/nginx.conf") 196 | assert(file ~= nil) 197 | assert(err == nil) 198 | assert(type(file) == "table") 199 | ngx.print("OK") 200 | } 201 | } 202 | 203 | --- request 204 | GET /t 205 | --- response_body: OK 206 | --- grep_error_log: lua io open mode:"r" 207 | --- grep_error_log_out 208 | lua io open mode:"r" 209 | --- grep_error_log: lua io file ctx cleanup 210 | --- grep_error_log_out 211 | lua io file ctx cleanup 212 | --- grep_error_log: lua io file ctx finalize 213 | --- grep_error_log_out 214 | lua io file ctx finalize 215 | --- no_error_log 216 | [error] 217 | 218 | 219 | 220 | === TEST 7: ngx.io.open() is success and file is closed duplicately 221 | --- main_config 222 | thread_pool default threads=2 max_queue=10; 223 | --- config 224 | server_tokens off; 225 | location /t { 226 | content_by_lua_block { 227 | local io = require "ngx.io" 228 | local file, err = io.open("conf/nginx.conf") 229 | assert(file ~= nil) 230 | assert(err == nil) 231 | assert(type(file) == "table") 232 | 233 | local ok, err = file:close() 234 | assert(ok ~= nil) 235 | assert(err == nil) 236 | 237 | local ok, err = file:close() 238 | assert(ok == nil) 239 | assert(err == "closed") 240 | 241 | local ok, err = file:close() 242 | assert(ok == nil) 243 | assert(err == "closed") 244 | 245 | ngx.print("OK") 246 | } 247 | } 248 | 249 | --- request 250 | GET /t 251 | --- response_body: OK 252 | --- grep_error_log: lua io open mode:"r" 253 | --- grep_error_log_out 254 | lua io open mode:"r" 255 | --- grep_error_log: lua io file ctx finalize 256 | --- grep_error_log_out 257 | lua io file ctx finalize 258 | --- no_error_log 259 | [error] 260 | --- no_error_log 261 | lua io file ctx cleanup 262 | 263 | 264 | 265 | === TEST 8: try to open a nonexistent file with the read-only mode (r) 266 | --- main_config 267 | thread_pool default threads=2 max_queue=10; 268 | --- config 269 | server_tokens off; 270 | location /t { 271 | content_by_lua_block { 272 | local io = require "ngx.io" 273 | local file, err = io.open("conf/nginx.con", "r") 274 | assert(file == nil) 275 | local code = os.execute("test -f " .. ngx.config.prefix() .. "/conf/nginx.con") 276 | assert(code ~= 0) 277 | ngx.print(err) 278 | } 279 | } 280 | 281 | --- request 282 | GET /t 283 | --- response_body: no such file or directory 284 | --- grep_error_log: lua io open mode:"r" 285 | --- grep_error_log_out 286 | lua io open mode:"r" 287 | --- grep_error_log: lua io file ctx cleanup 288 | --- grep_error_log_out 289 | lua io file ctx cleanup 290 | --- grep_error_log: lua io file ctx finalize 291 | --- grep_error_log_out 292 | lua io file ctx finalize 293 | --- no_error_log 294 | [error] 295 | 296 | 297 | 298 | === TEST 9: open a nonexistent file with the write-only mode (w) 299 | --- main_config 300 | thread_pool default threads=2 max_queue=10; 301 | --- config 302 | server_tokens off; 303 | location /t { 304 | content_by_lua_block { 305 | local io = require "ngx.io" 306 | local file, err = io.open("conf/nginx.con", "w") 307 | assert(type(file) == "table") 308 | assert(err == nil) 309 | local ok, err = file:close() 310 | assert(ok) 311 | assert(err == nil) 312 | 313 | local code = os.execute("test -f " .. ngx.config.prefix() .. "/conf/nginx.con") 314 | assert(code == 0) 315 | code = os.execute("rm -f " .. ngx.config.prefix() .. "/conf/nginx.con") 316 | ngx.print("OK") 317 | } 318 | } 319 | 320 | --- request 321 | GET /t 322 | --- response_body: OK 323 | --- grep_error_log: lua io open mode:"w" 324 | --- grep_error_log_out 325 | lua io open mode:"w" 326 | --- grep_error_log: lua io file ctx finalize 327 | --- grep_error_log_out 328 | lua io file ctx finalize 329 | --- no_error_log 330 | [error] 331 | --- no_error_log 332 | lua io file ctx cleanup 333 | 334 | 335 | 336 | === TEST 10: open a nonexistent file with the read/write mode (r+) 337 | --- main_config 338 | thread_pool default threads=2 max_queue=10; 339 | --- config 340 | server_tokens off; 341 | location /t { 342 | content_by_lua_block { 343 | local io = require "ngx.io" 344 | local file, err = io.open("conf/nginx.con", "r+") 345 | assert(file == nil) 346 | assert(err == "no such file or directory") 347 | local code = os.execute("test -f " .. ngx.config.prefix() .. "/conf/nginx.con") 348 | assert(code ~= 0) 349 | ngx.print("OK") 350 | } 351 | } 352 | 353 | --- request 354 | GET /t 355 | --- response_body: OK 356 | --- grep_error_log: lua io open mode:"r+" 357 | --- grep_error_log_out 358 | lua io open mode:"r+" 359 | --- no_error_log 360 | [error] 361 | 362 | 363 | 364 | === TEST 11: open a nonexistent file with the read/write mode (w+) 365 | --- main_config 366 | thread_pool default threads=2 max_queue=10; 367 | --- config 368 | server_tokens off; 369 | location /t { 370 | content_by_lua_block { 371 | local io = require "ngx.io" 372 | local file, err = io.open("conf/nginx.con", "w+") 373 | assert(type(file) == "table") 374 | assert(err == nil) 375 | local ok, err = file:close() 376 | assert(ok) 377 | assert(err == nil) 378 | 379 | local code = os.execute("test -f " .. ngx.config.prefix() .. "/conf/nginx.con") 380 | assert(code == 0) 381 | code = os.execute("rm -f " .. ngx.config.prefix() .. "/conf/nginx.con") 382 | ngx.print("OK") 383 | } 384 | } 385 | 386 | --- request 387 | GET /t 388 | --- response_body: OK 389 | --- grep_error_log: lua io open mode:"r+" 390 | --- grep_error_log_out 391 | lua io open mode:"w+" 392 | --- grep_error_log: lua io file ctx finalize 393 | --- grep_error_log_out 394 | lua io file ctx finalize 395 | --- no_error_log 396 | [error] 397 | --- no_error_log 398 | lua io file ctx cleanup 399 | 400 | 401 | === TEST 12: open a nonexistent file with the read/write/append mode (a+) 402 | --- main_config 403 | thread_pool default threads=2 max_queue=10; 404 | --- config 405 | server_tokens off; 406 | location /t { 407 | content_by_lua_block { 408 | local io = require "ngx.io" 409 | local file, err = io.open("conf/nginx.con", "a+") 410 | assert(type(file) == "table") 411 | assert(err == nil) 412 | local ok, err = file:close() 413 | assert(ok) 414 | assert(err == nil) 415 | 416 | local code = os.execute("test -f " .. ngx.config.prefix() .. "/conf/nginx.con") 417 | assert(code == 0) 418 | code = os.execute("rm -f " .. ngx.config.prefix() .. "/conf/nginx.con") 419 | ngx.print("OK") 420 | } 421 | } 422 | 423 | --- request 424 | GET /t 425 | --- response_body: OK 426 | --- grep_error_log: lua io open mode:"a+" 427 | --- grep_error_log_out 428 | lua io open mode:"a+" 429 | --- grep_error_log: lua io file ctx finalize 430 | --- grep_error_log_out 431 | lua io file ctx finalize 432 | --- no_error_log 433 | [error] 434 | --- no_error_log 435 | lua io file ctx cleanup 436 | 437 | 438 | 439 | === TEST 13: open a nonexistent file with the append mode (a) 440 | --- main_config 441 | thread_pool default threads=2 max_queue=10; 442 | --- config 443 | server_tokens off; 444 | location /t { 445 | content_by_lua_block { 446 | local io = require "ngx.io" 447 | local file, err = io.open("conf/nginx.con", "a") 448 | assert(type(file) == "table") 449 | assert(err == nil) 450 | local ok, err = file:close() 451 | assert(ok) 452 | assert(err == nil) 453 | 454 | local code = os.execute("test -f " .. ngx.config.prefix() .. "/conf/nginx.con") 455 | assert(code == 0) 456 | code = os.execute("rm -f " .. ngx.config.prefix() .. "/conf/nginx.con") 457 | ngx.print("OK") 458 | } 459 | } 460 | 461 | --- request 462 | GET /t 463 | --- response_body: OK 464 | --- grep_error_log: lua io open mode:"a" 465 | --- grep_error_log_out 466 | lua io open mode:"a" 467 | --- grep_error_log: lua io file ctx finalize 468 | --- grep_error_log_out 469 | lua io file ctx finalize 470 | --- no_error_log 471 | [error] 472 | --- no_error_log 473 | lua io file ctx cleanup 474 | -------------------------------------------------------------------------------- /t/01-thread-pool.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | repeat_each(3); 4 | 5 | plan tests => repeat_each() * 16; 6 | 7 | log_level 'debug'; 8 | 9 | no_long_string(); 10 | run_tests(); 11 | 12 | __DATA__ 13 | 14 | === TEST 1: thread pool not found 15 | --- config 16 | server_tokens off; 17 | location /t { 18 | content_by_lua_block { 19 | local io = require "ngx.io" 20 | local ok, err = pcall(io.open, "conf/nginx.conf") 21 | assert(not ok) 22 | ngx.print(err) 23 | } 24 | } 25 | 26 | --- request 27 | GET /t 28 | --- response_body: no thread pool found 29 | --- grep_error_log eval: qr/lua io use thread pool ".*?"/ 30 | --- grep_error_log_out 31 | lua io use thread pool "default" 32 | --- no_error_log 33 | [error] 34 | 35 | 36 | 37 | === TEST 2: complex thread pool and use it 38 | --- main_config 39 | thread_pool alex threads=1 max_queue=1; 40 | --- config 41 | server_tokens off; 42 | location /t { 43 | set $n1 a; 44 | set $n2 l; 45 | lua_io_thread_pool "${n1}${n2}ex"; 46 | content_by_lua_block { 47 | local io = require "ngx.io" 48 | local file, err = io.open("conf/nginx.conf") 49 | assert(type(file) == "table") 50 | assert(err == nil) 51 | local ok, err = file:close() 52 | assert(ok ~= nil) 53 | assert(err == nil) 54 | ngx.print("OK") 55 | } 56 | } 57 | 58 | --- request 59 | GET /t 60 | --- response_body: OK 61 | --- grep_error_log eval: qr/lua io use thread pool ".*?"/ 62 | --- grep_error_log_out 63 | lua io use thread pool "alex" 64 | --- no_error_log 65 | [error] 66 | 67 | 68 | 69 | === TEST 3: not found a defined thread pool 70 | --- main_config 71 | thread_pool alex threads=1 max_queue=1; 72 | --- config 73 | server_tokens off; 74 | location /t { 75 | set $n1 a; 76 | set $n2 l; 77 | lua_io_thread_pool "${n1}${n2}"; 78 | content_by_lua_block { 79 | local io = require "ngx.io" 80 | local ok, err = pcall(io.open, "conf/nginx.conf") 81 | assert(not ok) 82 | ngx.print(err) 83 | } 84 | } 85 | 86 | --- request 87 | GET /t 88 | --- response_body: no thread pool found 89 | --- grep_error_log eval: qr/lua io use thread pool ".*?"/ 90 | --- grep_error_log_out 91 | lua io use thread pool "al" 92 | --- no_error_log 93 | [error] 94 | 95 | 96 | 97 | === TEST 4: use a trivial thread pool 98 | --- main_config 99 | thread_pool alex threads=1 max_queue=1; 100 | --- config 101 | server_tokens off; 102 | location /t { 103 | lua_io_thread_pool "alex"; 104 | content_by_lua_block { 105 | local io = require "ngx.io" 106 | local file, err = io.open("conf/nginx.conf") 107 | assert(type(file) == "table") 108 | assert(err == nil) 109 | local ok, err = file:close() 110 | assert(ok ~= nil) 111 | assert(err == nil) 112 | ngx.print("OK") 113 | } 114 | } 115 | 116 | --- request 117 | GET /t 118 | --- response_body: OK 119 | --- grep_error_log eval: qr/lua io use thread pool ".*?"/ 120 | --- grep_error_log_out 121 | lua io use thread pool "alex" 122 | --- no_error_log 123 | [error] 124 | -------------------------------------------------------------------------------- /t/02-file-write.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | repeat_each(3); 4 | 5 | plan tests => repeat_each() * (3 * 6 + 4 * 5 + 8); 6 | 7 | log_level 'debug'; 8 | 9 | no_long_string(); 10 | run_tests(); 11 | 12 | __DATA__ 13 | 14 | === TEST 1: empty write 15 | --- main_config 16 | thread_pool default threads=2 max_queue=10; 17 | --- config 18 | server_tokens off; 19 | location /t { 20 | lua_io_write_buffer_size 0; 21 | content_by_lua_block { 22 | local io = require "ngx.io" 23 | local file, err = io.open("conf/test.txt", "a") 24 | assert(type(file) == "table") 25 | assert(err == nil) 26 | local n, err = file:write("") 27 | assert(n == 0) 28 | assert(err == nil) 29 | 30 | local ok, err = file:close() 31 | assert(ok) 32 | assert(err == nil) 33 | 34 | ngx.print("OK") 35 | 36 | local prefix = ngx.config.prefix() 37 | os.execute("rm -f " .. prefix .. "/conf/test.txt") 38 | } 39 | } 40 | 41 | --- request 42 | GET /t 43 | --- response_body: OK 44 | --- no_error_log 45 | [error] 46 | --- no_error_log 47 | [crit] 48 | 49 | 50 | 51 | === TEST 2: bool write 52 | --- main_config 53 | thread_pool default threads=2 max_queue=10; 54 | --- config 55 | server_tokens off; 56 | location /t { 57 | lua_io_write_buffer_size 0; 58 | content_by_lua_block { 59 | local ngx_io = require "ngx.io" 60 | local file, err = ngx_io.open("conf/test.txt", "w") 61 | assert(type(file) == "table") 62 | assert(err == nil) 63 | local data = true 64 | local n, err = file:write(data) 65 | assert(n == 4) 66 | assert(err == nil) 67 | data = false 68 | local n, err = file:write(data) 69 | assert(n == 5) 70 | assert(err == nil) 71 | 72 | local ok, err = file:close() 73 | assert(ok) 74 | assert(err == nil) 75 | 76 | local prefix = ngx.config.prefix() 77 | local name = prefix .. "/conf/test.txt" 78 | 79 | local f = io.open(name, "r") 80 | local data = f:read("*a") 81 | ngx.print(data) 82 | f:close() 83 | 84 | os.execute("rm -f " .. name) 85 | } 86 | } 87 | 88 | --- request 89 | GET /t 90 | --- response_body: truefalse 91 | --- grep_error_log: lua io thread event handler 92 | --- grep_error_log_out 93 | lua io thread event handler 94 | lua io thread event handler 95 | --- grep_error_log: lua io resume 96 | --- grep_error_log_out 97 | lua io resume 98 | lua io resume 99 | --- grep_error_log: lua io write done and resume 100 | --- grep_error_log_out 101 | lua io write done and resume 102 | lua io write done and resume 103 | --- no_error_log 104 | [error] 105 | --- no_error_log 106 | [crit] 107 | 108 | 109 | 110 | === TEST 3: nil write 111 | --- main_config 112 | thread_pool default threads=2 max_queue=10; 113 | --- config 114 | server_tokens off; 115 | location /t { 116 | lua_io_write_buffer_size 0; 117 | content_by_lua_block { 118 | local ngx_io = require "ngx.io" 119 | local file, err = ngx_io.open("conf/test.txt", "w") 120 | assert(type(file) == "table") 121 | assert(err == nil) 122 | local data = nil 123 | local n, err = file:write(data) 124 | assert(n == 3) 125 | assert(err == nil) 126 | 127 | local ok, err = file:close() 128 | assert(ok) 129 | assert(err == nil) 130 | 131 | local prefix = ngx.config.prefix() 132 | local name = prefix .. "/conf/test.txt" 133 | 134 | local f = io.open(name, "r") 135 | local data = f:read("*a") 136 | ngx.print(data) 137 | f:close() 138 | 139 | os.execute("rm -f " .. name) 140 | } 141 | } 142 | 143 | --- request 144 | GET /t 145 | --- response_body: nil 146 | --- grep_error_log: lua io thread event handler 147 | --- grep_error_log_out 148 | lua io thread event handler 149 | --- grep_error_log: lua io resume 150 | --- grep_error_log_out 151 | lua io resume 152 | --- grep_error_log: lua io write done and resume 153 | --- grep_error_log_out 154 | lua io write done and resume 155 | --- no_error_log 156 | [error] 157 | --- no_error_log 158 | [crit] 159 | 160 | 161 | 162 | === TEST 4: table write 163 | --- main_config 164 | thread_pool default threads=2 max_queue=10; 165 | --- config 166 | server_tokens off; 167 | location /t { 168 | lua_io_write_buffer_size 0; 169 | content_by_lua_block { 170 | local ngx_io = require "ngx.io" 171 | local file, err = ngx_io.open("conf/test.txt", "w") 172 | assert(type(file) == "table") 173 | assert(err == nil) 174 | local data = {1, 2, 3, "4", "55", "snoopy", "\n"} 175 | local n, err = file:write(data) 176 | assert(n == 13) 177 | assert(err == nil) 178 | 179 | local ok, err = file:close() 180 | assert(ok) 181 | assert(err == nil) 182 | 183 | local prefix = ngx.config.prefix() 184 | local name = prefix .. "/conf/test.txt" 185 | 186 | local f = io.open(name, "r") 187 | local data = f:read("*a") 188 | ngx.print(data) 189 | f:close() 190 | 191 | os.execute("rm -f " .. name) 192 | } 193 | } 194 | 195 | --- request 196 | GET /t 197 | --- response_body 198 | 123455snoopy 199 | --- grep_error_log: lua io thread event handler 200 | --- grep_error_log_out 201 | lua io thread event handler 202 | --- grep_error_log: lua io resume 203 | --- grep_error_log_out 204 | lua io resume 205 | --- grep_error_log: lua io write done and resume 206 | --- grep_error_log_out 207 | lua io write done and resume 208 | --- no_error_log 209 | [error] 210 | --- no_error_log 211 | [crit] 212 | 213 | 214 | 215 | === TEST 5: string, number write 216 | --- main_config 217 | thread_pool default threads=2 max_queue=10; 218 | --- config 219 | server_tokens off; 220 | location /t { 221 | lua_io_write_buffer_size 0; 222 | content_by_lua_block { 223 | local ngx_io = require "ngx.io" 224 | local file, err = ngx_io.open("conf/test.txt", "w") 225 | assert(type(file) == "table") 226 | assert(err == nil) 227 | local n, err = file:write("Hello, ") 228 | assert(n == 7) 229 | assert(err == nil) 230 | 231 | local n, err = file:write("World") 232 | assert(n == 5) 233 | assert(err == nil) 234 | 235 | local n, err = file:write(12345) 236 | assert(n == 5) 237 | assert(err == nil) 238 | 239 | local ok, err = file:close() 240 | assert(ok) 241 | assert(err == nil) 242 | 243 | local prefix = ngx.config.prefix() 244 | local name = prefix .. "/conf/test.txt" 245 | 246 | local f = io.open(name, "r") 247 | local data = f:read("*a") 248 | ngx.print(data) 249 | f:close() 250 | 251 | os.execute("rm -f " .. name) 252 | } 253 | } 254 | 255 | --- request 256 | GET /t 257 | --- response_body: Hello, World12345 258 | --- grep_error_log: lua io thread event handler 259 | --- grep_error_log_out 260 | lua io thread event handler 261 | lua io thread event handler 262 | lua io thread event handler 263 | --- grep_error_log: lua io resume 264 | --- grep_error_log_out 265 | lua io resume 266 | lua io resume 267 | lua io resume 268 | --- grep_error_log: lua io write done and resume 269 | --- grep_error_log_out 270 | lua io write done and resume 271 | lua io write done and resume 272 | lua io write done and resume 273 | --- no_error_log 274 | [error] 275 | --- no_error_log 276 | [crit] 277 | 278 | 279 | 280 | === TEST 6: bad write (bad parameter type) 281 | --- main_config 282 | thread_pool default threads=2 max_queue=10; 283 | --- config 284 | server_tokens off; 285 | location /t { 286 | lua_io_write_buffer_size 0; 287 | content_by_lua_block { 288 | local ngx_io = require "ngx.io" 289 | local file, err = ngx_io.open("conf/test.txt", "w") 290 | assert(type(file) == "table") 291 | assert(err == nil) 292 | local data = { a = 1 } 293 | local ok, err = pcall(file.write, file, data) 294 | assert(not ok) 295 | ngx.print(err) 296 | 297 | local ok, err = file:close() 298 | assert(ok) 299 | assert(err == nil) 300 | } 301 | } 302 | 303 | --- request 304 | GET /t 305 | --- response_body eval 306 | qr/.*non-array table found/ 307 | --- no_error_log 308 | [error] 309 | --- no_error_log 310 | [crit] 311 | 312 | 313 | 314 | === TEST 7: try to write but miss the write permission 315 | --- main_config 316 | thread_pool default threads=2 max_queue=10; 317 | --- config 318 | server_tokens off; 319 | location /t { 320 | lua_io_write_buffer_size 0; 321 | content_by_lua_block { 322 | local ngx_io = require "ngx.io" 323 | local file, err = ngx_io.open("conf/nginx.conf", "r") 324 | assert(type(file) == "table") 325 | assert(err == nil) 326 | local n, err = file:write("Hello, ") 327 | assert(n == nil) 328 | ngx.print(err) 329 | local ok, err = file:close() 330 | assert(ok) 331 | assert(err == nil) 332 | } 333 | } 334 | 335 | --- request 336 | GET /t 337 | --- response_body: operation not permitted 338 | --- no_error_log 339 | [error] 340 | --- no_error_log 341 | [crit] 342 | 343 | 344 | 345 | === TEST 8: append mode 346 | --- main_config 347 | thread_pool default threads=2 max_queue=10; 348 | --- config 349 | server_tokens off; 350 | location /t { 351 | lua_io_write_buffer_size 0; 352 | rewrite_by_lua_block { 353 | local ngx_io = require "ngx.io" 354 | local file, err = ngx_io.open("conf/test.txt", "a") 355 | assert(type(file) == "table") 356 | assert(err == nil) 357 | local n, err = file:write("Hello, ") 358 | assert(n == 7) 359 | assert(err == nil) 360 | local ok, err = file:close() 361 | assert(ok) 362 | assert(err == nil) 363 | } 364 | 365 | content_by_lua_block { 366 | local ngx_io = require "ngx.io" 367 | local file, err = ngx_io.open("conf/test.txt", "a") 368 | assert(type(file) == "table") 369 | assert(err == nil) 370 | local n, err = file:write("World") 371 | assert(n == 5) 372 | assert(err == nil) 373 | local ok, err = file:close() 374 | assert(ok) 375 | assert(err == nil) 376 | 377 | local prefix = ngx.config.prefix() 378 | local name = prefix .. "/conf/test.txt" 379 | 380 | local f = io.open(name, "r") 381 | local data = f:read("*a") 382 | ngx.print(data) 383 | f:close() 384 | 385 | os.execute("rm -f " .. name) 386 | } 387 | } 388 | 389 | --- request 390 | GET /t 391 | --- response_body: Hello, World 392 | --- grep_error_log: lua io thread event handler 393 | --- grep_error_log_out 394 | lua io thread event handler 395 | lua io thread event handler 396 | --- grep_error_log: lua io write done and resume 397 | --- grep_error_log_out 398 | lua io write done and resume 399 | lua io write done and resume 400 | --- grep_error_log: lua io open mode:"a" 401 | --- grep_error_log_out 402 | lua io open mode:"a" 403 | lua io open mode:"a" 404 | --- no_error_log 405 | [error] 406 | --- no_error_log 407 | [crit] 408 | 409 | 410 | 411 | === TEST 9: append mode first, then the normal write mode follows 412 | --- main_config 413 | thread_pool default threads=2 max_queue=10; 414 | --- config 415 | server_tokens off; 416 | location /t { 417 | lua_io_write_buffer_size 0; 418 | rewrite_by_lua_block { 419 | local ngx_io = require "ngx.io" 420 | local file, err = ngx_io.open("conf/test.txt", "a") 421 | assert(type(file) == "table") 422 | assert(err == nil) 423 | local n, err = file:write("Hello, ") 424 | assert(n == 7) 425 | assert(err == nil) 426 | local ok, err = file:close() 427 | assert(ok) 428 | assert(err == nil) 429 | } 430 | 431 | content_by_lua_block { 432 | local ngx_io = require "ngx.io" 433 | local file, err = ngx_io.open("conf/test.txt", "w") 434 | assert(type(file) == "table") 435 | assert(err == nil) 436 | local n, err = file:write("World") 437 | assert(n == 5) 438 | assert(err == nil) 439 | local ok, err = file:close() 440 | assert(ok) 441 | assert(err == nil) 442 | 443 | local prefix = ngx.config.prefix() 444 | local name = prefix .. "/conf/test.txt" 445 | 446 | local f = io.open(name, "r") 447 | local data = f:read("*a") 448 | ngx.print(data) 449 | f:close() 450 | 451 | os.execute("rm -f " .. name) 452 | } 453 | } 454 | 455 | --- request 456 | GET /t 457 | --- response_body eval 458 | "World" 459 | --- grep_error_log: lua io thread event handler 460 | --- grep_error_log_out 461 | lua io thread event handler 462 | lua io thread event handler 463 | --- grep_error_log: lua io write done and resume 464 | --- grep_error_log_out 465 | lua io write done and resume 466 | lua io write done and resume 467 | --- grep_error_log: lua io open mode:"a" 468 | --- grep_error_log_out 469 | lua io open mode:"a" 470 | --- grep_error_log: lua io open mode:"w" 471 | --- grep_error_log_out 472 | lua io open mode:"w" 473 | --- no_error_log 474 | [error] 475 | --- no_error_log 476 | [crit] 477 | 478 | 479 | 480 | === TEST 10: write multiple times with varying the write stuff's size 481 | --- main_config 482 | thread_pool default threads=2 max_queue=10; 483 | --- config 484 | server_tokens off; 485 | location /t { 486 | lua_io_write_buffer_size 0; 487 | content_by_lua_block { 488 | local ngx_io = require "ngx.io" 489 | local file, err = ngx_io.open("conf/test.txt", "w") 490 | assert(type(file) == "table") 491 | assert(err == nil) 492 | local t = {} 493 | for i = 1, 256 do 494 | local n, err = file:write("a") 495 | assert(n == 1) 496 | assert(err == nil) 497 | t[#t + 1] = "a" 498 | end 499 | 500 | for i = 1, 64 do 501 | local n, err = file:write("b") 502 | assert(n == 1) 503 | assert(err == nil) 504 | t[#t + 1] = "b" 505 | end 506 | 507 | for i = 1, 32 do 508 | local n, err = file:write("c") 509 | assert(n == 1) 510 | assert(err == nil) 511 | t[#t + 1] = "c" 512 | end 513 | 514 | local ok, err = file:close() 515 | assert(ok) 516 | assert(err == nil) 517 | 518 | local prefix = ngx.config.prefix() 519 | local name = prefix .. "/conf/test.txt" 520 | 521 | local f = io.open(name, "r") 522 | local data = f:read("*a") 523 | f:close() 524 | 525 | os.execute("rm -f " .. name) 526 | 527 | assert(data == table.concat(t)) 528 | ngx.log(ngx.WARN, data) 529 | ngx.print("OK") 530 | } 531 | } 532 | 533 | --- request 534 | GET /t 535 | --- response_body: OK 536 | --- no_error_log 537 | [error] 538 | --- no_error_log 539 | [crit] 540 | 541 | 542 | 543 | === TEST 11: execute file:write operations in subrequest 544 | --- main_config 545 | thread_pool default threads=2 max_queue=10; 546 | --- config 547 | server_tokens off; 548 | location /t { 549 | lua_io_write_buffer_size 0; 550 | content_by_lua_block { 551 | local res = ngx.location.capture("/write") 552 | ngx.status = res.status 553 | ngx.print(res.body) 554 | } 555 | } 556 | location /write { 557 | internal; 558 | lua_io_write_buffer_size 0; 559 | content_by_lua_block { 560 | local ngx_io = require "ngx.io" 561 | local file, err = ngx_io.open("conf/test.txt", "w") 562 | assert(type(file) == "table") 563 | assert(err == nil) 564 | 565 | local n, err = file:write("Hello, ") 566 | assert(n == 7) 567 | assert(err == nil) 568 | 569 | local n, err = file:write("World") 570 | assert(n == 5) 571 | assert(err == nil) 572 | 573 | local ok, err = file:close() 574 | assert(ok) 575 | assert(err == nil) 576 | 577 | local prefix = ngx.config.prefix() 578 | local name = prefix .. "/conf/test.txt" 579 | 580 | local f = io.open(name, "r") 581 | local data = f:read("*a") 582 | f:close() 583 | 584 | os.execute("rm -f " .. name) 585 | ngx.print(data) 586 | } 587 | } 588 | 589 | --- request 590 | GET /t 591 | --- response_body: Hello, World 592 | --- no_error_log 593 | [error] 594 | --- no_error_log 595 | [crit] 596 | 597 | 598 | 599 | === TEST 12: execute file:write operations in other light thread 600 | --- main_config 601 | thread_pool default threads=2 max_queue=10; 602 | --- config 603 | server_tokens off; 604 | location /t { 605 | lua_io_write_buffer_size 0; 606 | content_by_lua_block { 607 | local f = function() 608 | local ngx_io = require "ngx.io" 609 | local file, err = ngx_io.open("conf/test.txt", "w") 610 | assert(type(file) == "table") 611 | assert(err == nil) 612 | 613 | local n, err = file:write("Hello, ") 614 | assert(n == 7) 615 | assert(err == nil) 616 | 617 | local n, err = file:write("World") 618 | assert(n == 5) 619 | assert(err == nil) 620 | 621 | local ok, err = file:close() 622 | assert(ok) 623 | assert(err == nil) 624 | end 625 | 626 | local co = ngx.thread.spawn(f) 627 | 628 | local ok = ngx.thread.wait(co) 629 | assert(ok) 630 | 631 | local prefix = ngx.config.prefix() 632 | local name = prefix .. "/conf/test.txt" 633 | 634 | local f = io.open(name, "r") 635 | local data = f:read("*a") 636 | f:close() 637 | 638 | os.execute("rm -f " .. name) 639 | ngx.print(data) 640 | } 641 | } 642 | 643 | --- request 644 | GET /t 645 | --- response_body: Hello, World 646 | --- no_error_log 647 | [error] 648 | --- no_error_log 649 | [crit] 650 | 651 | 652 | === TEST 13: try to write data on a closed file handle 653 | --- main_config 654 | thread_pool default threads=2 max_queue=10; 655 | --- config 656 | server_tokens off; 657 | location /t { 658 | lua_io_log_errors on; 659 | lua_io_write_buffer_size 0; 660 | content_by_lua_block { 661 | local ngx_io = require "ngx.io" 662 | local file, err = ngx_io.open("conf/test.txt", "w") 663 | assert(type(file) == "table") 664 | assert(err == nil) 665 | 666 | local ok, err = file:close() 667 | assert(ok) 668 | assert(err == nil) 669 | 670 | local n, err = file:write("Hello") 671 | assert(not n) 672 | ngx.print(err) 673 | 674 | local prefix = ngx.config.prefix() 675 | local name = prefix .. "/conf/test.txt" 676 | 677 | os.execute("rm -f " .. name) 678 | } 679 | } 680 | 681 | --- request 682 | GET /t 683 | --- response_body: closed 684 | --- error_log 685 | attempt to write data on a closed file object 686 | --- no_error_log 687 | [crit] 688 | -------------------------------------------------------------------------------- /t/03-file-flush.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | repeat_each(1); 4 | 5 | plan tests => repeat_each() * (4 * 4); 6 | 7 | log_level 'debug'; 8 | 9 | no_long_string(); 10 | run_tests(); 11 | 12 | __DATA__ 13 | 14 | === TEST 1: empty flush 15 | --- main_config 16 | thread_pool default threads=2 max_queue=10; 17 | --- config 18 | server_tokens off; 19 | location /t { 20 | lua_io_write_buffer_size 0; 21 | content_by_lua_block { 22 | local io = require "ngx.io" 23 | local file, err = io.open("conf/test.txt", "w") 24 | assert(type(file) == "table") 25 | assert(err == nil) 26 | 27 | local n, err = file:flush(true) 28 | assert(n) 29 | assert(err == nil) 30 | 31 | local ok, err = file:close() 32 | assert(ok) 33 | assert(err == nil) 34 | 35 | ngx.print("OK") 36 | 37 | local prefix = ngx.config.prefix() 38 | os.execute("rm -f " .. prefix .. "/conf/test.txt") 39 | } 40 | } 41 | 42 | --- request 43 | GET /t 44 | --- response_body: OK 45 | --- grep_error_log: lua io flush 46 | --- grep_error_log_out 47 | lua io flush 48 | --- grep_error_log: lua io flush done and resume 49 | --- grep_error_log_out 50 | lua io flush done and resume 51 | --- no_error_log 52 | [error] 53 | --- no_error_log 54 | [crit] 55 | 56 | 57 | 58 | === TEST 2: native flush 59 | --- main_config 60 | thread_pool default threads=2 max_queue=10; 61 | --- config 62 | server_tokens off; 63 | location /t { 64 | lua_io_write_buffer_size 0; 65 | content_by_lua_block { 66 | local _io = io 67 | local io = require "ngx.io" 68 | local file, err = io.open("conf/test.txt", "w") 69 | assert(type(file) == "table") 70 | assert(err == nil) 71 | 72 | local data = "Hello, 世界" 73 | local n, err = file:write(data) 74 | assert(n == #data) 75 | assert(err == nil) 76 | 77 | local n, err = file:flush(true) 78 | assert(n) 79 | assert(err == nil) 80 | 81 | local ok, err = file:close() 82 | assert(ok) 83 | assert(err == nil) 84 | 85 | local prefix = ngx.config.prefix() 86 | local name = prefix .. "/conf/test.txt" 87 | 88 | local file = _io.open(name) 89 | local data = file:read("*a") 90 | file:close() 91 | 92 | ngx.print(data) 93 | 94 | os.execute("rm -f " .. name) 95 | } 96 | } 97 | 98 | --- request 99 | GET /t 100 | --- response_body: Hello, 世界 101 | --- grep_error_log: lua io write 102 | --- grep_error_log_out 103 | lua io write 104 | --- grep_error_log: lua io flush 105 | --- grep_error_log_out 106 | lua io flush 107 | --- grep_error_log: lua io write done and resume 108 | --- grep_error_log_out 109 | lua io write done and resume 110 | --- grep_error_log: lua io flush done and resume 111 | --- grep_error_log_out 112 | lua io flush done and resume 113 | --- no_error_log 114 | [error] 115 | --- no_error_log 116 | [crit] 117 | 118 | 119 | 120 | === TEST 3: flush twice 121 | --- main_config 122 | thread_pool default threads=2 max_queue=10; 123 | --- config 124 | server_tokens off; 125 | location /t { 126 | lua_io_write_buffer_size 0; 127 | content_by_lua_block { 128 | local _io = io 129 | local io = require "ngx.io" 130 | local file, err = io.open("conf/test.txt", "w") 131 | assert(type(file) == "table") 132 | assert(err == nil) 133 | 134 | local data = "Hello, 世界" 135 | local n, err = file:write(data) 136 | assert(n == #data) 137 | assert(err == nil) 138 | 139 | local n, err = file:flush(true) 140 | assert(n) 141 | assert(err == nil) 142 | 143 | local n, err = file:flush(true) 144 | assert(n) 145 | assert(err == nil) 146 | 147 | local ok, err = file:close() 148 | assert(ok) 149 | assert(err == nil) 150 | 151 | local prefix = ngx.config.prefix() 152 | local name = prefix .. "/conf/test.txt" 153 | 154 | local file = _io.open(name) 155 | local data = file:read("*a") 156 | file:close() 157 | 158 | ngx.print(data) 159 | 160 | os.execute("rm -f " .. name) 161 | } 162 | } 163 | 164 | --- request 165 | GET /t 166 | --- response_body: Hello, 世界 167 | --- grep_error_log: lua io write 168 | --- grep_error_log_out 169 | lua io write 170 | --- grep_error_log: lua io flush 171 | --- grep_error_log_out 172 | lua io flush 173 | lua io flush 174 | --- grep_error_log: lua io write done and resume 175 | --- grep_error_log_out 176 | lua io write done and resume 177 | --- grep_error_log: lua io flush done and resume 178 | --- grep_error_log_out 179 | lua io flush done and resume 180 | lua io flush done and resume 181 | --- no_error_log 182 | [error] 183 | --- no_error_log 184 | [crit] 185 | 186 | 187 | 188 | === TEST 4: flush on a closed file handle 189 | --- main_config 190 | thread_pool default threads=2 max_queue=10; 191 | --- config 192 | server_tokens off; 193 | location /t { 194 | lua_io_write_buffer_size 0; 195 | lua_io_log_errors on; 196 | content_by_lua_block { 197 | local _io = io 198 | local io = require "ngx.io" 199 | local file, err = io.open("conf/test.txt", "w") 200 | assert(type(file) == "table") 201 | assert(err == nil) 202 | 203 | local data = "Hello, 世界" 204 | local n, err = file:write(data) 205 | assert(n == #data) 206 | assert(err == nil) 207 | 208 | local ok, err = file:close() 209 | assert(ok) 210 | assert(err == nil) 211 | 212 | local n, err = file:flush(true) 213 | assert(not n) 214 | ngx.print(err) 215 | 216 | local prefix = ngx.config.prefix() 217 | local name = prefix .. "/conf/test.txt" 218 | 219 | os.execute("rm -f " .. name) 220 | } 221 | } 222 | 223 | --- request 224 | GET /t 225 | --- response_body: closed 226 | --- error_log 227 | attempt to flush data on a closed file object 228 | --- no_error_log 229 | [crit] 230 | -------------------------------------------------------------------------------- /t/04-file-write-back-cache.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | repeat_each(3); 4 | 5 | plan tests => repeat_each() * (5 * 4 + 1); 6 | 7 | log_level 'debug'; 8 | 9 | no_long_string(); 10 | run_tests(); 11 | 12 | __DATA__ 13 | 14 | === TEST 1: native write cache 15 | --- main_config 16 | thread_pool default threads=2 max_queue=10; 17 | --- config 18 | server_tokens off; 19 | location /t { 20 | lua_io_log_errors on; 21 | content_by_lua_block { 22 | local ngx_io = require "ngx.io" 23 | local file, err = ngx_io.open("conf/test.txt", "w") 24 | assert(type(file) == "table") 25 | assert(err == nil) 26 | 27 | local n, err = file:write("Hello") 28 | assert(n == 5) 29 | assert(err == nil) 30 | 31 | local n, err = file:write(", World") 32 | assert(n == 7) 33 | assert(err == nil) 34 | 35 | local ok, err = file:flush() 36 | assert(ok) 37 | assert(err == nil) 38 | 39 | local ok, err = file:close() 40 | assert(ok) 41 | assert(err == nil) 42 | 43 | local prefix = ngx.config.prefix() 44 | local name = prefix .. "/conf/test.txt" 45 | 46 | local file = io.open(name, "r") 47 | ngx.print(file:read("*a")) 48 | file:close() 49 | 50 | os.execute("rm -f " .. name) 51 | } 52 | } 53 | 54 | --- request 55 | GET /t 56 | --- response_body: Hello, World 57 | --- grep_error_log: lua io write cache 58 | --- grep_error_log_out 59 | lua io write cache 60 | lua io write cache 61 | --- grep_error_log: lua io flush 62 | --- grep_error_log_out 63 | lua io flush 64 | --- grep_error_log: lua io flush done and resume 65 | --- grep_error_log_out 66 | lua io flush done and resume 67 | --- no_error_log 68 | [error] 69 | --- no_error_log 70 | [crit] 71 | 72 | 73 | 74 | === TEST 2: write cache and through alternately 75 | --- main_config 76 | thread_pool default threads=2 max_queue=10; 77 | --- config 78 | server_tokens off; 79 | location /t { 80 | lua_io_log_errors on; 81 | lua_io_write_buffer_size 5; 82 | content_by_lua_block { 83 | local ngx_io = require "ngx.io" 84 | local file, err = ngx_io.open("conf/test.txt", "w") 85 | assert(type(file) == "table") 86 | assert(err == nil) 87 | 88 | -- cache 89 | local n, err = file:write("Hello") 90 | assert(n == 5) 91 | assert(err == nil) 92 | 93 | -- through 94 | local n, err = file:write(",") 95 | assert(n == 1) 96 | assert(err == nil) 97 | 98 | -- cache 99 | local n, err = file:write(" Wor") 100 | assert(n == 4) 101 | assert(err == nil) 102 | 103 | -- through 104 | local n, err = file:write("ld") 105 | assert(n == 2) 106 | assert(err == nil) 107 | 108 | local ok, err = file:flush() 109 | assert(ok) 110 | assert(err == nil) 111 | 112 | local ok, err = file:close() 113 | assert(ok) 114 | assert(err == nil) 115 | 116 | local prefix = ngx.config.prefix() 117 | local name = prefix .. "/conf/test.txt" 118 | 119 | local file = io.open(name, "r") 120 | ngx.print(file:read("*a")) 121 | file:close() 122 | 123 | os.execute("rm -f " .. name) 124 | } 125 | } 126 | 127 | --- request 128 | GET /t 129 | --- response_body: Hello, World 130 | --- grep_error_log: lua io write cache 131 | --- grep_error_log_out 132 | lua io write cache 133 | lua io write cache 134 | --- grep_error_log: lua io write through 135 | --- grep_error_log_out 136 | lua io write through 137 | --- grep_error_log: lua io flush 138 | --- grep_error_log_out 139 | lua io flush 140 | --- grep_error_log: lua io write done and resume 141 | --- grep_error_log_out 142 | lua io write done and resume 143 | --- grep_error_log: lua io flush done and resume 144 | --- grep_error_log_out 145 | lua io flush done and resume 146 | --- no_error_log 147 | [error] 148 | --- no_error_log 149 | [crit] 150 | 151 | 152 | === TEST 3: file flush while closing 153 | --- main_config 154 | thread_pool default threads=2 max_queue=10; 155 | --- config 156 | server_tokens off; 157 | location /t { 158 | lua_io_log_errors on; 159 | lua_io_write_buffer_size 12; 160 | content_by_lua_block { 161 | local ngx_io = require "ngx.io" 162 | local file, err = ngx_io.open("conf/test.txt", "w") 163 | assert(type(file) == "table") 164 | assert(err == nil) 165 | 166 | -- cache 167 | local n, err = file:write("Hello") 168 | assert(n == 5) 169 | assert(err == nil) 170 | 171 | -- cache 172 | local n, err = file:write(",") 173 | assert(n == 1) 174 | assert(err == nil) 175 | 176 | -- cache 177 | local n, err = file:write(" Wor") 178 | assert(n == 4) 179 | assert(err == nil) 180 | 181 | -- cache 182 | local n, err = file:write("ld") 183 | assert(n == 2) 184 | assert(err == nil) 185 | 186 | local prefix = ngx.config.prefix() 187 | local name = prefix .. "/conf/test.txt" 188 | 189 | local f = io.open(name, "r") 190 | assert(f:read("*a") == "") 191 | f:close() 192 | 193 | local ok, err = file:close() 194 | assert(ok) 195 | assert(err == nil) 196 | 197 | local file = io.open(name, "r") 198 | ngx.print(file:read("*a")) 199 | file:close() 200 | 201 | os.execute("rm -f " .. name) 202 | } 203 | } 204 | 205 | --- request 206 | GET /t 207 | --- response_body: Hello, World 208 | --- grep_error_log: lua io write cache 209 | --- grep_error_log_out 210 | lua io write cache 211 | lua io write cache 212 | lua io write cache 213 | lua io write cache 214 | --- grep_error_log: lua io flush 215 | --- grep_error_log_out 216 | lua io flush 217 | --- no_error_log 218 | lua io write done and resume 219 | --- grep_error_log: lua io flush done and resume 220 | --- grep_error_log_out 221 | lua io flush done and resume 222 | --- grep_error_log: lua io close flushing 223 | --- grep_error_log_out 224 | lua io close flushing 225 | --- grep_error_log: lua io file ctx finalize 226 | --- grep_error_log_out 227 | lua io file ctx finalize 228 | --- no_error_log 229 | [error] 230 | --- no_error_log 231 | [crit] 232 | 233 | 234 | 235 | === TEST 4: write data exceeds the buffer size 236 | --- main_config 237 | thread_pool default threads=2 max_queue=10; 238 | --- config 239 | server_tokens off; 240 | location /t { 241 | lua_io_log_errors on; 242 | lua_io_write_buffer_size 8; 243 | content_by_lua_block { 244 | local ngx_io = require "ngx.io" 245 | local file, err = ngx_io.open("conf/test.txt", "w") 246 | assert(type(file) == "table") 247 | assert(err == nil) 248 | 249 | -- cache 250 | local n, err = file:write("Hello") 251 | assert(n == 5) 252 | assert(err == nil) 253 | 254 | -- through 255 | local n, err = file:write(", World") 256 | assert(n == 7) 257 | assert(err == nil) 258 | 259 | -- cache 260 | local n, err = file:write("This is") 261 | assert(n == 7) 262 | assert(err == nil) 263 | 264 | -- through 265 | local n, err = file:write("a beautiful age") 266 | assert(n == 15) 267 | assert(err == nil) 268 | 269 | local ok, err = file:close() 270 | assert(ok) 271 | assert(err == nil) 272 | 273 | local prefix = ngx.config.prefix() 274 | local name = prefix .. "/conf/test.txt" 275 | 276 | local file = io.open(name, "r") 277 | ngx.print(file:read("*a")) 278 | file:close() 279 | 280 | os.execute("rm -f " .. name) 281 | } 282 | } 283 | 284 | --- request 285 | GET /t 286 | --- response_body: Hello, WorldThis isa beautiful age 287 | --- grep_error_log: lua io write cache 288 | --- grep_error_log_out 289 | lua io write cache 290 | lua io write cache 291 | --- grep_error_log: lua io write through 292 | --- grep_error_log_out 293 | lua io write through 294 | lua io write through 295 | --- grep_error_log: lua io thread write chain 296 | --- grep_error_log_out 297 | lua io thread write chain 298 | lua io thread write chain 299 | --- grep_error_log: lua io write done and resume 300 | --- grep_error_log_out 301 | lua io write done and resume 302 | lua io write done and resume 303 | --- grep_error_log: lua io file ctx finalize 304 | --- grep_error_log_out 305 | lua io file ctx finalize 306 | --- no_error_log 307 | [error] 308 | --- no_error_log 309 | [crit] 310 | --- no_error_log: lua io close flushing 311 | 312 | 313 | 314 | === TEST 5: 4096 size buffer 315 | --- main_config 316 | thread_pool default threads=2 max_queue=10; 317 | --- config 318 | server_tokens off; 319 | location /t { 320 | lua_io_log_errors on; 321 | lua_io_write_buffer_size 4096; 322 | content_by_lua_block { 323 | local new_tab = require "table.new" 324 | local ngx_io = require "ngx.io" 325 | local file, err = ngx_io.open("conf/test.txt", "w") 326 | assert(type(file) == "table") 327 | assert(err == nil) 328 | 329 | local total = 5050 330 | while true do 331 | local size = math.random(1, total) 332 | total = total - size 333 | 334 | local t = new_tab(size, 0) 335 | for i = 1, size do 336 | t[i] = string.char(math.random(48, 122)) 337 | end 338 | 339 | local n, err = file:write(t) 340 | assert(n == size) 341 | assert(err == nil) 342 | 343 | if total == 0 then 344 | break 345 | end 346 | end 347 | 348 | local ok, err = file:close() 349 | assert(ok) 350 | assert(err == nil) 351 | 352 | ngx.print("OK") 353 | } 354 | } 355 | 356 | --- request 357 | GET /t 358 | --- response_body: OK 359 | --- grep_error_log: lua io write through 360 | --- grep_error_log_out 361 | lua io write through 362 | --- grep_error_log: lua io thread write chain 363 | --- grep_error_log_out 364 | lua io thread write chain 365 | --- error_log: lua io close flushing 366 | --- grep_error_log: lua io write done and resume 367 | --- grep_error_log_out 368 | lua io write done and resume 369 | --- grep_error_log: lua io file ctx finalize 370 | --- grep_error_log_out 371 | lua io file ctx finalize 372 | --- no_error_log 373 | [error] 374 | --- no_error_log 375 | [crit] 376 | -------------------------------------------------------------------------------- /t/05-file-seek.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | repeat_each(3); 4 | 5 | plan tests => repeat_each() * (6 * 3 + 5 * 14); 6 | 7 | log_level 'debug'; 8 | 9 | no_long_string(); 10 | run_tests(); 11 | 12 | __DATA__ 13 | 14 | === TEST 1: file seek to the head with append mode and always write through 15 | --- main_config 16 | thread_pool default threads=2 max_queue=10; 17 | --- config 18 | server_tokens off; 19 | location /t { 20 | lua_io_write_buffer_size 0; 21 | content_by_lua_block { 22 | local ngx_io = require "ngx.io" 23 | local file, err = ngx_io.open("conf/test.txt", "a") 24 | assert(type(file) == "table") 25 | assert(err == nil) 26 | 27 | local n, err = file:write("Hello") 28 | assert(n == 5) 29 | assert(err == nil) 30 | 31 | local n, err = file:write(", World") 32 | assert(n == 7) 33 | assert(err == nil) 34 | 35 | local offset, err = file:seek("set", 1) 36 | assert(offset == 1) 37 | assert(err == nil) 38 | 39 | local n, err = file:write(". Word from tokers") 40 | assert(n == 18) 41 | assert(err == nil) 42 | 43 | local ok, err = file:close() 44 | assert(ok) 45 | assert(err == nil) 46 | 47 | local prefix = ngx.config.prefix() 48 | local name = prefix .. "/conf/test.txt" 49 | 50 | local file = io.open(name, "r") 51 | ngx.print(file:read("*a")) 52 | file:close() 53 | 54 | os.execute("rm -f " .. name) 55 | } 56 | } 57 | 58 | --- request 59 | GET /t 60 | --- response_body: Hello, World. Word from tokers 61 | --- grep_error_log: lua io seek whence:0 offset:1 62 | --- grep_error_log_out 63 | lua io seek whence:0 offset:1 64 | --- no_error_log eval 65 | ["error", "crit"] 66 | 67 | 68 | 69 | === TEST 2: file seek to the head with write only mode and combined with write back cache 70 | --- main_config 71 | thread_pool default threads=2 max_queue=10; 72 | --- config 73 | server_tokens off; 74 | location /t { 75 | lua_io_write_buffer_size 16; 76 | content_by_lua_block { 77 | local ngx_io = require "ngx.io" 78 | local file, err = ngx_io.open("conf/test.txt", "w") 79 | assert(type(file) == "table") 80 | assert(err == nil) 81 | 82 | local n, err = file:write("Hello") 83 | assert(n == 5) 84 | assert(err == nil) 85 | 86 | local n, err = file:write(", World") 87 | assert(n == 7) 88 | assert(err == nil) 89 | 90 | local offset, err = file:seek("set", 7) 91 | assert(offset == 7) 92 | assert(err == nil) 93 | 94 | local n, err = file:write("friend") 95 | assert(n == 6) 96 | assert(err == nil) 97 | 98 | local ok, err = file:close() 99 | assert(ok) 100 | assert(err == nil) 101 | 102 | local prefix = ngx.config.prefix() 103 | local name = prefix .. "/conf/test.txt" 104 | 105 | local file = io.open(name, "r") 106 | ngx.print(file:read("*a")) 107 | file:close() 108 | 109 | os.execute("rm -f " .. name) 110 | } 111 | } 112 | 113 | --- request 114 | GET /t 115 | --- response_body: Hello, friend 116 | --- grep_error_log: lua io seek whence:0 offset:7 117 | --- grep_error_log_out 118 | lua io seek whence:0 offset:7 119 | --- error_log: lua io seek saved co ctx 120 | --- no_error_log eval 121 | ["error", "crit"] 122 | 123 | 124 | 125 | === TEST 3: file seek to the current position 126 | --- main_config 127 | thread_pool default threads=2 max_queue=10; 128 | --- config 129 | server_tokens off; 130 | location /t { 131 | lua_io_write_buffer_size 16; 132 | content_by_lua_block { 133 | local ngx_io = require "ngx.io" 134 | local file, err = ngx_io.open("conf/test.txt", "w") 135 | assert(type(file) == "table") 136 | assert(err == nil) 137 | 138 | local n, err = file:write("Hello") 139 | assert(n == 5) 140 | assert(err == nil) 141 | 142 | local n, err = file:write(", World") 143 | assert(n == 7) 144 | assert(err == nil) 145 | 146 | local offset, err = file:seek("cur", 0) 147 | assert(offset == 12) 148 | assert(err == nil) 149 | 150 | local n, err = file:write("friend") 151 | assert(n == 6) 152 | assert(err == nil) 153 | 154 | local ok, err = file:close() 155 | assert(ok) 156 | assert(err == nil) 157 | 158 | local prefix = ngx.config.prefix() 159 | local name = prefix .. "/conf/test.txt" 160 | 161 | local file = io.open(name, "r") 162 | ngx.print(file:read("*a")) 163 | file:close() 164 | 165 | os.execute("rm -f " .. name) 166 | } 167 | } 168 | 169 | --- request 170 | GET /t 171 | --- response_body: Hello, Worldfriend 172 | --- grep_error_log: lua io seek whence:1 offset:0 173 | --- grep_error_log_out 174 | lua io seek whence:1 offset:0 175 | --- error_log: lua io seek saved co ctx 176 | --- no_error_log eval 177 | ["error", "crit"] 178 | 179 | 180 | 181 | === TEST 4: file seek to the current position plus negative offset 182 | --- main_config 183 | thread_pool default threads=2 max_queue=10; 184 | --- config 185 | server_tokens off; 186 | location /t { 187 | lua_io_write_buffer_size 16; 188 | content_by_lua_block { 189 | local ngx_io = require "ngx.io" 190 | local file, err = ngx_io.open("conf/test.txt", "w") 191 | assert(type(file) == "table") 192 | assert(err == nil) 193 | 194 | local n, err = file:write("Hello") 195 | assert(n == 5) 196 | assert(err == nil) 197 | 198 | local n, err = file:write(", World") 199 | assert(n == 7) 200 | assert(err == nil) 201 | 202 | local offset, err = file:seek("cur", -1) 203 | assert(offset == 11) 204 | assert(err == nil) 205 | 206 | local n, err = file:write("friend") 207 | assert(n == 6) 208 | assert(err == nil) 209 | 210 | local ok, err = file:close() 211 | assert(ok) 212 | assert(err == nil) 213 | 214 | local prefix = ngx.config.prefix() 215 | local name = prefix .. "/conf/test.txt" 216 | 217 | local file = io.open(name, "r") 218 | ngx.print(file:read("*a")) 219 | file:close() 220 | 221 | os.execute("rm -f " .. name) 222 | } 223 | } 224 | 225 | --- request 226 | GET /t 227 | --- response_body: Hello, Worlfriend 228 | --- grep_error_log: lua io seek whence:1 offset:-1 229 | --- grep_error_log_out 230 | lua io seek whence:1 offset:-1 231 | --- error_log: lua io seek saved co ctx 232 | --- no_error_log eval 233 | ["error", "crit"] 234 | 235 | 236 | 237 | === TEST 5: file seek to the current position plus negative offset and the ultimate offset is negative 238 | --- main_config 239 | thread_pool default threads=2 max_queue=10; 240 | --- config 241 | server_tokens off; 242 | location /t { 243 | lua_io_write_buffer_size 16; 244 | content_by_lua_block { 245 | local ngx_io = require "ngx.io" 246 | local file, err = ngx_io.open("conf/test.txt", "w") 247 | assert(type(file) == "table") 248 | assert(err == nil) 249 | 250 | local n, err = file:write("Hello") 251 | assert(n == 5) 252 | assert(err == nil) 253 | 254 | local n, err = file:write(", World") 255 | assert(n == 7) 256 | assert(err == nil) 257 | 258 | local offset, err = file:seek("cur", -100) 259 | assert(offset == nil) 260 | ngx.say(err) 261 | 262 | local ok, err = file:close() 263 | assert(ok) 264 | assert(err == nil) 265 | 266 | local prefix = ngx.config.prefix() 267 | local name = prefix .. "/conf/test.txt" 268 | 269 | local file = io.open(name, "r") 270 | ngx.say(file:read("*a")) 271 | file:close() 272 | 273 | os.execute("rm -f " .. name) 274 | } 275 | } 276 | 277 | --- request 278 | GET /t 279 | --- response_body 280 | invalid argument 281 | Hello, World 282 | --- grep_error_log: lua io seek whence:1 offset:-100 283 | --- grep_error_log_out 284 | lua io seek whence:1 offset:-100 285 | --- no_error_log eval 286 | ["error", "crit"] 287 | 288 | 289 | 290 | === TEST 6: file seek to the current position plus positive offset and generate the file hole 291 | --- main_config 292 | thread_pool default threads=2 max_queue=10; 293 | --- config 294 | server_tokens off; 295 | location /t { 296 | lua_io_write_buffer_size 16; 297 | content_by_lua_block { 298 | local ngx_io = require "ngx.io" 299 | local file, err = ngx_io.open("conf/test.txt", "w") 300 | assert(type(file) == "table") 301 | assert(err == nil) 302 | 303 | local n, err = file:write("Hello") 304 | assert(n == 5) 305 | assert(err == nil) 306 | 307 | local n, err = file:write(", World") 308 | assert(n == 7) 309 | assert(err == nil) 310 | 311 | local offset, err = file:seek("cur", 3) 312 | assert(offset == 15) 313 | assert(err == nil) 314 | 315 | local n, err = file:write(" after the hole") 316 | assert(n == 15) 317 | assert(err == nil) 318 | 319 | local ok, err = file:close() 320 | assert(ok) 321 | assert(err == nil) 322 | 323 | local prefix = ngx.config.prefix() 324 | local name = prefix .. "/conf/test.txt" 325 | 326 | local file = io.open(name, "r") 327 | local data = file:read("*a") 328 | ngx.print("data length: ", #data) 329 | file:close() 330 | 331 | os.execute("rm -f " .. name) 332 | } 333 | } 334 | 335 | --- request 336 | GET /t 337 | --- response_body: data length: 30 338 | --- grep_error_log: lua io seek whence:1 offset:3 339 | --- grep_error_log_out 340 | lua io seek whence:1 offset:3 341 | --- no_error_log eval 342 | ["error", "crit"] 343 | 344 | 345 | 346 | === TEST 7: file seek to the end of file plus a negative offset (backward) 347 | --- main_config 348 | thread_pool default threads=2 max_queue=10; 349 | --- config 350 | server_tokens off; 351 | location /t { 352 | lua_io_write_buffer_size 16; 353 | content_by_lua_block { 354 | local ngx_io = require "ngx.io" 355 | local file, err = ngx_io.open("conf/test.txt", "w") 356 | assert(type(file) == "table") 357 | assert(err == nil) 358 | 359 | local n, err = file:write("Hello") 360 | assert(n == 5) 361 | assert(err == nil) 362 | 363 | local n, err = file:write(", World") 364 | assert(n == 7) 365 | assert(err == nil) 366 | 367 | local offset, err = file:seek("end", -1) 368 | assert(offset == 11) 369 | assert(err == nil) 370 | 371 | local n, err = file:write("D") 372 | assert(n == 1) 373 | assert(err == nil) 374 | 375 | local ok, err = file:close() 376 | assert(ok) 377 | assert(err == nil) 378 | 379 | local prefix = ngx.config.prefix() 380 | local name = prefix .. "/conf/test.txt" 381 | 382 | local file = io.open(name, "r") 383 | ngx.print(file:read("*a")) 384 | file:close() 385 | 386 | os.execute("rm -f " .. name) 387 | } 388 | } 389 | 390 | --- request 391 | GET /t 392 | --- response_body: Hello, WorlD 393 | --- grep_error_log: lua io seek whence:2 offset:-1 394 | --- grep_error_log_out 395 | lua io seek whence:2 offset:-1 396 | --- no_error_log eval 397 | ["error", "crit"] 398 | 399 | 400 | 401 | === TEST 8: file seek to the end of file plus a negative offset and result in the final offset is negative 402 | --- main_config 403 | thread_pool default threads=2 max_queue=10; 404 | --- config 405 | server_tokens off; 406 | location /t { 407 | lua_io_write_buffer_size 16; 408 | content_by_lua_block { 409 | local ngx_io = require "ngx.io" 410 | local file, err = ngx_io.open("conf/test.txt", "w") 411 | assert(type(file) == "table") 412 | assert(err == nil) 413 | 414 | local n, err = file:write("Hello") 415 | assert(n == 5) 416 | assert(err == nil) 417 | 418 | local n, err = file:write(", World") 419 | assert(n == 7) 420 | assert(err == nil) 421 | 422 | local offset, err = file:seek("end", -1111) 423 | assert(offset == nil) 424 | ngx.say(err) 425 | 426 | local n, err = file:write(". I'm just go ahead~") 427 | assert(n == 20) 428 | assert(err == nil) 429 | 430 | local ok, err = file:close() 431 | assert(ok) 432 | assert(err == nil) 433 | 434 | local prefix = ngx.config.prefix() 435 | local name = prefix .. "/conf/test.txt" 436 | 437 | local file = io.open(name, "r") 438 | ngx.say(file:read("*a")) 439 | file:close() 440 | 441 | os.execute("rm -f " .. name) 442 | } 443 | } 444 | 445 | --- request 446 | GET /t 447 | --- response_body 448 | invalid argument 449 | Hello, World. I'm just go ahead~ 450 | --- grep_error_log: lua io seek whence:2 offset:-1111 451 | --- grep_error_log_out 452 | lua io seek whence:2 offset:-1111 453 | --- no_error_log eval 454 | ["error", "crit"] 455 | 456 | 457 | 458 | === TEST 9: file seek to the end of file plus a positive offset and generate the file hole 459 | --- main_config 460 | thread_pool default threads=2 max_queue=10; 461 | --- config 462 | server_tokens off; 463 | location /t { 464 | lua_io_write_buffer_size 16; 465 | content_by_lua_block { 466 | local ngx_io = require "ngx.io" 467 | local file, err = ngx_io.open("conf/test.txt", "w") 468 | assert(type(file) == "table") 469 | assert(err == nil) 470 | 471 | local n, err = file:write("Hello") 472 | assert(n == 5) 473 | assert(err == nil) 474 | 475 | local n, err = file:write(", World") 476 | assert(n == 7) 477 | assert(err == nil) 478 | 479 | local offset, err = file:seek("end", 31) 480 | assert(offset == 43) 481 | assert(err == nil) 482 | 483 | local n, err = file:write(". I'm just go ahead~") 484 | assert(n == 20) 485 | assert(err == nil) 486 | 487 | local ok, err = file:close() 488 | assert(ok) 489 | assert(err == nil) 490 | 491 | local prefix = ngx.config.prefix() 492 | local name = prefix .. "/conf/test.txt" 493 | 494 | local file = io.open(name, "r") 495 | ngx.print("data length: ", #file:read("*a")) 496 | file:close() 497 | 498 | os.execute("rm -f " .. name) 499 | } 500 | } 501 | 502 | --- request 503 | GET /t 504 | --- response_body: data length: 63 505 | --- grep_error_log: lua io seek whence:2 offset:31 506 | --- grep_error_log_out 507 | lua io seek whence:2 offset:31 508 | --- no_error_log eval 509 | ["error", "crit"] 510 | 511 | 512 | 513 | === TEST 10: file seek to a fixed postition 514 | --- main_config 515 | thread_pool default threads=2 max_queue=10; 516 | --- config 517 | server_tokens off; 518 | location /t { 519 | lua_io_write_buffer_size 16; 520 | content_by_lua_block { 521 | local ngx_io = require "ngx.io" 522 | local file, err = ngx_io.open("conf/test.txt", "w") 523 | assert(type(file) == "table") 524 | assert(err == nil) 525 | 526 | local n, err = file:write("Hello") 527 | assert(n == 5) 528 | assert(err == nil) 529 | 530 | local n, err = file:write(", World") 531 | assert(n == 7) 532 | assert(err == nil) 533 | 534 | local offset, err = file:seek("set", 8) 535 | assert(offset == 8) 536 | assert(err == nil) 537 | 538 | local n, err = file:write(". I'm just go ahead~") 539 | assert(n == 20) 540 | assert(err == nil) 541 | 542 | local ok, err = file:close() 543 | assert(ok) 544 | assert(err == nil) 545 | 546 | local prefix = ngx.config.prefix() 547 | local name = prefix .. "/conf/test.txt" 548 | 549 | local file = io.open(name, "r") 550 | ngx.print(file:read("*a")) 551 | file:close() 552 | 553 | os.execute("rm -f " .. name) 554 | } 555 | } 556 | 557 | --- request 558 | GET /t 559 | --- response_body: Hello, W. I'm just go ahead~ 560 | --- grep_error_log: lua io seek whence:0 offset:8 561 | --- grep_error_log_out 562 | lua io seek whence:0 offset:8 563 | --- no_error_log eval 564 | ["error", "crit"] 565 | 566 | 567 | 568 | === TEST 11: file seek to a fixed postition and generate a file hole 569 | --- main_config 570 | thread_pool default threads=2 max_queue=10; 571 | --- config 572 | server_tokens off; 573 | location /t { 574 | lua_io_write_buffer_size 16; 575 | content_by_lua_block { 576 | local ngx_io = require "ngx.io" 577 | local file, err = ngx_io.open("conf/test.txt", "w") 578 | assert(type(file) == "table") 579 | assert(err == nil) 580 | 581 | local n, err = file:write("Hello") 582 | assert(n == 5) 583 | assert(err == nil) 584 | 585 | local n, err = file:write(", World") 586 | assert(n == 7) 587 | assert(err == nil) 588 | 589 | local offset, err = file:seek("set", 88) 590 | assert(offset == 88) 591 | assert(err == nil) 592 | 593 | local n, err = file:write(". I'm just go ahead~") 594 | assert(n == 20) 595 | assert(err == nil) 596 | 597 | local ok, err = file:close() 598 | assert(ok) 599 | assert(err == nil) 600 | 601 | local prefix = ngx.config.prefix() 602 | local name = prefix .. "/conf/test.txt" 603 | 604 | local file = io.open(name, "r") 605 | ngx.print("data length: ", #file:read("*a")) 606 | file:close() 607 | 608 | os.execute("rm -f " .. name) 609 | } 610 | } 611 | 612 | --- request 613 | GET /t 614 | --- response_body: data length: 108 615 | --- grep_error_log: lua io seek whence:0 offset:88 616 | --- grep_error_log_out 617 | lua io seek whence:0 offset:88 618 | --- no_error_log eval 619 | ["error", "crit"] 620 | 621 | 622 | 623 | === TEST 12: buffered read file and do seek 624 | --- main_config 625 | thread_pool default threads=2 max_queue=10; 626 | --- config 627 | server_tokens off; 628 | location /t { 629 | lua_io_write_buffer_size 16; 630 | lua_io_read_buffer_size 256; 631 | content_by_lua_block { 632 | local ngx_io = require "ngx.io" 633 | local file, err = ngx_io.open("conf/test.txt", "w") 634 | assert(type(file) == "table") 635 | assert(err == nil) 636 | 637 | local n, err = file:write("Hello") 638 | assert(n == 5) 639 | assert(err == nil) 640 | 641 | local n, err = file:write(", World") 642 | assert(n == 7) 643 | assert(err == nil) 644 | 645 | local ok, err = file:close() 646 | assert(ok) 647 | assert(err == nil) 648 | 649 | local file, err = ngx_io.open("conf/test.txt", "r") 650 | assert(type(file) == "table") 651 | assert(err == nil) 652 | 653 | local data, err = file:read(6) 654 | ngx.print(data) 655 | assert(err == nil) 656 | 657 | local offset, err = file:seek("cur", -3) 658 | assert(offset == 3) 659 | assert(err == nil) 660 | 661 | local data, err = file:read("*a") 662 | ngx.print(data) 663 | assert(err == nil) 664 | 665 | local ok, err = file:close() 666 | assert(ok) 667 | assert(err == nil) 668 | } 669 | } 670 | 671 | --- request 672 | GET /t 673 | --- response_body: Hello,lo, World 674 | --- grep_error_log: lua io seek drain read chain 675 | --- grep_error_log_out 676 | lua io seek drain read chain 677 | --- no_error_log eval 678 | ["error", "crit"] 679 | 680 | 681 | 682 | === TEST 13: buffered read file and do seek, to a fixed position 683 | --- main_config 684 | thread_pool default threads=2 max_queue=10; 685 | --- config 686 | server_tokens off; 687 | location /t { 688 | lua_io_write_buffer_size 16; 689 | lua_io_read_buffer_size 256; 690 | content_by_lua_block { 691 | local ngx_io = require "ngx.io" 692 | local file, err = ngx_io.open("conf/test.txt", "w") 693 | assert(type(file) == "table") 694 | assert(err == nil) 695 | 696 | local n, err = file:write("Hello") 697 | assert(n == 5) 698 | assert(err == nil) 699 | 700 | local n, err = file:write(", World") 701 | assert(n == 7) 702 | assert(err == nil) 703 | 704 | local ok, err = file:close() 705 | assert(ok) 706 | assert(err == nil) 707 | 708 | local file, err = ngx_io.open("conf/test.txt", "r") 709 | assert(type(file) == "table") 710 | assert(err == nil) 711 | 712 | local data, err = file:read(6) 713 | ngx.print(data) 714 | assert(err == nil) 715 | 716 | local offset, err = file:seek("set", 1) 717 | assert(offset == 1) 718 | assert(err == nil) 719 | 720 | local data, err = file:read(4) 721 | ngx.print(data) 722 | assert(err == nil) 723 | 724 | local ok, err = file:close() 725 | assert(ok) 726 | assert(err == nil) 727 | 728 | local prefix = ngx.config.prefix() 729 | local name = prefix .. "/conf/test.txt" 730 | 731 | os.execute("rm -f " .. name) 732 | } 733 | } 734 | 735 | --- request 736 | GET /t 737 | --- response_body: Hello,ello 738 | --- grep_error_log: lua io seek drain read chain 739 | --- grep_error_log_out 740 | lua io seek drain read chain 741 | --- no_error_log eval 742 | ["error", "crit"] 743 | 744 | 745 | 746 | === TEST 14: buffered read file and do seek, to the position where it is relative to the end of file 747 | --- main_config 748 | thread_pool default threads=2 max_queue=10; 749 | --- config 750 | server_tokens off; 751 | location /t { 752 | lua_io_write_buffer_size 16; 753 | lua_io_read_buffer_size 256; 754 | content_by_lua_block { 755 | local ngx_io = require "ngx.io" 756 | local file, err = ngx_io.open("conf/test.txt", "w") 757 | assert(type(file) == "table") 758 | assert(err == nil) 759 | 760 | local n, err = file:write("Hello") 761 | assert(n == 5) 762 | assert(err == nil) 763 | 764 | local n, err = file:write(", World") 765 | assert(n == 7) 766 | assert(err == nil) 767 | 768 | local ok, err = file:close() 769 | assert(ok) 770 | assert(err == nil) 771 | 772 | local file, err = ngx_io.open("conf/test.txt", "r") 773 | assert(type(file) == "table") 774 | assert(err == nil) 775 | 776 | local data, err = file:read(6) 777 | ngx.print(data) 778 | assert(err == nil) 779 | 780 | local offset, err = file:seek("end", -1) 781 | assert(offset == 11) 782 | assert(err == nil) 783 | 784 | local data, err = file:read(4) 785 | ngx.print(data) 786 | assert(err == nil) 787 | 788 | local ok, err = file:close() 789 | assert(ok) 790 | assert(err == nil) 791 | 792 | local prefix = ngx.config.prefix() 793 | local name = prefix .. "/conf/test.txt" 794 | 795 | os.execute("rm -f " .. name) 796 | } 797 | } 798 | 799 | --- request 800 | GET /t 801 | --- response_body: Hello,d 802 | --- grep_error_log: lua io seek drain read chain 803 | --- grep_error_log_out 804 | lua io seek drain read chain 805 | --- no_error_log eval 806 | ["error", "crit"] 807 | 808 | 809 | 810 | === TEST 15: mix non-buffered write with buffered read (switch them with calling seek) 811 | --- main_config 812 | thread_pool default threads=2 max_queue=10; 813 | --- config 814 | server_tokens off; 815 | location /t { 816 | lua_io_write_buffer_size 0; 817 | lua_io_read_buffer_size 256; 818 | content_by_lua_block { 819 | local ngx_io = require "ngx.io" 820 | local file, err = ngx_io.open("conf/test.txt", "w+") 821 | assert(type(file) == "table") 822 | assert(err == nil) 823 | 824 | local n, err = file:write("Hello") 825 | assert(n == 5) 826 | assert(err == nil) 827 | 828 | local n, err = file:write(", World") 829 | assert(n == 7) 830 | assert(err == nil) 831 | 832 | local offset, err = file:seek("cur", -1) 833 | assert(offset == 11) 834 | assert(err == nil) 835 | 836 | local data, err = file:read(6) 837 | ngx.print(data) 838 | assert(err == nil) 839 | 840 | local offset, err = file:seek("end", 0) 841 | assert(offset == 12) 842 | assert(err == nil) 843 | 844 | local n, err = file:write("!") 845 | assert(n == 1) 846 | assert(err == nil) 847 | 848 | local offset, err = file:seek("set", 0) 849 | assert(offset == 0) 850 | assert(err == nil) 851 | 852 | local data, err = file:read("*a") 853 | assert(err == nil) 854 | ngx.print(data) 855 | 856 | local ok, err = file:close() 857 | assert(ok) 858 | assert(err == nil) 859 | 860 | local prefix = ngx.config.prefix() 861 | local name = prefix .. "/conf/test.txt" 862 | 863 | os.execute("rm -f " .. name) 864 | } 865 | } 866 | 867 | --- request 868 | GET /t 869 | --- response_body: dHello, World! 870 | --- grep_error_log: lua io seek drain read chain 871 | --- grep_error_log_out 872 | lua io seek drain read chain 873 | --- no_error_log eval 874 | ["error", "crit"] 875 | 876 | 877 | 878 | === TEST 16: mix buffered write with read (switch them with calling seek) 879 | --- main_config 880 | thread_pool default threads=2 max_queue=10; 881 | --- config 882 | server_tokens off; 883 | location /t { 884 | lua_io_write_buffer_size 10; 885 | lua_io_read_buffer_size 256; 886 | content_by_lua_block { 887 | local ngx_io = require "ngx.io" 888 | local file, err = ngx_io.open("conf/test.txt", "w+") 889 | assert(type(file) == "table") 890 | assert(err == nil) 891 | 892 | local n, err = file:write("Hello") 893 | assert(n == 5) 894 | assert(err == nil) 895 | 896 | local n, err = file:write(", World") 897 | assert(n == 7) 898 | assert(err == nil) 899 | 900 | local offset, err = file:seek("cur", -1) 901 | assert(offset == 11) 902 | assert(err == nil) 903 | 904 | local data, err = file:read(6) 905 | ngx.print(data) 906 | assert(err == nil) 907 | 908 | local offset, err = file:seek("end", 0) 909 | assert(offset == 12) 910 | assert(err == nil) 911 | 912 | local n, err = file:write("!") 913 | assert(n == 1) 914 | assert(err == nil) 915 | 916 | local offset, err = file:seek("set", 0) 917 | assert(offset == 0) 918 | assert(err == nil) 919 | 920 | local data, err = file:read("*a") 921 | assert(err == nil) 922 | ngx.print(data) 923 | 924 | local ok, err = file:close() 925 | assert(ok) 926 | assert(err == nil) 927 | 928 | local prefix = ngx.config.prefix() 929 | local name = prefix .. "/conf/test.txt" 930 | 931 | os.execute("rm -f " .. name) 932 | } 933 | } 934 | 935 | --- request 936 | GET /t 937 | --- response_body: dHello, World! 938 | --- grep_error_log: lua io seek drain read chain 939 | --- grep_error_log_out 940 | lua io seek drain read chain 941 | --- no_error_log eval 942 | ["error", "crit"] 943 | 944 | 945 | 946 | === TEST 17: mix buffered write with read (switch them with seek explicitly), and open with append mode 947 | --- main_config 948 | thread_pool default threads=2 max_queue=10; 949 | --- config 950 | server_tokens off; 951 | location /t { 952 | lua_io_write_buffer_size 100; 953 | lua_io_read_buffer_size 256; 954 | content_by_lua_block { 955 | local ngx_io = require "ngx.io" 956 | local file, err = ngx_io.open("conf/test.txt", "a+") 957 | assert(type(file) == "table") 958 | assert(err == nil) 959 | 960 | local n, err = file:write("Hello") 961 | assert(n == 5) 962 | assert(err == nil) 963 | 964 | local n, err = file:write(", World") 965 | assert(n == 7) 966 | assert(err == nil) 967 | 968 | local offset, err = file:seek("cur", -10) 969 | assert(offset == 2) 970 | assert(err == nil) 971 | 972 | local n, err = file:write(", again!") 973 | assert(n == 8) 974 | assert(err == nil) 975 | 976 | local offset, err = file:seek("set", 0) 977 | assert(offset == 0) 978 | assert(err == nil) 979 | 980 | local data, err = file:read(6) 981 | assert(err == nil) 982 | ngx.print(data) 983 | 984 | local offset, err = file:seek("set", 8) 985 | assert(offset == 8) 986 | assert(err == nil) 987 | 988 | local n, err = file:write("!") 989 | assert(n == 1) 990 | assert(err == nil) 991 | 992 | local offset, err = file:seek("set", 4) 993 | assert(offset == 4) 994 | assert(err == nil) 995 | 996 | local data, err = file:read("*a") 997 | assert(err == nil) 998 | ngx.print(data) 999 | 1000 | local ok, err = file:close() 1001 | assert(ok) 1002 | assert(err == nil) 1003 | 1004 | local name = ngx.config.prefix() .. "/conf/test.txt" 1005 | os.execute("rm -f " .. name) 1006 | } 1007 | } 1008 | 1009 | --- request 1010 | GET /t 1011 | --- response_body: Hello,o, World, again!! 1012 | --- grep_error_log: lua io seek drain read chain 1013 | --- grep_error_log_out 1014 | lua io seek drain read chain 1015 | --- no_error_log eval 1016 | ["error", "crit"] 1017 | -------------------------------------------------------------------------------- /t/06-file-read.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | repeat_each(3); 4 | 5 | plan tests => repeat_each() * (5 * 5 + 7 * 4); 6 | 7 | log_level 'debug'; 8 | 9 | no_long_string(); 10 | run_tests(); 11 | 12 | __DATA__ 13 | 14 | === TEST 1: try to read file but without the read permission 15 | --- main_config 16 | thread_pool default threads=2 max_queue=10; 17 | --- config 18 | server_tokens off; 19 | location /t { 20 | content_by_lua_block { 21 | local ngx_io = require "ngx.io" 22 | local file, err = ngx_io.open("conf/nginx.conf", "a") 23 | assert(type(file) == "table") 24 | assert(err == nil) 25 | 26 | local data, err = file:read(123) 27 | assert(data == nil) 28 | ngx.print(err) 29 | 30 | local ok, err = file:close() 31 | assert(ok) 32 | assert(err == nil) 33 | } 34 | } 35 | 36 | --- request 37 | GET /t 38 | --- response_body: operation not permitted 39 | --- no_error_log eval 40 | ["error", "crit"] 41 | 42 | 43 | 44 | === TEST 2: read file with specified bytes 45 | --- main_config 46 | thread_pool default threads=2 max_queue=10; 47 | --- config 48 | server_tokens off; 49 | location /t { 50 | lua_io_read_buffer_size 1; 51 | content_by_lua_block { 52 | local ngx_io = require "ngx.io" 53 | local file, err = ngx_io.open("conf/nginx.conf", "r") 54 | assert(type(file) == "table") 55 | assert(err == nil) 56 | 57 | local data, err = file:read(123) 58 | assert(err == nil) 59 | 60 | local ok, err = file:close() 61 | assert(ok) 62 | assert(err == nil) 63 | 64 | local prefix = ngx.config.prefix() 65 | local name = prefix .. "/conf/nginx.conf" 66 | local file = io.open(name, "r") 67 | local data2 = file:read(123) 68 | file:close() 69 | 70 | assert(data == data2) 71 | ngx.print("data ok") 72 | } 73 | } 74 | 75 | --- request 76 | GET /t 77 | --- response_body: data ok 78 | --- grep_error_log: lua io thread read 1 79 | --- grep_error_log_out eval 80 | "lua io thread read 1\n" x 123 81 | --- no_error_log eval 82 | ["error", "crit"] 83 | 84 | 85 | 86 | === TEST 3: read file with specified bytes and a large buffer 87 | --- main_config 88 | thread_pool default threads=2 max_queue=10; 89 | --- config 90 | server_tokens off; 91 | location /t { 92 | lua_io_read_buffer_size 1000; 93 | content_by_lua_block { 94 | local ngx_io = require "ngx.io" 95 | local file, err = ngx_io.open("conf/nginx.conf", "r") 96 | assert(type(file) == "table") 97 | assert(err == nil) 98 | 99 | local data1, err = file:read(500) 100 | assert(err == nil) 101 | local data2, err = file:read(500) 102 | assert(err == nil) 103 | 104 | local ok, err = file:close() 105 | assert(ok) 106 | assert(err == nil) 107 | 108 | local prefix = ngx.config.prefix() 109 | local name = prefix .. "/conf/nginx.conf" 110 | local file = io.open(name, "r") 111 | local data3 = file:read(500) 112 | local data4 = file:read(500) 113 | file:close() 114 | 115 | assert(data1 == data3) 116 | assert(data2 == data4) 117 | ngx.print("data ok") 118 | } 119 | } 120 | 121 | --- request 122 | GET /t 123 | --- response_body: data ok 124 | --- grep_error_log: lua io thread read 1000 125 | --- grep_error_log_out 126 | lua io thread read 1000 127 | --- no_error_log eval 128 | ["error", "crit"] 129 | 130 | 131 | 132 | === TEST 4: read the whole file and the data can be stuffed in the single buffer 133 | --- main_config 134 | thread_pool default threads=2 max_queue=10; 135 | --- config 136 | server_tokens off; 137 | location /t { 138 | lua_io_read_buffer_size 4096; 139 | content_by_lua_block { 140 | local ngx_io = require "ngx.io" 141 | local file, err = ngx_io.open("conf/nginx.conf", "r") 142 | assert(type(file) == "table") 143 | assert(err == nil) 144 | 145 | local data, err = file:read("*a") 146 | assert(err == nil) 147 | 148 | local ok, err = file:close() 149 | assert(ok) 150 | assert(err == nil) 151 | 152 | local prefix = ngx.config.prefix() 153 | local name = prefix .. "/conf/nginx.conf" 154 | local file = io.open(name, "r") 155 | local data2 = file:read("*a") 156 | file:close() 157 | 158 | assert(data == data2) 159 | ngx.print("data ok") 160 | } 161 | } 162 | 163 | --- request 164 | GET /t 165 | --- response_body: data ok 166 | --- no_error_log eval 167 | ["error", "crit"] 168 | 169 | 170 | 171 | === TEST 5: read the whole file and the data can be stuffed in the single buffer 172 | --- main_config 173 | thread_pool default threads=2 max_queue=10; 174 | --- config 175 | server_tokens off; 176 | location /t { 177 | lua_io_read_buffer_size 2; 178 | content_by_lua_block { 179 | local ngx_io = require "ngx.io" 180 | local file, err = ngx_io.open("conf/nginx.conf", "r") 181 | assert(type(file) == "table") 182 | assert(err == nil) 183 | 184 | local data, err = file:read("*a") 185 | assert(err == nil) 186 | 187 | local ok, err = file:close() 188 | assert(ok) 189 | assert(err == nil) 190 | 191 | local prefix = ngx.config.prefix() 192 | local name = prefix .. "/conf/nginx.conf" 193 | local file = io.open(name, "r") 194 | local data2 = file:read("*a") 195 | file:close() 196 | 197 | assert(data == data2) 198 | ngx.print("data ok") 199 | } 200 | } 201 | 202 | --- request 203 | GET /t 204 | --- response_body: data ok 205 | --- no_error_log eval 206 | ["error", "crit"] 207 | 208 | 209 | 210 | === TEST 6: read some lines with the really really tiny size buffers 211 | --- main_config 212 | thread_pool default threads=2 max_queue=10; 213 | --- config 214 | server_tokens off; 215 | location /t { 216 | lua_io_read_buffer_size 2; 217 | content_by_lua_block { 218 | local ngx_io = require "ngx.io" 219 | local file, err = ngx_io.open("conf/nginx.conf", "r") 220 | assert(type(file) == "table") 221 | assert(err == nil) 222 | 223 | local data_line1, err = file:read("*l") 224 | assert(err == nil) 225 | 226 | local data_line2, err = file:read() 227 | assert(err == nil) 228 | 229 | local ok, err = file:close() 230 | assert(ok) 231 | assert(err == nil) 232 | 233 | local prefix = ngx.config.prefix() 234 | local name = prefix .. "/conf/nginx.conf" 235 | local file = io.open(name, "r") 236 | local data_line3 = file:read("*l") 237 | local data_line4 = file:read("*l") 238 | file:close() 239 | 240 | assert(data_line1 == data_line3) 241 | assert(data_line2 == data_line4) 242 | ngx.print("data ok") 243 | } 244 | } 245 | 246 | --- request 247 | GET /t 248 | --- response_body: data ok 249 | --- no_error_log eval 250 | ["error", "crit"] 251 | 252 | 253 | 254 | === TEST 7: read bytes until eof 255 | --- main_config 256 | thread_pool default threads=2 max_queue=10; 257 | --- config 258 | server_tokens off; 259 | location /t { 260 | lua_io_read_buffer_size 100; 261 | content_by_lua_block { 262 | local ngx_io = require "ngx.io" 263 | local file, err = ngx_io.open("conf/nginx.conf", "r") 264 | assert(type(file) == "table") 265 | assert(err == nil) 266 | 267 | local d = {} 268 | while true do 269 | local data, err = file:read(7) 270 | assert(err == nil) 271 | if data == nil then 272 | break 273 | end 274 | 275 | d[#d + 1] = data 276 | end 277 | 278 | local ok, err = file:close() 279 | assert(ok) 280 | assert(err == nil) 281 | 282 | local prefix = ngx.config.prefix() 283 | local name = prefix .. "/conf/nginx.conf" 284 | local file = io.open(name, "r") 285 | local data = file:read("*a") 286 | file:close() 287 | 288 | assert(data == table.concat(d)) 289 | 290 | ngx.print("data ok") 291 | } 292 | } 293 | 294 | --- request 295 | GET /t 296 | --- response_body: data ok 297 | --- grep_error_log: lua io read eof 298 | --- grep_error_log_out 299 | lua io read eof 300 | --- no_error_log eval 301 | ["error", "crit"] 302 | 303 | 304 | 305 | === TEST 8: read file with mixing the use of read bytes and read lines 306 | --- main_config 307 | thread_pool default threads=2 max_queue=10; 308 | --- config 309 | server_tokens off; 310 | location /t { 311 | lua_io_read_buffer_size 100; 312 | content_by_lua_block { 313 | local ngx_io = require "ngx.io" 314 | local file, err = ngx_io.open("conf/nginx.conf", "r") 315 | assert(type(file) == "table") 316 | assert(err == nil) 317 | 318 | local d = {} 319 | 320 | while true do 321 | local d1, err = file:read(4) 322 | assert(err == nil) 323 | 324 | if d1 == nil then 325 | break 326 | end 327 | 328 | assert(#d1 <= 4) 329 | d[#d + 1] = d1 330 | 331 | local d2, err = file:read("*l") 332 | assert(d2) 333 | assert(err == nil) 334 | d[#d + 1] = d2 335 | d[#d + 1] = "\n" 336 | end 337 | 338 | local ok, err = file:close() 339 | assert(ok) 340 | assert(err == nil) 341 | 342 | local prefix = ngx.config.prefix() 343 | local name = prefix .. "/conf/nginx.conf" 344 | local file = io.open(name, "r") 345 | 346 | assert(table.concat(d) == file:read("*a")) 347 | file:close() 348 | 349 | ngx.print("data ok") 350 | } 351 | } 352 | 353 | --- request 354 | GET /t 355 | --- response_body: data ok 356 | --- grep_error_log: lua io read eof 357 | --- grep_error_log_out 358 | lua io read eof 359 | --- no_error_log eval 360 | ["error", "crit"] 361 | 362 | 363 | 364 | === TEST 9: read file with mixing the use of read bytes, read lines and read all 365 | --- main_config 366 | thread_pool default threads=2 max_queue=10; 367 | --- config 368 | server_tokens off; 369 | location /t { 370 | lua_io_read_buffer_size 100; 371 | content_by_lua_block { 372 | local ngx_io = require "ngx.io" 373 | local file, err = ngx_io.open("conf/nginx.conf", "r") 374 | assert(type(file) == "table") 375 | assert(err == nil) 376 | 377 | local d = {} 378 | 379 | local d1, err = file:read(4) 380 | assert(d1) 381 | assert(#d1 <= 4) 382 | assert(err == nil) 383 | d[#d + 1] = d1 384 | 385 | local d2, err = file:read("*l") 386 | assert(d2) 387 | assert(err == nil) 388 | d[#d + 1] = d2 389 | d[#d + 1] = "\n" 390 | 391 | local d1, err = file:read(4) 392 | assert(d1) 393 | assert(#d1 <= 4) 394 | assert(err == nil) 395 | d[#d + 1] = d1 396 | 397 | local d3, err = file:read("*a") 398 | assert(d3) 399 | assert(err == nil) 400 | d[#d + 1] = d3 401 | 402 | local d3, err = file:read("*a") 403 | assert(d3 == nil) 404 | assert(err == nil) 405 | 406 | local ok, err = file:close() 407 | assert(ok) 408 | assert(err == nil) 409 | 410 | local prefix = ngx.config.prefix() 411 | local name = prefix .. "/conf/nginx.conf" 412 | local file = io.open(name, "r") 413 | 414 | assert(table.concat(d) == file:read("*a")) 415 | file:close() 416 | 417 | ngx.print("data ok") 418 | } 419 | } 420 | 421 | --- request 422 | GET /t 423 | --- response_body: data ok 424 | --- grep_error_log: lua io read eof 425 | --- grep_error_log_out 426 | lua io read eof 427 | --- no_error_log eval 428 | ["error", "crit"] 429 | 430 | 431 | 432 | === TEST 10: read file with bad pattern 433 | --- main_config 434 | thread_pool default threads=2 max_queue=10; 435 | --- config 436 | server_tokens off; 437 | location /t { 438 | lua_io_read_buffer_size 100; 439 | content_by_lua_block { 440 | local ngx_io = require "ngx.io" 441 | local file, err = ngx_io.open("conf/nginx.conf", "r") 442 | assert(type(file) == "table") 443 | assert(err == nil) 444 | 445 | local ok, err = pcall(file.read, file, "bcc") 446 | assert(ok == false) 447 | ngx.print(err) 448 | 449 | local ok, err = file:close() 450 | assert(ok) 451 | assert(err == nil) 452 | } 453 | } 454 | 455 | --- request 456 | GET /t 457 | --- response_body_like: bad pattern argument: bcc 458 | --- no_error_log eval 459 | ["error", "crit"] 460 | 461 | 462 | 463 | === TEST 11: try to read a closed file 464 | --- main_config 465 | thread_pool default threads=2 max_queue=10; 466 | --- config 467 | server_tokens off; 468 | location /t { 469 | lua_io_log_errors on; 470 | content_by_lua_block { 471 | local ngx_io = require "ngx.io" 472 | local file, err = ngx_io.open("conf/nginx.conf", "r") 473 | assert(type(file) == "table") 474 | assert(err == nil) 475 | 476 | local ok, err = file:close() 477 | assert(ok) 478 | assert(err == nil) 479 | 480 | local data, err = file:read("*a") 481 | assert(data == nil) 482 | ngx.print(err) 483 | } 484 | } 485 | 486 | --- request 487 | GET /t 488 | --- response_body: closed 489 | --- grep_error_log: attempt to read data from a closed file object 490 | --- grep_error_log_out 491 | attempt to read data from a closed file object 492 | --- no_error_log eval 493 | ["crit"] 494 | 495 | 496 | 497 | === TEST 12: read line until eof 498 | --- main_config 499 | thread_pool default threads=2 max_queue=10; 500 | --- config 501 | server_tokens off; 502 | location /t { 503 | lua_io_log_errors on; 504 | content_by_lua_block { 505 | local ngx_io = require "ngx.io" 506 | local file, err = ngx_io.open("conf/nginx.conf", "r") 507 | assert(type(file) == "table") 508 | assert(err == nil) 509 | 510 | local len = 0 511 | 512 | while true do 513 | local data, err = file:read("*l") 514 | assert(err == nil) 515 | if data == nil then 516 | break 517 | end 518 | 519 | len = len + #data 520 | end 521 | 522 | local ok, err = file:close() 523 | assert(ok) 524 | assert(err == nil) 525 | 526 | local name = ngx.config.prefix() .. "/conf/nginx.conf" 527 | local file = io.open(name) 528 | local len2 = 0 529 | for line in file:lines() do 530 | len2 = len2 + #line 531 | end 532 | file:close() 533 | 534 | assert(len == len2) 535 | ngx.print("data ok") 536 | } 537 | } 538 | 539 | --- request 540 | GET /t 541 | --- response_body: data ok 542 | --- no_error_log eval 543 | ["crit", "error"] 544 | -------------------------------------------------------------------------------- /t/07-file-lines.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | 3 | repeat_each(3); 4 | 5 | plan tests => repeat_each() * (4 * 6); 6 | 7 | log_level 'debug'; 8 | 9 | no_long_string(); 10 | run_tests(); 11 | 12 | __DATA__ 13 | 14 | === TEST 1: use lines iterator in the for loop 15 | --- main_config 16 | thread_pool default threads=2 max_queue=10; 17 | --- config 18 | server_tokens off; 19 | location /t { 20 | content_by_lua_block { 21 | local ngx_io = require "ngx.io" 22 | local file, err = ngx_io.open("conf/nginx.conf", "r") 23 | assert(type(file) == "table") 24 | assert(err == nil) 25 | 26 | local t = {} 27 | for line in file:lines() do 28 | t[#t + 1] = line 29 | end 30 | 31 | local ok, err = file:close() 32 | assert(ok) 33 | assert(err == nil) 34 | 35 | local name = ngx.config.prefix() .. "/conf/nginx.conf" 36 | local file = io.open(name) 37 | local t2 = {} 38 | for line in file:lines() do 39 | t2[#t2 + 1] = line 40 | end 41 | 42 | assert(table.concat(t) == table.concat(t2)) 43 | ngx.print("data ok") 44 | } 45 | } 46 | 47 | --- request 48 | GET /t 49 | --- response_body: data ok 50 | --- no_error_log eval 51 | ["crit", "error"] 52 | 53 | 54 | 55 | === TEST 2: use lines iterator and mix it with file:read("*l") 56 | --- main_config 57 | thread_pool default threads=2 max_queue=10; 58 | --- config 59 | server_tokens off; 60 | location /t { 61 | content_by_lua_block { 62 | local ngx_io = require "ngx.io" 63 | local file, err = ngx_io.open("conf/nginx.conf", "r") 64 | assert(type(file) == "table") 65 | assert(err == nil) 66 | 67 | local t = {} 68 | for line in file:lines() do 69 | t[#t + 1] = line 70 | if #t == 4 then 71 | break 72 | end 73 | end 74 | 75 | while true do 76 | local line, err = file:read("*l") 77 | if line == nil then 78 | break 79 | end 80 | assert(err == nil) 81 | t[#t + 1] = line 82 | end 83 | 84 | local ok, err = file:close() 85 | assert(ok) 86 | assert(err == nil) 87 | 88 | local name = ngx.config.prefix() .. "/conf/nginx.conf" 89 | local file = io.open(name) 90 | local t2 = {} 91 | for line in file:lines() do 92 | t2[#t2 + 1] = line 93 | end 94 | 95 | assert(table.concat(t) == table.concat(t2)) 96 | ngx.print("data ok") 97 | } 98 | } 99 | 100 | --- request 101 | GET /t 102 | --- response_body: data ok 103 | --- no_error_log eval 104 | ["crit", "error"] 105 | 106 | 107 | 108 | === TEST 3: use lines iterator and mix it with file:read(num) 109 | --- main_config 110 | thread_pool default threads=2 max_queue=10; 111 | --- config 112 | server_tokens off; 113 | location /t { 114 | content_by_lua_block { 115 | local ngx_io = require "ngx.io" 116 | local file, err = ngx_io.open("conf/nginx.conf", "r") 117 | assert(type(file) == "table") 118 | assert(err == nil) 119 | 120 | local t = {} 121 | local data, err = file:read(1) 122 | assert(data) 123 | assert(err == nil) 124 | t[#t + 1] = data 125 | 126 | local data, err = file:read(1) 127 | assert(data) 128 | assert(err == nil) 129 | t[#t + 1] = data 130 | 131 | for line in file:lines() do 132 | t[#t + 1] = line 133 | end 134 | 135 | local ok, err = file:close() 136 | assert(ok) 137 | assert(err == nil) 138 | 139 | local name = ngx.config.prefix() .. "/conf/nginx.conf" 140 | local file = io.open(name) 141 | local t2 = {} 142 | local data, err = file:read(1) 143 | assert(data) 144 | assert(err == nil) 145 | t2[#t2 + 1] = data 146 | 147 | local data, err = file:read(1) 148 | assert(data) 149 | assert(err == nil) 150 | t2[#t2 + 1] = data 151 | for line in file:lines() do 152 | t2[#t2 + 1] = line 153 | end 154 | 155 | assert(table.concat(t) == table.concat(t2)) 156 | ngx.print("data ok") 157 | } 158 | } 159 | 160 | --- request 161 | GET /t 162 | --- response_body: data ok 163 | --- no_error_log eval 164 | ["crit", "error"] 165 | 166 | 167 | 168 | === TEST 4: use lines iterator and mix it with file:read("*a") 169 | --- main_config 170 | thread_pool default threads=2 max_queue=10; 171 | --- config 172 | server_tokens off; 173 | location /t { 174 | content_by_lua_block { 175 | local ngx_io = require "ngx.io" 176 | local file, err = ngx_io.open("conf/nginx.conf", "r") 177 | assert(type(file) == "table") 178 | assert(err == nil) 179 | 180 | local t = {} 181 | 182 | for line in file:lines() do 183 | t[#t + 1] = line 184 | if #t == 3 then 185 | break 186 | end 187 | end 188 | 189 | local data, err = file:read("*a") 190 | assert(data) 191 | assert(err == nil) 192 | t[#t + 1] = data 193 | 194 | local ok, err = file:close() 195 | assert(ok) 196 | assert(err == nil) 197 | 198 | local name = ngx.config.prefix() .. "/conf/nginx.conf" 199 | local file = io.open(name) 200 | local t2 = {} 201 | for line in file:lines() do 202 | t2[#t2 + 1] = line 203 | if #t2 == 3 then 204 | break 205 | end 206 | end 207 | 208 | t2[#t2 + 1] = file:read("*a") 209 | 210 | assert(table.concat(t) == table.concat(t2)) 211 | ngx.print("data ok") 212 | } 213 | } 214 | 215 | --- request 216 | GET /t 217 | --- response_body: data ok 218 | --- no_error_log eval 219 | ["crit", "error"] 220 | 221 | 222 | 223 | === TEST 5: use lines iterator on a closed file 224 | --- main_config 225 | thread_pool default threads=2 max_queue=10; 226 | --- config 227 | server_tokens off; 228 | location /t { 229 | lua_io_log_errors on; 230 | content_by_lua_block { 231 | local ngx_io = require "ngx.io" 232 | local file, err = ngx_io.open("conf/nginx.conf", "r") 233 | assert(type(file) == "table") 234 | assert(err == nil) 235 | 236 | local ok, err = file:close() 237 | assert(ok) 238 | assert(err == nil) 239 | 240 | local iter = file:lines() 241 | 242 | local line, err = iter() 243 | assert(line == nil) 244 | ngx.print(err) 245 | } 246 | } 247 | 248 | --- request 249 | GET /t 250 | --- response_body: closed 251 | --- grep_error_log: attempt to read a line from a closed file object 252 | --- grep_error_log_out 253 | attempt to read a line from a closed file object 254 | --- no_error_log 255 | [crit] 256 | 257 | 258 | 259 | === TEST 6: use lines iterator on a file which misses the read permission 260 | --- main_config 261 | thread_pool default threads=2 max_queue=10; 262 | --- config 263 | server_tokens off; 264 | location /t { 265 | lua_io_log_errors on; 266 | content_by_lua_block { 267 | local ngx_io = require "ngx.io" 268 | local file, err = ngx_io.open("conf/nginx.conf", "a") 269 | assert(type(file) == "table") 270 | assert(err == nil) 271 | 272 | local iter = file:lines() 273 | 274 | local line, err = iter() 275 | assert(line == nil) 276 | ngx.print(err) 277 | 278 | local ok, err = file:close() 279 | assert(ok) 280 | assert(err == nil) 281 | } 282 | } 283 | 284 | --- request 285 | GET /t 286 | --- response_body: operation not permitted 287 | --- no_error_log eval 288 | ["crit", "error"] 289 | -------------------------------------------------------------------------------- /util/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # this file is mostly meant to be used by the author himself. 4 | 5 | #set -x 6 | 7 | root=`pwd` 8 | version=$1 9 | home=~ 10 | force=$2 11 | 12 | ngx_redis_version=0.3.7 13 | cd $home/work/nginx/ || exit 1 14 | ngx_redis_path=$home/work/nginx/ngx_http_redis-$ngx_redis_version 15 | rm -rf $ngx_redis_path || exit 1 16 | tar -xzvf ngx_http_redis-$ngx_redis_version.tar.gz || exit 1 17 | 18 | cd $ngx_redis_path || exit 1 19 | 20 | patch_file=$root/../openresty/patches/ngx_http_redis-$ngx_redis_version-variables_in_redis_pass.patch 21 | if [ ! -f $patch_file ]; then 22 | echo "$patch_file: No such file" > /dev/stderr 23 | exit 1 24 | fi 25 | # we ignore any errors here since the target directory might have already been patched. 26 | patch -p1 < $patch_file || exit 1 27 | 28 | cd $ngx_redis_path || exit 1 29 | 30 | patch_file=$root/../openresty/patches/ngx_http_redis-$ngx_redis_version-default_port_fix.patch 31 | if [ ! -f $patch_file ]; then 32 | echo "$patch_file: No such file" > /dev/stderr 33 | exit 1 34 | fi 35 | # we ignore any errors here since the target directory might have already been patched. 36 | patch -p1 < $patch_file || exit 1 37 | 38 | cd $root || exit 1 39 | 40 | #--without-http_memcached_module \ 41 | ngx-build $force $version \ 42 | --with-cc-opt="-O0" \ 43 | --with-ld-opt="-Wl,-rpath,/opt/postgres/lib:/opt/drizzle/lib:/usr/local/lib" \ 44 | --without-mail_pop3_module \ 45 | --without-mail_imap_module \ 46 | --without-mail_smtp_module \ 47 | --without-http_upstream_ip_hash_module \ 48 | --without-http_empty_gif_module \ 49 | --without-http_referer_module \ 50 | --without-http_autoindex_module \ 51 | --without-http_auth_basic_module \ 52 | --without-http_userid_module \ 53 | --add-module=$root/../ndk-nginx-module \ 54 | --add-module=$root/../set-misc-nginx-module \ 55 | --add-module=$ngx_redis_path \ 56 | --add-module=$root/../echo-nginx-module \ 57 | --add-module=$root/../lua-nginx-module \ 58 | --add-module=$root $opts \ 59 | --with-select_module \ 60 | --with-poll_module \ 61 | --with-debug \ 62 | --with-threads 63 | -------------------------------------------------------------------------------- /valgrind.suppress: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Memcheck:Addr1 4 | fun:ngx_init_cycle 5 | fun:ngx_master_process_cycle 6 | fun:main 7 | } 8 | { 9 | 10 | Memcheck:Addr4 11 | fun:ngx_init_cycle 12 | fun:ngx_master_process_cycle 13 | fun:main 14 | } 15 | { 16 | 17 | Memcheck:Cond 18 | fun:ngx_vslprintf 19 | fun:ngx_snprintf 20 | fun:ngx_sock_ntop 21 | fun:ngx_event_accept 22 | fun:ngx_epoll_process_events 23 | fun:ngx_process_events_and_timers 24 | } 25 | { 26 | 27 | Memcheck:Addr1 28 | fun:ngx_vslprintf 29 | fun:ngx_snprintf 30 | fun:ngx_sock_ntop 31 | fun:ngx_event_accept 32 | } 33 | { 34 | 35 | exp-sgcheck:SorG 36 | fun:ngx_http_lua_ndk_set_var_get 37 | } 38 | { 39 | 40 | exp-sgcheck:SorG 41 | fun:ngx_http_variables_init_vars 42 | fun:ngx_http_block 43 | } 44 | { 45 | 46 | exp-sgcheck:SorG 47 | fun:ngx_conf_parse 48 | } 49 | { 50 | 51 | exp-sgcheck:SorG 52 | fun:ngx_vslprintf 53 | fun:ngx_log_error_core 54 | } 55 | { 56 | 57 | Memcheck:Param 58 | epoll_ctl(event) 59 | fun:epoll_ctl 60 | } 61 | { 62 | 63 | Memcheck:Cond 64 | fun:ngx_conf_flush_files 65 | fun:ngx_single_process_cycle 66 | } 67 | { 68 | 69 | Memcheck:Cond 70 | fun:memcpy 71 | fun:ngx_vslprintf 72 | fun:ngx_log_error_core 73 | fun:ngx_http_charset_header_filter 74 | } 75 | { 76 | 77 | Memcheck:Param 78 | socketcall.setsockopt(optval) 79 | fun:setsockopt 80 | fun:drizzle_state_connect 81 | } 82 | { 83 | 84 | Memcheck:Cond 85 | fun:ngx_conf_flush_files 86 | fun:ngx_single_process_cycle 87 | fun:main 88 | } 89 | { 90 | 91 | Memcheck:Leak 92 | fun:malloc 93 | fun:ngx_alloc 94 | fun:ngx_event_process_init 95 | } 96 | { 97 | 98 | Memcheck:Param 99 | sendmsg(mmsg[0].msg_hdr) 100 | fun:sendmmsg 101 | fun:__libc_res_nsend 102 | } 103 | { 104 | 105 | Memcheck:Param 106 | sendmsg(msg.msg_iov[0]) 107 | fun:__sendmsg_nocancel 108 | fun:ngx_write_channel 109 | fun:ngx_pass_open_channel 110 | fun:ngx_start_cache_manager_processes 111 | } 112 | { 113 | 114 | Memcheck:Cond 115 | fun:ngx_init_cycle 116 | fun:ngx_master_process_cycle 117 | fun:main 118 | } 119 | { 120 | 121 | Memcheck:Cond 122 | fun:index 123 | fun:expand_dynamic_string_token 124 | fun:_dl_map_object 125 | fun:map_doit 126 | fun:_dl_catch_error 127 | fun:do_preload 128 | fun:dl_main 129 | fun:_dl_sysdep_start 130 | fun:_dl_start 131 | } 132 | { 133 | 134 | Memcheck:Param 135 | sendmsg(mmsg[0].msg_hdr) 136 | fun:sendmmsg 137 | fun:__libc_res_nsend 138 | fun:__libc_res_nquery 139 | fun:__libc_res_nquerydomain 140 | fun:__libc_res_nsearch 141 | } 142 | { 143 | 144 | Memcheck:Leak 145 | match-leak-kinds: definite 146 | fun:malloc 147 | fun:ngx_alloc 148 | fun:ngx_set_environment 149 | fun:ngx_single_process_cycle 150 | } 151 | { 152 | 153 | Memcheck:Cond 154 | obj:* 155 | } 156 | { 157 | 158 | Memcheck:Leak 159 | match-leak-kinds: definite 160 | fun:malloc 161 | fun:ngx_alloc 162 | fun:ngx_set_environment 163 | fun:ngx_worker_process_init 164 | } 165 | { 166 | 167 | Memcheck:Leak 168 | match-leak-kinds: definite 169 | fun:malloc 170 | fun:ngx_alloc 171 | fun:ngx_create_pool 172 | fun:main 173 | } 174 | { 175 | 176 | Memcheck:Param 177 | epoll_pwait(sigmask) 178 | fun:epoll_pwait 179 | fun:epoll_wait 180 | fun:ngx_epoll_process_events 181 | fun:ngx_process_events_and_timers 182 | } 183 | { 184 | 185 | Memcheck:Param 186 | epoll_pwait(sigmask) 187 | fun:epoll_pwait 188 | fun:epoll_wait 189 | fun:ngx_epoll_test_rdhup 190 | fun:ngx_epoll_init 191 | fun:ngx_event_process_init 192 | } 193 | { 194 | 195 | Memcheck:Param 196 | epoll_pwait(sigmask) 197 | fun:epoll_pwait 198 | fun:ngx_epoll_process_events 199 | fun:ngx_process_events_and_timers 200 | } 201 | { 202 | 203 | Memcheck:Param 204 | epoll_pwait(sigmask) 205 | fun:epoll_pwait 206 | fun:ngx_epoll_test_rdhup 207 | fun:ngx_epoll_init 208 | fun:ngx_event_process_init 209 | } 210 | --------------------------------------------------------------------------------