/** * rest_post_method - performs an HTTP POST request, stores results in pvars * @msg: sip message struct * @url: HTTP URL to be queried * @ctype: Value for the "Content-Type: " header of the request * @body: Body of the request * @body_pv: pseudo var which will hold the result body * @ctype_pv: pvar which will hold the result content type * @code_pv: pvar to hold the HTTP return code */ int rest_post_method(struct sip_msg *msg, char *url, char *body, char *ctype, pv_spec_p body_pv, pv_spec_p ctype_pv, pv_spec_p code_pv) { CURLcode rc; CURL *handle = NULL; long http_rc; str st = { 0, 0 }; str res_body = { 0, 0 }; pv_value_t pv_val; handle = curl_easy_init(); if (!handle) { LM_ERR("Init curl handle failed!\n"); clean_header_list; return -1; } if (ctype) { sprintf(print_buff, "Content-Type: %s", ctype); header_list = curl_slist_append(header_list, print_buff); } if (header_list) w_curl_easy_setopt(handle, CURLOPT_HTTPHEADER, header_list); w_curl_easy_setopt(handle, CURLOPT_URL, url); w_curl_easy_setopt(handle, CURLOPT_POST, 1); w_curl_easy_setopt(handle, CURLOPT_POSTFIELDS, body); w_curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, connection_timeout); w_curl_easy_setopt(handle, CURLOPT_TIMEOUT, curl_timeout); w_curl_easy_setopt(handle, CURLOPT_VERBOSE, 1); w_curl_easy_setopt(handle, CURLOPT_STDERR, stdout); w_curl_easy_setopt(handle, CURLOPT_FAILONERROR, 1); w_curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_func); w_curl_easy_setopt(handle, CURLOPT_WRITEDATA, &res_body); w_curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, header_func); w_curl_easy_setopt(handle, CURLOPT_HEADERDATA, &st); if (ssl_capath) w_curl_easy_setopt(handle, CURLOPT_CAPATH, ssl_capath); if (!ssl_verifypeer) w_curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L); if (!ssl_verifyhost) w_curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L); rc = curl_easy_perform(handle); clean_header_list; if (code_pv) { curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &http_rc); LM_DBG("Last response code: %ld\n", http_rc); pv_val.flags = PV_VAL_INT|PV_TYPE_INT; pv_val.ri = (int)http_rc; if (pv_set_value(msg, code_pv, 0, &pv_val) != 0) { LM_ERR("Set code pv value failed!\n"); goto cleanup; } } if (rc != CURLE_OK) { LM_ERR("curl_easy_perform: %s\n", curl_easy_strerror(rc)); goto cleanup; } trim(&res_body); pv_val.flags = PV_VAL_STR; pv_val.rs = res_body; if (pv_set_value(msg, body_pv, 0, &pv_val) != 0) { LM_ERR("Set body pv value failed!\n"); goto cleanup; } if (res_body.s) { pkg_free(res_body.s); } if (ctype_pv) { pv_val.rs = st; if (pv_set_value(msg, ctype_pv, 0, &pv_val) != 0) { LM_ERR("Set content type pv value failed!\n"); goto cleanup; } if (st.s) pkg_free(st.s); } curl_easy_cleanup(handle); return 1; cleanup: curl_easy_cleanup(handle); return -1; }
/** * rest_put_method - performs an HTTP PUT request, stores results in pvars * @msg: sip message struct * @url: HTTP URL to be queried * @ctype: Value for the "Content-Type: " header of the request * @body: Body of the request * @body_pv: pseudo var which will hold the result body * @ctype_pv: pvar which will hold the result content type * @code_pv: pvar to hold the HTTP return code */ int rest_put_method(struct sip_msg *msg, char *url, char *body, char *ctype, pv_spec_p body_pv, pv_spec_p ctype_pv, pv_spec_p code_pv) { CURLcode rc; long http_rc; str st = { 0, 0 }; str res_body = { NULL, 0 }, tbody, ttype; pv_value_t pv_val; rest_trace_param_t tparam; /*Init handle for first use*/ if (!sync_handle) { sync_handle = curl_easy_init(); if (!sync_handle) { LM_ERR("Init curl handle failed!\n"); goto cleanup; } } else { curl_easy_reset(sync_handle); } if (ctype) { sprintf(print_buff, "Content-Type: %s", ctype); header_list = curl_slist_append(header_list, print_buff); } if (tls_dom) { w_curl_easy_setopt(sync_handle, CURLOPT_SSLCERT, tls_dom->cert.s); w_curl_easy_setopt(sync_handle, CURLOPT_SSLKEY, tls_dom->pkey.s); tls_api.release_domain(tls_dom); tls_dom = NULL; } if (header_list) w_curl_easy_setopt(sync_handle, CURLOPT_HTTPHEADER, header_list); w_curl_easy_setopt(sync_handle, CURLOPT_URL, url); if (curl_http_version != CURL_HTTP_VERSION_NONE) w_curl_easy_setopt(sync_handle, CURLOPT_HTTP_VERSION, curl_http_version); w_curl_easy_setopt(sync_handle, CURLOPT_CUSTOMREQUEST, "PUT"); w_curl_easy_setopt(sync_handle, CURLOPT_POSTFIELDS, body); w_curl_easy_setopt(sync_handle, CURLOPT_CONNECTTIMEOUT, connection_timeout); w_curl_easy_setopt(sync_handle, CURLOPT_TIMEOUT, curl_timeout); w_curl_easy_setopt(sync_handle, CURLOPT_VERBOSE, 1); w_curl_easy_setopt(sync_handle, CURLOPT_STDERR, stdout); w_curl_easy_setopt(sync_handle, CURLOPT_FAILONERROR, 0); w_curl_easy_setopt(sync_handle, CURLOPT_WRITEFUNCTION, write_func); w_curl_easy_setopt(sync_handle, CURLOPT_WRITEDATA, &res_body); w_curl_easy_setopt(sync_handle, CURLOPT_HEADERFUNCTION, header_func); w_curl_easy_setopt(sync_handle, CURLOPT_HEADERDATA, &st); if (ssl_capath) w_curl_easy_setopt(sync_handle, CURLOPT_CAPATH, ssl_capath); if (!ssl_verifypeer) w_curl_easy_setopt(sync_handle, CURLOPT_SSL_VERIFYPEER, 0L); if (!ssl_verifyhost) w_curl_easy_setopt(sync_handle, CURLOPT_SSL_VERIFYHOST, 0L); /* trace rest request */ if ( rest_trace_enabled() ) { memset( &tparam, 0, sizeof tparam); tparam.callid = msg->callid->body; w_curl_easy_setopt(sync_handle, CURLOPT_DEBUGFUNCTION, trace_rest_request_cb); w_curl_easy_setopt(sync_handle, CURLOPT_DEBUGDATA, &tparam); } rc = curl_easy_perform(sync_handle); clean_header_list; if (code_pv) { curl_easy_getinfo(sync_handle, CURLINFO_RESPONSE_CODE, &http_rc); LM_DBG("Last response code: %ld\n", http_rc); pv_val.flags = PV_VAL_INT|PV_TYPE_INT; pv_val.ri = (int)http_rc; if (pv_set_value(msg, code_pv, 0, &pv_val) != 0) { LM_ERR("Set code pv value failed!\n"); return -1; } } if (rc != CURLE_OK) { LM_ERR("curl_easy_perform: %s\n", curl_easy_strerror(rc)); return -1; } tbody = res_body; trim(&tbody); pv_val.flags = PV_VAL_STR; pv_val.rs = tbody; if (pv_set_value(msg, body_pv, 0, &pv_val) != 0) { LM_ERR("Set body pv value failed!\n"); return -1; } if (res_body.s) { pkg_free(res_body.s); } if (ctype_pv) { ttype = st; trim(&ttype); pv_val.rs = ttype; if (pv_set_value(msg, ctype_pv, 0, &pv_val) != 0) { LM_ERR("Set content type pv value failed!\n"); return -1; } } if (st.s) pkg_free(st.s); return 1; cleanup: clean_header_list; if (tls_dom) { tls_api.release_domain(tls_dom); tls_dom = NULL; } return -1; }
/** * start_async_http_req - performs an HTTP request, stores results in pvars * - TCP connect phase is synchronous, due to libcurl limitations * - TCP read phase is asynchronous, thanks to the libcurl multi interface * * @msg: sip message struct * @method: HTTP verb * @url: HTTP URL to be queried * @req_body: Body of the request (NULL if not needed) * @req_ctype: Value for the "Content-Type: " header of the request (same as ^) * @out_handle: CURL easy handle used to perform the transfer * @body: reply body; gradually reallocated as data arrives * @ctype: will eventually hold the last "Content-Type" header of the reply */ int start_async_http_req(struct sip_msg *msg, enum rest_client_method method, char *url, char *req_body, char *req_ctype, CURL **out_handle, str *body, str *ctype) { CURL *handle; CURLcode rc; CURLMcode mrc; fd_set rset, wset, eset; int max_fd, fd, i; long busy_wait, timeout; long retry_time, check_time = 5; /* 5ms looping time */ int msgs_in_queue; CURLMsg *cmsg; if (transfers == FD_SETSIZE) { LM_ERR("too many ongoing tranfers: %d\n", FD_SETSIZE); clean_header_list; return ASYNC_NO_IO; } handle = curl_easy_init(); if (!handle) { LM_ERR("Init curl handle failed!\n"); clean_header_list; return ASYNC_NO_IO; } w_curl_easy_setopt(handle, CURLOPT_URL, url); switch (method) { case REST_CLIENT_POST: w_curl_easy_setopt(handle, CURLOPT_POST, 1); w_curl_easy_setopt(handle, CURLOPT_POSTFIELDS, req_body); if (req_ctype) { sprintf(print_buff, "Content-Type: %s", req_ctype); header_list = curl_slist_append(header_list, print_buff); w_curl_easy_setopt(handle, CURLOPT_HTTPHEADER, header_list); } break; case REST_CLIENT_GET: break; default: LM_ERR("Unsupported rest_client_method: %d, defaulting to GET\n", method); } if (header_list) w_curl_easy_setopt(handle, CURLOPT_HTTPHEADER, header_list); w_curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, connection_timeout); w_curl_easy_setopt(handle, CURLOPT_TIMEOUT, curl_timeout); w_curl_easy_setopt(handle, CURLOPT_VERBOSE, 1); w_curl_easy_setopt(handle, CURLOPT_FAILONERROR, 1); w_curl_easy_setopt(handle, CURLOPT_STDERR, stdout); w_curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_func); w_curl_easy_setopt(handle, CURLOPT_WRITEDATA, body); if (ctype) { w_curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, header_func); w_curl_easy_setopt(handle, CURLOPT_HEADERDATA, ctype); } if (ssl_capath) w_curl_easy_setopt(handle, CURLOPT_CAPATH, ssl_capath); if (!ssl_verifypeer) w_curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L); if (!ssl_verifyhost) w_curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L); curl_multi_add_handle(multi_handle, handle); timeout = connection_timeout_ms; /* obtain a read fd in "connection_timeout" seconds at worst */ for (timeout = connection_timeout_ms; timeout > 0; timeout -= busy_wait) { mrc = curl_multi_perform(multi_handle, &running_handles); if (mrc != CURLM_OK) { LM_ERR("curl_multi_perform: %s\n", curl_multi_strerror(mrc)); goto error; } mrc = curl_multi_timeout(multi_handle, &retry_time); if (mrc != CURLM_OK) { LM_ERR("curl_multi_timeout: %s\n", curl_multi_strerror(mrc)); goto error; } if (retry_time == -1) { LM_INFO("curl_multi_timeout() returned -1, pausing %ldms...\n", sleep_on_bad_timeout); busy_wait = sleep_on_bad_timeout; usleep(1000UL * busy_wait); continue; } busy_wait = retry_time < timeout ? retry_time : timeout; /** * libcurl is currently stuck in internal operations (connect) * we have to wait a bit until we receive a read fd */ for (i = 0; i < busy_wait; i += check_time) { /* transfer may have already been completed!! */ while ((cmsg = curl_multi_info_read(multi_handle, &msgs_in_queue))) { if (cmsg->easy_handle == handle && cmsg->msg == CURLMSG_DONE) { LM_DBG("done, no need for async!\n"); clean_header_list; *out_handle = handle; return ASYNC_SYNC; } } FD_ZERO(&rset); mrc = curl_multi_fdset(multi_handle, &rset, &wset, &eset, &max_fd); if (mrc != CURLM_OK) { LM_ERR("curl_multi_fdset: %s\n", curl_multi_strerror(mrc)); goto error; } if (max_fd != -1) { for (fd = 0; fd <= max_fd; fd++) { if (FD_ISSET(fd, &rset)) { LM_DBG(" >>>>>>>>>> fd %d ISSET(read)\n", fd); if (is_new_transfer(fd)) { LM_DBG("add fd to read list: %d\n", fd); add_transfer(fd); goto success; } } } } usleep(1000UL * check_time); } } LM_ERR("timeout while connecting to '%s' (%ld sec)\n", url, connection_timeout); goto error; success: clean_header_list; *out_handle = handle; return fd; error: mrc = curl_multi_remove_handle(multi_handle, handle); if (mrc != CURLM_OK) LM_ERR("curl_multi_remove_handle: %s\n", curl_multi_strerror(mrc)); cleanup: clean_header_list; curl_easy_cleanup(handle); return ASYNC_NO_IO; }
/** * start_async_http_req - performs an HTTP request, stores results in pvars * - TCP connect phase is synchronous, due to libcurl limitations * - TCP read phase is asynchronous, thanks to the libcurl multi interface * * @msg: sip message struct * @method: HTTP verb * @url: HTTP URL to be queried * @req_body: Body of the request (NULL if not needed) * @req_ctype: Value for the "Content-Type: " header of the request (same as ^) * @async_parm: output param, will contain async handles * @body: reply body; gradually reallocated as data arrives * @ctype: will eventually hold the last "Content-Type" header of the reply */ int start_async_http_req(struct sip_msg *msg, enum rest_client_method method, char *url, char *req_body, char *req_ctype, rest_async_param *async_parm, str *body, str *ctype) { CURL *handle; CURLcode rc; CURLMcode mrc; fd_set rset, wset, eset; int max_fd, fd; long busy_wait, timeout; long retry_time; OSS_CURLM *multi_list; CURLM *multi_handle; if (transfers == FD_SETSIZE) { LM_ERR("too many ongoing transfers: %d\n", FD_SETSIZE); goto cleanup; } handle = curl_easy_init(); if (!handle) { LM_ERR("Init curl handle failed!\n"); goto cleanup; } w_curl_easy_setopt(handle, CURLOPT_URL, url); if (curl_http_version != CURL_HTTP_VERSION_NONE) w_curl_easy_setopt(handle, CURLOPT_HTTP_VERSION, curl_http_version); if (tls_dom) { w_curl_easy_setopt(handle, CURLOPT_SSLCERT, tls_dom->cert.s); w_curl_easy_setopt(handle, CURLOPT_SSLKEY, tls_dom->pkey.s); tls_api.release_domain(tls_dom); tls_dom = NULL; } switch (method) { case REST_CLIENT_POST: w_curl_easy_setopt(handle, CURLOPT_POST, 1); w_curl_easy_setopt(handle, CURLOPT_POSTFIELDS, req_body); if (req_ctype) { sprintf(print_buff, "Content-Type: %s", req_ctype); header_list = curl_slist_append(header_list, print_buff); w_curl_easy_setopt(handle, CURLOPT_HTTPHEADER, header_list); } break; case REST_CLIENT_PUT: w_curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "PUT"); w_curl_easy_setopt(handle, CURLOPT_POSTFIELDS, req_body); if (req_ctype) { sprintf(print_buff, "Content-Type: %s", req_ctype); header_list = curl_slist_append(header_list, print_buff); w_curl_easy_setopt(handle, CURLOPT_HTTPHEADER, header_list); } break; case REST_CLIENT_GET: break; default: LM_ERR("Unsupported rest_client_method: %d, defaulting to GET\n", method); } if (header_list) w_curl_easy_setopt(handle, CURLOPT_HTTPHEADER, header_list); w_curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, connection_timeout); w_curl_easy_setopt(handle, CURLOPT_TIMEOUT, curl_timeout); w_curl_easy_setopt(handle, CURLOPT_VERBOSE, 1); w_curl_easy_setopt(handle, CURLOPT_STDERR, stdout); w_curl_easy_setopt(handle, CURLOPT_FAILONERROR, 0); w_curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_func); w_curl_easy_setopt(handle, CURLOPT_WRITEDATA, body); if (ctype) { w_curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, header_func); w_curl_easy_setopt(handle, CURLOPT_HEADERDATA, ctype); } if (ssl_capath) w_curl_easy_setopt(handle, CURLOPT_CAPATH, ssl_capath); if (!ssl_verifypeer) w_curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L); if (!ssl_verifyhost) w_curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L); if ( rest_trace_enabled() ) { async_parm->tparam = pkg_malloc(sizeof(rest_trace_param_t)); if ( !async_parm->tparam ) { LM_ERR("no more pkg mem!\n"); clean_header_list; return -1; } memset( async_parm->tparam, 0, sizeof *async_parm->tparam); async_parm->tparam->callid = msg->callid->body; w_curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, trace_rest_request_cb); w_curl_easy_setopt(handle, CURLOPT_DEBUGDATA, async_parm->tparam); } multi_list = get_multi(); if (!multi_list) { LM_INFO("failed to get a multi handle, doing blocking query\n"); rc = curl_easy_perform(handle); clean_header_list; async_parm->handle = handle; return ASYNC_SYNC; } multi_handle = multi_list->multi_handle; curl_multi_add_handle(multi_handle, handle); timeout = connection_timeout_ms; busy_wait = connect_poll_interval; /* obtain a read fd in "connection_timeout" seconds at worst */ for (timeout = connection_timeout_ms; timeout > 0; timeout -= busy_wait) { mrc = curl_multi_perform(multi_handle, &running_handles); if (mrc != CURLM_OK && mrc != CURLM_CALL_MULTI_PERFORM) { LM_ERR("curl_multi_perform: %s\n", curl_multi_strerror(mrc)); goto error; } mrc = curl_multi_timeout(multi_handle, &retry_time); if (mrc != CURLM_OK) { LM_ERR("curl_multi_timeout: %s\n", curl_multi_strerror(mrc)); goto error; } LM_DBG("libcurl TCP connect: we should wait up to %ldms " "(timeout=%ldms, poll=%ldms)!\n", retry_time, connection_timeout_ms, connect_poll_interval); if (retry_time == -1) { LM_DBG("curl_multi_timeout() returned -1, pausing %ldms...\n", busy_wait); goto busy_wait; } /* transfer may have already been completed!! */ if (running_handles == 0) { LM_DBG("done, no need for async!\n"); clean_header_list; async_parm->handle = handle; mrc = curl_multi_remove_handle(multi_handle, handle); if (mrc != CURLM_OK) { LM_ERR("curl_multi_remove_handle: %s\n", curl_multi_strerror(mrc)); } put_multi(multi_list); return ASYNC_SYNC; } FD_ZERO(&rset); mrc = curl_multi_fdset(multi_handle, &rset, &wset, &eset, &max_fd); if (mrc != CURLM_OK) { LM_ERR("curl_multi_fdset: %s\n", curl_multi_strerror(mrc)); goto error; } if (max_fd != -1) { for (fd = 0; fd <= max_fd; fd++) { if (FD_ISSET(fd, &rset)) { LM_DBG("ongoing transfer on fd %d\n", fd); if (is_new_transfer(fd)) { LM_DBG(">>> add fd %d to ongoing transfers\n", fd); add_transfer(fd); goto success; } } } } /* * from curl_multi_timeout() docs: "retry_time" milliseconds "at most!" * -> we'll wait only 1/10 of this time before retrying */ busy_wait = connect_poll_interval < timeout ? connect_poll_interval : timeout; busy_wait: /* libcurl seems to be stuck in internal operations (TCP connect?) */ LM_DBG("busy waiting %ldms ...\n", busy_wait); usleep(1000UL * busy_wait); } LM_ERR("timeout while connecting to '%s' (%ld sec)\n", url, connection_timeout); goto error; success: async_parm->header_list = header_list; async_parm->handle = handle; async_parm->multi_list = multi_list; header_list = NULL; return fd; error: mrc = curl_multi_remove_handle(multi_handle, handle); if (mrc != CURLM_OK) { LM_ERR("curl_multi_remove_handle: %s\n", curl_multi_strerror(mrc)); } put_multi(multi_list); curl_easy_cleanup(handle); cleanup: clean_header_list; if (tls_dom) { tls_api.release_domain(tls_dom); tls_dom = NULL; } return ASYNC_NO_IO; }