성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>.NET 5부터 HTTP/1.1, 2.0 선택을 위한 HttpVersionPolicy 동작 방식</h1> <p> 이해를 돕기 위해, 우선 다음의 예제로 시작해 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // .NET 5 프로젝트 using System; using System.Net.Http; using System.Threading.Tasks; class Program { private static readonly HttpClient _client; static Program() { _client = new HttpClient(); } static async Task Main(string[] args) { { HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "<span style='color: blue; font-weight: bold'>http</span>://nghttp2.org/"); using var response = await _client.SendAsync(hrm); Console.WriteLine(response.Version); } { HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "<span style='color: blue; font-weight: bold'>https</span>://nghttp2.org/"); using var response = await _client.SendAsync(hrm); Console.WriteLine(response.Version); } } } /* 출력 결과 1.1 1.1 */ </pre> <br /> (나중에 설명하지만 HttpVersionPolicy의 기본값이 RequestVersionOrLower이고) HttpRequestMessage.Version의 기본값이 1.1이기 때문에 http/https 모두 1.1 통신을 합니다. 그리고 .NET 5의 HTTP/2 통신을 위한 지원을 설명하고 있는 다음의 문서를 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HTTP/2 - Version Selection ; <a target='tab' href='https://devblogs.microsoft.com/dotnet/net-5-new-networking-improvements/#version-selection'>https://devblogs.microsoft.com/dotnet/net-5-new-networking-improvements/#version-selection</a> </pre> <br /> <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpversionpolicy'>HttpVersionPolicy</a>에 대한 설명이 나옵니다. 그러니까, 결국 .NET Core 3.0에서 임시 처리한 "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport" 옵션을 대체하면서 기능이 확장된 경우인데요, .NET 5부터는 이 옵션을 이용해야 HTTP/2 통신을 하는 것이 가능합니다.<br /> <br /> 현재 HttpVersionPolicy는 3가지 값이 허용되는데,<br /> <br /> <ul> <li>RequestVersionExact(2)- Only use the requested version.</li> <li>RequestVersionOrHigher(1) - Use the highest available version, downgrading only to the requested version but not below.</li> <li>RequestVersionOrLower(0) - Use the requested version or downgrade to a lower one. This is the default behavior.</li> </ul> <br /> 이 중에서 가장 직관적인 RequestVersionExact부터 살펴보겠습니다. 이 값을 다음과 같이 이용하면 http와 https에서 모두 HTTP/2 통신을 하도록 만들 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > { HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "<span style='color: blue; font-weight: bold'>http</span>://nghttp2.org/") { <span style='color: blue; font-weight: bold'>VersionPolicy = HttpVersionPolicy.RequestVersionExact, Version = HttpVersion.Version20,</span> }; using var response = await _client.SendAsync(hrm); Console.WriteLine(response.Version); } { HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "<span style='color: blue; font-weight: bold'>https</span>://nghttp2.org/") { <span style='color: blue; font-weight: bold'>VersionPolicy = HttpVersionPolicy.RequestVersionExact, Version = HttpVersion.Version20,</span> }; using var response = await _client.SendAsync(hrm); Console.WriteLine(response.Version); } </pre> <br /> <a target='tab' href='https://www.sysnet.pe.kr/2/0/12495#http2_prior'>curl의 동작과 비교하면 마치 --http2-prior-knowledge 옵션</a>을 주고 실행한 것과 같습니다. 유의해야 할 점은, HttpClient의 동작에는 h2c 협상 같은 것은 없습니다. 모두 처음부터 HTTP/2로 요청하든지, HTTP/1.1로 요청하든지 둘 중의 하나입니다. 반면 h2는 HTTP/2로 동작할 것을 요구하는 상황에 한해 TLS 협상이 이뤄집니다. (이 글의 첫 번째 예제 코드에서도 나오지만, https 통신조차도 버전 결정이 1.1인 경우라면 TLS에 h2 협상을 얹지 않습니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그나저나, HttpVersionPolicy의 다른 옵션들은 직관적으로 잘 이해가 안 됩니다. 우선 RequestVersionOrHigher를 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "http://nghttp2.org/") { <span style='color: blue; font-weight: bold'>VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher, Version = HttpVersion.Version11,</span> }; // ==> <span style='color: blue; font-weight: bold'>HTTP/1.1 통신</span> HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "https://nghttp2.org/") { <span style='color: blue; font-weight: bold'>VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher, Version = HttpVersion.Version11,</span> }; // ==> <span style='color: blue; font-weight: bold'>HTTP/2 통신</span> </pre> <br /> (서버가 HTTP/2를 지원했을 때) https의 통신 결과는 상식적이지만, http의 통신 결과는 그렇지 않습니다. h2c 협상 절차를 보면 당연히 HTTP/2 통신으로 넘어가야 하는데 그렇지 못한 것입니다. (위에서도 언급했지만) 실제로 저 상태에서의 http 요청 패킷을 보면 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12495#h2c_upgrade'>h2c 협상</a>을 위한 Upgrade, HTTP2-Settings 헤더가 없습니다. <br /> <br /> 즉, VersionPolicy에 의한 통신 버전 결정은 서버와의 협상을 통해 진행하는 것이 아니라, 그 단계 이전에 내부적인 규칙에 의해 결정됩니다. 그리고 이에 대한 상세한 규칙은 다음의 글에 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HTTP Version Selection #39201 ; <a target='tab' href='https://github.com/dotnet/runtime/pull/39201'>https://github.com/dotnet/runtime/pull/39201</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > RequestVersionOrLower -- current behavior. (기본값) If HTTP/1 and no TLS, use HTTP/1. If HTTP/1 and TLS, use HTTP/1. If HTTP/2 and no TLS, use HTTP/1. If HTTP/2 and TLS, use ALPN to negotiate between HTTP/1 and HTTP/2. RequestVersionOrHigher -- start at the user's version. If HTTP/1 and no TLS, use HTTP/1. If HTTP/1 and TLS, use ALPN to negotiate between HTTP/1 and HTTP/2. If HTTP/2 and no TLS, use HTTP/2 cleartext (H2C) if the server doesn't support H2 (only recognizes H1) we get protocol error (observed with loopback server) it's expected behavior as-is now; discussion: #31132 (comment), code: If HTTP/2 and TLS, use ALPN and fail if server returns error. we leave the original exception as-is now, because we don't know exactly what to look for to be able to map it to a better one RequestVersionExact -- exactly what the user asked for. If HTTP/1 and no TLS, use HTTP/1. If HTTP/1 and TLS, use HTTP/1. If HTTP/2 and no TLS, use HTTP/2 cleartext (H2C) If HTTP/2 and TLS, use ALPN and fail if server returns error. </pre> <br /> 위의 "if HTTP/X"는 사용자가 요청한 Version을 의미합니다. (요청하지 않은 경우는 기본값 1.1) 일례로, 사용자가 요청한 Version이 HttpVersion.Version11이고 "no TLS"라면 - 달리 말해 http 통신이라면 RequestVersionOrLower에서는 HTTP/1.1이 선택되는 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그런데, 규칙을 외우는 것이 좀 힘듭니다. ^^; 그래서 쉽게 다음과 같이 기억할 수 있습니다.<br /> <br /> 일단, http는 1.1 버전이, https는 2.0 버전이 기본 비교 대상에 포함시키고, 그것을 사용자가 요구한 Version에 대해 RequestVersionOrLower 정책에서는 MIN 연산을, RequestVersionOrHigher 정책에서는 MAX 연산을 하는 식입니다.<br /> <br /> 따라서, 위의 규칙에서 RequestVersionOrLower를 다시 표현해 보면 아래와 같이 해석됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > RequestVersionOrLower MIN(1.1, 1.1) // 사용자 요구 버전 1.1, http의 기본 버전 1.1 MIN(1.1, 2.0) // 사용자 요구 버전 1.1, https의 기본 버전 2.0 MIN(2.0, 1.1) // 사용자 요구 버전 2.0, http의 기본 버전 1.1 MIN(2.0, 2.0) // 사용자 요구 버전 2.0, https의 기본 버잔 2.0 </pre> <br /> <hr style='width: 50%' /><br /> <br /> 저렇게 표현해도 헷갈릴 테니 RequestVersionOrHigher의 설명에 나오는,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > RequestVersionOrHigher(1) - Use the highest <span style='color: blue; font-weight: bold'>available version</span>, downgrading only to the requested version but not below. </pre> <br /> "available version"이 내부적으로 http는 1.1, https는 2.0으로 미리 포함되어 있다고 가정해도 됩니다.<br /> <br /> 그에 따라, http에 대해 Version11로 RequestVersionOrHigher 정책이 적용되면 MAX(1.1, 1.1)을 처리하는 것이므로 결국 1.1로 결정되고, https에 대해 Version11을 RequestVersionOrHigher 정책에 적용하면 MAX(1.1, 2.0)을 처리하는 것이므로 2.0이 결정되는 식입니다.<br /> <br /> 위의 가정으로 진행하면 다음과 같이 코드를 변경해 실행한 결과에도 수긍이 갑니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "<span style='color: blue; font-weight: bold'>http</span>://nghttp2.org/") { <span style='color: blue; font-weight: bold'>VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher, Version = HttpVersion.Version20,</span> /* 2.0과 1.1 중에 2.0 선택 */ }; // ==> <span style='color: blue; font-weight: bold'>HTTP/2 통신</span> HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "<span style='color: blue; font-weight: bold'>https</span>://nghttp2.org/") { <span style='color: blue; font-weight: bold'>VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher, Version = HttpVersion.Version20,</span> /* 2.0과 2.0 중에 2.0 선택 */ }; // ==> <span style='color: blue; font-weight: bold'>HTTP/2 통신</span> </pre> <br /> <hr style='width: 50%' /><br /> <br /> 위의 "available version"에 대한 규칙을 이제 (HttpRequestMessage.VersionPolicy 속성의 기본값인) RequestVersionOrLower의 동작 방식에도 적용할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "http://nghttp2.org/") { <span style='color: blue; font-weight: bold'>VersionPolicy = HttpVersionPolicy.RequestVersionOrLower, Version = HttpVersion.Version20,</span> /* 2.0과 1.1 중에 1.1 선택 */ }; // ==> <span style='color: blue; font-weight: bold'>HTTP/1.1 통신</span> HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "https://nghttp2.org/") { <span style='color: blue; font-weight: bold'>VersionPolicy = HttpVersionPolicy.RequestVersionOrLower, Version = HttpVersion.Version20,</span> /* 2.0과 2.0 중에 2.0 선택 */ }; // ==> <span style='color: blue; font-weight: bold'>HTTP/2 통신</span> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "http://nghttp2.org/") { <span style='color: blue; font-weight: bold'>VersionPolicy = HttpVersionPolicy.RequestVersionOrLower, Version = HttpVersion.Version11,</span> /* 1.1과 1.1 중에 1.1 선택 */ }; // ==> <span style='color: blue; font-weight: bold'>HTTP/1.1 통신</span> HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "https://nghttp2.org/") { <span style='color: blue; font-weight: bold'>VersionPolicy = HttpVersionPolicy.RequestVersionOrLower, Version = HttpVersion.Version11,</span> /* 1.1과 2.0 중에 1.1 선택 */ }; // ==> <span style='color: blue; font-weight: bold'>HTTP/1.1 통신</span> </pre> </pre> <br /> 좀 이해가 되시나요?!!! ^^<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1717&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로, 개별 요청에 대해 HttpRequestMessage를 설정하는 것은 이것을 인자로 받아들이는 SendAsync에 대해서만 가능하므로, 만약 GetAsync 같은 메서드에 대해서도 버전 정책을 지정하고 싶다면 HttpClient 수준에서 설정해야만 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private static readonly HttpClient _client; static Program() { _client = new HttpClient() { // http, https 요청에 대해 모두 HTTP/2 통신 시도 <span style='color: blue; font-weight: bold'>DefaultRequestVersion = HttpVersion.Version20, DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact,</span> }; } </pre> <br /> 또한, 이 글에서는 설명의 편의를 위해 HTTP/1.0과 HTTP/3 옵션을 언급하지 않았지만, 위의 규칙은 그것들에 대해서도 동일하게 적용할 수 있습니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1140
(왼쪽의 숫자를 입력해야 합니다.)