diff --git a/README.md b/README.md index f2d460e..2a5e8db 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,7 @@ As seen in the examples, you can unspool the array of `http_header` tuples into ## CURL Options -Select [CURL options](https://curl.se/libcurl/c/curl_easy_setopt.html) are available to set using the `http_set_curlopt(curlopt VARCHAR, value varchar)` function. +Select [CURL options](https://curl.se/libcurl/c/curl_easy_setopt.html) are available to set using the SQL `SET` command and the appropriate option name. * [CURLOPT_DNS_SERVERS](https://curl.se/libcurl/c/CURLOPT_DNS_SERVERS.html) * [CURLOPT_PROXY](https://curl.se/libcurl/c/CURLOPT_PROXY.html) @@ -279,22 +279,34 @@ Select [CURL options](https://curl.se/libcurl/c/curl_easy_setopt.html) are avail * [CURLOPT_CONNECTTIMEOUT_MS](https://curl.se/libcurl/c/CURLOPT_CONNECTTIMEOUT_MS.html) * [CURLOPT_USERAGENT](https://curl.se/libcurl/c/CURLOPT_USERAGENT.html) - For example, ```sql --- Set the PROXYPORT option -SELECT http_set_curlopt('CURLOPT_PROXYPORT', '12345'); +-- Set the curlopt_proxyport option +SET http.curlopt_proxyport = '12345'; + +-- View the curlopt_proxyport option +SHOW http.curlopt_proxyport; --- List all currently set options +-- View *all* currently set options SELECT * FROM http_list_curlopt(); ``` Will set the proxy port option for the lifetime of the database connection. You can reset all CURL options to their defaults using the `http_reset_curlopt()` function. -Using this extension as a background automated process without supervision (e.g as a trigger) may have unintended consequences for other servers. -It is considered a best practice to share contact information with your requests, -so that administrators can reach you in case your HTTP calls get out of control. +You can permanently set the CURL options for a database or role, using the `ALTER DATABASE` and `ALTER ROLE` commands. + +```sql +-- Applies to all roles in the database +ALTER DATABASE mydb SET http.curlopt_tlsauth_password = 'secret'; + +-- Applies to just one role in the database +ALTER ROLE myapp IN mydb SET http.curlopt_tlsauth_password = 'secret'; +``` + +## User Agents + +Using this extension as a background automated process without supervision (e.g as a trigger) may have unintended consequences for other servers. It is considered a best practice to share contact information with your requests, so that administrators can reach you in case your HTTP calls get out of control. Certain API policies (e.g. [Wikimedia User-Agent policy](https://meta.wikimedia.org/wiki/User-Agent_policy)) may even require sharing specific contact information with each request. Others may disallow (via `robots.txt`) certain agents they don't recognize. @@ -302,30 +314,32 @@ with each request. Others may disallow (via `robots.txt`) certain agents they do For such cases you can set the `CURLOPT_USERAGENT` option ```sql -SELECT http_set_curlopt('CURLOPT_USERAGENT', - 'Examplebot/2.1 (+http://www.example.com/bot.html) Contact abuse@example.com'); +SET http.curlopt_useragent = 'PgBot/2.1 (+http://www.example.com/bot.html) Contact abuse@pgbot.com'; -SELECT status, content::json ->> 'user-agent' FROM http_get('http://httpbun.com/user-agent'); +SELECT status, content::json->'headers'->>'User-Agent' + FROM http_get('http://httpbun.com/headers'); ``` ``` status | user_agent --------+----------------------------------------------------------- - 200 | Examplebot/2.1 (+http://www.example.com/bot.html) Contact abuse@example.com + 200 | PgBot/2.1 (+http://www.example.com/bot.html) Contact abuse@pgbot.com ``` ## Keep-Alive & Timeouts -*The `http_reset_curlopt()` approach described above is recommended. The global variables below will be deprecated and removed over time.* - By default each request uses a fresh connection and assures that the connection is closed when the request is done. This behavior reduces the chance of consuming system resources (sockets) as the extension runs over extended periods of time. High-performance applications may wish to enable keep-alive and connection persistence to reduce latency and enhance throughput. The following GUC variable changes the behavior of the http extension to maintain connections as long as possible: - http.keepalive = 'on' +```sql +SET http.curlopt_tcp_keepalive = 1; +``` By default a 5 second timeout is set for the completion of a request. If a different timeout is desired the following GUC variable can be used to set it in milliseconds: - http.timeout_msec = 200 +```sql +SET http.curlopt_timeout_msec = 200; +``` ## Installation diff --git a/http.c b/http.c index 513b3b2..e9dc877 100644 --- a/http.c +++ b/http.c @@ -136,68 +136,70 @@ typedef enum { /* CURLOPT string/enum value mapping */ typedef struct { - char *curlopt_str; - char *curlopt_val; CURLoption curlopt; http_curlopt_type curlopt_type; bool superuser_only; + char *curlopt_str; + char *curlopt_val; + char *curlopt_guc; } http_curlopt; /* CURLOPT values we allow user to set at run-time */ /* Be careful adding these, as they can be a security risk */ static http_curlopt settable_curlopts[] = { - { "CURLOPT_CAINFO", NULL, CURLOPT_CAINFO, CURLOPT_STRING, false }, - { "CURLOPT_TIMEOUT", NULL, CURLOPT_TIMEOUT, CURLOPT_LONG, false }, - { "CURLOPT_TIMEOUT_MS", NULL, CURLOPT_TIMEOUT_MS, CURLOPT_LONG, false }, - { "CURLOPT_CONNECTTIMEOUT", NULL, CURLOPT_CONNECTTIMEOUT, CURLOPT_LONG, false }, - { "CURLOPT_CONNECTTIMEOUT_MS", NULL, CURLOPT_CONNECTTIMEOUT_MS, CURLOPT_LONG, false }, - { "CURLOPT_USERAGENT", NULL, CURLOPT_USERAGENT, CURLOPT_STRING, false }, - { "CURLOPT_USERPWD", NULL, CURLOPT_USERPWD, CURLOPT_STRING, false }, - { "CURLOPT_IPRESOLVE", NULL, CURLOPT_IPRESOLVE, CURLOPT_LONG, false }, + { CURLOPT_CAINFO, CURLOPT_STRING, false, "CURLOPT_CAINFO", NULL, NULL }, + { CURLOPT_TIMEOUT, CURLOPT_LONG, false, "CURLOPT_TIMEOUT", NULL, NULL }, + { CURLOPT_TIMEOUT_MS, CURLOPT_LONG, false, "CURLOPT_TIMEOUT_MS", NULL, NULL }, + { CURLOPT_CONNECTTIMEOUT, CURLOPT_LONG, false, "CURLOPT_CONNECTTIMEOUT", NULL, NULL }, + { CURLOPT_CONNECTTIMEOUT_MS, CURLOPT_LONG, false, "CURLOPT_CONNECTTIMEOUT_MS", NULL, NULL }, + { CURLOPT_USERAGENT, CURLOPT_STRING, false, "CURLOPT_USERAGENT", NULL, NULL }, + { CURLOPT_USERPWD, CURLOPT_STRING, false, "CURLOPT_USERPWD", NULL, NULL }, + { CURLOPT_IPRESOLVE, CURLOPT_LONG, false, "CURLOPT_IPRESOLVE", NULL, NULL }, #if LIBCURL_VERSION_NUM >= 0x070903 /* 7.9.3 */ - { "CURLOPT_SSLCERTTYPE", NULL, CURLOPT_SSLCERTTYPE, CURLOPT_STRING, false }, + { CURLOPT_SSLCERTTYPE, CURLOPT_STRING, false, "CURLOPT_SSLCERTTYPE", NULL, NULL }, #endif #if LIBCURL_VERSION_NUM >= 0x070e01 /* 7.14.1 */ - { "CURLOPT_PROXY", NULL, CURLOPT_PROXY, CURLOPT_STRING, false }, - { "CURLOPT_PROXYPORT", NULL, CURLOPT_PROXYPORT, CURLOPT_LONG, false }, + { CURLOPT_PROXY, CURLOPT_STRING, false, "CURLOPT_PROXY", NULL, NULL }, + { CURLOPT_PROXYPORT, CURLOPT_LONG, false, "CURLOPT_PROXYPORT", NULL, NULL }, #endif #if LIBCURL_VERSION_NUM >= 0x071301 /* 7.19.1 */ - { "CURLOPT_PROXYUSERNAME", NULL, CURLOPT_PROXYUSERNAME, CURLOPT_STRING, false }, - { "CURLOPT_PROXYPASSWORD", NULL, CURLOPT_PROXYPASSWORD, CURLOPT_STRING, false }, + { CURLOPT_PROXYUSERNAME, CURLOPT_STRING, false, "CURLOPT_PROXYUSERNAME", NULL, NULL }, + { CURLOPT_PROXYPASSWORD, CURLOPT_STRING, false, "CURLOPT_PROXYPASSWORD", NULL, NULL }, #endif #if LIBCURL_VERSION_NUM >= 0x071504 /* 7.21.4 */ - { "CURLOPT_TLSAUTH_USERNAME", NULL, CURLOPT_TLSAUTH_USERNAME, CURLOPT_STRING, false }, - { "CURLOPT_TLSAUTH_PASSWORD", NULL, CURLOPT_TLSAUTH_PASSWORD, CURLOPT_STRING, false }, - { "CURLOPT_TLSAUTH_TYPE", NULL, CURLOPT_TLSAUTH_TYPE, CURLOPT_STRING, false }, + { CURLOPT_TLSAUTH_USERNAME, CURLOPT_STRING, false, "CURLOPT_TLSAUTH_USERNAME", NULL, NULL }, + { CURLOPT_TLSAUTH_PASSWORD, CURLOPT_STRING, false, "CURLOPT_TLSAUTH_PASSWORD", NULL, NULL }, + { CURLOPT_TLSAUTH_TYPE, CURLOPT_STRING, false, "CURLOPT_TLSAUTH_TYPE", NULL, NULL }, #endif #if LIBCURL_VERSION_NUM >= 0x071800 /* 7.24.0 */ - { "CURLOPT_DNS_SERVERS", NULL, CURLOPT_DNS_SERVERS, CURLOPT_STRING, false }, + { CURLOPT_DNS_SERVERS, CURLOPT_STRING, false, "CURLOPT_DNS_SERVERS", NULL, NULL }, #endif #if LIBCURL_VERSION_NUM >= 0x071900 /* 7.25.0 */ - { "CURLOPT_TCP_KEEPALIVE", NULL, CURLOPT_TCP_KEEPALIVE, CURLOPT_LONG, false }, - { "CURLOPT_TCP_KEEPIDLE", NULL, CURLOPT_TCP_KEEPIDLE, CURLOPT_LONG, false }, + { CURLOPT_TCP_KEEPALIVE, CURLOPT_LONG, false, "CURLOPT_TCP_KEEPALIVE", NULL, NULL }, + { CURLOPT_TCP_KEEPIDLE, CURLOPT_LONG, false, "CURLOPT_TCP_KEEPIDLE", NULL, NULL }, #endif #if LIBCURL_VERSION_NUM >= 0x072500 /* 7.37.0 */ - { "CURLOPT_SSL_VERIFYHOST", NULL, CURLOPT_SSL_VERIFYHOST, CURLOPT_LONG, false }, - { "CURLOPT_SSL_VERIFYPEER", NULL, CURLOPT_SSL_VERIFYPEER, CURLOPT_LONG, false }, + { CURLOPT_SSL_VERIFYHOST, CURLOPT_LONG, false, "CURLOPT_SSL_VERIFYHOST", NULL, NULL }, + { CURLOPT_SSL_VERIFYPEER, CURLOPT_LONG, false, "CURLOPT_SSL_VERIFYPEER", NULL, NULL }, #endif - { "CURLOPT_SSLCERT", NULL, CURLOPT_SSLCERT, CURLOPT_STRING, false }, - { "CURLOPT_SSLKEY", NULL, CURLOPT_SSLKEY, CURLOPT_STRING, false }, + { CURLOPT_SSLCERT, CURLOPT_STRING, false, "CURLOPT_SSLCERT", NULL, NULL }, + { CURLOPT_SSLKEY, CURLOPT_STRING, false, "CURLOPT_SSLKEY", NULL, NULL }, #if LIBCURL_VERSION_NUM >= 0x073400 /* 7.52.0 */ - { "CURLOPT_PRE_PROXY", NULL, CURLOPT_PRE_PROXY, CURLOPT_STRING, false }, - { "CURLOPT_PROXY_CAINFO", NULL, CURLOPT_PROXY_TLSAUTH_USERNAME, CURLOPT_STRING, false }, - { "CURLOPT_PROXY_TLSAUTH_USERNAME", NULL, CURLOPT_PROXY_TLSAUTH_USERNAME, CURLOPT_STRING, false }, - { "CURLOPT_PROXY_TLSAUTH_PASSWORD", NULL, CURLOPT_PROXY_TLSAUTH_PASSWORD, CURLOPT_STRING, false }, - { "CURLOPT_PROXY_TLSAUTH_TYPE", NULL, CURLOPT_PROXY_TLSAUTH_TYPE, CURLOPT_STRING, false }, + { CURLOPT_PRE_PROXY, CURLOPT_STRING, false, "CURLOPT_PRE_PROXY", NULL, NULL }, + { CURLOPT_PROXY_TLSAUTH_USERNAME, CURLOPT_STRING, false, "CURLOPT_PROXY_CAINFO", NULL, NULL }, + { CURLOPT_PROXY_TLSAUTH_USERNAME, CURLOPT_STRING, false, "CURLOPT_PROXY_TLSAUTH_USERNAME", NULL, NULL }, + { CURLOPT_PROXY_TLSAUTH_PASSWORD, CURLOPT_STRING, false, "CURLOPT_PROXY_TLSAUTH_PASSWORD", NULL, NULL }, + { CURLOPT_PROXY_TLSAUTH_TYPE, CURLOPT_STRING, false, "CURLOPT_PROXY_TLSAUTH_TYPE", NULL, NULL }, #endif #if LIBCURL_VERSION_NUM >= 0x074700 /* 7.71.0 */ - { "CURLOPT_SSLKEY_BLOB", NULL, CURLOPT_SSLKEY_BLOB, CURLOPT_BLOB, false }, - { "CURLOPT_SSLCERT_BLOB", NULL, CURLOPT_SSLCERT_BLOB, CURLOPT_BLOB, false }, + { CURLOPT_SSLKEY_BLOB, CURLOPT_BLOB, false, "CURLOPT_SSLKEY_BLOB", NULL, NULL }, + { CURLOPT_SSLCERT_BLOB, CURLOPT_BLOB, false, "CURLOPT_SSLCERT_BLOB", NULL, NULL }, #endif - { NULL, NULL, 0, 0, false } /* Array null terminator */ + { 0, 0, false, NULL, NULL, NULL } /* Array null terminator */ }; + /* Function signatures */ void _PG_init(void); void _PG_fini(void); @@ -205,11 +207,7 @@ static size_t http_writeback(void *contents, size_t size, size_t nmemb, void *us static size_t http_readback(void *buffer, size_t size, size_t nitems, void *instream); /* Global variables */ -bool g_use_keepalive; -int g_timeout_msec; - CURL * g_http_handle = NULL; -List * g_curl_opts = NIL; /* * Interrupt support is dependent on CURLOPT_XFERINFOFUNCTION which is @@ -293,32 +291,78 @@ http_malloc(size_t sz) #endif +static char * +http_strtolower(const char *input) +{ + char *ptr, *output; + if (input == NULL) + return NULL; + + /* Allocate memory for the output string */ + output = palloc(strlen(input) + 1); + ptr = output; + + while (*input) + { + *ptr++ = tolower((unsigned char) *input); + input++; + } + + *ptr = '\0'; // Null-terminate the string + return output; +} + + +static void +http_guc_init_opt(http_curlopt *opt) +{ + char *opt_name_lower = http_strtolower(opt->curlopt_str); + char *opt_name = psprintf("http.%s", opt_name_lower); + + const char *url_tmpl = "https://curl.se/libcurl/c/%s.html"; + char *opt_url = psprintf(url_tmpl, opt->curlopt_str); + opt->curlopt_guc = guc_strdup(ERROR, opt_name); + + DefineCustomStringVariable( + opt_name, // const char *name + guc_strdup(ERROR, opt_url), // const char *short_desc + NULL, // const char *long_desc + &(opt->curlopt_val), // char **valueAddr + NULL, // const char *bootValue + opt->superuser_only ? PGC_SUSET : PGC_USERSET, // GucContext context + 0, // int flags + NULL, // GucStringCheckHook check_hook + NULL, // GucStringAssignHook assign_hook + NULL // GucShowHook show_hook + ); + + pfree(opt_name_lower); + pfree(opt_name); + pfree(opt_url); +} + +static void +http_guc_init() +{ + http_curlopt *opt = settable_curlopts; + while (opt->curlopt) + { + http_guc_init_opt(opt); + opt++; + } +} + + /* Startup */ void _PG_init(void) { - DefineCustomBoolVariable("http.keepalive", - "reuse existing connections with keepalive", - NULL, - &g_use_keepalive, - false, - PGC_USERSET, - GUC_NOT_IN_SAMPLE, - NULL, - NULL, - NULL); - - DefineCustomIntVariable("http.timeout_msec", - "request completion timeout in milliseconds", - NULL, - &g_timeout_msec, - 0, - 0, - INT_MAX, - PGC_USERSET, - GUC_NOT_IN_SAMPLE | GUC_UNIT_MS, - NULL, - NULL, - NULL); + + /* + * Initialize the DefineCustomStringVariable GUC + * functions to allow "SET http.curlopt_var = value" + * to manipulate CURL options. + */ + http_guc_init(); #ifdef HTTP_MEM_CALLBACKS /* @@ -675,12 +719,12 @@ typname_get_tupledesc(const char *extname, const char *typname) #if PG_VERSION_NUM >= 120000 typoid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, - PointerGetDatum(typname), - ObjectIdGetDatum(extschemaoid)); + PointerGetDatum(typname), + ObjectIdGetDatum(extschemaoid)); #else typoid = GetSysCacheOid2(TYPENAMENSP, - PointerGetDatum(typname), - ObjectIdGetDatum(extschemaoid)); + PointerGetDatum(typname), + ObjectIdGetDatum(extschemaoid)); #endif if ( OidIsValid(typoid) ) @@ -787,6 +831,21 @@ http_check_curl_version(const curl_version_info_data *version_info) } } + +static bool +curlopt_is_set(CURLoption curlopt) +{ + http_curlopt *opt = settable_curlopts; + while (opt->curlopt) + { + if (opt->curlopt == curlopt && opt->curlopt_val) + return true; + opt++; + } + return false; +} + + static bool set_curlopt(CURL* handle, const http_curlopt *opt) { @@ -799,7 +858,7 @@ set_curlopt(CURL* handle, const http_curlopt *opt) if (opt->curlopt_type == CURLOPT_STRING) { err = curl_easy_setopt(handle, opt->curlopt, opt->curlopt_val); - elog(DEBUG2, "pgsql-http: set '%s' to value '%s', return value = %d", opt->curlopt_str, opt->curlopt_val, err); + elog(DEBUG2, "pgsql-http: set '%s' to value '%s', return value = %d", opt->curlopt_guc, opt->curlopt_val, err); } /* Argument is a long */ else if (opt->curlopt_type == CURLOPT_LONG) @@ -808,10 +867,10 @@ set_curlopt(CURL* handle, const http_curlopt *opt) errno = 0; value_long = strtol(opt->curlopt_val, NULL, 10); if ( errno == EINVAL || errno == ERANGE ) - elog(ERROR, "invalid integer provided for '%s'", opt->curlopt_str); + elog(ERROR, "invalid integer provided for '%s'", opt->curlopt_guc); err = curl_easy_setopt(handle, opt->curlopt, value_long); - elog(DEBUG2, "pgsql-http: set '%s' to value '%ld', return value = %d", opt->curlopt_str, value_long, err); + elog(DEBUG2, "pgsql-http: set '%s' to value '%ld', return value = %d", opt->curlopt_guc, value_long, err); } /* Only used for CURLOPT_SSLKEY_BLOB and CURLOPT_SSLCERT_BLOB */ else if (opt->curlopt_type == CURLOPT_BLOB) @@ -825,12 +884,12 @@ set_curlopt(CURL* handle, const http_curlopt *opt) elog(DEBUG2, "pgsql-http: set 'CURLOPT_SSLKEYTYPE' to value 'PEM', return value = %d", err); err = curl_easy_setopt(handle, opt->curlopt, &blob); - elog(DEBUG2, "pgsql-http: set '%s' to value '%s', return value = %d", opt->curlopt_str, opt->curlopt_val, err); + elog(DEBUG2, "pgsql-http: set '%s' to value '%s', return value = %d", opt->curlopt_guc, opt->curlopt_val, err); } else { /* Never get here */ - elog(ERROR, "invalid curlopt_type"); + elog(ERROR, "invalid curlopt_type, '%d'", opt->curlopt_type); } if ( err != CURLE_OK ) @@ -845,9 +904,8 @@ set_curlopt(CURL* handle, const http_curlopt *opt) static CURL * http_get_handle() { - http_curlopt opt; CURL *handle = g_http_handle; - size_t i = 0; + http_curlopt *opt = settable_curlopts; /* Initialize the global handle if needed */ if (!handle) @@ -866,20 +924,19 @@ http_get_handle() curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT_MS, 1000); curl_easy_setopt(handle, CURLOPT_TIMEOUT_MS, 5000); - /* Set the user agent. If not set, use PG_VERSION as default */ - curl_easy_setopt(handle, CURLOPT_USERAGENT, PG_VERSION_STR); + /* Set the user agent. If not set, use PG_VERSION as default */ + curl_easy_setopt(handle, CURLOPT_USERAGENT, PG_VERSION_STR); if (!handle) ereport(ERROR, (errmsg("Unable to initialize CURL"))); /* Bring in any options the user has set this session */ - while (1) + while (opt->curlopt) { - opt = settable_curlopts[i++]; - if (!opt.curlopt_str) break; /* Option value is already set */ - if (opt.curlopt_val) - set_curlopt(handle, &opt); + if (opt->curlopt_val) + set_curlopt(handle, opt); + opt++; } g_http_handle = handle; @@ -894,18 +951,17 @@ Datum http_reset_curlopt(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(http_reset_curlopt); Datum http_reset_curlopt(PG_FUNCTION_ARGS) { - size_t i = 0; + http_curlopt *opt = settable_curlopts; /* Set up global HTTP handle */ CURL * handle = http_get_handle(); curl_easy_reset(handle); /* Clean out the settable_curlopts global cache */ - while (1) + while (opt->curlopt) { - http_curlopt *opt = settable_curlopts + i++; - if (!opt->curlopt_str) break; if (opt->curlopt_val) pfree(opt->curlopt_val); opt->curlopt_val = NULL; + opt++; } PG_RETURN_BOOL(true); @@ -916,7 +972,7 @@ PG_FUNCTION_INFO_V1(http_list_curlopt); Datum http_list_curlopt(PG_FUNCTION_ARGS) { struct list_state { - size_t i; /* read position */ + size_t i; /* read position */ }; MemoryContext oldcontext, newcontext; @@ -927,25 +983,25 @@ Datum http_list_curlopt(PG_FUNCTION_ARGS) if (SRF_IS_FIRSTCALL()) { - funcctx = SRF_FIRSTCALL_INIT(); + funcctx = SRF_FIRSTCALL_INIT(); newcontext = funcctx->multi_call_memory_ctx; oldcontext = MemoryContextSwitchTo(newcontext); state = palloc0(sizeof(*state)); funcctx->user_fctx = state; if(get_call_result_type(fcinfo, 0, &funcctx->tuple_desc) != TYPEFUNC_COMPOSITE) - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("composite-returning function called in context that cannot accept a composite"))); + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("composite-returning function called in context that cannot accept a composite"))); - BlessTupleDesc(funcctx->tuple_desc); - MemoryContextSwitchTo(oldcontext); - } + BlessTupleDesc(funcctx->tuple_desc); + MemoryContextSwitchTo(oldcontext); + } - funcctx = SRF_PERCALL_SETUP(); - state = funcctx->user_fctx; + funcctx = SRF_PERCALL_SETUP(); + state = funcctx->user_fctx; while (1) { - Datum result; + Datum result; HeapTuple tuple; text *option, *value; http_curlopt *opt = settable_curlopts + state->i++; @@ -958,8 +1014,8 @@ Datum http_list_curlopt(PG_FUNCTION_ARGS) vals[0] = PointerGetDatum(option); vals[1] = PointerGetDatum(value); nulls[0] = nulls[1] = 0; - tuple = heap_form_tuple(funcctx->tuple_desc, vals, nulls); - result = HeapTupleGetDatum(tuple); + tuple = heap_form_tuple(funcctx->tuple_desc, vals, nulls); + result = HeapTupleGetDatum(tuple); SRF_RETURN_NEXT(funcctx, result); } @@ -973,10 +1029,10 @@ Datum http_set_curlopt(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(http_set_curlopt); Datum http_set_curlopt(PG_FUNCTION_ARGS) { - size_t i = 0; char *curlopt, *value; text *curlopt_txt, *value_txt; CURL *handle; + http_curlopt *opt = settable_curlopts; /* Version check */ http_check_curl_version(curl_version_info(CURLVERSION_NOW)); @@ -994,16 +1050,15 @@ Datum http_set_curlopt(PG_FUNCTION_ARGS) curlopt = text_to_cstring(curlopt_txt); value = text_to_cstring(value_txt); - while (1) + while (opt->curlopt) { - http_curlopt *opt = settable_curlopts + i++; - if (!opt->curlopt_str) break; if (strcasecmp(opt->curlopt_str, curlopt) == 0) { - if (opt->curlopt_val) pfree(opt->curlopt_val); - opt->curlopt_val = MemoryContextStrdup(CacheMemoryContext, value); + if (opt->curlopt_val) guc_free(opt->curlopt_val); + opt->curlopt_val = guc_strdup(ERROR, value); PG_RETURN_BOOL(set_curlopt(handle, opt)); } + opt++; } elog(ERROR, "curl option '%s' is not available for run-time configuration", curlopt); @@ -1125,8 +1180,7 @@ Datum http_request(PG_FUNCTION_ARGS) CURL_SETOPT(g_http_handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); #endif - - if ( g_use_keepalive ) + if ( curlopt_is_set(CURLOPT_TCP_KEEPALIVE) ) { /* Keep sockets held open */ CURL_SETOPT(g_http_handle, CURLOPT_FORBID_REUSE, 0); @@ -1152,10 +1206,6 @@ Datum http_request(PG_FUNCTION_ARGS) CURL_SETOPT(g_http_handle, CURLOPT_NOPROGRESS, 0); #endif - /* Set up the HTTP timeout */ - if (g_timeout_msec > 0) - CURL_SETOPT(g_http_handle, CURLOPT_TIMEOUT_MS, g_timeout_msec); - /* Set the HTTP content encoding to all curl supports */ CURL_SETOPT(g_http_handle, CURLOPT_ACCEPT_ENCODING, ""); @@ -1166,7 +1216,7 @@ Datum http_request(PG_FUNCTION_ARGS) CURL_SETOPT(g_http_handle, CURLOPT_MAXREDIRS, 5); } - if ( g_use_keepalive ) + if ( curlopt_is_set(CURLOPT_TCP_KEEPALIVE) ) { /* Add a keep alive option to the headers to reuse network sockets */ headers = curl_slist_append(headers, "Connection: Keep-Alive"); @@ -1326,10 +1376,10 @@ Datum http_request(PG_FUNCTION_ARGS) } /* Prepare our return object */ - if (get_call_result_type(fcinfo, 0, &tup_desc) != TYPEFUNC_COMPOSITE) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("%s called with incompatible return type", __func__))); - } + if (get_call_result_type(fcinfo, 0, &tup_desc) != TYPEFUNC_COMPOSITE) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("%s called with incompatible return type", __func__))); + } ncolumns = tup_desc->natts; values = palloc0(sizeof(Datum)*ncolumns); @@ -1422,7 +1472,7 @@ Datum http_request(PG_FUNCTION_ARGS) /* Clean up */ ReleaseTupleDesc(tup_desc); - if ( !g_use_keepalive ) + if ( ! curlopt_is_set(CURLOPT_TCP_KEEPALIVE) ) { curl_easy_cleanup(g_http_handle); g_http_handle = NULL; @@ -1580,11 +1630,11 @@ Datum urlencode_jsonb(PG_FUNCTION_ARGS) /* Read the value for this key */ #if PG_VERSION_NUM < 130000 { - JsonbValue k; - k.type = jbvString; - k.val.string.val = key; - k.val.string.len = strlen(key); - v = *findJsonbValueFromContainer(&jb->root, JB_FOBJECT, &k); + JsonbValue k; + k.type = jbvString; + k.val.string.val = key; + k.val.string.len = strlen(key); +v = *findJsonbValueFromContainer(&jb->root, JB_FOBJECT, &k); } #else getKeyJsonValueFromContainer(&jb->root, key, strlen(key), &v);