Implement HttpProtocolException for HTTP/3 - #72095

Merged
rzikm merged 10 commits into
dotnet:mainfrom
rzikm:use-protocol-exception
Jul 14, 2022
Merged

Implement HttpProtocolException for HTTP/3#72095
rzikm merged 10 commits into
dotnet:mainfrom
rzikm:use-protocol-exception

Conversation

@rzikm

Copy link
Copy Markdown
Member

Follow-up on #71432.

Closes#70684.

@ghostghost assigned rzikmJul 13, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

Follow-up on #71432.

Closes #70684.

Author:rzikm
Assignees:-
Labels:

area-System.Net.Http

Milestone:-

@rzikm
rzikm requested a review from antonfirsovJuly 13, 2022 13:33
@antonfirsov

Copy link
Copy Markdown
Contributor

We have plenty of tests for HTTP/2 exercising error scenarios, for example:

publicasyncTaskHttp2_StreamResetByServerBeforeHeadersSent_RequestFails()
{
using(Http2LoopbackServerserver=Http2LoopbackServer.CreateServer())
using(HttpClientclient=CreateHttpClient())
{
Task<HttpResponseMessage>sendTask=client.GetAsync(server.Address);
Http2LoopbackConnectionconnection=awaitserver.EstablishConnectionAsync();
intstreamId=awaitconnection.ReadRequestHeaderAsync();
// Send a reset stream frame so that the stream moves to a terminal state.
RstStreamFrameresetStream=newRstStreamFrame(FrameFlags.None,(int)ProtocolErrors.INTERNAL_ERROR,streamId);
awaitconnection.WriteFrameAsync(resetStream);
awaitAssertProtocolErrorAsync(sendTask,ProtocolErrors.INTERNAL_ERROR);
}
}

Is it possible to cover at least a few HTTP/3 cases to verify that we are throwing this exception (including at least one response stream case)?

Priority = 0b00100000,

ValidBits = 0b00101101
ValidBits = 0b00101101

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this formatting alignment intentionally removed?

@ManickaP

ManickaP commented Jul 13, 2022

Copy link
Copy Markdown
Member

Please don't merge this before #71969 unless you're 100% sure it will not create conflicts. I really need to get that PR in main.

@rzikm
rzikm requested a review from a teamJuly 14, 2022 11:37
// Our stream was reset.
Exception? abortException = _connection.AbortException;
throw new HttpRequestException(SR.net_http_client_execution_error, abortException ?? ex);
throw new HttpRequestException(SR.net_http_client_execution_error, HttpProtocolException.CreateHttp3StreamException(code));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it ok that we are ignoring both _connection.AbortException and ex now?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is any value of including ex in the HttpProtocolException (it will always be the same and the error code is already in HttpProtocolException.

To tell the truth, I am not 100% sure what the right behavior here is. I didn't notice anything in the RFC which would let server close the stream with other error codes than the two handled above. I would consider it protocol violation and tear down the connection, but the RFC does not AFAIK prohibit using any other code, so we should just pass it by.

As for the _connection.AbortException, there is no harm in checking first and prioritizing it if there is some already. This branch should be pretty rare anyway as explained above.

Exception abortException = _connection.Abort(ex);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, abortException));
case Http3ConnectionException:
case HttpProtocolException:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Judging by CreateHttp3StreamException in HttpProtocolException it seems to me like HttpProtocolException may not always be connection-level. Is that right? This line does not seem as equivalent change then. Or am I missing something?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HttpProtocolException thrown from stream is not on a code path which would affect this. In other words, only connection-level exceptions can be encountered here

Comment on lines +37 to +38
_output.WriteLine(outerEx.InnerException.Message);
HttpProtocolException protocolEx = Assert.IsType<HttpProtocolException>(outerEx.InnerException);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be this instead?

Suggested change
_output.WriteLine(outerEx.InnerException.Message);
HttpProtocolExceptionprotocolEx=Assert.IsType<HttpProtocolException>(outerEx.InnerException);
HttpProtocolExceptionprotocolEx=Assert.IsType<HttpProtocolException>(outerEx.InnerException);
_output.WriteLine(protocolEx.Message);

Also, if I'm wrong and it's for diagnostics purposes in case it was not a HttpProtocolException, would it be better to include all the info about exceptions, even the top one? Meaning, should we log ToString instead of just Message

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied it from HttpClientHandlerTest.Http2 implementation, I can change it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was somewhat accidental in my PR, we don't have to print anything, though it's informative for manual validation of HttpProtocolException's message.

@rzikm
rzikmforce-pushed the use-protocol-exception branch from d467805 to 9e4324bCompareJuly 14, 2022 13:50
@rzikm
rzikm removed request for lewing and radicalJuly 14, 2022 13:50
await new[] { clientTask, serverTask }.WhenAllOrAnyFailed(20_000);
}


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

@@ -1196,7 +1199,7 @@ private void HandleReadResponseContentException(Exception ex, CancellationToken
// Our connection was reset. Start aborting the connection.
Exception abortException = _connection.Abort(ex);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, abortException));

@antonfirsovantonfirsovJul 14, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering why are we throwing IOException here? Aren't these errors supposed to surface as HttpProtocolException from HttpContent's Http3ReadStream (which seems to delegate to this method)?

HttpProtocolException protocolEx = Assert.IsType<HttpProtocolException>(outerEx.InnerException);
_output.WriteLine(protocolEx.Message);
Assert.Equal(errorCode, protocolEx.ErrorCode);
}

@antonfirsovantonfirsovJul 14, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add yet another test (or a parameteric variant of existing tests), that validates the exception thrown from the response content stream. See AssertProtocolErrorForIOExceptionAsync in http2 tests (I should have renamed that method ...).

@rzikm
rzikm requested a review from antonfirsovJuly 14, 2022 16:00

@antonfirsovantonfirsov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@rzikm
rzikm merged commit e2e9df7 into dotnet:mainJul 14, 2022
@rzikm

Copy link
Copy Markdown
MemberAuthor

/backport to release/7.0-preview7

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/7.0-preview7: https://github.com/dotnet/runtime/actions/runs/2672573752

@karelzkarelz added this to the 7.0.0 milestone Jul 19, 2022
@ghostghost locked as resolved and limited conversation to collaborators Aug 18, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Exposing HTTP/2 and HTTP/3 protocol error codes

6 participants

@rzikm@antonfirsov@ManickaP@stephentoub@CarnaViire@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Implement HttpProtocolException for HTTP/3 - #72095

Merged
rzikm merged 10 commits into
dotnet:mainfrom
rzikm:use-protocol-exception
Jul 14, 2022
Merged

Implement HttpProtocolException for HTTP/3#72095
rzikm merged 10 commits into
dotnet:mainfrom
rzikm:use-protocol-exception

Conversation

@rzikm

Copy link
Copy Markdown
Member

Follow-up on #71432.

Closes#70684.

@ghostghost assigned rzikmJul 13, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

Follow-up on #71432.

Closes #70684.

Author:rzikm
Assignees:-
Labels:

area-System.Net.Http

Milestone:-

@rzikm
rzikm requested a review from antonfirsovJuly 13, 2022 13:33
@antonfirsov

Copy link
Copy Markdown
Contributor

We have plenty of tests for HTTP/2 exercising error scenarios, for example:

publicasyncTaskHttp2_StreamResetByServerBeforeHeadersSent_RequestFails()
{
using(Http2LoopbackServerserver=Http2LoopbackServer.CreateServer())
using(HttpClientclient=CreateHttpClient())
{
Task<HttpResponseMessage>sendTask=client.GetAsync(server.Address);
Http2LoopbackConnectionconnection=awaitserver.EstablishConnectionAsync();
intstreamId=awaitconnection.ReadRequestHeaderAsync();
// Send a reset stream frame so that the stream moves to a terminal state.
RstStreamFrameresetStream=newRstStreamFrame(FrameFlags.None,(int)ProtocolErrors.INTERNAL_ERROR,streamId);
awaitconnection.WriteFrameAsync(resetStream);
awaitAssertProtocolErrorAsync(sendTask,ProtocolErrors.INTERNAL_ERROR);
}
}

Is it possible to cover at least a few HTTP/3 cases to verify that we are throwing this exception (including at least one response stream case)?

Priority = 0b00100000,

ValidBits = 0b00101101
ValidBits = 0b00101101

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this formatting alignment intentionally removed?

@ManickaP

ManickaP commented Jul 13, 2022

Copy link
Copy Markdown
Member

Please don't merge this before #71969 unless you're 100% sure it will not create conflicts. I really need to get that PR in main.

@rzikm
rzikm requested a review from a teamJuly 14, 2022 11:37
// Our stream was reset.
Exception? abortException = _connection.AbortException;
throw new HttpRequestException(SR.net_http_client_execution_error, abortException ?? ex);
throw new HttpRequestException(SR.net_http_client_execution_error, HttpProtocolException.CreateHttp3StreamException(code));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it ok that we are ignoring both _connection.AbortException and ex now?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is any value of including ex in the HttpProtocolException (it will always be the same and the error code is already in HttpProtocolException.

To tell the truth, I am not 100% sure what the right behavior here is. I didn't notice anything in the RFC which would let server close the stream with other error codes than the two handled above. I would consider it protocol violation and tear down the connection, but the RFC does not AFAIK prohibit using any other code, so we should just pass it by.

As for the _connection.AbortException, there is no harm in checking first and prioritizing it if there is some already. This branch should be pretty rare anyway as explained above.

Exception abortException = _connection.Abort(ex);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, abortException));
case Http3ConnectionException:
case HttpProtocolException:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Judging by CreateHttp3StreamException in HttpProtocolException it seems to me like HttpProtocolException may not always be connection-level. Is that right? This line does not seem as equivalent change then. Or am I missing something?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HttpProtocolException thrown from stream is not on a code path which would affect this. In other words, only connection-level exceptions can be encountered here

Comment on lines +37 to +38
_output.WriteLine(outerEx.InnerException.Message);
HttpProtocolException protocolEx = Assert.IsType<HttpProtocolException>(outerEx.InnerException);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be this instead?

Suggested change
_output.WriteLine(outerEx.InnerException.Message);
HttpProtocolExceptionprotocolEx=Assert.IsType<HttpProtocolException>(outerEx.InnerException);
HttpProtocolExceptionprotocolEx=Assert.IsType<HttpProtocolException>(outerEx.InnerException);
_output.WriteLine(protocolEx.Message);

Also, if I'm wrong and it's for diagnostics purposes in case it was not a HttpProtocolException, would it be better to include all the info about exceptions, even the top one? Meaning, should we log ToString instead of just Message

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied it from HttpClientHandlerTest.Http2 implementation, I can change it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was somewhat accidental in my PR, we don't have to print anything, though it's informative for manual validation of HttpProtocolException's message.

@rzikm
rzikmforce-pushed the use-protocol-exception branch from d467805 to 9e4324bCompareJuly 14, 2022 13:50
@rzikm
rzikm removed request for lewing and radicalJuly 14, 2022 13:50
await new[] { clientTask, serverTask }.WhenAllOrAnyFailed(20_000);
}


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

@@ -1196,7 +1199,7 @@ private void HandleReadResponseContentException(Exception ex, CancellationToken
// Our connection was reset. Start aborting the connection.
Exception abortException = _connection.Abort(ex);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, abortException));

@antonfirsovantonfirsovJul 14, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering why are we throwing IOException here? Aren't these errors supposed to surface as HttpProtocolException from HttpContent's Http3ReadStream (which seems to delegate to this method)?

HttpProtocolException protocolEx = Assert.IsType<HttpProtocolException>(outerEx.InnerException);
_output.WriteLine(protocolEx.Message);
Assert.Equal(errorCode, protocolEx.ErrorCode);
}

@antonfirsovantonfirsovJul 14, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add yet another test (or a parameteric variant of existing tests), that validates the exception thrown from the response content stream. See AssertProtocolErrorForIOExceptionAsync in http2 tests (I should have renamed that method ...).

@rzikm
rzikm requested a review from antonfirsovJuly 14, 2022 16:00

@antonfirsovantonfirsov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@rzikm
rzikm merged commit e2e9df7 into dotnet:mainJul 14, 2022
@rzikm

Copy link
Copy Markdown
MemberAuthor

/backport to release/7.0-preview7

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/7.0-preview7: https://github.com/dotnet/runtime/actions/runs/2672573752

@karelzkarelz added this to the 7.0.0 milestone Jul 19, 2022
@ghostghost locked as resolved and limited conversation to collaborators Aug 18, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Exposing HTTP/2 and HTTP/3 protocol error codes

6 participants

@rzikm@antonfirsov@ManickaP@stephentoub@CarnaViire@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Implement HttpProtocolException for HTTP/3 - #72095

Merged
rzikm merged 10 commits into
dotnet:mainfrom
rzikm:use-protocol-exception
Jul 14, 2022
Merged

Implement HttpProtocolException for HTTP/3#72095
rzikm merged 10 commits into
dotnet:mainfrom
rzikm:use-protocol-exception

Conversation

@rzikm

Copy link
Copy Markdown
Member

Follow-up on #71432.

Closes#70684.

@ghostghost assigned rzikmJul 13, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

Follow-up on #71432.

Closes #70684.

Author:rzikm
Assignees:-
Labels:

area-System.Net.Http

Milestone:-

@rzikm
rzikm requested a review from antonfirsovJuly 13, 2022 13:33
@antonfirsov

Copy link
Copy Markdown
Contributor

We have plenty of tests for HTTP/2 exercising error scenarios, for example:

publicasyncTaskHttp2_StreamResetByServerBeforeHeadersSent_RequestFails()
{
using(Http2LoopbackServerserver=Http2LoopbackServer.CreateServer())
using(HttpClientclient=CreateHttpClient())
{
Task<HttpResponseMessage>sendTask=client.GetAsync(server.Address);
Http2LoopbackConnectionconnection=awaitserver.EstablishConnectionAsync();
intstreamId=awaitconnection.ReadRequestHeaderAsync();
// Send a reset stream frame so that the stream moves to a terminal state.
RstStreamFrameresetStream=newRstStreamFrame(FrameFlags.None,(int)ProtocolErrors.INTERNAL_ERROR,streamId);
awaitconnection.WriteFrameAsync(resetStream);
awaitAssertProtocolErrorAsync(sendTask,ProtocolErrors.INTERNAL_ERROR);
}
}

Is it possible to cover at least a few HTTP/3 cases to verify that we are throwing this exception (including at least one response stream case)?

Priority = 0b00100000,

ValidBits = 0b00101101
ValidBits = 0b00101101

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this formatting alignment intentionally removed?

@ManickaP

ManickaP commented Jul 13, 2022

Copy link
Copy Markdown
Member

Please don't merge this before #71969 unless you're 100% sure it will not create conflicts. I really need to get that PR in main.

@rzikm
rzikm requested a review from a teamJuly 14, 2022 11:37
// Our stream was reset.
Exception? abortException = _connection.AbortException;
throw new HttpRequestException(SR.net_http_client_execution_error, abortException ?? ex);
throw new HttpRequestException(SR.net_http_client_execution_error, HttpProtocolException.CreateHttp3StreamException(code));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it ok that we are ignoring both _connection.AbortException and ex now?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is any value of including ex in the HttpProtocolException (it will always be the same and the error code is already in HttpProtocolException.

To tell the truth, I am not 100% sure what the right behavior here is. I didn't notice anything in the RFC which would let server close the stream with other error codes than the two handled above. I would consider it protocol violation and tear down the connection, but the RFC does not AFAIK prohibit using any other code, so we should just pass it by.

As for the _connection.AbortException, there is no harm in checking first and prioritizing it if there is some already. This branch should be pretty rare anyway as explained above.

Exception abortException = _connection.Abort(ex);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, abortException));
case Http3ConnectionException:
case HttpProtocolException:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Judging by CreateHttp3StreamException in HttpProtocolException it seems to me like HttpProtocolException may not always be connection-level. Is that right? This line does not seem as equivalent change then. Or am I missing something?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HttpProtocolException thrown from stream is not on a code path which would affect this. In other words, only connection-level exceptions can be encountered here

Comment on lines +37 to +38
_output.WriteLine(outerEx.InnerException.Message);
HttpProtocolException protocolEx = Assert.IsType<HttpProtocolException>(outerEx.InnerException);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be this instead?

Suggested change
_output.WriteLine(outerEx.InnerException.Message);
HttpProtocolExceptionprotocolEx=Assert.IsType<HttpProtocolException>(outerEx.InnerException);
HttpProtocolExceptionprotocolEx=Assert.IsType<HttpProtocolException>(outerEx.InnerException);
_output.WriteLine(protocolEx.Message);

Also, if I'm wrong and it's for diagnostics purposes in case it was not a HttpProtocolException, would it be better to include all the info about exceptions, even the top one? Meaning, should we log ToString instead of just Message

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied it from HttpClientHandlerTest.Http2 implementation, I can change it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was somewhat accidental in my PR, we don't have to print anything, though it's informative for manual validation of HttpProtocolException's message.

@rzikm
rzikmforce-pushed the use-protocol-exception branch from d467805 to 9e4324bCompareJuly 14, 2022 13:50
@rzikm
rzikm removed request for lewing and radicalJuly 14, 2022 13:50
await new[] { clientTask, serverTask }.WhenAllOrAnyFailed(20_000);
}


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

@@ -1196,7 +1199,7 @@ private void HandleReadResponseContentException(Exception ex, CancellationToken
// Our connection was reset. Start aborting the connection.
Exception abortException = _connection.Abort(ex);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, abortException));

@antonfirsovantonfirsovJul 14, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering why are we throwing IOException here? Aren't these errors supposed to surface as HttpProtocolException from HttpContent's Http3ReadStream (which seems to delegate to this method)?

HttpProtocolException protocolEx = Assert.IsType<HttpProtocolException>(outerEx.InnerException);
_output.WriteLine(protocolEx.Message);
Assert.Equal(errorCode, protocolEx.ErrorCode);
}

@antonfirsovantonfirsovJul 14, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add yet another test (or a parameteric variant of existing tests), that validates the exception thrown from the response content stream. See AssertProtocolErrorForIOExceptionAsync in http2 tests (I should have renamed that method ...).

@rzikm
rzikm requested a review from antonfirsovJuly 14, 2022 16:00

@antonfirsovantonfirsov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@rzikm
rzikm merged commit e2e9df7 into dotnet:mainJul 14, 2022
@rzikm

Copy link
Copy Markdown
MemberAuthor

/backport to release/7.0-preview7

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/7.0-preview7: https://github.com/dotnet/runtime/actions/runs/2672573752

@karelzkarelz added this to the 7.0.0 milestone Jul 19, 2022
@ghostghost locked as resolved and limited conversation to collaborators Aug 18, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Exposing HTTP/2 and HTTP/3 protocol error codes

6 participants

@rzikm@antonfirsov@ManickaP@stephentoub@CarnaViire@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Implement HttpProtocolException for HTTP/3 - #72095

Merged
rzikm merged 10 commits into
dotnet:mainfrom
rzikm:use-protocol-exception
Jul 14, 2022
Merged

Implement HttpProtocolException for HTTP/3#72095
rzikm merged 10 commits into
dotnet:mainfrom
rzikm:use-protocol-exception

Conversation

@rzikm

Copy link
Copy Markdown
Member

Follow-up on #71432.

Closes#70684.

@ghostghost assigned rzikmJul 13, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

Follow-up on #71432.

Closes #70684.

Author:rzikm
Assignees:-
Labels:

area-System.Net.Http

Milestone:-

@rzikm
rzikm requested a review from antonfirsovJuly 13, 2022 13:33
@antonfirsov

Copy link
Copy Markdown
Contributor

We have plenty of tests for HTTP/2 exercising error scenarios, for example:

publicasyncTaskHttp2_StreamResetByServerBeforeHeadersSent_RequestFails()
{
using(Http2LoopbackServerserver=Http2LoopbackServer.CreateServer())
using(HttpClientclient=CreateHttpClient())
{
Task<HttpResponseMessage>sendTask=client.GetAsync(server.Address);
Http2LoopbackConnectionconnection=awaitserver.EstablishConnectionAsync();
intstreamId=awaitconnection.ReadRequestHeaderAsync();
// Send a reset stream frame so that the stream moves to a terminal state.
RstStreamFrameresetStream=newRstStreamFrame(FrameFlags.None,(int)ProtocolErrors.INTERNAL_ERROR,streamId);
awaitconnection.WriteFrameAsync(resetStream);
awaitAssertProtocolErrorAsync(sendTask,ProtocolErrors.INTERNAL_ERROR);
}
}

Is it possible to cover at least a few HTTP/3 cases to verify that we are throwing this exception (including at least one response stream case)?

Priority = 0b00100000,

ValidBits = 0b00101101
ValidBits = 0b00101101

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this formatting alignment intentionally removed?

@ManickaP

ManickaP commented Jul 13, 2022

Copy link
Copy Markdown
Member

Please don't merge this before #71969 unless you're 100% sure it will not create conflicts. I really need to get that PR in main.

@rzikm
rzikm requested a review from a teamJuly 14, 2022 11:37
// Our stream was reset.
Exception? abortException = _connection.AbortException;
throw new HttpRequestException(SR.net_http_client_execution_error, abortException ?? ex);
throw new HttpRequestException(SR.net_http_client_execution_error, HttpProtocolException.CreateHttp3StreamException(code));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it ok that we are ignoring both _connection.AbortException and ex now?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is any value of including ex in the HttpProtocolException (it will always be the same and the error code is already in HttpProtocolException.

To tell the truth, I am not 100% sure what the right behavior here is. I didn't notice anything in the RFC which would let server close the stream with other error codes than the two handled above. I would consider it protocol violation and tear down the connection, but the RFC does not AFAIK prohibit using any other code, so we should just pass it by.

As for the _connection.AbortException, there is no harm in checking first and prioritizing it if there is some already. This branch should be pretty rare anyway as explained above.

Exception abortException = _connection.Abort(ex);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, abortException));
case Http3ConnectionException:
case HttpProtocolException:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Judging by CreateHttp3StreamException in HttpProtocolException it seems to me like HttpProtocolException may not always be connection-level. Is that right? This line does not seem as equivalent change then. Or am I missing something?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HttpProtocolException thrown from stream is not on a code path which would affect this. In other words, only connection-level exceptions can be encountered here

Comment on lines +37 to +38
_output.WriteLine(outerEx.InnerException.Message);
HttpProtocolException protocolEx = Assert.IsType<HttpProtocolException>(outerEx.InnerException);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be this instead?

Suggested change
_output.WriteLine(outerEx.InnerException.Message);
HttpProtocolExceptionprotocolEx=Assert.IsType<HttpProtocolException>(outerEx.InnerException);
HttpProtocolExceptionprotocolEx=Assert.IsType<HttpProtocolException>(outerEx.InnerException);
_output.WriteLine(protocolEx.Message);

Also, if I'm wrong and it's for diagnostics purposes in case it was not a HttpProtocolException, would it be better to include all the info about exceptions, even the top one? Meaning, should we log ToString instead of just Message

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied it from HttpClientHandlerTest.Http2 implementation, I can change it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was somewhat accidental in my PR, we don't have to print anything, though it's informative for manual validation of HttpProtocolException's message.

@rzikm
rzikmforce-pushed the use-protocol-exception branch from d467805 to 9e4324bCompareJuly 14, 2022 13:50
@rzikm
rzikm removed request for lewing and radicalJuly 14, 2022 13:50
await new[] { clientTask, serverTask }.WhenAllOrAnyFailed(20_000);
}


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

@@ -1196,7 +1199,7 @@ private void HandleReadResponseContentException(Exception ex, CancellationToken
// Our connection was reset. Start aborting the connection.
Exception abortException = _connection.Abort(ex);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, abortException));

@antonfirsovantonfirsovJul 14, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering why are we throwing IOException here? Aren't these errors supposed to surface as HttpProtocolException from HttpContent's Http3ReadStream (which seems to delegate to this method)?

HttpProtocolException protocolEx = Assert.IsType<HttpProtocolException>(outerEx.InnerException);
_output.WriteLine(protocolEx.Message);
Assert.Equal(errorCode, protocolEx.ErrorCode);
}

@antonfirsovantonfirsovJul 14, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add yet another test (or a parameteric variant of existing tests), that validates the exception thrown from the response content stream. See AssertProtocolErrorForIOExceptionAsync in http2 tests (I should have renamed that method ...).

@rzikm
rzikm requested a review from antonfirsovJuly 14, 2022 16:00

@antonfirsovantonfirsov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@rzikm
rzikm merged commit e2e9df7 into dotnet:mainJul 14, 2022
@rzikm

Copy link
Copy Markdown
MemberAuthor

/backport to release/7.0-preview7

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/7.0-preview7: https://github.com/dotnet/runtime/actions/runs/2672573752

@karelzkarelz added this to the 7.0.0 milestone Jul 19, 2022
@ghostghost locked as resolved and limited conversation to collaborators Aug 18, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Exposing HTTP/2 and HTTP/3 protocol error codes

6 participants

@rzikm@antonfirsov@ManickaP@stephentoub@CarnaViire@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Implement HttpProtocolException for HTTP/3 - #72095

Merged
rzikm merged 10 commits into
dotnet:mainfrom
rzikm:use-protocol-exception
Jul 14, 2022
Merged

Implement HttpProtocolException for HTTP/3#72095
rzikm merged 10 commits into
dotnet:mainfrom
rzikm:use-protocol-exception

Conversation

@rzikm

Copy link
Copy Markdown
Member

Follow-up on #71432.

Closes#70684.

@ghostghost assigned rzikmJul 13, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

Follow-up on #71432.

Closes #70684.

Author:rzikm
Assignees:-
Labels:

area-System.Net.Http

Milestone:-

@rzikm
rzikm requested a review from antonfirsovJuly 13, 2022 13:33
@antonfirsov

Copy link
Copy Markdown
Contributor

We have plenty of tests for HTTP/2 exercising error scenarios, for example:

publicasyncTaskHttp2_StreamResetByServerBeforeHeadersSent_RequestFails()
{
using(Http2LoopbackServerserver=Http2LoopbackServer.CreateServer())
using(HttpClientclient=CreateHttpClient())
{
Task<HttpResponseMessage>sendTask=client.GetAsync(server.Address);
Http2LoopbackConnectionconnection=awaitserver.EstablishConnectionAsync();
intstreamId=awaitconnection.ReadRequestHeaderAsync();
// Send a reset stream frame so that the stream moves to a terminal state.
RstStreamFrameresetStream=newRstStreamFrame(FrameFlags.None,(int)ProtocolErrors.INTERNAL_ERROR,streamId);
awaitconnection.WriteFrameAsync(resetStream);
awaitAssertProtocolErrorAsync(sendTask,ProtocolErrors.INTERNAL_ERROR);
}
}

Is it possible to cover at least a few HTTP/3 cases to verify that we are throwing this exception (including at least one response stream case)?

Priority = 0b00100000,

ValidBits = 0b00101101
ValidBits = 0b00101101

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this formatting alignment intentionally removed?

@ManickaP

ManickaP commented Jul 13, 2022

Copy link
Copy Markdown
Member

Please don't merge this before #71969 unless you're 100% sure it will not create conflicts. I really need to get that PR in main.

@rzikm
rzikm requested a review from a teamJuly 14, 2022 11:37
// Our stream was reset.
Exception? abortException = _connection.AbortException;
throw new HttpRequestException(SR.net_http_client_execution_error, abortException ?? ex);
throw new HttpRequestException(SR.net_http_client_execution_error, HttpProtocolException.CreateHttp3StreamException(code));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it ok that we are ignoring both _connection.AbortException and ex now?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is any value of including ex in the HttpProtocolException (it will always be the same and the error code is already in HttpProtocolException.

To tell the truth, I am not 100% sure what the right behavior here is. I didn't notice anything in the RFC which would let server close the stream with other error codes than the two handled above. I would consider it protocol violation and tear down the connection, but the RFC does not AFAIK prohibit using any other code, so we should just pass it by.

As for the _connection.AbortException, there is no harm in checking first and prioritizing it if there is some already. This branch should be pretty rare anyway as explained above.

Exception abortException = _connection.Abort(ex);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, abortException));
case Http3ConnectionException:
case HttpProtocolException:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Judging by CreateHttp3StreamException in HttpProtocolException it seems to me like HttpProtocolException may not always be connection-level. Is that right? This line does not seem as equivalent change then. Or am I missing something?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HttpProtocolException thrown from stream is not on a code path which would affect this. In other words, only connection-level exceptions can be encountered here

Comment on lines +37 to +38
_output.WriteLine(outerEx.InnerException.Message);
HttpProtocolException protocolEx = Assert.IsType<HttpProtocolException>(outerEx.InnerException);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be this instead?

Suggested change
_output.WriteLine(outerEx.InnerException.Message);
HttpProtocolExceptionprotocolEx=Assert.IsType<HttpProtocolException>(outerEx.InnerException);
HttpProtocolExceptionprotocolEx=Assert.IsType<HttpProtocolException>(outerEx.InnerException);
_output.WriteLine(protocolEx.Message);

Also, if I'm wrong and it's for diagnostics purposes in case it was not a HttpProtocolException, would it be better to include all the info about exceptions, even the top one? Meaning, should we log ToString instead of just Message

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied it from HttpClientHandlerTest.Http2 implementation, I can change it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was somewhat accidental in my PR, we don't have to print anything, though it's informative for manual validation of HttpProtocolException's message.

@rzikm
rzikmforce-pushed the use-protocol-exception branch from d467805 to 9e4324bCompareJuly 14, 2022 13:50
@rzikm
rzikm removed request for lewing and radicalJuly 14, 2022 13:50
await new[] { clientTask, serverTask }.WhenAllOrAnyFailed(20_000);
}


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

@@ -1196,7 +1199,7 @@ private void HandleReadResponseContentException(Exception ex, CancellationToken
// Our connection was reset. Start aborting the connection.
Exception abortException = _connection.Abort(ex);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, abortException));

@antonfirsovantonfirsovJul 14, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering why are we throwing IOException here? Aren't these errors supposed to surface as HttpProtocolException from HttpContent's Http3ReadStream (which seems to delegate to this method)?

HttpProtocolException protocolEx = Assert.IsType<HttpProtocolException>(outerEx.InnerException);
_output.WriteLine(protocolEx.Message);
Assert.Equal(errorCode, protocolEx.ErrorCode);
}

@antonfirsovantonfirsovJul 14, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add yet another test (or a parameteric variant of existing tests), that validates the exception thrown from the response content stream. See AssertProtocolErrorForIOExceptionAsync in http2 tests (I should have renamed that method ...).

@rzikm
rzikm requested a review from antonfirsovJuly 14, 2022 16:00

@antonfirsovantonfirsov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@rzikm
rzikm merged commit e2e9df7 into dotnet:mainJul 14, 2022
@rzikm

Copy link
Copy Markdown
MemberAuthor

/backport to release/7.0-preview7

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/7.0-preview7: https://github.com/dotnet/runtime/actions/runs/2672573752

@karelzkarelz added this to the 7.0.0 milestone Jul 19, 2022
@ghostghost locked as resolved and limited conversation to collaborators Aug 18, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Exposing HTTP/2 and HTTP/3 protocol error codes

6 participants

@rzikm@antonfirsov@ManickaP@stephentoub@CarnaViire@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Implement HttpProtocolException for HTTP/3 - #72095

Merged
rzikm merged 10 commits into
dotnet:mainfrom
rzikm:use-protocol-exception
Jul 14, 2022
Merged

Implement HttpProtocolException for HTTP/3#72095
rzikm merged 10 commits into
dotnet:mainfrom
rzikm:use-protocol-exception

Conversation

@rzikm

Copy link
Copy Markdown
Member

Follow-up on #71432.

Closes#70684.

@ghostghost assigned rzikmJul 13, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

Follow-up on #71432.

Closes #70684.

Author:rzikm
Assignees:-
Labels:

area-System.Net.Http

Milestone:-

@rzikm
rzikm requested a review from antonfirsovJuly 13, 2022 13:33
@antonfirsov

Copy link
Copy Markdown
Contributor

We have plenty of tests for HTTP/2 exercising error scenarios, for example:

publicasyncTaskHttp2_StreamResetByServerBeforeHeadersSent_RequestFails()
{
using(Http2LoopbackServerserver=Http2LoopbackServer.CreateServer())
using(HttpClientclient=CreateHttpClient())
{
Task<HttpResponseMessage>sendTask=client.GetAsync(server.Address);
Http2LoopbackConnectionconnection=awaitserver.EstablishConnectionAsync();
intstreamId=awaitconnection.ReadRequestHeaderAsync();
// Send a reset stream frame so that the stream moves to a terminal state.
RstStreamFrameresetStream=newRstStreamFrame(FrameFlags.None,(int)ProtocolErrors.INTERNAL_ERROR,streamId);
awaitconnection.WriteFrameAsync(resetStream);
awaitAssertProtocolErrorAsync(sendTask,ProtocolErrors.INTERNAL_ERROR);
}
}

Is it possible to cover at least a few HTTP/3 cases to verify that we are throwing this exception (including at least one response stream case)?

Priority = 0b00100000,

ValidBits = 0b00101101
ValidBits = 0b00101101

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this formatting alignment intentionally removed?

@ManickaP

ManickaP commented Jul 13, 2022

Copy link
Copy Markdown
Member

Please don't merge this before #71969 unless you're 100% sure it will not create conflicts. I really need to get that PR in main.

@rzikm
rzikm requested a review from a teamJuly 14, 2022 11:37
// Our stream was reset.
Exception? abortException = _connection.AbortException;
throw new HttpRequestException(SR.net_http_client_execution_error, abortException ?? ex);
throw new HttpRequestException(SR.net_http_client_execution_error, HttpProtocolException.CreateHttp3StreamException(code));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it ok that we are ignoring both _connection.AbortException and ex now?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is any value of including ex in the HttpProtocolException (it will always be the same and the error code is already in HttpProtocolException.

To tell the truth, I am not 100% sure what the right behavior here is. I didn't notice anything in the RFC which would let server close the stream with other error codes than the two handled above. I would consider it protocol violation and tear down the connection, but the RFC does not AFAIK prohibit using any other code, so we should just pass it by.

As for the _connection.AbortException, there is no harm in checking first and prioritizing it if there is some already. This branch should be pretty rare anyway as explained above.

Exception abortException = _connection.Abort(ex);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, abortException));
case Http3ConnectionException:
case HttpProtocolException:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Judging by CreateHttp3StreamException in HttpProtocolException it seems to me like HttpProtocolException may not always be connection-level. Is that right? This line does not seem as equivalent change then. Or am I missing something?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HttpProtocolException thrown from stream is not on a code path which would affect this. In other words, only connection-level exceptions can be encountered here

Comment on lines +37 to +38
_output.WriteLine(outerEx.InnerException.Message);
HttpProtocolException protocolEx = Assert.IsType<HttpProtocolException>(outerEx.InnerException);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be this instead?

Suggested change
_output.WriteLine(outerEx.InnerException.Message);
HttpProtocolExceptionprotocolEx=Assert.IsType<HttpProtocolException>(outerEx.InnerException);
HttpProtocolExceptionprotocolEx=Assert.IsType<HttpProtocolException>(outerEx.InnerException);
_output.WriteLine(protocolEx.Message);

Also, if I'm wrong and it's for diagnostics purposes in case it was not a HttpProtocolException, would it be better to include all the info about exceptions, even the top one? Meaning, should we log ToString instead of just Message

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied it from HttpClientHandlerTest.Http2 implementation, I can change it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was somewhat accidental in my PR, we don't have to print anything, though it's informative for manual validation of HttpProtocolException's message.

@rzikm
rzikmforce-pushed the use-protocol-exception branch from d467805 to 9e4324bCompareJuly 14, 2022 13:50
@rzikm
rzikm removed request for lewing and radicalJuly 14, 2022 13:50
await new[] { clientTask, serverTask }.WhenAllOrAnyFailed(20_000);
}


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

@@ -1196,7 +1199,7 @@ private void HandleReadResponseContentException(Exception ex, CancellationToken
// Our connection was reset. Start aborting the connection.
Exception abortException = _connection.Abort(ex);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, abortException));

@antonfirsovantonfirsovJul 14, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering why are we throwing IOException here? Aren't these errors supposed to surface as HttpProtocolException from HttpContent's Http3ReadStream (which seems to delegate to this method)?

HttpProtocolException protocolEx = Assert.IsType<HttpProtocolException>(outerEx.InnerException);
_output.WriteLine(protocolEx.Message);
Assert.Equal(errorCode, protocolEx.ErrorCode);
}

@antonfirsovantonfirsovJul 14, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add yet another test (or a parameteric variant of existing tests), that validates the exception thrown from the response content stream. See AssertProtocolErrorForIOExceptionAsync in http2 tests (I should have renamed that method ...).

@rzikm
rzikm requested a review from antonfirsovJuly 14, 2022 16:00

@antonfirsovantonfirsov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@rzikm
rzikm merged commit e2e9df7 into dotnet:mainJul 14, 2022
@rzikm

Copy link
Copy Markdown
MemberAuthor

/backport to release/7.0-preview7

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/7.0-preview7: https://github.com/dotnet/runtime/actions/runs/2672573752

@karelzkarelz added this to the 7.0.0 milestone Jul 19, 2022
@ghostghost locked as resolved and limited conversation to collaborators Aug 18, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Exposing HTTP/2 and HTTP/3 protocol error codes

6 participants

@rzikm@antonfirsov@ManickaP@stephentoub@CarnaViire@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Implement HttpProtocolException for HTTP/3 - #72095

Merged
rzikm merged 10 commits into
dotnet:mainfrom
rzikm:use-protocol-exception
Jul 14, 2022
Merged

Implement HttpProtocolException for HTTP/3#72095
rzikm merged 10 commits into
dotnet:mainfrom
rzikm:use-protocol-exception

Conversation

@rzikm

Copy link
Copy Markdown
Member

Follow-up on #71432.

Closes#70684.

@ghostghost assigned rzikmJul 13, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

Follow-up on #71432.

Closes #70684.

Author:rzikm
Assignees:-
Labels:

area-System.Net.Http

Milestone:-

@rzikm
rzikm requested a review from antonfirsovJuly 13, 2022 13:33
@antonfirsov

Copy link
Copy Markdown
Contributor

We have plenty of tests for HTTP/2 exercising error scenarios, for example:

publicasyncTaskHttp2_StreamResetByServerBeforeHeadersSent_RequestFails()
{
using(Http2LoopbackServerserver=Http2LoopbackServer.CreateServer())
using(HttpClientclient=CreateHttpClient())
{
Task<HttpResponseMessage>sendTask=client.GetAsync(server.Address);
Http2LoopbackConnectionconnection=awaitserver.EstablishConnectionAsync();
intstreamId=awaitconnection.ReadRequestHeaderAsync();
// Send a reset stream frame so that the stream moves to a terminal state.
RstStreamFrameresetStream=newRstStreamFrame(FrameFlags.None,(int)ProtocolErrors.INTERNAL_ERROR,streamId);
awaitconnection.WriteFrameAsync(resetStream);
awaitAssertProtocolErrorAsync(sendTask,ProtocolErrors.INTERNAL_ERROR);
}
}

Is it possible to cover at least a few HTTP/3 cases to verify that we are throwing this exception (including at least one response stream case)?

Priority = 0b00100000,

ValidBits = 0b00101101
ValidBits = 0b00101101

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this formatting alignment intentionally removed?

@ManickaP

ManickaP commented Jul 13, 2022

Copy link
Copy Markdown
Member

Please don't merge this before #71969 unless you're 100% sure it will not create conflicts. I really need to get that PR in main.

@rzikm
rzikm requested a review from a teamJuly 14, 2022 11:37
// Our stream was reset.
Exception? abortException = _connection.AbortException;
throw new HttpRequestException(SR.net_http_client_execution_error, abortException ?? ex);
throw new HttpRequestException(SR.net_http_client_execution_error, HttpProtocolException.CreateHttp3StreamException(code));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it ok that we are ignoring both _connection.AbortException and ex now?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is any value of including ex in the HttpProtocolException (it will always be the same and the error code is already in HttpProtocolException.

To tell the truth, I am not 100% sure what the right behavior here is. I didn't notice anything in the RFC which would let server close the stream with other error codes than the two handled above. I would consider it protocol violation and tear down the connection, but the RFC does not AFAIK prohibit using any other code, so we should just pass it by.

As for the _connection.AbortException, there is no harm in checking first and prioritizing it if there is some already. This branch should be pretty rare anyway as explained above.

Exception abortException = _connection.Abort(ex);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, abortException));
case Http3ConnectionException:
case HttpProtocolException:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Judging by CreateHttp3StreamException in HttpProtocolException it seems to me like HttpProtocolException may not always be connection-level. Is that right? This line does not seem as equivalent change then. Or am I missing something?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HttpProtocolException thrown from stream is not on a code path which would affect this. In other words, only connection-level exceptions can be encountered here

Comment on lines +37 to +38
_output.WriteLine(outerEx.InnerException.Message);
HttpProtocolException protocolEx = Assert.IsType<HttpProtocolException>(outerEx.InnerException);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be this instead?

Suggested change
_output.WriteLine(outerEx.InnerException.Message);
HttpProtocolExceptionprotocolEx=Assert.IsType<HttpProtocolException>(outerEx.InnerException);
HttpProtocolExceptionprotocolEx=Assert.IsType<HttpProtocolException>(outerEx.InnerException);
_output.WriteLine(protocolEx.Message);

Also, if I'm wrong and it's for diagnostics purposes in case it was not a HttpProtocolException, would it be better to include all the info about exceptions, even the top one? Meaning, should we log ToString instead of just Message

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied it from HttpClientHandlerTest.Http2 implementation, I can change it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was somewhat accidental in my PR, we don't have to print anything, though it's informative for manual validation of HttpProtocolException's message.

@rzikm
rzikmforce-pushed the use-protocol-exception branch from d467805 to 9e4324bCompareJuly 14, 2022 13:50
@rzikm
rzikm removed request for lewing and radicalJuly 14, 2022 13:50
await new[] { clientTask, serverTask }.WhenAllOrAnyFailed(20_000);
}


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

@@ -1196,7 +1199,7 @@ private void HandleReadResponseContentException(Exception ex, CancellationToken
// Our connection was reset. Start aborting the connection.
Exception abortException = _connection.Abort(ex);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, abortException));

@antonfirsovantonfirsovJul 14, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering why are we throwing IOException here? Aren't these errors supposed to surface as HttpProtocolException from HttpContent's Http3ReadStream (which seems to delegate to this method)?

HttpProtocolException protocolEx = Assert.IsType<HttpProtocolException>(outerEx.InnerException);
_output.WriteLine(protocolEx.Message);
Assert.Equal(errorCode, protocolEx.ErrorCode);
}

@antonfirsovantonfirsovJul 14, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add yet another test (or a parameteric variant of existing tests), that validates the exception thrown from the response content stream. See AssertProtocolErrorForIOExceptionAsync in http2 tests (I should have renamed that method ...).

@rzikm
rzikm requested a review from antonfirsovJuly 14, 2022 16:00

@antonfirsovantonfirsov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@rzikm
rzikm merged commit e2e9df7 into dotnet:mainJul 14, 2022
@rzikm

Copy link
Copy Markdown
MemberAuthor

/backport to release/7.0-preview7

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/7.0-preview7: https://github.com/dotnet/runtime/actions/runs/2672573752

@karelzkarelz added this to the 7.0.0 milestone Jul 19, 2022
@ghostghost locked as resolved and limited conversation to collaborators Aug 18, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Exposing HTTP/2 and HTTP/3 protocol error codes

6 participants

@rzikm@antonfirsov@ManickaP@stephentoub@CarnaViire@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Implement HttpProtocolException for HTTP/3 - #72095

Merged
rzikm merged 10 commits into
dotnet:mainfrom
rzikm:use-protocol-exception
Jul 14, 2022
Merged

Implement HttpProtocolException for HTTP/3#72095
rzikm merged 10 commits into
dotnet:mainfrom
rzikm:use-protocol-exception

Conversation

@rzikm

Copy link
Copy Markdown
Member

Follow-up on #71432.

Closes#70684.

@ghostghost assigned rzikmJul 13, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

Follow-up on #71432.

Closes #70684.

Author:rzikm
Assignees:-
Labels:

area-System.Net.Http

Milestone:-

@rzikm
rzikm requested a review from antonfirsovJuly 13, 2022 13:33
@antonfirsov

Copy link
Copy Markdown
Contributor

We have plenty of tests for HTTP/2 exercising error scenarios, for example:

publicasyncTaskHttp2_StreamResetByServerBeforeHeadersSent_RequestFails()
{
using(Http2LoopbackServerserver=Http2LoopbackServer.CreateServer())
using(HttpClientclient=CreateHttpClient())
{
Task<HttpResponseMessage>sendTask=client.GetAsync(server.Address);
Http2LoopbackConnectionconnection=awaitserver.EstablishConnectionAsync();
intstreamId=awaitconnection.ReadRequestHeaderAsync();
// Send a reset stream frame so that the stream moves to a terminal state.
RstStreamFrameresetStream=newRstStreamFrame(FrameFlags.None,(int)ProtocolErrors.INTERNAL_ERROR,streamId);
awaitconnection.WriteFrameAsync(resetStream);
awaitAssertProtocolErrorAsync(sendTask,ProtocolErrors.INTERNAL_ERROR);
}
}

Is it possible to cover at least a few HTTP/3 cases to verify that we are throwing this exception (including at least one response stream case)?

Priority = 0b00100000,

ValidBits = 0b00101101
ValidBits = 0b00101101

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this formatting alignment intentionally removed?

@ManickaP

ManickaP commented Jul 13, 2022

Copy link
Copy Markdown
Member

Please don't merge this before #71969 unless you're 100% sure it will not create conflicts. I really need to get that PR in main.

@rzikm
rzikm requested a review from a teamJuly 14, 2022 11:37
// Our stream was reset.
Exception? abortException = _connection.AbortException;
throw new HttpRequestException(SR.net_http_client_execution_error, abortException ?? ex);
throw new HttpRequestException(SR.net_http_client_execution_error, HttpProtocolException.CreateHttp3StreamException(code));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it ok that we are ignoring both _connection.AbortException and ex now?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is any value of including ex in the HttpProtocolException (it will always be the same and the error code is already in HttpProtocolException.

To tell the truth, I am not 100% sure what the right behavior here is. I didn't notice anything in the RFC which would let server close the stream with other error codes than the two handled above. I would consider it protocol violation and tear down the connection, but the RFC does not AFAIK prohibit using any other code, so we should just pass it by.

As for the _connection.AbortException, there is no harm in checking first and prioritizing it if there is some already. This branch should be pretty rare anyway as explained above.

Exception abortException = _connection.Abort(ex);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, abortException));
case Http3ConnectionException:
case HttpProtocolException:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Judging by CreateHttp3StreamException in HttpProtocolException it seems to me like HttpProtocolException may not always be connection-level. Is that right? This line does not seem as equivalent change then. Or am I missing something?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HttpProtocolException thrown from stream is not on a code path which would affect this. In other words, only connection-level exceptions can be encountered here

Comment on lines +37 to +38
_output.WriteLine(outerEx.InnerException.Message);
HttpProtocolException protocolEx = Assert.IsType<HttpProtocolException>(outerEx.InnerException);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be this instead?

Suggested change
_output.WriteLine(outerEx.InnerException.Message);
HttpProtocolExceptionprotocolEx=Assert.IsType<HttpProtocolException>(outerEx.InnerException);
HttpProtocolExceptionprotocolEx=Assert.IsType<HttpProtocolException>(outerEx.InnerException);
_output.WriteLine(protocolEx.Message);

Also, if I'm wrong and it's for diagnostics purposes in case it was not a HttpProtocolException, would it be better to include all the info about exceptions, even the top one? Meaning, should we log ToString instead of just Message

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied it from HttpClientHandlerTest.Http2 implementation, I can change it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was somewhat accidental in my PR, we don't have to print anything, though it's informative for manual validation of HttpProtocolException's message.

@rzikm
rzikmforce-pushed the use-protocol-exception branch from d467805 to 9e4324bCompareJuly 14, 2022 13:50
@rzikm
rzikm removed request for lewing and radicalJuly 14, 2022 13:50
await new[] { clientTask, serverTask }.WhenAllOrAnyFailed(20_000);
}


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

@@ -1196,7 +1199,7 @@ private void HandleReadResponseContentException(Exception ex, CancellationToken
// Our connection was reset. Start aborting the connection.
Exception abortException = _connection.Abort(ex);
throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, abortException));

@antonfirsovantonfirsovJul 14, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering why are we throwing IOException here? Aren't these errors supposed to surface as HttpProtocolException from HttpContent's Http3ReadStream (which seems to delegate to this method)?

HttpProtocolException protocolEx = Assert.IsType<HttpProtocolException>(outerEx.InnerException);
_output.WriteLine(protocolEx.Message);
Assert.Equal(errorCode, protocolEx.ErrorCode);
}

@antonfirsovantonfirsovJul 14, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add yet another test (or a parameteric variant of existing tests), that validates the exception thrown from the response content stream. See AssertProtocolErrorForIOExceptionAsync in http2 tests (I should have renamed that method ...).

@rzikm
rzikm requested a review from antonfirsovJuly 14, 2022 16:00

@antonfirsovantonfirsov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@rzikm
rzikm merged commit e2e9df7 into dotnet:mainJul 14, 2022
@rzikm

Copy link
Copy Markdown
MemberAuthor

/backport to release/7.0-preview7

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/7.0-preview7: https://github.com/dotnet/runtime/actions/runs/2672573752

@karelzkarelz added this to the 7.0.0 milestone Jul 19, 2022
@ghostghost locked as resolved and limited conversation to collaborators Aug 18, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Exposing HTTP/2 and HTTP/3 protocol error codes

6 participants

@rzikm@antonfirsov@ManickaP@stephentoub@CarnaViire@karelz