Apache HTTPD
md_curl.c
Go to the documentation of this file.
1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <assert.h>
18
19#include <curl/curl.h>
20
21#include <apr_lib.h>
22#include <apr_strings.h>
23#include <apr_buckets.h>
24
25#include "md_http.h"
26#include "md_log.h"
27#include "md_util.h"
28#include "md_curl.h"
29
30/**************************************************************************************************/
31/* md_http curl implementation */
32
33
52
61
62static size_t req_data_cb(void *data, size_t len, size_t nmemb, void *baton)
63{
65 size_t blen, read_len = 0, max_len = len * nmemb;
66 const char *bdata;
67 char *rdata = data;
69 apr_status_t rv;
70
71 while (body && !APR_BRIGADE_EMPTY(body) && max_len > 0) {
72 b = APR_BRIGADE_FIRST(body);
74 if (APR_BUCKET_IS_EOS(b)) {
75 body = NULL;
76 }
77 }
78 else {
79 rv = apr_bucket_read(b, &bdata, &blen, APR_BLOCK_READ);
80 if (rv == APR_SUCCESS) {
81 if (blen > max_len) {
83 blen = max_len;
84 }
85 memcpy(rdata, bdata, blen);
86 read_len += blen;
87 max_len -= blen;
88 rdata += blen;
89 }
90 else {
91 body = NULL;
92 if (!APR_STATUS_IS_EOF(rv)) {
93 /* everything beside EOF is an error */
95 }
96 }
97
98 }
100 }
101
102 return read_len;
103}
104
105static size_t resp_data_cb(void *data, size_t len, size_t nmemb, void *baton)
106{
107 md_curl_internals_t *internals = baton;
108 md_http_response_t *res = internals->response;
109 size_t blen = len * nmemb;
110 apr_status_t rv;
111
112 if (res->body) {
113 if (res->req->resp_limit) {
114 apr_off_t body_len = 0;
115 apr_brigade_length(res->body, 0, &body_len);
116 if (body_len + (apr_off_t)blen > res->req->resp_limit) {
117 return 0; /* signal curl failure */
118 }
119 }
120 rv = apr_brigade_write(res->body, NULL, NULL, (const char *)data, blen);
121 if (rv != APR_SUCCESS) {
122 /* returning anything != blen will make CURL fail this */
123 return 0;
124 }
125 }
126 return blen;
127}
128
129static size_t header_cb(void *buffer, size_t elen, size_t nmemb, void *baton)
130{
131 md_curl_internals_t *internals = baton;
132 md_http_response_t *res = internals->response;
133 size_t len, clen = elen * nmemb;
134 const char *name = NULL, *value = "", *b = buffer;
136
137 len = (clen && b[clen-1] == '\n')? clen-1 : clen;
138 len = (len && b[len-1] == '\r')? len-1 : len;
139 for (i = 0; i < len; ++i) {
140 if (b[i] == ':') {
141 name = apr_pstrndup(res->req->pool, b, i);
142 ++i;
143 while (i < len && b[i] == ' ') {
144 ++i;
145 }
146 if (i < len) {
147 value = apr_pstrndup(res->req->pool, b+i, len - i);
148 }
149 break;
150 }
151 }
152
153 if (name != NULL) {
154 apr_table_add(res->headers, name, value);
155 }
156 return clen;
157}
158
164
165static int curlify_headers(void *baton, const char *key, const char *value)
166{
168 const char *s;
169
170 if (strchr(key, '\r') || strchr(key, '\n')
171 || strchr(value, '\r') || strchr(value, '\n')) {
172 ctx->rv = APR_EINVAL;
173 return 0;
174 }
175 s = apr_psprintf(ctx->req->pool, "%s: %s", key, value);
176 ctx->hdrs = curl_slist_append(ctx->hdrs, s);
177 return 1;
178}
179
180/* Convert timeout values for curl. Since curl uses 0 to disable
181 * timeout, return at least 1 if the apr_time_t value is non-zero. */
183{
185 return ms? ms : (timeout? 1 : 0);
186}
187
189{
190 long s = (long)apr_time_sec(timeout);
191 return s? s : (timeout? 1 : 0);
192}
193
194static int curl_debug_log(CURL *curl, curl_infotype type, char *data, size_t size, void *baton)
195{
197
198 (void)curl;
199 switch (type) {
200 case CURLINFO_TEXT:
202 "req[%d]: info %s", req->id, apr_pstrndup(req->pool, data, size));
203 break;
206 "req[%d]: header --> %s", req->id, apr_pstrndup(req->pool, data, size));
207 break;
210 "req[%d]: header <-- %s", req->id, apr_pstrndup(req->pool, data, size));
211 break;
214 "req[%d]: data --> %ld bytes", req->id, (long)size);
216 md_data_t d;
217 const char *s;
219 md_data_to_hex(&s, 0, req->pool, &d);
221 "req[%d]: data(hex) --> %s", req->id, s);
222 }
223 break;
224 case CURLINFO_DATA_IN:
226 "req[%d]: data <-- %ld bytes", req->id, (long)size);
228 md_data_t d;
229 const char *s;
231 md_data_to_hex(&s, 0, req->pool, &d);
233 "req[%d]: data(hex) <-- %s", req->id, s);
234 }
235 break;
236 default:
237 break;
238 }
239 return 0;
240}
241
243{
244 md_curl_internals_t *internals;
245 CURL *curl;
247
248 curl = md_http_get_impl_data(req->http);
249 if (!curl) {
250 md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool, "creating curl instance");
251 curl = curl_easy_init();
252 if (!curl) {
253 rv = APR_EGENERAL;
254 goto leave;
255 }
262 }
263 else {
264 md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool, "reusing curl instance from http");
265 }
266
267 internals = apr_pcalloc(req->pool, sizeof(*internals));
268 internals->curl = curl;
269
270 internals->response = apr_pcalloc(req->pool, sizeof(md_http_response_t));
271 internals->response->req = req;
272 internals->response->status = 400;
273 internals->response->headers = apr_table_make(req->pool, 5);
274 internals->response->body = apr_brigade_create(req->pool, req->bucket_alloc);
275
276 curl_easy_setopt(curl, CURLOPT_URL, req->url);
277 if (!apr_strnatcasecmp("GET", req->method)) {
278 /* nop */
279 }
280 else if (!apr_strnatcasecmp("HEAD", req->method)) {
282 }
283 else if (!apr_strnatcasecmp("POST", req->method)) {
285 }
286 else {
288 }
289 curl_easy_setopt(curl, CURLOPT_HEADERDATA, internals);
291 curl_easy_setopt(curl, CURLOPT_WRITEDATA, internals);
292
293 if (req->timeout.overall > 0) {
295 }
296 if (req->timeout.connect > 0) {
298 }
299 if (req->timeout.stalled > 0) {
302 }
303 if (req->ca_file) {
305 }
306 if (req->unix_socket_path) {
308 }
309
310 if (req->body_len >= 0) {
311 /* set the Content-Length */
314 }
315
316 if (req->user_agent) {
318 }
319 if (req->proxy_url) {
321 }
322 if (!apr_is_empty_table(req->headers)) {
324
325 ctx.req = req;
326 ctx.hdrs = NULL;
327 ctx.rv = APR_SUCCESS;
329 internals->req_hdrs = ctx.hdrs;
330 if (ctx.rv == APR_SUCCESS) {
332 }
333 }
334
336 "req[%d]: %s %s", req->id, req->method, req->url);
337
342 }
343
344leave:
345 req->internals = (APR_SUCCESS == rv)? internals : NULL;
346 return rv;
347}
348
350{
351 md_curl_internals_t *internals = req->internals;
352 long l;
354
355 if (internals) {
357 if (APR_SUCCESS == rv) {
358 internals->response->status = (int)l;
360 "req[%d]: http status is %d",
361 req->id, internals->response->status);
362 }
363 }
364 return rv;
365}
366
368{
369 md_curl_internals_t *internals = req->internals;
370
371 if (internals && !internals->status_fired) {
372 internals->status_fired = 1;
373
375 "req[%d] fire callbacks", req->id);
376 if ((APR_SUCCESS == rv) && req->cb.on_response) {
377 rv = req->cb.on_response(internals->response, req->cb.on_response_data);
378 }
379
380 internals->rv = rv;
381 if (req->cb.on_status) {
382 req->cb.on_status(req, rv, req->cb.on_status_data);
383 }
384 }
385}
386
388{
391 md_curl_internals_t *internals;
392 long l;
393
394 if (APR_SUCCESS != (rv = internals_setup(req))) goto leave;
395 internals = req->internals;
396
397 curle = curl_easy_perform(internals->curl);
398
399 rv = curl_status(curle);
400 if (APR_SUCCESS != rv) {
402 "request failed(%d): %s", curle, curl_easy_strerror(curle));
403 goto leave;
404 }
405
407 if (APR_SUCCESS == rv) {
408 internals->response->status = (int)l;
409 }
410 md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, req->pool, "request <-- %d",
411 internals->response->status);
412
413 if (req->cb.on_response) {
414 rv = req->cb.on_response(internals->response, req->cb.on_response_data);
415 req->cb.on_response = NULL;
416 }
417
418leave:
419 fire_status(req, rv);
421 return rv;
422}
423
425{
427 md_curl_internals_t *internals;
428 int i;
429
430 for (i = 0; i < requests->nelts; ++i) {
432 internals = req->internals;
433 if (internals && internals->curl == curl) {
434 return req;
435 }
436 }
437 return NULL;
438}
439
440static void add_to_curlm(md_http_request_t *req, CURLM *curlm)
441{
442 md_curl_internals_t *internals = req->internals;
443
444 assert(curlm);
445 assert(internals);
446 if (internals->curlm == NULL) {
447 internals->curlm = curlm;
448 }
449 assert(internals->curlm == curlm);
450 curl_multi_add_handle(curlm, internals->curl);
451}
452
454{
455 md_curl_internals_t *internals = req->internals;
456
457 assert(curlm);
458 assert(internals);
459 assert(internals->curlm == curlm);
460 curl_multi_remove_handle(curlm, internals->curl);
461 internals->curlm = NULL;
463}
464
466 md_http_next_req *nextreq, void *baton)
467{
470 CURLM *curlm = NULL;
472 struct CURLMsg *curlmsg;
476 apr_status_t rv;
477
478 http_spares = apr_array_make(p, 10, sizeof(md_http_t*));
480 curlm = curl_multi_init();
481 if (!curlm) {
482 rv = APR_ENOMEM;
483 goto leave;
484 }
485
486 running = 1;
487 slowdown = 0;
488 while(1) {
489 while (1) {
490 /* fetch as many requests as nextreq gives us */
491 if (http_spares->nelts > 0) {
493 }
494 else {
495 rv = md_http_clone(&sub_http, p, http);
496 if (APR_SUCCESS != rv) {
498 "multi_perform[%d reqs]: setup failed", requests->nelts);
499 goto leave;
500 }
501 }
502
503 rv = nextreq(&req, baton, sub_http, requests->nelts);
504 if (APR_STATUS_IS_ENOENT(rv)) {
506 "multi_perform[%d reqs]: no more requests", requests->nelts);
507 if (!requests->nelts) {
508 goto leave;
509 }
510 break;
511 }
512 else if (APR_SUCCESS != rv) {
514 "multi_perform[%d reqs]: nextreq() failed", requests->nelts);
516 goto leave;
517 }
518
519 if (APR_SUCCESS != (rv = internals_setup(req))) {
520 if (req->cb.on_status) req->cb.on_status(req, rv, req->cb.on_status_data);
522 "multi_perform[%d reqs]: setup failed", requests->nelts);
524 goto leave;
525 }
526
528 add_to_curlm(req, curlm);
530 "multi_perform[%d reqs]: added request", requests->nelts);
531 }
532
533 mc = curl_multi_perform(curlm, &running);
534 if (CURLM_OK == mc) {
535 mc = curl_multi_wait(curlm, NULL, 0, 1000, &numfds);
536 if (numfds) slowdown = 0;
537 }
538 if (CURLM_OK != mc) {
539 rv = APR_ECONNABORTED;
541 "multi_perform[%d reqs] failed(%d): %s",
543 goto leave;
544 }
545 if (!numfds) {
546 /* no activity on any connection, timeout */
548 "multi_perform[%d reqs]: slowdown %d", requests->nelts, slowdown);
550 ++slowdown;
551 }
552
553 /* process status messages, e.g. that a request is done */
556 if (!curlmsg) break;
557 if (curlmsg->msg == CURLMSG_DONE) {
558 req = find_curl_request(requests, curlmsg->easy_handle);
559 if (req) {
561 "multi_perform[%d reqs]: req[%d] done",
562 requests->nelts, req->id);
563 update_status(req);
564 fire_status(req, curl_status(curlmsg->data.result));
566 sub_http = req->http;
569 }
570 else {
572 "multi_perform[%d reqs]: req done, but not found by handle",
573 requests->nelts);
574 }
575 }
576 }
577 };
578
579leave:
581 "multi_perform[%d reqs]: leaving", requests->nelts);
582 for (i = 0; i < requests->nelts; ++i) {
585 sub_http = req->http;
588 }
589 if (curlm) curl_multi_cleanup(curlm);
590 return rv;
591}
592
593static int initialized;
594
596 if (!initialized) {
597 initialized = 1;
599 }
600 return APR_SUCCESS;
601}
602
604{
605 md_curl_internals_t *internals = req->internals;
606 if (internals) {
607 if (internals->curl) {
608 CURL *curl = md_http_get_impl_data(req->http);
609 if (curl == internals->curl) {
610 /* NOP: we have this curl at the md_http_t already */
611 }
612 else if (!curl) {
613 /* no curl at the md_http_t yet, install this one */
614 md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool, "register curl instance at http");
615 md_http_set_impl_data(req->http, internals->curl);
616 }
617 else {
618 /* There already is a curl at the md_http_t and it's not this one. */
619 curl_easy_cleanup(internals->curl);
620 }
621 }
622 if (internals->req_hdrs) curl_slist_free_all(internals->req_hdrs);
623 req->internals = NULL;
624 }
625}
626
628{
629 CURL *curl;
630
631 curl = md_http_get_impl_data(http);
632 if (curl) {
633 md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, pool, "cleanup curl instance");
635 curl_easy_cleanup(curl);
636 }
637}
638
646
648{
649 /* trigger early global curl init, before we are down a rabbit hole */
650 (void)p;
651 md_curl_init();
652 return &impl;
653}
const char apr_size_t len
Definition ap_regex.h:187
static void update_status(void)
Definition abts.c:62
APR-UTIL Buckets/Bucket Brigades.
APR general purpose library routines.
apr_size_t const unsigned char unsigned int unsigned int d
Definition apr_siphash.h:72
APR Strings library.
ap_vhost_iterate_conn_cb void * baton
Definition http_vhost.h:87
#define APR_EAGAIN
Definition apr_errno.h:730
#define APR_EGENERAL
Definition apr_errno.h:313
#define APR_ECONNREFUSED
Definition apr_errno.h:751
#define APR_EACCES
Definition apr_errno.h:641
#define APR_ENOMEM
Definition apr_errno.h:683
#define APR_ENOTIMPL
Definition apr_errno.h:476
#define APR_ECONNABORTED
Definition apr_errno.h:769
#define APR_TIMEUP
Definition apr_errno.h:450
#define APR_EINVAL
Definition apr_errno.h:711
#define APR_STATUS_IS_ENOENT(s)
Definition apr_errno.h:1246
#define APR_STATUS_IS_EOF(s)
Definition apr_errno.h:567
#define APR_BUCKET_IS_METADATA(e)
#define apr_bucket_split(e, point)
#define APR_BRIGADE_EMPTY(b)
#define apr_bucket_delete(e)
#define APR_BUCKET_IS_EOS(e)
apr_brigade_flush void * ctx
#define APR_BRIGADE_FIRST(b)
#define apr_bucket_read(e, str, len, block)
@ APR_BLOCK_READ
Definition apr_buckets.h:58
apr_pool_t apr_dbd_t apr_dbd_results_t ** res
Definition apr_dbd.h:287
apr_memcache_t * mc
apr_memcache_server_t * ms
apr_size_t size
const char int apr_pool_t * pool
Definition apr_cstr.h:84
const char * value
Definition apr_env.h:51
#define APR_SUCCESS
Definition apr_errno.h:225
int apr_status_t
Definition apr_errno.h:44
const char * key
void * data
int type
char * buffer
apr_pool_t * b
Definition apr_pools.h:529
#define apr_pcalloc(p, size)
Definition apr_pools.h:465
const char * s
Definition apr_strings.h:95
#define APR_ARRAY_PUSH(ary, type)
Definition apr_tables.h:150
int nelts
Definition apr_tables.h:122
#define APR_ARRAY_IDX(ary, i, type)
Definition apr_tables.h:141
#define apr_time_as_msec(time)
Definition apr_time.h:72
#define apr_time_from_msec(msec)
Definition apr_time.h:75
apr_int64_t apr_time_t
Definition apr_time.h:45
#define apr_time_sec(time)
Definition apr_time.h:63
static size_t header_cb(void *buffer, size_t elen, size_t nmemb, void *baton)
Definition md_curl.c:129
static apr_status_t internals_setup(md_http_request_t *req)
Definition md_curl.c:242
static long timeout_sec(apr_time_t timeout)
Definition md_curl.c:188
static void md_curl_cleanup(md_http_t *http, apr_pool_t *pool)
Definition md_curl.c:627
static void add_to_curlm(md_http_request_t *req, CURLM *curlm)
Definition md_curl.c:440
static apr_status_t curl_status(unsigned int curl_code)
Definition md_curl.c:34
static size_t resp_data_cb(void *data, size_t len, size_t nmemb, void *baton)
Definition md_curl.c:105
static apr_status_t md_curl_perform(md_http_request_t *req)
Definition md_curl.c:387
static md_http_request_t * find_curl_request(apr_array_header_t *requests, CURL *curl)
Definition md_curl.c:424
static void remove_from_curlm_and_destroy(md_http_request_t *req, CURLM *curlm)
Definition md_curl.c:453
static apr_status_t md_curl_init(void)
Definition md_curl.c:595
static apr_status_t md_curl_multi_perform(md_http_t *http, apr_pool_t *p, md_http_next_req *nextreq, void *baton)
Definition md_curl.c:465
static int curlify_headers(void *baton, const char *key, const char *value)
Definition md_curl.c:165
static void md_curl_req_cleanup(md_http_request_t *req)
Definition md_curl.c:603
static int curl_debug_log(CURL *curl, curl_infotype type, char *data, size_t size, void *baton)
Definition md_curl.c:194
static int initialized
Definition md_curl.c:593
static size_t req_data_cb(void *data, size_t len, size_t nmemb, void *baton)
Definition md_curl.c:62
static long timeout_msec(apr_time_t timeout)
Definition md_curl.c:182
static md_http_impl_t impl
Definition md_curl.c:639
static void fire_status(md_http_request_t *req, apr_status_t rv)
Definition md_curl.c:367
md_http_impl_t * md_curl_get_impl(apr_pool_t *p)
Definition md_curl.c:647
apr_pool_t * p
Definition md_event.c:32
apr_status_t md_http_clone(md_http_t **phttp, apr_pool_t *p, md_http_t *source_http)
Definition md_http.c:95
void * md_http_get_impl_data(md_http_t *http)
Definition md_http.c:119
void md_http_set_impl_data(md_http_t *http, void *data)
Definition md_http.c:114
void md_http_req_destroy(md_http_request_t *req)
Definition md_http.c:243
apr_status_t md_http_next_req(md_http_request_t **preq, void *baton, md_http_t *http, int in_flight)
Definition md_http.h:228
void md_log_perror(const char *file, int line, md_log_level_t level, apr_status_t rv, apr_pool_t *p, const char *fmt,...)
Definition md_log.c:68
int md_log_is_level(apr_pool_t *p, md_log_level_t level)
Definition md_log.c:60
#define MD_LOG_MARK
Definition md_log.h:39
@ MD_LOG_TRACE2
Definition md_log.h:30
@ MD_LOG_TRACE1
Definition md_log.h:29
@ MD_LOG_TRACE4
Definition md_log.h:32
@ MD_LOG_ERR
Definition md_log.h:24
@ MD_LOG_TRACE3
Definition md_log.h:31
@ MD_LOG_TRACE5
Definition md_log.h:33
@ MD_LOG_DEBUG
Definition md_log.h:28
int md_array_remove(struct apr_array_header_t *a, void *elem)
Definition md_util.c:211
void md_data_init(md_data_t *d, const char *data, apr_size_t len)
Definition md_util.c:95
apr_status_t md_data_to_hex(const char **phex, char separator, apr_pool_t *p, const md_data_t *data)
Definition md_util.c:169
return NULL
Definition mod_so.c:359
int i
Definition mod_so.c:347
char * name
md_http_request_t * req
Definition md_curl.c:160
struct curl_slist * hdrs
Definition md_curl.c:161
apr_status_t rv
Definition md_curl.c:162
struct curl_slist * req_hdrs
Definition md_curl.c:56
apr_status_t rv
Definition md_curl.c:58
md_http_response_t * response
Definition md_curl.c:57
void * on_response_data
Definition md_http.h:47
md_http_response_cb * on_response
Definition md_http.h:46
void * on_status_data
Definition md_http.h:45
md_http_status_cb * on_status
Definition md_http.h:44
apr_pool_t * pool
Definition md_http.h:60
apr_table_t * headers
Definition md_http.h:69
struct apr_bucket_alloc_t * bucket_alloc
Definition md_http.h:62
apr_off_t body_len
Definition md_http.h:71
struct apr_bucket_brigade * body
Definition md_http.h:70
md_http_callbacks_t cb
Definition md_http.h:74
void * internals
Definition md_http.h:75
const char * proxy_url
Definition md_http.h:66
const char * user_agent
Definition md_http.h:65
md_http_timeouts_t timeout
Definition md_http.h:73
const char * url
Definition md_http.h:64
const char * ca_file
Definition md_http.h:67
md_http_t * http
Definition md_http.h:59
const char * method
Definition md_http.h:63
const char * unix_socket_path
Definition md_http.h:68
apr_table_t * headers
Definition md_http.h:81
struct apr_bucket_brigade * body
Definition md_http.h:82
md_http_request_t * req
Definition md_http.h:79
long stall_bytes_per_sec
Definition md_http.h:54
apr_time_t stalled
Definition md_http.h:55
apr_time_t connect
Definition md_http.h:53
apr_time_t overall
Definition md_http.h:52
IN ULONG IN INT timeout
typedef int(WSAAPI *apr_winapi_fpt_WSAPoll)(IN OUT LPWSAPOLLFD fdArray