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

Description

@antonfirsov

Exposing HTTP/2 and HTTP/3 protocol error codes from SocketsHttpHandler

This proposal builds on our QuicException proposal.

Background and Motivation

GRPC (and potentially other lower-level users) need a way to get the underlying HTTP/2 or HTTP/3 error codes in case a protocol error occurs.

The error code should be observable not only when an error occurs while calling an HttpClient method (#43239), but also when invoking Stream API-s on the response's content read stream (#62228).

Proposed design

  • Define a new exception type HttpProtocolException, and embed it as HttpRequestException.InnerException
  • Throw ProtocolException directly from HttpResponse content read streams
namespaceSystem.Net.Http;publicsealedclassHttpProtocolException:IOException{publicHttpProtocolException(HttpProtocolErrorerrorCode,stringmessage,Exception?innerException);publicHttpProtocolErrorErrorCode{get;}}// Map enum values to H2 and H3 error codes// H3 error codes can be 62 bit longpublicenumHttpProtocolError:long{// Camel-cased names taken directly from the HTTP/2 spec// https://datatracker.ietf.org/doc/html/rfc7540#section-7NoError=0x0,ProtocolError,InternalError,FlowControlError,SettingsTimeout,StreamClosed,FrameSizeError,RefusedStream,Cancel,CompressionError,EnhanceYourCalm,InadequateSecurity,Http11Required,// Camel-cased names taken directly from the HTTP/3 spec// https://datatracker.ietf.org/doc/html/rfc9114/#section-8.1H3NoError=0x100,H3GeneralProtocolError,H3InternalERror,H3StreamCreationError,H3ClosedCriticalStream,H3FrameUnexpected,H3FrameError,H3ExcessiveLoad,H3IdError,H3SettingsError,H3MissingSettings,H3RequestRejected,H3RequestCancelled,H3RequestIncomplete,H3MessageError,H3ConnectError,H3VersionFallback}

Edit: Added a public constructor.

Notes

  • No public constructors, because we prefer to do the bare minimum to deliver a feature. This means that WinHttpHandler and user-made HttpClientHandlers won't be able to throw HttpProtocolException for now. We can consider public constructors later, if necessary.
  • We throw ProtocolError when a connection or stream is aborted, or when we detect a protocol violation ourselves
  • ErrorCode >= 256 means HTTP/3
  • In case of HTTP/3 connection or stream errors, we are embedding QuicException as ProtocolException.InnerException
  • We don't throw HttpProtocolException for transport-level errors

API Usage

Over HttpClient

usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync(".");}catch(HttpRequestExceptionex)when(ex.InnerExceptionisProtocolExceptionprotocolException){Console.WriteLine("HTTP error code: "+protocolException.ProtocolErrorCode)
if (protocolException.InnerExceptionisQuicExceptionquicException)
Console.WriteLine("Underlying QUIC error: "+quicException.Message);}catch(HttpRequestExceptionex)when(ex.InnerExceptionisAuthenticationExceptionauthenticationException){// TLS authentication error. Can originate from QUIC, we map some errors to high-level .NET exceptions}

Over response stream

usingvarclient=newHttpClient();usingvarresponse=awaitclient.GetAsync(".",HttpCompletionOption.ResponseHeadersRead);usingvarresponseStream=awaitresponse.Content.ReadAsStreamAsync();usingvarmemoryStream=newMemoryStream();try{awaitresponseStream.CopyToAsync(memoryStream);}catch(ProtocolExceptionprotocolException){// HTTP(2|3) protocol error}catch(QuicExceptionquicException){// QUIC transport error}catch(IOExceptionexception)when(ex.InnerExceptionisSocketExceptionsocketException){// TCP transport error}

Alternative designs

Parallel independent exception types for HTTP/2 and HTTP/3

publicclassHttp2ProtocolException:IOException{publicintErrorCode{get;}}// Use System.Net.Quic.QuicException for HTTP/3// public class QuicException : IOException { }
Usage
usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync("foo.bar");}// HTTP/2catch(HttpRequestExceptionex)when(ex.InnerExceptionisHttp2ProtocolExceptionprotocolException){Console.WriteLine(protocolException.ProtocolErrorCode)}// HTTP/3catch(HttpRequestExceptionex)when(ex.InnerExceptionisQuicExceptionquicException){Console.WriteLine(quicException.ApplicationErrorCode);}

Do not define an enum, use untyped long error codes

publicsealedclassHttpProtocolException:IOException{publiclongErrorCode{get;}}

Risks

  • HttpProtocolError enum: implementations may send unspecified error codes. If spec evolves in the future, we will have to introduce new error codes. In both cases field would contain a value outside the enum range, so users would have a workaround.
  • Unless I'm missing something, this is not a breaking change, since HttpProtocolException is an IOException.

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.Net.HttpblockingMarks issues that we want to fast track in order to unblock other important work

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , '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

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

    Description

    @antonfirsov

    Exposing HTTP/2 and HTTP/3 protocol error codes from SocketsHttpHandler

    This proposal builds on our QuicException proposal.

    Background and Motivation

    GRPC (and potentially other lower-level users) need a way to get the underlying HTTP/2 or HTTP/3 error codes in case a protocol error occurs.

    The error code should be observable not only when an error occurs while calling an HttpClient method (#43239), but also when invoking Stream API-s on the response's content read stream (#62228).

    Proposed design

    • Define a new exception type HttpProtocolException, and embed it as HttpRequestException.InnerException
    • Throw ProtocolException directly from HttpResponse content read streams
    namespaceSystem.Net.Http;publicsealedclassHttpProtocolException:IOException{publicHttpProtocolException(HttpProtocolErrorerrorCode,stringmessage,Exception?innerException);publicHttpProtocolErrorErrorCode{get;}}// Map enum values to H2 and H3 error codes// H3 error codes can be 62 bit longpublicenumHttpProtocolError:long{// Camel-cased names taken directly from the HTTP/2 spec// https://datatracker.ietf.org/doc/html/rfc7540#section-7NoError=0x0,ProtocolError,InternalError,FlowControlError,SettingsTimeout,StreamClosed,FrameSizeError,RefusedStream,Cancel,CompressionError,EnhanceYourCalm,InadequateSecurity,Http11Required,// Camel-cased names taken directly from the HTTP/3 spec// https://datatracker.ietf.org/doc/html/rfc9114/#section-8.1H3NoError=0x100,H3GeneralProtocolError,H3InternalERror,H3StreamCreationError,H3ClosedCriticalStream,H3FrameUnexpected,H3FrameError,H3ExcessiveLoad,H3IdError,H3SettingsError,H3MissingSettings,H3RequestRejected,H3RequestCancelled,H3RequestIncomplete,H3MessageError,H3ConnectError,H3VersionFallback}

    Edit: Added a public constructor.

    Notes

    • No public constructors, because we prefer to do the bare minimum to deliver a feature. This means that WinHttpHandler and user-made HttpClientHandlers won't be able to throw HttpProtocolException for now. We can consider public constructors later, if necessary.
    • We throw ProtocolError when a connection or stream is aborted, or when we detect a protocol violation ourselves
    • ErrorCode >= 256 means HTTP/3
    • In case of HTTP/3 connection or stream errors, we are embedding QuicException as ProtocolException.InnerException
    • We don't throw HttpProtocolException for transport-level errors

    API Usage

    Over HttpClient

    usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync(".");}catch(HttpRequestExceptionex)when(ex.InnerExceptionisProtocolExceptionprotocolException){Console.WriteLine("HTTP error code: "+protocolException.ProtocolErrorCode)
    if (protocolException.InnerExceptionisQuicExceptionquicException)
    Console.WriteLine("Underlying QUIC error: "+quicException.Message);}catch(HttpRequestExceptionex)when(ex.InnerExceptionisAuthenticationExceptionauthenticationException){// TLS authentication error. Can originate from QUIC, we map some errors to high-level .NET exceptions}

    Over response stream

    usingvarclient=newHttpClient();usingvarresponse=awaitclient.GetAsync(".",HttpCompletionOption.ResponseHeadersRead);usingvarresponseStream=awaitresponse.Content.ReadAsStreamAsync();usingvarmemoryStream=newMemoryStream();try{awaitresponseStream.CopyToAsync(memoryStream);}catch(ProtocolExceptionprotocolException){// HTTP(2|3) protocol error}catch(QuicExceptionquicException){// QUIC transport error}catch(IOExceptionexception)when(ex.InnerExceptionisSocketExceptionsocketException){// TCP transport error}

    Alternative designs

    Parallel independent exception types for HTTP/2 and HTTP/3

    publicclassHttp2ProtocolException:IOException{publicintErrorCode{get;}}// Use System.Net.Quic.QuicException for HTTP/3// public class QuicException : IOException { }
    Usage
    usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync("foo.bar");}// HTTP/2catch(HttpRequestExceptionex)when(ex.InnerExceptionisHttp2ProtocolExceptionprotocolException){Console.WriteLine(protocolException.ProtocolErrorCode)}// HTTP/3catch(HttpRequestExceptionex)when(ex.InnerExceptionisQuicExceptionquicException){Console.WriteLine(quicException.ApplicationErrorCode);}

    Do not define an enum, use untyped long error codes

    publicsealedclassHttpProtocolException:IOException{publiclongErrorCode{get;}}

    Risks

    • HttpProtocolError enum: implementations may send unspecified error codes. If spec evolves in the future, we will have to introduce new error codes. In both cases field would contain a value outside the enum range, so users would have a workaround.
    • Unless I'm missing something, this is not a breaking change, since HttpProtocolException is an IOException.

    Metadata

    Metadata

    Assignees

    Labels

    api-approvedAPI was approved in API review, it can be implementedarea-System.Net.HttpblockingMarks issues that we want to fast track in order to unblock other important work

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , '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

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

      Description

      @antonfirsov

      Exposing HTTP/2 and HTTP/3 protocol error codes from SocketsHttpHandler

      This proposal builds on our QuicException proposal.

      Background and Motivation

      GRPC (and potentially other lower-level users) need a way to get the underlying HTTP/2 or HTTP/3 error codes in case a protocol error occurs.

      The error code should be observable not only when an error occurs while calling an HttpClient method (#43239), but also when invoking Stream API-s on the response's content read stream (#62228).

      Proposed design

      • Define a new exception type HttpProtocolException, and embed it as HttpRequestException.InnerException
      • Throw ProtocolException directly from HttpResponse content read streams
      namespaceSystem.Net.Http;publicsealedclassHttpProtocolException:IOException{publicHttpProtocolException(HttpProtocolErrorerrorCode,stringmessage,Exception?innerException);publicHttpProtocolErrorErrorCode{get;}}// Map enum values to H2 and H3 error codes// H3 error codes can be 62 bit longpublicenumHttpProtocolError:long{// Camel-cased names taken directly from the HTTP/2 spec// https://datatracker.ietf.org/doc/html/rfc7540#section-7NoError=0x0,ProtocolError,InternalError,FlowControlError,SettingsTimeout,StreamClosed,FrameSizeError,RefusedStream,Cancel,CompressionError,EnhanceYourCalm,InadequateSecurity,Http11Required,// Camel-cased names taken directly from the HTTP/3 spec// https://datatracker.ietf.org/doc/html/rfc9114/#section-8.1H3NoError=0x100,H3GeneralProtocolError,H3InternalERror,H3StreamCreationError,H3ClosedCriticalStream,H3FrameUnexpected,H3FrameError,H3ExcessiveLoad,H3IdError,H3SettingsError,H3MissingSettings,H3RequestRejected,H3RequestCancelled,H3RequestIncomplete,H3MessageError,H3ConnectError,H3VersionFallback}

      Edit: Added a public constructor.

      Notes

      • No public constructors, because we prefer to do the bare minimum to deliver a feature. This means that WinHttpHandler and user-made HttpClientHandlers won't be able to throw HttpProtocolException for now. We can consider public constructors later, if necessary.
      • We throw ProtocolError when a connection or stream is aborted, or when we detect a protocol violation ourselves
      • ErrorCode >= 256 means HTTP/3
      • In case of HTTP/3 connection or stream errors, we are embedding QuicException as ProtocolException.InnerException
      • We don't throw HttpProtocolException for transport-level errors

      API Usage

      Over HttpClient

      usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync(".");}catch(HttpRequestExceptionex)when(ex.InnerExceptionisProtocolExceptionprotocolException){Console.WriteLine("HTTP error code: "+protocolException.ProtocolErrorCode)
      if (protocolException.InnerExceptionisQuicExceptionquicException)
      Console.WriteLine("Underlying QUIC error: "+quicException.Message);}catch(HttpRequestExceptionex)when(ex.InnerExceptionisAuthenticationExceptionauthenticationException){// TLS authentication error. Can originate from QUIC, we map some errors to high-level .NET exceptions}

      Over response stream

      usingvarclient=newHttpClient();usingvarresponse=awaitclient.GetAsync(".",HttpCompletionOption.ResponseHeadersRead);usingvarresponseStream=awaitresponse.Content.ReadAsStreamAsync();usingvarmemoryStream=newMemoryStream();try{awaitresponseStream.CopyToAsync(memoryStream);}catch(ProtocolExceptionprotocolException){// HTTP(2|3) protocol error}catch(QuicExceptionquicException){// QUIC transport error}catch(IOExceptionexception)when(ex.InnerExceptionisSocketExceptionsocketException){// TCP transport error}

      Alternative designs

      Parallel independent exception types for HTTP/2 and HTTP/3

      publicclassHttp2ProtocolException:IOException{publicintErrorCode{get;}}// Use System.Net.Quic.QuicException for HTTP/3// public class QuicException : IOException { }
      Usage
      usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync("foo.bar");}// HTTP/2catch(HttpRequestExceptionex)when(ex.InnerExceptionisHttp2ProtocolExceptionprotocolException){Console.WriteLine(protocolException.ProtocolErrorCode)}// HTTP/3catch(HttpRequestExceptionex)when(ex.InnerExceptionisQuicExceptionquicException){Console.WriteLine(quicException.ApplicationErrorCode);}

      Do not define an enum, use untyped long error codes

      publicsealedclassHttpProtocolException:IOException{publiclongErrorCode{get;}}

      Risks

      • HttpProtocolError enum: implementations may send unspecified error codes. If spec evolves in the future, we will have to introduce new error codes. In both cases field would contain a value outside the enum range, so users would have a workaround.
      • Unless I'm missing something, this is not a breaking change, since HttpProtocolException is an IOException.

      Metadata

      Metadata

      Assignees

      Labels

      api-approvedAPI was approved in API review, it can be implementedarea-System.Net.HttpblockingMarks issues that we want to fast track in order to unblock other important work

      Type

      No type

      Projects

      No projects

        Milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , '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

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

        Description

        @antonfirsov

        Exposing HTTP/2 and HTTP/3 protocol error codes from SocketsHttpHandler

        This proposal builds on our QuicException proposal.

        Background and Motivation

        GRPC (and potentially other lower-level users) need a way to get the underlying HTTP/2 or HTTP/3 error codes in case a protocol error occurs.

        The error code should be observable not only when an error occurs while calling an HttpClient method (#43239), but also when invoking Stream API-s on the response's content read stream (#62228).

        Proposed design

        • Define a new exception type HttpProtocolException, and embed it as HttpRequestException.InnerException
        • Throw ProtocolException directly from HttpResponse content read streams
        namespaceSystem.Net.Http;publicsealedclassHttpProtocolException:IOException{publicHttpProtocolException(HttpProtocolErrorerrorCode,stringmessage,Exception?innerException);publicHttpProtocolErrorErrorCode{get;}}// Map enum values to H2 and H3 error codes// H3 error codes can be 62 bit longpublicenumHttpProtocolError:long{// Camel-cased names taken directly from the HTTP/2 spec// https://datatracker.ietf.org/doc/html/rfc7540#section-7NoError=0x0,ProtocolError,InternalError,FlowControlError,SettingsTimeout,StreamClosed,FrameSizeError,RefusedStream,Cancel,CompressionError,EnhanceYourCalm,InadequateSecurity,Http11Required,// Camel-cased names taken directly from the HTTP/3 spec// https://datatracker.ietf.org/doc/html/rfc9114/#section-8.1H3NoError=0x100,H3GeneralProtocolError,H3InternalERror,H3StreamCreationError,H3ClosedCriticalStream,H3FrameUnexpected,H3FrameError,H3ExcessiveLoad,H3IdError,H3SettingsError,H3MissingSettings,H3RequestRejected,H3RequestCancelled,H3RequestIncomplete,H3MessageError,H3ConnectError,H3VersionFallback}

        Edit: Added a public constructor.

        Notes

        • No public constructors, because we prefer to do the bare minimum to deliver a feature. This means that WinHttpHandler and user-made HttpClientHandlers won't be able to throw HttpProtocolException for now. We can consider public constructors later, if necessary.
        • We throw ProtocolError when a connection or stream is aborted, or when we detect a protocol violation ourselves
        • ErrorCode >= 256 means HTTP/3
        • In case of HTTP/3 connection or stream errors, we are embedding QuicException as ProtocolException.InnerException
        • We don't throw HttpProtocolException for transport-level errors

        API Usage

        Over HttpClient

        usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync(".");}catch(HttpRequestExceptionex)when(ex.InnerExceptionisProtocolExceptionprotocolException){Console.WriteLine("HTTP error code: "+protocolException.ProtocolErrorCode)
        if (protocolException.InnerExceptionisQuicExceptionquicException)
        Console.WriteLine("Underlying QUIC error: "+quicException.Message);}catch(HttpRequestExceptionex)when(ex.InnerExceptionisAuthenticationExceptionauthenticationException){// TLS authentication error. Can originate from QUIC, we map some errors to high-level .NET exceptions}

        Over response stream

        usingvarclient=newHttpClient();usingvarresponse=awaitclient.GetAsync(".",HttpCompletionOption.ResponseHeadersRead);usingvarresponseStream=awaitresponse.Content.ReadAsStreamAsync();usingvarmemoryStream=newMemoryStream();try{awaitresponseStream.CopyToAsync(memoryStream);}catch(ProtocolExceptionprotocolException){// HTTP(2|3) protocol error}catch(QuicExceptionquicException){// QUIC transport error}catch(IOExceptionexception)when(ex.InnerExceptionisSocketExceptionsocketException){// TCP transport error}

        Alternative designs

        Parallel independent exception types for HTTP/2 and HTTP/3

        publicclassHttp2ProtocolException:IOException{publicintErrorCode{get;}}// Use System.Net.Quic.QuicException for HTTP/3// public class QuicException : IOException { }
        Usage
        usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync("foo.bar");}// HTTP/2catch(HttpRequestExceptionex)when(ex.InnerExceptionisHttp2ProtocolExceptionprotocolException){Console.WriteLine(protocolException.ProtocolErrorCode)}// HTTP/3catch(HttpRequestExceptionex)when(ex.InnerExceptionisQuicExceptionquicException){Console.WriteLine(quicException.ApplicationErrorCode);}

        Do not define an enum, use untyped long error codes

        publicsealedclassHttpProtocolException:IOException{publiclongErrorCode{get;}}

        Risks

        • HttpProtocolError enum: implementations may send unspecified error codes. If spec evolves in the future, we will have to introduce new error codes. In both cases field would contain a value outside the enum range, so users would have a workaround.
        • Unless I'm missing something, this is not a breaking change, since HttpProtocolException is an IOException.

        Metadata

        Metadata

        Assignees

        Labels

        api-approvedAPI was approved in API review, it can be implementedarea-System.Net.HttpblockingMarks issues that we want to fast track in order to unblock other important work

        Type

        No type

        Projects

        No projects

          Milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , '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

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

          Description

          @antonfirsov

          Exposing HTTP/2 and HTTP/3 protocol error codes from SocketsHttpHandler

          This proposal builds on our QuicException proposal.

          Background and Motivation

          GRPC (and potentially other lower-level users) need a way to get the underlying HTTP/2 or HTTP/3 error codes in case a protocol error occurs.

          The error code should be observable not only when an error occurs while calling an HttpClient method (#43239), but also when invoking Stream API-s on the response's content read stream (#62228).

          Proposed design

          • Define a new exception type HttpProtocolException, and embed it as HttpRequestException.InnerException
          • Throw ProtocolException directly from HttpResponse content read streams
          namespaceSystem.Net.Http;publicsealedclassHttpProtocolException:IOException{publicHttpProtocolException(HttpProtocolErrorerrorCode,stringmessage,Exception?innerException);publicHttpProtocolErrorErrorCode{get;}}// Map enum values to H2 and H3 error codes// H3 error codes can be 62 bit longpublicenumHttpProtocolError:long{// Camel-cased names taken directly from the HTTP/2 spec// https://datatracker.ietf.org/doc/html/rfc7540#section-7NoError=0x0,ProtocolError,InternalError,FlowControlError,SettingsTimeout,StreamClosed,FrameSizeError,RefusedStream,Cancel,CompressionError,EnhanceYourCalm,InadequateSecurity,Http11Required,// Camel-cased names taken directly from the HTTP/3 spec// https://datatracker.ietf.org/doc/html/rfc9114/#section-8.1H3NoError=0x100,H3GeneralProtocolError,H3InternalERror,H3StreamCreationError,H3ClosedCriticalStream,H3FrameUnexpected,H3FrameError,H3ExcessiveLoad,H3IdError,H3SettingsError,H3MissingSettings,H3RequestRejected,H3RequestCancelled,H3RequestIncomplete,H3MessageError,H3ConnectError,H3VersionFallback}

          Edit: Added a public constructor.

          Notes

          • No public constructors, because we prefer to do the bare minimum to deliver a feature. This means that WinHttpHandler and user-made HttpClientHandlers won't be able to throw HttpProtocolException for now. We can consider public constructors later, if necessary.
          • We throw ProtocolError when a connection or stream is aborted, or when we detect a protocol violation ourselves
          • ErrorCode >= 256 means HTTP/3
          • In case of HTTP/3 connection or stream errors, we are embedding QuicException as ProtocolException.InnerException
          • We don't throw HttpProtocolException for transport-level errors

          API Usage

          Over HttpClient

          usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync(".");}catch(HttpRequestExceptionex)when(ex.InnerExceptionisProtocolExceptionprotocolException){Console.WriteLine("HTTP error code: "+protocolException.ProtocolErrorCode)
          if (protocolException.InnerExceptionisQuicExceptionquicException)
          Console.WriteLine("Underlying QUIC error: "+quicException.Message);}catch(HttpRequestExceptionex)when(ex.InnerExceptionisAuthenticationExceptionauthenticationException){// TLS authentication error. Can originate from QUIC, we map some errors to high-level .NET exceptions}

          Over response stream

          usingvarclient=newHttpClient();usingvarresponse=awaitclient.GetAsync(".",HttpCompletionOption.ResponseHeadersRead);usingvarresponseStream=awaitresponse.Content.ReadAsStreamAsync();usingvarmemoryStream=newMemoryStream();try{awaitresponseStream.CopyToAsync(memoryStream);}catch(ProtocolExceptionprotocolException){// HTTP(2|3) protocol error}catch(QuicExceptionquicException){// QUIC transport error}catch(IOExceptionexception)when(ex.InnerExceptionisSocketExceptionsocketException){// TCP transport error}

          Alternative designs

          Parallel independent exception types for HTTP/2 and HTTP/3

          publicclassHttp2ProtocolException:IOException{publicintErrorCode{get;}}// Use System.Net.Quic.QuicException for HTTP/3// public class QuicException : IOException { }
          Usage
          usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync("foo.bar");}// HTTP/2catch(HttpRequestExceptionex)when(ex.InnerExceptionisHttp2ProtocolExceptionprotocolException){Console.WriteLine(protocolException.ProtocolErrorCode)}// HTTP/3catch(HttpRequestExceptionex)when(ex.InnerExceptionisQuicExceptionquicException){Console.WriteLine(quicException.ApplicationErrorCode);}

          Do not define an enum, use untyped long error codes

          publicsealedclassHttpProtocolException:IOException{publiclongErrorCode{get;}}

          Risks

          • HttpProtocolError enum: implementations may send unspecified error codes. If spec evolves in the future, we will have to introduce new error codes. In both cases field would contain a value outside the enum range, so users would have a workaround.
          • Unless I'm missing something, this is not a breaking change, since HttpProtocolException is an IOException.

          Metadata

          Metadata

          Assignees

          Labels

          api-approvedAPI was approved in API review, it can be implementedarea-System.Net.HttpblockingMarks issues that we want to fast track in order to unblock other important work

          Type

          No type

          Projects

          No projects

            Milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , '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

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

            Description

            @antonfirsov

            Exposing HTTP/2 and HTTP/3 protocol error codes from SocketsHttpHandler

            This proposal builds on our QuicException proposal.

            Background and Motivation

            GRPC (and potentially other lower-level users) need a way to get the underlying HTTP/2 or HTTP/3 error codes in case a protocol error occurs.

            The error code should be observable not only when an error occurs while calling an HttpClient method (#43239), but also when invoking Stream API-s on the response's content read stream (#62228).

            Proposed design

            • Define a new exception type HttpProtocolException, and embed it as HttpRequestException.InnerException
            • Throw ProtocolException directly from HttpResponse content read streams
            namespaceSystem.Net.Http;publicsealedclassHttpProtocolException:IOException{publicHttpProtocolException(HttpProtocolErrorerrorCode,stringmessage,Exception?innerException);publicHttpProtocolErrorErrorCode{get;}}// Map enum values to H2 and H3 error codes// H3 error codes can be 62 bit longpublicenumHttpProtocolError:long{// Camel-cased names taken directly from the HTTP/2 spec// https://datatracker.ietf.org/doc/html/rfc7540#section-7NoError=0x0,ProtocolError,InternalError,FlowControlError,SettingsTimeout,StreamClosed,FrameSizeError,RefusedStream,Cancel,CompressionError,EnhanceYourCalm,InadequateSecurity,Http11Required,// Camel-cased names taken directly from the HTTP/3 spec// https://datatracker.ietf.org/doc/html/rfc9114/#section-8.1H3NoError=0x100,H3GeneralProtocolError,H3InternalERror,H3StreamCreationError,H3ClosedCriticalStream,H3FrameUnexpected,H3FrameError,H3ExcessiveLoad,H3IdError,H3SettingsError,H3MissingSettings,H3RequestRejected,H3RequestCancelled,H3RequestIncomplete,H3MessageError,H3ConnectError,H3VersionFallback}

            Edit: Added a public constructor.

            Notes

            • No public constructors, because we prefer to do the bare minimum to deliver a feature. This means that WinHttpHandler and user-made HttpClientHandlers won't be able to throw HttpProtocolException for now. We can consider public constructors later, if necessary.
            • We throw ProtocolError when a connection or stream is aborted, or when we detect a protocol violation ourselves
            • ErrorCode >= 256 means HTTP/3
            • In case of HTTP/3 connection or stream errors, we are embedding QuicException as ProtocolException.InnerException
            • We don't throw HttpProtocolException for transport-level errors

            API Usage

            Over HttpClient

            usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync(".");}catch(HttpRequestExceptionex)when(ex.InnerExceptionisProtocolExceptionprotocolException){Console.WriteLine("HTTP error code: "+protocolException.ProtocolErrorCode)
            if (protocolException.InnerExceptionisQuicExceptionquicException)
            Console.WriteLine("Underlying QUIC error: "+quicException.Message);}catch(HttpRequestExceptionex)when(ex.InnerExceptionisAuthenticationExceptionauthenticationException){// TLS authentication error. Can originate from QUIC, we map some errors to high-level .NET exceptions}

            Over response stream

            usingvarclient=newHttpClient();usingvarresponse=awaitclient.GetAsync(".",HttpCompletionOption.ResponseHeadersRead);usingvarresponseStream=awaitresponse.Content.ReadAsStreamAsync();usingvarmemoryStream=newMemoryStream();try{awaitresponseStream.CopyToAsync(memoryStream);}catch(ProtocolExceptionprotocolException){// HTTP(2|3) protocol error}catch(QuicExceptionquicException){// QUIC transport error}catch(IOExceptionexception)when(ex.InnerExceptionisSocketExceptionsocketException){// TCP transport error}

            Alternative designs

            Parallel independent exception types for HTTP/2 and HTTP/3

            publicclassHttp2ProtocolException:IOException{publicintErrorCode{get;}}// Use System.Net.Quic.QuicException for HTTP/3// public class QuicException : IOException { }
            Usage
            usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync("foo.bar");}// HTTP/2catch(HttpRequestExceptionex)when(ex.InnerExceptionisHttp2ProtocolExceptionprotocolException){Console.WriteLine(protocolException.ProtocolErrorCode)}// HTTP/3catch(HttpRequestExceptionex)when(ex.InnerExceptionisQuicExceptionquicException){Console.WriteLine(quicException.ApplicationErrorCode);}

            Do not define an enum, use untyped long error codes

            publicsealedclassHttpProtocolException:IOException{publiclongErrorCode{get;}}

            Risks

            • HttpProtocolError enum: implementations may send unspecified error codes. If spec evolves in the future, we will have to introduce new error codes. In both cases field would contain a value outside the enum range, so users would have a workaround.
            • Unless I'm missing something, this is not a breaking change, since HttpProtocolException is an IOException.

            Metadata

            Metadata

            Assignees

            Labels

            api-approvedAPI was approved in API review, it can be implementedarea-System.Net.HttpblockingMarks issues that we want to fast track in order to unblock other important work

            Type

            No type

            Projects

            No projects

              Milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , '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

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

              Description

              @antonfirsov

              Exposing HTTP/2 and HTTP/3 protocol error codes from SocketsHttpHandler

              This proposal builds on our QuicException proposal.

              Background and Motivation

              GRPC (and potentially other lower-level users) need a way to get the underlying HTTP/2 or HTTP/3 error codes in case a protocol error occurs.

              The error code should be observable not only when an error occurs while calling an HttpClient method (#43239), but also when invoking Stream API-s on the response's content read stream (#62228).

              Proposed design

              • Define a new exception type HttpProtocolException, and embed it as HttpRequestException.InnerException
              • Throw ProtocolException directly from HttpResponse content read streams
              namespaceSystem.Net.Http;publicsealedclassHttpProtocolException:IOException{publicHttpProtocolException(HttpProtocolErrorerrorCode,stringmessage,Exception?innerException);publicHttpProtocolErrorErrorCode{get;}}// Map enum values to H2 and H3 error codes// H3 error codes can be 62 bit longpublicenumHttpProtocolError:long{// Camel-cased names taken directly from the HTTP/2 spec// https://datatracker.ietf.org/doc/html/rfc7540#section-7NoError=0x0,ProtocolError,InternalError,FlowControlError,SettingsTimeout,StreamClosed,FrameSizeError,RefusedStream,Cancel,CompressionError,EnhanceYourCalm,InadequateSecurity,Http11Required,// Camel-cased names taken directly from the HTTP/3 spec// https://datatracker.ietf.org/doc/html/rfc9114/#section-8.1H3NoError=0x100,H3GeneralProtocolError,H3InternalERror,H3StreamCreationError,H3ClosedCriticalStream,H3FrameUnexpected,H3FrameError,H3ExcessiveLoad,H3IdError,H3SettingsError,H3MissingSettings,H3RequestRejected,H3RequestCancelled,H3RequestIncomplete,H3MessageError,H3ConnectError,H3VersionFallback}

              Edit: Added a public constructor.

              Notes

              • No public constructors, because we prefer to do the bare minimum to deliver a feature. This means that WinHttpHandler and user-made HttpClientHandlers won't be able to throw HttpProtocolException for now. We can consider public constructors later, if necessary.
              • We throw ProtocolError when a connection or stream is aborted, or when we detect a protocol violation ourselves
              • ErrorCode >= 256 means HTTP/3
              • In case of HTTP/3 connection or stream errors, we are embedding QuicException as ProtocolException.InnerException
              • We don't throw HttpProtocolException for transport-level errors

              API Usage

              Over HttpClient

              usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync(".");}catch(HttpRequestExceptionex)when(ex.InnerExceptionisProtocolExceptionprotocolException){Console.WriteLine("HTTP error code: "+protocolException.ProtocolErrorCode)
              if (protocolException.InnerExceptionisQuicExceptionquicException)
              Console.WriteLine("Underlying QUIC error: "+quicException.Message);}catch(HttpRequestExceptionex)when(ex.InnerExceptionisAuthenticationExceptionauthenticationException){// TLS authentication error. Can originate from QUIC, we map some errors to high-level .NET exceptions}

              Over response stream

              usingvarclient=newHttpClient();usingvarresponse=awaitclient.GetAsync(".",HttpCompletionOption.ResponseHeadersRead);usingvarresponseStream=awaitresponse.Content.ReadAsStreamAsync();usingvarmemoryStream=newMemoryStream();try{awaitresponseStream.CopyToAsync(memoryStream);}catch(ProtocolExceptionprotocolException){// HTTP(2|3) protocol error}catch(QuicExceptionquicException){// QUIC transport error}catch(IOExceptionexception)when(ex.InnerExceptionisSocketExceptionsocketException){// TCP transport error}

              Alternative designs

              Parallel independent exception types for HTTP/2 and HTTP/3

              publicclassHttp2ProtocolException:IOException{publicintErrorCode{get;}}// Use System.Net.Quic.QuicException for HTTP/3// public class QuicException : IOException { }
              Usage
              usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync("foo.bar");}// HTTP/2catch(HttpRequestExceptionex)when(ex.InnerExceptionisHttp2ProtocolExceptionprotocolException){Console.WriteLine(protocolException.ProtocolErrorCode)}// HTTP/3catch(HttpRequestExceptionex)when(ex.InnerExceptionisQuicExceptionquicException){Console.WriteLine(quicException.ApplicationErrorCode);}

              Do not define an enum, use untyped long error codes

              publicsealedclassHttpProtocolException:IOException{publiclongErrorCode{get;}}

              Risks

              • HttpProtocolError enum: implementations may send unspecified error codes. If spec evolves in the future, we will have to introduce new error codes. In both cases field would contain a value outside the enum range, so users would have a workaround.
              • Unless I'm missing something, this is not a breaking change, since HttpProtocolException is an IOException.

              Metadata

              Metadata

              Assignees

              Labels

              api-approvedAPI was approved in API review, it can be implementedarea-System.Net.HttpblockingMarks issues that we want to fast track in order to unblock other important work

              Type

              No type

              Projects

              No projects

                Milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , '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

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

                Description

                @antonfirsov

                Exposing HTTP/2 and HTTP/3 protocol error codes from SocketsHttpHandler

                This proposal builds on our QuicException proposal.

                Background and Motivation

                GRPC (and potentially other lower-level users) need a way to get the underlying HTTP/2 or HTTP/3 error codes in case a protocol error occurs.

                The error code should be observable not only when an error occurs while calling an HttpClient method (#43239), but also when invoking Stream API-s on the response's content read stream (#62228).

                Proposed design

                • Define a new exception type HttpProtocolException, and embed it as HttpRequestException.InnerException
                • Throw ProtocolException directly from HttpResponse content read streams
                namespaceSystem.Net.Http;publicsealedclassHttpProtocolException:IOException{publicHttpProtocolException(HttpProtocolErrorerrorCode,stringmessage,Exception?innerException);publicHttpProtocolErrorErrorCode{get;}}// Map enum values to H2 and H3 error codes// H3 error codes can be 62 bit longpublicenumHttpProtocolError:long{// Camel-cased names taken directly from the HTTP/2 spec// https://datatracker.ietf.org/doc/html/rfc7540#section-7NoError=0x0,ProtocolError,InternalError,FlowControlError,SettingsTimeout,StreamClosed,FrameSizeError,RefusedStream,Cancel,CompressionError,EnhanceYourCalm,InadequateSecurity,Http11Required,// Camel-cased names taken directly from the HTTP/3 spec// https://datatracker.ietf.org/doc/html/rfc9114/#section-8.1H3NoError=0x100,H3GeneralProtocolError,H3InternalERror,H3StreamCreationError,H3ClosedCriticalStream,H3FrameUnexpected,H3FrameError,H3ExcessiveLoad,H3IdError,H3SettingsError,H3MissingSettings,H3RequestRejected,H3RequestCancelled,H3RequestIncomplete,H3MessageError,H3ConnectError,H3VersionFallback}

                Edit: Added a public constructor.

                Notes

                • No public constructors, because we prefer to do the bare minimum to deliver a feature. This means that WinHttpHandler and user-made HttpClientHandlers won't be able to throw HttpProtocolException for now. We can consider public constructors later, if necessary.
                • We throw ProtocolError when a connection or stream is aborted, or when we detect a protocol violation ourselves
                • ErrorCode >= 256 means HTTP/3
                • In case of HTTP/3 connection or stream errors, we are embedding QuicException as ProtocolException.InnerException
                • We don't throw HttpProtocolException for transport-level errors

                API Usage

                Over HttpClient

                usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync(".");}catch(HttpRequestExceptionex)when(ex.InnerExceptionisProtocolExceptionprotocolException){Console.WriteLine("HTTP error code: "+protocolException.ProtocolErrorCode)
                if (protocolException.InnerExceptionisQuicExceptionquicException)
                Console.WriteLine("Underlying QUIC error: "+quicException.Message);}catch(HttpRequestExceptionex)when(ex.InnerExceptionisAuthenticationExceptionauthenticationException){// TLS authentication error. Can originate from QUIC, we map some errors to high-level .NET exceptions}

                Over response stream

                usingvarclient=newHttpClient();usingvarresponse=awaitclient.GetAsync(".",HttpCompletionOption.ResponseHeadersRead);usingvarresponseStream=awaitresponse.Content.ReadAsStreamAsync();usingvarmemoryStream=newMemoryStream();try{awaitresponseStream.CopyToAsync(memoryStream);}catch(ProtocolExceptionprotocolException){// HTTP(2|3) protocol error}catch(QuicExceptionquicException){// QUIC transport error}catch(IOExceptionexception)when(ex.InnerExceptionisSocketExceptionsocketException){// TCP transport error}

                Alternative designs

                Parallel independent exception types for HTTP/2 and HTTP/3

                publicclassHttp2ProtocolException:IOException{publicintErrorCode{get;}}// Use System.Net.Quic.QuicException for HTTP/3// public class QuicException : IOException { }
                Usage
                usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync("foo.bar");}// HTTP/2catch(HttpRequestExceptionex)when(ex.InnerExceptionisHttp2ProtocolExceptionprotocolException){Console.WriteLine(protocolException.ProtocolErrorCode)}// HTTP/3catch(HttpRequestExceptionex)when(ex.InnerExceptionisQuicExceptionquicException){Console.WriteLine(quicException.ApplicationErrorCode);}

                Do not define an enum, use untyped long error codes

                publicsealedclassHttpProtocolException:IOException{publiclongErrorCode{get;}}

                Risks

                • HttpProtocolError enum: implementations may send unspecified error codes. If spec evolves in the future, we will have to introduce new error codes. In both cases field would contain a value outside the enum range, so users would have a workaround.
                • Unless I'm missing something, this is not a breaking change, since HttpProtocolException is an IOException.

                Metadata

                Metadata

                Assignees

                Labels

                api-approvedAPI was approved in API review, it can be implementedarea-System.Net.HttpblockingMarks issues that we want to fast track in order to unblock other important work

                Type

                No type

                Projects

                No projects

                  Milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions