Mercurial > dovecot > core-2.2
comparison src/lib-http/http-client-connection.c @ 15394:107c8b2c9594
lib-http: Added initial HTTP client implementation.
author | Stephan Bosch <stephan@rename-it.nl> |
---|---|
date | Sat, 24 Nov 2012 00:30:14 +0200 |
parents | |
children | 9cab24687819 |
comparison
equal
deleted
inserted
replaced
15393:b40bda50541c | 15394:107c8b2c9594 |
---|---|
1 /* Copyright (c) 2012 Dovecot authors, see the included COPYING file */ | |
2 | |
3 #include "lib.h" | |
4 #include "net.h" | |
5 #include "str.h" | |
6 #include "hash.h" | |
7 #include "array.h" | |
8 #include "ioloop.h" | |
9 #include "istream.h" | |
10 #include "ostream.h" | |
11 #include "iostream-rawlog.h" | |
12 #include "iostream-ssl.h" | |
13 #include "http-response-parser.h" | |
14 | |
15 #include "http-client-private.h" | |
16 | |
17 /* | |
18 * Logging | |
19 */ | |
20 | |
21 static inline void | |
22 http_client_connection_debug(struct http_client_connection *conn, | |
23 const char *format, ...) ATTR_FORMAT(2, 3); | |
24 static inline void | |
25 http_client_connection_error(struct http_client_connection *conn, | |
26 const char *format, ...) ATTR_FORMAT(2, 3); | |
27 | |
28 static inline void | |
29 http_client_connection_debug(struct http_client_connection *conn, | |
30 const char *format, ...) | |
31 { | |
32 va_list args; | |
33 | |
34 if (conn->client->set.debug) { | |
35 | |
36 va_start(args, format); | |
37 i_debug("http-client: conn %s: %s", | |
38 http_client_connection_label(conn), t_strdup_vprintf(format, args)); | |
39 va_end(args); | |
40 } | |
41 } | |
42 | |
43 static inline void | |
44 http_client_connection_error(struct http_client_connection *conn, | |
45 const char *format, ...) | |
46 { | |
47 va_list args; | |
48 | |
49 va_start(args, format); | |
50 i_error("http-client: conn %s: %s", | |
51 http_client_connection_label(conn), t_strdup_vprintf(format, args)); | |
52 va_end(args); | |
53 } | |
54 | |
55 | |
56 /* | |
57 * Connection | |
58 */ | |
59 | |
60 static void http_client_connection_input(struct connection *_conn); | |
61 | |
62 bool http_client_connection_is_ready(struct http_client_connection *conn) | |
63 { | |
64 return (conn->connected && !conn->output_locked && | |
65 array_count(&conn->request_wait_list) < | |
66 conn->client->set.max_pipelined_requests); | |
67 } | |
68 | |
69 static void | |
70 http_client_connection_retry_requests(struct http_client_connection *conn, | |
71 unsigned int status, const char *error) | |
72 { | |
73 struct http_client_request **req; | |
74 | |
75 array_foreach_modifiable(&conn->request_wait_list, req) { | |
76 http_client_request_retry(*req, status, error); | |
77 } | |
78 array_clear(&conn->request_wait_list); | |
79 } | |
80 | |
81 static void | |
82 http_client_connection_server_close(struct http_client_connection **_conn) | |
83 { | |
84 struct http_client_connection *conn = *_conn; | |
85 struct http_client_request **req; | |
86 | |
87 conn->connected = FALSE; | |
88 conn->closing = TRUE; | |
89 | |
90 http_client_connection_debug(conn, | |
91 "Server explicitly closed connection"); | |
92 | |
93 array_foreach_modifiable(&conn->request_wait_list, req) { | |
94 http_client_request_resubmit(*req); | |
95 } | |
96 array_clear(&conn->request_wait_list); | |
97 | |
98 if (conn->client->ioloop != NULL) | |
99 io_loop_stop(conn->client->ioloop); | |
100 | |
101 http_client_connection_free(_conn); | |
102 } | |
103 | |
104 static void | |
105 http_client_connection_abort_temp_error(struct http_client_connection **_conn, | |
106 unsigned int status, const char *error) | |
107 { | |
108 struct http_client_connection *conn = *_conn; | |
109 | |
110 conn->connected = FALSE; | |
111 conn->closing = TRUE; | |
112 | |
113 http_client_connection_retry_requests(conn, status, error); | |
114 http_client_connection_free(_conn); | |
115 } | |
116 | |
117 static void | |
118 http_client_connection_abort_error(struct http_client_connection **_conn, | |
119 unsigned int status, const char *error) | |
120 { | |
121 struct http_client_connection *conn = *_conn; | |
122 struct http_client_request **req; | |
123 | |
124 conn->connected = FALSE; | |
125 conn->closing = TRUE; | |
126 | |
127 array_foreach_modifiable(&conn->request_wait_list, req) { | |
128 http_client_request_error(*req, status, error); | |
129 } | |
130 array_clear(&conn->request_wait_list); | |
131 http_client_connection_free(_conn); | |
132 } | |
133 | |
134 static void | |
135 http_client_connection_idle_timeout(struct http_client_connection *conn) | |
136 { | |
137 http_client_connection_debug(conn, "Idle connection timed out"); | |
138 | |
139 http_client_connection_free(&conn); | |
140 } | |
141 | |
142 static void | |
143 http_client_connection_check_idle(struct http_client_connection *conn) | |
144 { | |
145 unsigned int timeout, count; | |
146 | |
147 if (array_count(&conn->request_wait_list) == 0 && | |
148 conn->incoming_payload == NULL && | |
149 conn->client->set.max_idle_time_msecs > 0) { | |
150 | |
151 if (conn->to_idle != NULL) { | |
152 /* timeout already set */ | |
153 return; | |
154 } | |
155 | |
156 http_client_connection_debug(conn, | |
157 "No more requests queued; going idle"); | |
158 | |
159 if (conn->client->ioloop != NULL) | |
160 io_loop_stop(conn->client->ioloop); | |
161 | |
162 count = array_count(&conn->peer->conns); | |
163 i_assert(count > 0); | |
164 | |
165 /* set timeout for this connection */ | |
166 if (count > conn->client->set.max_parallel_connections) { | |
167 /* instant death for (urgent) connections above limit */ | |
168 timeout = 0; | |
169 } else { | |
170 /* kill duplicate connections quicker; | |
171 linearly based on the number of connections */ | |
172 timeout = (conn->client->set.max_parallel_connections - count) * | |
173 (conn->client->set.max_idle_time_msecs / | |
174 conn->client->set.max_parallel_connections); | |
175 } | |
176 | |
177 conn->to_idle = | |
178 timeout_add(timeout, http_client_connection_idle_timeout, conn); | |
179 | |
180 } else { | |
181 /* there should be no idle timeout */ | |
182 i_assert(conn->to_idle == NULL); | |
183 } | |
184 } | |
185 | |
186 static void | |
187 http_client_connection_continue_timeout(struct http_client_connection *conn) | |
188 { | |
189 struct http_client_request *const *req_idx; | |
190 struct http_client_request *req; | |
191 | |
192 if (conn->to_response != NULL) | |
193 timeout_remove(&conn->to_response); | |
194 conn->peer->no_payload_sync = TRUE; | |
195 | |
196 http_client_connection_debug(conn, | |
197 "Expected 100-continue response timed out; sending payload anyway"); | |
198 | |
199 req_idx = array_idx(&conn->request_wait_list, 0); | |
200 req = req_idx[0]; | |
201 | |
202 // FIXME: we should mark this in the peer object for next requests | |
203 conn->payload_continue = TRUE; | |
204 if (http_client_request_send_more(req) < 0) { | |
205 http_client_connection_abort_temp_error(&conn, | |
206 HTTP_CLIENT_REQUEST_ERROR_CONNECTION_LOST, "Failed to send request"); | |
207 } | |
208 } | |
209 | |
210 bool http_client_connection_next_request(struct http_client_connection *conn) | |
211 { | |
212 struct http_client_request *req = NULL; | |
213 | |
214 if (!http_client_connection_is_ready(conn)) { | |
215 | |
216 http_client_connection_debug(conn, "Not ready for next request"); | |
217 return FALSE; | |
218 } | |
219 | |
220 /* claim request, but no urgent request can be second in line */ | |
221 req = http_client_peer_claim_request(conn->peer, | |
222 array_count(&conn->request_wait_list) > 0); | |
223 if (req == NULL) { | |
224 http_client_connection_check_idle(conn); | |
225 return FALSE; | |
226 } | |
227 | |
228 if (conn->to_idle != NULL) | |
229 timeout_remove(&conn->to_idle); | |
230 | |
231 req->conn = conn; | |
232 conn->payload_continue = FALSE; | |
233 if (conn->peer->no_payload_sync) | |
234 req->payload_sync = FALSE; | |
235 array_append(&conn->request_wait_list, &req, 1); | |
236 http_client_request_ref(req); | |
237 | |
238 if (http_client_request_send(req) < 0) { | |
239 http_client_connection_abort_temp_error(&conn, | |
240 HTTP_CLIENT_REQUEST_ERROR_CONNECTION_LOST, | |
241 "Failed to send request"); | |
242 return FALSE; | |
243 } | |
244 | |
245 /* https://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-21; | |
246 Section 6.1.2.1: | |
247 | |
248 Because of the presence of older implementations, the protocol allows | |
249 ambiguous situations in which a client might send "Expect: 100-continue" | |
250 without receiving either a 417 (Expectation Failed) or a 100 (Continue) | |
251 status code. Therefore, when a client sends this header field to an | |
252 origin server (possibly via a proxy) from which it has never seen a 100 | |
253 (Continue) status code, the client SHOULD NOT wait for an indefinite | |
254 period before sending the payload body. | |
255 */ | |
256 if (req->payload_sync) { | |
257 i_assert(req->input_size > 0); | |
258 i_assert(conn->to_response == NULL); | |
259 conn->to_response = timeout_add(HTTP_CLIENT_CONTINUE_TIMEOUT_MSECS, | |
260 http_client_connection_continue_timeout, conn); | |
261 } | |
262 | |
263 return TRUE; | |
264 } | |
265 | |
266 static void http_client_connection_destroy(struct connection *_conn) | |
267 { | |
268 struct http_client_connection *conn = | |
269 (struct http_client_connection *)_conn; | |
270 | |
271 conn->closing = TRUE; | |
272 conn->connected = FALSE; | |
273 | |
274 switch (_conn->disconnect_reason) { | |
275 case CONNECTION_DISCONNECT_CONNECT_TIMEOUT: | |
276 http_client_peer_connection_failure(conn->peer); | |
277 break; | |
278 case CONNECTION_DISCONNECT_CONN_CLOSED: | |
279 /* retry pending requests if possible */ | |
280 http_client_connection_retry_requests(conn, | |
281 HTTP_CLIENT_REQUEST_ERROR_CONNECTION_LOST, "Connection lost"); | |
282 default: | |
283 break; | |
284 } | |
285 | |
286 http_client_connection_free(&conn); | |
287 } | |
288 | |
289 static void http_client_payload_finished(struct http_client_connection *conn) | |
290 { | |
291 timeout_remove(&conn->to_input); | |
292 conn->conn.io = io_add(conn->conn.fd_in, IO_READ, | |
293 http_client_connection_input, &conn->conn); | |
294 } | |
295 | |
296 static void http_client_payload_destroyed(struct http_client_connection *conn) | |
297 { | |
298 i_assert(conn->incoming_payload != NULL); | |
299 i_assert(conn->pending_request != NULL); | |
300 i_assert(conn->conn.io == NULL); | |
301 | |
302 http_client_connection_debug(conn, "Response payload stream destroyed"); | |
303 | |
304 /* caller is allowed to change the socket fd to blocking while reading | |
305 the payload. make sure here that it's switched back. */ | |
306 net_set_nonblock(conn->conn.fd_in, TRUE); | |
307 | |
308 conn->incoming_payload = NULL; | |
309 | |
310 http_client_request_finish(&conn->pending_request); | |
311 conn->pending_request = NULL; | |
312 | |
313 if (conn->close_indicated) { | |
314 http_client_connection_server_close(&conn); | |
315 return; | |
316 } | |
317 | |
318 /* input stream may have pending input. make sure input handler | |
319 gets called (but don't do it directly, since we get get here | |
320 somewhere from the API user's code, which we can't really know what | |
321 state it is in). this call also triggers sending a new request if | |
322 necessary. */ | |
323 conn->to_input = | |
324 timeout_add_short(0, http_client_connection_input, &conn->conn); | |
325 } | |
326 | |
327 static bool | |
328 http_client_connection_return_response(struct http_client_connection *conn, | |
329 struct http_client_request *req, struct http_response *response) | |
330 { | |
331 struct istream *payload; | |
332 | |
333 i_assert(conn->incoming_payload == NULL); | |
334 i_assert(conn->pending_request == NULL); | |
335 | |
336 req->state = HTTP_REQUEST_STATE_GOT_RESPONSE; | |
337 | |
338 if (response->payload != NULL) { | |
339 /* wrap the stream to capture the destroy event without destroying the | |
340 actual payload stream. */ | |
341 conn->incoming_payload = response->payload = | |
342 i_stream_create_limit(response->payload, (uoff_t)-1); | |
343 i_stream_set_destroy_callback(response->payload, | |
344 http_client_payload_destroyed, | |
345 conn); | |
346 /* the callback may add its own I/O, so we need to remove | |
347 our one before calling it */ | |
348 io_remove(&conn->conn.io); | |
349 } | |
350 | |
351 http_client_request_callback(req, response); | |
352 | |
353 // FIXME: conn may be freed at this point.. | |
354 | |
355 if (response->payload != NULL) { | |
356 req->state = HTTP_REQUEST_STATE_PAYLOAD_IN; | |
357 payload = response->payload; | |
358 response->payload = NULL; | |
359 conn->pending_request = req; | |
360 i_stream_unref(&payload); | |
361 if (conn->to_input != NULL) { | |
362 /* already finished reading the payload */ | |
363 http_client_payload_finished(conn); | |
364 } | |
365 } else { | |
366 http_client_request_finish(&req); | |
367 } | |
368 | |
369 if (conn->incoming_payload == NULL) { | |
370 i_assert(conn->conn.io != NULL); | |
371 return TRUE; | |
372 } | |
373 | |
374 return FALSE; | |
375 } | |
376 | |
377 static void http_client_connection_input(struct connection *_conn) | |
378 { | |
379 struct http_client_connection *conn = | |
380 (struct http_client_connection *)_conn; | |
381 struct http_response *response; | |
382 struct http_client_request *const *req_idx; | |
383 struct http_client_request *req = NULL; | |
384 int finished = 0, ret; | |
385 const char *error; | |
386 bool no_payload = FALSE; | |
387 | |
388 i_assert(conn->incoming_payload == NULL); | |
389 | |
390 if (conn->to_input != NULL) { | |
391 /* We came here from a timeout added by | |
392 http_client_payload_destroyed(). The IO couldn't be added | |
393 back immediately in there, because the HTTP API user may | |
394 still have had its own IO pointed to the same fd. It should | |
395 be removed by now, so we can add it back. */ | |
396 http_client_payload_finished(conn); | |
397 finished++; | |
398 } | |
399 | |
400 /* get first waiting request */ | |
401 if (array_count(&conn->request_wait_list) > 0) { | |
402 req_idx = array_idx(&conn->request_wait_list, 0); | |
403 req = req_idx[0]; | |
404 | |
405 /* https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-21 | |
406 Section 3.3.2: | |
407 | |
408 A server MAY send a Content-Length header field in a response to a | |
409 HEAD request [...] | |
410 */ | |
411 no_payload = (strcmp(req->method, "HEAD") == 0); | |
412 } | |
413 | |
414 // FIXME: handle somehow if server replies before request->input is at EOF | |
415 while ((ret=http_response_parse_next | |
416 (conn->http_parser, no_payload, &response, &error)) > 0) { | |
417 bool aborted; | |
418 | |
419 if (req == NULL) { | |
420 /* server sent response without any requests in the wait list */ | |
421 http_client_connection_error(conn, "Got unexpected input from server"); | |
422 http_client_connection_free(&conn); | |
423 return; | |
424 } | |
425 | |
426 /* https://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-21 | |
427 Section 7.2: | |
428 | |
429 A client MUST be prepared to accept one or more 1xx status responses | |
430 prior to a regular response, even if the client does not expect a 100 | |
431 (Continue) status message. Unexpected 1xx status responses MAY be | |
432 ignored by a user agent. | |
433 */ | |
434 if (req->payload_sync && response->status == 100) { | |
435 conn->payload_continue = TRUE; | |
436 if (conn->to_response != NULL) | |
437 timeout_remove(&conn->to_response); | |
438 http_client_connection_debug(conn, | |
439 "Got expected 100-continue response"); | |
440 if (http_client_request_send_more(req) < 0) { | |
441 http_client_connection_abort_temp_error(&conn, | |
442 HTTP_CLIENT_REQUEST_ERROR_CONNECTION_LOST, | |
443 "Failed to send request"); | |
444 } | |
445 return; | |
446 } else if (response->status / 100 == 1) { | |
447 /* ignore them for now */ | |
448 http_client_connection_debug(conn, | |
449 "Got unexpected %u response; ignoring", response->status); | |
450 continue; | |
451 } // FIXME: handle 417 Expectation Failed | |
452 | |
453 http_client_connection_debug(conn, | |
454 "Got %u response for request %s", | |
455 response->status, http_client_request_label(req)); | |
456 | |
457 /* remove request from queue */ | |
458 array_delete(&conn->request_wait_list, 0, 1); | |
459 aborted = (req->state == HTTP_REQUEST_STATE_ABORTED); | |
460 http_client_request_unref(&req); | |
461 | |
462 conn->close_indicated = response->connection_close; | |
463 | |
464 if (!aborted) { | |
465 if (response->status / 100 == 3) { | |
466 /* redirect */ | |
467 http_client_request_redirect(req, response->location); | |
468 } else { | |
469 /* response for application */ | |
470 if (!http_client_connection_return_response(conn, req, response)) | |
471 return; | |
472 } | |
473 | |
474 finished++; | |
475 } | |
476 | |
477 /* server closing connection? */ | |
478 if (conn->close_indicated) { | |
479 http_client_connection_server_close(&conn); | |
480 return; | |
481 } | |
482 | |
483 /* get next waiting request */ | |
484 if (array_count(&conn->request_wait_list) > 0) { | |
485 req_idx = array_idx(&conn->request_wait_list, 0); | |
486 req = req_idx[0]; | |
487 no_payload = (strcmp(req->method, "HEAD") == 0); | |
488 } else { | |
489 req = NULL; | |
490 no_payload = FALSE; | |
491 } | |
492 } | |
493 | |
494 if (ret <= 0 && | |
495 (conn->conn.input->eof || conn->conn.input->stream_errno != 0)) { | |
496 int stream_errno = conn->conn.input->stream_errno; | |
497 http_client_connection_debug(conn, | |
498 "Lost connection to server (error=%s)", | |
499 stream_errno != 0 ? strerror(stream_errno) : "EOF"); | |
500 http_client_connection_abort_temp_error(&conn, | |
501 HTTP_CLIENT_REQUEST_ERROR_CONNECTION_LOST, "Connection lost"); | |
502 return; | |
503 } | |
504 | |
505 if (ret < 0) { | |
506 http_client_connection_abort_error(&conn, | |
507 HTTP_CLIENT_REQUEST_ERROR_BAD_RESPONSE, error); | |
508 return; | |
509 } | |
510 | |
511 if (finished > 0) { | |
512 /* room for new requests */ | |
513 http_client_peer_handle_requests(conn->peer); | |
514 http_client_connection_check_idle(conn); | |
515 } | |
516 } | |
517 | |
518 static int http_client_connection_output(struct http_client_connection *conn) | |
519 { | |
520 struct http_client_request *const *req_idx, *req; | |
521 struct ostream *output = conn->conn.output; | |
522 int ret; | |
523 | |
524 if ((ret = o_stream_flush(output)) <= 0) { | |
525 if (ret < 0) { | |
526 http_client_connection_abort_temp_error(&conn, | |
527 HTTP_CLIENT_REQUEST_ERROR_CONNECTION_LOST, "Connection lost"); | |
528 } | |
529 return ret; | |
530 } | |
531 | |
532 if (array_count(&conn->request_wait_list) > 0 && conn->output_locked) { | |
533 req_idx = array_idx(&conn->request_wait_list, 0); | |
534 req = req_idx[0]; | |
535 | |
536 if (!req->payload_sync || conn->payload_continue) { | |
537 if (http_client_request_send_more(req) < 0) { | |
538 http_client_connection_abort_temp_error(&conn, | |
539 HTTP_CLIENT_REQUEST_ERROR_CONNECTION_LOST, "Connection lost"); | |
540 return -1; | |
541 } | |
542 if (!conn->output_locked) { | |
543 /* room for new requests */ | |
544 http_client_peer_handle_requests(conn->peer); | |
545 http_client_connection_check_idle(conn); | |
546 } | |
547 } | |
548 } | |
549 return 1; | |
550 } | |
551 | |
552 static void | |
553 http_client_connection_ready(struct http_client_connection *conn) | |
554 { | |
555 struct stat st; | |
556 | |
557 conn->connected = TRUE; | |
558 | |
559 if (*conn->client->set.rawlog_dir != '\0' && | |
560 stat(conn->client->set.rawlog_dir, &st) == 0) { | |
561 iostream_rawlog_create(conn->client->set.rawlog_dir, | |
562 &conn->conn.input, &conn->conn.output); | |
563 } | |
564 | |
565 conn->http_parser = http_response_parser_init(conn->conn.input); | |
566 o_stream_set_flush_callback(conn->conn.output, | |
567 http_client_connection_output, conn); | |
568 | |
569 /* we never pipeline before the first response */ | |
570 (void)http_client_connection_next_request(conn); | |
571 } | |
572 | |
573 #ifdef HTTP_BUILD_SSL | |
574 static int http_client_connection_ssl_handshaked(void *context) | |
575 { | |
576 struct http_client_connection *conn = context; | |
577 | |
578 if (!conn->client->set.ssl_verify) { | |
579 /* skip certificate checks */ | |
580 http_client_connection_debug(conn, "SSL handshake successful"); | |
581 return 0; | |
582 } else if (!ssl_iostream_has_valid_client_cert(conn->ssl_iostream)) { | |
583 if (!ssl_iostream_has_broken_client_cert(conn->ssl_iostream)) { | |
584 http_client_connection_error(conn, "SSL certificate not received"); | |
585 } else { | |
586 http_client_connection_error(conn, "Received invalid SSL certificate"); | |
587 } | |
588 } else { | |
589 const char *host = http_client_peer_get_hostname(conn->peer); | |
590 | |
591 i_assert(host != NULL); | |
592 | |
593 if (ssl_iostream_cert_match_name(conn->ssl_iostream, host) < 0) { | |
594 http_client_connection_error(conn, | |
595 "SSL certificate doesn't match host name"); | |
596 } else { | |
597 http_client_connection_debug(conn, "SSL handshake successful"); | |
598 return 0; | |
599 } | |
600 } | |
601 i_stream_close(conn->conn.input); | |
602 return -1; | |
603 } | |
604 | |
605 static int | |
606 http_client_connection_ssl_init(struct http_client_connection *conn) | |
607 { | |
608 struct ssl_iostream_settings ssl_set; | |
609 const char *source; | |
610 | |
611 if (conn->peer->ssl_ctx == NULL) { | |
612 http_client_connection_error(conn, "No SSL context"); | |
613 return -1; | |
614 } | |
615 | |
616 memset(&ssl_set, 0, sizeof(ssl_set)); | |
617 if (conn->client->set.ssl_verify) { | |
618 ssl_set.verbose_invalid_cert = TRUE; | |
619 ssl_set.verify_remote_cert = TRUE; | |
620 ssl_set.require_valid_cert = TRUE; | |
621 } | |
622 | |
623 if (conn->client->set.debug) | |
624 http_client_connection_debug(conn, "Starting SSL handshake"); | |
625 | |
626 source = t_strdup_printf | |
627 ("connection %s: ", http_client_connection_label(conn)); | |
628 if (io_stream_create_ssl(conn->peer->ssl_ctx, source, &ssl_set, | |
629 &conn->conn.input, &conn->conn.output, &conn->ssl_iostream) < 0) { | |
630 http_client_connection_error(conn, "Couldn't initialize SSL client"); | |
631 return -1; | |
632 } | |
633 ssl_iostream_set_handshake_callback(conn->ssl_iostream, | |
634 http_client_connection_ssl_handshaked, conn); | |
635 if (ssl_iostream_handshake(conn->ssl_iostream) < 0) { | |
636 http_client_connection_error(conn, "SSL handshake failed: %s", | |
637 ssl_iostream_get_last_error(conn->ssl_iostream)); | |
638 return -1; | |
639 } | |
640 | |
641 http_client_connection_ready(conn); | |
642 return 0; | |
643 } | |
644 #endif | |
645 | |
646 static void | |
647 http_client_connection_connected(struct connection *_conn, bool success) | |
648 { | |
649 struct http_client_connection *conn = | |
650 (struct http_client_connection *)_conn; | |
651 | |
652 if (!success) { | |
653 http_client_connection_error(conn, "Connect failed: %m"); | |
654 http_client_peer_connection_failure(conn->peer); | |
655 | |
656 } else { | |
657 http_client_connection_debug(conn, "Connected"); | |
658 #ifdef HTTP_BUILD_SSL | |
659 if (conn->peer->addr.ssl) { | |
660 if (http_client_connection_ssl_init(conn) < 0) | |
661 http_client_peer_connection_failure(conn->peer); | |
662 return; | |
663 } | |
664 #endif | |
665 http_client_connection_ready(conn); | |
666 } | |
667 } | |
668 | |
669 static const struct connection_settings http_client_connection_set = { | |
670 .input_max_size = (size_t)-1, | |
671 .output_max_size = (size_t)-1, | |
672 .client = TRUE | |
673 }; | |
674 | |
675 static const struct connection_vfuncs http_client_connection_vfuncs = { | |
676 .destroy = http_client_connection_destroy, | |
677 .input = http_client_connection_input, | |
678 .client_connected = http_client_connection_connected | |
679 }; | |
680 | |
681 struct connection_list * | |
682 http_client_connection_list_init(void) | |
683 { | |
684 return connection_list_init | |
685 (&http_client_connection_set, &http_client_connection_vfuncs); | |
686 } | |
687 | |
688 static int http_client_connection_connect(struct http_client_connection *conn) | |
689 { | |
690 if (conn->conn.fd_in == -1) { | |
691 if (connection_client_connect(&conn->conn) < 0) { | |
692 http_client_connection_error(conn, "Could not connect"); | |
693 return -1; | |
694 } | |
695 } | |
696 | |
697 return 0; | |
698 } | |
699 | |
700 struct http_client_connection * | |
701 http_client_connection_create(struct http_client_peer *peer) | |
702 { | |
703 struct http_client_connection *conn; | |
704 static unsigned int id = 0; | |
705 | |
706 conn = i_new(struct http_client_connection, 1); | |
707 conn->client = peer->client; | |
708 conn->peer = peer; | |
709 i_array_init(&conn->request_wait_list, 16); | |
710 | |
711 connection_init_client_ip | |
712 (peer->client->conn_list, &conn->conn, &peer->addr.ip, peer->addr.port); | |
713 | |
714 if (http_client_connection_connect(conn) < 0) { | |
715 http_client_connection_free(&conn); | |
716 return NULL; | |
717 } | |
718 | |
719 conn->id = id++; | |
720 array_append(&peer->conns, &conn, 1); | |
721 | |
722 http_client_connection_debug(conn, | |
723 "Connection created (%d parallel connections exist)", | |
724 array_count(&peer->conns)); | |
725 return conn; | |
726 } | |
727 | |
728 void http_client_connection_free(struct http_client_connection **_conn) | |
729 { | |
730 struct http_client_connection *conn = *_conn; | |
731 struct http_client_connection *const *conn_idx; | |
732 struct http_client_peer *peer = conn->peer; | |
733 struct http_client_request **req; | |
734 | |
735 http_client_connection_debug(conn, "Connection destroy"); | |
736 | |
737 conn->connected = FALSE; | |
738 | |
739 #ifdef HTTP_BUILD_SSL | |
740 if (conn->ssl_iostream != NULL) | |
741 ssl_iostream_unref(&conn->ssl_iostream); | |
742 #endif | |
743 | |
744 connection_deinit(&conn->conn); | |
745 | |
746 /* abort all pending requests */ | |
747 array_foreach_modifiable(&conn->request_wait_list, req) { | |
748 http_client_request_error(*req, HTTP_CLIENT_REQUEST_ERROR_ABORTED, | |
749 "Aborting"); | |
750 http_client_request_unref(req); | |
751 } | |
752 array_free(&conn->request_wait_list); | |
753 | |
754 if (conn->to_input != NULL) | |
755 timeout_remove(&conn->to_input); | |
756 if (conn->to_idle != NULL) | |
757 timeout_remove(&conn->to_idle); | |
758 if (conn->to_response != NULL) | |
759 timeout_remove(&conn->to_response); | |
760 | |
761 /* remove this connection from the list */ | |
762 array_foreach(&conn->peer->conns, conn_idx) { | |
763 if (*conn_idx == conn) { | |
764 array_delete(&conn->peer->conns, | |
765 array_foreach_idx(&conn->peer->conns, conn_idx), 1); | |
766 break; | |
767 } | |
768 } | |
769 | |
770 if (conn->http_parser != NULL) | |
771 http_response_parser_deinit(&conn->http_parser); | |
772 | |
773 i_free(conn); | |
774 *_conn = NULL; | |
775 | |
776 http_client_peer_connection_lost(peer); | |
777 } | |
778 | |
779 void http_client_connection_switch_ioloop(struct http_client_connection *conn) | |
780 { | |
781 if (conn->to_input != NULL) | |
782 conn->to_input = io_loop_move_timeout(&conn->to_input); | |
783 if (conn->to_idle != NULL) | |
784 conn->to_idle = io_loop_move_timeout(&conn->to_idle); | |
785 if (conn->to_response != NULL) | |
786 conn->to_response = io_loop_move_timeout(&conn->to_response); | |
787 connection_switch_ioloop(&conn->conn); | |
788 } |