From dc35631ef7fe314c227ba0b8529a4e0f5f383f79 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 5 Aug 2019 15:17:31 +0200 Subject: [PATCH] quiche: first working HTTP/3 request - enable debug log - fix use of quiche API - use download buffer - separate header/body Closes #4193 --- lib/http.c | 7 ++-- lib/http.h | 11 +++-- lib/transfer.c | 2 +- lib/vquic/quiche.c | 100 ++++++++++++++++++++++++++++++++++----------- 4 files changed, 89 insertions(+), 31 deletions(-) diff --git a/lib/http.c b/lib/http.c index 83180fb29..9d8cd5570 100644 --- a/lib/http.c +++ b/lib/http.c @@ -3677,6 +3677,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, * guarantees on future behaviors since it isn't within the protocol. */ char separator; + char twoorthree[2]; nc = sscanf(HEADER1, " HTTP/%1d.%1d%c%3d", &httpversion_major, @@ -3684,8 +3685,8 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, &separator, &k->httpcode); - if(nc == 1 && httpversion_major == 2 && - 1 == sscanf(HEADER1, " HTTP/2 %d", &k->httpcode)) { + if(nc == 1 && httpversion_major >= 2 && + 2 == sscanf(HEADER1, " HTTP/%1[23] %d", twoorthree, &k->httpcode)) { conn->httpversion = 0; nc = 4; separator = ' '; @@ -3723,7 +3724,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, } } else { - failf(data, "Unsupported HTTP version in response\n"); + failf(data, "Unsupported HTTP version in response"); return CURLE_UNSUPPORTED_PROTOCOL; } } diff --git a/lib/http.h b/lib/http.h index ea1310c39..f6f5d4fd5 100644 --- a/lib/http.h +++ b/lib/http.h @@ -178,18 +178,21 @@ struct HTTP { size_t len; /* size of the buffer 'mem' points to */ size_t memlen; /* size of data copied to mem */ - const uint8_t *upload_mem; /* points to a buffer to read from */ - size_t upload_len; /* size of the buffer 'upload_mem' points to */ - curl_off_t upload_left; /* number of bytes left to upload */ - char **push_headers; /* allocated array */ size_t push_headers_used; /* number of entries filled in */ size_t push_headers_alloc; /* number of entries allocated */ #endif +#if defined(USE_NGHTTP2) || defined(ENABLE_QUIC) + /* fields used by both HTTP/2 and HTTP/3 */ + const uint8_t *upload_mem; /* points to a buffer to read from */ + size_t upload_len; /* size of the buffer 'upload_mem' points to */ + curl_off_t upload_left; /* number of bytes left to upload */ +#endif #ifdef ENABLE_QUIC /*********** for HTTP/3 we store stream-local data here *************/ int64_t stream3_id; /* stream we are interested in */ + bool firstbody; /* FALSE until body arrives */ #endif }; diff --git a/lib/transfer.c b/lib/transfer.c index 7f2e6d1fb..ab662fbc0 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -497,7 +497,7 @@ static int data_pending(const struct connectdata *conn) TRUE. The thing is if we read everything, then http2_recv won't be called and we cannot signal the HTTP/2 stream has closed. As a workaround, we return nonzero here to call http2_recv. */ - ((conn->handler->protocol&PROTO_FAMILY_HTTP) && conn->httpversion == 20); + ((conn->handler->protocol&PROTO_FAMILY_HTTP) && conn->httpversion >= 20); #else Curl_ssl_data_pending(conn, FIRSTSOCKET); #endif diff --git a/lib/vquic/quiche.c b/lib/vquic/quiche.c index 2ccee1142..a70fb2237 100644 --- a/lib/vquic/quiche.c +++ b/lib/vquic/quiche.c @@ -38,7 +38,7 @@ #include "curl_memory.h" #include "memdebug.h" -#define DEBUG_HTTP3 +/* #define DEBUG_HTTP3 */ #ifdef DEBUG_HTTP3 #define H3BUGF(x) x #else @@ -198,10 +198,12 @@ static CURLcode process_ingress(struct connectdata *conn, int sockfd) { ssize_t recvd; struct quicsocket *qs = &conn->quic; - static uint8_t buf[65535]; + struct Curl_easy *data = conn->data; + uint8_t *buf = (uint8_t *)data->state.buffer; + size_t bufsize = data->set.buffer_size; do { - recvd = recv(sockfd, buf, sizeof(buf), 0); + recvd = recv(sockfd, buf, bufsize, 0); if((recvd < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) break; @@ -245,13 +247,33 @@ static CURLcode flush_egress(struct connectdata *conn, int sockfd) return CURLE_OK; } +struct h3h1header { + char *dest; + size_t destlen; /* left to use */ + size_t nlen; /* used */ +}; + static int cb_each_header(uint8_t *name, size_t name_len, uint8_t *value, size_t value_len, void *argp) { - (void)argp; - fprintf(stderr, "got HTTP header: %.*s=%.*s\n", - (int) name_len, name, (int) value_len, value); + struct h3h1header *headers = (struct h3h1header *)argp; + size_t olen = 0; + + if((name_len == 7) && !strncmp(":status", (char *)name, 7)) { + msnprintf(headers->dest, + headers->destlen, "HTTP/3 %.*s\n", + (int) value_len, value); + } + else { + msnprintf(headers->dest, + headers->destlen, "%.*s: %.*s\n", + (int)name_len, name, (int) value_len, value); + } + olen = strlen(headers->dest); + headers->destlen -= olen; + headers->nlen += olen; + headers->dest += olen; return 0; } @@ -261,60 +283,80 @@ static ssize_t h3_stream_recv(struct connectdata *conn, size_t buffersize, CURLcode *curlcode) { - bool fin; - ssize_t recvd; + ssize_t recvd = -1; + ssize_t rcode; struct quicsocket *qs = &conn->quic; curl_socket_t sockfd = conn->sock[sockindex]; quiche_h3_event *ev; int rc; + struct h3h1header headers; + struct HTTP *stream = conn->data->req.protop; + headers.dest = buf; + headers.destlen = buffersize; + headers.nlen = 0; if(process_ingress(conn, sockfd)) { + infof(conn->data, "h3_stream_recv returns on ingress\n"); *curlcode = CURLE_RECV_ERROR; return -1; } - recvd = quiche_conn_stream_recv(qs->conn, 0, (uint8_t *) buf, buffersize, - &fin); - if(recvd == QUICHE_ERR_DONE) { - *curlcode = CURLE_AGAIN; - return -1; - } - - infof(conn->data, "%zd bytes of H3 to deal with\n", recvd); - - while(1) { + while(recvd < 0) { int64_t s = quiche_h3_conn_poll(qs->h3c, qs->conn, &ev); if(s < 0) /* nothing more to do */ break; + infof(conn->data, "quiche_h3_conn_poll got something: %zd\n", s); + switch(quiche_h3_event_type(ev)) { case QUICHE_H3_EVENT_HEADERS: - rc = quiche_h3_event_for_each_header(ev, cb_each_header, NULL); + infof(conn->data, "quiche says HEADERS\n"); + rc = quiche_h3_event_for_each_header(ev, cb_each_header, &headers); if(rc) { fprintf(stderr, "failed to process headers"); /* what do we do about this? */ } + recvd = headers.nlen; break; case QUICHE_H3_EVENT_DATA: - recvd = quiche_h3_recv_body(qs->h3c, qs->conn, s, (unsigned char *)buf, + infof(conn->data, "quiche says DATA\n"); + if(!stream->firstbody) { + /* add a header-body separator CRLF */ + buf[0] = '\r'; + buf[1] = '\n'; + buf += 2; + buffersize = 2; + stream->firstbody = TRUE; + recvd = 2; /* two bytes already */ + } + else + recvd = 0; + rcode = quiche_h3_recv_body(qs->h3c, qs->conn, s, (unsigned char *)buf, buffersize); - if(recvd <= 0) { + if(rcode <= 0) { + recvd = -1; break; } + recvd += rcode; break; case QUICHE_H3_EVENT_FINISHED: + infof(conn->data, "quiche says FINISHED\n"); if(quiche_conn_close(qs->conn, true, 0, NULL, 0) < 0) { fprintf(stderr, "failed to close connection\n"); } break; + default: + infof(conn->data, "quiche says UNKNOWN\n"); + break; } quiche_h3_event_free(ev); } - *curlcode = CURLE_OK; + *curlcode = (-1 == recvd)? CURLE_AGAIN : CURLE_OK; + infof(conn->data, "h3_stream_recv returns %zd\n", recvd); return recvd; } @@ -334,7 +376,7 @@ static ssize_t h3_stream_send(struct connectdata *conn, *curlcode = CURLE_SEND_ERROR; return -1; } - return len; + sent = len; } else { sent = quiche_conn_stream_send(qs->conn, 0, mem, len, true); @@ -362,6 +404,14 @@ int Curl_quic_ver(char *p, size_t len) return msnprintf(p, len, " quiche"); } +#ifdef DEBUG_HTTP3 +static void debug_log(const char *line, void *argp) +{ + (void)argp; + fprintf(stderr, "%s\n", line); +} +#endif + /* Index where :authority header field will appear in request header field list. */ #define AUTHORITY_DST_IDX 3 @@ -381,6 +431,10 @@ static CURLcode http_request(struct connectdata *conn, const void *mem, quiche_h3_header *nva = NULL; struct quicsocket *qs = &conn->quic; +#ifdef DEBUG_HTTP3 + quiche_enable_debug_logging(debug_log, NULL); +#endif + qs->config = quiche_h3_config_new(0, 1024, 0, 0); /* TODO: handle failure */ -- 2.40.0