@@ -327,8 +327,8 @@ size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint
327327
328328/* 
329329 * Abstract Response 
330-  * */  
331- 
330+  *  
331+   */ 
332332AsyncAbstractResponse::AsyncAbstractResponse (AwsTemplateProcessor callback) : _callback(callback) {
333333  //  In case of template processing, we're unable to determine real response size
334334  if  (callback) {
@@ -340,7 +340,7 @@ AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback) : _c
340340
341341void  AsyncAbstractResponse::_respond (AsyncWebServerRequest *request) {
342342  addHeader (T_Connection, T_close, false );
343-   _assembleHead (_head , request->version ());
343+   _assembleHead (_assembled_headers , request->version ());
344344  _state = RESPONSE_HEADERS;
345345  _ack (request, 0 , 0 );
346346}
@@ -364,127 +364,139 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
364364    async_ws_log_d (" (chunk) out of in-flight credits"  );
365365  }
366366
367-   _in_flight -= (_in_flight > len) ? len : _in_flight;
368-   //  get the size of available sock space
367+   _in_flight -= std::min (len, _in_flight);
368+   //  for response data we need to control the queue and in-flight fragmentation. Sending small chunks could give low latency,
369+   //  but flood asynctcp's queue and fragment socket buffer space for large responses.
370+   //  Let's ignore polled acks and acks in case when we have more in-flight data then the available socket buff space.
371+   //  That way we could balance on having half the buffer in-flight while another half is filling up, while minimizing events in asynctcp q
372+   if  (_in_flight > request->client ()->space ()) {
373+     //  async_ws_log_d("defer user call %u/%u", _in_flight, space);
374+     //  take the credit back since we are ignoring this ack and rely on other inflight data
375+     if  (len) {
376+       --_in_flight_credit;
377+     }
378+     return  0 ;
379+   }
369380#endif 
370381
382+   //  this is not functionally needed in AsyncAbstractResponse itself, but keept for compatibility if some of the derived classes are rely on it somehow
371383  _ackedLength += len;
372-   size_t  space = request->client ()->space ();
373384
374-   size_t  headLen = _head. length (); 
385+   //  send headers 
375386  if  (_state == RESPONSE_HEADERS) {
376-     if  (space >= headLen) {
377-       _state = RESPONSE_CONTENT;
378-       space -= headLen;
379-     } else  {
380-       String out = _head.substring (0 , space);
381-       _head = _head.substring (space);
382-       _writtenLength += request->client ()->write (out.c_str (), out.length ());
383- #if  ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
384-       _in_flight += out.length ();
387+     //  copy headers buffer to sock buffer
388+     size_t  const  added = request->client ()->add (_assembled_headers.c_str () + _assembled_headers_sent, _assembled_headers.length () - _assembled_headers_sent);
389+     _writtenLength += added;
390+     if  (added < _assembled_headers.length ()){
391+       //  we were not able to fit all headers in current buff, send this part here and return later for the rest
392+       _assembled_headers_sent += added;
393+       #if  ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
394+       _in_flight += added;
385395      --_in_flight_credit;  //  take a credit
386- #endif 
387-       return  out.length ();
396+       #endif 
397+       if  (!request->client ()->send ()){
398+         //  something is wrong
399+         request->client ()->close ();
400+       }
401+       return  added;
388402    }
403+     //  otherwise we've added all the (ramainder) headers in current buff
404+     _state = RESPONSE_CONTENT;
405+     _assembled_headers = String ();  //  clear
389406  }
390407
391-   if  (_state == RESPONSE_CONTENT) {
408+   //  if there are leftovers in buffer from the previous run, let's deal with it first
409+   if  (_state == RESPONSE_CONTENT && _send_buffer.size ()){
410+     size_t  const  written = request->client ()->add (reinterpret_cast <char *>(_send_buffer.data ()), _send_buffer.size ());
411+     if  (written != _send_buffer.size ()){
412+       //  we were not able to fit entire buffer again?! OK, let's send partial now and come back later
413+       _writtenLength += written;
392414#if  ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
393-     //  for response data we need to control the queue and in-flight fragmentation. Sending small chunks could give low latency,
394-     //  but flood asynctcp's queue and fragment socket buffer space for large responses.
395-     //  Let's ignore polled acks and acks in case when we have more in-flight data then the available socket buff space.
396-     //  That way we could balance on having half the buffer in-flight while another half is filling up, while minimizing events in asynctcp q
397-     if  (_in_flight > space) {
398-       //  async_ws_log_d("defer user call %u/%u", _in_flight, space);
399-       //   take the credit back since we are ignoring this ack and rely on other inflight data
400-       if  (len) {
401-         --_in_flight_credit;
402-       }
403-       return  0 ;
404-     }
415+       _in_flight += written;
416+       --_in_flight_credit;  //  take a credit
405417#endif 
418+       request->client ()->send ();
419+       _send_buffer.erase (_send_buffer.begin (), _send_buffer.begin () + written);
420+       return  written;
421+     } else 
422+       _send_buffer.clear ();
423+     //  OK buffer depleted, we can go on for more data within same sockbuff
424+   }
406425
407-     size_t  outLen;
408-     if  (_chunked) {
426+   //  send content body
427+   if  (_state == RESPONSE_CONTENT) {
428+     size_t  const  space = request->client ()->space ();
429+     if  (_chunked || !_sendContentLength) {
409430      if  (space <= 8 ) {
410431        return  0 ;
411432      }
412- 
413-       outLen = space;
414-     } else  if  (!_sendContentLength) {
415-       outLen = space;
433+       _send_buffer.resize (space);
416434    } else  {
417-       outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength);
418-     }
419- 
420-     uint8_t  *buf = (uint8_t  *)malloc (outLen + headLen);
421-     if  (!buf) {
422-       async_ws_log_e (" Failed to allocate"  );
423-       request->abort ();
424-       return  0 ;
425-     }
426- 
427-     if  (headLen) {
428-       memcpy (buf, _head.c_str (), _head.length ());
435+       _send_buffer.resize (std::min (space, _contentLength - _sentLength));
429436    }
430437
431-     size_t  readLen = 0 ;
432- 
433438    if  (_chunked) {
434439      //  HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added.
435-       //  See RFC2616 sections 2, 3.6.1. 
436-       readLen = _fillBufferAndProcessTemplates (buf  + headLen +  6 , outLen  - 8 );
440+       //  See RFC2616 sections 2, 3.6.1 https://datatracker.ietf.org/doc/html/rfc2616#section-3.6.1 
441+       size_t   const   readLen = _fillBufferAndProcessTemplates (_send_buffer. data ()  + 6 , _send_buffer. size ()  - 8 );   //  reserve 8 bytes for chunk size data 
437442      if  (readLen == RESPONSE_TRY_AGAIN) {
438-         free (buf);
439-         return  0 ;
443+         _send_buffer.clear (); //  we won't send anything, do not release mem but leave it for later
444+       } else  {
445+         sprintf (reinterpret_cast <char *>(_send_buffer.data ()), " %04x\r\n "  , readLen);   //  print chunk size in buffer
446+         _send_buffer.at (readLen + 6 ) = ' \r '  ;
447+         _send_buffer.at (readLen + 7 ) = ' \n '  ;
448+         _send_buffer.resize (readLen + 8 );     //  set internal vector's size to match added data
449+         if  (!readLen){
450+           //  last chunk?
451+           _state = RESPONSE_WAIT_ACK;
452+         }
453+         _sentLength += readLen;               //  not sure if that is needed for chunked data?
440454      }
441-       outLen = sprintf ((char  *)buf + headLen, " %04x"  , readLen) + headLen;
442-       buf[outLen++] = ' \r '  ;
443-       buf[outLen++] = ' \n '  ;
444-       outLen += readLen;
445-       buf[outLen++] = ' \r '  ;
446-       buf[outLen++] = ' \n '  ;
447455    } else  {
448-       readLen = _fillBufferAndProcessTemplates (buf + headLen, outLen );
456+       size_t   const   readLen = _fillBufferAndProcessTemplates (_send_buffer. data (), _send_buffer. size () );
449457      if  (readLen == RESPONSE_TRY_AGAIN) {
450-         free (buf);
451-         return  0 ;
458+         _send_buffer.clear (); //  won't release mem but leave it for later
459+       } else  if  (readLen == 0 ){
460+         //  no more data to send
461+         _state = RESPONSE_WAIT_ACK;
462+         _send_buffer.clear ();
463+       } else  {
464+         _send_buffer.resize (readLen);       //  set internal vector's size to match added data
465+         _sentLength += readLen;
466+         if  (_sendContentLength && _sentLength == _contentLength){
467+           //  it was last piece of content
468+           _state = RESPONSE_WAIT_ACK;
469+         }
452470      }
453-       outLen = readLen + headLen;
454-     }
455- 
456-     if  (headLen) {
457-       _head = emptyString;
458471    }
459472
460-     if  (outLen) {
461-       _writtenLength += request->client ()->write ((const  char  *)buf, outLen);
473+     size_t  written{0 };
474+     if  (_send_buffer.size ()){
475+       written = request->client ()->add (reinterpret_cast <char *>(_send_buffer.data ()), _send_buffer.size ());
476+       if  (written != _send_buffer.size ()){
477+         //  we were not able to send entire buffer now somehow, leave it for later
478+         //  (this should not happen normally unless connection's TCP window suddenly changed or some other thread highjacked our sock buffer :) )
479+         _send_buffer.erase (_send_buffer.begin (), _send_buffer.begin () + written);
480+       } else 
481+         _send_buffer.clear ();
482+ 
483+       _writtenLength += written;
462484#if  ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
463-       _in_flight += outLen ;
485+       _in_flight += written ;
464486      --_in_flight_credit;  //  take a credit
465487#endif 
466488    }
467- 
468-     if  (_chunked) {
469-       _sentLength += readLen;
470-     } else  {
471-       _sentLength += outLen - headLen;
472-     }
473- 
474-     free (buf);
475- 
476-     if  ((_chunked && readLen == 0 ) || (!_sendContentLength && outLen == 0 ) || (!_chunked && _sentLength == _contentLength)) {
477-       _state = RESPONSE_WAIT_ACK;
478-     }
479-     return  outLen;
480- 
481-   } else  if  (_state == RESPONSE_WAIT_ACK) {
482-     if  (!_sendContentLength || _ackedLength >= _writtenLength) {
483-       _state = RESPONSE_END;
484-       if  (!_chunked && !_sendContentLength) {
485-         request->client ()->close (true );
486-       }
487-     }
489+     //  wether or not we have a new content in buffer now we must send anyway whatever is in sockbuff (maybe empty body)
490+     //  might be other data in sockbuff with hdrs or previous buffer data
491+     request->client ()->send ();
492+     return  written;
493+   }
494+   
495+   if  (_state == RESPONSE_WAIT_ACK) {
496+     //  we do not need to wait for any acks actually if we won't send any more data,
497+     //  connection would be closed gracefully with last piece of data
498+     _state = RESPONSE_END;
499+     request->client ()->close ();
488500  }
489501  return  0 ;
490502}
@@ -512,8 +524,8 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t *data, size
512524  //  Now we've read 'len' bytes, either from cache or from file
513525  //  Search for template placeholders
514526  uint8_t  *pTemplateStart = data;
515-   while  ((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t  *)memchr (pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1 ] - pTemplateStart + 1 ))
516-   ) {   //  data[0] ... data[len - 1]
527+   while  ((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t  *)memchr (pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1 ] - pTemplateStart + 1 )) ) { 
528+     //  data[0] ... data[len - 1]
517529    uint8_t  *pTemplateEnd =
518530      (pTemplateStart < &data[len - 1 ]) ? (uint8_t  *)memchr (pTemplateStart + 1 , TEMPLATE_PLACEHOLDER, &data[len - 1 ] - pTemplateStart) : nullptr ;
519531    //  temporary buffer to hold parameter name
0 commit comments