mirror of
https://github.com/curl/curl.git
synced 2026-07-27 17:28:56 +03:00
http: expect 100 rework
Move all handling of HTTP's `Expect: 100-continue` feature into a client reader. Add sending flag `KEEP_SEND_TIMED` that triggers transfer sending on general events like a timer. HTTP installs a `CURL_CR_PROTOCOL` reader when announcing `Expect: 100-continue`. That reader works as follows: - on first invocation, records time, starts the `EXPIRE_100_TIMEOUT` timer, disables `KEEP_SEND`, enables `KEEP_SEND_TIMER` and returns 0, eos=FALSE like a paused upload. - on subsequent invocation it checks if the timer has expired. If so, it enables `KEEP_SEND` and switches to passing through reads to the underlying readers. Transfer handling's `readwrite()` will be invoked when a timer expires (like `EXPIRE_100_TIMEOUT`) or when data from the server arrives. Seeing `KEEP_SEND_TIMER`, it will try to upload more data, which triggers reading from the client readers again. Which then may lead to a new pausing or cause the upload to start. Flags and timestamps connected to this have been moved from `SingleRequest` into the reader's context. Closes #13110
This commit is contained in:
parent
3d0fd382a2
commit
80a3b830cc
17 changed files with 301 additions and 189 deletions
237
lib/http.c
237
lib/http.c
|
|
@ -102,6 +102,9 @@
|
|||
*/
|
||||
|
||||
static bool http_should_fail(struct Curl_easy *data);
|
||||
static bool http_exp100_is_waiting(struct Curl_easy *data);
|
||||
static CURLcode http_exp100_add_reader(struct Curl_easy *data);
|
||||
static void http_exp100_send_anyway(struct Curl_easy *data);
|
||||
|
||||
/*
|
||||
* HTTP handler interface.
|
||||
|
|
@ -1275,31 +1278,6 @@ static const char *get_http_string(const struct Curl_easy *data,
|
|||
}
|
||||
#endif
|
||||
|
||||
/* check and possibly add an Expect: header */
|
||||
static CURLcode expect100(struct Curl_easy *data, struct dynbuf *req)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
if(!data->state.disableexpect &&
|
||||
Curl_use_http_1_1plus(data, data->conn) &&
|
||||
(data->conn->httpversion < 20)) {
|
||||
/* if not doing HTTP 1.0 or version 2, or disabled explicitly, we add an
|
||||
Expect: 100-continue to the headers which actually speeds up post
|
||||
operations (as there is one packet coming back from the web server) */
|
||||
const char *ptr = Curl_checkheaders(data, STRCONST("Expect"));
|
||||
if(ptr) {
|
||||
data->state.expect100header =
|
||||
Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue"));
|
||||
}
|
||||
else {
|
||||
result = Curl_dyn_addn(req, STRCONST("Expect: 100-continue\r\n"));
|
||||
if(!result)
|
||||
data->state.expect100header = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
enum proxy_use {
|
||||
HEADER_SERVER, /* direct to server */
|
||||
HEADER_PROXY, /* regular request to proxy */
|
||||
|
|
@ -2164,26 +2142,39 @@ CURLcode Curl_http_req_set_reader(struct Curl_easy *data,
|
|||
return result;
|
||||
}
|
||||
|
||||
static CURLcode addexpect(struct Curl_easy *data, struct dynbuf *r)
|
||||
static CURLcode addexpect(struct Curl_easy *data, struct dynbuf *r,
|
||||
bool *announced_exp100)
|
||||
{
|
||||
data->state.expect100header = FALSE;
|
||||
CURLcode result;
|
||||
char *ptr;
|
||||
|
||||
*announced_exp100 = FALSE;
|
||||
/* Avoid Expect: 100-continue if Upgrade: is used */
|
||||
if(data->req.upgr101 == UPGR101_INIT) {
|
||||
/* For really small puts we don't use Expect: headers at all, and for
|
||||
the somewhat bigger ones we allow the app to disable it. Just make
|
||||
sure that the expect100header is always set to the preferred value
|
||||
here. */
|
||||
char *ptr = Curl_checkheaders(data, STRCONST("Expect"));
|
||||
if(ptr) {
|
||||
data->state.expect100header =
|
||||
Curl_compareheader(ptr, STRCONST("Expect:"),
|
||||
STRCONST("100-continue"));
|
||||
if(data->req.upgr101 != UPGR101_INIT)
|
||||
return CURLE_OK;
|
||||
|
||||
/* For really small puts we don't use Expect: headers at all, and for
|
||||
the somewhat bigger ones we allow the app to disable it. Just make
|
||||
sure that the expect100header is always set to the preferred value
|
||||
here. */
|
||||
ptr = Curl_checkheaders(data, STRCONST("Expect"));
|
||||
if(ptr) {
|
||||
*announced_exp100 =
|
||||
Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue"));
|
||||
}
|
||||
else if(!data->state.disableexpect &&
|
||||
Curl_use_http_1_1plus(data, data->conn) &&
|
||||
(data->conn->httpversion < 20)) {
|
||||
/* if not doing HTTP 1.0 or version 2, or disabled explicitly, we add an
|
||||
Expect: 100-continue to the headers which actually speeds up post
|
||||
operations (as there is one packet coming back from the web server) */
|
||||
curl_off_t client_len = Curl_creader_client_length(data);
|
||||
if(client_len > EXPECT_100_THRESHOLD || client_len < 0) {
|
||||
result = Curl_dyn_addn(r, STRCONST("Expect: 100-continue\r\n"));
|
||||
if(result)
|
||||
return result;
|
||||
*announced_exp100 = TRUE;
|
||||
}
|
||||
else {
|
||||
curl_off_t client_len = Curl_creader_client_length(data);
|
||||
if(client_len > EXPECT_100_THRESHOLD || client_len < 0)
|
||||
return expect100(data, r);
|
||||
}
|
||||
}
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
|
@ -2193,6 +2184,7 @@ CURLcode Curl_http_req_complete(struct Curl_easy *data,
|
|||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
curl_off_t req_clen;
|
||||
bool announced_exp100 = FALSE;
|
||||
|
||||
DEBUGASSERT(data->conn);
|
||||
#ifndef USE_HYPER
|
||||
|
|
@ -2251,7 +2243,7 @@ CURLcode Curl_http_req_complete(struct Curl_easy *data,
|
|||
goto out;
|
||||
}
|
||||
}
|
||||
result = addexpect(data, r);
|
||||
result = addexpect(data, r, &announced_exp100);
|
||||
if(result)
|
||||
goto out;
|
||||
break;
|
||||
|
|
@ -2262,6 +2254,8 @@ CURLcode Curl_http_req_complete(struct Curl_easy *data,
|
|||
/* end of headers */
|
||||
result = Curl_dyn_addn(r, STRCONST("\r\n"));
|
||||
Curl_pgrsSetUploadSize(data, req_clen);
|
||||
if(announced_exp100)
|
||||
result = http_exp100_add_reader(data);
|
||||
|
||||
out:
|
||||
if(!result) {
|
||||
|
|
@ -3434,11 +3428,7 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
|
|||
k->headerline = 0; /* restart the header line counter */
|
||||
|
||||
/* if we did wait for this do enable write now! */
|
||||
if(k->exp100 > EXP100_SEND_DATA) {
|
||||
k->exp100 = EXP100_SEND_DATA;
|
||||
k->keepon |= KEEP_SEND;
|
||||
Curl_expire_done(data, EXPIRE_100_TIMEOUT);
|
||||
}
|
||||
Curl_http_exp100_got100(data);
|
||||
break;
|
||||
case 101:
|
||||
if(conn->httpversion == 11) {
|
||||
|
|
@ -3617,13 +3607,11 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
|
|||
* request body has been sent we stop sending and mark the
|
||||
* connection for closure after we've read the entire response.
|
||||
*/
|
||||
Curl_expire_done(data, EXPIRE_100_TIMEOUT);
|
||||
if(!Curl_req_done_sending(data)) {
|
||||
if((k->httpcode == 417) && data->state.expect100header) {
|
||||
if((k->httpcode == 417) && Curl_http_exp100_is_selected(data)) {
|
||||
/* 417 Expectation Failed - try again without the Expect
|
||||
header */
|
||||
if(!k->writebytecount &&
|
||||
k->exp100 == EXP100_AWAITING_CONTINUE) {
|
||||
if(!k->writebytecount && http_exp100_is_waiting(data)) {
|
||||
infof(data, "Got HTTP failure 417 while waiting for a 100");
|
||||
}
|
||||
else {
|
||||
|
|
@ -3641,10 +3629,7 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
|
|||
}
|
||||
else if(data->set.http_keep_sending_on_error) {
|
||||
infof(data, "HTTP error before end of send, keep sending");
|
||||
if(k->exp100 > EXP100_SEND_DATA) {
|
||||
k->exp100 = EXP100_SEND_DATA;
|
||||
k->keepon |= KEEP_SEND;
|
||||
}
|
||||
http_exp100_send_anyway(data);
|
||||
}
|
||||
else {
|
||||
infof(data, "HTTP error before end of send, stop sending");
|
||||
|
|
@ -3652,8 +3637,6 @@ static CURLcode http_rw_headers(struct Curl_easy *data,
|
|||
result = Curl_req_abort_sending(data);
|
||||
if(result)
|
||||
return result;
|
||||
if(data->state.expect100header)
|
||||
k->exp100 = EXP100_FAILED;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -4337,4 +4320,142 @@ void Curl_http_resp_free(struct http_resp *resp)
|
|||
}
|
||||
}
|
||||
|
||||
struct cr_exp100_ctx {
|
||||
struct Curl_creader super;
|
||||
struct curltime start; /* time started waiting */
|
||||
enum expect100 state;
|
||||
};
|
||||
|
||||
/* Expect: 100-continue client reader, blocking uploads */
|
||||
|
||||
static void http_exp100_continue(struct Curl_easy *data,
|
||||
struct Curl_creader *reader)
|
||||
{
|
||||
struct cr_exp100_ctx *ctx = reader->ctx;
|
||||
if(ctx->state > EXP100_SEND_DATA) {
|
||||
ctx->state = EXP100_SEND_DATA;
|
||||
data->req.keepon |= KEEP_SEND;
|
||||
data->req.keepon &= ~KEEP_SEND_TIMED;
|
||||
Curl_expire_done(data, EXPIRE_100_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
static CURLcode cr_exp100_read(struct Curl_easy *data,
|
||||
struct Curl_creader *reader,
|
||||
char *buf, size_t blen,
|
||||
size_t *nread, bool *eos)
|
||||
{
|
||||
struct cr_exp100_ctx *ctx = reader->ctx;
|
||||
timediff_t ms;
|
||||
|
||||
switch(ctx->state) {
|
||||
case EXP100_SENDING_REQUEST:
|
||||
/* We are now waiting for a reply from the server or
|
||||
* a timeout on our side */
|
||||
DEBUGF(infof(data, "cr_exp100_read, start AWAITING_CONTINUE"));
|
||||
ctx->state = EXP100_AWAITING_CONTINUE;
|
||||
ctx->start = Curl_now();
|
||||
Curl_expire(data, data->set.expect_100_timeout, EXPIRE_100_TIMEOUT);
|
||||
data->req.keepon &= ~KEEP_SEND;
|
||||
data->req.keepon |= KEEP_SEND_TIMED;
|
||||
*nread = 0;
|
||||
*eos = FALSE;
|
||||
return CURLE_OK;
|
||||
case EXP100_FAILED:
|
||||
DEBUGF(infof(data, "cr_exp100_read, expectation failed, error"));
|
||||
*nread = 0;
|
||||
*eos = FALSE;
|
||||
return CURLE_READ_ERROR;
|
||||
case EXP100_AWAITING_CONTINUE:
|
||||
ms = Curl_timediff(Curl_now(), ctx->start);
|
||||
if(ms < data->set.expect_100_timeout) {
|
||||
DEBUGF(infof(data, "cr_exp100_read, AWAITING_CONTINUE, not expired"));
|
||||
data->req.keepon &= ~KEEP_SEND;
|
||||
data->req.keepon |= KEEP_SEND_TIMED;
|
||||
*nread = 0;
|
||||
*eos = FALSE;
|
||||
return CURLE_OK;
|
||||
}
|
||||
/* we've waited long enough, continue anyway */
|
||||
http_exp100_continue(data, reader);
|
||||
infof(data, "Done waiting for 100-continue");
|
||||
FALLTHROUGH();
|
||||
default:
|
||||
DEBUGF(infof(data, "cr_exp100_read, pass through"));
|
||||
return Curl_creader_read(data, reader->next, buf, blen, nread, eos);
|
||||
}
|
||||
}
|
||||
|
||||
static void cr_exp100_done(struct Curl_easy *data,
|
||||
struct Curl_creader *reader, int premature)
|
||||
{
|
||||
struct cr_exp100_ctx *ctx = reader->ctx;
|
||||
ctx->state = premature? EXP100_FAILED : EXP100_SEND_DATA;
|
||||
data->req.keepon &= ~KEEP_SEND_TIMED;
|
||||
Curl_expire_done(data, EXPIRE_100_TIMEOUT);
|
||||
}
|
||||
|
||||
static const struct Curl_crtype cr_exp100 = {
|
||||
"cr-exp100",
|
||||
Curl_creader_def_init,
|
||||
cr_exp100_read,
|
||||
Curl_creader_def_close,
|
||||
Curl_creader_def_needs_rewind,
|
||||
Curl_creader_def_total_length,
|
||||
Curl_creader_def_resume_from,
|
||||
Curl_creader_def_rewind,
|
||||
Curl_creader_def_unpause,
|
||||
cr_exp100_done,
|
||||
sizeof(struct cr_exp100_ctx)
|
||||
};
|
||||
|
||||
static CURLcode http_exp100_add_reader(struct Curl_easy *data)
|
||||
{
|
||||
struct Curl_creader *reader = NULL;
|
||||
CURLcode result;
|
||||
|
||||
result = Curl_creader_create(&reader, data, &cr_exp100,
|
||||
CURL_CR_PROTOCOL);
|
||||
if(!result)
|
||||
result = Curl_creader_add(data, reader);
|
||||
if(!result) {
|
||||
struct cr_exp100_ctx *ctx = reader->ctx;
|
||||
ctx->state = EXP100_SENDING_REQUEST;
|
||||
}
|
||||
|
||||
if(result && reader)
|
||||
Curl_creader_free(data, reader);
|
||||
return result;
|
||||
}
|
||||
|
||||
void Curl_http_exp100_got100(struct Curl_easy *data)
|
||||
{
|
||||
struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100);
|
||||
if(r)
|
||||
http_exp100_continue(data, r);
|
||||
}
|
||||
|
||||
static bool http_exp100_is_waiting(struct Curl_easy *data)
|
||||
{
|
||||
struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100);
|
||||
if(r) {
|
||||
struct cr_exp100_ctx *ctx = r->ctx;
|
||||
return (ctx->state == EXP100_AWAITING_CONTINUE);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void http_exp100_send_anyway(struct Curl_easy *data)
|
||||
{
|
||||
struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100);
|
||||
if(r)
|
||||
http_exp100_continue(data, r);
|
||||
}
|
||||
|
||||
bool Curl_http_exp100_is_selected(struct Curl_easy *data)
|
||||
{
|
||||
struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100);
|
||||
return r? TRUE : FALSE;
|
||||
}
|
||||
|
||||
#endif /* CURL_DISABLE_HTTP */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue