src/server/util/io.c

changeset 498
0d80f8a2b29f
parent 493
56cf890dd9ed
child 500
077aa138e8fb
--- a/src/server/util/io.c	Wed May 31 19:39:10 2023 +0200
+++ b/src/server/util/io.c	Sun Jun 04 20:09:18 2023 +0200
@@ -118,7 +118,7 @@
 }
 
 #ifdef XP_UNIX
-ssize_t net_sys_write(Sysstream *st, void *buf, size_t nbytes) {
+ssize_t net_sys_write(Sysstream *st, const void *buf, size_t nbytes) {
     return write(st->fd, buf, nbytes);
 }
 
@@ -264,6 +264,10 @@
     st->buflen = NULL;
     st->bufpos = NULL;
     st->chunk_buf_pos = 0;
+    st->current_chunk_length = 0;
+    st->current_chunk_pos = 0;
+    st->write_chunk_buf_len = 0;
+    st->write_chunk_buf_pos = 0;
     st->chunked_enc = WS_FALSE;
     st->read_eof = WS_TRUE;
     st->write_eof = WS_FALSE;
@@ -318,30 +322,141 @@
     return http->written;
 }
 
-ssize_t net_http_write(HttpStream *st, void *buf, size_t nbytes) {
+/*
+ * iovec callback func
+ * returns number of payload bytes written (number of bytes returned back to the net_write caller)
+ */
+typedef ssize_t(*writeop_finish_func)(HttpStream *st, char *base, size_t len, size_t written, void *udata);
+
+static ssize_t httpstream_finish_prev_header(HttpStream *st, char *base, size_t len, size_t written, void *udata) {
+    st->write_chunk_buf_pos += written;
+    if(st->write_chunk_buf_pos == st->write_chunk_buf_len) {
+        st->write_chunk_buf_len = 0;
+        st->write_chunk_buf_pos = 0;
+    }
+    return 0;
+}
+
+static ssize_t httpstream_finish_data(HttpStream *st, char *base, size_t len, size_t written, void *udata) {
+    st->current_chunk_pos += written;
+    if(st->current_chunk_pos == st->current_chunk_length) {
+        st->current_chunk_length = 0;
+        st->current_chunk_pos = 0;
+        st->current_trailer = 2;
+    }
+    return written;
+}
+
+static ssize_t httpstream_finish_new_header(HttpStream *st, char *base, size_t len, size_t written, void *udata) {
+    size_t *chunk_len = udata;
+    st->current_chunk_length = *chunk_len;
+    st->current_chunk_pos = 0; // new chunk started
+    if(written < len) {
+        st->write_chunk_buf_len = len-written;
+        st->write_chunk_buf_pos = 0;
+        memcpy(st->write_chunk_buf + st->write_chunk_buf_pos, base+written, st->write_chunk_buf_len);
+    } else {
+        st->write_chunk_buf_len = 0;
+        st->write_chunk_buf_pos = 0;
+    }
+    return 0;
+}
+
+static ssize_t httpstream_finish_trailer(HttpStream *st, char *base, size_t len, size_t written, void *udata) {
+    st->current_trailer -= written;
+    return 0;
+}
+
+ssize_t net_http_write(HttpStream *st, const void *buf, size_t nbytes) {
     if(st->write_eof) return 0;
     IOStream *fd = st->fd;
-    if(st->chunked_enc) {
-        // TODO: on some plattforms iov_len is smaller than size_t
-        struct iovec io[3];
-        char chunk_len[16];
-        io[0].iov_base = chunk_len;
-        io[0].iov_len = snprintf(chunk_len, 16, "%zx\r\n", nbytes);
-        io[1].iov_base = buf;
-        io[1].iov_len = nbytes;
-        io[2].iov_base = "\r\n";
-        io[2].iov_len = 2;
-        // TODO: FIXME: if wv < sum of iov_len, everything would explode
-        // we need to store the chunk state and remaining bytes
-        // TODO: FIX IT NOW, IT IS HORRIBLE BROKEN
-        ssize_t wv = fd->writev(fd, io, 3);
-        ssize_t w = wv - io[0].iov_len - io[2].iov_len;
+    if(!st->chunked_enc) {
+        ssize_t w = fd->write(fd, buf, nbytes);
         st->written += w > 0 ? w : 0;
         return w;
     } else {
-        ssize_t w = fd->write(fd, buf, nbytes);
-        st->written += w > 0 ? w : 0;
-        return w;
+        struct iovec io[8];
+        writeop_finish_func io_finished[8];
+        void *io_finished_udata[8];
+        int iovec_len = 0;
+        
+        char *str_crlf = "\r\n";
+        
+        size_t prev_chunk_len = st->current_chunk_length;
+        size_t new_chunk_len = 0;
+        
+        // was the previous chunk header completely sent?
+        if(st->write_chunk_buf_len > 0) {
+            io[0].iov_base = &st->write_chunk_buf[st->write_chunk_buf_pos];
+            io[0].iov_len = st->write_chunk_buf_len - st->write_chunk_buf_pos;
+            io_finished[0] = httpstream_finish_prev_header;
+            io_finished_udata[0] = &prev_chunk_len;
+            iovec_len++;
+        }
+        
+        // was the previous chunk payload completely sent?
+        if(st->current_chunk_length != 0) {
+            size_t chunk_remaining = st->current_chunk_length - st->current_chunk_pos;
+            size_t prev_nbytes = chunk_remaining > nbytes ? nbytes : chunk_remaining;
+            io[iovec_len].iov_base = (char*)buf;
+            io[iovec_len].iov_len = prev_nbytes;
+            io_finished[iovec_len] = httpstream_finish_data;
+            buf = ((char*)buf) + prev_nbytes;
+            nbytes -= prev_nbytes;
+            iovec_len++;
+            
+            io[iovec_len].iov_base = str_crlf;
+            io[iovec_len].iov_len = 2;
+            io_finished[iovec_len] = httpstream_finish_trailer;
+            iovec_len++;
+        } else if(st->current_trailer > 0) {
+            io[iovec_len].iov_base = str_crlf + 2 - st->current_trailer;
+            io[iovec_len].iov_len = st->current_trailer;
+            io_finished[iovec_len] = httpstream_finish_trailer;
+            iovec_len++;
+        }
+        
+        // TODO: on some plattforms iov_len is smaller than size_t
+        //       if nbytes > INT_MAX, it should be devided into multiple
+        //       iovec entries
+        char chunk_len[16];
+        if(nbytes > 0) {
+            new_chunk_len = nbytes;
+            io[iovec_len].iov_base = chunk_len;
+            io[iovec_len].iov_len = snprintf(chunk_len, 16, "%zx\r\n", nbytes);
+            io_finished[iovec_len] = httpstream_finish_new_header;
+            io_finished_udata[iovec_len] = &new_chunk_len;
+            iovec_len++;
+            
+            io[iovec_len].iov_base = (char*)buf;
+            io[iovec_len].iov_len = nbytes;
+            io_finished[iovec_len] = httpstream_finish_data;
+            iovec_len++;
+            
+            io[iovec_len].iov_base = str_crlf;
+            io[iovec_len].iov_len = 2;
+            io_finished[iovec_len] = httpstream_finish_trailer;
+            iovec_len++;
+        }
+        
+        ssize_t wv = fd->writev(fd, io, iovec_len);
+        if(wv <= 0) {
+            return wv;
+        }
+        
+        size_t ret_w = 0;
+        int i = 0;
+        while(wv > 0) {
+            char *base = io[i].iov_base;
+            size_t len = io[i].iov_len;
+            size_t wlen = wv > len ? len : wv;
+            ret_w += io_finished[i](st, base, len, wlen, io_finished_udata[i]);
+            wv -= wlen;
+            i++;
+        }
+        
+        st->written += ret_w;
+        return ret_w;
     }
 }
 
@@ -453,7 +568,7 @@
  *         -1 if an error occured
  *         >0 chunk header length
  */
-static int parse_chunk_header(char *str, int len, WSBool first, int64_t *chunklen) {
+int http_stream_parse_chunk_header(char *str, int len, WSBool first, int64_t *chunklen) {
     char *hdr_start = NULL;
     char *hdr_end = NULL;
     int i = 0;
@@ -569,7 +684,7 @@
             }
             int chunkbuf_len = st->chunk_buf_pos + r;
             int64_t chunklen;
-            int ret = parse_chunk_header(st->chunk_buf, chunkbuf_len, st->read_total > 0 ? FALSE : TRUE, &chunklen);
+            int ret = http_stream_parse_chunk_header(st->chunk_buf, chunkbuf_len, st->read_total > 0 ? FALSE : TRUE, &chunklen);
             if(ret == 0) {
                 // incomplete chunk header
                 st->chunk_buf_pos = chunkbuf_len;
@@ -651,7 +766,7 @@
     return (IOStream*)st;
 }
 
-ssize_t net_ssl_write(SSLStream *st, void *buf, size_t nbytes) {
+ssize_t net_ssl_write(SSLStream *st, const void *buf, size_t nbytes) {
     int ret = SSL_write(st->ssl, buf, nbytes);
     if(ret <= 0) {
         st->error = SSL_get_error(st->ssl, ret);
@@ -734,7 +849,7 @@
     return r;
 }
 
-ssize_t net_write(SYS_NETFD fd, void *buf, size_t nbytes) {
+ssize_t net_write(SYS_NETFD fd, const void *buf, size_t nbytes) {
     ssize_t r = ((IOStream*)fd)->write(fd, buf, nbytes);
     if(r < 0) {
         ((IOStream*)fd)->io_errno = errno;

mercurial