struct cf_h2_ctx {
nghttp2_session *h2;
uint32_t max_concurrent_streams;
- bool enable_push;
/* The easy handle used in the current filter call, cleared at return */
struct cf_call_data call_data;
int32_t pause_stream_id; /* stream ID which paused
nghttp2_session_mem_recv */
size_t drain_total; /* sum of all stream's UrlState.drain */
+ int32_t goaway_error;
+ int32_t last_stream_id;
+ BIT(goaway);
+ BIT(enable_push);
};
/* How to access `call_data` from a cf_h2 filter */
ctx->max_concurrent_streams = nghttp2_session_get_remote_settings(
session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
ctx->enable_push = nghttp2_session_get_remote_settings(
- session, NGHTTP2_SETTINGS_ENABLE_PUSH);
+ session, NGHTTP2_SETTINGS_ENABLE_PUSH) != 0;
DEBUGF(LOG_CF(data, cf, "MAX_CONCURRENT_STREAMS == %d",
ctx->max_concurrent_streams));
DEBUGF(LOG_CF(data, cf, "ENABLE_PUSH == %s",
break;
}
case NGHTTP2_GOAWAY:
+ ctx->goaway = TRUE;
+ ctx->goaway_error = frame->goaway.error_code;
+ ctx->last_stream_id = frame->goaway.last_stream_id;
if(data) {
infof(data, "recveived GOAWAY, error=%d, last_stream=%u",
- frame->goaway.error_code, frame->goaway.last_stream_id);
+ ctx->goaway_error, ctx->last_stream_id);
multi_connchanged(data->multi);
}
break;
break;
case NGHTTP2_RST_STREAM:
DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv RST", stream_id));
+ stream->closed = TRUE;
stream->reset = TRUE;
break;
case NGHTTP2_WINDOW_UPDATE:
CF_DATA_SAVE(save, cf, data);
+ /* IFF transfer was RST by server or
+ * we got a final GOAWAY and the last processed stream ID, as announced
+ * by the server, is lower than this stream's ID, we will never see
+ * anything more for this stream, so give up */
+ if(stream->reset ||
+ (ctx->goaway && ctx->goaway_error &&
+ ctx->last_stream_id < stream->stream_id)) {
+ *err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR;
+ nread = -1;
+ goto out;
+ }
+
if(should_close_session(ctx)) {
DEBUGF(LOG_CF(data, cf, "http2_recv: nothing to do in this session"));
if(cf->conn->bits.close) {
goto out;
}
else if(nread == 0) {
- if(!stream->closed) {
+ if(!stream->closed || stream->reset) {
/* This will happen when the server or proxy server is SIGKILLed
during data transfer. We should emit an error since our data
received may be incomplete. */
pytest.skip("h3 not supported")
if proto == 'h3' and env.curl_uses_lib('quiche'):
pytest.skip("quiche not reliable, sometimes reports success")
- count = 5
+ count = 20
curl = CurlClient(env=env)
urln = f'https://{env.authority_for(env.domain1, proto)}' \
f'/curltest/tweak?id=[0-{count - 1}]'\
assert len(r.stats) == count, f'did not get all stats: {r}'
invalid_stats = []
for idx, s in enumerate(r.stats):
- if 'exitcode' not in s or s['exitcode'] not in [18, 56, 92]:
+ if 'exitcode' not in s or s['exitcode'] not in [18, 56, 92, 95]:
invalid_stats.append(f'request {idx} exit with {s["exitcode"]}\n{s}')
assert len(invalid_stats) == 0, f'failed: {invalid_stats}'