├── Makefile ├── README.markdown ├── lib └── resty │ ├── http.lua │ └── url.lua ├── t ├── sanity.t └── servroot │ ├── conf │ └── nginx.conf │ ├── html │ └── index.html │ └── logs │ ├── access.log │ └── error.log └── valgrind.suppress /Makefile: -------------------------------------------------------------------------------- 1 | OPENRESTY_PREFIX=/usr/local/openresty-debug 2 | 3 | PREFIX ?= /usr/local 4 | LUA_INCLUDE_DIR ?= $(PREFIX)/include 5 | LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION) 6 | INSTALL ?= install 7 | 8 | .PHONY: all test install 9 | 10 | all: ; 11 | 12 | install: all 13 | $(INSTALL) -d $(DESTDIR)/$(LUA_LIB_DIR)/resty 14 | $(INSTALL) lib/resty/*.lua $(DESTDIR)/$(LUA_LIB_DIR)/resty 15 | 16 | test: all 17 | PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r t 18 | 19 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | lua-resty-http - Lua http client driver for the ngx_lua based on the cosocket API 5 | 6 | Status 7 | ====== 8 | 9 | This library is considered experimental and still under active development. 10 | 11 | The API is still in flux and may change without notice. 12 | 13 | Description 14 | =========== 15 | 16 | This Lua library is a http client driver for the ngx_lua nginx module: 17 | 18 | http://wiki.nginx.org/HttpLuaModule 19 | 20 | This Lua library takes advantage of ngx_lua's cosocket API, which ensures 21 | 100% nonblocking behavior. 22 | 23 | Note that at least [ngx_lua 0.5.0rc3](https://github.com/chaoslawful/lua-nginx-module/tags) or [ngx_openresty 1.0.11.3](http://openresty.org/#Download) is required. 24 | 25 | Synopsis 26 | ======== 27 | 28 | lua_package_path "/path/to/lua-resty-http/lib/?.lua;;"; 29 | 30 | server { 31 | location /test { 32 | content_by_lua ' 33 | local http = require "resty.http" 34 | local hc = http:new() 35 | 36 | local ok, code, headers, status, body = hc:request { 37 | url = "http://www.qunar.com/", 38 | --- proxy = "http://127.0.0.1:8888", 39 | --- timeout = 3000, 40 | --- scheme = 'https', 41 | method = "POST", -- POST or GET 42 | -- add post content-type and cookie 43 | headers = { Cookie = {"ABCDEFG"}, ["Content-Type"] = "application/x-www-form-urlencoded" }, 44 | body = "uid=1234567890", 45 | } 46 | 47 | ngx.say(ok) 48 | ngx.say(code) 49 | ngx.say(body) 50 | '; 51 | } 52 | } 53 | 54 | TODO 55 | ==== 56 | 57 | * implement the redirect supported 58 | * implement the chunked 59 | * implement the keepalive 60 | 61 | Authors 62 | ======= 63 | 64 | "liseen" 65 | 66 | "wendal" 67 | 68 | "wangchll" 69 | 70 | Copyright and License 71 | ===================== 72 | 73 | This module is licensed under the BSD license. 74 | 75 | Copyright (C) 2012, by Zhang "agentzh" Yichun (章亦春) . 76 | 77 | All rights reserved. 78 | 79 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 80 | 81 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 82 | 83 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 84 | 85 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 86 | 87 | See Also 88 | ======== 89 | * the ngx_lua module: http://wiki.nginx.org/HttpLuaModule 90 | * the memcached wired protocol specification: http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt 91 | * the [lua-resty-redis](https://github.com/agentzh/lua-resty-redis) library. 92 | * the [lua-resty-mysql](https://github.com/agentzh/lua-resty-mysql) library. 93 | 94 | -------------------------------------------------------------------------------- /lib/resty/http.lua: -------------------------------------------------------------------------------- 1 | module("resty.http", package.seeall) 2 | 3 | _VERSION = '0.2' 4 | 5 | -- constants 6 | -- connection timeout in seconds 7 | local TIMEOUT = 60 8 | -- default ports for document retrieval 9 | local PORT = 80 10 | local SSL_PORT = 443 11 | 12 | -- user agent field sent in request 13 | local USERAGENT = 'resty.http/' .. _VERSION 14 | 15 | -- default url parts 16 | local default = { 17 | host = "", 18 | path ="/", 19 | scheme = "http" 20 | } 21 | 22 | 23 | -- global variables 24 | local url = require("resty.url") 25 | 26 | local mt = { __index = resty.http } 27 | 28 | local tcp = ngx.socket.tcp 29 | local base64 = ngx.encode_base64 30 | 31 | 32 | local function adjusturi(reqt) 33 | local u = reqt 34 | -- if there is a proxy, we need the full url. otherwise, just a part. 35 | if not reqt.proxy and not PROXY then 36 | u = { 37 | path = reqt.path, 38 | params = reqt.params, 39 | query = reqt.query, 40 | fragment = reqt.fragment 41 | } 42 | end 43 | return url.build(u) 44 | end 45 | 46 | 47 | local function adjustheaders(reqt) 48 | -- default headers 49 | local lower = { 50 | ["user-agent"] = USERAGENT, 51 | ["host"] = reqt.host, 52 | ["connection"] = "close, TE", 53 | ["te"] = "trailers" 54 | } 55 | -- if we have authentication information, pass it along 56 | if reqt.user and reqt.password then 57 | lower["authorization"] = 58 | "Basic " .. (base64(reqt.user .. ":" .. reqt.password)) 59 | end 60 | -- override with user headers 61 | for i,v in pairs(reqt.headers or lower) do 62 | lower[string.lower(i)] = v 63 | end 64 | return lower 65 | end 66 | 67 | 68 | local function adjustproxy(reqt) 69 | local proxy = reqt.proxy or PROXY 70 | if proxy then 71 | proxy = url.parse(proxy) 72 | return proxy.host, proxy.port or 3128 73 | else 74 | return reqt.host, reqt.port 75 | end 76 | end 77 | 78 | 79 | local function adjustrequest(reqt) 80 | -- parse url if provided 81 | local nreqt = reqt.url and url.parse(reqt.url, default) or {} 82 | -- explicit components override url 83 | for i,v in pairs(reqt) do nreqt[i] = v end 84 | 85 | if nreqt.port == nil or nreqt.port == "" then 86 | if nreqt.scheme == "https" then 87 | nreqt.port = SSL_PORT 88 | else 89 | nreqt.port = PORT 90 | end 91 | end 92 | 93 | -- compute uri if user hasn't overriden 94 | nreqt.uri = reqt.uri or adjusturi(nreqt) 95 | -- ajust host and port if there is a proxy 96 | nreqt.host, nreqt.port = adjustproxy(nreqt) 97 | -- adjust headers in request 98 | nreqt.headers = adjustheaders(nreqt) 99 | 100 | nreqt.timeout = reqt.timeout or TIMEOUT * 1000; 101 | 102 | nreqt.fetch_size = reqt.fetch_size or 16*1024 -- 16k 103 | nreqt.max_body_size = reqt.max_body_size or 1024*1024*1024 -- 1024mb 104 | 105 | if reqt.keepalive then 106 | nreqt.headers['connection'] = 'keep-alive' 107 | end 108 | 109 | return nreqt 110 | end 111 | 112 | 113 | local function receivestatusline(sock) 114 | local status_reader = sock:receiveuntil("\r\n") 115 | 116 | local data, err, partial = status_reader() 117 | if not data then 118 | return nil, "read status line failed " .. err 119 | end 120 | 121 | local t1, t2, code = string.find(data, "HTTP/%d*%.%d* (%d%d%d)") 122 | 123 | return tonumber(code), data 124 | end 125 | 126 | 127 | local function receiveheaders(sock, headers) 128 | local line, name, value, err, tmp1, tmp2 129 | headers = headers or {} 130 | -- get first line 131 | line, err = sock:receive() 132 | if err then return nil, err end 133 | -- headers go until a blank line is found 134 | while line ~= "" do 135 | -- get field-name and value 136 | tmp1, tmp2, name, value = string.find(line, "^(.-):%s*(.*)") 137 | if not (name and value) then return nil, "malformed reponse headers" end 138 | name = string.lower(name) 139 | -- get next line (value might be folded) 140 | line, err = sock:receive() 141 | if err then return nil, err end 142 | -- unfold any folded values 143 | while string.find(line, "^%s") do 144 | value = value .. line 145 | line = sock:receive() 146 | if err then return nil, err end 147 | end 148 | -- save pair in table 149 | if headers[name] then 150 | if name == "set-cookie" then 151 | headers[name] = headers[name] .. "," .. value 152 | else 153 | headers[name] = headers[name] .. ", " .. value 154 | end 155 | else headers[name] = value end 156 | end 157 | return headers 158 | end 159 | 160 | local function read_body_data(sock, size, fetch_size, callback) 161 | local p_size = fetch_size 162 | while size and size > 0 do 163 | if size < p_size then 164 | p_size = size 165 | end 166 | local data, err, partial = sock:receive(p_size) 167 | if not err then 168 | if data then 169 | callback(data) 170 | end 171 | elseif err == "closed" then 172 | if partial then 173 | callback(partial) 174 | end 175 | return 1 -- 'closed' 176 | else 177 | return nil, err 178 | end 179 | size = size - p_size 180 | end 181 | return 1 182 | end 183 | 184 | local function receivebody(sock, headers, nreqt) 185 | local t = headers["transfer-encoding"] -- shortcut 186 | local body = {} -- data chunks of response body 187 | local callback = nreqt.body_callback 188 | if not callback then 189 | local function bc(data, chunked_header, ...) 190 | if chunked_header then return end 191 | body[#body+1] = data 192 | end 193 | callback = bc 194 | end 195 | if t and t ~= "identity" then 196 | -- chunked 197 | while true do 198 | local chunk_header = sock:receiveuntil("\r\n") 199 | local data, err, partial = chunk_header() 200 | if not data then 201 | return nil,err 202 | else 203 | if data == "0" then 204 | return table.concat(body) -- end of chunk 205 | else 206 | local length = tonumber(data, 16) 207 | 208 | -- TODO check nreqt.max_body_size !! 209 | 210 | local ok, err = read_body_data(sock,length, nreqt.fetch_size, callback) 211 | if err then 212 | return nil,err 213 | end 214 | end 215 | end 216 | end 217 | elseif headers["content-length"] ~= nil and tonumber(headers["content-length"]) >= 0 then 218 | -- content length 219 | local length = tonumber(headers["content-length"]) 220 | if length > nreqt.max_body_size then 221 | ngx.log(ngx.INFO, 'content-length > nreqt.max_body_size !! Tail it !') 222 | length = nreqt.max_body_size 223 | end 224 | 225 | local ok, err = read_body_data(sock,length, nreqt.fetch_size, callback) 226 | if not ok then 227 | return nil,err 228 | end 229 | else 230 | -- connection close 231 | local ok, err = read_body_data(sock,nreqt.max_body_size, nreqt.fetch_size, callback) 232 | if not ok then 233 | return nil,err 234 | end 235 | end 236 | return table.concat(body) 237 | end 238 | 239 | local function shouldredirect(reqt, code, headers) 240 | return headers.location and 241 | string.gsub(headers.location, "%s", "") ~= "" and 242 | (reqt.redirect ~= false) and 243 | (code == 301 or code == 302) and 244 | (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") 245 | and (not reqt.nredirects or reqt.nredirects < 5) 246 | end 247 | 248 | 249 | local function shouldreceivebody(reqt, code) 250 | if reqt.method == "HEAD" then return nil end 251 | if code == 204 or code == 304 then return nil end 252 | if code >= 100 and code < 200 then return nil end 253 | return 1 254 | end 255 | 256 | 257 | function new(self) 258 | return setmetatable({}, mt) 259 | end 260 | 261 | function request(self, reqt) 262 | local code, headers, status, body, bytes, ok, err 263 | 264 | local nreqt = adjustrequest(reqt) 265 | 266 | local sock = tcp() 267 | if not sock then 268 | return nil, "create sock failed" 269 | end 270 | 271 | sock:settimeout(nreqt.timeout) 272 | 273 | -- connect 274 | ok, err = sock:connect(nreqt.host, nreqt.port) 275 | if err then 276 | return nil, "sock connected failed " .. err 277 | end 278 | 279 | -- check type of req_body, maybe string, file, function 280 | local req_body = nreqt.body 281 | local req_body_type = nil 282 | if req_body then 283 | req_body_type = type(req_body) 284 | if req_body_type == 'string' then -- fixed Content-Length 285 | nreqt.headers['content-length'] = #req_body 286 | end 287 | end 288 | 289 | -- send request line and headers 290 | local reqline = string.format("%s %s HTTP/1.1\r\n", nreqt.method or "GET", nreqt.uri) 291 | local h = "" 292 | for i, v in pairs(nreqt.headers) do 293 | -- fix cookie is a table value 294 | if type(v) == "table" then 295 | if i == "cookie" then 296 | v = table.concat(v, "; ") 297 | else 298 | v = table.concat(v, ", ") 299 | end 300 | end 301 | h = i .. ": " .. v .. "\r\n" .. h 302 | end 303 | 304 | h = h .. '\r\n' -- close headers 305 | 306 | -- @modify: add ssl support 307 | if nreqt.scheme == 'https' then 308 | local sess, err = sock:sslhandshake(); 309 | if err then 310 | return nil, err; 311 | end 312 | end 313 | 314 | bytes, err = sock:send(reqline .. h) 315 | if err then 316 | sock:close() 317 | return nil, err 318 | end 319 | 320 | -- send req_body, if exists 321 | if req_body_type == 'string' then 322 | bytes, err = sock:send(req_body) 323 | if err then 324 | sock:close() 325 | return nil, err 326 | end 327 | elseif req_body_type == 'file' then 328 | local buf = nil 329 | while true do -- TODO chunked maybe better 330 | buf = req_body:read(8192) 331 | if not buf then break end 332 | bytes, err = sock:send(buf) 333 | if err then 334 | sock:close() 335 | return nil, err 336 | end 337 | end 338 | elseif req_body_type == 'function' then 339 | err = req_body(sock) -- as callback(sock) 340 | if err then 341 | return err 342 | end 343 | end 344 | 345 | -- receive status line 346 | code, status = receivestatusline(sock) 347 | if not code then 348 | sock:close() 349 | if not status then 350 | return nil, "read status line failed " 351 | else 352 | return nil, "read status line failed " .. status 353 | end 354 | end 355 | 356 | -- ignore any 100-continue messages 357 | while code == 100 do 358 | headers, err = receiveheaders(sock, {}) 359 | code, status = receivestatusline(sock) 360 | end 361 | 362 | -- notify code_callback 363 | if nreqt.code_callback then 364 | nreqt.code_callback(code) 365 | end 366 | 367 | -- receive headers 368 | headers, err = receiveheaders(sock, {}) 369 | if err then 370 | sock:close() 371 | return nil, "read headers failed " .. err 372 | end 373 | 374 | -- notify header_callback 375 | if nreqt.header_callback then 376 | nreqt.header_callback(headers) 377 | end 378 | 379 | -- TODO rediret check 380 | 381 | -- receive body 382 | if shouldreceivebody(nreqt, code) then 383 | body, err = receivebody(sock, headers, nreqt) 384 | if err then 385 | sock:close() 386 | if code == 200 then 387 | return 1, code, headers, status, nil 388 | end 389 | return nil, "read body failed " .. err 390 | end 391 | end 392 | 393 | -- read CR/LF or LF otherwise thorw 'unread data in buffer' error 394 | sock:receive("*l") 395 | if nreqt.keepalive then 396 | local ok, err = sock:setkeepalive(nreqt.keepalive) 397 | if not ok then 398 | ngx.log(ngx.WARN, "failed to set keepalive: " .. err) 399 | end 400 | else 401 | sock:close() 402 | end 403 | 404 | return 1, code, headers, status, body 405 | end 406 | 407 | function proxy_pass(self, reqt) 408 | local nreqt = {} 409 | for i,v in pairs(reqt) do nreqt[i] = v end 410 | 411 | if not nreqt.code_callback then 412 | nreqt.code_callback = function(code, ...) 413 | ngx.status = code 414 | end 415 | end 416 | 417 | if not nreqt.header_callback then 418 | nreqt.header_callback = function (headers, ...) 419 | for i, v in pairs(headers) do 420 | ngx.header[i] = v 421 | end 422 | end 423 | end 424 | 425 | if not nreqt.body_callback then 426 | nreqt.body_callback = function (data, ...) 427 | ngx.print(data) -- Will auto package as chunked format!! 428 | end 429 | end 430 | return request(self, nreqt) 431 | end 432 | 433 | -- to prevent use of casual module global variables 434 | getmetatable(resty.http).__newindex = function (table, key, val) 435 | error('attempt to write to undeclared variable "' .. key .. '": ' 436 | .. debug.traceback()) 437 | end 438 | 439 | -------------------------------------------------------------------------------- /lib/resty/url.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | -- URI parsing, composition and relative URL resolution 3 | -- LuaSocket toolkit. 4 | -- Author: Diego Nehab 5 | -- RCS ID: $Id: url.lua,v 1.38 2006/04/03 04:45:42 diego Exp $ 6 | ----------------------------------------------------------------------------- 7 | 8 | ----------------------------------------------------------------------------- 9 | -- Declare module 10 | ----------------------------------------------------------------------------- 11 | local string = require("string") 12 | local base = _G 13 | local table = require("table") 14 | module("resty.url", package.seeall) 15 | 16 | ----------------------------------------------------------------------------- 17 | -- Module version 18 | ----------------------------------------------------------------------------- 19 | _VERSION = "URL 1.0.1" 20 | 21 | ----------------------------------------------------------------------------- 22 | -- Encodes a string into its escaped hexadecimal representation 23 | -- Input 24 | -- s: binary string to be encoded 25 | -- Returns 26 | -- escaped representation of string binary 27 | ----------------------------------------------------------------------------- 28 | function escape(s) 29 | return string.gsub(s, "([^A-Za-z0-9_])", function(c) 30 | return string.format("%%%02x", string.byte(c)) 31 | end) 32 | end 33 | 34 | ----------------------------------------------------------------------------- 35 | -- Protects a path segment, to prevent it from interfering with the 36 | -- url parsing. 37 | -- Input 38 | -- s: binary string to be encoded 39 | -- Returns 40 | -- escaped representation of string binary 41 | ----------------------------------------------------------------------------- 42 | local function make_set(t) 43 | local s = {} 44 | for i,v in base.ipairs(t) do 45 | s[t[i]] = 1 46 | end 47 | return s 48 | end 49 | 50 | -- these are allowed withing a path segment, along with alphanum 51 | -- other characters must be escaped 52 | local segment_set = make_set { 53 | "-", "_", ".", "!", "~", "*", "'", "(", 54 | ")", ":", "@", "&", "=", "+", "$", ",", 55 | } 56 | 57 | local function protect_segment(s) 58 | return string.gsub(s, "([^A-Za-z0-9_])", function (c) 59 | if segment_set[c] then return c 60 | else return string.format("%%%02x", string.byte(c)) end 61 | end) 62 | end 63 | 64 | ----------------------------------------------------------------------------- 65 | -- Encodes a string into its escaped hexadecimal representation 66 | -- Input 67 | -- s: binary string to be encoded 68 | -- Returns 69 | -- escaped representation of string binary 70 | ----------------------------------------------------------------------------- 71 | function unescape(s) 72 | return string.gsub(s, "%%(%x%x)", function(hex) 73 | return string.char(base.tonumber(hex, 16)) 74 | end) 75 | end 76 | 77 | ----------------------------------------------------------------------------- 78 | -- Builds a path from a base path and a relative path 79 | -- Input 80 | -- base_path 81 | -- relative_path 82 | -- Returns 83 | -- corresponding absolute path 84 | ----------------------------------------------------------------------------- 85 | local function absolute_path(base_path, relative_path) 86 | if string.sub(relative_path, 1, 1) == "/" then return relative_path end 87 | local path = string.gsub(base_path, "[^/]*$", "") 88 | path = path .. relative_path 89 | path = string.gsub(path, "([^/]*%./)", function (s) 90 | if s ~= "./" then return s else return "" end 91 | end) 92 | path = string.gsub(path, "/%.$", "/") 93 | local reduced 94 | while reduced ~= path do 95 | reduced = path 96 | path = string.gsub(reduced, "([^/]*/%.%./)", function (s) 97 | if s ~= "../../" then return "" else return s end 98 | end) 99 | end 100 | path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) 101 | if s ~= "../.." then return "" else return s end 102 | end) 103 | return path 104 | end 105 | 106 | ----------------------------------------------------------------------------- 107 | -- Parses a url and returns a table with all its parts according to RFC 2396 108 | -- The following grammar describes the names given to the URL parts 109 | -- ::= :///;?# 110 | -- ::= @: 111 | -- ::= [:] 112 | -- :: = {/} 113 | -- Input 114 | -- url: uniform resource locator of request 115 | -- default: table with default values for each field 116 | -- Returns 117 | -- table with the following fields, where RFC naming conventions have 118 | -- been preserved: 119 | -- scheme, authority, userinfo, user, password, host, port, 120 | -- path, params, query, fragment 121 | -- Obs: 122 | -- the leading '/' in {/} is considered part of 123 | ----------------------------------------------------------------------------- 124 | function parse(url, default) 125 | -- initialize default parameters 126 | local parsed = {} 127 | for i,v in base.pairs(default or parsed) do parsed[i] = v end 128 | -- empty url is parsed to nil 129 | if not url or url == "" then return nil, "invalid url" end 130 | -- remove whitespace 131 | -- url = string.gsub(url, "%s", "") 132 | -- get fragment 133 | url = string.gsub(url, "#(.*)$", function(f) 134 | parsed.fragment = f 135 | return "" 136 | end) 137 | -- get scheme 138 | url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", 139 | function(s) parsed.scheme = s; return "" end) 140 | -- get authority 141 | url = string.gsub(url, "^//([^/]*)", function(n) 142 | parsed.authority = n 143 | return "" 144 | end) 145 | -- get query stringing 146 | url = string.gsub(url, "%?(.*)", function(q) 147 | parsed.query = q 148 | return "" 149 | end) 150 | -- get params 151 | url = string.gsub(url, "%;(.*)", function(p) 152 | parsed.params = p 153 | return "" 154 | end) 155 | -- path is whatever was left 156 | if url ~= "" then parsed.path = url end 157 | local authority = parsed.authority 158 | if not authority then return parsed end 159 | authority = string.gsub(authority,"^([^@]*)@", 160 | function(u) parsed.userinfo = u; return "" end) 161 | authority = string.gsub(authority, ":([^:]*)$", 162 | function(p) parsed.port = p; return "" end) 163 | if authority ~= "" then parsed.host = authority end 164 | local userinfo = parsed.userinfo 165 | if not userinfo then return parsed end 166 | userinfo = string.gsub(userinfo, ":([^:]*)$", 167 | function(p) parsed.password = p; return "" end) 168 | parsed.user = userinfo 169 | return parsed 170 | end 171 | 172 | ----------------------------------------------------------------------------- 173 | -- Rebuilds a parsed URL from its components. 174 | -- Components are protected if any reserved or unallowed characters are found 175 | -- Input 176 | -- parsed: parsed URL, as returned by parse 177 | -- Returns 178 | -- a stringing with the corresponding URL 179 | ----------------------------------------------------------------------------- 180 | function build(parsed) 181 | local ppath = parse_path(parsed.path or "") 182 | local url = build_path(ppath) 183 | if parsed.params then url = url .. ";" .. parsed.params end 184 | if parsed.query then url = url .. "?" .. parsed.query end 185 | local authority = parsed.authority 186 | if parsed.host then 187 | authority = parsed.host 188 | if parsed.port then authority = authority .. ":" .. parsed.port end 189 | local userinfo = parsed.userinfo 190 | if parsed.user then 191 | userinfo = parsed.user 192 | if parsed.password then 193 | userinfo = userinfo .. ":" .. parsed.password 194 | end 195 | end 196 | if userinfo then authority = userinfo .. "@" .. authority end 197 | end 198 | if authority then url = "//" .. authority .. url end 199 | if parsed.scheme then url = parsed.scheme .. ":" .. url end 200 | if parsed.fragment then url = url .. "#" .. parsed.fragment end 201 | -- url = string.gsub(url, "%s", "") 202 | return url 203 | end 204 | 205 | ----------------------------------------------------------------------------- 206 | -- Builds a absolute URL from a base and a relative URL according to RFC 2396 207 | -- Input 208 | -- base_url 209 | -- relative_url 210 | -- Returns 211 | -- corresponding absolute url 212 | ----------------------------------------------------------------------------- 213 | function absolute(base_url, relative_url) 214 | if base.type(base_url) == "table" then 215 | base_parsed = base_url 216 | base_url = build(base_parsed) 217 | else 218 | base_parsed = parse(base_url) 219 | end 220 | local relative_parsed = parse(relative_url) 221 | if not base_parsed then return relative_url 222 | elseif not relative_parsed then return base_url 223 | elseif relative_parsed.scheme then return relative_url 224 | else 225 | relative_parsed.scheme = base_parsed.scheme 226 | if not relative_parsed.authority then 227 | relative_parsed.authority = base_parsed.authority 228 | if not relative_parsed.path then 229 | relative_parsed.path = base_parsed.path 230 | if not relative_parsed.params then 231 | relative_parsed.params = base_parsed.params 232 | if not relative_parsed.query then 233 | relative_parsed.query = base_parsed.query 234 | end 235 | end 236 | else 237 | relative_parsed.path = absolute_path(base_parsed.path or "", 238 | relative_parsed.path) 239 | end 240 | end 241 | return build(relative_parsed) 242 | end 243 | end 244 | 245 | ----------------------------------------------------------------------------- 246 | -- Breaks a path into its segments, unescaping the segments 247 | -- Input 248 | -- path 249 | -- Returns 250 | -- segment: a table with one entry per segment 251 | ----------------------------------------------------------------------------- 252 | function parse_path(path) 253 | local parsed = {} 254 | path = path or "" 255 | --path = string.gsub(path, "%s", "") 256 | string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) 257 | for i = 1, #parsed do 258 | parsed[i] = unescape(parsed[i]) 259 | end 260 | if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end 261 | if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end 262 | return parsed 263 | end 264 | 265 | ----------------------------------------------------------------------------- 266 | -- Builds a path component from its segments, escaping protected characters. 267 | -- Input 268 | -- parsed: path segments 269 | -- unsafe: if true, segments are not protected before path is built 270 | -- Returns 271 | -- path: corresponding path stringing 272 | ----------------------------------------------------------------------------- 273 | function build_path(parsed, unsafe) 274 | local path = "" 275 | local n = #parsed 276 | if unsafe then 277 | for i = 1, n-1 do 278 | path = path .. parsed[i] 279 | path = path .. "/" 280 | end 281 | if n > 0 then 282 | path = path .. parsed[n] 283 | if parsed.is_directory then path = path .. "/" end 284 | end 285 | else 286 | for i = 1, n-1 do 287 | path = path .. protect_segment(parsed[i]) 288 | path = path .. "/" 289 | end 290 | if n > 0 then 291 | path = path .. protect_segment(parsed[n]) 292 | if parsed.is_directory then path = path .. "/" end 293 | end 294 | end 295 | if parsed.is_absolute then path = "/" .. path end 296 | return path 297 | end 298 | -------------------------------------------------------------------------------- /t/sanity.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use Test::Nginx::Socket; 4 | use Cwd qw(cwd); 5 | 6 | repeat_each(1); 7 | 8 | plan tests => repeat_each() * ( 3 * blocks()); 9 | 10 | my $pwd = cwd(); 11 | 12 | our $HttpConfig = qq{ 13 | lua_package_path "$pwd/lib/?.lua;;"; 14 | resolver 192.168.101.11; 15 | }; 16 | 17 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 18 | 19 | no_long_string(); 20 | 21 | run_tests(); 22 | 23 | __DATA__ 24 | 25 | === TEST 1: basic 26 | --- http_config eval: $::HttpConfig 27 | --- config 28 | location /t { 29 | content_by_lua ' 30 | local http = require "resty.http" 31 | local hc = http:new() 32 | 33 | local ok, code, headers, status, body = hc:request { 34 | url = "http://www.qunar.com/", 35 | -- proxy = "http://127.0.0.1:8888", 36 | -- timeout = 3000, 37 | -- headers = { UserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"} 38 | } 39 | 40 | ngx.say(ok) 41 | ngx.say(code) 42 | ngx.say(status) 43 | '; 44 | } 45 | --- request 46 | GET /t 47 | --- response_body 48 | 1 49 | 200 50 | HTTP/1.1 200 OK 51 | --- no_error_log 52 | [error] 53 | -------------------------------------------------------------------------------- /t/servroot/conf/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | daemon on; 3 | master_process off; 4 | error_log /home/liseen/git/lua-resty-http/t/servroot/logs/error.log debug; 5 | pid /home/liseen/git/lua-resty-http/t/servroot/logs/nginx.pid; 6 | env MOCKEAGAIN_VERBOSE; 7 | env MOCKEAGAIN_WRITE_TIMEOUT_PATTERN; 8 | env LD_PRELOAD; 9 | env DYLD_INSERT_LIBRARIES; 10 | 11 | 12 | 13 | http { 14 | access_log /home/liseen/git/lua-resty-http/t/servroot/logs/access.log; 15 | 16 | default_type text/plain; 17 | keepalive_timeout 68; 18 | 19 | 20 | lua_package_path "/home/liseen/git/lua-resty-http/lib/?.lua;;"; 21 | resolver 192.168.101.11; 22 | 23 | 24 | server { 25 | listen 1984; 26 | server_name 'localhost'; 27 | 28 | client_max_body_size 30M; 29 | #client_body_buffer_size 4k; 30 | 31 | # Begin preamble config... 32 | 33 | # End preamble config... 34 | 35 | # Begin test case config... 36 | location /t { 37 | content_by_lua ' 38 | local http = require "resty.http" 39 | local hc = http:new() 40 | 41 | local ok, code, headers, status, body = hc:request { 42 | url = "http://www.qunar.com/", 43 | -- proxy = "http://127.0.0.1:8888", 44 | -- timeout = 3000, 45 | -- headers = { UserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"} 46 | } 47 | 48 | ngx.say(ok) 49 | ngx.say(code) 50 | ngx.say(status) 51 | '; 52 | } 53 | 54 | # End test case config. 55 | 56 | location / { 57 | root /home/liseen/git/lua-resty-http/t/servroot/html; 58 | index index.html index.htm; 59 | } 60 | } 61 | } 62 | 63 | events { 64 | worker_connections 64; 65 | } 66 | -------------------------------------------------------------------------------- /t/servroot/html/index.html: -------------------------------------------------------------------------------- 1 | It works!It works! -------------------------------------------------------------------------------- /t/servroot/logs/access.log: -------------------------------------------------------------------------------- 1 | 127.0.0.1 - - [09/Apr/2012:15:56:14 +0800] "GET /t HTTP/1.1" 200 43 "-" "-" 2 | -------------------------------------------------------------------------------- /t/servroot/logs/error.log: -------------------------------------------------------------------------------- 1 | 2012/04/09 15:56:14 [notice] 15078#0: using the "epoll" event method 2 | 2012/04/09 15:56:14 [notice] 15078#0: ngx_openresty/1.0.11.28 3 | 2012/04/09 15:56:14 [notice] 15078#0: built by gcc 4.6.1 (Ubuntu/Linaro 4.6.1-9ubuntu3) 4 | 2012/04/09 15:56:14 [notice] 15078#0: OS: Linux 3.0.0-16-generic 5 | 2012/04/09 15:56:14 [notice] 15078#0: getrlimit(RLIMIT_NOFILE): 1024:4096 6 | 2012/04/09 15:56:14 [notice] 15079#0: signal 3 (SIGQUIT) received, shutting down 7 | 2012/04/09 15:56:14 [info] 15079#0: epoll_wait() failed (4: Interrupted system call) 8 | 2012/04/09 15:56:14 [notice] 15079#0: exit 9 | -------------------------------------------------------------------------------- /valgrind.suppress: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Memcheck:Param 4 | write(buf) 5 | fun:__write_nocancel 6 | fun:ngx_log_error_core 7 | fun:ngx_resolver_read_response 8 | } 9 | { 10 | 11 | Memcheck:Cond 12 | fun:ngx_sprintf_num 13 | fun:ngx_vslprintf 14 | fun:ngx_log_error_core 15 | fun:ngx_resolver_read_response 16 | fun:ngx_epoll_process_events 17 | fun:ngx_process_events_and_timers 18 | fun:ngx_single_process_cycle 19 | fun:main 20 | } 21 | { 22 | 23 | Memcheck:Addr1 24 | fun:ngx_vslprintf 25 | fun:ngx_snprintf 26 | fun:ngx_sock_ntop 27 | fun:ngx_event_accept 28 | } 29 | { 30 | 31 | Memcheck:Param 32 | write(buf) 33 | fun:__write_nocancel 34 | fun:ngx_log_error_core 35 | fun:ngx_resolver_read_response 36 | fun:ngx_event_process_posted 37 | fun:ngx_process_events_and_timers 38 | fun:ngx_single_process_cycle 39 | fun:main 40 | } 41 | { 42 | 43 | Memcheck:Cond 44 | fun:ngx_sprintf_num 45 | fun:ngx_vslprintf 46 | fun:ngx_log_error_core 47 | fun:ngx_resolver_read_response 48 | fun:ngx_event_process_posted 49 | fun:ngx_process_events_and_timers 50 | fun:ngx_single_process_cycle 51 | fun:main 52 | } 53 | { 54 | 55 | exp-sgcheck:SorG 56 | fun:lj_str_new 57 | fun:lua_pushlstring 58 | } 59 | { 60 | 61 | Memcheck:Leak 62 | fun:malloc 63 | fun:ngx_alloc 64 | obj:* 65 | } 66 | { 67 | 68 | exp-sgcheck:SorG 69 | fun:lj_str_new 70 | fun:lua_pushlstring 71 | } 72 | { 73 | 74 | exp-sgcheck:SorG 75 | fun:ngx_http_lua_ndk_set_var_get 76 | } 77 | { 78 | 79 | exp-sgcheck:SorG 80 | fun:lj_str_new 81 | fun:lua_getfield 82 | } 83 | { 84 | 85 | exp-sgcheck:SorG 86 | fun:lj_str_new 87 | fun:lua_setfield 88 | } 89 | { 90 | 91 | exp-sgcheck:SorG 92 | fun:ngx_http_variables_init_vars 93 | fun:ngx_http_block 94 | } 95 | { 96 | 97 | exp-sgcheck:SorG 98 | fun:ngx_conf_parse 99 | } 100 | { 101 | 102 | exp-sgcheck:SorG 103 | fun:ngx_vslprintf 104 | fun:ngx_log_error_core 105 | } 106 | { 107 | 108 | Memcheck:Leak 109 | fun:malloc 110 | fun:ngx_alloc 111 | fun:ngx_calloc 112 | fun:ngx_event_process_init 113 | } 114 | { 115 | 116 | Memcheck:Leak 117 | fun:malloc 118 | fun:ngx_alloc 119 | fun:ngx_malloc 120 | fun:ngx_pcalloc 121 | } 122 | { 123 | 124 | Memcheck:Addr4 125 | fun:lj_str_new 126 | fun:lua_setfield 127 | } 128 | { 129 | 130 | Memcheck:Addr4 131 | fun:lj_str_new 132 | fun:lua_getfield 133 | } 134 | { 135 | 136 | Memcheck:Leak 137 | fun:malloc 138 | fun:ngx_alloc 139 | fun:(below main) 140 | } 141 | { 142 | 143 | Memcheck:Param 144 | epoll_ctl(event) 145 | fun:epoll_ctl 146 | } 147 | { 148 | 149 | Memcheck:Leak 150 | fun:malloc 151 | fun:ngx_alloc 152 | fun:ngx_event_process_init 153 | } 154 | { 155 | 156 | Memcheck:Cond 157 | fun:ngx_conf_flush_files 158 | fun:ngx_single_process_cycle 159 | } 160 | { 161 | 162 | Memcheck:Cond 163 | fun:memcpy 164 | fun:ngx_vslprintf 165 | fun:ngx_log_error_core 166 | fun:ngx_http_charset_header_filter 167 | } 168 | { 169 | 170 | Memcheck:Leak 171 | fun:memalign 172 | fun:posix_memalign 173 | fun:ngx_memalign 174 | fun:ngx_pcalloc 175 | } 176 | { 177 | 178 | Memcheck:Addr4 179 | fun:lj_str_new 180 | fun:lua_pushlstring 181 | } 182 | { 183 | 184 | Memcheck:Cond 185 | fun:lj_str_new 186 | fun:lj_str_fromnum 187 | } 188 | { 189 | 190 | Memcheck:Cond 191 | fun:lj_str_new 192 | fun:lua_pushlstring 193 | } 194 | { 195 | 196 | Memcheck:Addr4 197 | fun:lj_str_new 198 | fun:lua_setfield 199 | fun:ngx_http_lua_cache_store_code 200 | } 201 | { 202 | 203 | Memcheck:Cond 204 | fun:lj_str_new 205 | fun:lua_getfield 206 | fun:ngx_http_lua_cache_load_code 207 | } 208 | { 209 | 210 | Memcheck:Cond 211 | fun:lj_str_new 212 | fun:lua_setfield 213 | fun:ngx_http_lua_cache_store_code 214 | } 215 | { 216 | 217 | Memcheck:Addr4 218 | fun:lj_str_new 219 | fun:lua_getfield 220 | fun:ngx_http_lua_cache_load_code 221 | } 222 | { 223 | 224 | Memcheck:Param 225 | socketcall.setsockopt(optval) 226 | fun:setsockopt 227 | fun:drizzle_state_connect 228 | } 229 | { 230 | 231 | Memcheck:Leak 232 | fun:malloc 233 | fun:ngx_alloc 234 | fun:ngx_palloc_large 235 | } 236 | { 237 | 238 | Memcheck:Leak 239 | fun:malloc 240 | fun:ngx_alloc 241 | fun:ngx_pool_cleanup_add 242 | } 243 | { 244 | 245 | Memcheck:Leak 246 | fun:malloc 247 | fun:ngx_alloc 248 | fun:ngx_pnalloc 249 | } 250 | { 251 | 252 | Memcheck:Cond 253 | fun:ngx_conf_flush_files 254 | fun:ngx_single_process_cycle 255 | fun:main 256 | } 257 | 258 | { 259 | 260 | Memcheck:Leak 261 | fun:malloc 262 | fun:ngx_alloc 263 | fun:ngx_palloc 264 | } 265 | { 266 | 267 | Memcheck:Leak 268 | fun:malloc 269 | fun:ngx_alloc 270 | fun:ngx_pcalloc 271 | } 272 | { 273 | 274 | Memcheck:Leak 275 | fun:malloc 276 | fun:ngx_alloc 277 | fun:ngx_malloc 278 | fun:ngx_palloc_large 279 | } 280 | { 281 | 282 | Memcheck:Leak 283 | fun:malloc 284 | fun:ngx_alloc 285 | fun:ngx_create_pool 286 | } 287 | { 288 | 289 | Memcheck:Leak 290 | fun:malloc 291 | fun:ngx_alloc 292 | fun:ngx_malloc 293 | fun:ngx_palloc 294 | } 295 | { 296 | 297 | Memcheck:Leak 298 | fun:malloc 299 | fun:ngx_alloc 300 | fun:ngx_malloc 301 | fun:ngx_pnalloc 302 | } 303 | 304 | { 305 | 306 | Memcheck:Leak 307 | fun:malloc 308 | fun:ngx_alloc 309 | fun:ngx_palloc_large 310 | fun:ngx_palloc 311 | fun:ngx_array_push 312 | fun:ngx_http_get_variable_index 313 | fun:ngx_http_memc_add_variable 314 | fun:ngx_http_memc_init 315 | fun:ngx_http_block 316 | fun:ngx_conf_parse 317 | fun:ngx_init_cycle 318 | fun:main 319 | } 320 | 321 | { 322 | 323 | Memcheck:Leak 324 | fun:malloc 325 | fun:ngx_alloc 326 | fun:ngx_event_process_init 327 | fun:ngx_single_process_cycle 328 | fun:main 329 | } 330 | { 331 | 332 | Memcheck:Leak 333 | fun:malloc 334 | fun:ngx_alloc 335 | fun:ngx_crc32_table_init 336 | fun:main 337 | } 338 | { 339 | 340 | Memcheck:Leak 341 | fun:malloc 342 | fun:ngx_alloc 343 | fun:ngx_event_process_init 344 | fun:ngx_worker_process_init 345 | fun:ngx_worker_process_cycle 346 | fun:ngx_spawn_process 347 | fun:ngx_start_worker_processes 348 | fun:ngx_master_process_cycle 349 | fun:main 350 | } 351 | { 352 | 353 | Memcheck:Param 354 | socketcall.sendmsg(msg.msg_iov[i]) 355 | fun:__sendmsg_nocancel 356 | fun:ngx_write_channel 357 | fun:ngx_signal_worker_processes 358 | fun:ngx_master_process_cycle 359 | fun:main 360 | } 361 | { 362 | 363 | Memcheck:Leak 364 | fun:malloc 365 | fun:ngx_alloc 366 | fun:ngx_palloc_large 367 | fun:ngx_palloc 368 | fun:ngx_pcalloc 369 | fun:ngx_hash_init 370 | fun:ngx_http_variables_init_vars 371 | fun:ngx_http_block 372 | fun:ngx_conf_parse 373 | fun:ngx_init_cycle 374 | fun:main 375 | } 376 | { 377 | 378 | Memcheck:Leak 379 | fun:malloc 380 | fun:ngx_alloc 381 | fun:ngx_palloc_large 382 | fun:ngx_palloc 383 | fun:ngx_pcalloc 384 | fun:ngx_http_upstream_drizzle_create_srv_conf 385 | fun:ngx_http_upstream 386 | fun:ngx_conf_parse 387 | fun:ngx_http_block 388 | fun:ngx_conf_parse 389 | fun:ngx_init_cycle 390 | fun:main 391 | } 392 | { 393 | 394 | Memcheck:Leak 395 | fun:malloc 396 | fun:ngx_alloc 397 | fun:ngx_palloc_large 398 | fun:ngx_palloc 399 | fun:ngx_pcalloc 400 | fun:ngx_hash_keys_array_init 401 | fun:ngx_http_variables_add_core_vars 402 | fun:ngx_http_core_preconfiguration 403 | fun:ngx_http_block 404 | fun:ngx_conf_parse 405 | fun:ngx_init_cycle 406 | fun:main 407 | } 408 | { 409 | 410 | Memcheck:Leak 411 | fun:malloc 412 | fun:ngx_alloc 413 | fun:ngx_palloc_large 414 | fun:ngx_palloc 415 | fun:ngx_array_push 416 | fun:ngx_hash_add_key 417 | fun:ngx_http_add_variable 418 | fun:ngx_http_echo_add_variables 419 | fun:ngx_http_echo_handler_init 420 | fun:ngx_http_block 421 | fun:ngx_conf_parse 422 | fun:ngx_init_cycle 423 | } 424 | { 425 | 426 | Memcheck:Leak 427 | fun:malloc 428 | fun:ngx_alloc 429 | fun:ngx_palloc_large 430 | fun:ngx_palloc 431 | fun:ngx_pcalloc 432 | fun:ngx_http_upstream_drizzle_create_srv_conf 433 | fun:ngx_http_core_server 434 | fun:ngx_conf_parse 435 | fun:ngx_http_block 436 | fun:ngx_conf_parse 437 | fun:ngx_init_cycle 438 | fun:main 439 | } 440 | { 441 | 442 | Memcheck:Leak 443 | fun:malloc 444 | fun:ngx_alloc 445 | fun:ngx_palloc_large 446 | fun:ngx_palloc 447 | fun:ngx_pcalloc 448 | fun:ngx_http_upstream_drizzle_create_srv_conf 449 | fun:ngx_http_block 450 | fun:ngx_conf_parse 451 | fun:ngx_init_cycle 452 | fun:main 453 | } 454 | { 455 | 456 | Memcheck:Leak 457 | fun:malloc 458 | fun:ngx_alloc 459 | fun:ngx_palloc_large 460 | fun:ngx_palloc 461 | fun:ngx_array_push 462 | fun:ngx_hash_add_key 463 | fun:ngx_http_variables_add_core_vars 464 | fun:ngx_http_core_preconfiguration 465 | fun:ngx_http_block 466 | fun:ngx_conf_parse 467 | fun:ngx_init_cycle 468 | fun:main 469 | } 470 | { 471 | 472 | Memcheck:Leak 473 | fun:malloc 474 | fun:ngx_alloc 475 | fun:ngx_palloc_large 476 | fun:ngx_palloc 477 | fun:ngx_pcalloc 478 | fun:ngx_init_cycle 479 | fun:main 480 | } 481 | { 482 | 483 | Memcheck:Leak 484 | fun:malloc 485 | fun:ngx_alloc 486 | fun:ngx_palloc_large 487 | fun:ngx_palloc 488 | fun:ngx_hash_init 489 | fun:ngx_http_upstream_init_main_conf 490 | fun:ngx_http_block 491 | fun:ngx_conf_parse 492 | fun:ngx_init_cycle 493 | fun:main 494 | } 495 | { 496 | 497 | Memcheck:Leak 498 | fun:malloc 499 | fun:ngx_alloc 500 | fun:ngx_palloc_large 501 | fun:ngx_palloc 502 | fun:ngx_pcalloc 503 | fun:ngx_http_drizzle_keepalive_init 504 | fun:ngx_http_upstream_drizzle_init 505 | fun:ngx_http_upstream_init_main_conf 506 | fun:ngx_http_block 507 | fun:ngx_conf_parse 508 | fun:ngx_init_cycle 509 | fun:main 510 | } 511 | { 512 | 513 | Memcheck:Leak 514 | fun:malloc 515 | fun:ngx_alloc 516 | fun:ngx_palloc_large 517 | fun:ngx_palloc 518 | fun:ngx_hash_init 519 | fun:ngx_http_variables_init_vars 520 | fun:ngx_http_block 521 | fun:ngx_conf_parse 522 | fun:ngx_init_cycle 523 | fun:main 524 | } 525 | { 526 | 527 | Memcheck:Leak 528 | fun:memalign 529 | fun:posix_memalign 530 | fun:ngx_memalign 531 | fun:ngx_create_pool 532 | } 533 | { 534 | 535 | Memcheck:Leak 536 | fun:memalign 537 | fun:posix_memalign 538 | fun:ngx_memalign 539 | fun:ngx_palloc_block 540 | fun:ngx_palloc 541 | } 542 | 543 | --------------------------------------------------------------------------------