다음을 통해 공유


REST and POX

이 샘플에서는 indigo1에서 HTTP 전송을 사용하여 POX(Plain Old XML) 메시지, 즉 바깥쪽 SOAP 봉투 없이 XML 페이로드만으로 구성된 메시지를 보내고 받는 방법을 보여 줍니다. POX 메시지는 다양한 유형의 클라이언트에서 보내고 받을 수 있으며, SOAP 기반 프로토콜을 기본적으로 지원하지 않는 웹 브라우저와 같은 클라이언트도 여기에 포함됩니다. POX는 HTTP를 통해 데이터를 교환하며 SOAP 및 WS-*의 고급 프로토콜 기능(예: HTTP가 아닌 전송, 요청/응답이 아닌 메시지 교환 패턴, 메시지 기반 보안, 안정성 및 트랜잭션)을 사용할 필요가 없는 서비스에 적합한 선택입니다.

참고

이제 REST 및 POX 스타일 서비스에 대한 지원을 .NET Framework 3.5에서 직접 제공합니다. 자세한 내용은 .NET Framework 3.5 설명서의 Web Programming Model 항목을 참조하십시오.

POX 서비스 구현

이 샘플의 서비스는 매우 기본적인 고객 데이터베이스를 구현합니다. 계약 측면에서 보면, Message를 입력으로 받아 Message를 반환하는 ProcessMessage라는 작업을 노출합니다.

[ServiceContract]
public interface IUniversalContract
{
    [OperationContract(Action = "*", ReplyAction = "*")]
    Message ProcessMessage(Message input);
}

그러나 이 계약은 그다지 기능적이지 않습니다. 따라서 다음과 같이 HTTP를 사용할 수 있는 방법으로 이 컬렉션의 내용에 액세스하는 기본 주소 지정 규칙을 구현하려고 합니다.

  • 이 컬렉션은 https://localhost:8100/customers에 있습니다. 이 URI로 보낸 HTTP GET 요청은 개별 항목을 가리키는 URI 목록으로 컬렉션의 내용을 반환합니다.
  • 컬렉션의 각 항목은 컬렉션 URI에 고객 ID를 추가하여 만든 고유한 URI를 갖습니다. 예를 들어 https://localhost:8100/customers/1은 ID가 1인 고객을 나타냅니다.
  • 항목 URI가 있으면 해당 항목 URI에 HTTP GET 요청을 보내 고객의 XML 표현을 검색할 수 있습니다.
  • 기존 항목 URI에 새 표현을 적용하기 위해 PUT을 사용하여 항목을 수정할 수 있습니다.
  • 항목 추가는 HTTP POST를 사용하여 새 항목의 내용을 컬렉션 URI에 보내는 방법으로 이루어집니다. 새 항목의 URI는 서버 응답의 HTTP 위치 헤더를 통해 반환됩니다.
  • 항목의 URI에 DELETE 요청을 보내 항목을 제거할 수 있습니다.

이 아키텍처 스타일을 REST(Representational State Transfer)라고 하는데, 이는 HTTP 및 POX 메시지를 사용하여 통신하는 응용 프로그램을 디자인하는 방법 중 하나입니다.

이 모든 작업을 위해 먼저 노출하려는 계약을 구현하는 서비스를 만듭니다.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, 
  AddressFilterMode = AddressFilterMode.Prefix)]
class CustomerService : IUniversalContract
{
    Dictionary<string, Customer> customerList;
    public Message ProcessMessage(Message request) { ... }
}

로컬 customerList 변수는 데이터베이스의 내용을 저장합니다. 이 변수의 내용이 다른 요청에서도 유지되어야 하므로, ServiceBehavior 특성을 사용하여 InstanceContextMode.Single을 지정합니다. 이는 WCF에게 요청이 바뀌더라도 동일한 물리적 서비스 인스턴스를 사용하라고 전달합니다. 또한 서비스에서 구현할 계층적 주소 지정 구조를 지원하는 AddressFilterMode.Prefix를 설정합니다. AddressFilterMode.Prefix는 서비스가 정확하게 주소와 일치하는 URI뿐 아니라 끝점 주소로 시작하는 모든 URI에서 수신 대기하도록 합니다.

서비스는 다음 구성을 사용하여 구성됩니다.

<system.serviceModel>
    <bindings>
        <customBinding>
            <binding name="poxBinding">
                <textMessageEncoding messageVersion="None" />
                <httpTransport />
            </binding>
        </customBinding>
    </bindings>
    <services>
        <service name="Microsoft.ServiceModel.Samples.CustomerService">
          <host>
            <baseAddresses>
              <add baseAddress="https://localhost:8100/" />
            </baseAddresses>
          </host>
            <endpoint address="customers" 
                      binding="customBinding" 
                      bindingConfiguration="poxBinding"
                    contract="Microsoft.ServiceModel.Samples.IUniversalContract" />
        </service>
    </services>
 </system.serviceModel>

이 구성은 단일 끝점(https://localhost:8100/customers)을 사용하여 단일 서비스(https://localhost:8100)를 설정합니다. 이 끝점은 HTTP 전송 및 텍스트 인코더가 있는 사용자 지정 바인딩을 사용하여 통신합니다. 인코더는 MessageVersion.None을 사용하도록 설정되어 있어서 읽기에서 SOAP 봉투가 없는 메시지를 수락할 수 있으며 응답 메시지를 작성할 때 SOAP 봉투가 표시되지 않게 합니다. 즉, 이 샘플에서는 보안이 설정되지 않은 전송을 통한 통신을 보여 줍니다. 보안이 필요한 경우 POX 응용 프로그램은 HTTPS(HTTP 전송 보안)를 포함하는 바인딩을 사용해야 합니다.

ProcessMessage() 구현

ProcessMessage() 구현에서는 들어오는 요청에 존재하는 HTTP 메서드에 따라 다른 작업을 수행하려고 합니다. 그러기 위해서는 Message에서 직접 노출되지 않는 몇 가지 HTTP 프로토콜 정보에 액세스해야 하는데, HttpRequestMessageProperty 클래스를 통해 HTTP 메서드 및 그 밖의 유용한 프로토콜 요소(예: 요청의 헤더 컬렉션)에 액세스할 수 있습니다.

public Message ProcessMessage(Message request)
{
    Message response = null;

    //The HTTP Method (for example, GET) from the incoming HTTP request
    //can be found on the HttpRequestMessageProperty. The MessageProperty
    //is added by the HTTP Transport when the message is received.
    HttpRequestMessageProperty requestProperties =
        (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
    …
}

HttpRequestMessageProperty가 있으면 이를 사용하여 여러 내부 구현 메서드에 디스패치할 수 있습니다.

if (String.Equals("GET", requestProperties.Method,
    StringComparison.OrdinalIgnoreCase))
{
    response = GetCustomer(request);
}
else if (String.Equals("PUT", requestProperties.Method,
    StringComparison.OrdinalIgnoreCase))
{
    response = UpdateCustomer(request);
}
else if (String.Equals("POST", requestProperties.Method,
    StringComparison.OrdinalIgnoreCase))
{
    response = AddCustomer(request);
}
else if (String.Equals("DELETE", requestProperties.Method,
    StringComparison.OrdinalIgnoreCase))
{
    response = DeleteCustomer(request);
}

GET 및 POST가 가장 많이 사용되는 HTTP 메서드이긴 하지만(PUT 및 DELETE는 상대적으로 덜 사용되는 편) HTTP 사양에서는 이 샘플 서비스에서 지원하지 않는 HEAD 및 OPTIONS와 같은 몇 가지 다른 동사도 정의합니다. HTTP 사양에서는 이 용도를 위한 상태 코드(405 메서드를 사용할 수 없음)도 정의합니다. 따라서 이 서비스의 ProcessMessage는 다음 논리를 갖습니다.

else
{
    //This service does not implement handlers for other HTTP verbs (such as HEAD), so we
    //construct a response message and use the HttpResponseMessageProperty to
    //set the HTTP status code to 405 (Method Not Allowed) which indicates the client 
    //used an HTTP verb not supported by the server.
    response = Message.CreateMessage(MessageVersion.None, String.Empty, String.Empty);
    HttpResponseMessageProperty responseProperty = new HttpResponseMessageProperty();
    responseProperty.StatusCode = HttpStatusCode.MethodNotAllowed;

    response.Properties.Add( HttpResponseMessageProperty.Name, responseProperty );
}

상태 코드 및 상태 설명과 같은 응답 관련 속성을 전달한다는 점을 제외하고 HttpResponseMessagePropertyHttpRequestMessageProperty와 매우 비슷합니다. 메시지가 만들어질 때는 기본적으로 이 속성이 없으므로 응답 메시지를 반환하기 전에 Properties 컬렉션에 이를 명시적으로 추가해야 합니다.

서비스 구현 메서드 중 나머지(GetCustomer, AddCustomer 등)는 HttpRequest/HttpResponse 메시지 속성을 비슷하게 활용하며 사용이 간단합니다.

POX 클라이언트 구현

이 샘플 서비스의 REST 아키텍처 때문에 이 샘플의 클라이언트는 기본 HTTP 클라이언트입니다. 여기서는 설명을 위해 WCF HTTP 전송 스택의 맨 위에 구현되지만, 클라이언트에서 WCF를 사용할 수 없는 경우 HttpWebRequest와 함께 사용하거나, 대부분의 최신 웹 브라우저에서 제공하는 XmlHttpRequest 지원과 함께 사용할 수도 있습니다.

WCF를 사용하여 원시 HTTP 요청을 보내려면 메시지를 만들고, HttpRequestMessageProperty에 적절한 값을 설정하고, 선택적으로 엔터티 본문을 서버에 보낼 데이터로 채워야 합니다. 이 프로세스를 더 쉽게 수행하기 위해 기본 HTTP 클라이언트 클래스를 작성할 수 있습니다.

public class HttpClient : ClientBase<IRequestChannel>
{
    public HttpClient( Uri baseUri, bool keepAliveEnabled ) : 
                       this( baseUri.ToString(), keepAliveEnabled )
    {
    }
    
    public HttpClient( string baseUri, bool keepAliveEnabled ) : 
base( HttpClient.CreatePoxBinding( keepAliveEnabled ), 
      new EndpointAddress( baseUri ) )
    {
    }

    //Other members elided for clarity
}

ServiceModel Metadata Utility Tool (Svcutil.exe)에 의해 WSDL 메타데이터로부터 생성되는 클라이언트 클래스처럼 HttpClient 클래스는 ClientBase<TChannel>을 상속합니다. 클라이언트가 매우 일반적인 계약의 관점에서 구현되어 있기는 하지만(IRequestChannel) ClientBase`1<TChannel>은 여전히 유용한 기능을 다양하게 제공합니다. 예를 들어, ChannelFactory<IRequestChannel>을 자동으로 만들고 그 수명을 관리합니다.

서비스에서 사용되는 바인딩과 비슷하게 HTTP 클라이언트는 사용자 지정 바인딩을 사용하여 통신합니다.

private static Binding CreatePoxBinding( bool keepAliveEnabled )
{
    TextMessageEncodingBindingElement encoder = 
new TextMessageEncodingBindingElement( MessageVersion.None, Encoding.UTF8 );

    HttpTransportBindingElement transport = new HttpTransportBindingElement();
    transport.ManualAddressing = true;
    transport.KeepAliveEnabled = keepAliveEnabled;

    return new CustomBinding( new BindingElement[] { encoder, transport } );
}

클라이언트 바인딩에 추가된 주요 요소는 HttpTransportBindingElement에서 ManualAddressingtrue로 설정하는 추가 단계입니다. 일반적으로 ManualAddressingfalse로 설정되어 있으면 전송을 통해 보내진 메시지는 전송의 ChannelFactory가 만들어질 때 제공된 URI로 주소가 지정됩니다. 수동 주소 지정에서는 전송을 통해 보내진 개별 메시지가 요청이 바뀌면 다른 URI를 가질 수 있습니다. 단, 이 URI는 ChannelFactory의 URI와 동일한 접두사를 가져야 합니다. 메시지를 보내려는 URI는 응용 프로그램 계층에서 선택되므로 의도한 대로 사용하려면 ManualAddressingtrue로 설정하는 것이 좋습니다.

HttpClient에서 가장 중요한 메서드는 특정 URI로 메시지를 보내고 서버의 응답을 반환하는 Request입니다. GET 및 DELETE와 같이 HTTP 메시지를 통해 데이터가 전달되는 것을 허용하지 않는 HTTP 메서드의 경우 SuppressEntityBodytrue로 설정하는 Request()의 오버로드를 정의합니다.

public Message Request( Uri requestUri, string httpMethod )
{
    Message request = Message.CreateMessage( MessageVersion.None, String.Empty );
    request.Headers.To = requestUri;

    HttpRequestMessageProperty property = new HttpRequestMessageProperty();
    property.Method = httpMethod;
    property.SuppressEntityBody = true;
    request.Properties.Add( HttpRequestMessageProperty.Name, property );
    return this.Channel.Request( request );
}

POST 및 PUT처럼 요청의 본문에서 데이터 전달을 허용하는 동사를 지원하기 위해 엔터티 본문을 나타내는 개체를 받는 Request()의 오버로드도 정의합니다.

public Message Request( Uri requestUri, string httpMethod, object entityBody )
{
    Message request = Message.CreateMessage( MessageVersion.None, String.Empty, entityBody );
    request.Headers.To = requestUri;

    HttpRequestMessageProperty property = new HttpRequestMessageProperty();
    property.Method = httpMethod;

    request.Properties.Add( HttpRequestMessageProperty.Name, property );
    return this.Channel.Request( request );
}

entityBody 개체는 Message.CreateMessage()에 직접 전달되므로 기본 WCF 동작은 DataContractSerializer를 통해 이 개체 인스턴스를 XML로 변환하는 것입니다. 다른 동작을 원하는 경우 Request()에 전달하기 전에 BodyWriter의 구현에서 이 개체를 래핑할 수 있습니다.

HttpClient의 구현을 마치기 위해 지원하려는 동사에 대한 프로그래밍 모델을 만드는 몇 가지 유틸리티 메서드가 추가됩니다.

public Message Get( Uri requestUri )
{
    return Request( requestUri, "GET" );
}

public Message Post( Uri requestUri, object body )
{
    return Request( requestUri, "POST", body );
}

public Message Put( Uri requestUri, object body )
{
    return Request( requestUri, "PUT", body );
}

public Message Delete( Uri requestUri )
{
    return Request( requestUri, "DELETE" );
}

이 기본 HTTP 도우미 클래스가 있으므로 이 클래스를 사용하여 다음과 비슷한 코드를 작성함으로써 서비스에 대한 HTTP 요청을 수행할 수 있습니다.

HttpClient client = new HttpClient( collectionUri );

//Get Customer 1 by doing an HTTP GET to the customer's URI
requestUri = new Uri( collectionUri, "1" );
Message response = client.Get( requestUri );
string statusCode = client.GetStatusCode( response );
string statusDescription = client.GetStatusDescription( response );
Customer aCustomer = response.GetBody<Customer>();

샘플 실행

샘플을 실행하려면 먼저 서버 프로젝트를 시작합니다. 이 프로젝트는 콘솔 응용 프로그램에서 자체 호스팅 서비스를 시작합니다. 서버는 수신하는 요청의 상태를 콘솔 창에 보고합니다.

서비스 프로젝트가 실행 중이고 메시지를 기다리는 중이면 클라이언트 프로젝트를 시작할 수 있습니다. 클라이언트는 일련의 HTTP 요청을 서버에 보내 HTTP 및 POX 메시지를 사용하여 서버에서 상태를 수정하는 것을 보여 줍니다. 구체적으로 이 클라이언트는 다음 작업을 수행합니다.

  • Customer 1을 검색하고 데이터를 표시합니다.
  • Customer 1의 이름을 "Bob"에서 "Robert"로 변경합니다.
  • Customer 1을 다시 검색하여 서버의 상태가 수정되었음을 표시합니다.
  • Alice 및 Charlie라는 이름의 두 고객을 새로 만듭니다.
  • 서버에서 Customer 1을 삭제합니다.
  • 서버에서 다시 Customer 1을 검색하고 끝점을 찾을 수 없다는 응답을 받습니다.
  • 컬렉션의 현재 내용(링크의 목록)을 가져옵니다.
  • 컬렉션의 각 링크에서 GET 요청을 보내 컬렉션의 각 요소를 검색합니다.

각 요청 및 응답의 출력은 클라이언트 콘솔 창에 표시됩니다.

샘플을 설치, 빌드 및 실행하려면

  1. Windows Communication Foundation 샘플의 일회 설치 절차를 수행했는지 확인합니다.

  2. C# 또는 Visual Basic .NET 버전의 솔루션을 빌드하려면 Windows Communication Foundation 샘플 빌드의 지침을 따릅니다.

  3. 단일 컴퓨터 또는 다중 컴퓨터 구성에서 샘플을 실행하려면 Windows Communication Foundation 샘플 실행의 지침을 따릅니다.

참고 항목

기타 리소스

How To: Create a Basic Self-Hosted Service
How To: Create a Basic IIS-Hosted Service

Send comments about this topic to Microsoft.
© 2007 Microsoft Corporation. All rights reserved.