├── .gitignore ├── .gitmodules ├── test ├── main.lua ├── main.c ├── get.lua ├── response_code.lua ├── header.lua ├── CMakeLists.txt ├── post.lua ├── redirect.lua ├── example.lua └── json.lua ├── README.md ├── LICENSE ├── CMakeLists.txt ├── webclient.lua └── webclient.c /.gitignore: -------------------------------------------------------------------------------- 1 | proj/ 2 | vs2017.bat 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rd/curl"] 2 | path = 3rd/curl 3 | url = https://github.com/curl/curl.git 4 | [submodule "3rd/lua"] 5 | path = 3rd/lua 6 | url = https://github.com/lua/lua.git 7 | -------------------------------------------------------------------------------- /test/main.lua: -------------------------------------------------------------------------------- 1 | package.path = "?.lua;" 2 | 3 | require "post" 4 | require "get" 5 | require "header" 6 | require "redirect" 7 | require "response_code" 8 | 9 | print "All tests succeed." 10 | print "\n\n\n\n\n" 11 | 12 | require "example" 13 | -------------------------------------------------------------------------------- /test/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lauxlib.h" 3 | #include "lualib.h" 4 | 5 | extern int luaopen_webclient(lua_State * L); 6 | 7 | int main(int argc, const char * argv[]) { 8 | lua_State* l = luaL_newstate(); 9 | luaL_requiref(l, "webclient", luaopen_webclient, 0); 10 | luaL_openlibs(l); 11 | if (luaL_dofile(l, "main.lua")) 12 | printf("[Lua] %s\n", lua_tostring(l, -1)); 13 | lua_close(l); 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-webclient 2 | 3 | 非堵塞的lua http库,是对libcurl multi interface(curlm)的简单封装。 4 | 可用于大量http、https请求在单线程内非阻塞处理,如游戏服务器和渠道进行用户的账户密码验证。 5 | 6 | ## 文件说明 7 | 8 | webclient.c 9 | webclient 对curlm的封装 10 | 11 | test/ 12 | webclient 使用纯lua的示例 13 | 14 | webclient.lua 15 | webclient 在 skynet 中的简单应用 16 | 17 | ## Build 18 | 19 | * linux or mac `cmake ../` 20 | * windows `cmake -G "Visual Studio 15 2017" ../` 21 | 22 | ## Usage 23 | 24 | [example.lua](https://github.com/dpull/lua-webclient/blob/master/test/example.lua) 25 | 26 | ## 如何接入skynet 27 | 28 | [参考makefile](https://github.com/dpull/lua-webclient/issues/13) 感谢 [@peigongdh](https://github.com/peigongdh) 29 | -------------------------------------------------------------------------------- /test/get.lua: -------------------------------------------------------------------------------- 1 | local json = require "json" 2 | local webclient_lib = require 'webclient' 3 | local webclient = webclient_lib.create() 4 | local requests = {}; 5 | 6 | local requests = {} 7 | 8 | local req, key = webclient:request("http://httpbin.org/get?a=1&b=2") 9 | requests[key] = req 10 | 11 | while next(requests) do 12 | local finish_key, result = webclient:query() 13 | if finish_key then 14 | local req = requests[finish_key] 15 | assert(req) 16 | requests[finish_key] = nil 17 | 18 | assert(result == 0) 19 | 20 | local content, errmsg = webclient:get_respond(req) 21 | local data = json.decode(content) 22 | assert(data.args.a == "1") 23 | assert(data.args.b == "2") 24 | 25 | webclient:remove_request(req) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/response_code.lua: -------------------------------------------------------------------------------- 1 | local json = require "json" 2 | local webclient_lib = require 'webclient' 3 | local webclient = webclient_lib.create() 4 | local requests = {}; 5 | 6 | local requests = {} 7 | local response_codes = {401, 404, 418, 500, 502, 504} 8 | 9 | for k, v in ipairs(response_codes) do 10 | local req, key = webclient:request("http://httpbin.org/status/" .. v) 11 | requests[key] = {req, v} 12 | end 13 | 14 | while next(requests) do 15 | local finish_key, result = webclient:query() 16 | if finish_key then 17 | local req, response_code = table.unpack(requests[finish_key]) 18 | requests[finish_key] = nil 19 | 20 | assert(result == 0) 21 | 22 | local info = webclient:get_info(req) 23 | assert(info.response_code == response_code) 24 | 25 | webclient:remove_request(req) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/header.lua: -------------------------------------------------------------------------------- 1 | local json = require "json" 2 | local webclient_lib = require 'webclient' 3 | local webclient = webclient_lib.create() 4 | local requests = {}; 5 | 6 | local requests = {} 7 | 8 | local req, key = webclient:request("http://httpbin.org/headers") 9 | webclient:set_httpheader(req, "User-Agent: dpull", [[If-None-Match:"573dff7cd86a737f0fd9ecc862aed14f"]]) 10 | 11 | requests[key] = req 12 | 13 | while next(requests) do 14 | local finish_key, result = webclient:query() 15 | if finish_key then 16 | local req = requests[finish_key] 17 | assert(req) 18 | requests[finish_key] = nil 19 | 20 | assert(result == 0) 21 | 22 | local content, errmsg = webclient:get_respond(req) 23 | local data = json.decode(content) 24 | assert(data.headers["User-Agent"] == "dpull") 25 | assert(data.headers["If-None-Match"] == [["573dff7cd86a737f0fd9ecc862aed14f"]]) 26 | 27 | webclient:remove_request(req) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJ_NAME "Test") 2 | 3 | set (WEBCLIENT_SRC ${CMAKE_SOURCE_DIR}/webclient.c main.c) 4 | 5 | set(LUA_SRC_PATH "${CMAKE_SOURCE_DIR}/3rd/lua") 6 | set(LUA_SRC_IGNORE "${LUA_SRC_PATH}/lua.c;${LUA_SRC_PATH}/luac.c") 7 | 8 | aux_source_directory(${LUA_SRC_PATH} LUA_SRC) 9 | list(REMOVE_ITEM LUA_SRC ${LUA_SRC_IGNORE}) 10 | 11 | include_directories(${CMAKE_SOURCE_DIR}/3rd/lua) 12 | add_executable(${PROJ_NAME} ${WEBCLIENT_SRC} ${LUA_SRC}) 13 | 14 | source_group(lua FILES ${LUA_SRC}) 15 | source_group(test FILES ${WEBCLIENT_SRC}) 16 | 17 | if (CURL_FOUND) 18 | include_directories(${CURL_INCLUDE_DIR}) 19 | target_link_libraries(${PROJ_NAME} ${CURL_LIBRARY}) 20 | else(CURL_FOUND) 21 | add_definitions(-DCURL_STATICLIB) 22 | include_directories(${CMAKE_SOURCE_DIR}/3rd/curl/include) 23 | include_directories(${CMAKE_BINARY_DIR}/3rd/curl/include/curl) 24 | target_link_libraries(${PROJ_NAME} libcurl) 25 | endif(CURL_FOUND) 26 | 27 | if(MSVC) 28 | add_definitions(-D_CRT_SECURE_NO_WARNINGS) 29 | endif(MSVC) -------------------------------------------------------------------------------- /test/post.lua: -------------------------------------------------------------------------------- 1 | local json = require "json" 2 | local webclient_lib = require 'webclient' 3 | local webclient = webclient_lib.create() 4 | local requests = {}; 5 | 6 | local requests = {} 7 | 8 | local req, key = webclient:request("http://httpbin.org/post", "a=1&b=2") 9 | requests[key] = req 10 | 11 | local req, key = webclient:request("http://httpbin.org/post", {{name="a", contents = 1}, {name="b", file="main.lua", content_type="text/plain", filename="example.txt"}}) 12 | requests[key] = req 13 | 14 | while next(requests) do 15 | local finish_key, result = webclient:query() 16 | if finish_key then 17 | local req = requests[finish_key] 18 | assert(req) 19 | requests[finish_key] = nil 20 | 21 | assert(result == 0) 22 | 23 | local content, errmsg = webclient:get_respond(req) 24 | local data = json.decode(content) 25 | 26 | assert(data.form.a == "1") 27 | 28 | if data.form.b then 29 | assert(data.form.b == "2") 30 | else 31 | assert(data.files.b) 32 | end 33 | 34 | webclient:remove_request(req) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /test/redirect.lua: -------------------------------------------------------------------------------- 1 | local json = require "json" 2 | local webclient_lib = require 'webclient' 3 | local webclient = webclient_lib.create() 4 | local requests = {}; 5 | 6 | local requests = {} 7 | 8 | local req, key = webclient:request("http://httpbin.org/relative-redirect/6") --302 9 | requests[key] = req 10 | 11 | local req, key = webclient:request("http://httpbin.org/absolute-redirect/6") --302 12 | requests[key] = req 13 | 14 | local req, key = webclient:request("http://httpbin.org/redirect-to?status_code=307&url=" .. webclient:url_encoding("http://httpbin.org/get")) --307 15 | requests[key] = req 16 | 17 | while next(requests) do 18 | local finish_key, result = webclient:query() 19 | if finish_key then 20 | local req = requests[finish_key] 21 | assert(req) 22 | requests[finish_key] = nil 23 | 24 | assert(result == 0) 25 | 26 | local content, errmsg = webclient:get_respond(req) 27 | local data = json.decode(content) 28 | 29 | assert(data.url == "http://httpbin.org/get") 30 | 31 | local info = webclient:get_info(req) 32 | assert(info.response_code == 200) 33 | webclient:remove_request(req) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2017 dpull.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/example.lua: -------------------------------------------------------------------------------- 1 | local json = require "json" 2 | local webclient_lib = require 'webclient' 3 | local webclient = webclient_lib.create() 4 | local requests = {}; 5 | 6 | local requests = {} 7 | 8 | local req, key = webclient:request("http://httpbin.org/anything?a=1&b=2", "c=3&d=4&e=" .. webclient:url_encoding("http://httpbin.org/"), 5000) -- connect timeout 5000 ms 9 | webclient:set_httpheader(req, "User-Agent: dpull", [[If-None-Match:"573dff7cd86a737f0fd9ecc862aed14f"]]) 10 | webclient:debug(req, true) 11 | 12 | requests[key] = req 13 | 14 | while next(requests) do 15 | local finish_key, result = webclient:query() 16 | if finish_key then 17 | local req = requests[finish_key] 18 | assert(req) 19 | requests[finish_key] = nil 20 | 21 | local content, errmsg = webclient:get_respond(req) 22 | print("respond", result, content, errmsg) 23 | 24 | local info = webclient:get_info(req) 25 | print("info", info.ip, info.port, info.content_length, info.response_code) 26 | 27 | webclient:remove_request(req) 28 | else 29 | local is_get_upload_progress = false 30 | local down, total = webclient:get_progress(req, is_get_upload_progress) 31 | -- print("progress", down, total) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | set(CMAKE_SUPPRESS_REGENERATION TRUE CACHE BOOL "Disable Zero Check Project") 3 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 4 | 5 | set(PROJ_NAME "lua-webclient") 6 | project(${PROJ_NAME}) 7 | 8 | find_package(CURL) 9 | if(NOT CURL_FOUND) 10 | set(BUILD_TESTING OFF CACHE BOOL "") 11 | set(CURL_STATICLIB ON CACHE BOOL "") 12 | set(HTTP_ONLY ON CACHE BOOL "") 13 | set(BUILD_CURL_EXE OFF CACHE BOOL "") 14 | set(BUILD_CURL_TESTS OFF CACHE BOOL "") 15 | set(CMAKE_USE_OPENSSL OFF CACHE BOOL "") 16 | set(CMAKE_USE_LIBSSH2 OFF CACHE BOOL "") 17 | set(CURL_ZLIB OFF CACHE BOOL "") 18 | set(ENABLE_MANUAL OFF CACHE BOOL "") 19 | add_subdirectory(3rd/curl) 20 | endif() 21 | add_subdirectory(test) 22 | 23 | message(STATUS *******************************************************) 24 | message(STATUS CMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}) 25 | message(STATUS CMAKE_SYSTEM_VERSION=${CMAKE_SYSTEM_VERSION}) 26 | message(STATUS CMAKE_HOST_SYSTEM_NAME=${CMAKE_HOST_SYSTEM_NAME}) 27 | message(STATUS CMAKE_GENERATOR=${CMAKE_GENERATOR}) 28 | message(STATUS CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}) 29 | message(STATUS CMAKE_C_COMPILER=${CMAKE_C_COMPILER}) 30 | message(STATUS CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}) 31 | message(STATUS CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES=${CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES}) 32 | message(STATUS CMAKE_CXX_STANDARD_LIBRARIES=${CMAKE_CXX_STANDARD_LIBRARIES}) 33 | message(STATUS CMAKE_C_FLAGS=${CMAKE_C_FLAGS}) 34 | message(STATUS CMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}) 35 | message(STATUS CMAKE_SHARED_LINKER_FLAGS=${CMAKE_SHARED_LINKER_FLAGS}) 36 | message(STATUS CMAKE_MODULE_LINKER_FLAGS=${CMAKE_MODULE_LINKER_FLAGS}) 37 | message(STATUS CMAKE_EXE_LINKER_FLAGS=${CMAKE_EXE_LINKER_FLAGS}) 38 | message(STATUS CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}) 39 | message(STATUS CURL_FOUND=${CURL_FOUND}) 40 | message(STATUS *******************************************************) 41 | -------------------------------------------------------------------------------- /webclient.lua: -------------------------------------------------------------------------------- 1 | --- webclient. (skynet服务). 2 | -- 3 | -- @module webclient 4 | -- @usage local webclient = skynet.newservice("webclient") 5 | 6 | local skynet = require "skynet" 7 | local webclientlib = require "webclient" 8 | local webclient = webclientlib.create() 9 | local requests = nil 10 | 11 | local function resopnd(request, result) 12 | if not request.response then 13 | return 14 | end 15 | 16 | local content, errmsg = webclient:get_respond(request.req) 17 | local info = webclient:get_info(request.req) 18 | 19 | if result == 0 then 20 | request.response(true, true, content, info) 21 | else 22 | request.response(true, false, errmsg, info) 23 | end 24 | end 25 | 26 | local function query() 27 | while next(requests) do 28 | local finish_key, result = webclient:query() 29 | if finish_key then 30 | local request = requests[finish_key]; 31 | assert(request) 32 | 33 | xpcall(resopnd, function() skynet.error(debug.traceback()) end, request, result) 34 | 35 | webclient:remove_request(request.req) 36 | requests[finish_key] = nil 37 | else 38 | skynet.sleep(1) 39 | end 40 | end 41 | requests = nil 42 | end 43 | 44 | --- 请求某个url 45 | -- @function request 46 | -- @string url url 47 | -- @tab[opt] get get的参数 48 | -- @param[opt] post post参数,table or string类型 49 | -- @bool[opt] no_reply 使用skynet.call则要设置为nil或false,使用skynet.send则要设置为true 50 | -- @treturn bool 请求是否成功 51 | -- @treturn string 当成功时,返回内容,当失败时,返回出错原因 52 | -- @usage skynet.call(webclient, "lua", "request", "http://www.dpull.com") 53 | -- @usage skynet.send(webclient, "lua", "request", "http://www.dpull.com", nil, nil, true) 54 | local function request(url, get, post, no_reply) 55 | if get then 56 | local i = 0 57 | for k, v in pairs(get) do 58 | k = webclient:url_encoding(k) 59 | v = webclient:url_encoding(v) 60 | 61 | url = string.format("%s%s%s=%s", url, i == 0 and "?" or "&", k, v) 62 | i = i + 1 63 | end 64 | end 65 | 66 | if post and type(post) == "table" then 67 | local data = {} 68 | for k,v in pairs(post) do 69 | k = webclient:url_encoding(k) 70 | v = webclient:url_encoding(v) 71 | 72 | table.insert(data, string.format("%s=%s", k, v)) 73 | end 74 | post = table.concat(data , "&") 75 | end 76 | 77 | local req, key = webclient:request(url, post) 78 | if not req then 79 | return skynet.ret() 80 | end 81 | assert(key) 82 | 83 | local response = nil 84 | if not no_reply then 85 | response = skynet.response() 86 | end 87 | 88 | if requests == nil then 89 | requests = {} 90 | skynet.fork(query) 91 | end 92 | 93 | requests[key] = { 94 | url = url, 95 | req = req, 96 | response = response, 97 | } 98 | end 99 | 100 | skynet.start(function() 101 | skynet.dispatch("lua", function(session, source, command, ...) 102 | assert(command == "request") 103 | request(...) 104 | end) 105 | end) 106 | -------------------------------------------------------------------------------- /test/json.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- json.lua 3 | -- 4 | -- Copyright (c) 2015 rxi 5 | -- 6 | -- This library is free software; you can redistribute it and/or modify it 7 | -- under the terms of the MIT license. See LICENSE for details. 8 | -- 9 | 10 | local json = { _version = "0.1.0" } 11 | 12 | ------------------------------------------------------------------------------- 13 | -- Encode 14 | ------------------------------------------------------------------------------- 15 | 16 | local encode 17 | 18 | local escape_char_map = { 19 | [ "\\" ] = "\\\\", 20 | [ "\"" ] = "\\\"", 21 | [ "\b" ] = "\\b", 22 | [ "\f" ] = "\\f", 23 | [ "\n" ] = "\\n", 24 | [ "\r" ] = "\\r", 25 | [ "\t" ] = "\\t", 26 | } 27 | 28 | local escape_char_map_inv = { [ "\\/" ] = "/" } 29 | for k, v in pairs(escape_char_map) do 30 | escape_char_map_inv[v] = k 31 | end 32 | 33 | 34 | local function escape_char(c) 35 | return escape_char_map[c] or string.format("\\u%04x", c:byte()) 36 | end 37 | 38 | 39 | local function encode_nil(val) 40 | return "null" 41 | end 42 | 43 | 44 | local function encode_table(val, stack) 45 | local res = {} 46 | stack = stack or {} 47 | 48 | -- Circular reference? 49 | if stack[val] then error("circular reference") end 50 | 51 | stack[val] = true 52 | 53 | if val[1] ~= nil or next(val) == nil then 54 | -- Treat as array -- check keys are valid and it is not sparse 55 | local n = 0 56 | for k in pairs(val) do 57 | if type(k) ~= "number" then 58 | error("invalid table: mixed or invalid key types") 59 | end 60 | n = n + 1 61 | end 62 | if n ~= #val then 63 | error("invalid table: sparse array") 64 | end 65 | -- Encode 66 | for i, v in ipairs(val) do 67 | table.insert(res, encode(v, stack)) 68 | end 69 | stack[val] = nil 70 | return "[" .. table.concat(res, ",") .. "]" 71 | 72 | else 73 | -- Treat as an object 74 | for k, v in pairs(val) do 75 | if type(k) ~= "string" then 76 | error("invalid table: mixed or invalid key types") 77 | end 78 | table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) 79 | end 80 | stack[val] = nil 81 | return "{" .. table.concat(res, ",") .. "}" 82 | end 83 | end 84 | 85 | 86 | local function encode_string(val) 87 | return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' 88 | end 89 | 90 | 91 | local function encode_number(val) 92 | -- Check for NaN, -inf and inf 93 | if val ~= val or val <= -math.huge or val >= math.huge then 94 | error("unexpected number value '" .. tostring(val) .. "'") 95 | end 96 | return string.format("%.14g", val) 97 | end 98 | 99 | 100 | local type_func_map = { 101 | [ "nil" ] = encode_nil, 102 | [ "table" ] = encode_table, 103 | [ "string" ] = encode_string, 104 | [ "number" ] = encode_number, 105 | [ "boolean" ] = tostring, 106 | } 107 | 108 | 109 | encode = function(val, stack) 110 | local t = type(val) 111 | local f = type_func_map[t] 112 | if f then 113 | return f(val, stack) 114 | end 115 | error("unexpected type '" .. t .. "'") 116 | end 117 | 118 | 119 | function json.encode(val) 120 | return ( encode(val) ) 121 | end 122 | 123 | 124 | ------------------------------------------------------------------------------- 125 | -- Decode 126 | ------------------------------------------------------------------------------- 127 | 128 | local parse 129 | 130 | local function create_set(...) 131 | local res = {} 132 | for i = 1, select("#", ...) do 133 | res[ select(i, ...) ] = true 134 | end 135 | return res 136 | end 137 | 138 | local space_chars = create_set(" ", "\t", "\r", "\n") 139 | local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") 140 | local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") 141 | local literals = create_set("true", "false", "null") 142 | 143 | local literal_map = { 144 | [ "true" ] = true, 145 | [ "false" ] = false, 146 | [ "null" ] = nil, 147 | } 148 | 149 | 150 | local function next_char(str, idx, set, negate) 151 | for i = idx, #str do 152 | if set[str:sub(i, i)] ~= negate then 153 | return i 154 | end 155 | end 156 | return #str + 1 157 | end 158 | 159 | 160 | local function decode_error(str, idx, msg) 161 | local line_count = 1 162 | local col_count = 1 163 | for i = 1, idx - 1 do 164 | col_count = col_count + 1 165 | if str:sub(i, i) == "\n" then 166 | line_count = line_count + 1 167 | col_count = 1 168 | end 169 | end 170 | error( string.format("%s at line %d col %d", msg, line_count, col_count) ) 171 | end 172 | 173 | 174 | local function codepoint_to_utf8(n) 175 | -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa 176 | local f = math.floor 177 | if n <= 0x7f then 178 | return string.char(n) 179 | elseif n <= 0x7ff then 180 | return string.char(f(n / 64) + 192, n % 64 + 128) 181 | elseif n <= 0xffff then 182 | return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) 183 | elseif n <= 0x10ffff then 184 | return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, 185 | f(n % 4096 / 64) + 128, n % 64 + 128) 186 | end 187 | error( string.format("invalid unicode codepoint '%x'", n) ) 188 | end 189 | 190 | 191 | local function parse_unicode_escape(s) 192 | local n1 = tonumber( s:sub(3, 6), 16 ) 193 | local n2 = tonumber( s:sub(9, 12), 16 ) 194 | -- Surrogate pair? 195 | if n2 then 196 | return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) 197 | else 198 | return codepoint_to_utf8(n1) 199 | end 200 | end 201 | 202 | 203 | local function parse_string(str, i) 204 | local has_unicode_escape = false 205 | local has_surrogate_escape = false 206 | local has_escape = false 207 | local last 208 | for j = i + 1, #str do 209 | local x = str:byte(j) 210 | 211 | if x < 32 then 212 | decode_error(str, j, "control character in string") 213 | end 214 | 215 | if last == 92 then -- "\\" (escape char) 216 | if x == 117 then -- "u" (unicode escape sequence) 217 | local hex = str:sub(j + 1, j + 5) 218 | if not hex:find("%x%x%x%x") then 219 | decode_error(str, j, "invalid unicode escape in string") 220 | end 221 | if hex:find("^[dD][89aAbB]") then 222 | has_surrogate_escape = true 223 | else 224 | has_unicode_escape = true 225 | end 226 | else 227 | local c = string.char(x) 228 | if not escape_chars[c] then 229 | decode_error(str, j, "invalid escape char '" .. c .. "' in string") 230 | end 231 | has_escape = true 232 | end 233 | last = nil 234 | 235 | elseif x == 34 then -- '"' (end of string) 236 | local s = str:sub(i + 1, j - 1) 237 | if has_surrogate_escape then 238 | s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape) 239 | end 240 | if has_unicode_escape then 241 | s = s:gsub("\\u....", parse_unicode_escape) 242 | end 243 | if has_escape then 244 | s = s:gsub("\\.", escape_char_map_inv) 245 | end 246 | return s, j + 1 247 | 248 | else 249 | last = x 250 | end 251 | end 252 | decode_error(str, i, "expected closing quote for string") 253 | end 254 | 255 | 256 | local function parse_number(str, i) 257 | local x = next_char(str, i, delim_chars) 258 | local s = str:sub(i, x - 1) 259 | local n = tonumber(s) 260 | if not n then 261 | decode_error(str, i, "invalid number '" .. s .. "'") 262 | end 263 | return n, x 264 | end 265 | 266 | 267 | local function parse_literal(str, i) 268 | local x = next_char(str, i, delim_chars) 269 | local word = str:sub(i, x - 1) 270 | if not literals[word] then 271 | decode_error(str, i, "invalid literal '" .. word .. "'") 272 | end 273 | return literal_map[word], x 274 | end 275 | 276 | 277 | local function parse_array(str, i) 278 | local res = {} 279 | local n = 1 280 | i = i + 1 281 | while 1 do 282 | local x 283 | i = next_char(str, i, space_chars, true) 284 | -- Empty / end of array? 285 | if str:sub(i, i) == "]" then 286 | i = i + 1 287 | break 288 | end 289 | -- Read token 290 | x, i = parse(str, i) 291 | res[n] = x 292 | n = n + 1 293 | -- Next token 294 | i = next_char(str, i, space_chars, true) 295 | local chr = str:sub(i, i) 296 | i = i + 1 297 | if chr == "]" then break end 298 | if chr ~= "," then decode_error(str, i, "expected ']' or ','") end 299 | end 300 | return res, i 301 | end 302 | 303 | 304 | local function parse_object(str, i) 305 | local res = {} 306 | i = i + 1 307 | while 1 do 308 | local key, val 309 | i = next_char(str, i, space_chars, true) 310 | -- Empty / end of object? 311 | if str:sub(i, i) == "}" then 312 | i = i + 1 313 | break 314 | end 315 | -- Read key 316 | if str:sub(i, i) ~= '"' then 317 | decode_error(str, i, "expected string for key") 318 | end 319 | key, i = parse(str, i) 320 | -- Read ':' delimiter 321 | i = next_char(str, i, space_chars, true) 322 | if str:sub(i, i) ~= ":" then 323 | decode_error(str, i, "expected ':' after key") 324 | end 325 | i = next_char(str, i + 1, space_chars, true) 326 | -- Read value 327 | val, i = parse(str, i) 328 | -- Set 329 | res[key] = val 330 | -- Next token 331 | i = next_char(str, i, space_chars, true) 332 | local chr = str:sub(i, i) 333 | i = i + 1 334 | if chr == "}" then break end 335 | if chr ~= "," then decode_error(str, i, "expected '}' or ','") end 336 | end 337 | return res, i 338 | end 339 | 340 | 341 | local char_func_map = { 342 | [ '"' ] = parse_string, 343 | [ "0" ] = parse_number, 344 | [ "1" ] = parse_number, 345 | [ "2" ] = parse_number, 346 | [ "3" ] = parse_number, 347 | [ "4" ] = parse_number, 348 | [ "5" ] = parse_number, 349 | [ "6" ] = parse_number, 350 | [ "7" ] = parse_number, 351 | [ "8" ] = parse_number, 352 | [ "9" ] = parse_number, 353 | [ "-" ] = parse_number, 354 | [ "t" ] = parse_literal, 355 | [ "f" ] = parse_literal, 356 | [ "n" ] = parse_literal, 357 | [ "[" ] = parse_array, 358 | [ "{" ] = parse_object, 359 | } 360 | 361 | 362 | parse = function(str, idx) 363 | local chr = str:sub(idx, idx) 364 | local f = char_func_map[chr] 365 | if f then 366 | return f(str, idx) 367 | end 368 | decode_error(str, idx, "unexpected character '" .. chr .. "'") 369 | end 370 | 371 | 372 | function json.decode(str) 373 | if type(str) ~= "string" then 374 | error("expected argument of type string, got " .. type(str)) 375 | end 376 | return ( parse(str, next_char(str, 1, space_chars, true)) ) 377 | end 378 | 379 | 380 | return json 381 | -------------------------------------------------------------------------------- /webclient.c: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | Copyright (c) 2014 dpull.com 3 | 4 | http://www.dpull.com 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | ****************************************************************************/ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "lua.h" 31 | #include "lualib.h" 32 | #include "lauxlib.h" 33 | 34 | #ifdef _MSC_VER 35 | #include 36 | #define WEBCLIENT_MUTEX_T SRWLOCK 37 | #define WEBCLIENT_MUTEX_INITIALIZER SRWLOCK_INIT 38 | #define WEBCLIENT_MUTEX_LOCK AcquireSRWLockExclusive 39 | #define WEBCLIENT_MUTEX_UNLOCK ReleaseSRWLockExclusive 40 | #else 41 | #include 42 | #define WEBCLIENT_MUTEX_T pthread_mutex_t 43 | #define WEBCLIENT_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER 44 | #define WEBCLIENT_MUTEX_LOCK pthread_mutex_lock 45 | #define WEBCLIENT_MUTEX_UNLOCK pthread_mutex_unlock 46 | #endif 47 | 48 | #define IP_LENGTH 16 49 | #define MAX(a, b) (((a) > (b)) ? (a) : (b)) 50 | #define LUA_WEB_CLIENT_MT ("com.dpull.lib.WebClientMT") 51 | #define ENABLE_FOLLOWLOCATION 1 52 | 53 | struct webclient 54 | { 55 | CURLM* curlm; 56 | CURL* encoding_curl; 57 | }; 58 | 59 | struct webrequest 60 | { 61 | CURL* curl; 62 | struct curl_slist* header; 63 | struct curl_httppost* http_post; 64 | char error[CURL_ERROR_SIZE]; 65 | char* content; 66 | size_t content_length; 67 | size_t content_maxlength; 68 | bool content_realloc_failed; 69 | }; 70 | 71 | static WEBCLIENT_MUTEX_T s_webclient_mutex = WEBCLIENT_MUTEX_INITIALIZER; 72 | 73 | static void webclient_global_init() 74 | { 75 | WEBCLIENT_MUTEX_LOCK(&s_webclient_mutex); 76 | curl_global_init(CURL_GLOBAL_ALL); 77 | WEBCLIENT_MUTEX_UNLOCK(&s_webclient_mutex); 78 | } 79 | 80 | static void webclient_global_cleanup() 81 | { 82 | WEBCLIENT_MUTEX_LOCK(&s_webclient_mutex); 83 | curl_global_cleanup(); 84 | WEBCLIENT_MUTEX_UNLOCK(&s_webclient_mutex); 85 | } 86 | 87 | static int webclient_create(lua_State* l) 88 | { 89 | curl_version_info_data* data = curl_version_info(CURLVERSION_NOW); 90 | if (data->version_num < 0x070F04) 91 | return luaL_error(l, "requires 7.15.4 or higher curl, current version is %s", data->version); 92 | 93 | webclient_global_init(); 94 | CURLM* curlm = curl_multi_init(); 95 | if (!curlm) { 96 | webclient_global_cleanup(); 97 | return luaL_error(l, "webclient create failed"); 98 | } 99 | 100 | struct webclient* webclient = (struct webclient*)lua_newuserdata(l, sizeof(*webclient)); 101 | webclient->curlm = curlm; 102 | webclient->encoding_curl = NULL; 103 | 104 | luaL_getmetatable(l, LUA_WEB_CLIENT_MT); 105 | lua_setmetatable(l, -2); 106 | 107 | return 1; 108 | } 109 | 110 | static int webclient_destory(lua_State* l) 111 | { 112 | struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT); 113 | if (!webclient) 114 | return luaL_argerror(l, 1, "parameter self invalid"); 115 | 116 | if (webclient->encoding_curl) { 117 | curl_easy_cleanup(webclient->encoding_curl); 118 | webclient->encoding_curl = NULL; 119 | } 120 | curl_multi_cleanup(webclient->curlm); 121 | webclient->curlm = NULL; 122 | webclient_global_cleanup(); 123 | return 0; 124 | } 125 | 126 | static CURL* webclient_realquery(struct webclient* webclient, CURLcode* ret_result) 127 | { 128 | while (true) { 129 | int msgs_in_queue; 130 | CURLMsg* curlmsg = curl_multi_info_read(webclient->curlm, &msgs_in_queue); 131 | if (!curlmsg) 132 | return NULL; 133 | 134 | if (curlmsg->msg != CURLMSG_DONE) 135 | continue; 136 | 137 | *ret_result = curlmsg->data.result; 138 | return curlmsg->easy_handle; 139 | } 140 | } 141 | 142 | static int webclient_query(lua_State* l) 143 | { 144 | struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT); 145 | if (!webclient) 146 | return luaL_argerror(l, 1, "parameter self invalid"); 147 | 148 | CURLcode handle_result; 149 | CURL* handle = webclient_realquery(webclient, &handle_result); 150 | if (handle) { 151 | lua_pushlightuserdata(l, handle); 152 | lua_pushinteger(l, handle_result); 153 | return 2; 154 | } 155 | 156 | int running_handles; 157 | CURLMcode perform_result = curl_multi_perform(webclient->curlm, &running_handles); 158 | if (perform_result != CURLM_OK && perform_result != CURLM_CALL_MULTI_PERFORM) { 159 | return luaL_error(l, "webclient query failed"); 160 | } 161 | 162 | handle = webclient_realquery(webclient, &handle_result); 163 | if (handle) { 164 | lua_pushlightuserdata(l, handle); 165 | lua_pushinteger(l, handle_result); 166 | return 2; 167 | } 168 | return 0; 169 | } 170 | 171 | static size_t write_callback(char* buffer, size_t block_size, size_t count, void* arg) 172 | { 173 | struct webrequest* webrequest = (struct webrequest*)arg; 174 | assert(webrequest); 175 | 176 | size_t length = block_size * count; 177 | if (webrequest->content_realloc_failed) 178 | return length; 179 | 180 | if (webrequest->content_length + length > webrequest->content_maxlength) { 181 | webrequest->content_maxlength = MAX(webrequest->content_maxlength, webrequest->content_length + length); 182 | webrequest->content_maxlength = MAX(webrequest->content_maxlength, 512); 183 | webrequest->content_maxlength = 2 * webrequest->content_maxlength; 184 | 185 | void* new_content = (char*)realloc(webrequest->content, webrequest->content_maxlength); 186 | if (!new_content) { 187 | webrequest->content_realloc_failed = true; 188 | return length; 189 | } 190 | webrequest->content = (char *)new_content; 191 | } 192 | 193 | memcpy(webrequest->content + webrequest->content_length, buffer, length); 194 | webrequest->content_length += length; 195 | return length; 196 | } 197 | 198 | static struct webrequest* webclient_realrequest(struct webclient* webclient, const char* url, struct curl_httppost* http_post, const char* post_field, size_t post_field_len, long connect_timeout_ms) 199 | { 200 | struct webrequest* webrequest = (struct webrequest*)malloc(sizeof(*webrequest)); 201 | memset(webrequest, 0, sizeof(*webrequest)); 202 | 203 | CURL* handle = curl_easy_init(); 204 | if (!handle) 205 | goto failed; 206 | 207 | curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1); 208 | curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, false); 209 | curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, false); 210 | curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, ENABLE_FOLLOWLOCATION); 211 | curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback); 212 | curl_easy_setopt(handle, CURLOPT_WRITEDATA, webrequest); 213 | curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, webrequest->error); 214 | curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT_MS, connect_timeout_ms); 215 | curl_easy_setopt(handle, CURLOPT_URL, url); 216 | 217 | if (http_post) { 218 | webrequest->http_post = http_post; 219 | curl_easy_setopt(handle, CURLOPT_HTTPPOST, webrequest->http_post); 220 | } 221 | else if (post_field) { 222 | curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE, (long)post_field_len); 223 | curl_easy_setopt(handle, CURLOPT_COPYPOSTFIELDS, post_field); 224 | } 225 | 226 | if (curl_multi_add_handle(webclient->curlm, handle) == CURLM_OK) { 227 | webrequest->curl = handle; 228 | return webrequest; 229 | } 230 | 231 | failed: 232 | if (handle) { 233 | curl_easy_cleanup(handle); 234 | handle = NULL; 235 | } 236 | free(webrequest); 237 | return NULL; 238 | } 239 | 240 | static struct curl_httppost* webclient_tohttppost(lua_State* l, int index) 241 | { 242 | enum { forms_max_index = 7 }; 243 | struct curl_forms forms[forms_max_index + 1]; 244 | 245 | int formadd_failed = false; 246 | struct curl_httppost* firstitem = NULL; 247 | struct curl_httppost* lastitem = NULL; 248 | 249 | lua_pushnil(l); 250 | while (!formadd_failed && lua_next(l, index)) { 251 | int forms_index = 0; 252 | 253 | lua_pushnil(l); 254 | while (forms_index < forms_max_index && lua_next(l, -2)) { 255 | lua_pushvalue(l, -2); 256 | 257 | const char* key = lua_tostring(l, -1); 258 | size_t value_length = 0; 259 | const char* value = lua_tolstring(l, -2, &value_length); 260 | 261 | if (strcmp(key, "name") == 0) { 262 | forms[forms_index].option = CURLFORM_COPYNAME; 263 | forms[forms_index].value = value; 264 | forms_index++; 265 | } 266 | else if (strcmp(key, "contents") == 0) { 267 | forms[forms_index].option = CURLFORM_COPYCONTENTS; 268 | forms[forms_index].value = value; 269 | forms_index++; 270 | 271 | forms[forms_index].option = CURLFORM_CONTENTSLENGTH; 272 | forms[forms_index].value = (const char*)value_length; 273 | forms_index++; 274 | } 275 | else if (strcmp(key, "file") == 0) { 276 | forms[forms_index].option = CURLFORM_FILE; 277 | forms[forms_index].value = value; 278 | forms_index++; 279 | } 280 | else if (strcmp(key, "content_type") == 0) { 281 | forms[forms_index].option = CURLFORM_CONTENTTYPE; 282 | forms[forms_index].value = value; 283 | forms_index++; 284 | } 285 | else if (strcmp(key, "filename") == 0) { 286 | forms[forms_index].option = CURLFORM_FILENAME; 287 | forms[forms_index].value = value; 288 | forms_index++; 289 | } 290 | lua_pop(l, 2); 291 | } 292 | lua_pop(l, 1); 293 | 294 | forms[forms_index].option = CURLFORM_END; 295 | CURLFORMcode result = curl_formadd(&firstitem, &lastitem, CURLFORM_ARRAY, forms, CURLFORM_END); 296 | if (result != CURL_FORMADD_OK) { 297 | formadd_failed = true; 298 | break; 299 | } 300 | } 301 | 302 | if (formadd_failed) { 303 | curl_formfree(firstitem); 304 | return NULL; 305 | } 306 | return firstitem; 307 | } 308 | 309 | static int webclient_request(lua_State* l) 310 | { 311 | struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT); 312 | if (!webclient) 313 | return luaL_argerror(l, 1, "parameter self invalid"); 314 | 315 | const char* url = lua_tostring(l, 2); 316 | if (!url) 317 | return luaL_argerror(l, 2, "parameter url invalid"); 318 | 319 | struct curl_httppost* http_post = NULL; 320 | const char* postdata = NULL; 321 | size_t postdatalen = 0; 322 | long connect_timeout_ms = 5000; 323 | 324 | int top = lua_gettop(l); 325 | if (top > 2 && lua_istable(l, 3)) { 326 | http_post = webclient_tohttppost(l, 3); 327 | if (!http_post) 328 | return luaL_argerror(l, 3, "parameter post_form invalid"); 329 | } 330 | else if (top > 2 && lua_isstring(l, 3)) { 331 | postdata = lua_tolstring(l, 3, &postdatalen); 332 | } 333 | 334 | if (top > 3 && lua_isnumber(l, 4)) { 335 | connect_timeout_ms = (long)lua_tointeger(l, 4); 336 | if (connect_timeout_ms < 0) 337 | return luaL_argerror(l, 4, "parameter connect_timeout_ms invalid"); 338 | } 339 | 340 | struct webrequest* webrequest = webclient_realrequest(webclient, url, http_post, postdata, postdatalen, connect_timeout_ms); 341 | if (!webrequest) 342 | return 0; 343 | 344 | lua_pushlightuserdata(l, webrequest); 345 | lua_pushlightuserdata(l, webrequest->curl); 346 | return 2; 347 | } 348 | 349 | static int webclient_removerequest(lua_State* l) 350 | { 351 | struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT); 352 | if (!webclient) 353 | return luaL_argerror(l, 1, "parameter self invalid"); 354 | 355 | struct webrequest* webrequest = (struct webrequest*)lua_touserdata(l, 2); 356 | if (!webrequest) 357 | return luaL_argerror(l, 2, "parameter index invalid"); 358 | 359 | curl_multi_remove_handle(webclient->curlm, webrequest->curl); 360 | curl_easy_cleanup(webrequest->curl); 361 | curl_slist_free_all(webrequest->header); 362 | curl_formfree(webrequest->http_post); 363 | if (webrequest->content) 364 | free(webrequest->content); 365 | free(webrequest); 366 | return 0; 367 | } 368 | 369 | static int webclient_getrespond(lua_State* l) 370 | { 371 | struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT); 372 | if (!webclient) 373 | return luaL_argerror(l, 1, "parameter self invalid"); 374 | 375 | struct webrequest* webrequest = (struct webrequest*)lua_touserdata(l, 2); 376 | if (!webrequest) 377 | return luaL_argerror(l, 2, "parameter index invalid"); 378 | 379 | if (webrequest->content_realloc_failed) { 380 | strncpy(webrequest->error, "not enough memory.", sizeof(webrequest->error)); 381 | } 382 | 383 | if (webrequest->error[0] == '\0') { 384 | lua_pushlstring(l, webrequest->content, webrequest->content_length); 385 | return 1; 386 | } 387 | 388 | lua_pushlstring(l, webrequest->content, webrequest->content_length); 389 | lua_pushstring(l, webrequest->error); 390 | return 2; 391 | } 392 | 393 | static int webclient_getinfo(lua_State* l) 394 | { 395 | struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT); 396 | if (!webclient) 397 | return luaL_argerror(l, 1, "parameter self invalid"); 398 | 399 | struct webrequest* webrequest = (struct webrequest*)lua_touserdata(l, 2); 400 | if (!webrequest) 401 | return luaL_argerror(l, 2, "parameter index invalid"); 402 | 403 | lua_newtable(l); 404 | 405 | char* ip = NULL; 406 | if (curl_easy_getinfo(webrequest->curl, CURLINFO_PRIMARY_IP, &ip) == CURLE_OK) { 407 | lua_pushstring(l, "ip"); 408 | lua_pushstring(l, ip); 409 | lua_settable(l, -3); 410 | } 411 | 412 | long port = 0; 413 | if (curl_easy_getinfo(webrequest->curl, CURLINFO_LOCAL_PORT, &port) == CURLE_OK) { 414 | lua_pushstring(l, "port"); 415 | lua_pushinteger(l, port); 416 | lua_settable(l, -3); 417 | } 418 | 419 | double content_length = 0; 420 | if (curl_easy_getinfo(webrequest->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &content_length) == CURLE_OK) { 421 | lua_pushstring(l, "content_length"); 422 | lua_pushnumber(l, content_length); 423 | lua_settable(l, -3); 424 | } 425 | 426 | long response_code = 0; 427 | if (curl_easy_getinfo(webrequest->curl, CURLINFO_RESPONSE_CODE, &response_code) == CURLE_OK) { 428 | lua_pushstring(l, "response_code"); 429 | lua_pushinteger(l, response_code); 430 | lua_settable(l, -3); 431 | } 432 | 433 | if (webrequest->content_realloc_failed) { 434 | lua_pushstring(l, "content_save_failed"); 435 | lua_pushboolean(l, webrequest->content_realloc_failed); 436 | lua_settable(l, -3); 437 | } 438 | return 1; 439 | } 440 | 441 | static int webclient_getprogress(lua_State* l) 442 | { 443 | struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT); 444 | if (!webclient) 445 | return luaL_argerror(l, 1, "parameter self invalid"); 446 | 447 | struct webrequest* webrequest = (struct webrequest*)lua_touserdata(l, 2); 448 | if (!webrequest) 449 | return luaL_argerror(l, 2, "parameter index invalid"); 450 | 451 | int is_uploadprogress = lua_toboolean(l, 3); 452 | 453 | double finished = 0.0f; 454 | double total = 0.0f; 455 | 456 | if(!is_uploadprogress) { 457 | if (curl_easy_getinfo(webrequest->curl, CURLINFO_SIZE_DOWNLOAD, &finished) != CURLE_OK) 458 | return 0; 459 | if (curl_easy_getinfo(webrequest->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &total) != CURLE_OK) 460 | return 0; 461 | } 462 | else { 463 | if (curl_easy_getinfo(webrequest->curl, CURLINFO_SIZE_UPLOAD, &finished) != CURLE_OK) 464 | return 0; 465 | if (curl_easy_getinfo(webrequest->curl, CURLINFO_CONTENT_LENGTH_UPLOAD, &total) != CURLE_OK) 466 | return 0; 467 | } 468 | 469 | lua_pushnumber(l, finished); 470 | lua_pushnumber(l, total); 471 | return 2; 472 | } 473 | 474 | static int webclient_sethttpheader(lua_State* l) 475 | { 476 | struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT); 477 | if (!webclient) 478 | return luaL_argerror(l, 1, "parameter self invalid"); 479 | 480 | struct webrequest* webrequest = (struct webrequest*)lua_touserdata(l, 2); 481 | if (!webrequest) 482 | return luaL_argerror(l, 2, "parameter index invalid"); 483 | 484 | int i; 485 | int top = lua_gettop(l); 486 | for (i = 3; i <= top; ++i) { 487 | const char* str = lua_tostring(l, i); 488 | webrequest->header = curl_slist_append(webrequest->header, str); 489 | } 490 | 491 | if (webrequest->header) { 492 | curl_easy_setopt(webrequest->curl, CURLOPT_HTTPHEADER, webrequest->header); 493 | } 494 | return 0; 495 | } 496 | 497 | static int webclient_debug(lua_State* l) 498 | { 499 | struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT); 500 | if (!webclient) 501 | return luaL_argerror(l, 1, "parameter self invalid"); 502 | 503 | struct webrequest* webrequest = (struct webrequest*)lua_touserdata(l, 2); 504 | if (!webrequest) 505 | return luaL_argerror(l, 2, "parameter index invalid"); 506 | 507 | int enable = lua_toboolean(l, 3); 508 | curl_easy_setopt(webrequest->curl, CURLOPT_VERBOSE, enable ? 1L : 0L); 509 | return 0; 510 | } 511 | 512 | static int url_encoding(lua_State* l) 513 | { 514 | struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT); 515 | if (!webclient) 516 | return luaL_argerror(l, 1, "parameter self invalid"); 517 | 518 | if (!webclient->encoding_curl) 519 | webclient->encoding_curl = curl_easy_init(); 520 | 521 | size_t length = 0; 522 | const char* str = lua_tolstring(l, 2, &length); 523 | char* ret = curl_easy_escape(webclient->encoding_curl, str, (int)length); 524 | if (!ret) { 525 | lua_pushlstring(l, str, length); 526 | return 1; 527 | } 528 | 529 | lua_pushstring(l, ret); 530 | curl_free(ret); 531 | return 1; 532 | } 533 | 534 | luaL_Reg webclient_createfuns[] = { 535 | { "create", webclient_create }, 536 | { NULL, NULL } 537 | }; 538 | 539 | luaL_Reg webclient_funs[] = { 540 | { "__gc", webclient_destory }, 541 | { "query", webclient_query }, 542 | { "request", webclient_request }, 543 | { "remove_request", webclient_removerequest }, 544 | { "get_respond", webclient_getrespond }, 545 | { "get_info", webclient_getinfo }, 546 | { "get_progress", webclient_getprogress }, 547 | { "set_httpheader", webclient_sethttpheader }, 548 | { "debug", webclient_debug }, 549 | { "url_encoding", url_encoding }, 550 | 551 | { NULL, NULL } 552 | }; 553 | 554 | #if (LUA_VERSION_NUM == 501) 555 | int luaopen_webclient(lua_State * L) 556 | { 557 | if (luaL_newmetatable(L, LUA_WEB_CLIENT_MT)) { 558 | luaL_register(L, NULL, webclient_funs); 559 | lua_pushvalue(L, -1); 560 | lua_setfield(L, -2, "__index"); 561 | lua_pop(L, 1); 562 | } 563 | 564 | luaL_register(L, "webclient", webclient_createfuns); 565 | return 1; 566 | } 567 | #elif (LUA_VERSION_NUM >= 502) 568 | int luaopen_webclient(lua_State * L) 569 | { 570 | luaL_checkversion(L); 571 | 572 | if (luaL_newmetatable(L, LUA_WEB_CLIENT_MT)) { 573 | lua_pushvalue(L, -1); 574 | lua_setfield(L, -2, "__index"); 575 | luaL_setfuncs(L, webclient_funs, 0); 576 | lua_pop(L, 1); 577 | } 578 | 579 | luaL_newlib(L, webclient_createfuns); 580 | return 1; 581 | } 582 | #endif 583 | --------------------------------------------------------------------------------