diff options
Diffstat (limited to 'src/microhttpd/mhd_send.c')
-rw-r--r-- | src/microhttpd/mhd_send.c | 709 |
1 files changed, 562 insertions, 147 deletions
diff --git a/src/microhttpd/mhd_send.c b/src/microhttpd/mhd_send.c index eab145bc..018ba657 100644 --- a/src/microhttpd/mhd_send.c +++ b/src/microhttpd/mhd_send.c | |||
@@ -1,6 +1,8 @@ | |||
1 | /* | 1 | /* |
2 | This file is part of libmicrohttpd | 2 | This file is part of libmicrohttpd |
3 | Copyright (C) 2019 ng0 <ng0@n0.is> | 3 | Copyright (C) 2017,2020 Karlson2k (Evgeny Grin), Full re-write of buffering and |
4 | pushing, many bugs fixes, optimisations, sendfile() porting | ||
5 | Copyright (C) 2019 ng0 <ng0@n0.is>, Initial version of send() wrappers | ||
4 | 6 | ||
5 | This library is free software; you can redistribute it and/or | 7 | This library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public | 8 | modify it under the terms of the GNU Lesser General Public |
@@ -21,9 +23,9 @@ | |||
21 | /** | 23 | /** |
22 | * @file microhttpd/mhd_send.c | 24 | * @file microhttpd/mhd_send.c |
23 | * @brief Implementation of send() wrappers. | 25 | * @brief Implementation of send() wrappers. |
26 | * @author Karlson2k (Evgeny Grin) | ||
24 | * @author ng0 (N. Gillmann) | 27 | * @author ng0 (N. Gillmann) |
25 | * @author Christian Grothoff | 28 | * @author Christian Grothoff |
26 | * @author Evgeny Grin | ||
27 | */ | 29 | */ |
28 | 30 | ||
29 | /* Worth considering for future improvements and additions: | 31 | /* Worth considering for future improvements and additions: |
@@ -32,10 +34,6 @@ | |||
32 | * large a chunk as possible to the socket. Alternatively, | 34 | * large a chunk as possible to the socket. Alternatively, |
33 | * use madvise(..., MADV_SEQUENTIAL). */ | 35 | * use madvise(..., MADV_SEQUENTIAL). */ |
34 | 36 | ||
35 | /* Functions to be used in: send_param_adapter, MHD_send_ | ||
36 | * and every place where sendfile(), sendfile64(), setsockopt() | ||
37 | * are used. */ | ||
38 | |||
39 | #include "mhd_send.h" | 37 | #include "mhd_send.h" |
40 | #ifdef MHD_LINUX_SOLARIS_SENDFILE | 38 | #ifdef MHD_LINUX_SOLARIS_SENDFILE |
41 | #include <sys/sendfile.h> | 39 | #include <sys/sendfile.h> |
@@ -51,6 +49,7 @@ | |||
51 | #endif /* HAVE_SYS_PARAM_H */ | 49 | #endif /* HAVE_SYS_PARAM_H */ |
52 | #include "mhd_assert.h" | 50 | #include "mhd_assert.h" |
53 | 51 | ||
52 | #include "mhd_limits.h" | ||
54 | 53 | ||
55 | /** | 54 | /** |
56 | * sendfile() chuck size | 55 | * sendfile() chuck size |
@@ -107,6 +106,121 @@ MHD_send_init_static_vars_ (void) | |||
107 | 106 | ||
108 | 107 | ||
109 | /** | 108 | /** |
109 | * Set required TCP_NODELAY state for connection socket | ||
110 | * | ||
111 | * The function automatically updates sk_nodelay state. | ||
112 | * @param connection the connection to manipulate | ||
113 | * @param nodelay_state the requested new state of socket | ||
114 | * @return true if succeed, false if failed | ||
115 | */ | ||
116 | static bool | ||
117 | connection_set_nodelay_state_ (struct MHD_Connection *connection, | ||
118 | bool nodelay_state) | ||
119 | { | ||
120 | const MHD_SCKT_OPT_BOOL_ off_val = 0; | ||
121 | const MHD_SCKT_OPT_BOOL_ on_val = 1; | ||
122 | int err_code; | ||
123 | |||
124 | if (0 == setsockopt (connection->socket_fd, | ||
125 | IPPROTO_TCP, | ||
126 | TCP_NODELAY, | ||
127 | (const void *) (nodelay_state ? &on_val : &off_val), | ||
128 | sizeof (off_val))) | ||
129 | { | ||
130 | connection->sk_nodelay = nodelay_state; | ||
131 | return true; | ||
132 | } | ||
133 | err_code = MHD_socket_get_error_ (); | ||
134 | switch (err_code) | ||
135 | { | ||
136 | case ENOTSOCK: | ||
137 | /* FIXME: Could be we are talking to a pipe, maybe remember this | ||
138 | and avoid all setsockopt() in the future? */ | ||
139 | break; | ||
140 | case EBADF: | ||
141 | /* FIXME: should we die hard here? */ | ||
142 | case EINVAL: | ||
143 | /* FIXME: optlen invalid, should at least log this, maybe die */ | ||
144 | case EFAULT: | ||
145 | /* wopsie, should at least log this, FIXME: maybe die */ | ||
146 | case ENOPROTOOPT: | ||
147 | /* optlen unknown, should at least log this */ | ||
148 | default: | ||
149 | #ifdef HAVE_MESSAGES | ||
150 | MHD_DLOG (connection->daemon, | ||
151 | _ ("Setting %s option to %s state failed: %s\n"), | ||
152 | "TCP_NODELAY", | ||
153 | nodelay_state ? _ ("ON") : _ ("OFF"), | ||
154 | MHD_socket_strerr_ (err_code)); | ||
155 | #endif /* HAVE_MESSAGES */ | ||
156 | break; | ||
157 | } | ||
158 | return false; | ||
159 | } | ||
160 | |||
161 | |||
162 | #if defined(MHD_TCP_CORK_NOPUSH) | ||
163 | /** | ||
164 | * Set required cork state for connection socket | ||
165 | * | ||
166 | * The function automatically updates sk_corked state. | ||
167 | * @param connection the connection to manipulate | ||
168 | * @param cork_state the requested new state of socket | ||
169 | * @return true if succeed, false if failed | ||
170 | */ | ||
171 | static bool | ||
172 | connection_set_cork_state_ (struct MHD_Connection *connection, | ||
173 | bool cork_state) | ||
174 | { | ||
175 | const MHD_SCKT_OPT_BOOL_ off_val = 0; | ||
176 | const MHD_SCKT_OPT_BOOL_ on_val = 1; | ||
177 | int err_code; | ||
178 | |||
179 | if (0 == setsockopt (connection->socket_fd, | ||
180 | IPPROTO_TCP, | ||
181 | MHD_TCP_CORK_NOPUSH, | ||
182 | (const void *) (cork_state ? &on_val : &off_val), | ||
183 | sizeof (off_val))) | ||
184 | { | ||
185 | connection->sk_corked = cork_state; | ||
186 | return true; | ||
187 | } | ||
188 | err_code = MHD_socket_get_error_ (); | ||
189 | switch (err_code) | ||
190 | { | ||
191 | case ENOTSOCK: | ||
192 | /* FIXME: Could be we are talking to a pipe, maybe remember this | ||
193 | and avoid all setsockopt() in the future? */ | ||
194 | break; | ||
195 | case EBADF: | ||
196 | /* FIXME: should we die hard here? */ | ||
197 | case EINVAL: | ||
198 | /* FIXME: optlen invalid, should at least log this, maybe die */ | ||
199 | case EFAULT: | ||
200 | /* wopsie, should at least log this, FIXME: maybe die */ | ||
201 | case ENOPROTOOPT: | ||
202 | /* optlen unknown, should at least log this */ | ||
203 | default: | ||
204 | #ifdef HAVE_MESSAGES | ||
205 | MHD_DLOG (connection->daemon, | ||
206 | _ ("Setting %s option to %s state failed: %s\n"), | ||
207 | #ifdef TCP_CORK | ||
208 | "TCP_CORK", | ||
209 | #else /* ! TCP_CORK */ | ||
210 | "TCP_NOPUSH", | ||
211 | #endif /* ! TCP_CORK */ | ||
212 | cork_state ? _ ("ON") : _ ("OFF"), | ||
213 | MHD_socket_strerr_ (err_code)); | ||
214 | #endif /* HAVE_MESSAGES */ | ||
215 | break; | ||
216 | } | ||
217 | return false; | ||
218 | } | ||
219 | |||
220 | |||
221 | #endif /* MHD_TCP_CORK_NOPUSH */ | ||
222 | |||
223 | /** | ||
110 | * Handle pre-send setsockopt calls. | 224 | * Handle pre-send setsockopt calls. |
111 | * | 225 | * |
112 | * @param connection the MHD_Connection structure | 226 | * @param connection the MHD_Connection structure |
@@ -126,96 +240,259 @@ pre_send_setopt (struct MHD_Connection *connection, | |||
126 | * Final piece is indicated by push_data == true. */ | 240 | * Final piece is indicated by push_data == true. */ |
127 | const bool buffer_data = (! push_data); | 241 | const bool buffer_data = (! push_data); |
128 | 242 | ||
129 | #ifdef MHD_USE_MSG_MORE | 243 | /* The goal is to minimise the total number of additional sys-calls |
130 | if (plain_send) | 244 | * before and after send(). |
245 | * The following tricky (over-)complicated algorithm typically use zero, | ||
246 | * one or two additional sys-calls (depending on OS) for each response. */ | ||
247 | |||
248 | if (buffer_data) | ||
131 | { | 249 | { |
132 | /* MSG_MORE is used, no need for extra syscalls! */ | 250 | /* Need to buffer data if possible. */ |
133 | return; | 251 | #ifdef MHD_USE_MSG_MORE |
134 | } | 252 | if (plain_send) |
253 | return; /* Data is buffered by send() with MSG_MORE flag. | ||
254 | * No need to check or change anything. */ | ||
135 | #else /* ! MHD_USE_MSG_MORE */ | 255 | #else /* ! MHD_USE_MSG_MORE */ |
136 | (void) plain_send; /* Mute compiler warning. */ | 256 | (void) plain_send; /* Mute compiler warning. */ |
137 | #endif /* ! MHD_USE_MSG_MORE */ | 257 | #endif /* ! MHD_USE_MSG_MORE */ |
138 | 258 | ||
139 | #if defined(MHD_TCP_CORK_NOPUSH) | 259 | #ifdef MHD_TCP_CORK_NOPUSH |
140 | /* If connection is already in required corked state, do nothing. */ | 260 | if (_MHD_ON == connection->sk_corked) |
141 | if (connection->sk_corked == buffer_data) | 261 | return; /* The connection was already corked. */ |
142 | return; | 262 | |
143 | if (push_data) | 263 | if (connection_set_cork_state_ (connection, true)) |
144 | return; /* nothing to do *pre* syscall! BUG: to be fixed */ | 264 | return; /* The connection has been corked. */ |
145 | ret = MHD_socket_cork_ (connection->socket_fd, | 265 | |
146 | buffer_data); | 266 | /* Failed to cork the connection. |
147 | if (0 != ret) | 267 | * Really unlikely to happen on TCP connections. */ |
148 | { | 268 | #endif /* MHD_TCP_CORK_NOPUSH */ |
149 | connection->sk_corked = buffer_data; | 269 | if (_MHD_OFF == connection->sk_nodelay) |
270 | return; /* TCP_NODELAY was not set for the socket. | ||
271 | * Nagle's algorithm will buffer some data. */ | ||
272 | |||
273 | /* Try to reset TCP_NODELAY state for the socket. | ||
274 | * Ignore possible error as no other options exist to | ||
275 | * buffer data. */ | ||
276 | connection_set_nodelay_state_ (connection, false); | ||
277 | /* TCP_NODELAY has been (hopefully) reset for the socket. | ||
278 | * Nagle's algorithm will buffer some data. */ | ||
150 | return; | 279 | return; |
151 | } | 280 | } |
152 | switch (errno) | 281 | |
153 | { | 282 | /* Need to push data after send() */ |
154 | case ENOTSOCK: | 283 | /* Prefer to make additional sys-call after the send() |
155 | /* FIXME: Could be we are talking to a pipe, maybe remember this | 284 | * as the next send() may consume only part of the |
156 | and avoid all setsockopt() in the future? */ | 285 | * prepared data and additional send() may be required. */ |
157 | break; | 286 | #ifdef MHD_TCP_CORK_NOPUSH |
158 | case EBADF: | 287 | #ifdef _MHD_CORK_RESET_PUSH_DATA |
159 | /* FIXME: should we die hard here? */ | 288 | #ifdef _MHD_CORK_RESET_PUSH_DATA_ALWAYS |
160 | break; | 289 | /* Data can be pushed immediately by uncorking socket regardless of |
161 | case EINVAL: | 290 | * cork state before. */ |
162 | /* FIXME: optlen invalid, should at least log this, maybe die */ | 291 | /* This is typical for Linux, no other kernel with |
163 | #ifdef HAVE_MESSAGES | 292 | * such behavior are known so far. */ |
164 | MHD_DLOG (connection->daemon, | 293 | |
165 | _ ("optlen invalid: %s\n"), | 294 | /* No need to check the current state of TCP_CORK / TCP_NOPUSH |
166 | MHD_socket_last_strerr_ ()); | 295 | * as reset of cork will push the data anyway. */ |
167 | #endif | 296 | return; /* Data may be pushed by resetting of |
168 | break; | 297 | * TCP_CORK / TCP_NOPUSH after send() */ |
169 | case EFAULT: | 298 | #else /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */ |
170 | /* wopsie, should at least log this, FIXME: maybe die */ | 299 | /* Reset of TCP_CORK / TCP_NOPUSH will push the data |
171 | #ifdef HAVE_MESSAGES | 300 | * only if socket is corked. */ |
172 | MHD_DLOG (connection->daemon, | 301 | |
173 | _ ( | 302 | #ifdef _MHD_NODELAY_SET_PUSH_DATA_ALWAYS |
174 | "The address pointed to by optval is not a valid part of the process address space: %s\n"), | 303 | /* Data can be pushed immediately by setting TCP_NODELAY regardless |
175 | MHD_socket_last_strerr_ ()); | 304 | * of TCP_NODDELAY or corking state before. */ |
176 | #endif | 305 | |
177 | break; | 306 | /* Dead code currently, no known kernels with such behavior. */ |
178 | case ENOPROTOOPT: | 307 | return; /* Data may be pushed by setting of TCP_NODELAY after send(). |
179 | /* optlen unknown, should at least log this */ | 308 | No need to make extra sys-calls before send().*/ |
180 | #ifdef HAVE_MESSAGES | 309 | #else /* ! _MHD_NODELAY_SET_PUSH_DATA_ALWAYS */ |
181 | MHD_DLOG (connection->daemon, | 310 | |
182 | _ ("The option is unknown: %s\n"), | 311 | #ifdef _MHD_NODELAY_SET_PUSH_DATA |
183 | MHD_socket_last_strerr_ ()); | 312 | /* Setting of TCP_NODELAY will push the data only if |
184 | #endif | 313 | * both TCP_NODELAY and TCP_CORK / TCP_NOPUSH were not set. */ |
185 | break; | 314 | |
186 | default: | 315 | /* Data can be pushed immediately by uncorking socket if |
187 | /* any others? man page does not list more... */ | 316 | * socket was corked before or by setting TCP_NODELAY if |
188 | break; | 317 | * socket was not corked and TCP_NODELAY was not set before. */ |
189 | } | 318 | |
190 | #else | 319 | /* Dead code currently as Linux is the only kernel that push |
191 | /* CORK/NOPUSH do not exist on this platform, | 320 | * data by setting of TCP_NODELAY and Linux push data always. */ |
192 | Turning on/off of Naggle's algorithm | 321 | #else /* ! _MHD_NODELAY_SET_PUSH_DATA */ |
193 | (otherwise we keep it always off) */ | 322 | /* Data can be pushed immediately by uncorking socket or |
194 | if (connection->sk_nodelay == push_data) | 323 | * can be pushed by send() on uncorked socket if |
324 | * TCP_NODELAY was set *before*. */ | ||
325 | |||
326 | /* This is typical FreeBSD behavior. */ | ||
327 | #endif /* ! _MHD_NODELAY_SET_PUSH_DATA */ | ||
328 | |||
329 | if (_MHD_ON == connection->sk_corked) | ||
330 | return; /* Socket is corked. Data can be pushed by resetting of | ||
331 | * TCP_CORK / TCP_NOPUSH after send() */ | ||
332 | else if (_MHD_OFF == connection->sk_corked) | ||
195 | { | 333 | { |
196 | /* nothing to do, success! */ | 334 | /* The socket is not corked. */ |
197 | return; | 335 | if (_MHD_ON == connection->sk_nodelay) |
336 | return; /* TCP_NODELAY was already set, | ||
337 | * data will be pushed automatically by the next send() */ | ||
338 | #ifdef _MHD_NODELAY_SET_PUSH_DATA | ||
339 | else if (_MHD_UNKNOWN == connection->sk_nodelay) | ||
340 | { | ||
341 | /* Setting TCP_NODELAY may push data. | ||
342 | * Cork socket here and uncork after send(). */ | ||
343 | if (connection_set_cork_state_ (connection, true)) | ||
344 | return; /* The connection has been corked. | ||
345 | * Data can be pushed by resetting of | ||
346 | * TCP_CORK / TCP_NOPUSH after send() */ | ||
347 | else | ||
348 | { | ||
349 | /* The socket cannot be corked. | ||
350 | * Really unlikely to happen on TCP connections */ | ||
351 | /* Have to set TCP_NODELAY. | ||
352 | * If TCP_NODELAY real system state was OFF then | ||
353 | * already buffered data may be pushed here, but this is unlikely | ||
354 | * to happen as it is only a backup solution when corking has failed. | ||
355 | * Ignore possible error here as no other options exist to | ||
356 | * push data. */ | ||
357 | connection_set_nodelay_state_ (connection, true); | ||
358 | /* TCP_NODELAY has been (hopefully) set for the socket. | ||
359 | * The data will be pushed by the next send(). */ | ||
360 | return; | ||
361 | } | ||
362 | } | ||
363 | #endif /* _MHD_NODELAY_SET_PUSH_DATA */ | ||
364 | else | ||
365 | { | ||
366 | #ifdef _MHD_NODELAY_SET_PUSH_DATA | ||
367 | /* TCP_NODELAY was switched off and | ||
368 | * the socket is not corked. */ | ||
369 | #else /* ! _MHD_NODELAY_SET_PUSH_DATA */ | ||
370 | /* Socket is not corked and TCP_NODELAY was not set or unknown. */ | ||
371 | #endif /* ! _MHD_NODELAY_SET_PUSH_DATA */ | ||
372 | |||
373 | /* At least one additional sys-call is required. */ | ||
374 | /* Setting TCP_NODELAY is optimal here as data will be pushed | ||
375 | * automatically by the next send() and no additional | ||
376 | * sys-call are needed after the send(). */ | ||
377 | if (connection_set_nodelay_state_ (connection, true)) | ||
378 | return; | ||
379 | else | ||
380 | { | ||
381 | /* Failed to set TCP_NODELAY for the socket. | ||
382 | * Really unlikely to happen on TCP connections. */ | ||
383 | /* Cork the socket here and make additional sys-call | ||
384 | * to uncork the socket after send(). */ | ||
385 | /* Ignore possible error here as no other options exist to | ||
386 | * push data. */ | ||
387 | connection_set_cork_state_ (connection, true); | ||
388 | /* The connection has been (hopefully) corked. | ||
389 | * Data can be pushed by resetting of TCP_CORK / TCP_NOPUSH | ||
390 | * after send() */ | ||
391 | return; | ||
392 | } | ||
393 | } | ||
198 | } | 394 | } |
199 | if (0 == MHD_socket_set_nodelay_ (connection->socket_fd, | 395 | /* Corked state is unknown. Need to make sys-call here otherwise |
200 | (push_data))) | 396 | * data may not be pushed. */ |
201 | connection->sk_nodelay = push_data; | 397 | if (connection_set_cork_state_ (connection, true)) |
202 | #endif | 398 | return; /* The connection has been corked. |
399 | * Data can be pushed by resetting of | ||
400 | * TCP_CORK / TCP_NOPUSH after send() */ | ||
401 | /* The socket cannot be corked. | ||
402 | * Really unlikely to happen on TCP connections */ | ||
403 | if (_MHD_ON == connection->sk_nodelay) | ||
404 | return; /* TCP_NODELAY was already set, | ||
405 | * data will be pushed by the next send() */ | ||
406 | /* Have to set TCP_NODELAY. */ | ||
407 | #ifdef _MHD_NODELAY_SET_PUSH_DATA | ||
408 | /* If TCP_NODELAY state was unknown (external connection) then | ||
409 | * already buffered data may be pushed here, but this is unlikely | ||
410 | * to happen as it is only a backup solution when corking has failed. */ | ||
411 | #endif /* _MHD_NODELAY_SET_PUSH_DATA */ | ||
412 | /* Ignore possible error here as no other options exist to | ||
413 | * push data. */ | ||
414 | connection_set_nodelay_state_ (connection, true); | ||
415 | /* TCP_NODELAY has been (hopefully) set for the socket. | ||
416 | * The data will be pushed by the next send(). */ | ||
417 | return; | ||
418 | #endif /* ! _MHD_NODELAY_SET_PUSH_DATA_ALWAYS */ | ||
419 | #endif /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */ | ||
420 | #else /* ! _MHD_CORK_RESET_PUSH_DATA */ | ||
421 | /* Neither uncorking the socket or setting TCP_NODELAY | ||
422 | * push the data immediately. */ | ||
423 | /* The only way to push the data is to use send() on uncorked | ||
424 | * socket with TCP_NODELAY switched on . */ | ||
425 | |||
426 | /* This is a typical *BSD (except FreeBSD) and Darwin behavior. */ | ||
427 | |||
428 | /* Uncork socket if socket wasn't uncorked. */ | ||
429 | if (_MHD_OFF != connection->sk_corked) | ||
430 | connection_set_cork_state_ (connection, false); | ||
431 | |||
432 | /* Set TCP_NODELAY if it wasn't set. */ | ||
433 | if (_MHD_ON != connection->sk_nodelay) | ||
434 | connection_set_nodelay_state_ (connection, true); | ||
435 | |||
436 | return; | ||
437 | #endif /* ! _MHD_CORK_RESET_PUSH_DATA */ | ||
438 | #else /* ! MHD_TCP_CORK_NOPUSH */ | ||
439 | /* Buffering of data is controlled only by | ||
440 | * Nagel's algorithm. */ | ||
441 | /* Set TCP_NODELAY if it wasn't set. */ | ||
442 | if (_MHD_ON != connection->sk_nodelay) | ||
443 | connection_set_nodelay_state_ (connection, true); | ||
444 | #endif /* ! MHD_TCP_CORK_NOPUSH */ | ||
445 | } | ||
446 | |||
447 | |||
448 | #ifndef _MHD_CORK_RESET_PUSH_DATA_ALWAYS | ||
449 | /** | ||
450 | * Send zero-sized data | ||
451 | * | ||
452 | * This function use send of zero-sized data to kick data from the socket | ||
453 | * buffers to the network. The socket must not be corked and must have | ||
454 | * TCP_NODELAY switched on. | ||
455 | * Used only as last resort option, when other options are failed due to | ||
456 | * some errors. | ||
457 | * Should not be called on typical data processing. | ||
458 | * @return true if succeed, false if failed | ||
459 | */ | ||
460 | static bool | ||
461 | zero_send_ (struct MHD_Connection *connection) | ||
462 | { | ||
463 | int dummy; | ||
464 | mhd_assert (_MHD_OFF == connection->sk_corked); | ||
465 | mhd_assert (_MHD_ON == connection->sk_nodelay); | ||
466 | |||
467 | dummy = 0; /* Mute compiler and analyzer warnings */ | ||
468 | |||
469 | if (0 == MHD_send_ (connection->socket_fd, &dummy, 0)) | ||
470 | return true; | ||
471 | |||
472 | #ifdef HAVE_MESSAGES | ||
473 | MHD_DLOG (connection->daemon, | ||
474 | _ ("Zero-send failed: %s\n"), | ||
475 | MHD_socket_last_strerr_ () ); | ||
476 | #endif /* HAVE_MESSAGES */ | ||
477 | return false; | ||
203 | } | 478 | } |
204 | 479 | ||
205 | 480 | ||
481 | #endif /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */ | ||
482 | |||
206 | /** | 483 | /** |
207 | * Handle post-send setsockopt calls. | 484 | * Handle post-send setsockopt calls. |
208 | * | 485 | * |
209 | * @param connection the MHD_Connection structure | 486 | * @param connection the MHD_Connection structure |
210 | * @param plain_send set to true if plain send() or sendmsg() have been | 487 | * @param plain_send_next set to true if plain send() or sendmsg() will be |
211 | * called, | 488 | * called next, |
212 | * set to false if TLS socket send(), sendfile() or | 489 | * set to false if TLS socket send(), sendfile() or |
213 | * writev() has been called. | 490 | * writev() will be called next. |
214 | * @param push_data whether to push data to the network from buffers | 491 | * @param push_data whether to push data to the network from buffers |
215 | */ | 492 | */ |
216 | static void | 493 | static void |
217 | post_send_setopt (struct MHD_Connection *connection, | 494 | post_send_setopt (struct MHD_Connection *connection, |
218 | bool plain_send, | 495 | bool plain_send_next, |
219 | bool push_data) | 496 | bool push_data) |
220 | { | 497 | { |
221 | int ret; | 498 | int ret; |
@@ -223,70 +500,163 @@ post_send_setopt (struct MHD_Connection *connection, | |||
223 | * Final piece is indicated by push_data == true. */ | 500 | * Final piece is indicated by push_data == true. */ |
224 | const bool buffer_data = (! push_data); | 501 | const bool buffer_data = (! push_data); |
225 | 502 | ||
503 | if (buffer_data) | ||
504 | return; /* Nothing to do after send(). */ | ||
505 | |||
506 | #ifndef MHD_USE_MSG_MORE | ||
507 | (void) plain_send_next; /* Mute compiler warning */ | ||
508 | #endif /* ! MHD_USE_MSG_MORE */ | ||
509 | |||
510 | /* Need to push data. */ | ||
511 | #ifdef MHD_TCP_CORK_NOPUSH | ||
512 | #ifdef _MHD_CORK_RESET_PUSH_DATA_ALWAYS | ||
513 | #ifdef _MHD_NODELAY_SET_PUSH_DATA_ALWAYS | ||
226 | #ifdef MHD_USE_MSG_MORE | 514 | #ifdef MHD_USE_MSG_MORE |
227 | if (plain_send) | 515 | if (_MHD_OFF == connection->sk_corked) |
228 | { | 516 | { |
229 | /* MSG_MORE is used, no need for extra syscalls! */ | 517 | if (_MHD_ON == connection->sk_nodelay) |
230 | return; | 518 | return; /* Data was already pushed by send(). */ |
519 | } | ||
520 | /* This is Linux kernel. There are options: | ||
521 | * * Push the data by setting of TCP_NODELAY (without change | ||
522 | * of the cork on the socket), | ||
523 | * * Push the data by resetting of TCP_CORK. | ||
524 | * The optimal choice depends on the next final send functions | ||
525 | * used on the same socket. If TCP_NODELAY wasn't set then push | ||
526 | * data by setting TCP_NODELAY (TCP_NODELAY will not be removed | ||
527 | * and is needed to push the data by send() without MSG_MORE). | ||
528 | * If send()/sendmsg() will be used next than push data by | ||
529 | * reseting of TCP_CORK so next send without MSG_MORE will push | ||
530 | * data to the network (without additional sys-call to push data). | ||
531 | * If next final send function will not support MSG_MORE (like | ||
532 | * sendfile() or TLS-connection) than push data by setting | ||
533 | * TCP_NODELAY so socket will remain corked (no additional | ||
534 | * sys-call before next send()). */ | ||
535 | if ((_MHD_ON != connection->sk_nodelay) || | ||
536 | (! plain_send_next)) | ||
537 | { | ||
538 | if (connection_set_nodelay_state_ (connection, true)) | ||
539 | return; /* Data has been pushed by TCP_NODELAY. */ | ||
540 | /* Failed to set TCP_NODELAY for the socket. | ||
541 | * Really unlikely to happen on TCP connections. */ | ||
542 | if (connection_set_cork_state_ (connection, false)) | ||
543 | return; /* Data has been pushed by uncorking the socket. */ | ||
544 | /* Failed to uncork the socket. | ||
545 | * Really unlikely to happen on TCP connections. */ | ||
546 | |||
547 | /* The socket cannot be uncorked, no way to push data */ | ||
548 | } | ||
549 | else | ||
550 | { | ||
551 | if (connection_set_cork_state_ (connection, false)) | ||
552 | return; /* Data has been pushed by uncorking the socket. */ | ||
553 | /* Failed to uncork the socket. | ||
554 | * Really unlikely to happen on TCP connections. */ | ||
555 | if (connection_set_nodelay_state_ (connection, true)) | ||
556 | return; /* Data has been pushed by TCP_NODELAY. */ | ||
557 | /* Failed to set TCP_NODELAY for the socket. | ||
558 | * Really unlikely to happen on TCP connections. */ | ||
559 | |||
560 | /* The socket cannot be uncorked, no way to push data */ | ||
231 | } | 561 | } |
232 | #else /* ! MHD_USE_MSG_MORE */ | 562 | #else /* ! MHD_USE_MSG_MORE */ |
233 | (void) plain_send; /* Mute compiler warning. */ | 563 | /* Use setting of TCP_NODELAY here to avoid sys-call |
564 | * for corking the socket during sending of the next response. */ | ||
565 | if (connection_set_nodelay_state_ (connection, true)) | ||
566 | return; /* Data was pushed by TCP_NODELAY. */ | ||
567 | /* Failed to set TCP_NODELAY for the socket. | ||
568 | * Really unlikely to happen on TCP connections. */ | ||
569 | if (connection_set_cork_state_ (connection, false)) | ||
570 | return; /* Data was pushed by uncorking the socket. */ | ||
571 | /* Failed to uncork the socket. | ||
572 | * Really unlikely to happen on TCP connections. */ | ||
573 | |||
574 | /* The socket remains corked, no way to push data */ | ||
234 | #endif /* ! MHD_USE_MSG_MORE */ | 575 | #endif /* ! MHD_USE_MSG_MORE */ |
576 | #else /* ! _MHD_NODELAY_SET_PUSH_DATA_ALWAYS */ | ||
577 | if (connection_set_cork_state_ (connection, false)) | ||
578 | return; /* Data was pushed by uncorking the socket. */ | ||
579 | /* Failed to uncork the socket. | ||
580 | * Really unlikely to happen on TCP connections. */ | ||
581 | return; /* Socket remains corked, no way to push data */ | ||
582 | #endif /* ! _MHD_NODELAY_SET_PUSH_DATA_ALWAYS */ | ||
583 | #else /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */ | ||
584 | /* This is a typical *BSD or Darwin kernel. */ | ||
235 | 585 | ||
236 | #if defined(MHD_TCP_CORK_NOPUSH) | 586 | if (_MHD_OFF == connection->sk_corked) |
237 | /* If connection is already in required corked state, do nothing. */ | ||
238 | if (connection->sk_corked == buffer_data) | ||
239 | return; | ||
240 | if (buffer_data) | ||
241 | return; /* nothing to do *post* syscall (in fact, we should never | ||
242 | get here, as sk_corked should have succeeded in the | ||
243 | pre-syscall) */ | ||
244 | ret = MHD_socket_cork_ (connection->socket_fd, | ||
245 | buffer_data); | ||
246 | if (0 != ret) | ||
247 | { | 587 | { |
248 | connection->sk_corked = buffer_data; | 588 | if (_MHD_ON == connection->sk_nodelay) |
249 | return; | 589 | return; /* Data was already pushed by send(). */ |
590 | |||
591 | /* Unlikely to reach this code. | ||
592 | * TCP_NODELAY should be turned on before send(). */ | ||
593 | if (connection_set_nodelay_state_ (connection, true)) | ||
594 | { | ||
595 | /* TCP_NODELAY has been set on uncorked socket. | ||
596 | * Use zero-send to push the data. */ | ||
597 | if (zero_send_ (connection)) | ||
598 | return; /* The data has been pushed by zero-send. */ | ||
599 | } | ||
600 | |||
601 | /* Failed to push the data by all means. */ | ||
602 | /* There is nothing left to try. */ | ||
250 | } | 603 | } |
251 | switch (errno) | 604 | else |
252 | { | 605 | { |
253 | case ENOTSOCK: | 606 | #ifdef _MHD_CORK_RESET_PUSH_DATA |
254 | /* FIXME: Could be we are talking to a pipe, maybe remember this | 607 | enum MHD_tristate old_cork_state = connection->sk_corked; |
255 | and avoid all setsockopt() in the future? */ | 608 | #endif /* _MHD_CORK_RESET_PUSH_DATA */ |
256 | break; | 609 | /* The socket is corked or cork state is unknown. */ |
257 | case EBADF: | 610 | |
258 | /* FIXME: should we die hard here? */ | 611 | if (connection_set_cork_state_ (connection, false)) |
259 | break; | 612 | { |
260 | case EINVAL: | 613 | #ifdef _MHD_CORK_RESET_PUSH_DATA |
261 | /* FIXME: optlen invalid, should at least log this, maybe die */ | 614 | /* FreeBSD kernel */ |
262 | #ifdef HAVE_MESSAGES | 615 | if (_MHD_OFF == old_cork_state) |
263 | MHD_DLOG (connection->daemon, | 616 | return; /* Data has been pushed by uncorking the socket. */ |
264 | _ ("optlen invalid: %s\n"), | 617 | #endif /* _MHD_CORK_RESET_PUSH_DATA */ |
265 | MHD_socket_last_strerr_ ()); | 618 | |
266 | #endif | 619 | /* Unlikely to reach this code. |
267 | break; | 620 | * The data should be pushed by uncorking (FreeBSD) or |
268 | case EFAULT: | 621 | * the socket should be uncorked before send(). */ |
269 | /* wopsie, should at least log this, FIXME: maybe die */ | 622 | if ((_MHD_ON == connection->sk_nodelay) || |
270 | #ifdef HAVE_MESSAGES | 623 | (connection_set_nodelay_state_ (connection, true))) |
271 | MHD_DLOG (connection->daemon, | 624 | { |
272 | _ ( | 625 | /* TCP_NODELAY is turned ON on uncorked socket. |
273 | "The address pointed to by optval is not a valid part of the process address space: %s\n"), | 626 | * Use zero-send to push the data. */ |
274 | MHD_socket_last_strerr_ ()); | 627 | if (zero_send_ (connection)) |
275 | #endif | 628 | return; /* The data has been pushed by zero-send. */ |
276 | break; | 629 | } |
277 | case ENOPROTOOPT: | 630 | } |
278 | /* optlen unknown, should at least log this */ | 631 | /* The socket remains corked. Data cannot be pushed. */ |
279 | #ifdef HAVE_MESSAGES | ||
280 | MHD_DLOG (connection->daemon, | ||
281 | _ ("The option is unknown: %s\n"), | ||
282 | MHD_socket_last_strerr_ ()); | ||
283 | #endif | ||
284 | break; | ||
285 | default: | ||
286 | /* any others? man page does not list more... */ | ||
287 | break; | ||
288 | } | 632 | } |
289 | #endif | 633 | #endif /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */ |
634 | #else /* ! MHD_TCP_CORK_NOPUSH */ | ||
635 | /* Corking is not supported. Buffering is controlled | ||
636 | * by TCP_NODELAY only. */ | ||
637 | mhd_assert (_MHD_ON != connection->sk_corked); | ||
638 | if (_MHD_ON == connection->sk_nodelay) | ||
639 | return; /* Data was already pushed by send(). */ | ||
640 | |||
641 | /* Unlikely to reach this code. | ||
642 | * TCP_NODELAY should be turned on before send(). */ | ||
643 | if (connection_set_nodelay_state_ (connection, true)) | ||
644 | { | ||
645 | /* TCP_NODELAY has been set. | ||
646 | * Use zero-send to push the data. */ | ||
647 | if (zero_send_ (connection)) | ||
648 | return; /* The data has been pushed by zero-send. */ | ||
649 | } | ||
650 | |||
651 | /* Failed to push the data. */ | ||
652 | #endif /* ! MHD_TCP_CORK_NOPUSH */ | ||
653 | #ifdef HAVE_MESSAGES | ||
654 | MHD_DLOG (connection->daemon, | ||
655 | _ ("Failed to put the data from buffers to the network. " | ||
656 | "Client may experience some delay " | ||
657 | "(usually in range 200ms - 5 sec).\n")); | ||
658 | #endif /* HAVE_MESSAGES */ | ||
659 | return; | ||
290 | } | 660 | } |
291 | 661 | ||
292 | 662 | ||
@@ -422,8 +792,16 @@ MHD_send_on_connection_ (struct MHD_Connection *connection, | |||
422 | connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY; | 792 | connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY; |
423 | #endif /* EPOLL_SUPPORT */ | 793 | #endif /* EPOLL_SUPPORT */ |
424 | } | 794 | } |
425 | post_send_setopt (connection, tls_conn, | 795 | |
426 | (push_data && (buffer_size == (size_t) ret)) ); | 796 | /* If there is a need to push the data from network buffers |
797 | * call post_send_setopt(). */ | ||
798 | /* If TLS connection is used then next final send() will be | ||
799 | * without MSG_MORE support. If non-TLS connection is used | ||
800 | * it's unknown whether sendfile() will be used or not so | ||
801 | * assume that next call will be the same, like this call. */ | ||
802 | if ( (push_data) && | ||
803 | (buffer_size == (size_t) ret) ) | ||
804 | post_send_setopt (connection, (! tls_conn), push_data); | ||
427 | 805 | ||
428 | return ret; | 806 | return ret; |
429 | } | 807 | } |
@@ -455,7 +833,9 @@ MHD_send_on_connection2_ (struct MHD_Connection *connection, | |||
455 | { | 833 | { |
456 | MHD_socket s = connection->socket_fd; | 834 | MHD_socket s = connection->socket_fd; |
457 | ssize_t ret; | 835 | ssize_t ret; |
836 | #if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV) | ||
458 | struct iovec vector[2]; | 837 | struct iovec vector[2]; |
838 | #endif /* HAVE_SENDMSG || HAVE_WRITEV */ | ||
459 | #ifdef HTTPS_SUPPORT | 839 | #ifdef HTTPS_SUPPORT |
460 | const bool tls_conn = (connection->daemon->options & MHD_USE_TLS); | 840 | const bool tls_conn = (connection->daemon->options & MHD_USE_TLS); |
461 | #else /* ! HTTPS_SUPPORT */ | 841 | #else /* ! HTTPS_SUPPORT */ |
@@ -477,7 +857,13 @@ MHD_send_on_connection2_ (struct MHD_Connection *connection, | |||
477 | #if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV) | 857 | #if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV) |
478 | /* Since we generally give the fully answer, we do not want | 858 | /* Since we generally give the fully answer, we do not want |
479 | corking to happen */ | 859 | corking to happen */ |
480 | pre_send_setopt (connection, (! tls_conn), true); | 860 | pre_send_setopt (connection, |
861 | #if HAVE_SENDMSG | ||
862 | true, | ||
863 | #elif HAVE_WRITEV | ||
864 | false, | ||
865 | #endif /* HAVE_WRITEV */ | ||
866 | true); | ||
481 | 867 | ||
482 | vector[0].iov_base = (void *) header; | 868 | vector[0].iov_base = (void *) header; |
483 | vector[0].iov_len = header_size; | 869 | vector[0].iov_len = header_size; |
@@ -509,10 +895,21 @@ MHD_send_on_connection2_ (struct MHD_Connection *connection, | |||
509 | } | 895 | } |
510 | #endif | 896 | #endif |
511 | 897 | ||
512 | /* Only if we succeeded sending the full buffer, we need to make sure that | 898 | /* If there is a need to push the data from network buffers |
513 | the OS flushes at the end */ | 899 | * call post_send_setopt(). */ |
514 | post_send_setopt (connection, (! tls_conn), | 900 | /* If TLS connection is used then next final send() will be |
515 | (header_size + buffer_size == (size_t) ret)); | 901 | * without MSG_MORE support. If non-TLS connection is used |
902 | * it's unknown whether sendfile() will be used or not so | ||
903 | * assume that next final send() will be the same, like for | ||
904 | * this response. */ | ||
905 | if ((header_size + buffer_size) == (size_t) ret) | ||
906 | post_send_setopt (connection, | ||
907 | #if HAVE_SENDMSG | ||
908 | true, | ||
909 | #elif HAVE_WRITEV | ||
910 | false, | ||
911 | #endif /* HAVE_WRITEV */ | ||
912 | true); | ||
516 | 913 | ||
517 | return ret; | 914 | return ret; |
518 | 915 | ||
@@ -563,22 +960,36 @@ MHD_send_sendfile_ (struct MHD_Connection *connection) | |||
563 | const size_t chunk_size = used_thr_p_c ? MHD_SENFILE_CHUNK_THR_P_C_ : | 960 | const size_t chunk_size = used_thr_p_c ? MHD_SENFILE_CHUNK_THR_P_C_ : |
564 | MHD_SENFILE_CHUNK_; | 961 | MHD_SENFILE_CHUNK_; |
565 | size_t send_size = 0; | 962 | size_t send_size = 0; |
963 | bool push_data; | ||
566 | mhd_assert (MHD_resp_sender_sendfile == connection->resp_sender); | 964 | mhd_assert (MHD_resp_sender_sendfile == connection->resp_sender); |
567 | mhd_assert (0 == (connection->daemon->options & MHD_USE_TLS)); | 965 | mhd_assert (0 == (connection->daemon->options & MHD_USE_TLS)); |
568 | 966 | ||
569 | pre_send_setopt (connection, false, true); | ||
570 | |||
571 | offsetu64 = connection->response_write_position | 967 | offsetu64 = connection->response_write_position |
572 | + connection->response->fd_off; | 968 | + connection->response->fd_off; |
573 | left = connection->response->total_size - connection->response_write_position; | ||
574 | /* Do not allow system to stick sending on single fast connection: | ||
575 | * use 128KiB chunks (2MiB for thread-per-connection). */ | ||
576 | send_size = (left > chunk_size) ? chunk_size : (size_t) left; | ||
577 | if (max_off_t < offsetu64) | 969 | if (max_off_t < offsetu64) |
578 | { /* Retry to send with standard 'send()'. */ | 970 | { /* Retry to send with standard 'send()'. */ |
579 | connection->resp_sender = MHD_resp_sender_std; | 971 | connection->resp_sender = MHD_resp_sender_std; |
580 | return MHD_ERR_AGAIN_; | 972 | return MHD_ERR_AGAIN_; |
581 | } | 973 | } |
974 | |||
975 | left = connection->response->total_size - connection->response_write_position; | ||
976 | |||
977 | if ( (uint64_t) SSIZE_MAX > left) | ||
978 | left = SSIZE_MAX; | ||
979 | /* Do not allow system to stick sending on single fast connection: | ||
980 | * use 128KiB chunks (2MiB for thread-per-connection). */ | ||
981 | if (chunk_size < left) | ||
982 | { | ||
983 | send_size = chunk_size; | ||
984 | push_data = false; /* No need to push data, there is more to send, */ | ||
985 | } | ||
986 | else | ||
987 | { | ||
988 | send_size = (size_t) left; | ||
989 | push_data = true; /* Final piece of data, need to push to the network. */ | ||
990 | } | ||
991 | pre_send_setopt (connection, false, push_data); | ||
992 | |||
582 | #ifdef MHD_LINUX_SOLARIS_SENDFILE | 993 | #ifdef MHD_LINUX_SOLARIS_SENDFILE |
583 | #ifndef HAVE_SENDFILE64 | 994 | #ifndef HAVE_SENDFILE64 |
584 | offset = (off_t) offsetu64; | 995 | offset = (off_t) offsetu64; |
@@ -708,9 +1119,13 @@ MHD_send_sendfile_ (struct MHD_Connection *connection) | |||
708 | ret = (ssize_t) len; | 1119 | ret = (ssize_t) len; |
709 | #endif /* HAVE_FREEBSD_SENDFILE */ | 1120 | #endif /* HAVE_FREEBSD_SENDFILE */ |
710 | 1121 | ||
711 | /* Make sure we send the data without delay ONLY if we | 1122 | /* If there is a need to push the data from network buffers |
712 | provided the complete response (not on partial write) */ | 1123 | * call post_send_setopt(). */ |
713 | post_send_setopt (connection, false, (left == (uint64_t) ret)); | 1124 | /* It's unknown whether sendfile() will be used in the next |
1125 | * response so assume that next response will be the same. */ | ||
1126 | if ( (push_data) && | ||
1127 | (send_size == (size_t) ret) ) | ||
1128 | post_send_setopt (connection, false, push_data); | ||
714 | 1129 | ||
715 | return ret; | 1130 | return ret; |
716 | } | 1131 | } |