41
42
pool_t result_pool;
42
43
/* box_id -> solr_result */
43
struct hash_table *mailboxes;
44
ARRAY_DEFINE(results, struct solr_result *);
44
HASH_TABLE(char *, struct solr_result *) mailboxes;
45
ARRAY(struct solr_result *) results;
47
48
struct solr_connection_post {
48
49
struct solr_connection *conn;
49
const unsigned char *data;
51
struct http_client_request *http_req;
53
53
unsigned int failed:1;
56
56
struct solr_connection {
57
struct http_client *http_client;
60
char curl_errorbuf[CURL_ERROR_SIZE];
61
struct curl_slist *headers, *headers_post;
62
59
XML_Parser xml_parser;
64
char *url, *last_sent_url;
65
64
char *http_failure;
68
struct istream *payload;
67
71
unsigned int debug:1;
68
72
unsigned int posting:1;
69
73
unsigned int xml_failed:1;
74
unsigned int http_ssl:1;
73
curl_output_func(void *data, size_t element_size, size_t nmemb, void *context)
75
struct solr_connection_post *post = context;
76
size_t size = element_size * nmemb;
79
if (size > post->size - post->pos)
80
size = post->size - post->pos;
82
memcpy(data, post->data + post->pos, size);
87
77
static int solr_xml_parse(struct solr_connection *conn,
88
78
const void *data, size_t size, bool done)
113
curl_input_func(void *data, size_t element_size, size_t nmemb, void *context)
115
struct solr_connection *conn = context;
116
size_t size = element_size * nmemb;
118
(void)solr_xml_parse(conn, data, size, FALSE);
123
curl_header_func(void *data, size_t element_size, size_t nmemb, void *context)
125
struct solr_connection *conn = context;
126
size_t size = element_size * nmemb;
127
const unsigned char *p;
130
if (conn->http_failure != NULL)
133
for (i = 0, p = data; i < size; i++) {
139
if (i == size || p[i] < '0' || p[i] > '9')
141
conn->http_failure = i_strndup(p + i, size - i);
145
struct solr_connection *solr_connection_init(const char *url, bool debug)
102
int solr_connection_init(const char *url, bool debug,
103
struct solr_connection **conn_r, const char **error_r)
105
struct http_client_settings http_set;
147
106
struct solr_connection *conn;
107
struct http_url *http_url;
110
if (http_url_parse(url, NULL, 0, pool_datastack_create(),
111
&http_url, &error) < 0) {
112
*error_r = t_strdup_printf(
113
"fts_solr: Failed to parse HTTP url: %s", error);
149
117
conn = i_new(struct solr_connection, 1);
150
conn->url = i_strdup(url);
118
conn->http_host = i_strdup(http_url->host_name);
119
conn->http_port = http_url->port;
120
conn->http_base_url = i_strconcat(http_url->path, http_url->enc_query, NULL);
121
conn->http_ssl = http_url->have_ssl;
151
122
conn->debug = debug;
153
conn->curlm = curl_multi_init();
154
conn->curl = curl_easy_init();
155
if (conn->curl == NULL || conn->curlm == NULL) {
156
i_fatal_status(FATAL_OUTOFMEM,
157
"fts_solr: Failed to allocate curl");
160
/* set global curl options */
161
curl_easy_setopt(conn->curl, CURLOPT_ERRORBUFFER, conn->curl_errorbuf);
163
curl_easy_setopt(conn->curl, CURLOPT_VERBOSE, 1L);
165
curl_easy_setopt(conn->curl, CURLOPT_NOPROGRESS, 1L);
166
curl_easy_setopt(conn->curl, CURLOPT_NOSIGNAL, 1L);
167
curl_easy_setopt(conn->curl, CURLOPT_READFUNCTION, curl_output_func);
168
curl_easy_setopt(conn->curl, CURLOPT_WRITEFUNCTION, curl_input_func);
169
curl_easy_setopt(conn->curl, CURLOPT_WRITEDATA, conn);
170
curl_easy_setopt(conn->curl, CURLOPT_HEADERFUNCTION, curl_header_func);
171
curl_easy_setopt(conn->curl, CURLOPT_HEADERDATA, conn);
173
conn->headers = curl_slist_append(NULL, "Content-Type: text/xml");
174
conn->headers_post = curl_slist_append(NULL, "Content-Type: text/xml");
175
conn->headers_post = curl_slist_append(conn->headers_post,
176
"Transfer-Encoding: chunked");
177
conn->headers_post = curl_slist_append(conn->headers_post,
179
curl_easy_setopt(conn->curl, CURLOPT_HTTPHEADER, conn->headers);
124
memset(&http_set, 0, sizeof(http_set));
125
http_set.debug = TRUE;
126
http_set.max_idle_time_msecs = 5*1000;
127
http_set.max_parallel_connections = 1;
128
http_set.max_pipelined_requests = 1;
129
http_set.max_redirects = 1;
130
http_set.max_attempts = 3;
131
http_set.debug = conn->debug;
133
conn->http_client = http_client_init(&http_set);
181
135
conn->xml_parser = XML_ParserCreate("UTF-8");
182
136
if (conn->xml_parser == NULL) {
183
137
i_fatal_status(FATAL_OUTOFMEM,
184
138
"fts_solr: Failed to allocate XML parser");
189
144
void solr_connection_deinit(struct solr_connection *conn)
191
curl_slist_free_all(conn->headers);
192
curl_slist_free_all(conn->headers_post);
193
curl_multi_cleanup(conn->curlm);
194
curl_easy_cleanup(conn->curl);
146
http_client_deinit(&conn->http_client);
195
147
XML_ParserFree(conn->xml_parser);
196
i_free(conn->last_sent_url);
148
i_free(conn->http_host);
149
i_free(conn->http_base_url);
201
void solr_connection_http_escape(struct solr_connection *conn, string_t *dest,
206
encoded = curl_easy_escape(conn->curl, str, 0);
207
str_append(dest, encoded);
211
153
static const char *attrs_get_name(const char **attrs)
213
155
for (; *attrs != NULL; attrs += 2) {
353
static void solr_connection_payload_input(struct solr_connection *conn)
355
const unsigned char *data;
360
while ((ret = i_stream_read_data(conn->payload, &data, &size, 0)) > 0) {
361
(void)solr_xml_parse(conn, data, size, FALSE);
362
i_stream_skip(conn->payload, size);
366
/* we will be called again for more data */
368
if (conn->payload->stream_errno != 0) {
369
i_error("fts_solr: failed to read payload from HTTP server: %m");
370
conn->request_status = -1;
372
io_remove(&conn->io);
373
i_stream_unref(&conn->payload);
378
solr_connection_select_response(const struct http_response *response,
379
struct solr_connection *conn)
381
if (response->status / 100 != 2) {
382
i_error("fts_solr: Lookup failed: %s", response->reason);
383
conn->request_status = -1;
387
if (response->payload == NULL) {
388
i_error("fts_solr: Lookup failed: Empty response payload");
389
conn->request_status = -1;
393
i_stream_ref(response->payload);
394
conn->payload = response->payload;
395
conn->io = io_add(i_stream_get_fd(response->payload), IO_READ,
396
solr_connection_payload_input, conn);
397
solr_connection_payload_input(conn);
411
400
int solr_connection_select(struct solr_connection *conn, const char *query,
412
401
pool_t pool, struct solr_result ***box_results_r)
414
403
struct solr_lookup_xml_context solr_lookup_context;
404
struct http_client_request *http_req;
419
408
i_assert(!conn->posting);
421
410
memset(&solr_lookup_context, 0, sizeof(solr_lookup_context));
422
411
solr_lookup_context.result_pool = pool;
423
solr_lookup_context.mailboxes =
424
hash_table_create(default_pool, default_pool, 0,
425
str_hash, (hash_cmp_callback_t *)strcmp);
412
hash_table_create(&solr_lookup_context.mailboxes, default_pool, 0,
426
414
p_array_init(&solr_lookup_context.results, pool, 32);
428
416
i_free_and_null(conn->http_failure);
433
421
XML_SetCharacterDataHandler(conn->xml_parser, solr_lookup_xml_data);
434
422
XML_SetUserData(conn->xml_parser, &solr_lookup_context);
436
/* curl v7.16 and older don't strdup() the URL */
437
i_free(conn->last_sent_url);
438
conn->last_sent_url = i_strconcat(conn->url, "select?", query, NULL);
440
curl_easy_setopt(conn->curl, CURLOPT_URL, conn->last_sent_url);
441
ret = curl_easy_perform(conn->curl);
443
i_error("fts_solr: HTTP GET failed: %s",
444
conn->curl_errorbuf);
447
curl_easy_getinfo(conn->curl, CURLINFO_RESPONSE_CODE, &httpret);
448
if (httpret != 200) {
449
i_error("fts_solr: Lookup failed: %s", conn->http_failure);
452
parse_ret = solr_xml_parse(conn, NULL, 0, TRUE);
424
url = t_strconcat(conn->http_base_url, "select?", query, NULL);
426
http_req = http_client_request(conn->http_client, "GET",
427
conn->http_host, url,
428
solr_connection_select_response, conn);
429
http_client_request_set_port(http_req, conn->http_port);
430
http_client_request_set_ssl(http_req, conn->http_ssl);
431
http_client_request_add_header(http_req, "Content-Type", "text/xml");
432
http_client_request_submit(http_req);
434
conn->request_status = 0;
435
http_client_wait(conn->http_client);
437
if (conn->request_status < 0)
440
parse_ret = solr_xml_parse(conn, "", 0, TRUE);
453
441
hash_table_destroy(&solr_lookup_context.mailboxes);
455
(void)array_append_space(&solr_lookup_context.results);
443
array_append_zero(&solr_lookup_context.results);
456
444
*box_results_r = array_idx_modifiable(&solr_lookup_context.results, 0);
457
445
return parse_ret;
449
solr_connection_update_response(const struct http_response *response,
450
struct solr_connection *conn)
452
if (response == NULL) {
454
i_error("fts_solr: HTTP POST request failed");
455
conn->request_status = -1;
459
if (response->status / 100 != 2) {
460
i_error("fts_solr: Indexing failed: %s", response->reason);
461
conn->request_status = -1;
466
static struct http_client_request *
467
solr_connection_post_request(struct solr_connection *conn)
469
struct http_client_request *http_req;
472
url = t_strconcat(conn->http_base_url, "update", NULL);
474
http_req = http_client_request(conn->http_client, "POST",
475
conn->http_host, url,
476
solr_connection_update_response, conn);
477
http_client_request_set_port(http_req, conn->http_port);
478
http_client_request_set_ssl(http_req, conn->http_ssl);
479
http_client_request_add_header(http_req, "Content-Type", "text/xml");
460
483
struct solr_connection_post *
461
484
solr_connection_post_begin(struct solr_connection *conn)
463
486
struct solr_connection_post *post;
488
i_assert(!conn->posting);
489
conn->posting = TRUE;
466
491
post = i_new(struct solr_connection_post, 1);
467
492
post->conn = conn;
469
i_assert(!conn->posting);
470
conn->posting = TRUE;
472
i_free_and_null(conn->http_failure);
474
curl_easy_setopt(conn->curl, CURLOPT_READDATA, post);
475
merr = curl_multi_add_handle(conn->curlm, conn->curl);
476
if (merr != CURLM_OK) {
477
i_error("fts_solr: curl_multi_add_handle() failed: %s",
478
curl_multi_strerror(merr));
481
/* curl v7.16 and older don't strdup() the URL */
482
post->url = i_strconcat(conn->url, "update", NULL);
483
curl_easy_setopt(conn->curl, CURLOPT_URL, post->url);
484
curl_easy_setopt(conn->curl, CURLOPT_HTTPHEADER,
486
curl_easy_setopt(conn->curl, CURLOPT_POST, (long)1);
487
XML_ParserReset(conn->xml_parser, "UTF-8");
493
post->http_req = solr_connection_post_request(conn);
494
XML_ParserReset(conn->xml_parser, "UTF-8");
492
498
void solr_connection_post_more(struct solr_connection_post *post,
493
499
const unsigned char *data, size_t size)
498
struct timeval timeout_tv;
502
int ret, handles, maxfd, n;
501
struct solr_connection *conn = post->conn;
504
502
i_assert(post->conn->posting);
506
504
if (post->failed)
514
merr = curl_multi_perform(post->conn->curlm, &handles);
515
if (merr == CURLM_CALL_MULTI_PERFORM)
517
if (merr != CURLM_OK) {
518
i_error("fts_solr: curl_multi_perform() failed: %s",
519
curl_multi_strerror(merr));
522
if ((post->pos == post->size && post->size != 0) ||
523
(handles == 0 && post->size == 0)) {
524
/* everything sent successfully */
527
msg = curl_multi_info_read(post->conn->curlm, &n);
528
if (msg != NULL && msg->msg == CURLMSG_DONE &&
529
msg->data.result != CURLE_OK) {
530
i_error("fts_solr: curl post failed: %s",
531
curl_easy_strerror(msg->data.result));
535
/* everything wasn't sent - wait. just use select,
536
since libcurl interface is easiest with it. */
541
merr = curl_multi_fdset(post->conn->curlm, &fdread, &fdwrite,
543
if (merr != CURLM_OK) {
544
i_error("fts_solr: curl_multi_fdset() failed: %s",
545
curl_multi_strerror(merr));
548
i_assert(maxfd >= 0);
550
merr = curl_multi_timeout(post->conn->curlm, &timeout);
551
if (merr != CURLM_OK) {
552
i_error("fts_solr: curl_multi_timeout() failed: %s",
553
curl_multi_strerror(merr));
558
timeout_tv.tv_sec = 1;
559
timeout_tv.tv_usec = 0;
561
timeout_tv.tv_sec = timeout / 1000;
562
timeout_tv.tv_usec = (timeout % 1000) * 1000;
564
ret = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout_tv);
566
i_error("fts_solr: select() failed: %m");
507
if (http_client_request_send_payload(&post->http_req, data, size) != 0 &&
508
conn->request_status < 0)
573
512
int solr_connection_post_end(struct solr_connection_post *post)
575
514
struct solr_connection *conn = post->conn;
577
515
int ret = post->failed ? -1 : 0;
579
517
i_assert(conn->posting);
581
solr_connection_post_more(post, NULL, 0);
583
curl_easy_getinfo(conn->curl, CURLINFO_RESPONSE_CODE, &httpret);
584
if (httpret != 200 && ret == 0) {
585
i_error("fts_solr: Indexing failed: %s", conn->http_failure);
520
if (http_client_request_finish_payload(&post->http_req) <= 0 ||
521
conn->request_status < 0) {
525
if (post->http_req != NULL)
526
http_client_request_abort(&post->http_req);
589
curl_easy_setopt(conn->curl, CURLOPT_READDATA, NULL);
590
curl_easy_setopt(conn->curl, CURLOPT_POST, (long)0);
591
curl_easy_setopt(conn->curl, CURLOPT_HTTPHEADER, conn->headers);
593
(void)curl_multi_remove_handle(conn->curlm, conn->curl);
597
530
conn->posting = FALSE;