]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
lib: client writer tweaks
authorStefan Eissing <stefan@eissing.org>
Thu, 16 Jul 2026 09:26:33 +0000 (11:26 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 20 Jul 2026 22:07:03 +0000 (00:07 +0200)
- keep pause state at request
- keep presense of content decoding writers at request
- only add cw-pause writer when content decoding happens
- define macro for passing data down the writer chain

scorecard: add option to test with h2o server

Closes #22335

lib/cw-out.c
lib/cw-out.h
lib/cw-pause.c
lib/request.h
lib/sendf.c
lib/sendf.h
tests/http/scorecard.py
tests/http/testenv/__init__.py

index b38fde455fd4c16414be94b2f54028e357e4b0d2..35ded4b475d5f1622cf68dff42ef9ace2674052a 100644 (file)
@@ -97,7 +97,6 @@ static void cw_out_buf_free(struct cw_out_buf *cwbuf)
 struct cw_out_ctx {
   struct Curl_cwriter super;
   struct cw_out_buf *buf;
-  BIT(paused);
   BIT(errored);
 };
 
@@ -171,8 +170,7 @@ static void cw_get_writefunc(struct Curl_easy *data, cw_out_type otype,
   }
 }
 
-static CURLcode cw_out_cb_write(struct cw_out_ctx *ctx,
-                                struct Curl_easy *data,
+static CURLcode cw_out_cb_write(struct Curl_easy *data,
                                 curl_write_callback wcb,
                                 void *wcb_data,
                                 cw_out_type otype,
@@ -200,7 +198,7 @@ static CURLcode cw_out_cb_write(struct cw_out_ctx *ctx,
       failf(data, "Write callback asked for PAUSE when not supported");
       return CURLE_WRITE_ERROR;
     }
-    ctx->paused = TRUE;
+    data->req.writer.paused = TRUE;
     CURL_TRC_WRITE(data, "[OUT] PAUSE requested by client");
     result = Curl_xfer_pause_recv(data, TRUE);
     return result ? result : CURLE_AGAIN;
@@ -245,18 +243,18 @@ static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx,
   *pconsumed = 0;
   if(otype == CW_OUT_BODY_0LEN) {
     DEBUGASSERT(!blen);
-    return cw_out_cb_write(ctx, data, wcb, wcb_data, otype,
+    return cw_out_cb_write(data, wcb, wcb_data, otype,
                            buf, blen, &nwritten);
   }
   else {
-    while(blen && !ctx->paused) {
+    while(blen && !data->req.writer.paused) {
       if(!flush_all && blen < min_write)
         break;
       wlen = max_write ? CURLMIN(blen, max_write) : blen;
       if(otype == CW_OUT_BODY)
         result = Curl_pgrs_deliver_check(data, wlen);
       if(!result)
-        result = cw_out_cb_write(ctx, data, wcb, wcb_data, otype,
+        result = cw_out_cb_write(data, wcb, wcb_data, otype,
                                  buf, wlen, &nwritten);
       if(result)
         return result;
@@ -314,7 +312,7 @@ static CURLcode cw_out_flush_chain(struct cw_out_ctx *ctx,
 
   if(!cwbuf)
     return CURLE_OK;
-  if(ctx->paused)
+  if(data->req.writer.paused)
     return CURLE_OK;
 
   /* write the end of the chain until it blocks or gets empty */
@@ -327,7 +325,7 @@ static CURLcode cw_out_flush_chain(struct cw_out_ctx *ctx,
       return result;
     if(*plast) {
       /* could not write last, paused again? */
-      DEBUGASSERT(ctx->paused);
+      DEBUGASSERT(data->req.writer.paused);
       return CURLE_OK;
     }
   }
@@ -457,7 +455,7 @@ static CURLcode cw_out_do_flush(struct Curl_easy *data,
 
   if(ctx->errored)
     return CURLE_WRITE_ERROR;
-  if(ctx->paused)
+  if(data->req.writer.paused)
     return CURLE_OK;  /* not doing it */
 
   result = cw_out_flush_chain(ctx, data, &ctx->buf, flush_all);
@@ -486,33 +484,6 @@ const struct Curl_cwtype Curl_cwt_out = {
   sizeof(struct cw_out_ctx)
 };
 
-bool Curl_cw_out_is_paused(struct Curl_easy *data)
-{
-  struct Curl_cwriter *cw_out;
-  struct cw_out_ctx *ctx;
-
-  cw_out = Curl_cwriter_get_by_type(data, &Curl_cwt_out);
-  if(!cw_out)
-    return FALSE;
-
-  ctx = (struct cw_out_ctx *)cw_out;
-  return (bool)ctx->paused;
-}
-
-CURLcode Curl_cw_out_unpause(struct Curl_easy *data)
-{
-  struct Curl_cwriter *cw_out;
-  CURLcode result = CURLE_OK;
-
-  cw_out = Curl_cwriter_get_by_type(data, &Curl_cwt_out);
-  if(cw_out) {
-    struct cw_out_ctx *ctx = (struct cw_out_ctx *)cw_out;
-    CURL_TRC_WRITE(data, "[OUT] unpause");
-    ctx->paused = FALSE;
-  }
-  return result;
-}
-
 CURLcode Curl_cw_out_done(struct Curl_easy *data)
 {
   struct Curl_cwriter *cw_out;
index 7de6524bc5801906ed22503ca61eb962f9131fd0..ee2b4b6307bc2b1b21c7454c88538ded1232da76 100644 (file)
@@ -34,16 +34,6 @@ struct Curl_easy;
  */
 extern const struct Curl_cwtype Curl_cwt_out;
 
-/**
- * Return TRUE iff 'cw-out' client write has paused data.
- */
-bool Curl_cw_out_is_paused(struct Curl_easy *data);
-
-/**
- * Flush any buffered date to the client, chunk collation still applies.
- */
-CURLcode Curl_cw_out_unpause(struct Curl_easy *data);
-
 /**
  * Mark EndOfStream reached and flush ALL data to the client.
  */
index a42f61f4baf9f562c4717f9e29a327816c12622c..ccdc7657b187c13b93cb0c1581d4beaba8cf733c 100644 (file)
@@ -100,7 +100,6 @@ static CURLcode cw_pause_flush(struct Curl_easy *data,
                                struct Curl_cwriter *cw_pause)
 {
   struct cw_pause_ctx *ctx = (struct cw_pause_ctx *)cw_pause;
-  bool decoding = Curl_cwriter_is_content_decoding(data);
   CURLcode result = CURLE_OK;
 
   /* write the end of the chain until it blocks or gets empty */
@@ -112,7 +111,7 @@ static CURLcode cw_pause_flush(struct Curl_easy *data,
     while((*plast)->next) /* got to last in list */
       plast = &(*plast)->next;
     if(Curl_bufq_peek(&(*plast)->b, &buf, &blen)) {
-      wlen = (decoding && ((*plast)->type & CLIENTWRITE_BODY)) ?
+      wlen = ((*plast)->type & CLIENTWRITE_BODY) ?
              CURLMIN(blen, CW_PAUSE_DEC_WRITE_CHUNK) : blen;
       result = Curl_cwriter_write(data, cw_pause->next, (*plast)->type,
                                   (const char *)buf, wlen);
@@ -151,7 +150,6 @@ static CURLcode cw_pause_write(struct Curl_easy *data,
   struct cw_pause_ctx *ctx = writer->ctx;
   CURLcode result = CURLE_OK;
   size_t wlen = 0;
-  bool decoding = Curl_cwriter_is_content_decoding(data);
 
   if(ctx->buf && !Curl_cwriter_is_paused(data)) {
     result = cw_pause_flush(data, writer);
@@ -164,7 +162,7 @@ static CURLcode cw_pause_write(struct Curl_easy *data,
     DEBUGASSERT(!ctx->buf);
     /* content decoding might blow up size considerably, write smaller
      * chunks to make pausing need buffer less. */
-    wlen = (decoding && (type & CLIENTWRITE_BODY)) ?
+    wlen = (type & CLIENTWRITE_BODY) ?
            CURLMIN(blen, CW_PAUSE_DEC_WRITE_CHUNK) : blen;
     if(wlen < blen)
       wtype &= ~CLIENTWRITE_EOS;
index e67865a98433562ffa9d1656d4bce04679c4194a..6662cf19e7e0e9383b249703ea8cc71c9fc6994d 100644 (file)
@@ -101,10 +101,16 @@ struct SingleRequest {
 
   /* Client Writer stack, handles transfer- and content-encodings, protocol
    * checks, pausing by client callbacks. */
-  struct Curl_cwriter *writer_stack;
+  struct {
+    struct Curl_cwriter *stack;
+    BIT(paused);
+  } writer;
   /* Client Reader stack, handles transfer- and content-encodings, protocol
    * checks, pausing by client callbacks. */
-  struct Curl_creader *reader_stack;
+  struct {
+    struct Curl_creader *stack;
+    BIT(paused);
+  } reader;
   struct bufq sendbuf; /* data which needs to be send to the server */
   size_t sendbuf_hds_len; /* amount of header bytes in sendbuf */
   time_t timeofdoc;
index efaba08eb4cad5c416af2703d476866f5b1610a2..8f51a772a228ec7fbb8a894b91f909b928df371b 100644 (file)
 
 static void cl_reset_writer(struct Curl_easy *data)
 {
-  struct Curl_cwriter *writer = data->req.writer_stack;
+  struct Curl_cwriter *writer = data->req.writer.stack;
   while(writer) {
-    data->req.writer_stack = writer->next;
+    data->req.writer.stack = writer->next;
     writer->cwt->do_close(data, writer);
     curlx_free(writer);
-    writer = data->req.writer_stack;
+    writer = data->req.writer.stack;
   }
 }
 
 static void cl_reset_reader(struct Curl_easy *data)
 {
-  struct Curl_creader *reader = data->req.reader_stack;
+  struct Curl_creader *reader = data->req.reader.stack;
   data->req.reader_started = FALSE;
   while(reader) {
-    data->req.reader_stack = reader->next;
+    data->req.reader.stack = reader->next;
     reader->crt->do_close(data, reader);
     curlx_free(reader);
-    reader = data->req.reader_stack;
+    reader = data->req.reader.stack;
   }
 }
 
@@ -95,7 +95,7 @@ void Curl_client_reset(struct Curl_easy *data)
 CURLcode Curl_client_start(struct Curl_easy *data)
 {
   if(data->req.rewind_read) {
-    struct Curl_creader *r = data->req.reader_stack;
+    struct Curl_creader *r = data->req.reader.stack;
     CURLcode result = CURLE_OK;
 
     CURL_TRC_READ(data, "client start, rewind readers");
@@ -124,25 +124,6 @@ void Curl_creader_set_rewind(struct Curl_easy *data, bool enable)
   data->req.rewind_read = !!enable;
 }
 
-/* Write data using an unencoding writer stack. */
-CURLcode Curl_cwriter_write(struct Curl_easy *data,
-                            struct Curl_cwriter *writer, int type,
-                            const char *buf, size_t nbytes)
-{
-  if(!writer)
-    return CURLE_WRITE_ERROR;
-  return writer->cwt->do_write(data, writer, type, buf, nbytes);
-}
-
-CURLcode Curl_cwriter_flush(struct Curl_easy *data,
-                            struct Curl_cwriter *writer)
-{
-  if(!writer) {
-    return CURLE_WRITE_ERROR;
-  }
-  return writer->cwt->do_flush(data, writer);
-}
-
 CURLcode Curl_cwriter_def_init(struct Curl_easy *data,
                                struct Curl_cwriter *writer)
 {
@@ -340,47 +321,39 @@ static const struct Curl_cwtype cw_raw = {
   sizeof(struct Curl_cwriter)
 };
 
+static void cwriter_add(struct Curl_easy *data,
+                        struct Curl_cwriter *writer)
+{
+  struct Curl_cwriter **anchor = &data->req.writer.stack;
+
+  /* Insert the writer as first in its phase.
+   * Skip existing writers of lower phases. */
+  while(*anchor && (*anchor)->phase < writer->phase)
+    anchor = &((*anchor)->next);
+  writer->next = *anchor;
+  *anchor = writer;
+}
+
 static CURLcode do_init_writer_stack(struct Curl_easy *data)
 {
   struct Curl_cwriter *writer;
   CURLcode result;
 
-  DEBUGASSERT(!data->req.writer_stack);
-  result = Curl_cwriter_create(&data->req.writer_stack,
+  DEBUGASSERT(!data->req.writer.stack);
+  result = Curl_cwriter_create(&data->req.writer.stack,
                                data, &Curl_cwt_out, CURL_CW_CLIENT);
   if(result)
     return result;
 
-  /* This places the "pause" writer behind the "download" writer that
-   * is added below. Meaning the "download" can do checks on content length
-   * and other things *before* write outs are buffered for paused transfers. */
-  result = Curl_cwriter_create(&writer, data, &Curl_cwt_pause,
-                               CURL_CW_PROTOCOL);
-  if(!result) {
-    result = Curl_cwriter_add(data, writer);
-    if(result)
-      Curl_cwriter_free(data, writer);
-  }
-  if(result)
-    return result;
-
   result = Curl_cwriter_create(&writer, data, &cw_download, CURL_CW_PROTOCOL);
-  if(!result) {
-    result = Curl_cwriter_add(data, writer);
-    if(result)
-      Curl_cwriter_free(data, writer);
-  }
   if(result)
     return result;
+  cwriter_add(data, writer);
 
   result = Curl_cwriter_create(&writer, data, &cw_raw, CURL_CW_RAW);
-  if(!result) {
-    result = Curl_cwriter_add(data, writer);
-    if(result)
-      Curl_cwriter_free(data, writer);
-  }
   if(result)
     return result;
+  cwriter_add(data, writer);
 
   return result;
 }
@@ -405,14 +378,14 @@ CURLcode Curl_client_write(struct Curl_easy *data, int type, const char *buf,
   DEBUGASSERT(!(type & CLIENTWRITE_INFO) ||
               ((type & ~(CLIENTWRITE_INFO | CLIENTWRITE_EOS)) == 0));
 
-  if(!data->req.writer_stack) {
+  if(!data->req.writer.stack) {
     result = do_init_writer_stack(data);
     if(result)
       return result;
-    DEBUGASSERT(data->req.writer_stack);
+    DEBUGASSERT(data->req.writer.stack);
   }
 
-  result = Curl_cwriter_write(data, data->req.writer_stack, type, buf, len);
+  result = Curl_cwriter_write(data, data->req.writer.stack, type, buf, len);
   CURL_TRC_WRITE(data, "client_write(type=%x, len=%zu) -> %d",
                  (unsigned int)type, len, (int)result);
   return result;
@@ -422,14 +395,14 @@ CURLcode Curl_client_flush(struct Curl_easy *data)
 {
   CURLcode result;
 
-  if(!data->req.writer_stack) {
+  if(!data->req.writer.stack) {
     result = do_init_writer_stack(data);
     if(result)
       return result;
-    DEBUGASSERT(data->req.writer_stack);
+    DEBUGASSERT(data->req.writer.stack);
   }
 
-  result = Curl_cwriter_flush(data, data->req.writer_stack);
+  result = Curl_cwriter_flush(data, data->req.writer.stack);
   CURL_TRC_WRITE(data, "client_flush() -> %d", (int)result);
   return result;
 }
@@ -476,18 +449,33 @@ size_t Curl_cwriter_count(struct Curl_easy *data, Curl_cwriter_phase phase)
   struct Curl_cwriter *w;
   size_t n = 0;
 
-  for(w = data->req.writer_stack; w; w = w->next) {
+  for(w = data->req.writer.stack; w; w = w->next) {
     if(w->phase == phase)
       ++n;
   }
   return n;
 }
 
+static CURLcode cwriter_ensure_pause_writer(struct Curl_easy *data)
+{
+  struct Curl_cwriter *writer =
+    Curl_cwriter_get_by_type(data, &Curl_cwt_pause);
+  CURLcode result = CURLE_OK;
+
+  if(!writer) {
+    result = Curl_cwriter_create(&writer, data, &Curl_cwt_pause,
+                                 CURL_CW_BEFORE_DECODE);
+    if(!result)
+      cwriter_add(data, writer);
+  }
+  return result;
+}
+
 CURLcode Curl_cwriter_add(struct Curl_easy *data,
                           struct Curl_cwriter *writer)
 {
   CURLcode result;
-  struct Curl_cwriter **anchor = &data->req.writer_stack;
+  struct Curl_cwriter **anchor = &data->req.writer.stack;
 
   if(!*anchor) {
     result = do_init_writer_stack(data);
@@ -495,12 +483,16 @@ CURLcode Curl_cwriter_add(struct Curl_easy *data,
       return result;
   }
 
-  /* Insert the writer as first in its phase.
-   * Skip existing writers of lower phases. */
-  while(*anchor && (*anchor)->phase < writer->phase)
-    anchor = &((*anchor)->next);
-  writer->next = *anchor;
-  *anchor = writer;
+  if(writer->phase == CURL_CW_CONTENT_DECODE) {
+    /* On adding a content decoder, add the pause writer. Do this
+     * BEFORE the given writer as any failure will make the
+     * caller destroy the writer again. */
+    result = cwriter_ensure_pause_writer(data);
+    if(result)
+      return result;
+  }
+
+  cwriter_add(data, writer);
   return CURLE_OK;
 }
 
@@ -508,7 +500,7 @@ struct Curl_cwriter *Curl_cwriter_get_by_name(struct Curl_easy *data,
                                               const char *name)
 {
   struct Curl_cwriter *writer;
-  for(writer = data->req.writer_stack; writer; writer = writer->next) {
+  for(writer = data->req.writer.stack; writer; writer = writer->next) {
     if(!strcmp(name, writer->cwt->name))
       return writer;
   }
@@ -519,34 +511,17 @@ struct Curl_cwriter *Curl_cwriter_get_by_type(struct Curl_easy *data,
                                               const struct Curl_cwtype *cwt)
 {
   struct Curl_cwriter *writer;
-  for(writer = data->req.writer_stack; writer; writer = writer->next) {
+  for(writer = data->req.writer.stack; writer; writer = writer->next) {
     if(writer->cwt == cwt)
       return writer;
   }
   return NULL;
 }
 
-bool Curl_cwriter_is_content_decoding(struct Curl_easy *data)
-{
-  struct Curl_cwriter *writer;
-  for(writer = data->req.writer_stack; writer; writer = writer->next) {
-    if(writer->phase == CURL_CW_CONTENT_DECODE)
-      return TRUE;
-  }
-  return FALSE;
-}
-
-bool Curl_cwriter_is_paused(struct Curl_easy *data)
-{
-  return Curl_cw_out_is_paused(data);
-}
-
 CURLcode Curl_cwriter_unpause(struct Curl_easy *data)
 {
-  CURLcode result = Curl_cw_out_unpause(data);
-  if(!result)
-    result = Curl_cwriter_flush(data, data->req.writer_stack);
-  return result;
+  data->req.writer.paused = FALSE;
+  return Curl_cwriter_flush(data, data->req.writer.stack);
 }
 
 CURLcode Curl_creader_read(struct Curl_easy *data,
@@ -1141,9 +1116,9 @@ static CURLcode do_init_reader_stack(struct Curl_easy *data,
   DEBUGASSERT(r);
   DEBUGASSERT(r->crt);
   DEBUGASSERT(r->phase == CURL_CR_CLIENT);
-  DEBUGASSERT(!data->req.reader_stack);
+  DEBUGASSERT(!data->req.reader.stack);
 
-  data->req.reader_stack = r;
+  data->req.reader.stack = r;
   clen = r->crt->total_length(data, r);
   /* if we do not have 0 length init, and crlf conversion is wanted,
    * add the reader for it */
@@ -1184,7 +1159,7 @@ CURLcode Curl_creader_add(struct Curl_easy *data,
                           struct Curl_creader *reader)
 {
   CURLcode result;
-  struct Curl_creader **anchor = &data->req.reader_stack;
+  struct Curl_creader **anchor = &data->req.reader.stack;
 
   if(!*anchor) {
     result = Curl_creader_set_fread(data, data->state.infilesize);
@@ -1227,11 +1202,11 @@ CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen,
   DEBUGASSERT(eos);
   *nread = 0;
 
-  if(!data->req.reader_stack) {
+  if(!data->req.reader.stack) {
     result = Curl_creader_set_fread(data, data->state.infilesize);
     if(result)
       return result;
-    DEBUGASSERT(data->req.reader_stack);
+    DEBUGASSERT(data->req.reader.stack);
   }
   if(!data->req.reader_started) {
     Curl_rlimit_start(&data->progress.ul.rlimit, Curl_pgrs_now(data), -1);
@@ -1248,7 +1223,7 @@ CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen,
     if(ul_avail < (curl_off_t)blen)
       blen = (size_t)ul_avail;
   }
-  result = Curl_creader_read(data, data->req.reader_stack, buf, blen,
+  result = Curl_creader_read(data, data->req.reader.stack, buf, blen,
                              nread, eos);
 
 out:
@@ -1259,7 +1234,7 @@ out:
 
 bool Curl_creader_needs_rewind(struct Curl_easy *data)
 {
-  struct Curl_creader *reader = data->req.reader_stack;
+  struct Curl_creader *reader = data->req.reader.stack;
   while(reader) {
     if(reader->crt->needs_rewind(data, reader)) {
       CURL_TRC_READ(data, "client reader needs rewind before next request");
@@ -1445,13 +1420,13 @@ out:
 
 curl_off_t Curl_creader_total_length(struct Curl_easy *data)
 {
-  struct Curl_creader *r = data->req.reader_stack;
+  struct Curl_creader *r = data->req.reader.stack;
   return r ? r->crt->total_length(data, r) : -1;
 }
 
 curl_off_t Curl_creader_client_length(struct Curl_easy *data)
 {
-  struct Curl_creader *r = data->req.reader_stack;
+  struct Curl_creader *r = data->req.reader.stack;
   while(r && r->phase != CURL_CR_CLIENT)
     r = r->next;
   return r ? r->crt->total_length(data, r) : -1;
@@ -1459,7 +1434,7 @@ curl_off_t Curl_creader_client_length(struct Curl_easy *data)
 
 CURLcode Curl_creader_resume_from(struct Curl_easy *data, curl_off_t offset)
 {
-  struct Curl_creader *r = data->req.reader_stack;
+  struct Curl_creader *r = data->req.reader.stack;
   while(r && r->phase != CURL_CR_CLIENT)
     r = r->next;
   return r ? r->crt->resume_from(data, r, offset) : CURLE_READ_ERROR;
@@ -1467,7 +1442,7 @@ CURLcode Curl_creader_resume_from(struct Curl_easy *data, curl_off_t offset)
 
 CURLcode Curl_creader_unpause(struct Curl_easy *data)
 {
-  struct Curl_creader *reader = data->req.reader_stack;
+  struct Curl_creader *reader = data->req.reader.stack;
   CURLcode result = CURLE_OK;
 
   while(reader) {
@@ -1482,7 +1457,7 @@ CURLcode Curl_creader_unpause(struct Curl_easy *data)
 
 bool Curl_creader_is_paused(struct Curl_easy *data)
 {
-  struct Curl_creader *reader = data->req.reader_stack;
+  struct Curl_creader *reader = data->req.reader.stack;
 
   while(reader) {
     if(reader->crt->is_paused(data, reader))
@@ -1494,7 +1469,7 @@ bool Curl_creader_is_paused(struct Curl_easy *data)
 
 void Curl_creader_done(struct Curl_easy *data, int premature)
 {
-  struct Curl_creader *reader = data->req.reader_stack;
+  struct Curl_creader *reader = data->req.reader.stack;
   while(reader) {
     reader->crt->done(data, reader, premature);
     reader = reader->next;
@@ -1505,7 +1480,7 @@ struct Curl_creader *Curl_creader_get_by_type(struct Curl_easy *data,
                                               const struct Curl_crtype *crt)
 {
   struct Curl_creader *r;
-  for(r = data->req.reader_stack; r; r = r->next) {
+  for(r = data->req.reader.stack; r; r = r->next) {
     if(r->crt == crt)
       return r;
   }
index 0cf6245ff673a7aab873e641a8aca55e93786ef8..7f31b246622b4a82efe852ee26ecfec0f6407298 100644 (file)
@@ -24,6 +24,7 @@
  *
  ***************************************************************************/
 #include "curl_setup.h"
+#include "cw-out.h"
 
 /**
  * Type of data that is being written to the client (application)
@@ -104,6 +105,7 @@ typedef enum {
   CURL_CW_RAW,  /* raw data written, before any decoding */
   CURL_CW_TRANSFER_DECODE, /* remove transfer-encodings */
   CURL_CW_PROTOCOL, /* after transfer, but before content decoding */
+  CURL_CW_BEFORE_DECODE, /* after protocol, but before content decoding */
   CURL_CW_CONTENT_DECODE, /* remove content-encodings */
   CURL_CW_CLIENT  /* data written to client */
 } Curl_cwriter_phase;
@@ -176,22 +178,16 @@ struct Curl_cwriter *Curl_cwriter_get_by_type(struct Curl_easy *data,
 struct Curl_cwriter *Curl_cwriter_get_by_name(struct Curl_easy *data,
                                               const char *name);
 
-/**
- * Convenience method for calling `writer->do_write()` that
- * checks for NULL writer.
- */
-CURLcode Curl_cwriter_write(struct Curl_easy *data,
-                            struct Curl_cwriter *writer, int type,
-                            const char *buf, size_t nbytes);
-CURLcode Curl_cwriter_flush(struct Curl_easy *data,
-                            struct Curl_cwriter *writer);
+/* Convenience method for calling `writer->do_write()` that
+ * checks for NULL writer. */
+#define Curl_cwriter_write(d, w, t, b, n) \
+  ((w) ? (w)->cwt->do_write((d), (w), (t), (b), (n)) : CURLE_WRITE_ERROR)
 
-/**
- * Return TRUE iff client writer is paused.
- */
-bool Curl_cwriter_is_paused(struct Curl_easy *data);
+#define Curl_cwriter_flush(d, w) \
+  ((w) ? (w)->cwt->do_flush((d), (w)) : CURLE_WRITE_ERROR)
 
-bool Curl_cwriter_is_content_decoding(struct Curl_easy *data);
+/* TRUE if client writer is paused. */
+#define Curl_cwriter_is_paused(d)     ((bool)(d)->req.writer.paused)
 
 /**
  * Unpause client writer and flush any buffered date to the client.
index 2a45450aa40d6c8ebb3311ae6c4d57320e803c23..60b1a35f4c2dc08347ce806e3d3d6877f5043841 100644 (file)
@@ -40,6 +40,7 @@ from testenv import (
     Dante,
     Env,
     ExecResult,
+    H2oServer,
     Httpd,
     NghttpxQuic,
     RunProfile,
@@ -776,17 +777,19 @@ def run_score(args, protocol):
             uploads = None
         requests = args.requests
 
-    test_httpd = protocol != 'h3'
-    test_caddy = protocol == 'h3'
-    if args.caddy or args.httpd:
-        test_caddy = args.caddy
-        test_httpd = args.httpd
-
     rv = 0
     env = Env()
     env.setup()
     env.test_timeout = None
 
+    test_httpd = protocol != 'h3'
+    test_h2o = protocol == 'h3' and env.have_h2o()
+    test_caddy = protocol == 'h3' and not test_h2o
+    if args.caddy or args.httpd or args.h2o:
+        test_caddy = args.caddy
+        test_httpd = args.httpd
+        test_h2o = args.h2o
+
     sockd = None
     socks_args = None
     if args.socks4 and args.socks5:
@@ -803,6 +806,7 @@ def run_score(args, protocol):
     httpd = None
     nghttpx = None
     caddy = None
+    h2o = None
     try:
         cards = []
 
@@ -859,6 +863,26 @@ def run_score(args, protocol):
             card.setup_resources(server_docs, downloads)
             cards.append(card)
 
+        if test_h2o:
+            h2o = H2oServer(env=env)
+            h2o.clear_logs()
+            assert h2o.initial_start()
+            server_descr = f'H2o/{env.h2o_version()}'
+            server_port = h2o.port
+            server_docs = h2o.docs_dir
+            card = ScoreRunner(env=env,
+                               protocol=protocol,
+                               server_descr=server_descr,
+                               server_port=server_port,
+                               verbose=args.verbose, curl_verbose=args.curl_verbose,
+                               download_parallel=args.download_parallel,
+                               upload_parallel=args.upload_parallel,
+                               with_flame=args.flame,
+                               socks_args=socks_args,
+                               limit_rate=args.limit_rate)
+            card.setup_resources(server_docs, downloads)
+            cards.append(card)
+
         if test_caddy and env.caddy:
             backend = ''
             if uploads and httpd is None:
@@ -921,6 +945,8 @@ def run_score(args, protocol):
             caddy.stop()
         if nghttpx:
             nghttpx.stop(wait_dead=False)
+        if h2o:
+            h2o.stop()
         if httpd:
             httpd.stop()
         if sockd:
@@ -951,6 +977,8 @@ def main():
                         default=1, help="how many sample runs to make")
     parser.add_argument("--httpd", action='store_true', default=False,
                         help="evaluate httpd server only")
+    parser.add_argument("--h2o", action='store_true', default=False,
+                        help="evaluate h2o server only")
     parser.add_argument("--caddy", action='store_true', default=False,
                         help="evaluate caddy server only")
     parser.add_argument("--curl-verbose", action='store_true',
index 08ab2e231162fe57277c51d209ab8e193ad99c83..d2eea486faa73f5f4d53c2442a6be108c7a01c6f 100644 (file)
@@ -38,6 +38,7 @@ from .client import LocalClient
 from .dante import Dante
 from .dnsd import Dnsd
 from .env import Env
+from .h2o import H2oServer, H2oProxy
 from .httpd import Httpd
 from .nghttpx import Nghttpx, NghttpxFwd, NghttpxQuic
 from .sshd import Sshd