gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libmicrohttpd] 29/29: Focused all read-buffer grows in a single point,


From: gnunet
Subject: [libmicrohttpd] 29/29: Focused all read-buffer grows in a single point, related improvements.
Date: Tue, 20 Jun 2023 22:24:42 +0200

This is an automated email from the git hooks/post-receive script.

karlson2k pushed a commit to branch master
in repository libmicrohttpd.

commit f02888d4de4a9b24c1023112119a9a3e46fcb654
Author: Evgeny Grin (Karlson2k) <k2k@narod.ru>
AuthorDate: Mon Jun 19 18:47:57 2023 +0300

    Focused all read-buffer grows in a single point, related improvements.
    
    Improved handling of low memory in connection pool.
    Improved handling of read buffer growing.
    Removed impossible conditions combinations handling.
---
 src/microhttpd/connection.c | 313 +++++++++++++++++++++++++++++++++-----------
 1 file changed, 234 insertions(+), 79 deletions(-)

diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c
index d6c4ac1e..4d912ce8 100644
--- a/src/microhttpd/connection.c
+++ b/src/microhttpd/connection.c
@@ -56,6 +56,16 @@
 #include "mhd_send.h"
 #include "mhd_assert.h"
 
+/**
+ * The reasonable length of the upload chunk "header" (the size specifier
+ * with optional chunk extension).
+ * MHD tries to keep the space in the read buffer large enough to read
+ * the chunk "header" in one step.
+ * The real "header" could be much larger, it will be handled correctly
+ * anyway, however it may require several rounds of buffer grow.
+ */
+#define MHD_CHUNK_HEADER_REASONABLE_LEN 24
+
 /**
  * Message to transmit when http 1.1 request is received
  */
@@ -444,13 +454,13 @@
  * minimal.
  */
 #ifdef HAVE_MESSAGES
-#define INTERNAL_ERROR \
+#define ERROR_MSG_DATA_NOT_HANDLED_BY_APP \
   "<html><head><title>Internal server error</title></head>" \
   "<body>Please ask the developer of this Web server to carefully " \
   "read the GNU libmicrohttpd documentation about connection "\
   "management and blocking.</body></html>"
 #else
-#define INTERNAL_ERROR ""
+#define ERROR_MSG_DATA_NOT_HANDLED_BY_APP ""
 #endif
 
 /**
@@ -2819,6 +2829,182 @@ transmit_error_response_len (struct MHD_Connection 
*connection,
                                hd_n, hd_n_l, \
                                hd_v, hd_v_l)
 
+
+/**
+ * Check whether the read buffer has any upload body data ready to
+ * be processed.
+ * Must be called only when connection is in MHD_CONNECTION_BODY_RECEIVING
+ * state.
+ *
+ * @param c the connection to check
+ * @return 'true' if upload body data is already in the read buffer,
+ *         'false' if no upload data is received and not processed.
+ */
+static bool
+has_unprocessed_upload_body_data_in_buffer (struct MHD_Connection *c)
+{
+  mhd_assert (MHD_CONNECTION_BODY_RECEIVING == c->state);
+  if (! c->rq.have_chunked_upload)
+    return 0 != c->read_buffer_offset;
+
+  /* Chunked upload */
+  mhd_assert (0 != c->rq.remaining_upload_size); /* Must not be possible in 
MHD_CONNECTION_BODY_RECEIVING state */
+  if (c->rq.current_chunk_offset == c->rq.current_chunk_size)
+  {
+    /* 0 == c->rq.current_chunk_size: Waiting the chunk size (chunk header).
+       0 != c->rq.current_chunk_size: Waiting for chunk-closing CRLF. */
+    return false; /*  */
+  }
+  return 0 != c->read_buffer_offset; /* Chunk payload data in the read buffer 
*/
+}
+
+
+/**
+ * Check whether enough space is available in the read buffer for the next
+ * operation.
+ * Handles grow of the buffer if required and error conditions (when buffer
+ * grow is required but not possible).
+ * Must be called only when processing the event loop states and when
+ * reading is required for the next phase.
+ * @param c the connection to check
+ * @return true if connection handled successfully and enough buffer
+ *         is available,
+ *         false if not enough buffer is available and the loop's states
+ *         must be processed again as connection is in the error state.
+ */
+static bool
+check_and_grow_read_buffer_space (struct MHD_Connection *c)
+{
+  /**
+   * The increase of read buffer size is desirable.
+   */
+  bool rbuff_grow_desired;
+  /**
+   * The increase of read buffer size is a hard requirement.
+   */
+  bool rbuff_grow_required;
+
+  mhd_assert (0 != (MHD_EVENT_LOOP_INFO_READ & c->event_loop_info));
+  mhd_assert (! c->discard_request);
+
+  rbuff_grow_required = (c->read_buffer_offset == c->read_buffer_size);
+  if (rbuff_grow_required)
+    rbuff_grow_desired = true;
+  else
+  {
+    rbuff_grow_desired = (c->read_buffer_offset + c->daemon->pool_increment >
+                          c->read_buffer_size);
+
+    if ((rbuff_grow_desired) &&
+        (MHD_CONNECTION_BODY_RECEIVING == c->state))
+    {
+      if (! c->rq.have_chunked_upload)
+      {
+        mhd_assert (MHD_SIZE_UNKNOWN != c->rq.remaining_upload_size);
+        /* Do not grow read buffer more than necessary to process the current
+           request. */
+        rbuff_grow_desired =
+          (c->rq.remaining_upload_size > c->read_buffer_size);
+      }
+      else
+      {
+        mhd_assert (MHD_SIZE_UNKNOWN == c->rq.remaining_upload_size);
+        if (0 == c->rq.current_chunk_size)
+          rbuff_grow_desired =  /* Reading value of the next chunk size */
+                               (MHD_CHUNK_HEADER_REASONABLE_LEN >
+                                c->read_buffer_size);
+        else
+        {
+          const size_t cur_chunk_left =
+            c->rq.current_chunk_size - c->rq.current_chunk_offset;
+          /* Do not grow read buffer more than necessary to process the current
+             chunk with terminating CRLF. */
+          mhd_assert (c->rq.current_chunk_offset <= c->rq.current_chunk_size);
+          rbuff_grow_desired = ((cur_chunk_left + 2) > c->read_buffer_size);
+        }
+      }
+    }
+  }
+
+  if (! rbuff_grow_desired)
+    return true; /* No need to increase the buffer */
+
+  if (try_grow_read_buffer (c, rbuff_grow_required))
+    return true; /* Buffer increase succeed */
+
+  if (! rbuff_grow_required)
+    return true; /* Can continue without buffer increase */
+
+  /* Failed to increase the read buffer size, but need to read the data
+     from the network.
+     No more space in the buffer, no more space to increase the buffer. */
+
+  /* 'PROCESS_READ' event state flag must be set only if the last application
+     callback has processed some data. If any data is processed then some
+     space in the read buffer must be available. */
+  mhd_assert (0 == (MHD_EVENT_LOOP_INFO_PROCESS & c->event_loop_info));
+
+  if (MHD_CONNECTION_BODY_RECEIVING != c->state)
+  {
+    /* Receiving request line, request headers or request footers */
+    /* TODO: Improve detection of the conditions */
+    if (c->rq.url != NULL)
+      transmit_error_response_static (c,
+                                      MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE,
+                                      REQUEST_TOO_BIG);
+    else
+      transmit_error_response_static (c,
+                                      MHD_HTTP_URI_TOO_LONG,
+                                      REQUEST_TOO_BIG);
+    return false;
+  }
+
+  /* Receiving the request body and no space left in the buffer */
+
+  if (! has_unprocessed_upload_body_data_in_buffer (c))
+  {
+    /* Full header is received and no space left for reading
+       the request body.
+       For chunked upload encoding: chunk-extension is too long or
+       chunk size is encoded with excessive number of leading zeros. */
+    /* TODO: report proper cause for the error */
+    transmit_error_response_static (c,
+                                    MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE,
+                                    REQUEST_TOO_BIG);
+    return false;
+  }
+
+  /* No space left in the buffer but some upload body data can be processed
+     and some space could be freed. */
+  mhd_assert (! c->rq.some_payload_processed);
+  if (0 == (c->daemon->options & MHD_USE_INTERNAL_POLLING_THREAD))
+  {
+    /* The application is handling processing cycles.
+       The data may be processed later. */
+    c->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS;
+    return true;
+  }
+
+  /* Using internal thread for sockets polling */
+
+  /* failed to grow the read buffer, and the
+     client which is supposed to handle the
+     received data in a *blocking* fashion
+     (in this mode) did not handle the data as
+     it was supposed to!
+     => we would either have to do busy-waiting
+     (on the client, which would likely fail),
+     or if we do nothing, we would just timeout
+     on the connection (if a timeout is even
+     set!).
+     Solution: we kill the connection with an error */
+  transmit_error_response_static (c,
+                                  MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                  ERROR_MSG_DATA_NOT_HANDLED_BY_APP);
+  return false;
+}
+
+
 /**
  * Update the 'event_loop_info' field of this connection based on the state
  * that the connection is now in.  May also close the connection or
@@ -2875,31 +3061,15 @@ MHD_connection_update_event_loop_info (struct 
MHD_Connection *connection)
     {
     case MHD_CONNECTION_INIT:
     case MHD_CONNECTION_REQ_LINE_RECEIVING:
+      connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ;
+      break;
     case MHD_CONNECTION_REQ_LINE_RECEIVED:
+      mhd_assert (0);
+      break;
     case MHD_CONNECTION_REQ_HEADERS_RECEIVING:
-      /* while reading headers, we always grow the
-         read buffer if needed, no size-check required */
-      if ( (connection->read_buffer_offset == connection->read_buffer_size) &&
-           (! try_grow_read_buffer (connection, true)) )
-      {
-        if (connection->rq.url != NULL)
-          transmit_error_response_static (connection,
-                                          
MHD_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE,
-                                          REQUEST_TOO_BIG);
-        else
-          transmit_error_response_static (connection,
-                                          MHD_HTTP_URI_TOO_LONG,
-                                          REQUEST_TOO_BIG);
-        continue;
-      }
-      if (! connection->discard_request)
-        connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ;
-      else
-        connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS;
+      connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ;
       break;
     case MHD_CONNECTION_HEADERS_RECEIVED:
-      mhd_assert (0);
-      break;
     case MHD_CONNECTION_HEADERS_PROCESSED:
       mhd_assert (0);
       break;
@@ -2907,54 +3077,37 @@ MHD_connection_update_event_loop_info (struct 
MHD_Connection *connection)
       connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE;
       break;
     case MHD_CONNECTION_BODY_RECEIVING:
-      if (connection->read_buffer_offset == connection->read_buffer_size)
+      if ((connection->rq.some_payload_processed) &&
+          has_unprocessed_upload_body_data_in_buffer (connection))
       {
-        const bool internal_poll = (0 != (connection->daemon->options
-                                          & MHD_USE_INTERNAL_POLLING_THREAD));
-        if ( (! try_grow_read_buffer (connection, true)) &&
-             internal_poll)
+        /* Some data was processed, the buffer must have some free space */
+        mhd_assert (connection->read_buffer_offset < \
+                    connection->read_buffer_size);
+        if (! connection->rq.have_chunked_upload)
         {
-          /* failed to grow the read buffer, and the
-             client which is supposed to handle the
-             received data in a *blocking* fashion
-             (in this mode) did not handle the data as
-             it was supposed to!
-             => we would either have to do busy-waiting
-             (on the client, which would likely fail),
-             or if we do nothing, we would just timeout
-             on the connection (if a timeout is even
-             set!).
-             Solution: we kill the connection with an error */
-          transmit_error_response_static (connection,
-                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                          INTERNAL_ERROR);
-          continue;
+          /* Not a chunked upload. Do not read more than necessary to
+             process the current request. */
+          if (connection->rq.remaining_upload_size >=
+              connection->read_buffer_offset)
+            connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS;
+          else
+            connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS_READ;
+        }
+        else
+        {
+          /* Chunked upload. The size of the current request is unknown.
+             Continue reading as the space in the read buffer is available. */
+          connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS_READ;
         }
       }
-      if (connection->discard_request)
-        connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS;
-      else if (connection->read_buffer_offset == connection->read_buffer_size)
-        connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS;
-      else if (0 == connection->read_buffer_offset)
-        connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ;
-      else if (connection->rq.some_payload_processed)
-        connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS_READ;
       else
         connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ;
       break;
     case MHD_CONNECTION_BODY_RECEIVED:
+      mhd_assert (0);
+      break;
     case MHD_CONNECTION_FOOTERS_RECEIVING:
-      /* while reading footers, we always grow the
-         read buffer if needed, no size-check required */
-      if (connection->read_closed)
-      {
-        CONNECTION_CLOSE_ERROR (connection,
-                                NULL);
-        continue;
-      }
       connection->event_loop_info = MHD_EVENT_LOOP_INFO_READ;
-      /* transition to FOOTERS_RECEIVED
-         happens in read handler */
       break;
     case MHD_CONNECTION_FOOTERS_RECEIVED:
       mhd_assert (0);
@@ -2972,18 +3125,18 @@ MHD_connection_update_event_loop_info (struct 
MHD_Connection *connection)
     case MHD_CONNECTION_HEADERS_SENT:
       mhd_assert (0);
       break;
-    case MHD_CONNECTION_NORMAL_BODY_READY:
-      connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE;
-      break;
     case MHD_CONNECTION_NORMAL_BODY_UNREADY:
       connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS;
       break;
-    case MHD_CONNECTION_CHUNKED_BODY_READY:
+    case MHD_CONNECTION_NORMAL_BODY_READY:
       connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE;
       break;
     case MHD_CONNECTION_CHUNKED_BODY_UNREADY:
       connection->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS;
       break;
+    case MHD_CONNECTION_CHUNKED_BODY_READY:
+      connection->event_loop_info = MHD_EVENT_LOOP_INFO_WRITE;
+      break;
     case MHD_CONNECTION_CHUNKED_BODY_SENT:
       mhd_assert (0);
       break;
@@ -3004,7 +3157,17 @@ MHD_connection_update_event_loop_info (struct 
MHD_Connection *connection)
     default:
       mhd_assert (0);
     }
-    break;
+
+    if (0 != (MHD_EVENT_LOOP_INFO_READ & connection->event_loop_info))
+    {
+      /* Check whether the space is available to receive data */
+      if (! check_and_grow_read_buffer_space (connection))
+      {
+        mhd_assert (connection->discard_request);
+        continue;
+      }
+    }
+    break; /* Everything was processed. */
   }
 }
 
@@ -3555,8 +3718,6 @@ process_request_body (struct MHD_Connection *connection)
     size_t left_unprocessed;
     size_t processed_size;
 
-    connection->rq.some_payload_processed = false;
-
     instant_retry = false;
     if (connection->rq.have_chunked_upload)
     {
@@ -3756,9 +3917,9 @@ process_request_body (struct MHD_Connection *connection)
     if (left_unprocessed > to_be_processed)
       MHD_PANIC (_ ("libmicrohttpd API violation.\n"));
 
-    if (left_unprocessed != to_be_processed)
-      /* Something was processed by the application. */
-      connection->rq.some_payload_processed = true;
+    connection->rq.some_payload_processed =
+      (left_unprocessed != to_be_processed);
+
     if (0 != left_unprocessed)
     {
       instant_retry = false; /* client did not process everything */
@@ -5461,16 +5622,10 @@ MHD_connection_handle_read (struct MHD_Connection 
*connection,
   }
 #endif /* HTTPS_SUPPORT */
 
-  /* make sure "read" has a reasonable number of bytes
-     in buffer to use per system call (if possible) */
-  if (connection->read_buffer_offset + connection->daemon->pool_increment >
-      connection->read_buffer_size)
-    try_grow_read_buffer (connection,
-                          (connection->read_buffer_size ==
-                           connection->read_buffer_offset));
-
+  mhd_assert (NULL != connection->read_buffer);
   if (connection->read_buffer_size == connection->read_buffer_offset)
     return; /* No space for receiving data. */
+
   bytes_read = connection->recv_cls (connection,
                                      &connection->read_buffer
                                      [connection->read_buffer_offset],

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]