mirror of
https://github.com/moparisthebest/curl
synced 2024-11-15 14:05:03 -05:00
quiche: first working HTTP/3 request
- enable debug log - fix use of quiche API - use download buffer - separate header/body Closes #4193
This commit is contained in:
parent
a42b0957ab
commit
dc35631ef7
@ -3677,6 +3677,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
|
|||||||
* guarantees on future behaviors since it isn't within the protocol.
|
* guarantees on future behaviors since it isn't within the protocol.
|
||||||
*/
|
*/
|
||||||
char separator;
|
char separator;
|
||||||
|
char twoorthree[2];
|
||||||
nc = sscanf(HEADER1,
|
nc = sscanf(HEADER1,
|
||||||
" HTTP/%1d.%1d%c%3d",
|
" HTTP/%1d.%1d%c%3d",
|
||||||
&httpversion_major,
|
&httpversion_major,
|
||||||
@ -3684,8 +3685,8 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
|
|||||||
&separator,
|
&separator,
|
||||||
&k->httpcode);
|
&k->httpcode);
|
||||||
|
|
||||||
if(nc == 1 && httpversion_major == 2 &&
|
if(nc == 1 && httpversion_major >= 2 &&
|
||||||
1 == sscanf(HEADER1, " HTTP/2 %d", &k->httpcode)) {
|
2 == sscanf(HEADER1, " HTTP/%1[23] %d", twoorthree, &k->httpcode)) {
|
||||||
conn->httpversion = 0;
|
conn->httpversion = 0;
|
||||||
nc = 4;
|
nc = 4;
|
||||||
separator = ' ';
|
separator = ' ';
|
||||||
@ -3723,7 +3724,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
failf(data, "Unsupported HTTP version in response\n");
|
failf(data, "Unsupported HTTP version in response");
|
||||||
return CURLE_UNSUPPORTED_PROTOCOL;
|
return CURLE_UNSUPPORTED_PROTOCOL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
lib/http.h
11
lib/http.h
@ -178,18 +178,21 @@ struct HTTP {
|
|||||||
size_t len; /* size of the buffer 'mem' points to */
|
size_t len; /* size of the buffer 'mem' points to */
|
||||||
size_t memlen; /* size of data copied to mem */
|
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 */
|
char **push_headers; /* allocated array */
|
||||||
size_t push_headers_used; /* number of entries filled in */
|
size_t push_headers_used; /* number of entries filled in */
|
||||||
size_t push_headers_alloc; /* number of entries allocated */
|
size_t push_headers_alloc; /* number of entries allocated */
|
||||||
#endif
|
#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
|
#ifdef ENABLE_QUIC
|
||||||
/*********** for HTTP/3 we store stream-local data here *************/
|
/*********** for HTTP/3 we store stream-local data here *************/
|
||||||
int64_t stream3_id; /* stream we are interested in */
|
int64_t stream3_id; /* stream we are interested in */
|
||||||
|
bool firstbody; /* FALSE until body arrives */
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
|
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
|
be called and we cannot signal the HTTP/2 stream has closed. As
|
||||||
a workaround, we return nonzero here to call http2_recv. */
|
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
|
#else
|
||||||
Curl_ssl_data_pending(conn, FIRSTSOCKET);
|
Curl_ssl_data_pending(conn, FIRSTSOCKET);
|
||||||
#endif
|
#endif
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
#include "curl_memory.h"
|
#include "curl_memory.h"
|
||||||
#include "memdebug.h"
|
#include "memdebug.h"
|
||||||
|
|
||||||
#define DEBUG_HTTP3
|
/* #define DEBUG_HTTP3 */
|
||||||
#ifdef DEBUG_HTTP3
|
#ifdef DEBUG_HTTP3
|
||||||
#define H3BUGF(x) x
|
#define H3BUGF(x) x
|
||||||
#else
|
#else
|
||||||
@ -198,10 +198,12 @@ static CURLcode process_ingress(struct connectdata *conn, int sockfd)
|
|||||||
{
|
{
|
||||||
ssize_t recvd;
|
ssize_t recvd;
|
||||||
struct quicsocket *qs = &conn->quic;
|
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 {
|
do {
|
||||||
recvd = recv(sockfd, buf, sizeof(buf), 0);
|
recvd = recv(sockfd, buf, bufsize, 0);
|
||||||
if((recvd < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))
|
if((recvd < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -245,13 +247,33 @@ static CURLcode flush_egress(struct connectdata *conn, int sockfd)
|
|||||||
return CURLE_OK;
|
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,
|
static int cb_each_header(uint8_t *name, size_t name_len,
|
||||||
uint8_t *value, size_t value_len,
|
uint8_t *value, size_t value_len,
|
||||||
void *argp)
|
void *argp)
|
||||||
{
|
{
|
||||||
(void)argp;
|
struct h3h1header *headers = (struct h3h1header *)argp;
|
||||||
fprintf(stderr, "got HTTP header: %.*s=%.*s\n",
|
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);
|
(int)name_len, name, (int) value_len, value);
|
||||||
|
}
|
||||||
|
olen = strlen(headers->dest);
|
||||||
|
headers->destlen -= olen;
|
||||||
|
headers->nlen += olen;
|
||||||
|
headers->dest += olen;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,60 +283,80 @@ static ssize_t h3_stream_recv(struct connectdata *conn,
|
|||||||
size_t buffersize,
|
size_t buffersize,
|
||||||
CURLcode *curlcode)
|
CURLcode *curlcode)
|
||||||
{
|
{
|
||||||
bool fin;
|
ssize_t recvd = -1;
|
||||||
ssize_t recvd;
|
ssize_t rcode;
|
||||||
struct quicsocket *qs = &conn->quic;
|
struct quicsocket *qs = &conn->quic;
|
||||||
curl_socket_t sockfd = conn->sock[sockindex];
|
curl_socket_t sockfd = conn->sock[sockindex];
|
||||||
quiche_h3_event *ev;
|
quiche_h3_event *ev;
|
||||||
int rc;
|
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)) {
|
if(process_ingress(conn, sockfd)) {
|
||||||
|
infof(conn->data, "h3_stream_recv returns on ingress\n");
|
||||||
*curlcode = CURLE_RECV_ERROR;
|
*curlcode = CURLE_RECV_ERROR;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
recvd = quiche_conn_stream_recv(qs->conn, 0, (uint8_t *) buf, buffersize,
|
while(recvd < 0) {
|
||||||
&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) {
|
|
||||||
int64_t s = quiche_h3_conn_poll(qs->h3c, qs->conn, &ev);
|
int64_t s = quiche_h3_conn_poll(qs->h3c, qs->conn, &ev);
|
||||||
if(s < 0)
|
if(s < 0)
|
||||||
/* nothing more to do */
|
/* nothing more to do */
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
infof(conn->data, "quiche_h3_conn_poll got something: %zd\n", s);
|
||||||
|
|
||||||
switch(quiche_h3_event_type(ev)) {
|
switch(quiche_h3_event_type(ev)) {
|
||||||
case QUICHE_H3_EVENT_HEADERS:
|
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) {
|
if(rc) {
|
||||||
fprintf(stderr, "failed to process headers");
|
fprintf(stderr, "failed to process headers");
|
||||||
/* what do we do about this? */
|
/* what do we do about this? */
|
||||||
}
|
}
|
||||||
|
recvd = headers.nlen;
|
||||||
break;
|
break;
|
||||||
case QUICHE_H3_EVENT_DATA:
|
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);
|
buffersize);
|
||||||
if(recvd <= 0) {
|
if(rcode <= 0) {
|
||||||
|
recvd = -1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
recvd += rcode;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case QUICHE_H3_EVENT_FINISHED:
|
case QUICHE_H3_EVENT_FINISHED:
|
||||||
|
infof(conn->data, "quiche says FINISHED\n");
|
||||||
if(quiche_conn_close(qs->conn, true, 0, NULL, 0) < 0) {
|
if(quiche_conn_close(qs->conn, true, 0, NULL, 0) < 0) {
|
||||||
fprintf(stderr, "failed to close connection\n");
|
fprintf(stderr, "failed to close connection\n");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
infof(conn->data, "quiche says UNKNOWN\n");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
quiche_h3_event_free(ev);
|
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;
|
return recvd;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,7 +376,7 @@ static ssize_t h3_stream_send(struct connectdata *conn,
|
|||||||
*curlcode = CURLE_SEND_ERROR;
|
*curlcode = CURLE_SEND_ERROR;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return len;
|
sent = len;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sent = quiche_conn_stream_send(qs->conn, 0, mem, len, true);
|
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");
|
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
|
/* Index where :authority header field will appear in request header
|
||||||
field list. */
|
field list. */
|
||||||
#define AUTHORITY_DST_IDX 3
|
#define AUTHORITY_DST_IDX 3
|
||||||
@ -381,6 +431,10 @@ static CURLcode http_request(struct connectdata *conn, const void *mem,
|
|||||||
quiche_h3_header *nva = NULL;
|
quiche_h3_header *nva = NULL;
|
||||||
struct quicsocket *qs = &conn->quic;
|
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);
|
qs->config = quiche_h3_config_new(0, 1024, 0, 0);
|
||||||
/* TODO: handle failure */
|
/* TODO: handle failure */
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user