diff --git a/docs/libcurl/opts/CURLOPT_STREAM_DEPENDS.3 b/docs/libcurl/opts/CURLOPT_STREAM_DEPENDS.3 new file mode 100644 index 000000000..f3dcff31d --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_STREAM_DEPENDS.3 @@ -0,0 +1,56 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. +.\" * +.\" * This software is licensed as described in the file COPYING, which +.\" * you should have received as part of this distribution. The terms +.\" * are also available at http://curl.haxx.se/docs/copyright.html. +.\" * +.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell +.\" * copies of the Software, and permit persons to whom the Software is +.\" * furnished to do so, under the terms of the COPYING file. +.\" * +.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +.\" * KIND, either express or implied. +.\" * +.\" ************************************************************************** +.\" +.TH CURLOPT_STREAM_DEPENDS 3 "13 Sep 2015" "libcurl 7.46.0" "curl_easy_setopt options" +.SH NAME +CURLOPT_STREAM_DEPENDS \- set stream this transfer depends on +.SH SYNOPSIS +#include + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_STREAM_DEPENDS, CURL *dephandle); +.SH DESCRIPTION +Pass a CURL * pointer in \fIdephandle\fP to identify the stream within the +same connection that this stream is depending upon. This option clears the +exclusive it and is mutually exclusive to the +\fICURLOPT_STREAM_DEPENDS_E(3)\fP option. + +The spec says "Including a dependency expresses a preference to allocate +resources to the identified stream rather than to the dependent stream." + +This option can be set during transfer. + +\fIdephandle\fP must not be the same as \fIhandle\fP, that will cause this +function to return an error. It must be another easy handle, and it also needs +to be a handle of a transfer that will be sent over the same HTTP/2 connection +for this option to have an actual effect. +.SH DEFAULT +NULL +.SH PROTOCOLS +HTTP/2 +.SH EXAMPLE +TODO +.SH AVAILABILITY +Added in 7.46.0 +.SH RETURN VALUE +Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not. +.SH "SEE ALSO" +.BR CURLOPT_STREAM_PRIORITY "(3), " CURLOPT_STREAM_DEPENDS_E "(3), " diff --git a/docs/libcurl/opts/CURLOPT_STREAM_DEPENDS_E.3 b/docs/libcurl/opts/CURLOPT_STREAM_DEPENDS_E.3 new file mode 100644 index 000000000..1684c369d --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_STREAM_DEPENDS_E.3 @@ -0,0 +1,59 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. +.\" * +.\" * This software is licensed as described in the file COPYING, which +.\" * you should have received as part of this distribution. The terms +.\" * are also available at http://curl.haxx.se/docs/copyright.html. +.\" * +.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell +.\" * copies of the Software, and permit persons to whom the Software is +.\" * furnished to do so, under the terms of the COPYING file. +.\" * +.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +.\" * KIND, either express or implied. +.\" * +.\" ************************************************************************** +.\" +.TH CURLOPT_STREAM_DEPENDS_E 3 "13 Sep 2015" "libcurl 7.46.0" "curl_easy_setopt options" +.SH NAME +CURLOPT_STREAM_DEPENDS_E \- set stream this transfer depends on execlusively +.SH SYNOPSIS +#include + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_STREAM_DEPENDS_E, CURL *dephandle); +.SH DESCRIPTION +Pass a CURL * pointer in \fIdephandle\fP to identify the stream within the +same connection that this stream is depending upon exclusively. That means it +depends on it and sets the Exclusive bit. + +The spec says "Including a dependency expresses a preference to allocate +resources to the identified stream rather than to the dependent stream." + +Setting a dependency with the exclusive flag for a reprioritized stream causes +all the dependencies of the new parent stream to become dependent on the +reprioritized stream. + +This option can be set during transfer. + +\fIdephandle\fP must not be the same as \fIhandle\fP, that will cause this +function to return an error. It must be another easy handle, and it also needs +to be a handle of a transfer that will be sent over the same HTTP/2 connection +for this option to have an actual effect. +.SH DEFAULT +NULL +.SH PROTOCOLS +HTTP/2 +.SH EXAMPLE +TODO +.SH AVAILABILITY +Added in 7.46.0 +.SH RETURN VALUE +Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not. +.SH "SEE ALSO" +.BR CURLOPT_STREAM_PRIORITY "(3), " CURLOPT_STREAM_DEPENDS "(3), " diff --git a/docs/libcurl/opts/CURLOPT_STREAM_PRIORITY.3 b/docs/libcurl/opts/CURLOPT_STREAM_PRIORITY.3 new file mode 100644 index 000000000..4b0f16a95 --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_STREAM_PRIORITY.3 @@ -0,0 +1,56 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. +.\" * +.\" * This software is licensed as described in the file COPYING, which +.\" * you should have received as part of this distribution. The terms +.\" * are also available at http://curl.haxx.se/docs/copyright.html. +.\" * +.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell +.\" * copies of the Software, and permit persons to whom the Software is +.\" * furnished to do so, under the terms of the COPYING file. +.\" * +.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +.\" * KIND, either express or implied. +.\" * +.\" ************************************************************************** +.\" +.TH CURLOPT_STREAM_PRIORITY 3 "13 Sep 2015" "libcurl 7.46.0" "curl_easy_setopt options" +.SH NAME +CURLOPT_STREAM_PRIORITY \- set numerical stream priority +.SH SYNOPSIS +#include + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_STREAM_PRIORITY, long prio); +.SH DESCRIPTION +Set the long \fIprio\fP to a number between 1 and 256. + +For protocols with priorities on streams, such as HTTP/2, this option can be +used to set an individual priority for this particular stream used by the easy +\fIhandle\fP. Setting and using priorities really only makes sense and is only +usable when doing multiple streams over the same connections, which thus +implies that you use \fICURLMOPT_PIPELINING(3)\fP. + +This option can be set during transfer. + +See section 5.3 of RFC 7540 for protocol details: +https://httpwg.github.io/specs/rfc7540.html#StreamPriority +.SH DEFAULT +If nothing is set, the HTTP/2 protocol itself will use its own default which +is 16. +.SH PROTOCOLS +HTTP/2 +.SH EXAMPLE +TODO +.SH AVAILABILITY +Added in 7.46.0 +.SH RETURN VALUE +Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not. +.SH "SEE ALSO" +.BR CURLOPT_STREAM_DEPENDS "(3), " CURLOPT_STREAM_DEPENDS_E "(3), " +.BR CURLOPT_PIPEWAIT "(3), " CURLMOPT_PIPELINING "(3), " diff --git a/include/curl/curl.h b/include/curl/curl.h index 2d482750d..5ea3d4ce9 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -1648,6 +1648,15 @@ typedef enum { /* Set the protocol used when curl is given a URL without a protocol */ CINIT(DEFAULT_PROTOCOL, OBJECTPOINT, 238), + /* Set stream priority, 1 - 256 */ + CINIT(STREAM_PRIORITY, LONG, 239), + + /* Set stream dependency on another CURL handle */ + CINIT(STREAM_DEPENDS, OBJECTPOINT, 240), + + /* Set E-xclusive stream dependency on another CURL handle */ + CINIT(STREAM_DEPENDS_E, OBJECTPOINT, 241), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; diff --git a/lib/http2.c b/lib/http2.c index 0cbb623c4..9768c97da 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -46,6 +46,24 @@ #error too old nghttp2 version, upgrade! #endif +/* + * Curl_http2_init_state() is called when the easy handle is created and + * allows for HTTP/2 specific init of state. + */ +void Curl_http2_init_state(struct UrlState *state) +{ + state->stream_prio = NGHTTP2_DEFAULT_WEIGHT; +} + +/* + * Curl_http2_init_userset() is called when the easy handle is created and + * allows for HTTP/2 specific user-set fields. + */ +void Curl_http2_init_userset(struct UserDefined *set) +{ + set->stream_prio = NGHTTP2_DEFAULT_WEIGHT; +} + static int http2_perform_getsock(const struct connectdata *conn, curl_socket_t *sock, /* points to numsocks @@ -986,6 +1004,54 @@ static ssize_t http2_handle_stream_close(struct http_conn *httpc, return 0; } +/* + * h2_pri_spec() fills in the pri_spec struct, used by nghttp2 to send weight + * and dependency to the peer. It also stores the updated values in the state + * struct. + */ + +static void h2_pri_spec(struct SessionHandle *data, + nghttp2_priority_spec *pri_spec) +{ + struct HTTP *depstream = (data->set.stream_depends_on? + data->set.stream_depends_on->req.protop:NULL); + int32_t depstream_id = depstream? depstream->stream_id:0; + nghttp2_priority_spec_init(pri_spec, depstream_id, data->set.stream_prio, + data->set.stream_depends_e); + data->state.stream_prio = data->set.stream_prio; + data->state.stream_depends_e = data->set.stream_depends_e; + data->state.stream_depends_on = data->set.stream_depends_on; +} + +/* + * h2_session_send() checks if there's been an update in the priority / + * dependency settings and if so it submits a PRIORITY frame with the updated + * info. + */ +static int h2_session_send(struct SessionHandle *data, + nghttp2_session *h2) +{ + struct HTTP *stream = data->req.protop; + if((data->set.stream_prio != data->state.stream_prio) || + (data->set.stream_depends_e != data->state.stream_depends_e) || + (data->set.stream_depends_on != data->state.stream_depends_on) ) { + /* send new weight and/or dependency */ + nghttp2_priority_spec pri_spec; + int rv; + + h2_pri_spec(data, &pri_spec); + + DEBUGF(infof(data, "Queuing HTTP/2 PRIORITY frame on stream %u!\n", + stream->stream_id)); + rv = nghttp2_submit_priority(h2, NGHTTP2_FLAG_NONE, stream->stream_id, + &pri_spec); + if(rv) + return rv; + } + + return nghttp2_session_send(h2); +} + /* * If the read would block (EWOULDBLOCK) we return -1. Otherwise we return * a regular CURLcode value. @@ -1140,7 +1206,7 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex, } /* Always send pending frames in nghttp2 session, because nghttp2_session_mem_recv() may queue new frame */ - rv = nghttp2_session_send(httpc->h2); + rv = h2_session_send(data, httpc->h2); if(rv != 0) { *err = CURLE_SEND_ERROR; return 0; @@ -1199,6 +1265,7 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex, nghttp2_data_provider data_prd; int32_t stream_id; nghttp2_session *h2 = httpc->h2; + nghttp2_priority_spec pri_spec; (void)sockindex; @@ -1210,7 +1277,7 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex, stream->upload_mem = mem; stream->upload_len = len; nghttp2_session_resume_data(h2, stream->stream_id); - rv = nghttp2_session_send(h2); + rv = h2_session_send(conn->data, h2); if(nghttp2_is_fatal(rv)) { *err = CURLE_SEND_ERROR; return -1; @@ -1351,17 +1418,19 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex, nva[i] = authority; } + h2_pri_spec(conn->data, &pri_spec); + switch(conn->data->set.httpreq) { case HTTPREQ_POST: case HTTPREQ_POST_FORM: case HTTPREQ_PUT: data_prd.read_callback = data_source_read_callback; data_prd.source.ptr = NULL; - stream_id = nghttp2_submit_request(h2, NULL, nva, nheader, + stream_id = nghttp2_submit_request(h2, &pri_spec, nva, nheader, &data_prd, conn->data); break; default: - stream_id = nghttp2_submit_request(h2, NULL, nva, nheader, + stream_id = nghttp2_submit_request(h2, &pri_spec, nva, nheader, NULL, conn->data); } @@ -1377,6 +1446,8 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex, stream_id, conn->data); stream->stream_id = stream_id; + /* this does not call h2_session_send() since there can not have been any + * priority upodate since the nghttp2_submit_request() call above */ rv = nghttp2_session_send(h2); if(rv != 0) { @@ -1536,7 +1607,7 @@ CURLcode Curl_http2_switched(struct connectdata *conn, } /* Try to send some frames since we may read SETTINGS already. */ - rv = nghttp2_session_send(httpc->h2); + rv = h2_session_send(data, httpc->h2); if(rv != 0) { failf(data, "nghttp2_session_send() failed: %s(%d)", diff --git a/lib/http2.h b/lib/http2.h index bb7ad9c4c..b9743806c 100644 --- a/lib/http2.h +++ b/lib/http2.h @@ -38,6 +38,8 @@ int Curl_http2_ver(char *p, size_t len); CURLcode Curl_http2_init(struct connectdata *conn); +void Curl_http2_init_state(struct UrlState *state); +void Curl_http2_init_userset(struct UserDefined *set); CURLcode Curl_http2_send_request(struct connectdata *conn); CURLcode Curl_http2_request_upgrade(Curl_send_buffer *req, struct connectdata *conn); @@ -55,6 +57,8 @@ void Curl_http2_setup_req(struct SessionHandle *data); #define Curl_http2_switched(x,y,z) CURLE_UNSUPPORTED_PROTOCOL #define Curl_http2_setup_conn(x) #define Curl_http2_setup_req(x) +#define Curl_http2_init_state(x) +#define Curl_http2_init_userset(x) #endif #endif /* HEADER_CURL_HTTP2_H */ diff --git a/lib/url.c b/lib/url.c index ed8659675..866fee2eb 100644 --- a/lib/url.c +++ b/lib/url.c @@ -111,6 +111,7 @@ int curl_win32_idn_to_ascii(const char *in, char **out); #include "telnet.h" #include "tftp.h" #include "http.h" +#include "http2.h" #include "file.h" #include "curl_ldap.h" #include "ssh.h" @@ -616,6 +617,8 @@ CURLcode Curl_init_userdefined(struct UserDefined *set) set->expect_100_timeout = 1000L; /* Wait for a second by default. */ set->sep_headers = TRUE; /* separated header lists by default */ + + Curl_http2_init_userset(set); return result; } @@ -673,6 +676,8 @@ CURLcode Curl_open(struct SessionHandle **curl) data->wildcard.filelist = NULL; data->set.fnmatch = ZERO_NULL; data->set.maxconnects = DEFAULT_CONNCACHE_SIZE; /* for easy handles */ + + Curl_http2_init_state(&data->state); } if(result) { @@ -2658,6 +2663,29 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, case CURLOPT_PIPEWAIT: data->set.pipewait = (0 != va_arg(param, long))?TRUE:FALSE; break; + case CURLOPT_STREAM_PRIORITY: +#ifndef USE_NGHTTP2 + return CURLE_NOT_BUILT_IN; +#else + arg = va_arg(param, long); + if((arg>=1) && (arg <= 256)) + data->set.stream_prio = (int)arg; + break; +#endif + case CURLOPT_STREAM_DEPENDS: + case CURLOPT_STREAM_DEPENDS_E: + { +#ifndef USE_NGHTTP2 + return CURLE_NOT_BUILT_IN; +#else + struct SessionHandle *dep = va_arg(param, struct SessionHandle *); + if(dep && GOOD_EASY_HANDLE(dep)) { + data->set.stream_depends_on = dep; + data->set.stream_depends_e = (option == CURLOPT_STREAM_DEPENDS_E); + } + break; +#endif + } default: /* unknown tag and its companion, just ignore: */ result = CURLE_UNKNOWN_OPTION; diff --git a/lib/urldata.h b/lib/urldata.h index b467e503e..7d6025cb5 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1337,6 +1337,10 @@ struct UrlState { curl_read_callback fread_func; /* read callback/function */ void *in; /* CURLOPT_READDATA */ + + struct SessionHandle *stream_depends_on; + bool stream_depends_e; /* set or don't set the Exclusive bit */ + int stream_prio; }; @@ -1653,6 +1657,10 @@ struct UserDefined { bool pipewait; /* wait for pipe/multiplex status before starting a new connection */ long expect_100_timeout; /* in milliseconds */ + + struct SessionHandle *stream_depends_on; + bool stream_depends_e; /* set or don't set the Exclusive bit */ + int stream_prio; }; struct Names {