Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 5개 있습니다.)
개발 환경 구성: 361. Azure Web App(App Service)의 HTTP/2 프로토콜 지원
; https://www.sysnet.pe.kr/2/0/11493

웹: 40. IIS의 HTTP/2 지원 여부 - h2, h2c
; https://www.sysnet.pe.kr/2/0/12495

.NET Framework: 1014. ASP.NET Core(Kestrel)의 HTTP/2 지원 여부
; https://www.sysnet.pe.kr/2/0/12500

.NET Framework: 1015. .NET 5부터 HTTP/1.1, 2.0 선택을 위한 HttpVersionPolicy 동작 방식
; https://www.sysnet.pe.kr/2/0/12501

.NET Framework: 1016. .NET Core HttpClient의 HTTP/2 지원
; https://www.sysnet.pe.kr/2/0/12502




.NET 5부터 HTTP/1.1, 2.0 선택을 위한 HttpVersionPolicy 동작 방식

이해를 돕기 위해, 우선 다음의 예제로 시작해 보겠습니다.

// .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, "http://nghttp2.org/");
            using var response = await _client.SendAsync(hrm);
            Console.WriteLine(response.Version);
        }

        {
            HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "https://nghttp2.org/");
            using var response = await _client.SendAsync(hrm);
            Console.WriteLine(response.Version);
        }
    }
}

/* 출력 결과
1.1
1.1
*/

(나중에 설명하지만 HttpVersionPolicy의 기본값이 RequestVersionOrLower이고) HttpRequestMessage.Version의 기본값이 1.1이기 때문에 http/https 모두 1.1 통신을 합니다. 그리고 .NET 5의 HTTP/2 통신을 위한 지원을 설명하고 있는 다음의 문서를 보면,

HTTP/2 - Version Selection
; https://devblogs.microsoft.com/dotnet/net-5-new-networking-improvements/#version-selection

HttpVersionPolicy에 대한 설명이 나옵니다. 그러니까, 결국 .NET Core 3.0에서 임시 처리한 "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport" 옵션을 대체하면서 기능이 확장된 경우인데요, .NET 5부터는 이 옵션을 이용해야 HTTP/2 통신을 하는 것이 가능합니다.

현재 HttpVersionPolicy는 3가지 값이 허용되는데,

  • RequestVersionExact(2)- Only use the requested version.
  • RequestVersionOrHigher(1) - Use the highest available version, downgrading only to the requested version but not below.
  • RequestVersionOrLower(0) - Use the requested version or downgrade to a lower one. This is the default behavior.

이 중에서 가장 직관적인 RequestVersionExact부터 살펴보겠습니다. 이 값을 다음과 같이 이용하면 http와 https에서 모두 HTTP/2 통신을 하도록 만들 수 있습니다.

{
    HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "http://nghttp2.org/")
    {
        VersionPolicy = HttpVersionPolicy.RequestVersionExact,
        Version = HttpVersion.Version20,
    };

    using var response = await _client.SendAsync(hrm);
    Console.WriteLine(response.Version);
}

{
    HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "https://nghttp2.org/")
    {
        VersionPolicy = HttpVersionPolicy.RequestVersionExact,
        Version = HttpVersion.Version20,
    };

    using var response = await _client.SendAsync(hrm);
    Console.WriteLine(response.Version);
}

curl의 동작과 비교하면 마치 --http2-prior-knowledge 옵션을 주고 실행한 것과 같습니다. 유의해야 할 점은, HttpClient의 동작에는 h2c 협상 같은 것은 없습니다. 모두 처음부터 HTTP/2로 요청하든지, HTTP/1.1로 요청하든지 둘 중의 하나입니다. 반면 h2는 HTTP/2로 동작할 것을 요구하는 상황에 한해 TLS 협상이 이뤄집니다. (이 글의 첫 번째 예제 코드에서도 나오지만, https 통신조차도 버전 결정이 1.1인 경우라면 TLS에 h2 협상을 얹지 않습니다.)




그나저나, HttpVersionPolicy의 다른 옵션들은 직관적으로 잘 이해가 안 됩니다. 우선 RequestVersionOrHigher를 보면,

HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "http://nghttp2.org/")
{
    VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher,
    Version = HttpVersion.Version11,
}; // ==> HTTP/1.1 통신

HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "https://nghttp2.org/")
{
    VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher,
    Version = HttpVersion.Version11,
}; // ==> HTTP/2 통신

(서버가 HTTP/2를 지원했을 때) https의 통신 결과는 상식적이지만, http의 통신 결과는 그렇지 않습니다. h2c 협상 절차를 보면 당연히 HTTP/2 통신으로 넘어가야 하는데 그렇지 못한 것입니다. (위에서도 언급했지만) 실제로 저 상태에서의 http 요청 패킷을 보면 h2c 협상을 위한 Upgrade, HTTP2-Settings 헤더가 없습니다.

즉, VersionPolicy에 의한 통신 버전 결정은 서버와의 협상을 통해 진행하는 것이 아니라, 그 단계 이전에 내부적인 규칙에 의해 결정됩니다. 그리고 이에 대한 상세한 규칙은 다음의 글에 나옵니다.

HTTP Version Selection #39201
; https://github.com/dotnet/runtime/pull/39201

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.

위의 "if HTTP/X"는 사용자가 요청한 Version을 의미합니다. (요청하지 않은 경우는 기본값 1.1) 일례로, 사용자가 요청한 Version이 HttpVersion.Version11이고 "no TLS"라면 - 달리 말해 http 통신이라면 RequestVersionOrLower에서는 HTTP/1.1이 선택되는 것입니다.




그런데, 규칙을 외우는 것이 좀 힘듭니다. ^^; 그래서 쉽게 다음과 같이 기억할 수 있습니다.

일단, http는 1.1 버전이, https는 2.0 버전이 기본 비교 대상에 포함시키고, 그것을 사용자가 요구한 Version에 대해 RequestVersionOrLower 정책에서는 MIN 연산을, RequestVersionOrHigher 정책에서는 MAX 연산을 하는 식입니다.

따라서, 위의 규칙에서 RequestVersionOrLower를 다시 표현해 보면 아래와 같이 해석됩니다.

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




저렇게 표현해도 헷갈릴 테니 RequestVersionOrHigher의 설명에 나오는,

RequestVersionOrHigher(1) - Use the highest available version, downgrading only to the requested version but not below.

"available version"이 내부적으로 http는 1.1, https는 2.0으로 미리 포함되어 있다고 가정해도 됩니다.

그에 따라, http에 대해 Version11로 RequestVersionOrHigher 정책이 적용되면 MAX(1.1, 1.1)을 처리하는 것이므로 결국 1.1로 결정되고, https에 대해 Version11을 RequestVersionOrHigher 정책에 적용하면 MAX(1.1, 2.0)을 처리하는 것이므로 2.0이 결정되는 식입니다.

위의 가정으로 진행하면 다음과 같이 코드를 변경해 실행한 결과에도 수긍이 갑니다.

HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "http://nghttp2.org/")
{
    VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher,
    Version = HttpVersion.Version20,  /* 2.0과 1.1 중에 2.0 선택 */
}; // ==> HTTP/2 통신

HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "https://nghttp2.org/")
{
    VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher,
    Version = HttpVersion.Version20,  /* 2.0과 2.0 중에 2.0 선택 */
}; // ==> HTTP/2 통신




위의 "available version"에 대한 규칙을 이제 (HttpRequestMessage.VersionPolicy 속성의 기본값인) RequestVersionOrLower의 동작 방식에도 적용할 수 있습니다.

HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "http://nghttp2.org/")
{
    VersionPolicy = HttpVersionPolicy.RequestVersionOrLower,
    Version = HttpVersion.Version20,  /* 2.0과 1.1 중에 1.1 선택 */
}; // ==> HTTP/1.1 통신

HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "https://nghttp2.org/")
{
    VersionPolicy = HttpVersionPolicy.RequestVersionOrLower,
    Version = HttpVersion.Version20,  /* 2.0과 2.0 중에 2.0 선택 */
}; // ==> HTTP/2 통신

HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "http://nghttp2.org/")
{
    VersionPolicy = HttpVersionPolicy.RequestVersionOrLower,
    Version = HttpVersion.Version11,  /* 1.1과 1.1 중에 1.1 선택 */
}; // ==> HTTP/1.1 통신

HttpRequestMessage hrm = new HttpRequestMessage(HttpMethod.Get, "https://nghttp2.org/")
{
    VersionPolicy = HttpVersionPolicy.RequestVersionOrLower,
    Version = HttpVersion.Version11,  /* 1.1과 2.0 중에 1.1 선택 */
}; // ==> HTTP/1.1 통신

좀 이해가 되시나요?!!! ^^

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




참고로, 개별 요청에 대해 HttpRequestMessage를 설정하는 것은 이것을 인자로 받아들이는 SendAsync에 대해서만 가능하므로, 만약 GetAsync 같은 메서드에 대해서도 버전 정책을 지정하고 싶다면 HttpClient 수준에서 설정해야만 합니다.

private static readonly HttpClient _client;

static Program()
{
    _client = new HttpClient()
    {
        // http, https 요청에 대해 모두 HTTP/2 통신 시도
        DefaultRequestVersion = HttpVersion.Version20,
        DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact,
    };
}

또한, 이 글에서는 설명의 편의를 위해 HTTP/1.0과 HTTP/3 옵션을 언급하지 않았지만, 위의 규칙은 그것들에 대해서도 동일하게 적용할 수 있습니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 6/28/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 181  182  183  184  185  [186]  187  188  189  190  191  192  193  194  195  ...
NoWriterDateCnt.TitleFile(s)
324정성태8/15/200634758    답변글 디버깅 기술: 5.11. PDB 파일과 소스 코드
325정성태9/8/200627487    답변글 디버깅 기술: 5.12. CCP를 이용한 Windows Source Code 수준의 디버깅
329정성태8/19/200626438    답변글 디버깅 기술: 5.13. 소스 서버 구성 [1]
332정성태8/20/200627937    답변글 디버깅 기술: 5.14. GAC 에 등록된 Assembly 디버그 [2]
341정성태9/16/200620290    답변글 디버깅 기술: 5.15. [내용 예약]
342정성태9/16/200637828    답변글 디버깅 기술: 5.16. ASP.NET 디버깅 환경 구성 [1]파일 다운로드1
306정성태2/13/200717348기타: 15. .NET 이 생산성이 높다는 증거(!)
304정성태7/21/200619309VS.NET IDE: 41. 하위 폴더의 모든 프로젝트의 출력물을 제거 (Clean)
305정성태7/21/200618981    답변글 VS.NET IDE: 41.1. 하위 폴더의 모든 프로젝트의 출력물을 제거 (Clean) [1]
303정성태7/20/200616997Team Foundation Server: 12. 사용자 계정 재생성에 따른 Version Control 영향
302정성태8/21/200618587Team Foundation Server: 11. TFS Team Build와 VC++ Project 설정
299정성태7/23/200619004개발 환경 구성: 5. VMWare - VM 생성 화면 캡쳐
300정성태7/15/200623405    답변글 개발 환경 구성: 5.1. VMWare 오류 유형 - The handle is invalid.
301정성태7/18/200618490    답변글 개발 환경 구성: 5.2. VMWare - 사용 후기.
298정성태7/14/200618836개발 환경 구성: 4. VMWare Server를 64bit 운영체제에 설치 시 주의 사항 [2]
296정성태7/10/200628109.NET Framework: 73. [ASP.NET] HTC(DHTML Control Behavior)를 WebResource.axd로 제공하는 방법 [3]
295정성태7/1/200621322VC++: 25. Microsoft National Language Support Downlevel APIs 1.0 사용 방법파일 다운로드1
294정성태6/30/200617892.NET Framework: 72. XSDObjectGen.EXE 기능 개선
293정성태6/29/200619635Team Foundation Server: 10. TFS 버전 컨트롤(TFVC)에 참여시킨 프로젝트의 로컬 경로를 옮기는 방법
290정성태6/26/200617017Team Foundation Server: 9. HTTPS를 통한 Team Server 접근 - 두 번째 이야기 [1]
291정성태6/26/200618339    답변글 Team Foundation Server: 9.1. [선행 작업] HTTPS 를 통한 Team Server 접근 - 두번째 이야기 [1]
292정성태6/26/200617984    답변글 Team Foundation Server: 9.2. TF30177 오류 발생
307정성태8/3/200619675    답변글 Team Foundation Server: 9.3. Team Server 접근 이름을 바꾸는 방법 [1]파일 다운로드1
308정성태2/18/200719413        답변글 Team Foundation Server: 9.4. Team Server HTTPS 접근 완료. ^^ [1]
288정성태6/26/200634265오류 유형: 10. error MSB6006: "aspnet_merge.exe" exited with code 1
286정성태6/23/200622378웹: 4. 웹 사이트 식별자(Identifier) 값 변경
... 181  182  183  184  185  [186]  187  188  189  190  191  192  193  194  195  ...