├── .gitignore ├── .gitmodules ├── .travis.yml ├── AUTHORS ├── CMakeLists.txt ├── Jenkinsfile ├── LICENSE ├── README.md ├── cmake ├── BuildLibCURL.cmake ├── FindCARES.cmake ├── FindLibEV.cmake └── FindTarantool.cmake ├── curl ├── CMakeLists.txt ├── curl_wrapper.c ├── curl_wrapper.h ├── debug.h ├── driver.c ├── driver.h ├── init.lua ├── request_pool.c ├── request_pool.h ├── unit_tests.c └── utils.h ├── debian ├── .gitignore ├── changelog ├── compat ├── control ├── copyright ├── docs ├── rules └── source │ └── format ├── rockspecs ├── tarantool-curl-2.3.1-1.rockspec └── tarantool-curl-scm-1.rockspec ├── rpm └── tarantool-curl.spec └── tests ├── async.lua ├── bugs.lua ├── example.lua ├── load.lua ├── run.sh └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeFiles/ 2 | CMakeCache.txt 3 | Makefile 4 | cmake_*.cmake 5 | install_manifest.txt 6 | *.a 7 | *.cbp 8 | *.d 9 | *.dylib 10 | *.gcno 11 | *.gcda 12 | *.user 13 | *.o 14 | *.reject 15 | *.so 16 | *.snap* 17 | *.xlog* 18 | *~ 19 | .gdb_history 20 | *.old 21 | .*.sw? 22 | build 23 | node_modules 24 | packpack 25 | 26 | third_party/curl-out 27 | third_party/.curl.tmp 28 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/curl"] 2 | path = third_party/curl 3 | url = https://github.com/curl/curl.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: C 3 | services: 4 | - docker 5 | 6 | cache: 7 | directories: 8 | - $HOME/.cache 9 | 10 | env: 11 | global: 12 | - PRODUCT=tarantool-curl 13 | matrix: 14 | - OS=el DIST=6 15 | - OS=el DIST=7 16 | - OS=fedora DIST=24 17 | - OS=fedora DIST=25 18 | # - OS=ubuntu DIST=precise 19 | - OS=ubuntu DIST=trusty 20 | - OS=ubuntu DIST=xenial 21 | - OS=ubuntu DIST=yakkety 22 | - OS=debian DIST=wheezy 23 | - OS=debian DIST=jessie 24 | - OS=debian DIST=stretch 25 | 26 | script: 27 | - git describe --long 28 | - git clone https://github.com/packpack/packpack.git packpack 29 | - packpack/packpack 30 | 31 | before_deploy: 32 | - ls -l build/ 33 | 34 | deploy: 35 | - provider: packagecloud 36 | username: tarantool 37 | repository: "1_7" 38 | token: ${PACKAGECLOUD_TOKEN} 39 | dist: ${OS}/${DIST} 40 | package_glob: build/*.{rpm,deb,dsc} 41 | skip_cleanup: true 42 | on: 43 | branch: master 44 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" 45 | - provider: packagecloud 46 | username: tarantool 47 | repository: "1_8" 48 | token: ${PACKAGECLOUD_TOKEN} 49 | dist: ${OS}/${DIST} 50 | package_glob: build/*.{rpm,deb,dsc} 51 | skip_cleanup: true 52 | on: 53 | branch: master 54 | condition: -n "${OS}" && -n "${DIST}" && -n "${PACKAGECLOUD_TOKEN}" 55 | 56 | notifications: 57 | email: 58 | recipients: 59 | - build@tarantool.org 60 | on_success: change 61 | on_failure: always 62 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # 2 | # Below is complete list of people, who contributed their 3 | # code. 4 | # 5 | # NOTE: If you can commit a change this list, please do not hesitate 6 | # to add your name to it. 7 | # 8 | 9 | Vasiliy Soshnikov 10 | Georgy Kirichenko 11 | Andrey Drozdov 12 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8 FATAL_ERROR) 2 | 3 | include(CMakeDependentOption) 4 | include(ExternalProject) 5 | 6 | project(tarantool-curl C) 7 | 8 | set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) 9 | set(CMAKE_INCLUDE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) 10 | 11 | # Set CFLAGS 12 | set(MY_C_FLAGS "-Wall -Wextra -Werror -std=gnu11 -fno-strict-aliasing") 13 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${MY_C_FLAGS}") 14 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${MY_C_FLAGS}") 15 | 16 | find_package(Tarantool REQUIRED) 17 | find_package(LibEV REQUIRED) 18 | 19 | option(WITH_SYSTEM_CURL "Use system curl, if it's available" ON) 20 | include(BuildLibCURL) 21 | build_libcurl_if_needed() 22 | 23 | message(STATUS "tarantool: ${TARANTOOL_INCLUDE_DIRS}") 24 | message(STATUS "libev includes: ${LIBEV_INCLUDE_DIR} ") 25 | message(STATUS "libev libraries: ${LIBEV_LIBRARIES} ") 26 | message(STATUS "curl includes: ${CURL_INCLUDE_DIRS} ") 27 | message(STATUS "curl libraries: ${CURL_LIBRARIES} ") 28 | 29 | include_directories(${CURL_INCLUDE_DIRS} 30 | ${TARANTOOL_INCLUDE_DIRS} 31 | ${LIBEV_INCLUDE_DIR} ) 32 | 33 | add_custom_target(test 34 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 35 | COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tests/run.sh 36 | DEPENDS driver) 37 | 38 | # Build module 39 | add_subdirectory(curl) 40 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | stage('Build'){ 2 | packpack = new org.tarantool.packpack() 3 | node { 4 | checkout scm 5 | packpack.prepareSources() 6 | } 7 | packpack.packpackBuildMatrix('result') 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2016 Tarantool AUTHORS: please see AUTHORS file. 2 | 3 | Redistribution and use in source and binary forms, with or 4 | without modification, are permitted provided that the following 5 | conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above 8 | copyright notice, this list of conditions and the 9 | following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials 14 | provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 20 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 21 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 24 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 27 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | # THIS MODULE IS NOT SUPPORTED 9 | This module has been migrated into the tarantool core that means you should use `http.client` 10 | 11 | # libcurl bindings for Tarantool 12 | 13 | The `tarantool/curl` package exposes some functionality of 14 | [libcurl](http://https://curl.haxx.se/libcurl), 15 | a library for data transfer via URLs. 16 | With this package, Tarantool Lua applications can 17 | send and receive data over the Internet using a variety of protocols. 18 | 19 | The advantage of integrating `curl` with Tarantool, which is an application 20 | server plus a DBMS, is that anyone can handle all of the tasks associated with 21 | webs (control, manipulation, storage, access) with the same high-level language 22 | and with minimal delay. 23 | 24 | ## Table of contents 25 | 26 | * [How to install](#how-to-install) 27 | * [Getting started](#getting-started) 28 | * [API reference](#api-reference) 29 | * [Example function](#example-function) 30 | * [Example program](#example-program) 31 | 32 | ## How to install 33 | 34 | We assume that you have Tarantool 1.7 and an operating system with developer 35 | tools including `cmake`, a C suppors gnu99 compiler, `git` and Lua. 36 | 37 | You will need the `curl` and `libev` developer packages. To download and install it, say 38 | (example for Ubuntu): 39 | ``` 40 | sudo apt-get install cmake 41 | sudo apt-get install libev libev-dev 42 | sudo apt-get install curl 43 | sudo apt-get install libcurl4-openssl-dev 44 | ``` 45 | (example Mac OS) 46 | ``` 47 | brew install cmake 48 | brew install libev 49 | brew install curl 50 | brew install tarantool 51 | ``` 52 | 53 | The `curl` package itself is on 54 | [github.com/tarantool/curl](github.com/tarantool/curl). 55 | To download and install it, say: 56 | ``` 57 | cd ~ 58 | git clone https://github.com/tarantool/curl.git ~/tarantool-curl 59 | cd tarantool-curl 60 | cmake . 61 | make 62 | ``` 63 | 64 | This should produce a library named `./curl/driver.so`. 65 | Make sure this file, or a copy, is on the path that the Lua "require" statement 66 | uses (`package.path`). The easiest way to ensure this is to add: 67 | ``` 68 | sudo make install 69 | ``` 70 | 71 | ## Getting started 72 | 73 | Start Tarantool in the interactive mode. Execute these requests: 74 | ```lua 75 | curl = require('curl') 76 | http = curl.http() 77 | response = http:request('GET', 'http://tarantool.org', '') 78 | response.code 79 | ``` 80 | 81 | If all goes well, you should see: 82 | ``` 83 | ... 84 | tarantool> response = http:request('GET', 'http://tarantool.org', '') 85 | --- 86 | ... 87 | 88 | tarantool> response.code 89 | --- 90 | - 200 91 | ... 92 | ``` 93 | 94 | This means that you have successfully installed `tarantool/curl` and 95 | successfully executed an instruction that brought data from the Internet. 96 | 97 | ## API reference 98 | 99 | The `curl` package contains one component, `http()`, which contains following 100 | functions: 101 | 102 | * `VERSION` -- a version as string 103 | 104 | * `request(method, url [, options])` -- Asynchronous data transfer 105 | request. See details below. 106 | 107 | * `get(url [, options])` -- This is the same as `request('GET', 108 | url [, options])`. 109 | 110 | * `post(url, body [, options])` -- Post request, this is the same as 111 | `request('POST', url [, options]).` 112 | 113 | * `put(url, body [, options])` -- Put request, this is the same as 114 | `request('PUT', url [, options]).` 115 | 116 | * `async_request(self, method, url[, options])` -- This function does HTTP 117 | request. See details below. 118 | 119 | * `async_get(self, url, options)` -- This is the same as `async_request('GET', 120 | url [, options])`. 121 | 122 | * `async_post(self, url, options)` -- This is the same as `async_request('POST', 123 | url [, options])`. 124 | 125 | * `async_put(self, url, options)` -- This is the same as `async_request('PUT', 126 | url [, options])`. 127 | 128 | * `stat()` -- This function returns a table with many values of statistic. See 129 | details below. 130 | ```lua 131 | local r = http:stat() 132 | r = { 133 | active_requests -- this is number of currently executing requests 134 | 135 | sockets_added -- this is a total number of added sockets into libev loop 136 | 137 | sockets_deleted -- this is a total number of deleted sockets from libev loop 138 | 139 | loop_calls -- this is a total number of iterations over libev loop 140 | 141 | total_requests -- this is a total number of requests 142 | 143 | http_200_responses -- this is a total number of requests which have returned a code HTTP 200 144 | 145 | http_other_responses -- this is a total number of requests which have requests not a HTTP 200 146 | 147 | failed_requests -- this is a total number of requests which have 148 | -- failed (included systeme erros, curl errors, HTTP 149 | -- erros and so on) 150 | } 151 | ``` 152 | 153 | * `free()` -- Should be called at the end of work. This function cleans all 154 | resources (i.e. destructor). 155 | 156 | The `request`, `get`, `post`, `put` functions return a table {code, body} or an error. 157 | 158 | The `async_request`, `async_get`, `async_post`, `async_put` functions return true either error. 159 | 160 | The parameters that can go with the operations are: 161 | 162 | * `method` -- type = string; value = any HTTP method, for example 'GET', 163 | 'POST', 'PUT'. 164 | 165 | * `body` -- type = string; value = anything that should be passed to the 166 | server, for example '{data: 123}'. 167 | 168 | * `url` -- type = string; value = any universal resource locator, for 169 | example 'http://mail.ru'. 170 | 171 | * `options` -- type = table; value = one or more of the following: 172 | 173 | * `ca_path` - a path to an SSL certificate directory; 174 | 175 | * `ca_file` - a path to an SSL certificate file; 176 | 177 | * `headers` - a table of HTTP headers, for example: 178 | `{headers = {['Content-type'] = 'application/json'}}` 179 | Note: If you pass a value for the body parameter, you must set Content-Length header. 180 | 181 | * `keepalive_idle` & `keepalive_interval` - non-universal keepalive 182 | knobs (Linux, AIX, HP-UX, more); 183 | 184 | * `low_speed_time` & `low_speed_limit` - If the download receives 185 | less than "low speed limit" bytes/second during "low speed time" seconds, 186 | the operations is aborted. You could i.e if you have a pretty high speed 187 | connection, abort if it is less than 2000 bytes/sec during 20 seconds; 188 | 189 | * `read_timeout` - Time-out the read operation after this amount of seconds; 190 | 191 | * `connect_timeout` - Time-out connect operations after this amount of 192 | seconds, if connects are; OK within this time, 193 | then fine... This only aborts the connect phase; 194 | 195 | * `dns_cache_timeout` - DNS cache timeout; 196 | 197 | * `curl:async_*(...,)` - a further call; 198 | 199 | * `ctx` - user-defined context; 200 | 201 | * `done` - name of a callback function which is invoked when a request 202 | was completed; 203 | ```lua 204 | function done(curl_code, http_code, curl_error_message, my_ctx) 205 | my_ctx.done = true 206 | my_ctx.http_code = http_code 207 | my_ctx.curl_code = curl_code 208 | my_ctx.error_message = curl_error_message 209 | fiber.wakeup(my_ctx.callee_fiber_id) 210 | end 211 | ``` 212 | * `write` - name of a callback function which is invoked if the 213 | server returns data to the client; 214 | For example, the `write` function might look like this: 215 | ```lua 216 | function(data, context) 217 | context.in_buffer = context.in_buffer .. data 218 | return data:len() 219 | end 220 | ``` 221 | 222 | * `read` - name of a callback function which is invoked if the 223 | client passes data to the server. 224 | For example, the `read` function might look like this: 225 | ```lua 226 | function(content_size, context) 227 | local out_buffer = context.out_buffer 228 | local to_server = out_buffer:sub(1, content_size) 229 | context.out_buffer = out_buffer:sub(content_size + 1) 230 | return to_server` 231 | end 232 | ``` 233 | 234 | ## Example function 235 | 236 | In this example, we define a function named `d()` and make three GET requests: 237 | * asynchronous with `d()` as a callback, 238 | * synchronous with `d()` as a callback, and 239 | * synchronous without callbacks. 240 | 241 | ```lua 242 | http = require('curl').http() 243 | function d() print('done') end 244 | result=http:async_request('GET','mail.ru',{done=d()}) 245 | result=http:request('GET','mail.ru','',{read_timeout=1,done=d())}) 246 | result=http:get('http://mail.ru/page1') 247 | ``` 248 | 249 | ## Example program 250 | 251 | In this example, we make two requests: 252 | * GET request to `mail.ru` (and print the resulting HTTP code), and 253 | * PUT request to `rbc.ru` (we send JSON data and then print the request body). 254 | 255 | ```lua 256 | #!/usr/bin/env tarantool 257 | local curl = require('curl') 258 | http = curl.http() 259 | res = http:request('GET', 'mail.ru') 260 | print('GET', res.body) 261 | res = http:request('PUT', 'www.rbc.ru', '{data: 123}', 262 |    {headers = {['Content-type'] = 'application/json'}}) 263 | print('PUT', res.body) 264 | ``` 265 | 266 | More examples could be found into a directory tests/*.lua 267 | -------------------------------------------------------------------------------- /cmake/BuildLibCURL.cmake: -------------------------------------------------------------------------------- 1 | macro(build_libcurl) 2 | set(OPENSSL_ARG "") 3 | # Latest versions of Homebrew wont 'link --force' for libraries, that were 4 | # preinstalled in system. So we'll use this dirty hack 5 | if(APPLE) 6 | find_program(HOMEBREW_EXECUTABLE brew) 7 | if(EXISTS ${HOMEBREW_EXECUTABLE}) 8 | execute_process(COMMAND ${HOMEBREW_EXECUTABLE} --prefix 9 | OUTPUT_VARIABLE HOMEBREW_PREFIX 10 | OUTPUT_STRIP_TRAILING_WHITESPACE) 11 | message(STATUS "Detected Homebrew install at ${HOMEBREW_PREFIX}") 12 | 13 | # Detecting OpenSSL 14 | execute_process(COMMAND ${HOMEBREW_EXECUTABLE} --prefix openssl 15 | OUTPUT_VARIABLE HOMEBREW_OPENSSL 16 | OUTPUT_STRIP_TRAILING_WHITESPACE) 17 | if (DEFINED HOMEBREW_OPENSSL) 18 | if (NOT DEFINED OPENSSL_ROOT_DIR) 19 | message(STATUS "Setting OpenSSL root to ${HOMEBREW_OPENSSL}") 20 | set(OPENSSL_ROOT_DIR "${HOMEBREW_OPENSSL}") 21 | endif() 22 | # set(OPENSSL_ARG " -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR}") 23 | elseif(NOT DEFINED OPENSSL_ROOT_DIR) 24 | message(WARNING "Homebrew's OpenSSL isn't installed. Work isn't " 25 | "guarenteed if built with system OpenSSL") 26 | endif() 27 | endif() 28 | endif() 29 | 30 | set(LIBCURL "${CMAKE_CURRENT_BINARY_DIR}/third_party/curl-out") 31 | 32 | ExternalProject_Add(libcurl_project 33 | PREFIX "${CMAKE_CURRENT_BINARY_DIR}/third_party/.curl.tmp" 34 | SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/curl" 35 | CMAKE_ARGS -DCURL_STATICLIB=ON 36 | -DHTTP_ONLY=TRUE 37 | -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} 38 | -DCMAKE_INSTALL_PREFIX=${LIBCURL} 39 | -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} 40 | -DBUILD_CURL_EXE=OFF 41 | -DBUILD_TESTING=OFF 42 | -DENABLE_ARES=ON 43 | -DCMAKE_POSITION_INDEPENDENT_CODE=ON) 44 | 45 | add_library(libcurl STATIC IMPORTED) 46 | set_target_properties(libcurl PROPERTIES IMPORTED_LOCATION "${LIBCURL}/lib/libcurl.a") 47 | add_dependencies(libcurl libcurl_project) 48 | 49 | find_package(CARES REQUIRED) 50 | 51 | # finally, set paths 52 | set(CURL_LIBRARIES libcurl ${CARES_LIBRARY}) 53 | set(CURL_INCLUDE_DIRS "${LIBCURL}/include") 54 | endmacro() 55 | 56 | macro(build_libcurl_if_needed) 57 | if(WITH_SYSTEM_CURL) 58 | find_package(CURL) 59 | if (NOT CURL_FOUND) 60 | message(STATUS "Failed to find pre-installed libCURL") 61 | endif() 62 | endif() 63 | 64 | if (NOT CURL_FOUND) 65 | message(STATUS "Using bundled libCURL") 66 | build_libcurl() 67 | endif() 68 | endmacro() 69 | -------------------------------------------------------------------------------- /cmake/FindCARES.cmake: -------------------------------------------------------------------------------- 1 | # - Find c-ares 2 | # Find the c-ares includes and library 3 | # This module defines 4 | # CARES_INCLUDE_DIR, where to find ares.h, etc. 5 | # CARES_LIBRARIES, the libraries needed to use c-ares. 6 | # CARES_FOUND, If false, do not try to use c-ares. 7 | # also defined, but not for general use are 8 | # CARES_LIBRARY, where to find the c-ares library. 9 | 10 | # find_path(CARES_INCLUDE_DIR ares.h 11 | # HINTS ENV ) 12 | 13 | FIND_PATH(CARES_INCLUDE_DIR ares.h 14 | /usr/local/include 15 | /usr/include 16 | ) 17 | 18 | SET(CARES_NAMES ${CARES_NAMES} cares) 19 | FIND_LIBRARY(CARES_LIBRARY 20 | NAMES ${CARES_NAMES} 21 | PATHS /usr/lib /usr/local/lib 22 | ) 23 | 24 | IF (CARES_LIBRARY AND CARES_INCLUDE_DIR) 25 | SET(CARES_LIBRARIES ${CARES_LIBRARY}) 26 | SET(CARES_FOUND "YES") 27 | ELSE (CARES_LIBRARY AND CARES_INCLUDE_DIR) 28 | SET(CARES_FOUND "NO") 29 | ENDIF (CARES_LIBRARY AND CARES_INCLUDE_DIR) 30 | 31 | 32 | IF (CARES_FOUND) 33 | IF (NOT CARES_FIND_QUIETLY) 34 | MESSAGE(STATUS "Found c-ares: ${CARES_LIBRARIES}") 35 | ENDIF (NOT CARES_FIND_QUIETLY) 36 | ELSE (CARES_FOUND) 37 | IF (CARES_FIND_REQUIRED) 38 | MESSAGE(FATAL_ERROR "Could not find c-ares library") 39 | ENDIF (CARES_FIND_REQUIRED) 40 | ENDIF (CARES_FOUND) 41 | 42 | MARK_AS_ADVANCED( 43 | CARES_FOUND 44 | CARES_LIBRARY 45 | CARES_INCLUDE_DIR 46 | ) 47 | -------------------------------------------------------------------------------- /cmake/FindLibEV.cmake: -------------------------------------------------------------------------------- 1 | find_path(LIBEV_INCLUDE_DIR NAMES ev.h 2 | HINTS /usr/include/libev /usr/local/Cellar/libev/4.24/include ) 3 | find_library(LIBEV_LIBRARIES NAMES ev 4 | HINTS /usr/lib /usr/lib64 /usr/local/Cellar/libev/4.24/libs ) 5 | 6 | if(LIBEV_INCLUDE_DIR AND LIBEV_LIBRARIES) 7 | set(LIBEV_FOUND ON) 8 | endif(LIBEV_INCLUDE_DIR AND LIBEV_LIBRARIES) 9 | 10 | if(LIBEV_FOUND) 11 | if (NOT LIBEV_FIND_QUIETLY) 12 | message(STATUS "Found libev includes: ${LIBEV_INCLUDE_DIR}/ev.h") 13 | message(STATUS "Found libev library: ${LIBEV_LIBRARIES}") 14 | endif (NOT LIBEV_FIND_QUIETLY) 15 | else(LIBEV_FOUND) 16 | if (LIBEV_FIND_REQUIRED) 17 | message(FATAL_ERROR "Could not find libev development files") 18 | endif (LIBEV_FIND_REQUIRED) 19 | endif (LIBEV_FOUND) 20 | -------------------------------------------------------------------------------- /cmake/FindTarantool.cmake: -------------------------------------------------------------------------------- 1 | # Define GNU standard installation directories 2 | include(GNUInstallDirs) 3 | 4 | macro(extract_definition name output input) 5 | string(REGEX MATCH "#define[\t ]+${name}[\t ]+\"([^\"]*)\"" 6 | _t "${input}") 7 | string(REGEX REPLACE "#define[\t ]+${name}[\t ]+\"(.*)\"" "\\1" 8 | ${output} "${_t}") 9 | endmacro() 10 | 11 | find_path(TARANTOOL_INCLUDE_DIR tarantool/module.h 12 | HINTS ENV TARANTOOL_DIR /usr/local/include 13 | ) 14 | 15 | if(TARANTOOL_INCLUDE_DIR) 16 | set(_config "-") 17 | file(READ "${TARANTOOL_INCLUDE_DIR}/tarantool/module.h" _config0) 18 | string(REPLACE "\\" "\\\\" _config ${_config0}) 19 | unset(_config0) 20 | extract_definition(PACKAGE_VERSION TARANTOOL_VERSION ${_config}) 21 | extract_definition(INSTALL_PREFIX _install_prefix ${_config}) 22 | unset(_config) 23 | endif() 24 | 25 | include(FindPackageHandleStandardArgs) 26 | find_package_handle_standard_args(TARANTOOL 27 | REQUIRED_VARS TARANTOOL_INCLUDE_DIR VERSION_VAR TARANTOOL_VERSION) 28 | if(TARANTOOL_FOUND) 29 | set(TARANTOOL_INCLUDE_DIRS "${TARANTOOL_INCLUDE_DIR}" 30 | "${TARANTOOL_INCLUDE_DIR}/tarantool/" 31 | CACHE PATH "Include directories for Tarantool") 32 | set(TARANTOOL_INSTALL_LIBDIR "${CMAKE_INSTALL_LIBDIR}/tarantool" 33 | CACHE PATH "Directory for storing Lua modules written in Lua") 34 | set(TARANTOOL_INSTALL_LUADIR "${CMAKE_INSTALL_DATADIR}/tarantool" 35 | CACHE PATH "Directory for storing Lua modules written in C") 36 | 37 | if (NOT TARANTOOL_FIND_QUIETLY AND NOT FIND_TARANTOOL_DETAILS) 38 | set(FIND_TARANTOOL_DETAILS ON CACHE INTERNAL "Details about TARANTOOL") 39 | message(STATUS "Tarantool LUADIR is ${TARANTOOL_INSTALL_LUADIR}") 40 | message(STATUS "Tarantool LIBDIR is ${TARANTOOL_INSTALL_LIBDIR}") 41 | endif () 42 | endif() 43 | mark_as_advanced(TARANTOOL_INCLUDE_DIRS TARANTOOL_INSTALL_LIBDIR 44 | TARANTOOL_INSTALL_LUADIR) 45 | -------------------------------------------------------------------------------- /curl/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Driver 2 | 3 | add_library(driver SHARED curl_wrapper.c 4 | request_pool.c 5 | driver.c ) 6 | 7 | if (APPLE) 8 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -undefined suppress -flat_namespace -rdynamic") 9 | endif(APPLE) 10 | 11 | target_link_libraries(driver ${CURL_LIBRARIES} ${LIBEV_LIBRARIES}) 12 | 13 | set_target_properties(driver PROPERTIES PREFIX "" OUTPUT_NAME "driver") 14 | 15 | install(TARGETS driver LIBRARY DESTINATION ${TARANTOOL_INSTALL_LIBDIR}/curl) 16 | install(FILES init.lua DESTINATION ${TARANTOOL_INSTALL_LUADIR}/curl) 17 | -------------------------------------------------------------------------------- /curl/curl_wrapper.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 - 2017 Tarantool AUTHORS: please see AUTHORS file. 3 | * 4 | * Redistribution and use in source and binary forms, with or 5 | * without modification, are permitted provided that the following 6 | * conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above 9 | * copyright notice, this list of conditions and the 10 | * following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above 13 | * copyright notice, this list of conditions and the following 14 | * disclaimer in the documentation and/or other materials 15 | * provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 25 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 28 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | * SUCH DAMAGE. 30 | */ 31 | 32 | #include "debug.h" 33 | #include "curl_wrapper.h" 34 | 35 | #include 36 | #include 37 | 38 | /** Information associated with a specific socket 39 | */ 40 | typedef struct { 41 | CURL *easy; 42 | curl_ctx_t *curl_ctx; 43 | struct ev_io ev; 44 | 45 | curl_socket_t sockfd; 46 | 47 | int action; 48 | long timeout; 49 | int evset; 50 | } sock_t; 51 | 52 | 53 | #define is_mcode_good(mcode) is_mcode_good_(__FUNCTION__, (mcode)) 54 | static void timer_cb(EV_P_ struct ev_timer *w, int revents); 55 | 56 | 57 | static inline 58 | bool 59 | is_mcode_good_(const char *where __attribute__((unused)), 60 | CURLMcode code) 61 | { 62 | if (code == CURLM_OK) 63 | return true; 64 | 65 | #if defined (MY_DEBUG) 66 | const char *s; 67 | 68 | switch(code) { 69 | case CURLM_BAD_HANDLE: 70 | s = "CURLM_BAD_HANDLE"; 71 | break; 72 | case CURLM_BAD_EASY_HANDLE: 73 | s = "CURLM_BAD_EASY_HANDLE"; 74 | break; 75 | case CURLM_OUT_OF_MEMORY: 76 | s = "CURLM_OUT_OF_MEMORY"; 77 | break; 78 | case CURLM_INTERNAL_ERROR: 79 | s = "CURLM_INTERNAL_ERROR"; 80 | break; 81 | case CURLM_UNKNOWN_OPTION: 82 | s = "CURLM_UNKNOWN_OPTION"; 83 | break; 84 | case CURLM_LAST: 85 | s = "CURLM_LAST"; 86 | break; 87 | default: 88 | s = "CURLM_unknown"; 89 | break; 90 | case CURLM_BAD_SOCKET: 91 | s = "CURLM_BAD_SOCKET"; 92 | /* ignore this error */ 93 | return true; 94 | } 95 | 96 | dd("ERROR: %s returns = %s", where, s); 97 | #else /* MY_DEBUG */ 98 | if (code == CURLM_BAD_SOCKET) 99 | return true; 100 | #endif 101 | 102 | return false; 103 | } 104 | 105 | 106 | /** Update the event timer after curl_multi library calls 107 | */ 108 | static 109 | int 110 | multi_timer_cb(CURLM *multi __attribute__((unused)), 111 | long timeout_ms, 112 | void *ctx) 113 | { 114 | dd("timeout_ms = %li", timeout_ms); 115 | 116 | curl_ctx_t *l = (curl_ctx_t *) ctx; 117 | 118 | ev_timer_stop(l->loop, &l->timer_event); 119 | if (timeout_ms > 0) { 120 | ev_timer_init(&l->timer_event, timer_cb, 121 | (double) (timeout_ms / 1000), 0.); 122 | ev_timer_start(l->loop, &l->timer_event); 123 | } 124 | else if (timeout_ms == 0) { 125 | ev_set_cb(&l->timer_event, timer_cb); 126 | ev_feed_event(l->loop, &l->timer_event, 0); 127 | } 128 | 129 | return 0; 130 | } 131 | 132 | 133 | /** Check for completed transfers, and remove their easy handles 134 | */ 135 | static 136 | void 137 | check_multi_info(curl_ctx_t *l) 138 | { 139 | char *eff_url; 140 | CURLMsg *msg; 141 | int msgs_left; 142 | request_t *r; 143 | long http_code; 144 | 145 | dd("REMAINING: still_running = %d", l->still_running); 146 | 147 | while ((msg = curl_multi_info_read(l->multi, &msgs_left))) { 148 | 149 | if (msg->msg != CURLMSG_DONE) 150 | continue; 151 | 152 | CURL *easy = msg->easy_handle; 153 | CURLcode curl_code = msg->data.result; 154 | 155 | curl_easy_getinfo(easy, CURLINFO_PRIVATE, (void *) &r); 156 | curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &eff_url); 157 | curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &http_code); 158 | 159 | dd("DONE: url = %s, curl_code = %d, http_code = %d", 160 | eff_url, curl_code, (int) http_code); 161 | 162 | if (curl_code != CURLE_OK) 163 | ++l->stat.failed_requests; 164 | 165 | if (http_code == 200) 166 | ++l->stat.http_200_responses; 167 | else 168 | ++l->stat.http_other_responses; 169 | 170 | if (r->lua_ctx.done_fn != LUA_REFNIL) { 171 | /* 172 | Signature: 173 | function (curl_code, http_code, error_message, ctx) 174 | */ 175 | lua_rawgeti(r->lua_ctx.L, LUA_REGISTRYINDEX, r->lua_ctx.done_fn); 176 | lua_pushinteger(r->lua_ctx.L, (int) curl_code); 177 | lua_pushinteger(r->lua_ctx.L, (int) http_code); 178 | lua_pushstring(r->lua_ctx.L, curl_easy_strerror(curl_code)); 179 | lua_rawgeti(r->lua_ctx.L, LUA_REGISTRYINDEX, r->lua_ctx.fn_ctx); 180 | lua_pcall(r->lua_ctx.L, 4, 0 ,0); 181 | } 182 | 183 | free_request(l, r); 184 | } /* while */ 185 | } 186 | 187 | 188 | /** Called by libevent when we get action on a multi socket 189 | */ 190 | static 191 | void 192 | event_cb(EV_P_ struct ev_io *w, int revents) 193 | { 194 | (void) loop; 195 | 196 | dd("w = %p, revents = %d", (void *) w, revents); 197 | 198 | curl_ctx_t *l = (curl_ctx_t*) w->data; 199 | 200 | const int action = ( (revents & EV_READ ? CURL_POLL_IN : 0) | 201 | (revents & EV_WRITE ? CURL_POLL_OUT : 0) ); 202 | CURLMcode rc = curl_multi_socket_action(l->multi, 203 | w->fd, action, &l->still_running); 204 | if (!is_mcode_good(rc)) 205 | ++l->stat.failed_requests; 206 | 207 | check_multi_info(l); 208 | 209 | if (l->still_running <= 0) { 210 | dd("last transfer done, kill timeout"); 211 | ev_timer_stop(l->loop, &l->timer_event); 212 | } 213 | } 214 | 215 | 216 | /** Called by libevent when our timeout expires 217 | */ 218 | static 219 | void 220 | timer_cb(EV_P_ struct ev_timer *w, int revents __attribute__((unused))) 221 | { 222 | (void) loop; 223 | 224 | dd("w = %p, revents = %i", (void *) w, revents); 225 | 226 | curl_ctx_t *l = (curl_ctx_t *) w->data; 227 | CURLMcode rc = curl_multi_socket_action(l->multi, CURL_SOCKET_TIMEOUT, 0, 228 | &l->still_running); 229 | if (!is_mcode_good(rc)) 230 | ++l->stat.failed_requests; 231 | 232 | check_multi_info(l); 233 | } 234 | 235 | /** Clean up the sock_t structure 236 | */ 237 | static inline 238 | void 239 | remsock(sock_t *f, curl_ctx_t *l) 240 | { 241 | dd("removing socket"); 242 | 243 | if (f == NULL) 244 | return; 245 | 246 | if (f->evset) 247 | ev_io_stop(l->loop, &f->ev); 248 | 249 | ++l->stat.sockets_deleted; 250 | 251 | free(f); 252 | } 253 | 254 | 255 | /** Assign information to a sock_t structure 256 | */ 257 | static inline 258 | void 259 | setsock(sock_t *f, 260 | curl_socket_t s, 261 | CURL *e, 262 | int act, 263 | curl_ctx_t *l) 264 | { 265 | dd("set new socket"); 266 | 267 | const int kind = ( (act & CURL_POLL_IN ? EV_READ : 0) | 268 | (act & CURL_POLL_OUT ? EV_WRITE : 0) ); 269 | 270 | f->sockfd = s; 271 | f->action = act; 272 | f->easy = e; 273 | f->ev.data = l; 274 | f->evset = 1; 275 | 276 | if (f->evset) 277 | ev_io_stop(l->loop, &f->ev); 278 | 279 | ev_io_init(&f->ev, event_cb, f->sockfd, kind); 280 | ev_io_start(l->loop, &f->ev); 281 | } 282 | 283 | 284 | /** Initialize a new sock_t structure 285 | */ 286 | static 287 | bool 288 | addsock(curl_socket_t s, CURL *easy, int action, curl_ctx_t *l) 289 | { 290 | sock_t *fdp = (sock_t *) malloc(sizeof(sock_t)); 291 | if (fdp == NULL) 292 | return false; 293 | 294 | memset(fdp, 0, sizeof(sock_t)); 295 | 296 | fdp->curl_ctx = l; 297 | 298 | setsock(fdp, s, easy, action, l); 299 | 300 | curl_multi_assign(l->multi, s, fdp); 301 | 302 | ++fdp->curl_ctx->stat.sockets_added; 303 | 304 | return true; 305 | } 306 | 307 | 308 | /* CURLMOPT_SOCKETFUNCTION */ 309 | static 310 | int 311 | sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp) 312 | { 313 | curl_ctx_t *l = (curl_ctx_t*) cbp; 314 | sock_t *fdp = (sock_t*) sockp; 315 | 316 | #if defined(MY_DEBUG) 317 | static const char *whatstr[] = { 318 | "none", "IN", "OUT", "INOUT", "REMOVE" }; 319 | #endif /* MY_DEBUG */ 320 | 321 | dd("e = %p, s = %i, what = %s, cbp = %p, sockp = %p", 322 | e, s, whatstr[what], cbp, sockp); 323 | 324 | if (what == CURL_POLL_REMOVE) { 325 | remsock(fdp, l); 326 | } else { 327 | if (fdp == NULL) { 328 | if (!addsock(s, e, what, l)) 329 | return 1; 330 | } 331 | else { 332 | dd("Changing action from = %s, to = %s", 333 | whatstr[fdp->action], whatstr[what]); 334 | setsock(fdp, s, e, what, l); 335 | } 336 | } 337 | 338 | return 0; 339 | } 340 | 341 | 342 | /** CURLOPT_WRITEFUNCTION / CURLOPT_READFUNCTION 343 | */ 344 | static 345 | size_t 346 | read_cb(void *ptr, size_t size, size_t nmemb, void *ctx) 347 | { 348 | dd("size = %zu, nmemb = %zu", size, nmemb); 349 | 350 | request_t *r = (request_t *) ctx; 351 | const size_t total_size = size * nmemb; 352 | 353 | if (r->lua_ctx.read_fn == LUA_REFNIL) 354 | return total_size; 355 | 356 | lua_rawgeti(r->lua_ctx.L, LUA_REGISTRYINDEX, r->lua_ctx.read_fn); 357 | lua_pushnumber(r->lua_ctx.L, total_size); 358 | lua_rawgeti(r->lua_ctx.L, LUA_REGISTRYINDEX, r->lua_ctx.fn_ctx); 359 | lua_pcall(r->lua_ctx.L, 2, 1, 0); 360 | 361 | size_t readen; 362 | const char *data = lua_tolstring(r->lua_ctx.L, 363 | lua_gettop(r->lua_ctx.L), &readen); 364 | memcpy(ptr, data, readen); 365 | lua_pop(r->lua_ctx.L, 1); 366 | 367 | return readen; 368 | } 369 | 370 | 371 | static 372 | size_t 373 | write_cb(void *ptr, size_t size, size_t nmemb, void *ctx) 374 | { 375 | dd("size = %zu, nmemb = %zu", size, nmemb); 376 | 377 | request_t *r = (request_t *) ctx; 378 | const size_t bytes = size * nmemb; 379 | 380 | if (r->lua_ctx.write_fn == LUA_REFNIL) 381 | return bytes; 382 | 383 | lua_rawgeti(r->lua_ctx.L, LUA_REGISTRYINDEX, r->lua_ctx.write_fn); 384 | lua_pushlstring(r->lua_ctx.L, (const char *) ptr, bytes); 385 | lua_rawgeti(r->lua_ctx.L, LUA_REGISTRYINDEX, r->lua_ctx.fn_ctx); 386 | lua_pcall(r->lua_ctx.L, 2, 1, 0); 387 | const size_t written = lua_tointeger(r->lua_ctx.L, 388 | lua_gettop(r->lua_ctx.L)); 389 | lua_pop(r->lua_ctx.L, 1); 390 | 391 | return written; 392 | } 393 | 394 | 395 | CURLMcode 396 | request_start(request_t *r, const request_start_args_t *a) 397 | { 398 | assert(r); 399 | assert(a); 400 | assert(r->easy); 401 | assert(r->curl_ctx); 402 | 403 | if (a->max_conns > 0) 404 | curl_easy_setopt(r->easy, CURLOPT_MAXCONNECTS, a->max_conns); 405 | 406 | if (a->keepalive_idle > 0 && a->keepalive_interval > 0) { 407 | 408 | curl_easy_setopt(r->easy, CURLOPT_TCP_KEEPALIVE, 1L); 409 | curl_easy_setopt(r->easy, CURLOPT_TCP_KEEPIDLE, a->keepalive_idle); 410 | curl_easy_setopt(r->easy, CURLOPT_TCP_KEEPINTVL, 411 | a->keepalive_interval); 412 | if (!request_add_header(r, "Connection: Keep-Alive") && 413 | !request_add_header_keepaive(r, a)) 414 | { 415 | ++r->curl_ctx->stat.failed_requests; 416 | return CURLM_OUT_OF_MEMORY; 417 | } 418 | } else { 419 | if (!request_add_header(r, "Connection: close")) { 420 | ++r->curl_ctx->stat.failed_requests; 421 | return CURLM_OUT_OF_MEMORY; 422 | } 423 | } 424 | 425 | if (a->read_timeout > 0) 426 | curl_easy_setopt(r->easy, CURLOPT_TIMEOUT_MS, a->read_timeout); 427 | 428 | if (a->connect_timeout > 0) 429 | curl_easy_setopt(r->easy, CURLOPT_CONNECTTIMEOUT_MS, a->connect_timeout); 430 | 431 | if (a->dns_cache_timeout > 0) 432 | curl_easy_setopt(r->easy, CURLOPT_DNS_CACHE_TIMEOUT, 433 | a->dns_cache_timeout); 434 | 435 | if (a->curl_verbose) 436 | curl_easy_setopt(r->easy, CURLOPT_VERBOSE, 1L); 437 | 438 | curl_easy_setopt(r->easy, CURLOPT_PRIVATE, (void *) r); 439 | 440 | curl_easy_setopt(r->easy, CURLOPT_READFUNCTION, read_cb); 441 | curl_easy_setopt(r->easy, CURLOPT_READDATA, (void *) r); 442 | 443 | curl_easy_setopt(r->easy, CURLOPT_WRITEFUNCTION, write_cb); 444 | curl_easy_setopt(r->easy, CURLOPT_WRITEDATA, (void *) r); 445 | 446 | curl_easy_setopt(r->easy, CURLOPT_NOPROGRESS, 1L); 447 | 448 | curl_easy_setopt(r->easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 449 | 450 | if (a->low_speed_time > 0) 451 | curl_easy_setopt(r->easy, CURLOPT_LOW_SPEED_TIME, a->low_speed_time); 452 | 453 | if (a->low_speed_limit > 0) 454 | curl_easy_setopt(r->easy, CURLOPT_LOW_SPEED_LIMIT, a->low_speed_limit); 455 | 456 | /* Headers have to seted right before add_handle() */ 457 | if (r->headers != NULL) 458 | curl_easy_setopt(r->easy, CURLOPT_HTTPHEADER, r->headers); 459 | 460 | ++r->curl_ctx->stat.total_requests; 461 | 462 | CURLMcode rc = curl_multi_add_handle(r->curl_ctx->multi, r->easy); 463 | if (!is_mcode_good(rc)) { 464 | ++r->curl_ctx->stat.failed_requests; 465 | return rc; 466 | } 467 | 468 | return rc; 469 | } 470 | 471 | 472 | #if defined (MY_DEBUG) 473 | request_t* 474 | new_request_test(curl_ctx_t *l, const char *url) 475 | { 476 | request_t *r = new_request(l); 477 | if (r == NULL) 478 | return NULL; 479 | 480 | curl_easy_setopt(r->easy, CURLOPT_URL, url); 481 | 482 | request_start_args_t a; 483 | request_start_args_init(&a); 484 | 485 | a.keepalive_interval = 60; 486 | a.keepalive_idle = 120; 487 | a.read_timeout = 2; 488 | 489 | if (request_start(r, &a) != CURLM_OK) 490 | goto error_exit; 491 | 492 | return r; 493 | 494 | error_exit: 495 | free_request(l, r); 496 | return NULL; 497 | } 498 | #endif /* MY_DEBUG */ 499 | 500 | 501 | curl_ctx_t* 502 | curl_ctx_new(const curl_args_t *a) 503 | { 504 | assert(a); 505 | 506 | curl_ctx_t *l = (curl_ctx_t *) malloc(sizeof(curl_ctx_t)); 507 | if (l == NULL) 508 | return NULL; 509 | 510 | memset(l, 0, sizeof(curl_ctx_t)); 511 | 512 | if (!request_pool_new(&l->cpool, l, a->pool_size)) 513 | goto error_exit; 514 | 515 | l->loop = ev_loop_new(0); 516 | if (l->loop == NULL) 517 | goto error_exit; 518 | 519 | l->multi = curl_multi_init(); 520 | if (l->multi == NULL) 521 | goto error_exit; 522 | 523 | ev_timer_init(&l->timer_event, timer_cb, 0., 0.); 524 | l->timer_event.data = (void *) l; 525 | 526 | curl_multi_setopt(l->multi, CURLMOPT_SOCKETFUNCTION, sock_cb); 527 | curl_multi_setopt(l->multi, CURLMOPT_SOCKETDATA, (void *) l); 528 | 529 | curl_multi_setopt(l->multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb); 530 | curl_multi_setopt(l->multi, CURLMOPT_TIMERDATA, (void *) l); 531 | 532 | if (a->pipeline) 533 | curl_multi_setopt(l->multi, CURLMOPT_PIPELINING, 1L /* pipline on */); 534 | 535 | if (a->max_conns > 0) 536 | curl_multi_setopt(l->multi, CURLMOPT_MAXCONNECTS, a->max_conns); 537 | 538 | return l; 539 | 540 | error_exit: 541 | curl_destroy(l); 542 | return NULL; 543 | } 544 | 545 | 546 | void 547 | curl_destroy(curl_ctx_t *l) 548 | { 549 | if (l == NULL) 550 | return; 551 | 552 | if (l->multi != NULL) 553 | curl_multi_cleanup(l->multi); 554 | 555 | if (l->loop) 556 | ev_loop_destroy(l->loop); 557 | 558 | request_pool_free(&l->cpool); 559 | 560 | free(l); 561 | } 562 | 563 | 564 | void 565 | curl_poll_one(curl_ctx_t *l) 566 | { 567 | if (l == NULL) 568 | return; 569 | 570 | assert(l->loop); 571 | 572 | /* 573 | We don't call any curl_multi_socket*() 574 | function yet as we have no handles added! 575 | */ 576 | 577 | ev_loop(l->loop, EVRUN_NOWAIT); 578 | 579 | ++l->stat.loop_calls; 580 | } 581 | 582 | 583 | void 584 | curl_print_stat(curl_ctx_t *l, FILE* out) 585 | { 586 | if (l == NULL) 587 | return; 588 | 589 | fprintf(out, "active_requests = %zu, sockets_added = %zu, " 590 | "sockets_deleted = %zu, loop_calls = %zu, " 591 | "total_requests = %llu, failed_requests = %llu, " 592 | "http_200_responses = %llu, http_other_responses = %llu" 593 | "\n", 594 | l->stat.active_requests, 595 | l->stat.sockets_added, 596 | l->stat.sockets_deleted, 597 | l->stat.loop_calls, 598 | (unsigned long long) l->stat.total_requests, 599 | (unsigned long long) l->stat.failed_requests, 600 | (unsigned long long) l->stat.http_200_responses, 601 | (unsigned long long) l->stat.http_other_responses 602 | ); 603 | } 604 | 605 | 606 | void 607 | request_start_args_print(const request_start_args_t *a, FILE *out) 608 | { 609 | fprintf(out, "max_conns = %d, keepalive_idle = %d, keepalive_interval = %d, " 610 | "low_speed_time = %d, low_speed_limit = %d, curl_verbose = %d" 611 | "\n", 612 | (int) a->max_conns, 613 | (int) a->keepalive_idle, 614 | (int) a->keepalive_interval, 615 | (int) a->low_speed_time, 616 | (int) a->low_speed_limit, 617 | (int) a->curl_verbose ); 618 | } 619 | -------------------------------------------------------------------------------- /curl/curl_wrapper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 - 2017 Tarantool AUTHORS: please see AUTHORS file. 3 | * 4 | * Redistribution and use in source and binary forms, with or 5 | * without modification, are permitted provided that the following 6 | * conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above 9 | * copyright notice, this list of conditions and the 10 | * following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above 13 | * copyright notice, this list of conditions and the following 14 | * disclaimer in the documentation and/or other materials 15 | * provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 25 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 28 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | * SUCH DAMAGE. 30 | */ 31 | 32 | #ifndef CURL_WRAPPER_H_INCLUDED 33 | #define CURL_WRAPPER_H_INCLUDED 1 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include 41 | #include 42 | 43 | #include 44 | #include 45 | #include 46 | 47 | #include "request_pool.h" 48 | 49 | /** curl_ctx information, common to all requestections 50 | */ 51 | typedef struct curl_ctx_s curl_ctx_t; 52 | 53 | struct curl_ctx_s { 54 | 55 | struct ev_loop *loop; 56 | struct ev_timer timer_event; 57 | 58 | request_pool_t cpool; 59 | 60 | CURLM *multi; 61 | int still_running; 62 | 63 | /* Various values of statistics, it are used only for all 64 | * requestection in curl context */ 65 | struct { 66 | uint64_t total_requests; 67 | uint64_t http_200_responses; 68 | uint64_t http_other_responses; 69 | size_t failed_requests; 70 | size_t active_requests; 71 | size_t sockets_added; 72 | size_t sockets_deleted; 73 | size_t loop_calls; 74 | } stat; 75 | 76 | }; 77 | 78 | 79 | typedef struct { 80 | 81 | /* Max amount of cached alive requestections */ 82 | long max_conns; 83 | 84 | /* Non-universal keepalive knobs (Linux, AIX, HP-UX, more) */ 85 | long keepalive_idle; 86 | long keepalive_interval; 87 | 88 | /* Set the "low speed limit & time" 89 | If the download receives less than "low speed limit" bytes/second during 90 | "low speed time" seconds, the operations is aborted. You could i.e if you 91 | have a pretty high speed requestection, abort if it is less than 2000 92 | bytes/sec during 20 seconds; 93 | */ 94 | long low_speed_time; 95 | long low_speed_limit; 96 | 97 | /* Time-out the read operation after this amount of seconds */ 98 | long read_timeout; 99 | 100 | /* Time-out connects operations after this amount of seconds, if connects are 101 | OK within this time, then fine... This only aborts the requestect phase. */ 102 | long connect_timeout; 103 | 104 | /* DNS cache timeout */ 105 | long dns_cache_timeout; 106 | 107 | /* Enable/Disable curl verbose mode */ 108 | bool curl_verbose; 109 | } request_start_args_t; 110 | 111 | 112 | typedef struct { 113 | /* Set to true to enable pipelining for this multi handle */ 114 | bool pipeline; 115 | 116 | /* Maximum number of entries in the requestection cache */ 117 | long max_conns; 118 | 119 | size_t pool_size; 120 | } curl_args_t; 121 | 122 | 123 | /** Curl context API {{{ 124 | */ 125 | curl_ctx_t* curl_ctx_new(const curl_args_t *a); 126 | void curl_destroy(curl_ctx_t *l); /* curl_free exists! */ 127 | void curl_poll_one(curl_ctx_t *l); 128 | void curl_print_stat(curl_ctx_t *l, FILE* out); 129 | 130 | static inline 131 | curl_ctx_t* 132 | curl_ctx_new_easy(void) { 133 | const curl_args_t a = { .pipeline = false, 134 | .max_conns = 5, 135 | .pool_size = 1000 }; 136 | return curl_ctx_new(&a); 137 | } 138 | /* }}} */ 139 | 140 | /** request API {{{ 141 | */ 142 | static inline request_t *new_request(curl_ctx_t *ctx) { 143 | return request_pool_get_request(&ctx->cpool); 144 | } 145 | 146 | static inline void free_request(curl_ctx_t *ctx, request_t *r) { 147 | request_pool_free_request(&ctx->cpool, r); 148 | } 149 | 150 | CURLMcode request_start(request_t *c, const request_start_args_t *a); 151 | 152 | #if defined (MY_DEBUG) 153 | request_t* new_request_test(curl_ctx_t *l, const char *url); 154 | #endif /* MY_DEBUG */ 155 | 156 | static inline 157 | bool 158 | request_add_header(request_t *c, const char *http_header) 159 | { 160 | assert(c); 161 | assert(http_header); 162 | struct curl_slist *l = curl_slist_append(c->headers, http_header); 163 | if (l == NULL) 164 | return false; 165 | c->headers = l; 166 | return true; 167 | } 168 | 169 | static inline 170 | bool 171 | request_add_header_keepaive(request_t *c, const request_start_args_t *a) 172 | { 173 | static char buf[255]; 174 | 175 | assert(c); 176 | assert(a); 177 | 178 | snprintf(buf, sizeof(buf) - 1, "Keep-Alive: timeout=%d", 179 | (int) a->keepalive_idle); 180 | 181 | struct curl_slist *l = curl_slist_append(c->headers, buf); 182 | if (l == NULL) 183 | return false; 184 | 185 | c->headers = l; 186 | return true; 187 | } 188 | 189 | static inline 190 | bool 191 | request_set_post(request_t *c) 192 | { 193 | assert(c); 194 | assert(c->easy); 195 | if (!request_add_header(c, "Accept: */*")) 196 | return false; 197 | curl_easy_setopt(c->easy, CURLOPT_POST, 1L); 198 | return true; 199 | } 200 | 201 | static inline 202 | bool 203 | request_set_put(request_t *c) 204 | { 205 | assert(c); 206 | assert(c->easy); 207 | if (!request_add_header(c, "Accept: */*")) 208 | return false; 209 | curl_easy_setopt(c->easy, CURLOPT_UPLOAD, 1L); 210 | return true; 211 | } 212 | 213 | 214 | static inline 215 | void 216 | request_start_args_init(request_start_args_t *a) 217 | { 218 | assert(a); 219 | a->max_conns = -1; 220 | a->keepalive_idle = -1; 221 | a->keepalive_interval = -1; 222 | a->low_speed_time = -1; 223 | a->low_speed_limit = -1; 224 | a->read_timeout = -1; 225 | a->connect_timeout = -1; 226 | a->dns_cache_timeout = -1; 227 | a->curl_verbose = false; 228 | } 229 | 230 | void request_start_args_print(const request_start_args_t *a, FILE *out); 231 | /* }}} */ 232 | 233 | #endif /* CURL_WRAPPER_H_INCLUDED */ 234 | -------------------------------------------------------------------------------- /curl/debug.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2017 Tarantool AUTHORS: please see AUTHORS file. 3 | * 4 | * Redistribution and use in source and binary forms, with or 5 | * without modification, are permitted provided that the following 6 | * conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above 9 | * copyright notice, this list of conditions and the 10 | * following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above 13 | * copyright notice, this list of conditions and the following 14 | * disclaimer in the documentation and/or other materials 15 | * provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY AUTHORS ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 | * AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 25 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 28 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | * SUCH DAMAGE. 30 | * 31 | * Copyright (C) 2016-2017 Tarantool AUTHORS: 32 | * please see AUTHORS file. 33 | */ 34 | 35 | #ifndef DEBUG_UTILS_H_INCLUDED 36 | #define DEBUG_UTILS_H_INCLUDED 1 37 | 38 | #include 39 | #include 40 | 41 | #if defined(MY_DEBUG) 42 | 43 | # define dd(...) do { \ 44 | fprintf(stderr, "tnt *** %s ", __FUNCTION__); \ 45 | fprintf(stderr, __VA_ARGS__); \ 46 | fprintf(stderr, " at %s line %d.\n", __FILE__, __LINE__); \ 47 | } while(0) 48 | 49 | #else 50 | # define dd(...) 51 | #endif /* MY_DEBUG */ 52 | 53 | #endif /* DEBUG_UTILS_H_INCLUDED */ 54 | -------------------------------------------------------------------------------- /curl/driver.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2017 Tarantool AUTHORS: please see AUTHORS file. 3 | * 4 | * Redistribution and use in source and binary forms, with or 5 | * without modification, are permitted provided that the following 6 | * conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above 9 | * copyright notice, this list of conditions and the 10 | * following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above 13 | * copyright notice, this list of conditions and the following 14 | * disclaimer in the documentation and/or other materials 15 | * provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 25 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 28 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | * SUCH DAMAGE. 30 | */ 31 | 32 | #include "driver.h" 33 | 34 | #include 35 | 36 | 37 | static 38 | int 39 | curl_ev_f(va_list ap) 40 | { 41 | lib_ctx_t *ctx = va_arg(ap, lib_ctx_t *); 42 | 43 | fiber_set_cancellable(true); 44 | 45 | for (;;) { 46 | if (ctx->done) 47 | break; 48 | curl_poll_one(ctx->curl_ctx); 49 | fiber_sleep(0.01); 50 | } 51 | 52 | return 0; 53 | } 54 | 55 | 56 | /* 57 | This function does async HTTP request 58 | 59 | Parameters: 60 | 61 | method - HTTP method, like GET, POST, PUT and so on 62 | url - HTTP url, like https://tarantool.org/doc 63 | options - this is a table of options. 64 | 65 | done - name of a callback function which is invoked when a request 66 | was completed; 67 | 68 | write - name of a callback function which is invoked if the 69 | server returns data to the client; 70 | signature is function(data, context) 71 | 72 | read - name of a callback function which is invoked if the 73 | client passes data to the server. 74 | signature is function(content_size, context) 75 | 76 | done - name of a callback function which is invoked when a request 77 | was completed; 78 | signature is function(curl_code, http_code, error_message, ctx) 79 | 80 | ca_path - a path to ssl certificate dir; 81 | 82 | ca_file - a path to ssl certificate file; 83 | 84 | headers - a table of HTTP headers; 85 | 86 | max_conns - max amount of cached alive connections; 87 | 88 | keepalive_idle & keepalive_interval - non-universal keepalive knobs (Linux, AIX, HP-UX, more); 89 | 90 | low_speed_time & low_speed_limit - If the download receives less than "low speed limit" bytes/second 91 | during "low speed time" seconds, the operations is aborted. 92 | You could i.e if you have a pretty high speed connection, abort if 93 | it is less than 2000 bytes/sec during 20 seconds; 94 | 95 | read_timeout - Time-out the read operation after this amount of seconds; 96 | 97 | connect_timeout - Time-out connect operations after this amount of seconds, if connects are; 98 | OK within this time, then fine... This only aborts the connect phase; 99 | 100 | dns_cache_timeout - DNS cache timeout; 101 | 102 | curl_verbose - make libcurl verbose!; 103 | 104 | Returns: 105 | bool, msg or error() 106 | */ 107 | static 108 | int 109 | async_request(lua_State *L) 110 | { 111 | const char *reason = "unknown error"; 112 | 113 | lib_ctx_t *ctx = ctx_get(L); 114 | if (ctx == NULL) 115 | return luaL_error(L, "can't get lib ctx"); 116 | 117 | if (ctx->done) 118 | return luaL_error(L, "curl stopped"); 119 | 120 | request_t *r = new_request(ctx->curl_ctx); 121 | if (r == NULL) 122 | return luaL_error(L, "can't get request obj from pool"); 123 | 124 | request_start_args_t req_args; 125 | request_start_args_init(&req_args); 126 | 127 | const char *method = luaL_checkstring(L, 2); 128 | const char *url = luaL_checkstring(L, 3); 129 | 130 | /** Set Options {{{ 131 | */ 132 | if (lua_istable(L, 4)) { 133 | 134 | const int top = lua_gettop(L); 135 | 136 | r->lua_ctx.L = L; 137 | 138 | /* Read callback */ 139 | lua_pushstring(L, "read"); 140 | lua_gettable(L, 4); 141 | if (lua_isfunction(L, top + 1)) 142 | r->lua_ctx.read_fn = luaL_ref(L, LUA_REGISTRYINDEX); 143 | else 144 | lua_pop(L, 1); 145 | 146 | /* Write callback */ 147 | lua_pushstring(L, "write"); 148 | lua_gettable(L, 4); 149 | if (lua_isfunction(L, top + 1)) 150 | r->lua_ctx.write_fn = luaL_ref(L, LUA_REGISTRYINDEX); 151 | else 152 | lua_pop(L, 1); 153 | 154 | /* Done callback */ 155 | lua_pushstring(L, "done"); 156 | lua_gettable(L, 4); 157 | if (lua_isfunction(L, top + 1)) 158 | r->lua_ctx.done_fn = luaL_ref(L, LUA_REGISTRYINDEX); 159 | else 160 | lua_pop(L, 1); 161 | 162 | /* callback's context */ 163 | lua_pushstring(L, "ctx"); 164 | lua_gettable(L, 4); 165 | r->lua_ctx.fn_ctx = luaL_ref(L, LUA_REGISTRYINDEX); 166 | 167 | /** Http headers */ 168 | lua_pushstring(L, "headers"); 169 | lua_gettable(L, 4); 170 | if (!lua_isnil(L, top + 1)) { 171 | lua_pushnil(L); 172 | char header[4096]; 173 | while (lua_next(L, -2) != 0) { 174 | snprintf(header, sizeof(header) - 1, 175 | "%s: %s", lua_tostring(L, -2), lua_tostring(L, -1)); 176 | if (!request_add_header(r, header)) { 177 | reason = "can't allocate memory (request_add_header)"; 178 | goto error_exit; 179 | } 180 | lua_pop(L, 1); 181 | } // while 182 | } 183 | lua_pop(L, 1); 184 | 185 | /* SSL/TLS cert {{{ */ 186 | lua_pushstring(L, "ca_path"); 187 | lua_gettable(L, 4); 188 | if (!lua_isnil(L, top + 1)) 189 | curl_easy_setopt(r->easy, CURLOPT_CAPATH, 190 | lua_tostring(L, top + 1)); 191 | lua_pop(L, 1); 192 | 193 | lua_pushstring(L, "ca_file"); 194 | lua_gettable(L, 4); 195 | if (!lua_isnil(L, top + 1)) 196 | curl_easy_setopt(r->easy, CURLOPT_CAINFO, 197 | lua_tostring(L, top + 1)); 198 | lua_pop(L, 1); 199 | /* }}} */ 200 | 201 | lua_pushstring(L, "max_conns"); 202 | lua_gettable(L, 4); 203 | if (!lua_isnil(L, top + 1)) 204 | req_args.max_conns = (long) lua_tointeger(L, top + 1); 205 | lua_pop(L, 1); 206 | 207 | lua_pushstring(L, "keepalive_idle"); 208 | lua_gettable(L, 4); 209 | if (!lua_isnil(L, top + 1)) 210 | req_args.keepalive_idle = (long) lua_tointeger(L, top + 1); 211 | lua_pop(L, 1); 212 | 213 | lua_pushstring(L, "keepalive_interval"); 214 | lua_gettable(L, 4); 215 | if (!lua_isnil(L, top + 1)) 216 | req_args.keepalive_interval = (long) lua_tointeger(L, top + 1); 217 | lua_pop(L, 1); 218 | 219 | lua_pushstring(L, "low_speed_limit"); 220 | lua_gettable(L, 4); 221 | if (!lua_isnil(L, top + 1)) 222 | req_args.low_speed_limit = (long) lua_tointeger(L, top + 1); 223 | lua_pop(L, 1); 224 | 225 | lua_pushstring(L, "low_speed_time"); 226 | lua_gettable(L, 4); 227 | if (!lua_isnil(L, top + 1)) 228 | req_args.low_speed_time = (long) lua_tointeger(L, top + 1); 229 | lua_pop(L, 1); 230 | 231 | lua_pushstring(L, "read_timeout"); 232 | lua_gettable(L, 4); 233 | if (!lua_isnil(L, top + 1)) 234 | req_args.read_timeout = (long) floor(lua_tonumber(L, top + 1) * 1000); 235 | lua_pop(L, 1); 236 | 237 | lua_pushstring(L, "connect_timeout"); 238 | lua_gettable(L, 4); 239 | if (!lua_isnil(L, top + 1)) 240 | req_args.connect_timeout = (long) floor(lua_tonumber(L, top + 1) * 1000); 241 | lua_pop(L, 1); 242 | 243 | lua_pushstring(L, "dns_cache_timeout"); 244 | lua_gettable(L, 4); 245 | if (!lua_isnil(L, top + 1)) 246 | req_args.dns_cache_timeout = (long) lua_tointeger(L, top + 1); 247 | lua_pop(L, 1); 248 | 249 | /* Debug- / Internal- options */ 250 | lua_pushstring(L, "curl_verbose"); 251 | lua_gettable(L, 4); 252 | if (!lua_isnil(L, top + 1) && lua_isboolean(L, top + 1)) 253 | req_args.curl_verbose = true; 254 | lua_pop(L, 1); 255 | } else { 256 | reason = "4-arg have to be a table"; 257 | goto error_exit; 258 | } 259 | /* }}} */ 260 | 261 | curl_easy_setopt(r->easy, CURLOPT_PRIVATE, (void *) r); 262 | 263 | curl_easy_setopt(r->easy, CURLOPT_URL, url); 264 | curl_easy_setopt(r->easy, CURLOPT_FOLLOWLOCATION, 1); 265 | 266 | curl_easy_setopt(r->easy, CURLOPT_SSL_VERIFYPEER, 1); 267 | 268 | /* Method {{{ */ 269 | 270 | if (*method == 'G') { 271 | curl_easy_setopt(r->easy, CURLOPT_HTTPGET, 1); 272 | } 273 | else if (strcmp(method, "POST") == 0) { 274 | if (!request_set_post(r)) { 275 | reason = "can't allocate memory (request_set_post)"; 276 | goto error_exit; 277 | } 278 | } 279 | else if (strcmp(method, "PUT") == 0) { 280 | if (!request_set_put(r)) { 281 | reason = "can't allocate memory (request_set_put)"; 282 | goto error_exit; 283 | } 284 | } else { 285 | reason = "method does not supported"; 286 | goto error_exit; 287 | } 288 | /* }}} */ 289 | 290 | /* Note that the add_handle() will set a 291 | * time-out to trigger very soon so that 292 | * the necessary socket_action() call will be 293 | * called by this app */ 294 | CURLMcode rc = request_start(r, &req_args); 295 | if (rc != CURLM_OK) 296 | goto error_exit; 297 | 298 | return curl_make_result(L, CURL_LAST, rc); 299 | 300 | error_exit: 301 | free_request(ctx->curl_ctx, r); 302 | return luaL_error(L, reason); 303 | } 304 | 305 | 306 | static 307 | int 308 | get_stat(lua_State *L) 309 | { 310 | lib_ctx_t *ctx = ctx_get(L); 311 | if (ctx == NULL) 312 | return luaL_error(L, "can't get lib ctx"); 313 | 314 | curl_ctx_t *l = ctx->curl_ctx; 315 | if (l == NULL) 316 | return luaL_error(L, "it doesn't initialized"); 317 | 318 | lua_newtable(L); 319 | 320 | add_field_u64(L, "active_requests", (uint64_t) l->stat.active_requests); 321 | add_field_u64(L, "sockets_added", (uint64_t) l->stat.sockets_added); 322 | add_field_u64(L, "sockets_deleted", (uint64_t) l->stat.sockets_deleted); 323 | add_field_u64(L, "loop_calls", (uint64_t) l->stat.loop_calls); 324 | add_field_u64(L, "total_requests", l->stat.total_requests); 325 | add_field_u64(L, "http_200_responses", l->stat.http_200_responses); 326 | add_field_u64(L, "http_other_responses", l->stat.http_other_responses); 327 | add_field_u64(L, "failed_requests", (uint64_t) l->stat.failed_requests); 328 | 329 | return 1; 330 | } 331 | 332 | 333 | static 334 | int 335 | pool_stat(lua_State *L) 336 | { 337 | lib_ctx_t *ctx = ctx_get(L); 338 | if (ctx == NULL) 339 | return luaL_error(L, "can't get lib ctx"); 340 | 341 | curl_ctx_t *l = ctx->curl_ctx; 342 | if (l == NULL) 343 | return luaL_error(L, "it doesn't initialized"); 344 | 345 | lua_newtable(L); 346 | 347 | add_field_u64(L, "pool_size", (uint64_t) l->cpool.size); 348 | add_field_u64(L, "free", (uint64_t) request_pool_get_free_size(&l->cpool)); 349 | 350 | return 1; 351 | } 352 | 353 | 354 | /** Lib functions {{{ 355 | */ 356 | static 357 | int 358 | version(lua_State *L) 359 | { 360 | char version[sizeof("tarantool.curl: xxx.xxx.xxx") + 361 | sizeof("curl: xxx.xxx.xxx,") + 362 | sizeof("libev: xxx.xxx") ]; 363 | 364 | snprintf(version, sizeof(version) - 1, 365 | "tarantool.curl: %i.%i.%i, curl: %i.%i.%i, libev: %i.%i", 366 | TNT_CURL_VERSION_MAJOR, 367 | TNT_CURL_VERSION_MINOR, 368 | TNT_CURL_VERSION_PATCH, 369 | 370 | LIBCURL_VERSION_MAJOR, 371 | LIBCURL_VERSION_MINOR, 372 | LIBCURL_VERSION_PATCH, 373 | 374 | EV_VERSION_MAJOR, 375 | EV_VERSION_MINOR ); 376 | 377 | return make_str_result(L, true, version); 378 | } 379 | 380 | 381 | /** lib API {{{ 382 | */ 383 | 384 | static void do_free_(lib_ctx_t *ctx); 385 | 386 | static 387 | int 388 | new(lua_State *L) 389 | { 390 | const char *reason = "unknown error"; 391 | 392 | lib_ctx_t *ctx = (lib_ctx_t *) 393 | lua_newuserdata(L, sizeof(lib_ctx_t)); 394 | if (ctx == NULL) 395 | return luaL_error(L, "lua_newuserdata failed: lib_ctx_t"); 396 | 397 | ctx->curl_ctx = NULL; 398 | ctx->fiber = NULL; 399 | ctx->done = false; 400 | 401 | curl_args_t args = { .pipeline = false, 402 | .max_conns = 5, 403 | .pool_size = 10000 }; 404 | 405 | /* pipeline: 1 - on, 0 - off */ 406 | args.pipeline = (bool) luaL_checkint(L, 1); 407 | args.max_conns = luaL_checklong(L, 2); 408 | args.pool_size = (size_t) luaL_checklong(L, 3); 409 | 410 | ctx->curl_ctx = curl_ctx_new(&args); 411 | if (ctx->curl_ctx == NULL) 412 | return luaL_error(L, "curl_new failed"); 413 | 414 | ctx->fiber = fiber_new("__curl_ev_fiber", curl_ev_f); 415 | if (ctx->fiber == NULL) { 416 | reason = "can't create new fiber: __curl_ev_fiber"; 417 | goto error_exit; 418 | } 419 | 420 | /* Run fibers */ 421 | fiber_set_joinable(ctx->fiber, true); 422 | fiber_start(ctx->fiber, (void *) ctx); 423 | 424 | luaL_getmetatable(L, DRIVER_LUA_UDATA_NAME); 425 | lua_setmetatable(L, -2); 426 | 427 | return 1; 428 | 429 | error_exit: 430 | do_free_(ctx); 431 | return luaL_error(L, reason); 432 | } 433 | 434 | 435 | static 436 | void 437 | do_free_(lib_ctx_t *ctx) 438 | { 439 | if (ctx == NULL) 440 | return; 441 | 442 | ctx->done = true; 443 | 444 | if (ctx->fiber) 445 | fiber_join(ctx->fiber); 446 | 447 | curl_destroy(ctx->curl_ctx); 448 | } 449 | 450 | 451 | static 452 | int 453 | cleanup(lua_State *L) 454 | { 455 | do_free_(ctx_get(L)); 456 | 457 | /* remove all methods operating on ctx */ 458 | lua_newtable(L); 459 | lua_setmetatable(L, -2); 460 | 461 | return make_int_result(L, true, 0); 462 | } 463 | 464 | 465 | /* 466 | * Lists of exporting: object and/or functions to the Lua 467 | */ 468 | 469 | static const struct luaL_Reg R[] = { 470 | {"version", version}, 471 | {"new", new}, 472 | {NULL, NULL} 473 | }; 474 | 475 | static const struct luaL_Reg M[] = { 476 | {"async_request", async_request}, 477 | {"stat", get_stat}, 478 | {"pool_stat", pool_stat}, 479 | {"free", cleanup /* free already exists */}, 480 | {NULL, NULL} 481 | }; 482 | 483 | 484 | /* 485 | * Lib initializer 486 | */ 487 | LUA_API 488 | int 489 | luaopen_curl_driver(lua_State *L) 490 | { 491 | /* 492 | Add metatable.__index = metatable 493 | */ 494 | luaL_newmetatable(L, DRIVER_LUA_UDATA_NAME); 495 | lua_pushvalue(L, -1); 496 | lua_setfield(L, -2, "__index"); 497 | luaL_register(L, NULL, M); 498 | luaL_register(L, NULL, R); 499 | 500 | return 1; 501 | } 502 | -------------------------------------------------------------------------------- /curl/driver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 - 2017 Tarantool AUTHORS: please see AUTHORS file. 3 | * 4 | * Redistribution and use in source and binary forms, with or 5 | * without modification, are permitted provided that the following 6 | * conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above 9 | * copyright notice, this list of conditions and the 10 | * following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above 13 | * copyright notice, this list of conditions and the following 14 | * disclaimer in the documentation and/or other materials 15 | * provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 25 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 28 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | * SUCH DAMAGE. 30 | */ 31 | 32 | #ifndef DRIVER_H_INCLUDED 33 | #define DRIVER_H_INCLUDED 1 34 | 35 | #include "curl_wrapper.h" 36 | #include "utils.h" 37 | 38 | /** 39 | * Unique name for userdata metatables 40 | */ 41 | #define DRIVER_LUA_UDATA_NAME "__tnt_curl" 42 | #define WORK_TIMEOUT 0.3 43 | #define TNT_CURL_VERSION_MAJOR 2 44 | #define TNT_CURL_VERSION_MINOR 3 45 | #define TNT_CURL_VERSION_PATCH 0 46 | 47 | typedef struct { 48 | curl_ctx_t *curl_ctx; 49 | struct fiber *fiber; 50 | bool done; 51 | } lib_ctx_t; 52 | 53 | 54 | static inline 55 | lib_ctx_t* 56 | ctx_get(lua_State *L) 57 | { 58 | return (lib_ctx_t *) 59 | luaL_checkudata(L, 1, DRIVER_LUA_UDATA_NAME); 60 | } 61 | 62 | static inline 63 | int 64 | curl_make_result(lua_State *L, CURLcode code, CURLMcode mcode) 65 | { 66 | const char *emsg = NULL; 67 | if (code != CURL_LAST) 68 | emsg = curl_easy_strerror(code); 69 | else if (mcode != CURLM_LAST) 70 | emsg = curl_multi_strerror(mcode); 71 | return make_str_result(L, 72 | code != CURLE_OK, 73 | (emsg != NULL ? emsg : "ok")); 74 | } 75 | 76 | static inline 77 | void 78 | add_field_u64(lua_State *L, const char *key, uint64_t value) 79 | { 80 | lua_pushstring(L, key); 81 | lua_pushinteger(L, value); 82 | lua_settable(L, -3); /* 3rd element from the stack top */ 83 | } 84 | 85 | #endif /* DRIVER_H_INCLUDED */ 86 | -------------------------------------------------------------------------------- /curl/init.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright (C) 2016-2017 Tarantool AUTHORS: please see AUTHORS file. 3 | -- 4 | -- Redistribution and use in source and binary forms, with or 5 | -- without modification, are permitted provided that the following 6 | -- conditions are met: 7 | -- 8 | -- 1. Redistributions of source code must retain the above 9 | -- copyright notice, this list of conditions and the 10 | -- following disclaimer. 11 | -- 12 | -- 2. Redistributions in binary form must reproduce the above 13 | -- copyright notice, this list of conditions and the following 14 | -- disclaimer in the documentation and/or other materials 15 | -- provided with the distribution. 16 | -- 17 | -- THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 18 | -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | -- TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | -- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 | -- OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22 | -- INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | -- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 25 | -- BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | -- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 28 | -- THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | -- SUCH DAMAGE. 30 | -- 31 | 32 | local fiber = require('fiber') 33 | local curl_driver = require('curl.driver') 34 | 35 | local curl_mt 36 | 37 | -- 38 | -- - create a new curl instance. 39 | -- 40 | -- Parameters: 41 | -- 42 | -- pipeline - set to true to enable pipelining for this multi handle */ 43 | -- max_conns - Maximum number of entries in the connection cache */ 44 | -- 45 | -- Returns: 46 | -- curl object or raise error() 47 | -- 48 | local http = function(opts) 49 | 50 | opts = opts or {} 51 | 52 | opts.pipeline = opts.pipeline or 0 53 | opts.max_conns = opts.max_conns or 5 54 | opts.pool_size = opts.pool_size or 1000 55 | 56 | local curl = curl_driver.new(opts.pipeline, opts.max_conns, opts.pool_size) 57 | 58 | local ok, version = curl:version() 59 | if not ok then 60 | error("can't get curl:version()") 61 | end 62 | 63 | return setmetatable({VERSION = version, 64 | curl = curl, }, 65 | curl_mt ) 66 | end 67 | 68 | 69 | -- Internal {{{ 70 | local function read_cb(cnt, ctx) 71 | local res = ctx.body:sub(ctx.off, ctx.off + cnt - 1) 72 | ctx.off = ctx.off + res:len() 73 | return res 74 | end 75 | 76 | local function write_cb(data, ctx) 77 | ctx.response = ctx.response .. data 78 | return data:len() 79 | end 80 | 81 | local function done_cb(curl_code, http_code, error_message, ctx) 82 | ctx.http_code = http_code 83 | ctx.curl_code = curl_code 84 | ctx.error_message = error_message 85 | ctx.cond:signal() 86 | end 87 | 88 | -- 89 | -- This function does HTTP request 90 | -- 91 | -- Parameters: 92 | -- 93 | -- method - HTTP method, like GET, POST, PUT and so on 94 | -- url - HTTP url, like https://tarantool.org/doc 95 | -- body - this parameter is optional, you may use it for passing the 96 | -- body to a server. Like 'My text string!' 97 | -- options - this is a table of options. 98 | -- ca_path - a path to ssl certificate dir; 99 | -- ca_file - a path to ssl certificate file; 100 | -- headers - a table of HTTP headers; 101 | -- max_conns - max amount of cached alive connections; 102 | -- keepalive_idle & keepalive_interval - non-universal keepalive knobs (Linux, AIX, HP-UX, more); 103 | -- low_speed_time & low_speed_limit - If the download receives less than "low speed limit" bytes/second 104 | -- during "low speed time" seconds, the operations is aborted. 105 | -- You could i.e if you have a pretty high speed connection, abort if 106 | -- it is less than 2000 bytes/sec during 20 seconds; 107 | -- read_timeout - Time-out the read operation after this amount of seconds; 108 | -- connect_timeout - Time-out connect operations after this amount of seconds, if connects are; 109 | -- OK within this time, then fine... This only aborts the connect phase; 110 | -- dns_cache_timeout - DNS cache timeout; 111 | -- 112 | -- Returns: 113 | -- {code=NUMBER, body=STRING} or error() 114 | -- 115 | local function sync_request(self, method, url, body, opts) 116 | 117 | if not method or not url then 118 | error('sync_request(method, url [, body [, options]])') 119 | end 120 | 121 | opts = opts or {} 122 | 123 | local ctx = {cond = fiber.cond(), 124 | http_code = 0, 125 | curl_code = 0, 126 | error_message = '', 127 | response = '', 128 | body = body or '', 129 | off = 1} 130 | 131 | local headers = opts.headers or {} 132 | 133 | -- I have to set CL since CURL-engine works async 134 | if body then 135 | headers['Content-Length'] = body:len() 136 | end 137 | 138 | local ok, emsg = self.curl:async_request(method, url, 139 | {ca_path = opts.ca_path, 140 | ca_file = opts.ca_file, 141 | headers = headers, 142 | read = read_cb, 143 | write = write_cb, 144 | done = done_cb, 145 | ctx = ctx, 146 | max_conns = opts.max_conns, 147 | keepalive_idle = opts.keepalive_idle, 148 | keepalive_interval = opts.keepalive_interval, 149 | low_speed_time = opts.low_speed_time, 150 | low_speed_limit = opts.low_speed_limit, 151 | read_timeout = opts.read_timeout, 152 | connect_timeout = opts.connect_timeout, 153 | dns_cache_timeout = opts.dns_cache_timeout, 154 | curl_verbose = opts.curl_verbose, } ) 155 | 156 | -- Curl can't add a new request 157 | if not ok then 158 | error("curl has an internal error, msg = " .. emsg) 159 | end 160 | 161 | -- 'yield' until all data have arrived {{{ 162 | ctx.cond:wait() 163 | -- }}} 164 | 165 | -- Curl has an internal error 166 | if ctx.curl_code ~= 0 then 167 | error("curl has an internal error, msg = " .. ctx.error_message) 168 | end 169 | 170 | -- Curl did a request and he has a response 171 | return { code = ctx.http_code, body = ctx.response } 172 | end 173 | -- }}} 174 | 175 | 176 | curl_mt = { 177 | __index = { 178 | -- 179 | -- see 180 | -- 181 | request = function(self, method, url, body, options) 182 | if not method or not url then 183 | error('signature (method, url [, body [, options]])') 184 | end 185 | return sync_request(self, method, url, body, options) 186 | end, 187 | 188 | -- 189 | -- - see 190 | -- 191 | get = function(self, url, options) 192 | return self:request('GET', url, '', options) 193 | end, 194 | 195 | -- 196 | -- - see 197 | -- 198 | post = function(self, url, body, options) 199 | return self:request('POST', url, body, options) 200 | end, 201 | 202 | -- 203 | -- - see 204 | -- 205 | put = function(self, url, body, options) 206 | return self:request('PUT', url, body, options) 207 | end, 208 | 209 | -- 210 | -- This function does HTTP request 211 | -- 212 | -- Parameters: 213 | -- 214 | -- method - HTTP method, like GET, POST, PUT and so on 215 | -- url - HTTP url, like https://tarantool.org/doc 216 | -- options - this is a table of options. 217 | -- 218 | -- done - name of a callback function which is invoked when a request 219 | -- was completed; 220 | -- 221 | -- write - name of a callback function which is invoked if the 222 | -- server returns data to the client; 223 | -- signature is function(data, context) 224 | -- 225 | -- read - name of a callback function which is invoked if the 226 | -- client passes data to the server. 227 | -- signature is function(content_size, context) 228 | -- 229 | -- done - name of a callback function which is invoked when a request 230 | -- was completed; 231 | -- signature is function(curl_code, http_code, error_message, ctx) 232 | -- 233 | -- ca_path - a path to ssl certificate dir; 234 | -- 235 | -- ca_file - a path to ssl certificate file; 236 | -- 237 | -- headers - a table of HTTP headers; 238 | -- 239 | -- max_conns - max amount of cached alive connections; 240 | -- 241 | -- keepalive_idle & keepalive_interval - non-universal keepalive knobs (Linux, AIX, HP-UX, more); 242 | -- 243 | -- low_speed_time & low_speed_limit - If the download receives less than "low speed limit" bytes/second 244 | -- during "low speed time" seconds, the operations is aborted. 245 | -- You could i.e if you have a pretty high speed connection, abort if 246 | -- it is less than 2000 bytes/sec during 20 seconds; 247 | -- 248 | -- read_timeout - Time-out the read operation after this amount of seconds; 249 | -- 250 | -- connect_timeout - Time-out connect operations after this amount of seconds, if connects are; 251 | -- OK within this time, then fine... This only aborts the connect phase; 252 | -- 253 | -- dns_cache_timeout - DNS cache timeout; 254 | -- 255 | -- curl_verbose - make libcurl verbose!; 256 | -- 257 | -- Returns: 258 | -- ok, msg or error() 259 | -- 260 | async_request = function(self, method, url, options) 261 | if not method or not url or not options then 262 | error('signature (method, url [, body [, options]])') 263 | end 264 | if type(options.read) ~= 'function' or 265 | type(options.write) ~= 'function' or 266 | type(options.done) ~= 'function' 267 | then 268 | error('options should have read write and done functions') 269 | end 270 | return self.curl:async_request(method, url, options) 271 | end, 272 | 273 | -- 274 | -- - see 275 | -- 276 | async_get = function(self, url, options) 277 | return self:async_request('GET', url, options) 278 | end, 279 | 280 | -- 281 | -- - see 282 | -- 283 | async_post = function(self, url, options) 284 | return self:async_request('POST', url, options) 285 | end, 286 | 287 | -- 288 | -- - see 289 | -- 290 | async_put = function(self, url, options) 291 | return self:async_request('PUT', url, options) 292 | end, 293 | 294 | -- 295 | -- - this function returns a table with many values of statistic. 296 | -- 297 | -- Returns { 298 | -- 299 | -- active_requests - this is number of currently executing requests 300 | -- 301 | -- sockets_added - this is a total number of added sockets into libev loop 302 | -- 303 | -- sockets_deleted - this is a total number of deleted sockets from libev 304 | -- loop 305 | -- 306 | -- loop_calls - this is a total number of iterations over libev loop 307 | -- 308 | -- total_requests - this is a total number of requests 309 | -- 310 | -- http_200_responses - this is a total number of requests which have 311 | -- returned a code HTTP 200 312 | -- 313 | -- http_other_responses - this is a total number of requests which have 314 | -- requests not a HTTP 200 315 | -- 316 | -- failed_requests - this is a total number of requests which have 317 | -- failed (included systeme erros, curl errors, HTTP 318 | -- erros and so on) 319 | -- } 320 | -- or error() 321 | -- 322 | stat = function(self) 323 | return self.curl:stat() 324 | end, 325 | 326 | pool_stat = function(self) 327 | return self.curl:pool_stat() 328 | end, 329 | 330 | -- 331 | -- - cleanup resources 332 | -- 333 | -- Should be called at the end of work. 334 | -- This function does clean all resources (i.e. destructor). 335 | -- 336 | free = function(self) 337 | self.curl:free() 338 | end, 339 | }, 340 | } 341 | 342 | -- 343 | -- Export 344 | -- 345 | return { 346 | -- 347 | http = http, 348 | } 349 | -------------------------------------------------------------------------------- /curl/request_pool.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 - 2017 Tarantool AUTHORS: please see AUTHORS file. 3 | * 4 | * Redistribution and use in source and binary forms, with or 5 | * without modification, are permitted provided that the following 6 | * conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above 9 | * copyright notice, this list of conditions and the 10 | * following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above 13 | * copyright notice, this list of conditions and the following 14 | * disclaimer in the documentation and/or other materials 15 | * provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 25 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 28 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | * SUCH DAMAGE. 30 | */ 31 | 32 | #include "request_pool.h" 33 | 34 | #include "curl_wrapper.h" 35 | 36 | #include 37 | #include 38 | 39 | static inline void reset_request(request_t *r); 40 | 41 | 42 | static inline 43 | bool 44 | create_request(struct curl_ctx_s *ctx, size_t idx, request_t *r) 45 | { 46 | assert(ctx); 47 | assert(r); 48 | 49 | memset(r, 0, sizeof(request_t)); 50 | 51 | r->pool.idx = idx; 52 | r->curl_ctx = ctx; 53 | 54 | reset_request(r); 55 | 56 | return true; 57 | } 58 | 59 | 60 | static inline 61 | void 62 | reset_request(request_t *r) 63 | { 64 | assert(r); 65 | 66 | r->pool.busy = false; 67 | 68 | if (r->headers) { 69 | curl_slist_free_all(r->headers); 70 | r->headers = NULL; 71 | } 72 | 73 | if (r->easy) { 74 | curl_easy_cleanup(r->easy); 75 | r->easy = NULL; 76 | } 77 | 78 | if (r->lua_ctx.L) { 79 | luaL_unref(r->lua_ctx.L, LUA_REGISTRYINDEX, 80 | r->lua_ctx.read_fn); 81 | luaL_unref(r->lua_ctx.L, LUA_REGISTRYINDEX, 82 | r->lua_ctx.write_fn); 83 | luaL_unref(r->lua_ctx.L, LUA_REGISTRYINDEX, 84 | r->lua_ctx.done_fn); 85 | luaL_unref(r->lua_ctx.L, LUA_REGISTRYINDEX, 86 | r->lua_ctx.fn_ctx); 87 | } 88 | 89 | r->lua_ctx.L = NULL; 90 | r->lua_ctx.read_fn = LUA_REFNIL; 91 | r->lua_ctx.write_fn = LUA_REFNIL; 92 | r->lua_ctx.done_fn = LUA_REFNIL; 93 | r->lua_ctx.fn_ctx = LUA_REFNIL; 94 | } 95 | 96 | 97 | bool 98 | request_pool_new(request_pool_t *p, struct curl_ctx_s *c, size_t s) 99 | { 100 | assert(p); 101 | assert(c); 102 | 103 | memset(p, 0, sizeof(request_pool_t)); 104 | 105 | p->size = s; 106 | 107 | p->mem = (request_t *) malloc(p->size * sizeof(request_t)); 108 | if (p->mem == NULL) 109 | goto error_exit; 110 | memset(p->mem, 0, p->size * sizeof(request_t)); 111 | 112 | for (size_t i = 0; i < p->size; ++i) { 113 | if (!create_request(c, i, &p->mem[i])) 114 | goto error_exit; 115 | } 116 | 117 | return true; 118 | error_exit: 119 | request_pool_free(p); 120 | return false; 121 | } 122 | 123 | 124 | void 125 | request_pool_free(request_pool_t *p) 126 | { 127 | assert(p); 128 | 129 | if (p->mem) { 130 | for (size_t i = 0; i < p->size; ++i) 131 | reset_request(&p->mem[i]); 132 | free(p->mem); 133 | p->mem = NULL; 134 | } 135 | } 136 | 137 | 138 | request_t* 139 | request_pool_get_request(request_pool_t *p) 140 | { 141 | assert(p); 142 | 143 | if (p->mem == NULL) 144 | return NULL; 145 | 146 | for (size_t i = 0; i < p->size; ++i) { 147 | 148 | if (!p->mem[i].pool.busy) { 149 | 150 | request_t *r = &p->mem[i]; 151 | 152 | r->easy = curl_easy_init(); 153 | if (r->easy == NULL) 154 | return NULL; 155 | 156 | ++r->curl_ctx->stat.active_requests; 157 | r->pool.busy = true; 158 | 159 | return r; 160 | } 161 | } 162 | 163 | return NULL; 164 | } 165 | 166 | 167 | void 168 | request_pool_free_request(request_pool_t *p, request_t *r) 169 | { 170 | if (r == NULL || p->mem == NULL) 171 | return; 172 | 173 | if (r->pool.busy) { 174 | --r->curl_ctx->stat.active_requests; 175 | curl_multi_remove_handle(r->curl_ctx->multi, r->easy); 176 | } 177 | 178 | reset_request(r); 179 | } 180 | 181 | 182 | size_t 183 | request_pool_get_free_size(request_pool_t *p) 184 | { 185 | size_t size = 0; 186 | 187 | if (p == NULL) 188 | return size; 189 | 190 | for (size_t i = 0; i < p->size; ++i) { 191 | if (!p->mem[i].pool.busy) 192 | ++size; 193 | } 194 | 195 | return size; 196 | } 197 | -------------------------------------------------------------------------------- /curl/request_pool.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 - 2017 Tarantool AUTHORS: please see AUTHORS file. 3 | * 4 | * Redistribution and use in source and binary forms, with or 5 | * without modification, are permitted provided that the following 6 | * conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above 9 | * copyright notice, this list of conditions and the 10 | * following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above 13 | * copyright notice, this list of conditions and the following 14 | * disclaimer in the documentation and/or other materials 15 | * provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 25 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 28 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | * SUCH DAMAGE. 30 | */ 31 | 32 | #ifndef REQUEST_POOL_H_INCLUDED 33 | #define REQUEST_POOL_H_INCLUDED 1 34 | 35 | #include 36 | #include 37 | #include 38 | 39 | #include 40 | #include 41 | #include 42 | 43 | #include 44 | 45 | struct curl_ctx_s; 46 | 47 | typedef struct { 48 | 49 | /* pool meta info */ 50 | struct { 51 | size_t idx; 52 | bool busy; 53 | } pool; 54 | 55 | /** Information associated with a specific easy handle */ 56 | CURL *easy; 57 | 58 | /* Reference to curl context */ 59 | struct curl_ctx_s *curl_ctx; 60 | 61 | /* Callbacks from lua and Lua context */ 62 | struct { 63 | lua_State *L; 64 | int read_fn; 65 | int write_fn; 66 | int done_fn; 67 | int fn_ctx; 68 | } lua_ctx; 69 | 70 | /* HTTP headers */ 71 | struct curl_slist *headers; 72 | } request_t; 73 | 74 | typedef struct { 75 | request_t *mem; 76 | size_t size; 77 | } request_pool_t; 78 | 79 | 80 | bool request_pool_new(request_pool_t *p, struct curl_ctx_s *c, size_t s); 81 | void request_pool_free(request_pool_t *p); 82 | 83 | request_t* request_pool_get_request(request_pool_t *p); 84 | void request_pool_free_request(request_pool_t *p, request_t *c); 85 | size_t request_pool_get_free_size(request_pool_t *p); 86 | 87 | #endif /* REQUEST_POOL_H_INCLUDED */ 88 | -------------------------------------------------------------------------------- /curl/unit_tests.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 - 2017 Tarantool AUTHORS: please see AUTHORS file. 3 | * 4 | * Redistribution and use in source and binary forms, with or 5 | * without modification, are permitted provided that the following 6 | * conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above 9 | * copyright notice, this list of conditions and the 10 | * following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above 13 | * copyright notice, this list of conditions and the following 14 | * disclaimer in the documentation and/or other materials 15 | * provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 25 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 28 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | * SUCH DAMAGE. 30 | */ 31 | 32 | #include 33 | #include "curl_wrapper.h" 34 | 35 | #define say_(...) do { \ 36 | fprintf(stderr, __VA_ARGS__); \ 37 | fprintf(stderr, "\n"); \ 38 | } while(0) 39 | 40 | 41 | static inline 42 | void 43 | basic_features(void) 44 | { 45 | say_("[+] basic_features"); 46 | 47 | curl_ctx_t *l = curl_ctx_new_easy(); 48 | 49 | for (;;) { 50 | #if defined (MY_DEBUG) 51 | new_request_test(l, "127.0.0.1:10000/"); 52 | #endif 53 | if (l->stat.active_requests > 10) 54 | break; 55 | curl_poll_one(l); 56 | } 57 | 58 | for (;;) { 59 | if (l->stat.active_requests == 0) 60 | break; 61 | curl_poll_one(l); 62 | } 63 | 64 | curl_print_stat(l, stderr); 65 | 66 | assert(l->stat.active_requests == 0); 67 | assert(l->stat.sockets_deleted == l->stat.sockets_added); 68 | 69 | say_("[+] finished"); 70 | 71 | curl_destroy(l); 72 | } 73 | 74 | 75 | int main(int argc __attribute__((unused)), 76 | char ** argv __attribute__((unused))) 77 | { 78 | basic_features(); 79 | return 0; 80 | } 81 | -------------------------------------------------------------------------------- /curl/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2017 Tarantool AUTHORS: please see AUTHORS file. 3 | * 4 | * Redistribution and use in source and binary forms, with or 5 | * without modification, are permitted provided that the following 6 | * conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above 9 | * copyright notice, this list of conditions and the 10 | * following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above 13 | * copyright notice, this list of conditions and the following 14 | * disclaimer in the documentation and/or other materials 15 | * provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 25 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 28 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | * SUCH DAMAGE. 30 | */ 31 | 32 | #ifndef DRIVER_UTILS_H_INCLUDED 33 | #define DRIVER_UTILS_H_INCLUDED 1 34 | 35 | #include 36 | #include 37 | #include 38 | 39 | #include 40 | 41 | 42 | #ifndef TIMEOUT_INFINITY 43 | # define TIMEOUT_INFINITY ((size_t)-1) 44 | #endif /*TIMEOUT_INFINITY*/ 45 | 46 | 47 | static inline 48 | int 49 | make_str_result(lua_State *L, bool ok, const char *str) 50 | { 51 | lua_pushboolean(L, ok); 52 | lua_pushstring(L, str); 53 | return 2; 54 | } 55 | 56 | static inline 57 | int 58 | make_int_result(lua_State *L, bool ok, int i) 59 | { 60 | lua_pushboolean(L, ok); 61 | lua_pushinteger(L, i); 62 | return 2; 63 | } 64 | 65 | static inline 66 | int 67 | make_errorno_result(lua_State *L, int the_errno) 68 | { 69 | lua_pushboolean(L, false); 70 | lua_pushstring(L, strerror(the_errno)); 71 | return 2; 72 | } 73 | 74 | #endif /* DRIVER_UTILS_H_INCLUDED */ 75 | -------------------------------------------------------------------------------- /debian/.gitignore: -------------------------------------------------------------------------------- 1 | tarantool-http/ 2 | files 3 | stamp-* 4 | *.substvars 5 | *.log 6 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | tarantool-curl (2.3.0-1) stable; urgency=medium 2 | 3 | * Allow to pass floating-point values to 'connect_timeout' and 'read_timeout' 4 | options 5 | 6 | -- Vasiliy Soshnikov Sun, 1 Jun 2017 22:01:00 +0300 7 | 8 | tarantool-curl (2.2.9-1) stable; urgency=medium 9 | 10 | * Build issues have been fixed 11 | 12 | -- Vasiliy Soshnikov Sun, 2 Apr 2017 22:01:00 +0300 13 | tarantool-curl (2.2.3-1) stable; urgency=medium 14 | 15 | * libev support 16 | * imported many curl's options, see https://github.com/tarantool/curl/#api-reference 17 | 18 | -- V. Soshnikov Mon, 30 Jan 2017 20:05:01 +0300 19 | tarantool-curl (1.0.0-1) unstable; urgency=medium 20 | 21 | * Initial release. 22 | 23 | -- Andrey Drozdov Wed, 31 Aug 2016 11:19:56 +0300 24 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: tarantool-curl 2 | Priority: optional 3 | Section: database 4 | Maintainer: Andrey Drozdov 5 | Build-Depends: debhelper (>= 9), cdbs, 6 | cmake (>= 2.8), 7 | tarantool-dev, 8 | tarantool (>= 1.7.2.0), 9 | libssl-dev, 10 | libev-dev, 11 | nodejs, 12 | libc-ares-dev, 13 | Standards-Version: 3.9.6 14 | Homepage: https://github.com/tarantool/tarantool-curl 15 | Vcs-Git: git://github.com/tarantool/tarantool-curl.git 16 | Vcs-Browser: https://github.com/tarantool/tarantool-curl 17 | 18 | Package: tarantool-curl 19 | Architecture: i386 amd64 armhf arm64 20 | Depends: tarantool (>= 1.7.2.0), libssl, libc-ares2, ${shlibs:Depends}, ${misc:Depends} 21 | Pre-Depends: ${misc:Pre-Depends} 22 | Description: Curl based HTTP client for Tarantool 23 | This package provides a Curl based HTTP client for Tarantool. 24 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Debianized-By: Roman Tsisyk 3 | Upstream-Name: tarantool-http 4 | Upstream-Contact: roman@tarantool.org 5 | Source: https://github.com/tarantool/http 6 | 7 | Files: * 8 | Copyright: 2010-2013 by Tarantool AUTHORS, please see AUTHORS file. 9 | License: BSD-2-Clause 10 | Redistribution and use in source and binary forms, with or 11 | without modification, are permitted provided that the following 12 | conditions are met: 13 | . 14 | 1. Redistributions of source code must retain the above 15 | copyright notice, this list of conditions and the 16 | following disclaimer. 17 | . 18 | 2. Redistributions in binary form must reproduce the above 19 | copyright notice, this list of conditions and the following 20 | disclaimer in the documentation and/or other materials 21 | provided with the distribution. 22 | . 23 | THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 24 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 25 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 27 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 28 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 31 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 32 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 34 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 | SUCH DAMAGE. 36 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | README.md 2 | AUTHORS 3 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | DEB_CMAKE_EXTRA_FLAGS := -DCMAKE_INSTALL_LIBDIR=lib/$(DEB_HOST_MULTIARCH) \ 4 | -DCMAKE_BUILD_TYPE=RelWithDebInfo \ 5 | -DWITH_SYSTEM_CURL=OFF 6 | 7 | DEB_MAKE_CHECK_TARGET := #test TODO fix this 8 | 9 | include /usr/share/cdbs/1/rules/debhelper.mk 10 | include /usr/share/cdbs/1/class/cmake.mk 11 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /rockspecs/tarantool-curl-2.3.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = 'tarantool-curl' 2 | version = '2.3.1-1' 3 | 4 | source = { 5 | url = 'git://github.com/tarantool/curl.git'; 6 | tag = '2.3.1'; 7 | } 8 | 9 | description = { 10 | summary = "libcurl bindings for tarantool"; 11 | detailed = [[ 12 | This module is a set of bindings for libcurl that allows you to use most of standard HTTP client functions. 13 | ]]; 14 | homepage = 'https://github.com/tarantool/curl.git'; 15 | license = 'BSD'; 16 | maintainer = "Konstantin Nazarov "; 17 | } 18 | 19 | dependencies = { 20 | 'lua >= 5.1'; 21 | } 22 | 23 | build = { 24 | type = 'cmake', 25 | variables = { 26 | CMAKE_BUILD_TYPE="RelWithDebInfo", 27 | TARANTOOL_INSTALL_LIBDIR="$(LIBDIR)", 28 | TARANTOOL_INSTALL_LUADIR="$(LUADIR)", 29 | }, 30 | } 31 | -- vim: syntax=lua ts=4 sts=4 sw=4 et 32 | -------------------------------------------------------------------------------- /rockspecs/tarantool-curl-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = 'tarantool-curl' 2 | version = 'scm-1' 3 | 4 | source = { 5 | url = 'git://github.com/tarantool/curl.git'; 6 | branch = 'master'; 7 | } 8 | 9 | description = { 10 | summary = "libcurl bindings for tarantool"; 11 | detailed = [[ 12 | This module is a set of bindings for libcurl that allows you to use most of standard HTTP client functions. 13 | ]]; 14 | homepage = 'https://github.com/tarantool/curl.git'; 15 | license = 'BSD'; 16 | maintainer = "Konstantin Nazarov "; 17 | } 18 | 19 | dependencies = { 20 | 'lua >= 5.1'; 21 | } 22 | 23 | build = { 24 | type = 'cmake', 25 | variables = { 26 | CMAKE_BUILD_TYPE="RelWithDebInfo", 27 | TARANTOOL_INSTALL_LIBDIR="$(LIBDIR)", 28 | TARANTOOL_INSTALL_LUADIR="$(LUADIR)", 29 | }, 30 | } 31 | -- vim: syntax=lua ts=4 sts=4 sw=4 et 32 | -------------------------------------------------------------------------------- /rpm/tarantool-curl.spec: -------------------------------------------------------------------------------- 1 | Name: tarantool-curl 2 | Version: 2.3.0 3 | Release: 1%{?dist} 4 | Summary: Curl based HTTP client for Tarantool 5 | Group: Applications/Databases 6 | License: BSD 7 | URL: https://github.com/tarantool/tarantool-curl 8 | Source0: https://github.com/tarantool/%{name}/archive/%{version}/%{name}-%{version}.tar.gz 9 | BuildRequires: cmake >= 2.8 10 | BuildRequires: gcc >= 4.5 11 | BuildRequires: tarantool >= 1.7.2.0 12 | BuildRequires: tarantool-devel 13 | BuildRequires: libev, libev-devel 14 | BuildRequires: openssl, openssl-devel 15 | BuildRequires: c-ares, c-ares-devel 16 | BuildRequires: nodejs, libuv 17 | 18 | Requires: tarantool >= 1.7.2, libev, c-ares 19 | 20 | %description 21 | This package provides a Curl based HTTP client for Tarantool. 22 | 23 | %prep 24 | %setup -q -n %{name}-%{version} 25 | 26 | %build 27 | %cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DWITH_SYSTEM_CURL=OFF 28 | make %{?_smp_mflags} 29 | make %{?_smp_mflags} test 30 | 31 | %install 32 | %make_install 33 | 34 | %files 35 | %{_libdir}/tarantool/*/ 36 | %{_datarootdir}/tarantool/*/ 37 | %doc README.md 38 | %{!?_licensedir:%global license %doc} 39 | %license LICENSE AUTHORS 40 | 41 | %changelog 42 | * Sun Apr 2 2017 V. Soshnikov 2.2.9-1 43 | - Build issues have been fixed 44 | 45 | * Mon Jan 30 2017 V. Soshnikov 2.2.3-1 46 | - libev support 47 | - imported many curl's options, see https://github.com/tarantool/curl/#api-reference 48 | 49 | * Wed Aug 31 2016 Andrey Drozdov 1.0.0-1 50 | - Initial version of the RPM spec 51 | -------------------------------------------------------------------------------- /tests/async.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tarantool 2 | 3 | -- Those lines of code are for debug purposes only 4 | -- So you have to ignore them 5 | -- {{ 6 | package.preload['curl.driver'] = 'curl/driver.so' 7 | -- }} 8 | -- 9 | 10 | box.cfg { } 11 | 12 | -- Includes 13 | local curl = require('curl') 14 | local fiber = require('fiber') 15 | local json = require('json') 16 | local os = require('os') 17 | 18 | local headers = { my_header = "1", my_header2 = "2" } 19 | local my_body = { key="value" } 20 | 21 | local http = curl.http({pool_size = 2}) 22 | 23 | print(http.VERSION) 24 | 25 | local function write(data, ctx) 26 | ctx.response = ctx.response .. data 27 | return data:len() 28 | end 29 | 30 | local function done(curl_code, http_code, error_msg, ctx) 31 | ctx.http_code = http_code 32 | ctx.curl_code = curl_code 33 | ctx.error_msg = error_msg 34 | ctx.done = true 35 | end 36 | 37 | local contexts = {} 38 | 39 | contexts['GET'] = { http_code = 0, 40 | curl_code = 0, 41 | error_msg = '', 42 | done = true, 43 | response = '' } 44 | 45 | contexts['POST']= {done = false, 46 | http_code = 0, 47 | curl_code = 0, 48 | error_message = '', 49 | body = json.encode(my_body), 50 | response = ''} 51 | 52 | -- GET 53 | local ok, msg = http:async_get('http://httpbin.org/get', 54 | {ctx = contexts['GET'], 55 | headers = headers, 56 | read = function(cnt, ctx) 57 | return cnt:len() 58 | end, 59 | write = write, 60 | done = done, } ) 61 | assert(ok) 62 | 63 | -- POST 64 | local ok, msg = http:async_post('http://httpbin.org/post', 65 | {headers=headers, 66 | keepalive_idle = 30, 67 | keepalive_interval = 60, 68 | read = function(cnt, ctx) 69 | local res = ctx.body:sub(1, cnt) 70 | ctx.body = ctx.body:sub(cnt + 1) 71 | return res 72 | end, 73 | write = write, 74 | done = done, 75 | ctx = contexts['POST'], 76 | }) 77 | assert(ok) 78 | 79 | -- Join & tests 80 | local ticks = 0 81 | while http:stat().active_requests ~= 0 do 82 | if ticks > 60 then 83 | os.exit(1) 84 | end 85 | 86 | for _, ctx in ipairs(contexts) do 87 | if ctx.done then 88 | assert(ctx.curl_code == 0) 89 | assert(ctx.http_code == 200) 90 | local obody = json.decode(r.body) 91 | assert(obody.headers['My-Header'] == headers.my_header) 92 | assert(obody.headers['My-Header2'] == headers.my_header2) 93 | end 94 | end 95 | 96 | ticks = ticks + 2 97 | fiber.sleep(2) 98 | end 99 | 100 | local st = http:stat() 101 | assert(st.sockets_added == st.sockets_deleted) 102 | assert(st.active_requests == 0) 103 | assert(st.loop_calls > 0) 104 | local pst = http:pool_stat() 105 | assert(pst.pool_size == 2) 106 | assert(pst.free == pst.pool_size) 107 | 108 | http:free() 109 | 110 | print('[+] async OK') 111 | 112 | os.exit(0) 113 | -------------------------------------------------------------------------------- /tests/bugs.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tarantool 2 | 3 | -- Those lines of code are for debug purposes only 4 | -- So you have to ignore them 5 | -- {{ 6 | package.preload['curl.driver'] = '../curl/driver.so' 7 | -- }} 8 | 9 | local os = require('os') 10 | 11 | box.cfg {} 12 | 13 | function run(is_skipping, desc, func) 14 | print('Running', desc) 15 | if not is_skipping then 16 | if not func() then 17 | os.exit(1) 18 | end 19 | else 20 | print('SKIP') 21 | end 22 | end 23 | 24 | run(false, 'Issue https://github.com/tarantool/curl/issues/3', function() 25 | local curl = require('curl') 26 | local json = require('json') 27 | local http = curl.http() 28 | local data = {a = 'b'} 29 | local headers = {} 30 | headers['Content-Type'] = 'application/json' 31 | local res = http:post('https://httpbin.org/post', json.encode(data), 32 | {headers=headers}) 33 | assert(res.code == 200) 34 | assert(json.decode(res.body)['json']['a'] == data['a']) 35 | http:free() 36 | return true 37 | end) 38 | 39 | run(false, 'Issue https://github.com/tarantool/curl/issues/16', function() 40 | local curl = require('curl') 41 | local http = curl.http() 42 | local res = http:get('http://httpbin.org/get', {read_timeout = 1}) 43 | assert(res.code == 200) 44 | 45 | local ok, msg = pcall(function() 46 | http:get('http://httpbin.org/get', {read_timeout = 0.001}) 47 | end) 48 | assert(ok == false) 49 | http:free() 50 | return true 51 | end) 52 | 53 | run(false, 'Issue timeout', function() 54 | local curl = require('curl') 55 | local http = curl.http() 56 | local res = http:get('http://httpbin.org/get', {read_timeout = 0.5}) 57 | assert(res.code == 200) 58 | 59 | local curl = require('curl') 60 | local http = curl.http() 61 | local res = http:get('http://httpbin.org/get', {read_timeout = 1}) 62 | assert(res.code == 200) 63 | return true 64 | end) 65 | 66 | print('[+] bugs OK') 67 | 68 | os.exit(0) 69 | -------------------------------------------------------------------------------- /tests/example.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tarantool 2 | 3 | -- Those lines of code are for debug purposes only 4 | -- So you have to ignore them 5 | -- {{ 6 | package.preload['curl.driver'] = 'curl/driver.so' 7 | -- }} 8 | -- 9 | 10 | box.cfg {} 11 | 12 | -- Includes 13 | local curl = require('curl') 14 | local fiber = require('fiber') 15 | local json = require('json') 16 | local os = require('os') 17 | 18 | local http = curl.http({pool_size=1}) 19 | 20 | print(http.VERSION) 21 | 22 | local headers = { my_header = "1", my_header2 = "2" } 23 | local my_body = { key="value" } 24 | local json_body = json.encode(my_body) 25 | 26 | -- Sync request 27 | local r = http:get('https://tarantool.org/this/page/not/exists', 28 | {headers=headers} ) 29 | assert(r.code == 404) 30 | assert(r.body:len() ~= 0) 31 | 32 | -- Sync requests {{{ 33 | local r = http:get('https://tarantool.org/', {headers=headers}) 34 | assert(r.code == 200) 35 | assert(r.body:len() ~= 0) 36 | 37 | local res = http:request('GET', 'mail.ru') 38 | assert(r.code == 200) 39 | 40 | local r = http:post('http://httpbin.org/post', json_body, 41 | {headers=headers, 42 | keepalive_idle = 30, 43 | keepalive_interval = 60, }) 44 | assert(r.code == 200) 45 | local obody = json.decode(r.body) 46 | assert(obody.headers['My-Header'] == headers.my_header) 47 | assert(obody.headers['My-Header2'] == headers.my_header2) 48 | -- }}} 49 | 50 | local st = http:stat() 51 | assert(st.sockets_added == st.sockets_deleted) 52 | assert(st.active_requests == 0) 53 | assert(st.loop_calls > 0) 54 | local pst = http:pool_stat() 55 | assert(pst.pool_size == 1) 56 | assert(pst.free == pst.pool_size) 57 | http:free() 58 | 59 | print('[+] example OK') 60 | os.exit(0) 61 | -------------------------------------------------------------------------------- /tests/load.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tarantool 2 | 3 | -- Those lines of code are for debug purposes only 4 | -- So you have to ignore them 5 | -- {{ 6 | package.preload['curl.driver'] = 'curl/driver.so' 7 | -- }} 8 | -- 9 | 10 | -- Includes 11 | box.cfg { slab_alloc_factor = 0.1 } 12 | 13 | local curl = require('curl') 14 | local fiber = require('fiber') 15 | local json = require('json') 16 | 17 | local num = 10 18 | local host = '127.0.0.1:10000' 19 | local curls = { } 20 | local headers = { } 21 | 22 | -- Init [[ 23 | for i = 1, num do 24 | headers["My-header" .. i] = "my-value" 25 | end 26 | 27 | for i = 1, num do 28 | table.insert(curls, {url = host .. '/', 29 | http = curl.http(), 30 | body = json.encode({stat = box.stat(), 31 | info = box.info() }), 32 | headers = headers, 33 | connect_timeout = 5, 34 | read_timeout = 5, 35 | dns_cache_timeout = 1, 36 | } ) 37 | end 38 | -- ]] 39 | 40 | -- Start test 41 | for i = 1, num do 42 | 43 | local obj = curls[i] 44 | 45 | for j = 1, 10 do 46 | fiber.create(function() 47 | obj.http:post(obj.url, obj.body, 48 | {headers = obj.headers, 49 | keepalive_idle = 30, 50 | keepalive_interval = 60, 51 | connect_timeout = obj.connect_timeout, 52 | read_timeout = obj.read_timeout, 53 | dns_cache_timeout = obj.dns_cache_timeout, }) 54 | end ) 55 | fiber.create(function() 56 | obj.http:get(obj.url, 57 | {headers = obj.headers, 58 | keepalive_idle = 30, 59 | keepalive_interval = 60, 60 | connect_timeout = obj.connect_timeout, 61 | read_timeout = obj.read_timeout, 62 | dns_cache_timeout = obj.dns_cache_timeout, }) 63 | end ) 64 | end 65 | end 66 | 67 | -- Join test 68 | fiber.create(function() 69 | 70 | local os = require('os') 71 | local yaml = require('yaml') 72 | local rest = num 73 | 74 | local ticks = 0 75 | 76 | while true do 77 | 78 | fiber.sleep(1) 79 | 80 | for i = 1, num do 81 | 82 | local obj = curls[i] 83 | 84 | if obj.http ~= nil and obj.http:stat().active_requests == 0 then 85 | local st = obj.http:stat() 86 | assert(st.sockets_added == st.sockets_deleted) 87 | assert(st.active_requests == 0) 88 | assert(st.loop_calls > 0) 89 | 90 | local pst = obj.http:pool_stat() 91 | assert(pst.pool_size == pst.free) 92 | 93 | obj.http:free() 94 | rest = rest - 1 95 | curls[i].http = nil 96 | end 97 | 98 | end 99 | 100 | if rest <= 0 then 101 | break 102 | end 103 | 104 | ticks = ticks + 1 105 | 106 | -- Test failed 107 | if ticks > 80 then 108 | os.exit(1) 109 | end 110 | 111 | end 112 | 113 | print('[+] load OK') 114 | 115 | os.exit(0) 116 | end ) 117 | -------------------------------------------------------------------------------- /tests/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -x 4 | 5 | # Don't copy since we build into the root [[[ 6 | #cp -f build/curl/driver.so curl/driver.so 7 | # ]]] 8 | 9 | tarantool tests/example.lua 10 | tarantool tests/bugs.lua 11 | tarantool tests/async.lua 12 | ./tests/server.js & 13 | tarantool tests/load.lua 14 | kill -s TERM %1 15 | 16 | echo '[+] OK' 17 | -------------------------------------------------------------------------------- /tests/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * Copyright (C) 2016 - 2017 Tarantool AUTHORS: please see AUTHORS file. 4 | * 5 | * Redistribution and use in source and binary forms, with or 6 | * without modification, are permitted provided that the following 7 | * conditions are met: 8 | * 9 | * 1. Redistributions of source code must retain the above 10 | * copyright notice, this list of conditions and the 11 | * following disclaimer. 12 | * 13 | * 2. Redistributions in binary form must reproduce the above 14 | * copyright notice, this list of conditions and the following 15 | * disclaimer in the documentation and/or other materials 16 | * provided with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 22 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 23 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 26 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 29 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | var http = require('http'); 34 | 35 | http.createServer(function (req, res) { 36 | setTimeout(function () { 37 | res.writeHead(200, {'Content-Type': 'text/plain'}); 38 | res.end("Hello World"); 39 | }, 1 ) 40 | }).on('connection', function (socket) { 41 | socket.setTimeout(10000*2); 42 | }).listen(10000); 43 | --------------------------------------------------------------------------------