~ubuntu-branches/ubuntu/wily/dovecot/wily

« back to all changes in this revision

Viewing changes to src/plugins/fts-solr/solr-connection.c

  • Committer: Package Import Robot
  • Author(s): Jaldhar H. Vyas
  • Date: 2013-09-09 00:57:32 UTC
  • mfrom: (1.13.11)
  • mto: (4.8.5 experimental) (1.16.1)
  • mto: This revision was merged to the branch mainline in revision 97.
  • Revision ID: package-import@ubuntu.com-20130909005732-dn1eell8srqbhh0e
Tags: upstream-2.2.5
ImportĀ upstreamĀ versionĀ 2.2.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* Copyright (c) 2006-2012 Dovecot authors, see the included COPYING file */
2
 
 
3
 
/* curl: 7.16.0 curl_multi_timeout */
 
1
/* Copyright (c) 2006-2013 Dovecot authors, see the included COPYING file */
4
2
 
5
3
#include "lib.h"
6
4
#include "array.h"
7
5
#include "hash.h"
8
6
#include "str.h"
9
7
#include "strescape.h"
 
8
#include "ioloop.h"
 
9
#include "istream.h"
 
10
#include "http-url.h"
 
11
#include "http-client.h"
10
12
#include "solr-connection.h"
11
13
 
12
 
#include <curl/curl.h>
13
14
#include <expat.h>
14
15
 
15
16
enum solr_xml_response_state {
40
41
 
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;
45
46
};
46
47
 
47
48
struct solr_connection_post {
48
49
        struct solr_connection *conn;
49
 
        const unsigned char *data;
50
 
        size_t size, pos;
51
 
        char *url;
 
50
 
 
51
        struct http_client_request *http_req;
52
52
 
53
53
        unsigned int failed:1;
54
54
};
55
55
 
56
56
struct solr_connection {
57
 
        CURL *curl;
58
 
        CURLM *curlm;
 
57
        struct http_client *http_client;
59
58
 
60
 
        char curl_errorbuf[CURL_ERROR_SIZE];
61
 
        struct curl_slist *headers, *headers_post;
62
59
        XML_Parser xml_parser;
63
60
 
64
 
        char *url, *last_sent_url;
 
61
        char *http_host;
 
62
        in_port_t http_port;
 
63
        char *http_base_url;
65
64
        char *http_failure;
66
65
 
 
66
        int request_status;
 
67
 
 
68
        struct istream *payload;
 
69
        struct io *io;
 
70
 
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;
70
75
};
71
76
 
72
 
static size_t
73
 
curl_output_func(void *data, size_t element_size, size_t nmemb, void *context)
74
 
{
75
 
        struct solr_connection_post *post = context;
76
 
        size_t size = element_size * nmemb;
77
 
 
78
 
        /* @UNSAFE */
79
 
        if (size > post->size - post->pos)
80
 
                size = post->size - post->pos;
81
 
 
82
 
        memcpy(data, post->data + post->pos, size);
83
 
        post->pos += size;
84
 
        return size;
85
 
}
86
 
 
87
77
static int solr_xml_parse(struct solr_connection *conn,
88
78
                          const void *data, size_t size, bool done)
89
79
{
109
99
        return 0;
110
100
}
111
101
 
112
 
static size_t
113
 
curl_input_func(void *data, size_t element_size, size_t nmemb, void *context)
114
 
{
115
 
        struct solr_connection *conn = context;
116
 
        size_t size = element_size * nmemb;
117
 
 
118
 
        (void)solr_xml_parse(conn, data, size, FALSE);
119
 
        return size;
120
 
}
121
 
 
122
 
static size_t
123
 
curl_header_func(void *data, size_t element_size, size_t nmemb, void *context)
124
 
{
125
 
        struct solr_connection *conn = context;
126
 
        size_t size = element_size * nmemb;
127
 
        const unsigned char *p;
128
 
        size_t i;
129
 
 
130
 
        if (conn->http_failure != NULL)
131
 
                return size;
132
 
 
133
 
        for (i = 0, p = data; i < size; i++) {
134
 
                if (p[i] == ' ') {
135
 
                        i++;
136
 
                        break;
137
 
                }
138
 
        }
139
 
        if (i == size || p[i] < '0' || p[i] > '9')
140
 
                i = 0;
141
 
        conn->http_failure = i_strndup(p + i, size - i);
142
 
        return size;
143
 
}
144
 
 
145
 
struct solr_connection *solr_connection_init(const char *url, bool debug)
146
 
{
 
102
int solr_connection_init(const char *url, bool debug,
 
103
                         struct solr_connection **conn_r, const char **error_r)
 
104
{
 
105
        struct http_client_settings http_set;
147
106
        struct solr_connection *conn;
 
107
        struct http_url *http_url;
 
108
        const char *error;
 
109
 
 
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);
 
114
                return -1;
 
115
        }
148
116
 
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;
152
123
 
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");
158
 
        }
159
 
 
160
 
        /* set global curl options */
161
 
        curl_easy_setopt(conn->curl, CURLOPT_ERRORBUFFER, conn->curl_errorbuf);
162
 
        if (conn->debug)
163
 
                curl_easy_setopt(conn->curl, CURLOPT_VERBOSE, 1L);
164
 
 
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);
172
 
 
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,
178
 
                                               "Expect:");
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;
 
132
 
 
133
        conn->http_client = http_client_init(&http_set);
180
134
 
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");
185
139
        }
186
 
        return conn;
 
140
        *conn_r = conn;
 
141
        return 0;
187
142
}
188
143
 
189
144
void solr_connection_deinit(struct solr_connection *conn)
190
145
{
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);
197
 
        i_free(conn->url);
 
148
        i_free(conn->http_host);
 
149
        i_free(conn->http_base_url);
198
150
        i_free(conn);
199
151
}
200
152
 
201
 
void solr_connection_http_escape(struct solr_connection *conn, string_t *dest,
202
 
                                 const char *str)
203
 
{
204
 
        char *encoded;
205
 
 
206
 
        encoded = curl_easy_escape(conn->curl, str, 0);
207
 
        str_append(dest, encoded);
208
 
        curl_free(encoded);
209
 
}
210
 
 
211
153
static const char *attrs_get_name(const char **attrs)
212
154
{
213
155
        for (; *attrs != NULL; attrs += 2) {
320
262
        }
321
263
        result = solr_result_get(ctx, box_id);
322
264
 
323
 
        seq_range_array_add(&result->uids, 0, ctx->uid);
 
265
        seq_range_array_add(&result->uids, ctx->uid);
324
266
        if (ctx->score != 0) {
325
267
                score = array_append_space(&result->scores);
326
268
                score->uid = ctx->uid;
408
350
        }
409
351
}
410
352
 
 
353
static void solr_connection_payload_input(struct solr_connection *conn)
 
354
{
 
355
        const unsigned char *data;
 
356
        size_t size;
 
357
        int ret;
 
358
 
 
359
        /* read payload */
 
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);
 
363
        }
 
364
 
 
365
        if (ret == 0) {
 
366
                /* we will be called again for more data */
 
367
        } else {
 
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;
 
371
                }
 
372
                io_remove(&conn->io);
 
373
                i_stream_unref(&conn->payload);
 
374
        }
 
375
}
 
376
 
 
377
static void
 
378
solr_connection_select_response(const struct http_response *response,
 
379
                                struct solr_connection *conn)
 
380
{
 
381
        if (response->status / 100 != 2) {
 
382
                i_error("fts_solr: Lookup failed: %s", response->reason);
 
383
                conn->request_status = -1;
 
384
                return;
 
385
        }
 
386
 
 
387
        if (response->payload == NULL) {
 
388
                i_error("fts_solr: Lookup failed: Empty response payload");
 
389
                conn->request_status = -1;
 
390
                return;
 
391
        }
 
392
 
 
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);
 
398
}
 
399
 
411
400
int solr_connection_select(struct solr_connection *conn, const char *query,
412
401
                           pool_t pool, struct solr_result ***box_results_r)
413
402
{
414
403
        struct solr_lookup_xml_context solr_lookup_context;
415
 
        CURLcode ret;
416
 
        long httpret;
 
404
        struct http_client_request *http_req;
 
405
        const char *url;
417
406
        int parse_ret;
418
407
 
419
408
        i_assert(!conn->posting);
420
409
 
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,
 
413
                          str_hash, strcmp);
426
414
        p_array_init(&solr_lookup_context.results, pool, 32);
427
415
 
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);
435
423
 
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);
439
 
 
440
 
        curl_easy_setopt(conn->curl, CURLOPT_URL, conn->last_sent_url);
441
 
        ret = curl_easy_perform(conn->curl);
442
 
        if (ret != 0) {
443
 
                i_error("fts_solr: HTTP GET failed: %s",
444
 
                        conn->curl_errorbuf);
445
 
                return -1;
446
 
        }
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);
450
 
                return -1;
451
 
        }
452
 
        parse_ret = solr_xml_parse(conn, NULL, 0, TRUE);
 
424
        url = t_strconcat(conn->http_base_url, "select?", query, NULL);
 
425
 
 
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);
 
433
 
 
434
        conn->request_status = 0;
 
435
        http_client_wait(conn->http_client);
 
436
 
 
437
        if (conn->request_status < 0)
 
438
                return -1;
 
439
 
 
440
        parse_ret = solr_xml_parse(conn, "", 0, TRUE);
453
441
        hash_table_destroy(&solr_lookup_context.mailboxes);
454
442
 
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;
458
446
}
459
447
 
 
448
static void
 
449
solr_connection_update_response(const struct http_response *response,
 
450
                                struct solr_connection *conn)
 
451
{
 
452
        if (response == NULL) {
 
453
                /* request failed */
 
454
                i_error("fts_solr: HTTP POST request failed");
 
455
                conn->request_status = -1;
 
456
                return;
 
457
        }
 
458
 
 
459
        if (response->status / 100 != 2) {
 
460
                i_error("fts_solr: Indexing failed: %s", response->reason);
 
461
                conn->request_status = -1;
 
462
                return;
 
463
        }
 
464
}
 
465
 
 
466
static struct http_client_request *
 
467
solr_connection_post_request(struct solr_connection *conn)
 
468
{
 
469
        struct http_client_request *http_req;
 
470
        const char *url;
 
471
 
 
472
        url = t_strconcat(conn->http_base_url, "update", NULL);
 
473
 
 
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");
 
480
        return http_req;
 
481
}
 
482
 
460
483
struct solr_connection_post *
461
484
solr_connection_post_begin(struct solr_connection *conn)
462
485
{
463
486
        struct solr_connection_post *post;
464
 
        CURLMcode merr;
 
487
 
 
488
        i_assert(!conn->posting);
 
489
        conn->posting = TRUE;
465
490
 
466
491
        post = i_new(struct solr_connection_post, 1);
467
492
        post->conn = conn;
468
 
 
469
 
        i_assert(!conn->posting);
470
 
        conn->posting = TRUE;
471
 
 
472
 
        i_free_and_null(conn->http_failure);
473
 
 
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));
479
 
                post->failed = TRUE;
480
 
        } else {
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,
485
 
                                 conn->headers_post);
486
 
                curl_easy_setopt(conn->curl, CURLOPT_POST, (long)1);
487
 
                XML_ParserReset(conn->xml_parser, "UTF-8");
488
 
        }
 
493
        post->http_req = solr_connection_post_request(conn);
 
494
        XML_ParserReset(conn->xml_parser, "UTF-8");
489
495
        return post;
490
496
}
491
497
 
492
498
void solr_connection_post_more(struct solr_connection_post *post,
493
499
                               const unsigned char *data, size_t size)
494
500
{
495
 
        fd_set fdread;
496
 
        fd_set fdwrite;
497
 
        fd_set fdexcep;
498
 
        struct timeval timeout_tv;
499
 
        long timeout;
500
 
        CURLMsg *msg;
501
 
        CURLMcode merr;
502
 
        int ret, handles, maxfd, n;
503
 
 
 
501
        struct solr_connection *conn = post->conn;
504
502
        i_assert(post->conn->posting);
505
503
 
506
504
        if (post->failed)
507
505
                return;
508
506
 
509
 
        post->data = data;
510
 
        post->size = size;
511
 
        post->pos = 0;
512
 
 
513
 
        for (;;) {
514
 
                merr = curl_multi_perform(post->conn->curlm, &handles);
515
 
                if (merr == CURLM_CALL_MULTI_PERFORM)
516
 
                        continue;
517
 
                if (merr != CURLM_OK) {
518
 
                        i_error("fts_solr: curl_multi_perform() failed: %s",
519
 
                                curl_multi_strerror(merr));
520
 
                        break;
521
 
                }
522
 
                if ((post->pos == post->size && post->size != 0) ||
523
 
                    (handles == 0 && post->size == 0)) {
524
 
                        /* everything sent successfully */
525
 
                        return;
526
 
                }
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));
532
 
                        break;
533
 
                }
534
 
 
535
 
                /* everything wasn't sent - wait. just use select,
536
 
                   since libcurl interface is easiest with it. */
537
 
                FD_ZERO(&fdread);
538
 
                FD_ZERO(&fdwrite);
539
 
                FD_ZERO(&fdexcep);
540
 
 
541
 
                merr = curl_multi_fdset(post->conn->curlm, &fdread, &fdwrite,
542
 
                                        &fdexcep, &maxfd);
543
 
                if (merr != CURLM_OK) {
544
 
                        i_error("fts_solr: curl_multi_fdset() failed: %s",
545
 
                                curl_multi_strerror(merr));
546
 
                        break;
547
 
                }
548
 
                i_assert(maxfd >= 0);
549
 
 
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));
554
 
                        break;
555
 
                }
556
 
 
557
 
                if (timeout < 0) {
558
 
                        timeout_tv.tv_sec = 1;
559
 
                        timeout_tv.tv_usec = 0;
560
 
                } else {
561
 
                        timeout_tv.tv_sec = timeout / 1000;
562
 
                        timeout_tv.tv_usec = (timeout % 1000) * 1000;
563
 
                }
564
 
                ret = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout_tv);
565
 
                if (ret < 0) {
566
 
                        i_error("fts_solr: select() failed: %m");
567
 
                        break;
568
 
                }
569
 
        }
570
 
        post->failed = TRUE;
 
507
        if (http_client_request_send_payload(&post->http_req, data, size) != 0 &&
 
508
            conn->request_status < 0)
 
509
                post->failed = TRUE;
571
510
}
572
511
 
573
512
int solr_connection_post_end(struct solr_connection_post *post)
574
513
{
575
514
        struct solr_connection *conn = post->conn;
576
 
        long httpret;
577
515
        int ret = post->failed ? -1 : 0;
578
516
 
579
517
        i_assert(conn->posting);
580
518
 
581
 
        solr_connection_post_more(post, NULL, 0);
582
 
 
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);
586
 
                ret = -1;
 
519
        if (!post->failed) {
 
520
                if (http_client_request_finish_payload(&post->http_req) <= 0 ||
 
521
                        conn->request_status < 0) {
 
522
                        ret = -1;
 
523
                }
 
524
        } else {
 
525
                if (post->http_req != NULL)
 
526
                        http_client_request_abort(&post->http_req);
587
527
        }
588
 
 
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);
592
 
 
593
 
        (void)curl_multi_remove_handle(conn->curlm, conn->curl);
594
 
        i_free(post->url);
595
528
        i_free(post);
596
529
 
597
530
        conn->posting = FALSE;
600
533
 
601
534
int solr_connection_post(struct solr_connection *conn, const char *cmd)
602
535
{
603
 
        struct solr_connection_post *post;
604
 
 
605
 
        post = solr_connection_post_begin(conn);
606
 
        solr_connection_post_more(post, (const unsigned char *)cmd,
607
 
                                  strlen(cmd));
608
 
        return solr_connection_post_end(post);
 
536
        struct http_client_request *http_req;
 
537
        struct istream *post_payload;
 
538
 
 
539
        i_assert(!conn->posting);
 
540
 
 
541
        http_req = solr_connection_post_request(conn);
 
542
        post_payload = i_stream_create_from_data(cmd, strlen(cmd));
 
543
        http_client_request_set_payload(http_req, post_payload, TRUE);
 
544
        i_stream_unref(&post_payload);
 
545
        http_client_request_submit(http_req);
 
546
 
 
547
        XML_ParserReset(conn->xml_parser, "UTF-8");
 
548
 
 
549
        conn->request_status = 0;
 
550
        http_client_wait(conn->http_client);
 
551
 
 
552
        return conn->request_status;
609
553
}