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

비밀번호

댓글 작성자
 




... 136  137  138  139  140  141  [142]  143  144  145  146  147  148  149  150  ...
NoWriterDateCnt.TitleFile(s)
1505정성태9/26/201375394기타: 35. Microsoft Office 2007 인증 생략하는 방법 [10]
1504정성태9/24/201330283.NET Framework: 387. UDP 브로드캐스팅을 이용해 서비스 측의 IP 주소를 구하는 방법 [1]파일 다운로드1
1503정성태9/21/201335444개발 환경 구성: 199. Visual Studio - github 연동 [7]
1502정성태9/21/201339056개발 환경 구성: 198. Visual Studio - git을 이용한 로컬 소스 컨트롤
1501정성태9/21/201346157개발 환경 구성: 197. Visual Studio를 위한 Git 환경 설정 [5]
1500정성태9/20/201345127.NET Framework: 386. C# 버전의 한글 형태소 분석기 [1]파일 다운로드1
1499정성태9/20/201321715개발 환경 구성: 196. Windows Azure - Cloud Service의 인스턴스 타입 변경하는 방법
1498정성태9/20/201327809Windows: 76. 윈도우 8.1 / 서버 2012 R2 마이그레이션 [5]
1497정성태9/20/201360105웹: 28. IE 11로 바꾼 후 발생하는 문제 정리
1496정성태9/20/201332406Windows: 75. 윈도우 8.1, 2012 R2 설치 후 원격 접속이 안 되는 문제
1495정성태9/20/201323547웹: 27. IE 11 - YBM Sisa.com에서 검색된 영단어의 발음 기호가 안 나오는 문제
1494정성태9/13/201333146.NET Framework: 385. Html Agility Pack 소개 - 웹 문서에서 텍스트만 분리하는 방법 [2]파일 다운로드1
1493정성태9/13/201334934.NET Framework: 384. WebClient.DownloadString 문자열 인코딩 문제
1492정성태9/13/201322356오류 유형: 186. The .NET assembly 'Microsoft.Vsa' could not be found.
1491정성태9/9/201325479.NET Framework: 383. RSAParameters의 ToXmlString과 ExportParameters의 결과 비교
1490정성태9/7/201360483기타: 34. 도서: 시작하세요! C# 프로그래밍: 기본 문법부터 실전 예제까지 [7]
1489정성태9/4/201344923오류 유형: 185. 오피스 워드 파일이 저장되지 않는 문제 [2]
1488정성태8/27/201329064.NET Framework: 382. WCF에서 DataSet을 binary encoding으로 직렬화하는 방법파일 다운로드1
1487정성태8/27/201331369개발 환경 구성: 195. 로컬 PC에서의 WCF 통신을 Fiddler로 보는 방법 [1]
1486정성태8/27/201328872.NET Framework: 381. SqlCommand를 이용해 Microsoft SQL 서버의 쿼리 실행 계획을 구하는 방법파일 다운로드1
1485정성태8/26/201332558.NET Framework: 380. 프로세스 스스로 풀 덤프 남기는 방법 [3]파일 다운로드1
1484정성태8/23/201326823제니퍼 .NET: 24. 제니퍼 닷넷 적용 사례 (4) - GZIP 인코딩으로 인한 성능 하락
1483정성태8/23/201326935.NET Framework: 379. System.IO.MemoryStream, ArraySegment<T> 의 효율적인 사용법 [1]
1482정성태8/23/201320393.NET Framework: 378. Java / C# - 정수의 부호 유무에 따른 16진수 문자열 변환
1481정성태8/22/201321209오류 유형: 184. PaaS 유형(Cloud Services)의 Azure VM에 연결할 때 계정 만료 에러가 발생하는 경우
1480정성태8/22/201337863개발 환경 구성: 194. 윈도우 서버의 80 포트에 대한 port forwarding 설정 방법파일 다운로드1
... 136  137  138  139  140  141  [142]  143  144  145  146  147  148  149  150  ...