다음을 통해 공유


HttpWebRequest에서 HttpClient로의 마이그레이션 가이드

이 문서는 개발자가 HttpWebRequest, ServicePoint, ServicePointManager에서 HttpClient로 마이그레이션하는 과정을 안내하는 데 목적이 있습니다. 이전 API의 노후화와 HttpClient이 제공하는 성능 향상, 리소스 관리 개선, 보다 현대적이고 유연한 API 설계 등 다양한 이점으로 인해 마이그레이션이 필요했습니다. 이 문서에 설명된 단계를 따르면 개발자는 코드베이스를 원활하게 전환하고 HttpClient에서 제공하는 기능을 최대한 활용할 수 있습니다.

Warning

HttpWebRequest, ServicePoint, ServicePointManager에서 HttpClient로 마이그레이션하는 것은 단순히 “있으면 좋은” 성능 향상에 그치지 않습니다. .NET(Core)로 전환하면 기존 WebRequest 로직의 성능이 크게 저하될 수 있다는 점을 이해하는 것이 중요합니다. WebRequest는 최소한의 호환성 계층으로 유지되기 때문에 많은 경우 연결 재사용과 같은 많은 최적화가 부족하기 때문입니다. 따라서 애플리케이션의 성능과 리소스 관리를 최신 표준에 맞게 유지하려면 HttpClient로 전환하는 것이 필수적입니다.

HttpWebRequest에서 HttpClient로 마이그레이션

몇 가지 예부터 시작하겠습니다:

HttpWebRequest을 이용한 간단한 GET 요청

다음은 코드가 어떻게 표시되는지 보여주는 예시입니다:

HttpWebRequest request = WebRequest.CreateHttp(uri);
using WebResponse response = await request.GetResponseAsync();

HttpClient을 이용한 간단한 GET 요청

다음은 코드가 어떻게 표시되는지 보여주는 예시입니다:

HttpClient client = new();
using HttpResponseMessage message = await client.GetAsync(uri);

HttpWebRequest을 이용한 간단한 POST 요청

다음은 코드가 어떻게 표시되는지 보여주는 예시입니다:

HttpWebRequest request = WebRequest.CreateHttp(uri);
request.Method = "POST";
request.ContentType = "text/plain";
await using Stream stream = await request.GetRequestStreamAsync();
await stream.WriteAsync("Hello World!"u8.ToArray());
using WebResponse response = await request.GetResponseAsync();

HttpClient을 이용한 간단한 POST 요청

다음은 코드가 어떻게 표시되는지 보여주는 예시입니다:

HttpClient client = new();
using HttpResponseMessage responseMessage = await client.PostAsync(uri, new StringContent("Hello World!"));

HttpWebRequest에서 HttpClient로, SocketsHttpHandler 마이그레이션 가이드

HttpWebRequest이전 API 새 API 주의
Accept Accept 예시: 요청 헤더 설정.
Address RequestUri 예시: 리디렉션된 URI 가져오기.
AllowAutoRedirect AllowAutoRedirect 예시: SocketsHttpHandler 속성 설정하기.
AllowReadStreamBuffering 직접적으로 동등한 API 없음 버퍼링 속성 사용.
AllowWriteStreamBuffering 직접적으로 동등한 API 없음 버퍼링 속성 사용.
AuthenticationLevel 직접적으로 동등한 API 없음 예시: 상호 인증 사용 설정.
AutomaticDecompression AutomaticDecompression 예시: SocketsHttpHandler 속성 설정하기.
CachePolicy 직접적으로 동등한 API 없음 예시: CachePolicy 헤더 적용.
ClientCertificates SslOptions.ClientCertificates HttpClient에서 인증서 관련 속성 사용.
Connection Connection 예시: 요청 헤더 설정.
ConnectionGroupName 동등한 API 없음 해결 방법 없음
ContentLength ContentLength 예시: 콘텐츠 헤더 설정.
ContentType ContentType 예시: 콘텐츠 헤더 설정.
ContinueDelegate 동등한 API 없음 해결 방법이 없습니다.
ContinueTimeout Expect100ContinueTimeout 예시: SocketsHttpHandler 속성 설정.
CookieContainer CookieContainer 예시: SocketsHttpHandler 속성 설정.
Credentials Credentials 예시: SocketsHttpHandler 속성 설정.
Date Date 예시: 요청 헤더 설정.
DefaultCachePolicy 직접적으로 동등한 API 없음 예시: CachePolicy 헤더 적용.
DefaultMaximumErrorResponseLength 직접적으로 동등한 API 없음 예시: HttpClient에서 MaximumErrorResponseLength 설정.
DefaultMaximumResponseHeadersLength 동등한 API 없음 MaxResponseHeadersLength을 대신 사용할 수 있습니다.
DefaultWebProxy 동등한 API 없음 Proxy을 대신 사용할 수 있습니다.
Expect Expect 예시: 요청 헤더 설정.
HaveResponse 동등한 API 없음 HttpResponseMessage 인스턴스가 있는 것으로 암시됩니다.
Headers Headers 예시: 요청 헤더 설정.
Host Host 예시: 요청 헤더 설정.
IfModifiedSince IfModifiedSince 예시: 요청 헤더 설정.
ImpersonationLevel 직접적으로 동등한 API 없음 예시: ImpersonationLevel 변경.
KeepAlive 직접적으로 동등한 API 없음 예시: 요청 헤더 설정.
MaximumAutomaticRedirections MaxAutomaticRedirections 예시: SocketsHttpHandler 속성 설정하기.
MaximumResponseHeadersLength MaxResponseHeadersLength 예시: SocketsHttpHandler 속성 설정하기.
MediaType 직접적으로 동등한 API 없음 예시: 콘텐츠 헤더 설정.
Method Method 예시: HttpRequestMessage 속성 사용법.
Pipelined 동등한 API 없음 HttpClient은 파이프라이닝을 지원하지 않습니다.
PreAuthenticate PreAuthenticate
ProtocolVersion HttpRequestMessage.Version 예시: HttpRequestMessage 속성 사용법.
Proxy Proxy 예시: SocketsHttpHandler 속성 설정하기.
ReadWriteTimeout 직접적으로 동등한 API 없음 SocketsHttpHandle 및 ConnectCallback 사용법.
Referer Referrer 예시: 요청 헤더 설정.
RequestUri RequestUri 예시: HttpRequestMessage 속성 사용법.
SendChunked TransferEncodingChunked 예시: 요청 헤더 설정.
ServerCertificateValidationCallback SslOptions.RemoteCertificateValidationCallback 예시: SocketsHttpHandler 속성 설정하기.
ServicePoint 동등한 API 없음 ServicePointHttpClient의 일부가 아닙니다.
SupportsCookieContainer 동등한 API 없음 HttpClient의 경우 항상 true입니다.
Timeout Timeout
TransferEncoding TransferEncoding 예시: 요청 헤더 설정.
UnsafeAuthenticatedConnectionSharing 동등한 API 없음 해결 방법 없음
UseDefaultCredentials 직접적으로 동등한 API 없음 예시: SocketsHttpHandler 속성 설정하기.
UserAgent UserAgent 예시: 요청 헤더 설정.

ServicePoint(Manager) 사용량 마이그레이션

ServicePointManager는 정적 클래스이므로 해당 속성을 변경하면 애플리케이션 내에서 새로 생성된 모든 ServicePoint 객체에 전역적으로 영향을 미친다는 점에 유의해야 합니다. 예를 들어 ConnectionLimit 또는 Expect100Continue와 같은 속성을 수정하면 모든 새 ServicePoint 인스턴스에 영향을 미칩니다.

Warning

최신 .NET에서 HttpClientServicePointManager에 설정된 구성을 고려하지 않습니다.

ServicePointManager 속성 매핑

ServicePointManager이전 API 새 API 주의
CheckCertificateRevocationList SslOptions.CertificateRevocationCheckMode 예시: SocketsHttpHandler로 CRL 검사 활성화.
DefaultConnectionLimit MaxConnectionsPerServer 예시: SocketsHttpHandler 속성 설정하기.
DnsRefreshTimeout 동등한 API 없음 예시: DNS Round Robin 사용.
EnableDnsRoundRobin 동등한 API 없음 예시: DNS Round Robin 사용.
EncryptionPolicy SslOptions.EncryptionPolicy 예시: SocketsHttpHandler 속성 설정하기.
Expect100Continue ExpectContinue 예시: 요청 헤더 설정.
MaxServicePointIdleTime PooledConnectionIdleTimeout 예시: SocketsHttpHandler 속성 설정하기.
MaxServicePoints 동등한 API 없음 ServicePointHttpClient의 일부가 아닙니다.
ReusePort 직접적으로 동등한 API 없음 SocketsHttpHandle 및 ConnectCallback 사용법.
SecurityProtocol SslOptions.EnabledSslProtocols 예시: SocketsHttpHandler 속성 설정하기.
ServerCertificateValidationCallback SslOptions.RemoteCertificateValidationCallback 둘 다 RemoteCertificateValidationCallback입니다.
UseNagleAlgorithm 직접적으로 동등한 API 없음 SocketsHttpHandle 및 ConnectCallback 사용법.

Warning

최신 .NET에서는 UseNagleAlgorithmExpect100Continue 속성의 기본값이 false으로 설정되어 있습니다. 이 값은 .NET Framework에서 기본적으로 true입니다.

ServicePointManager매핑 방법

ServicePointManager이전 API 새 API 주의
FindServicePoint 동등한 API 없음 해결 방법 없음
SetTcpKeepAlive 직접적으로 동등한 API 없음 SocketsHttpHandle 및 ConnectCallback 사용법.

ServicePoint 속성 매핑

ServicePoint이전 API 새 API 주의
Address HttpRequestMessage.RequestUri 요청 URL이며, 이 정보는 HttpRequestMessage에서 확인할 수 있습니다.
BindIPEndPointDelegate 직접적으로 동등한 API 없음 SocketsHttpHandle 및 ConnectCallback 사용법.
Certificate 직접적으로 동등한 API 없음 이 정보는 RemoteCertificateValidationCallback에서 가져올 수 있습니다. 예시: 인증서 가져오기.
ClientCertificate 동등한 API 없음 예시: 상호 인증 사용 설정.
ConnectionLeaseTimeout SocketsHttpHandler.PooledConnectionLifetime HttpClient의 동등한 설정
ConnectionLimit MaxConnectionsPerServer 예시: SocketsHttpHandler 속성 설정하기.
ConnectionName 동등한 API 없음 해결 방법 없음
CurrentConnections 동등한 API 없음 .NET의 네트워킹 원격 분석을 참조하세요.
Expect100Continue ExpectContinue 예시: 요청 헤더 설정.
IdleSince 동등한 API 없음 해결 방법 없음
MaxIdleTime PooledConnectionIdleTimeout 예시: SocketsHttpHandler 속성 설정하기.
ProtocolVersion HttpRequestMessage.Version 예시: HttpRequestMessage 속성 사용법.
ReceiveBufferSize 직접적으로 동등한 API 없음 SocketsHttpHandle 및 ConnectCallback 사용법.
SupportsPipelining 동등한 API 없음 HttpClient은 파이프라이닝을 지원하지 않습니다.
UseNagleAlgorithm 직접적으로 동등한 API 없음 SocketsHttpHandle 및 ConnectCallback 사용법.

ServicePoint매핑 방법

ServicePoint이전 API 새 API 주의
CloseConnectionGroup 해당 항목 없음 해결 방법 없음
SetTcpKeepAlive 직접적으로 동등한 API 없음 SocketsHttpHandle 및 ConnectCallback 사용법.

HttpClient 및 HttpRequestMessage 속성 사용법

.NET에서 HttpClient로 작업할 때는 HTTP 요청 및 응답을 구성하고 사용자 지정할 수 있는 다양한 프로퍼티에 액세스할 수 있습니다. 이러한 속성을 이해하면 HttpClient를 최대한 활용하고 애플리케이션이 웹 서비스와 효율적이고 안전하게 통신하는 데 도움이 될 수 있습니다.

예시: HttpRequestMessage 속성 사용법

다음은 HttpClient와 HttpRequestMessage를 함께 사용하는 방법의 예시입니다.

var client = new HttpClient();

var request = new HttpRequestMessage(HttpMethod.Post, "https://example.com"); // Method and RequestUri usage
var request = new HttpRequestMessage() // Alternative way to set RequestUri and Method
{
    RequestUri = new Uri("https://example.com"),
    Method = HttpMethod.Post
};
request.Headers.Add("Custom-Header", "value");
request.Content = new StringContent("somestring");

using var response = await client.SendAsync(request);
var protocolVersion = response.RequestMessage.Version; // Fetch `ProtocolVersion`.

예시: 리디렉션된 URI 가져오기

다음은 리디렉션된 URI를 가져오는 방법의 예시입니다(HttpWebRequest.Address과 동일).

var client = new HttpClient();
using var response = await client.GetAsync(uri);
var redirectedUri = response.RequestMessage.RequestUri;

SocketsHttpHandle 및 ConnectCallback 사용법

SocketsHttpHandlerConnectCallback 속성을 통해 개발자는 TCP 연결 설정 프로세스를 사용자 지정할 수 있습니다. 이는 DNS 확인을 제어하거나 연결에 특정 소켓 옵션을 적용해야 하는 시나리오에 유용할 수 있습니다. ConnectCallback을 사용하면 HttpClient에서 사용하기 전에 연결 프로세스를 가로채서 수정할 수 있습니다.

예시: 소켓에 IP 주소 바인딩

HttpWebRequest을 사용하는 기존 접근 방식에서는 사용자 지정 로직을 사용하여 특정 IP 주소를 소켓에 바인딩했을 수 있습니다. HttpClientConnectCallback를 사용하여 유사한 기능을 구현하는 방법은 다음과 같습니다.

이전 코드 사용 HttpWebRequest:

HttpWebRequest request = WebRequest.CreateHttp(uri);
request.ServicePoint.BindIPEndPointDelegate = (servicePoint, remoteEndPoint, retryCount) =>
{
    // Bind to a specific IP address
    IPAddress localAddress = IPAddress.Parse("192.168.1.100");
    return new IPEndPoint(localAddress, 0);
};
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();

HttpClientConnectCallback를 사용한 새 코드:

var handler = new SocketsHttpHandler
{
    ConnectCallback = async (context, cancellationToken) =>
    {
        // Bind to a specific IP address
        IPAddress localAddress = IPAddress.Parse("192.168.1.100");
        var socket = new Socket(localAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        try
        {
            socket.Bind(new IPEndPoint(localAddress, 0));
            await socket.ConnectAsync(context.DnsEndPoint, cancellationToken);
            return new NetworkStream(socket, ownsSocket: true);
        }
        catch
        {
            socket.Dispose();
            throw;
        }
    }
};
var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);

예시: 특정 소켓 옵션 적용

TCP 킵얼라이브 활성화와 같은 특정 소켓 옵션을 적용해야 하는 경우 ConnectCallback을 사용하여 HttpClient에서 사용하기 전에 소켓을 구성할 수 있습니다. 실제로 ConnectCallback이 소켓 옵션을 더 유연하게 구성할 수 있습니다.

이전 코드 사용 HttpWebRequest:

ServicePointManager.ReusePort = true;
HttpWebRequest request = WebRequest.CreateHttp(uri);
request.ServicePoint.SetTcpKeepAlive(true, 60000, 1000);
request.ServicePoint.ReceiveBufferSize = 8192;
request.ServicePoint.UseNagleAlgorithm = false;
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();

HttpClientConnectCallback를 사용한 새 코드:

var handler = new SocketsHttpHandler
{
    ConnectCallback = async (context, cancellationToken) =>
    {
        var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
        try
        {
            // Setting TCP Keep Alive
            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
            socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, 60);
            socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 1);

            // Setting ReceiveBufferSize
            socket.ReceiveBufferSize = 8192;

            // Enabling ReusePort
            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseUnicastPort, true);

            // Disabling Nagle Algorithm
            socket.NoDelay = true;

            await socket.ConnectAsync(context.DnsEndPoint, cancellationToken);
            return new NetworkStream(socket, ownsSocket: true);
        }
        catch
        {
            socket.Dispose();
            throw;
        }
    }
};
var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);

예: DNS 라운드 로빈 활성화

DNS Round Robin은 단일 도메인 이름과 연결된 IP 주소 목록을 순환하여 여러 서버에 네트워크 트래픽을 분산하는 데 사용되는 기술입니다. 이는 로드 밸런싱과 서비스 가용성 향상에 도움이 됩니다. HttpClient를 사용하는 경우, DNS 확인을 수동으로 처리하고 SocketsHttpHandler의 ConnectCallback 속성을 사용하여 IP 주소를 순환하는 방식으로 DNS Round Robin을 구현할 수 있습니다.

HttpClient에서 DNS Round Robin을 사용하려면 ConnectCallback 속성을 사용하여 DNS 항목을 수동으로 확인하고 IP 주소를 순환할 수 있습니다. 다음은 HttpWebRequestHttpClient의 예시입니다.

이전 코드 사용 HttpWebRequest:

ServicePointManager.DnsRefreshTimeout = 60000;
ServicePointManager.EnableDnsRoundRobin = true;
HttpWebRequest request = WebRequest.CreateHttp(uri);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();

이전 HttpWebRequest API에서는 이 기능이 기본 지원되었기 때문에 DNS Round Robin을 활성화하는 것이 간단했습니다. 그러나 최신 HttpClient API는 동일한 기본 제공 기능을 제공하지 않습니다. 그럼에도 불구하고 DNS 확인을 통해 반환된 IP 주소를 수동으로 순환하는 DnsRoundRobinConnector을 구현하여 유사한 동작을 얻을 수 있습니다.

HttpClient을 사용한 새 코드:

// This is available as NuGet Package: https://www.nuget.org/packages/DnsRoundRobin/
// The original source code can be found also here: https://github.com/MihaZupan/DnsRoundRobin
public sealed class DnsRoundRobinConnector : IDisposable

DnsRoundRobinConnector의 구현 방법은 여기에서 확인할 수 있습니다.

DnsRoundRobinConnector 사용법:

private static readonly DnsRoundRobinConnector s_roundRobinConnector = new(
        dnsRefreshInterval: TimeSpan.FromSeconds(10),
        endpointConnectTimeout: TimeSpan.FromSeconds(5));
static async Task DnsRoundRobinConnectAsync()
{
    var handler = new SocketsHttpHandler
    {
        ConnectCallback = async (context, cancellation) =>
        {
            Socket socket = await DnsRoundRobinConnector.Shared.ConnectAsync(context.DnsEndPoint, cancellation);
            // Or you can create and use your custom DnsRoundRobinConnector instance
            // Socket socket = await s_roundRobinConnector.ConnectAsync(context.DnsEndPoint, cancellation);
            return new NetworkStream(socket, ownsSocket: true);
        }
    };
    var client = new HttpClient(handler);
    HttpResponseMessage response = await client.GetAsync(Uri);
}

예시: SocketsHttpHandler 속성 설정

SocketsHttpHandler는 HTTP 연결을 관리하기 위한 고급 구성 옵션을 제공하는 강력하고 유연한 .NET의 핸들러입니다. SocketsHttpHandler의 다양한 속성을 설정하여 성능 최적화, 보안 강화, 사용자 지정 연결 처리 등 특정 요구 사항을 충족하도록 HTTP 클라이언트의 동작을 미세 조정할 수 있습니다.

다음은 다양한 프로퍼티로 SocketsHttpHandler를 구성하고 HttpClient와 함께 사용하는 방법의 예시입니다.

var cookieContainer = new CookieContainer();
cookieContainer.Add(new Cookie("cookieName", "cookieValue"));

var handler = new SocketsHttpHandler
{
    AllowAutoRedirect = true,
    AutomaticDecompression = DecompressionMethods.All,
    Expect100ContinueTimeout = TimeSpan.FromSeconds(1),
    CookieContainer = cookieContainer,
    Credentials = new NetworkCredential("user", "pass"),
    MaxAutomaticRedirections = 10,
    MaxResponseHeadersLength = 1,
    Proxy = new WebProxy("http://proxyserver:8080"), // Don't forget to set UseProxy
    UseProxy = true,
};

var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);

예시: ImpersonationLevel 변경

이 기능은 특정 플랫폼에 한정되어 있으며 다소 오래된 기능입니다. 해결 방법이 필요한 경우 코드의 이 섹션을 참조하세요.

HttpClient로 작업할 때 서버 인증서의 사용자 지정 유효성 검사 또는 서버 인증서 가져오기 등 다양한 목적으로 클라이언트 인증서를 처리해야 할 수 있습니다. HttpClient은 인증서를 효과적으로 관리하기 위한 몇 가지 속성과 옵션을 제공합니다.

예시: SocketsHttpHandler로 인증서 해지 목록 확인

SocketsHttpHandler.SslOptionsCheckCertificateRevocationList 속성을 통해 개발자는 SSL/TLS 핸드셰이크 중 인증서 해지 목록(CRL) 확인을 사용하거나 사용하지 않도록 설정할 수 있습니다. 이 속성을 사용하도록 설정하면 클라이언트가 서버의 인증서가 해지되었는지 확인하여 연결의 보안을 강화합니다.

이전 코드 사용 HttpWebRequest:

ServicePointManager.CheckCertificateRevocationList = true;
HttpWebRequest request = WebRequest.CreateHttp(uri);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();

HttpClient을 사용한 새 코드:

bool checkCertificateRevocationList = true;
var handler = new SocketsHttpHandler
{
    SslOptions =
    {
        CertificateRevocationCheckMode = checkCertificateRevocationList ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
    }
};
var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);

예시: 인증서 가져오기

HttpClientRemoteCertificateValidationCallback에서 인증서를 가져오려면 HttpClientHandler 또는 SocketsHttpHandler.SslOptionsServerCertificateCustomValidationCallback 속성을 사용할 수 있습니다. 이 콜백을 사용하면 SSL/TLS 핸드셰이크 중에 서버의 인증서를 검사할 수 있습니다.

이전 코드 사용 HttpWebRequest:

HttpWebRequest request = WebRequest.CreateHttp(uri);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();
X509Certificate? serverCertificate = request.ServicePoint.Certificate;

HttpClient을 사용한 새 코드:

X509Certificate? serverCertificate = null;
var handler = new SocketsHttpHandler
{
    SslOptions = new SslClientAuthenticationOptions
    {
        RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
        {
            serverCertificate = certificate;

            // Leave the validation as-is.
            return sslPolicyErrors == SslPolicyErrors.None;
        }
    }
};
var client = new HttpClient(handler);
using var response = await client.GetAsync("https://example.com");

예시: 상호 인증 사용 설정

양방향 SSL 또는 클라이언트 인증서 인증이라고도 하는 상호 인증은 클라이언트와 서버가 서로를 인증하는 보안 프로세스입니다. 이렇게 하면 두 당사자가 모두 자신이 주장하는 당사자임을 보장하여 민감한 커뮤니케이션에 대한 추가적인 보안 계층을 제공합니다. HttpClient에서 클라이언트 인증서를 포함하도록 HttpClientHandler 또는 SocketsHttpHandler을 구성하고 서버의 인증서를 유효성 검사하여 상호 인증을 활성화할 수 있습니다.

상호 인증을 사용하려면 다음 단계를 따르세요.

  • 클라이언트 인증서를 로드합니다.
  • 클라이언트 인증서를 포함하도록 HttpClientHandler 또는 SocketsHttpHandler를 구성합니다.
  • 사용자 지정 유효성 검사가 필요한 경우 서버 인증서 유효성 검사 콜백을 설정합니다.

다음은 SocketsHttpHandler를 사용한 예제입니다.

var handler = new SocketsHttpHandler
{
    SslOptions = new SslClientAuthenticationOptions
    {
        ClientCertificates = new X509CertificateCollection
        {
            // Load the client certificate from a file
            new X509Certificate2("path_to_certificate.pfx", "certificate_password")
        },
        RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
        {
            // Custom validation logic for the server certificate
            return sslPolicyErrors == SslPolicyErrors.None;
        }
    }
};

var client = new HttpClient(handler);
using var response = await client.GetAsync(uri);

헤더 속성 사용

헤더는 HTTP 통신에서 중요한 역할을 하며 요청과 응답에 대한 필수 메타데이터를 제공합니다. .NET에서 HttpClient로 작업할 때 다양한 헤더 속성을 설정하고 관리하여 HTTP 요청 및 응답의 동작을 제어할 수 있습니다. 이러한 헤더 속성을 효과적으로 사용하는 방법을 해석하면 애플리케이션이 웹 서비스와 효율적이고 안전하게 통신하는 데 도움이 될 수 있습니다.

요청 헤더 설정

요청 헤더는 서버에 요청에 대한 추가 정보를 제공하는 데 사용됩니다. 일반적인 사용 사례로는 콘텐츠 유형 지정, 인증 토큰 설정, 사용자 지정 헤더 추가 등이 있습니다. HttpClientDefaultRequestHeaders 속성 또는 HttpRequestMessage의 헤더 속성을 사용하여 요청 헤더를 설정할 수 있습니다.

예시: 사용자 지정 요청 헤더 설정

HttpClient에서 기본 사용자 정의 요청 헤더 설정하기

var client = new HttpClient();
client.DefaultRequestHeaders.Add("Custom-Header", "value");

HttpRequestMessage에서 사용자 지정 요청 헤더 설정하기

var request = new HttpRequestMessage(HttpMethod.Get, uri);
request.Headers.Add("Custom-Header", "value");

예시: 공통 요청 헤더 설정

.NET에서 HttpRequestMessage로 작업할 때 서버에 요청에 대한 추가 정보를 제공하려면 공통 요청 헤더를 설정하는 것이 필수적입니다. 이러한 헤더에는 인증 토큰 등이 포함될 수 있습니다. 이러한 헤더를 올바르게 구성하면 서버에서 HTTP 요청을 올바르게 처리할 수 있습니다. HttpRequestHeaders에서 사용할 수 있는 공통 속성의 전체 목록은 속성을 참조하세요.

HttpRequestMessage에서 공통 요청 헤더를 설정하려면 HttpRequestMessage 객체의 Headers 속성을 사용하면 됩니다. 이 속성은 필요에 따라 헤더를 추가하거나 수정할 수 있는 HttpRequestHeaders 컬렉션에 대한 액세스를 제공합니다.

HttpClient에서 일반적인 기본 요청 헤더 설정하기

using System.Net.Http.Headers;

var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "token");

HttpRequestMessage에서 공통 요청 헤더 설정하기

using System.Net.Http.Headers;

var request = new HttpRequestMessage(HttpMethod.Get, uri);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "token");

예시: 콘텐츠 헤더 설정

콘텐츠 헤더는 HTTP 요청 또는 응답의 본문에 대한 추가 정보를 제공하는 데 사용됩니다. .NET에서 HttpClient로 작업할 때 콘텐츠 헤더를 설정하여 전송 또는 수신되는 콘텐츠와 관련된 미디어 유형, 인코딩 및 기타 메타데이터를 지정할 수 있습니다. 콘텐츠 헤더를 올바르게 구성하면 서버와 클라이언트가 콘텐츠를 올바르게 해석하고 처리할 수 있습니다.

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, uri);

// Create the content and set the content headers
var jsonData = "{\"key\":\"value\"}";
var content = new StringContent(jsonData, Encoding.UTF8, "application/json");

// The following headers are set automatically by `StringContent`. If you wish to override their values, you can do it like so:
// content.Headers.ContentType = new MediaTypeHeaderValue("application/json; charset=utf-8");
// content.Headers.ContentLength = Encoding.UTF8.GetByteCount(jsonData);

// Assign the content to the request
request.Content = content;

using var response = await client.SendAsync(request);

예시: HttpClient에서 MaximumErrorResponseLength 설정하기

MaximumErrorResponseLength을 사용하면 개발자가 핸들러가 버퍼링할 오류 응답 콘텐츠의 최대 길이를 지정할 수 있습니다. 이는 서버에서 오류 응답이 수신될 때 메모리에 읽고 저장되는 데이터의 양을 제어하는 데 유용합니다. 이 기술을 사용하면 대용량 오류 응답을 처리할 때 과도한 메모리 사용을 방지하고 애플리케이션의 성능을 개선할 수 있습니다.

이를 수행하는 방법에는 몇 가지가 있는데, 이 예제에서는 TruncatedReadStream 기법을 살펴보겠습니다.

internal sealed class TruncatedReadStream(Stream innerStream, long maxSize) : Stream
{
    private long _maxRemainingLength = maxSize;
    public override bool CanRead => true;
    public override bool CanSeek => false;
    public override bool CanWrite => false;

    public override long Length => throw new NotSupportedException();
    public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }

    public override void Flush() => throw new NotSupportedException();

    public override int Read(byte[] buffer, int offset, int count)
    {
        return Read(new Span<byte>(buffer, offset, count));
    }

    public override int Read(Span<byte> buffer)
    {
        int readBytes = innerStream.Read(buffer.Slice(0, (int)Math.Min(buffer.Length, _maxRemainingLength)));
        _maxRemainingLength -= readBytes;
        return readBytes;
    }

    public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
    {
        return ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
    }

    public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
    {
        int readBytes = await innerStream.ReadAsync(buffer.Slice(0, (int)Math.Min(buffer.Length, _maxRemainingLength)), cancellationToken)
            .ConfigureAwait(false);
        _maxRemainingLength -= readBytes;
        return readBytes;
    }

    public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
    public override void SetLength(long value) => throw new NotSupportedException();
    public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();

    public override ValueTask DisposeAsync() => innerStream.DisposeAsync();

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            innerStream.Dispose();
        }
    }
}

그리고 TruncatedReadStream의 사용 예시입니다.

int maxErrorResponseLength = 1 * 1024; // 1 KB

HttpClient client = new HttpClient();
using HttpResponseMessage response = await client.GetAsync(Uri);

if (response.Content is not null)
{
    Stream responseReadStream = await response.Content.ReadAsStreamAsync();
    // If MaxErrorResponseLength is set and the response status code is an error code, then wrap the response stream in a TruncatedReadStream
    if (maxErrorResponseLength >= 0 && !response.IsSuccessStatusCode)
    {
        responseReadStream = new TruncatedReadStream(responseReadStream, maxErrorResponseLength);
    }
    // Read the response stream
    Memory<byte> buffer = new byte[1024];
    int readValue = await responseReadStream.ReadAsync(buffer);
}

예시: CachePolicy 헤더 적용

Warning

HttpClient에는 응답을 캐시하는 로직이 내장되어 있지 않습니다. 모든 캐싱을 직접 구현하는 것 외에는 해결 방법이 없습니다. 단순히 헤더를 설정하는 것만으로는 캐싱이 이루어지지 않습니다.

HttpWebRequest에서 HttpClient로 마이그레이션할 때 pragmacache-control와 같은 캐시 관련 헤더를 올바르게 처리하는 것이 중요합니다. 이러한 헤더는 응답이 캐시되고 검색되는 방식을 제어하여 애플리케이션이 성능 및 데이터 최신성 측면에서 예상대로 작동하도록 합니다.

HttpWebRequest에서 CachePolicy 속성을 사용하여 이러한 헤더를 설정했을 수 있습니다. 그러나 HttpClient에서는 요청에서 이러한 헤더를 수동으로 설정해야 합니다.

이전 코드 사용 HttpWebRequest:

HttpWebRequest request = WebRequest.CreateHttp(uri);
request.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore);
using HttpWebResponse response = (HttpWebResponse)request.GetResponse();

이전 HttpWebRequest API에서는 이 기능이 기본적으로 지원되었기 때문에 CachePolicy를 적용하는 것이 간단했습니다. 그러나 최신 HttpClient API는 동일한 기본 제공 기능을 제공하지 않습니다. 그럼에도 불구하고 캐시 관련 헤더를 수동으로 추가하는 AddCacheControlHeaders을 구현하여 유사한 동작을 얻을 수 있습니다.

HttpClient을 사용한 새 코드:

public static class CachePolicy
{
    public static void AddCacheControlHeaders(HttpRequestMessage request, RequestCachePolicy policy)

AddCacheControlHeaders의 구현 방법은 여기에서 확인할 수 있습니다.

AddCacheControlHeaders 사용법:

static async Task AddCacheControlHeaders()
{
    HttpClient client = new HttpClient();
    HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, Uri);
    CachePolicy.AddCacheControlHeaders(requestMessage, new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore));
    HttpResponseMessage response = await client.SendAsync(requestMessage);
}

버퍼링 속성 사용

HttpWebRequest에서 HttpClient로 마이그레이션할 때는 이 두 API가 버퍼링을 처리하는 방식의 차이점을 이해하는 것이 중요합니다.

이전 코드 사용 HttpWebRequest:

HttpWebRequest에서는 AllowWriteStreamBufferingAllowReadStreamBuffering 속성을 통해 버퍼링 속성을 직접 제어할 수 있습니다. 이러한 속성은 서버와 주고받는 데이터의 버퍼링을 활성화 또는 비활성화합니다.

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.AllowReadStreamBuffering = true; // Default is `false`.
request.AllowWriteStreamBuffering = false; // Default is `true`.

HttpClient을 사용한 새 코드:

HttpClient에서 AllowWriteStreamBufferingAllowReadStreamBuffering 속성과 직접적으로 등가되는 속성은 없습니다.

HttpClient는 자체적으로 요청 본문을 버퍼링하지 않고 대신 사용된 HttpContent에 책임을 위임합니다. StringContent 또는 ByteArrayContent와 같은 콘텐츠는 논리적으로 이미 메모리에 버퍼링되어 있는 반면, StreamContent을 사용하면 기본적으로 버퍼링이 발생하지 않습니다. 콘텐츠를 강제로 버퍼링하려면 요청을 보내기 전에 HttpContent.LoadIntoBufferAsync을 호출하면 됩니다. 예를 들어 다음과 같습니다.

HttpClient client = new HttpClient();

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri);
request.Content = new StreamContent(yourStream);
await request.Content.LoadIntoBufferAsync();

HttpResponseMessage response = await client.SendAsync(request);

HttpClient에서는 읽기 버퍼링이 기본적으로 활성화되어 있습니다. 이를 방지하려면 HttpCompletionOption.ResponseHeadersRead 플래그를 지정하거나 GetStreamAsync 도우미를 사용할 수 있습니다.

HttpClient client = new HttpClient();

using HttpResponseMessage response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
await using Stream responseStream = await response.Content.ReadAsStreamAsync();

// Or simply
await using Stream responseStream = await client.GetStreamAsync(uri);