--- 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;