ASP.NET Core 8과 API개발을 시작하기에 앞서 우리는 모든 Web Application에서 기본이 되는 사항을 되짚어볼 필요가 있습니다. Website가 Browser에서 동작하든 Web Service(Web API)로 동작하든 항상 동일한 원칙이 적용됩니다. Client와 Server는 서로 통신하는 관계로서 Client는 Server에 요청을 보내고 Server는 Client를 이에 대한 응답을 수행하게 됩니다. 그리고 이러한 모든 과정은 'HTTP 통신 Protocol'을 통해 이루어집니다. Protocol이면에 Data는 다양한 형식과 제약조건들을 사용하여 전송될 수 있습니다. 이쯤부터는 REST라는 것이 등장하게 되는데 REST는 Data를 표현하기 위한 기술적 개념입니다. HTTP와 REST를 혼동해서는 안됩니다. 이번 글을 통해 각각에 대해서 자세히 알아볼 것입니다.
1. Web 이면에 숨겨진 HTTP
HTTP는 HyperText Transfer Protocol의 약자로서 Client와 Server사이에 Data를 교환하기 위한 Network Protocol입니다. 이 Protocol은 1990년 영국의 Computer 과학자인 Tim Berners-Lee가 WWW(World Wide Web)의 접근을 위해 개발한 것으로 WWW는 Browser를 통해 HTML(HyperText Markup Language)을 사용하는 Web Page를 URI(Uniform Resource Identifier) Web 주소를 사용하여 검색할 수 있게 한 것입니다. HTTP역사는 사실상 이때 부터 시작되었다고 볼 수 있습니다. 여기서 HTML은 Web Page를 만드는 데 사용되는 언어인 반면 HTTP는 HTML을 넘어 다양한 Data형식을 처리할 수 있는 Web Server로 발전하였습니다. 예를 들어 Web Server는 구조화된 언어로 불리는 XML(Extensible Markup Language)이나 또는 JSON(JavaScript Object Notation)을 다룰 수 있으며 필요한 경우 이들을 통한 입력처리 역시 가능합니다. 물론 XML이나 JSON뿐만 아니라 MIME(Multipurpose Internet Mail Extenstions)와 같은 Data Type 역시 취급할 수 있습니다.
HTTP는 다양한 Data를 다룰 수 있는 Protocol이며 전세계 수많은 사용자를 대상으로 하기에 서로 지켜야 할 특정 규칙을 지정할 필요가 있습니다. 그 규칙이 IETF(Internet Engineering Task Force)에서 정의한 RFC(Request From Comment)이며 HTTP는 바로 이 RFC의 명세를 따르고 있습니다. 숫자로 식별가능한 엄청난 수의 RFC명세가 존재하는데 이들 사이에 공통점은 Internet에 대한 명세를 정의하고 Internet만을 정의한다는 것입니다. HTTP는 RFC 7231에 의해 정의되는데 해당 명세에 대한 자세한 사항은 아래 Link를 참고하시기 바랍니다.
https://www.rfc-editor.org/rfc/rfc7231
여기서는 HTTP를 사용하는 모범적인 사례를 알려드리기 위해 종종 RFC를 언급할 수 있지만 실제 RFC를 구현하는 것은 다를 수 있습니다. 또한 여기서 다뤄지는 HTTP는 ASP.NET Core를 통해 잘 구조화된 API를 제작하는데 필요한 부분만으로 한정됩니다.
HTTP는 계속 진화를 거듭함에 따라 아래와 같은 다양한 Version이 존재합니다. 모든 Version에 대해 상세히 알아둘 필요는 없으며 대략 다음과 같은 Version이 존재하는 것 정도만 이해하고 넘어가면 충분합니다.
- HTTP/0.9(사용되지 않음)
- HTTP/1.0(사용되지 않음)
- HTTP/1.1(사용중)
- HTTP/2(특수목적에 한하여 간헐적으로 사용 중)
- HTTP/3(신규 Version, 극히 드물게 사용)
여기서는 위의 Version 중 HTTP/1.1을 주요 주제로 다룰 것이며 필요한 경우 HTTP/2와 HTTP/3을 부가적으로 알아볼 것입니다.
1) HTTP의 특징
HTTP는 크게 3가지 특징으로 설명될 수 있습니다.
무상태성(Stateless) | Client가 요청을 Server로 보내면 이에 대한 응답을 Client가 Server로 부터 수신합니다. Stateless는 이때 Client나 Server모두 요청과 응답을 주고받는 사이에 어떠한 정보도 관리(보관)하지 않는 다는 것을 의미합니다. |
비연결성(Connectionless) | HTTP연결은 Client와 Server사이에 열리는 것을 말하는데 일단 Client가 Server로 부터 응답을 수신하면 연결은 닫히게 됩니다. 즉, 연결은 Client와 Server사이에 영구적이지 않습니다. |
Media에 독립적임(Independent) | Client와 Server가 서로 교환할 Data에 합의(동의)가 이루어지기만 한다면 Server는 어떠한 유형의 Data도 전송할 수 있습니다. |
HTTP는 무상태성이므로 Client와 Server 간 필요한 경우 특정 정보를 전송해야 할 수 있습니다. 예를 들어 대부분의 Web Application은 사용자가 일부 Web Page사이를 Browsing 하는 동안 동일사용자 여부를 인식하여 이에 대한 응답을 수행합니다. 실제 이러한 구현을 위해서 RFC는 HTTP Cookie가 Browser측면에서 사용자 Data를 유지하도록 설계되어야 함을 정의하고 있습니다. Cookie와 관련되서는 그리 상세히 다루지 않을 것이지만 대체 가능한 더 높은 수준의 최신기술을 알아볼 것입니다. Cookie에 대해 관심이 있다면 아래 문서를 참고하시기 바랍니다.
https://www.rfc-editor.org/rfc/rfc6265
지금은 이러한 특징들이 추상적으로 느껴질 수 있지만 학습을 진행해 나가다 보면 이런 것들이 분명하게 다가올 것입니다.
2) HTTP 요청과 응답
HTTP 요청은 연결이 단절되지 않는 한 그에 따른 응답수신이 수신되는데 모든 요청과 응답은 거의 비슷한 방식으로 작동합니다.
● HTTP요청 및 관련 요소
- Client(Web Browser 및 기타 Application) URI(Server상으로 요청된 Resource주소)를 호출함으로써 HTTP를 요청을 개시합니다. 이때 URI는 수행될 동작을 결정하는 동사를 사용해야 합니다.
- HTTP요청의 Metadata가 전송되고 header가 호출됩니다. 이때 header를 통해 인증정보의 발신과 같은 Server와의 Content 협상을 제어할 수 있습니다.
- 매개변수는 Server와 Content를 교환하고 원하는 응답을 가져오기 위해 필요하며 요청의 본문이나 Route, URI에 포함될 수 있습니다.
● HTTP응답 및 관련 요소
- Server는 HTTP Status Code와 함께 응답을 반환함으로써 HTTP요청 처리방식을 확인할 수 있습니다.
- Server는 Client가 다른 Metadata를 제공하면 그에 따른 응답으로 Header를 반환합니다.
- Server는 필요한 경우 Client에 의해 요청된 MIME type에 따라 규격화된 Data를 반환합니다.
위와 같은 사항을 하나의 그림으로 요약하면 다음과 같이 표현할 수 있습니다.
이제 HTTP verb, Request Header, URI, 요청 시 전달되는 다양한 매개변수, Status Code, 응답 Header 등 각각의 요소에 대해 더 자세히 들어가 볼 것입니다. 그리고 시간이 지남에 따라 위 이미지를 더욱 구체적으로 이해할 수 있을 것입니다.
3) HTTP 구현
지금까지 전체적인 부분을 언급하였는데 이제 좀 더 상세한 면으로 들어가 HTTP Method가 무엇인지, 요청/응답 Header 그리고 HTTP 상태 Code가 무엇인지에 알아보고자 합니다. 또한 Client가 HTTP 요청에서 URI와 함께 매개변수를 전달하는 방식에 대해서도 같이 다뤄볼 것입니다.
(1) HTTP Verbs(Method)
RFC 7231은 HTTP Method를 아래와 같이 정의하고 있습니다.
GET | 가장 일반적인 것으로 Server의 Resource를 요청하고 원하는 형식(Header에 의해 정의됨)으로 응답을 수신합니다. 이때 Resource는 경우에 따라 Cache(Memory상에서 Data유지)될 수 있습니다. |
HEAD | GET과 유사하나 어떠한 Data(Body)도 반환하지 않습니다. 오로지 요청 URI에 해당하는 Header만을 반환하며 200 OK와 같이 상태 Code를 통해 Server에 특정 Resource가 존재하는지 여부를 확인할 수 있습니다. HEAD는 잘 사용되지는 않은것으로 이런것이 있다는 정도만 알고있으면 충분합니다. |
POST | GET만큼 자주 사용되는 것으로 요청의 Body(요청 Body)에 첨부되어 Server상에 새로운 Resource를 생성하도록 합니다. 참고로 Data를 보내는 또 다른 방식으로 form-data라는 것도 존재합니다.(엄밀히 API에 속할 수 있는 기술은 아닙니다.) RFC에 의하면 POST는 Resource에 Data를 추가하는(Data에 변경하고자 하는 Content를 붙임)방식으로 기존의 Data를 변경할 수도 있습니다. Server응답은 max-age혹은 Expires headers와 같은 응답 Header에 새로운 정보가 추가되지 않는 이상 Cache되지 않습니다. |
PUT | RFC에 의하면 Server의 Resource를 대체한다고 언급하고 있는데 이 때문에 기능적으로 혼란스럽게 느껴질 수 있습니다. 특히 POST가 그러한데 POST는 기존의 Data에서 덧붙이는 것이며 PUT은 기존의 Data를 대체한다는 것으로 개념상의 차이가 존재합니다. 또한 PUT은 Server에 Resource가 존재하지 않을때 Resource를 신규생성할 수도 있는데 이렇게 되면 결론적으로 POST와 동일한 역활을 수행하게 되는 것입니다. |
DELETE | Resource를 삭제하는데 사용되며 Server응답은 Cache되지 않습니다. |
CONNECT | HTTP요청이 위임될 Server로서 요청된 요청에 대한 Server로 접근하는 Method입니다. 또한 TLS(Transport Layer Security:HTTPS)를 통한 보안 요청에도 사용되는데 실질적으로 잘 사용되지 않는 Method입니다. |
OPTIONS | 해당 Method를 사용하면 주어진 URI에서 어떤 Method가 지원되는지 확인할 수 있으며 CORS(Cross-Origin Resource Sharing)의 context에서도 사용됩니다. 보통은 API명세를 통해 가능한 URI와 Method를 확인할 수 있으므로 해당 Method가 사용되는 경우는 거의 없습니다. |
TRACE | Proxy와 같은 중간 Server가 요청을 변경하는지를 확인하는데 사용되는 Method로서 잘 사용되지는 않습니다. |
RFC 7231은 현존하는 모든 Method를 서술하지는 않으며 상기 Method 외에 다른 Method도 존재합니다. 참고로 RFC 5789에서는 PATCH라는 Method를 정의하고 있으며 관련 내용은 아래 Link에서 확인할 수 있습니다.
https://www.rfc-editor.org/rfc/rfc5789.html
PATCH는 PUT과 POST에서 Server의 Resource에 변경을 가할 수 있다는 특징을 공유하고 있어서 혼란스러울 수 있는데 PUT은 Resource전체를 대체하는 반면 PATCH는 Resource를 부분적으로 변경한다는 차이가 있습니다.
여러 가지 Method가 존재하고 어떤 것들은 비슷한 면도 있어서 개발자들 사이에서도 각각의 역할에 혼란을 겪곤 합니다. 각각의 Method와 기능이 공통된 경우 이를 꼭 명확히 구분하지 않는 경우도 있습니다. 예를 들어 Resource를 생성하기 위해 POST를 사용하거나 URI에 너무 많은 매개변수가 존재하여 POST의 Body에 이들을 넣을 수 없는 경우 GET Method로 대체할 수 있으며 심지어는 PATCH가 Resource의 전체 혹은 부분적인 대체를 위해 만들어졌음에도 불구하고 이를 위해 PUT을 사용하는 것도 가능합니다. 어떤 것을 사용할지 기준이 모호하다면 Resource에 대한 단일 속성만을 변경하는 경우에 한하여 PATCH를 사용하고 만약 변경하는 Resource의 속성이 다수라면 PUT를 사용식으로 용도를 정의해도 됩니다.
HTTP 상태 Code는 HTTP Method와 연결될 수 있는데 어떤 Method의 경우 특정 HTTP 상태와 함께 사용됩니다. HTTP 상태와 함께 이에 어떤 Method가 연결될 수 있는지 대한 자세한 내용 역시 곧 알아볼 것입니다.
(2) HTTP 상태 Code
HTTP 상태 Code는 Server와 Client 간 요청과 응답에서 필수적으로 사용됩니다. 특히 Client는 Server의 응답이 수신되면 그 처리의 결과를 확인하는 데 사용할 수 있습니다. 참고로 HTTP 상태 Code는 RFC 7231에서 정의하고 있습니다. API와 관련하여 상태 Code는 Server가 무엇을 말하려 하는지 Client가 이해하기 위해 반드시 필요하며 이를 토대로 다음에 어떤 Action을 취해야 하는지도 알 수 있습니다.
HTTP 상태 Code는 전체적으로 3개의 숫자로 이루어져 있는데 이 중 첫 번째 숫자는 상태분류를 의미합니다. 그리고 상태분류는 다시 아래 5가지로 나눌 수 있습니다.
1xx | 순수 정보 제공용입니다. |
2xx | Server가 요청을 수신하고 성공적으로 처리했음을 의미합니다. |
3xx | Resource가 표시된 주소에 존재하지 않는 등의 이유로 Server가 Redirection처리했음을 의미합니다. |
4xx | Client의 요청이 변경되었거나 요청에 잘못된 입력이 발생한 경우를 의미합니다. 4xx대 오류는 대부분 Client측에서 요청을 수정함으로서 정상적으로 처리될 수 있습니다. |
5xx | Client의 요청이 Server에서 처리되는 도중 오류가 발생했음을 의미합니다. |
RFC 7231이 HTTP 상태 Code를 정의하는 유일한 RFC는 아니지만 가장 자주 사용되는 Code를 서술하고 있으며 RFC 4918과 EFC 6585에서 이 목록을 완성하고 있습니다. 기타 다른 Code들은 다른 Scenario를 다루고 있습니다.
각 RFC의 문서는 다음 표를 참고하시기 바랍니다.
RFC 7231 | https://www.rfc-editor.org/rfc/rfc7231 |
RFC 4918 | https://www.rfc-editor.org/rfc/rfc4918 |
RFC 6585 | https://www.rfc-editor.org/rfc/rfc6585 |
100% 일치하는 것은 아니지만 아래 표와 같이 HTTP 상태 Code와 HTTP 동사를 연결시키는 것도 가능합니다. 물론 이 목록을 외울 필요는 없습니다. 이런 것이 있다는 정도만 알고 있으면 충분하고 실제 필요할 때 찾아보면 어느새 익숙하게 기억할 수 있을 것입니다.
Code | 의미 | Verbs (Method) |
100 | Continue | ALL |
101 | Switching Protocols | ALL |
200 | OK | GET, HEAD |
201 | Created | POST |
202 | Accepted | ALL |
203 | Non-Authoritative | GET |
204 | No Content | POST, PUT, PATCH |
205 | Reset Content | POST, PUT, PATCH |
206 | Partial Content | GET |
207 | Multi-Status | ALL |
300 | Multiple Choices | ALL |
301 | Moved Permanently | GET, HEAD, DELETE |
302 | Found | GET, HEAD, DELETE |
303 | See Other | GET, HEAD, DELETE |
304 | Not Modified | GET, HEAD |
305 | Use Proxy (deprecated) | ALL |
307 | Temporary Redirect | ALL |
400 | Bad Request | POST, PUT, PATCH |
401 | Unauthorized | ALL |
402 | Payment Required | 미사용 |
403 | Forbidden | ALL |
404 | Not Found | POST를 제외하고 전부 |
405 | Method Not Allowed | ALL |
406 | Not Acceptable | ALL |
407 | Proxy Authentication Required | ALL |
408 | Request Timeout | ALL |
409 | Conflict | POST, PUT, PATCH |
410 | Gone | POST를 제외하고 전부 |
411 | Length Required | POST, PUT, PATCH |
412 | Precondition Failed | ALL |
413 | Payload Too Large | POST, PUT, PATCH |
414 | URI Too Long | GET을 제외하고 전부 |
415 | Unsupported Media Type | POST, PUT, PATCH |
417 | Expectation Failed | ALL |
422 | Unprocessable Entity | POST, PUT, PATCH |
423 | Locked | GET, HEAD, POST, PUT, PATCH |
424 | Failed Dependency | ALL |
426 | Upgrade Required | ALL |
500 | Internal Error | ALL |
501 | Not Implemented | ALL |
502 | Bad Gateway | ALL |
503 | Service Unavailable | ALL |
504 | Gateway Timeout | ALL |
505 | HTTP Version Not Supported | ALL |
507 | Insufficient Storage | POST, PUT, PATCH |
목록만 보면 HTTP 상태 Code가 생각보다 많다고 느껴질 수 있지만 실제 사용되는 건 몇 가지 안 되므로 그리 걱정할 필요는 없습니다.
(3) 요청과 응답 Header
HTTP Header는 Client와 Server 간 요청과 응답의 흐름을 통해 Data의 정보를 전달하는 데 사용됩니다. 이때 Header가 전달하는 정보에는 Client Browser의 정보나 인증 Data에만 국한되어 있지 않습니다.
Header는 목적에 따라 요청 Header와 응답 Header로 구분할 수 있습니다. 요청과 응답 Header모두 RFC 7231에서 각각 정의하고 있으며 이 중 일부는 상세하게 다루지만 다른 일부는 RFC 7231에서 참조하는 다른 RFC에서 다루고 있습니다.) 특정 Header는 요청 중 Browser에 의해 자동적으로 생성되기도 하며 Server의 응답 중에 생성되기도 합니다. 물론 이러한 Header까지 상세하게 알아야 하는 것은 아닙니다. 그냥 이러한 Header가 존재한다는 정도만 알면 충분하며 필요하다면 이들을 사용자정의하여 사용하는 것도 가능합니다.
RFC 7231에서는 잘 알려진 몇몇 Header에 대해 다루고 있지만(물론 다른 RFC로 Redirect 하는 경우도 있음) RFC 4229를 통해 잘 알려지지 않은 Header를 포함, 거의 대부분의 Header에 대한 모든 목록을 확인해 볼 수 있습니다.
https://datatracker.ietf.org/doc/html/rfc4229
① 요청 Header
HTTP 상태 Code와 비슷하게 요청 Header도 아래 5가지 Class정도로 분류될 수 있습니다.
- Controls Headers
- Conditional Headers
- Content Negotiation Headers
- Authentication Credentials Headers
- Request Context Headers
● Controls Headers
Controls Class에는 7개의 Header가 존재하며 이들 중 일부는 Key와 값의 쌍으로 활용할 수 있는 다양한 지시문을 갖고 있습니다.
Cache-Control | 요청과 응답의 Chain사이에서 특정 Cache기간을 지정하는데 사용됩니다. 또한 이때 사용되는 이름을 통해 용도를 알 수 있습니다. 좀더 상세하게는 RFC 7234를 참고하시기 바랍니다. - no-cache : 자체적으로 동작하며 어떠한 값도 허용하지 않습니다. - no-store : 자체적으로 동작하며 어떠한 값도 허용하지 않습니다. - max-age : 초단위 값을 사용합니다.(예:Cache-Control:max-age=9600) - max-stale : 초단위 값을 사용합니다.(예:Cache-Control:max-stale=9600) - min-fresh : 초단위 값을 사용합니다.(예:Cache-Control:max-fresh=9600) - no-transform : 자체적으로 동작하며 어떠한 값도 허용하지 않습니다. - only-if-cached : 자체적으로 동작하며 어떠한 값도 허용하지 않습니다. |
Expect | 요청을 정확하게 처리하기 위해 Server로 부터 예상치를 나타내는데 사용됩니다.(예:Expect:100-continue) 좀더 상세하게는 RFC 7231을 참고하시기 바랍니다. |
Host | 대상 URL로 부터 Server와 Port(선택적)를 나타내는데 사용됩니다.(예:Host:http://www.example.com) 좀더 상세하게는 RFC 7230을 참고하시기 바랍니다. |
Max-Forwards | 요청을 전달하는 중간서버(proxi)의 한도를 지정하는데 사용됩니다. TRACE와 OPTIONS Method에서만 사용되며 정수값으로 지정합니다.(예:Max-Forwards:1) 좀더 자세한 사항은 RFC 7231을 참고하시기 바랍니다. |
Pragma | HTTP 1.0 cache와의 하위 호환성에 사용됩니다. 해당 Header는 Cache-Control Header가 사용되는 경우에는 무시됩니다.(예:Pragma:no-cache) 좀더 자세한 사항은 RFC 7234를 참고하시기 바랍니다. |
Range | 주어진 Byte의 범위안에서 문서의 Port를 반환하는데 사용됩니다.(예:Range:bytes 0-1024) 좀더 자세한 사항은 RFC 7233을 참고하시기 바랍니다. |
TE | 압축 algorithm을 정의하는 등 chunk transfer coding을 지정하는데 사용됩니다.(예:TE:gzip) 좀더 자세한 사항은 RFC 7230을 참고하시기 바랍니다. |
● Conditional Headers
Conditional Class에는 5개의 Header가 존재하며 이를 통해 요청을 완료하기 위해 대상 Resource에 조건을 적용할 수 있습니다.
If-Match | 요청된 Resource가 현재 표현된 Resource와 일치하는지 여부를 확인하는데 사용됩니다.(예: If-Match:*(모든 Resource)) 좀더 자세한 사항은 RFC 7232를 참고바랍니다. |
If-None-Match | 요청된 Resource가 현재 표현된 어떠한 Resource와도 일치하지 않는지를 확인하는데 사용됩니다. If-Match와는 반대로 작동합니다.(예: If-None-Match:*(모든 Resource)) 좀더 자세한 사항은 RFC 7232를 참고바랍니다. |
If-Modified-Since | 대상 Resource 수정 날짜가 제공된 날짜보다 더 최신인지를 확인하는데 사용됩니다. 좀더 자세한 사항은 RFC 7232를 참고바랍니다. |
If-Unmodified-Since | 대상 Resource 수정 날짜가 제공된 날짜보다 더 이전날짜인지를 확인하는데 사용됩니다. 좀더 자세한 사항은 RFC 7232를 참고바랍니다. |
If-Range | If-Match와 If-Modified-Since Header를 결합한 형태입니다. 좀더 자세한 사항은 RFC 7233을 참고바랍니다. |
● Content Negotiation Headers
Content Negotiation Headers는 HTTP 요청에서 필수입니다. 이를 통해 Client와 Server는 서로가 다루고자 하는 Data의 형식을 이해할 수 있습니다.
Accept | Client가 다룰 수 있는 MIME type을 정의하는데 사용됩니다. Accept:application/json과 같이 사용되며 더 자세한 사항은 RFC 7231을 참고하시기 바랍니다. |
Accept-Charset | 더이상 사용하지 않습니다. 대부분의 Browser와 Server는 해당 Header를 무시합니다. 좀 더 자세한 사항은 RFC 7231을 참고하시기 바랍니다. |
Accept-Encoding | 압축 Algorithm을 정의하는데 사용됩니다. Accept-Encoding:deflate, gzip과 같이 사용되며 더 자세한 사항은 RFC 7231을 참고하시기 바랍니다. |
Accept-Language | Client측이 어떤 언어를 허용할지를 Server에게 알려주는데 사용됩니다. Accept-Language: *(all)와 같이 사용되며 좀 더 자세한 사항은 RFC 7231을 참고하시기 바랍니다. |
● Authentication Credentials Headers
아래 2개의 Authentication Header가 자격 증명으로 보호된 Resource와의 상호작용을 위해 사용됩니다. 특히 첫 번째 Header가 자주 다뤄질 것이기 때문에 눈여겨봐야 합니다.
Authorization | 대상 Server에서의 인증을 위해 사용됩니다. Bearer token은 물론 기본적인 자격증명등 다양한 유형의 자격증명을 처리할 수 있습니다. Authorization:bearer xxxxx..와 같이 사용되며 더 자세한 사항은 RFC 7235를 참고하시기 바랍니다. |
Proxy-Authorization | Authorization과 동일하지만 Proxy를 인증하는데 사용된다는 차이가 있습니다. 더 자세한 사항은 RFC 7235를 참고하시기 바랍니다. |
● Request Context Headers
마지막으로 아래 3개의 Header는 Server에 추가적인 Contextual Data를 제공하는 데 사용됩니다.
From | email을 통해 누가 요청을 보내는건지를 Server에 알리는데 사용됩니다. From:who@cliel.com처럼 사용되며 더 자세한 사항은 RFC 7231을 참고하시기 바랍니다. |
Referrer | 요청의 출처가 되는 URI를 Server에 알리는데 사용됩니다. Referrer:https://lab.cliel.com 처럼 사용되며 더 자세한 사항은 RFC 7231을 참고하시기 바랍니다. |
User-Agent | HTTP 요청을 발생시킨 사용자에 대한 정보를 수집하는데 사용됩니다. 대부분의 Browser에 대한 것으로 User-Agent:Mozilla/5.0(Windows NT 10.0; Win64;x64)...처럼 사용되며 더 자세한 사항은 RFC 7231을 참고하시기 바랍니다. |
위의 요청 Header들은 가장 흔히 볼 수 있는 Header들로서 지금 당장 이들을 모두 자세하게 알아야 할 필요는 없습니다. RFC에서 이들에 대한 표준을 정의하고 있고 우리는 그저 언제 어디서 어떻게 사용할지만 알고 있으면 충분합니다.
RFC에서는 대부분의 Header에 대한 사용법을 설명하고 있지만 필요하다면 사용자 정의형식의 Header 또한 사용할 수 있습니다. 특정 Application만의 기능을 위해 특별한 Header를 생성할 수 있으며 여기에 필요한 값을 자유롭게 저장하여 사용합니다.
위에서 언급된 각 RFC에 대한 URL은 www.rfc-editor.org/rfc/rfc[숫자] 형태로 확인하실 수 있습니다. 예를 들어 RFC 7230이라면 https://www.rfc-editor.org/rfc/rfc7230 이 됩니다.
② 응답 Header
지금까지는 HTTP요청에서 사용되는 몇 가지 Header에 대해 알아보았습니다. Server에 Metadata를 보내는 것만큼 응답 Header에 대한 Metadata도 적지 않은데 이들은 Client에 HTTP 응답의 Context와 관련된 추가적인 Metadata를 알려주는데 필수적으로 사용됩니다. 위에서 언급한 것처럼 이들 중 일부는 RFC 7231에 정의되어 있으며 기타 RFC 7232, RFC 7233, RFC 7234에 정의되어 있습니다.
응답 Header에는 총 4개의 Class로 이루어져 있습니다.
- Control Data Headers
- Validator Header Fields
- Authentication Challenges headers
- Response Context headers
● Control Data Headers
이들 Header는 가장 중요한 Header로서 Client로 전송되는 정보를 풍부하게 만들 수 있으며 또한 대부분 적절한 HTTP 상태 Code와 연결됩니다. 해당 Class에 속하는 Header는 아래와 같이 8가지가 존재합니다.
Age | Client에 응답이 생성되는 시점을 알려주는데 사용됩니다. 대부분 0에 근접하며 응답이 Proxy상에서 Cache되는 경우 0보다 더 많이질 수 있습니다. 좀더 상세한 내용은 RFC 7234를 참고하시기 바랍니다. |
Cache-Control | 요청 Header의 Cache-Control과 비슷하며 응답 Header값 역시 요청 Header의 것과 동일합니다. |
Expires | Client에게 응답 날짜와 시간이 오래 되었음을 알리는데 사용됩니다. 좀더 자세하게는 RFC 7234를 참고하시기 바랍니다. |
Date | Client에게 Server상에서 응답이 생성되는 날짜와 시간을 알려주는데 사용됩니다. 좀더 자세하게는 RFC 7231을 참고하시기 바랍니다. |
Location | Resource를 생성한 후 특히 POST요청전 해당 Resource가 존재하는 위치의 URI를 알려주는데 사용됩니다. 좀더 자세하게는 RFC 7231을 참고하시기 바랍니다. |
Retry-After | 503응답(Service Unavailable)으로 인해 HTTP요청이 실패하는 경우 이를 다시 재시도한 시점(날짜와 시간 혹은 초)을 Client에게 알려주는데 사용됩니다. 좀더 자세하게는 RFC 7231을 참고하시기 바랍니다. |
Vary | 어떤 요청 매개변수 Header가 Server로 부터의 응답에 영향을 줄 수 있는지 Client에게 알려주는데 사용됩니다. '*' 표시는 요청안에 모든 매개변수가 응답에 영향을 줄 수 있음을 의미합니다. 좀더 자세하게는 RFC 7231을 참고하시기 바랍니다. |
Warning | Client에게 유용한 정보를 제공하는데 사용됩니다. 하지만 향후 제거될 수 있는 Header로서 사용하지 않는것을 권장합니다. 좀더 자세하게는 RFC 7234를 참고하시기 바랍니다. |
● Validator Header Fields
여기에는 HTTP요청 중 요청된 Resource의 Version에 Metadata의 추가를 허용하는 2개의 응답 Header가 존재합니다.
ETag | Client에 요청된 Resource의 Version을 알리는데 사용되며 일반적으로 문자열이 사용됩니다. 좀더 자세한 내용은 RFC 7232를 참고하시기 바랍니다. |
Last-Modified | Client에 요청된 Resource가 변경된 마지막 시간이 언제인지를 알리는데 사용됩니다. 좀더 자세한 내용은 RFC 7232를 참고하시기 바랍니다. |
● Authentication Challenges Headers
Server는 Client에게 Server가 어떤 인증을 허용할지를 알려주는 데 사용되며 다음 2가지 Header가 존재합니다.
WWW-Authenticate | Client에게 Server가 어떤 인증방법을 허용하는지를 알려주는데 사용됩니다. |
Proxy-Authenticate | Client에게 Proxy가 어떤 인증방법을 허용하는지를 알려주는데 사용됩니다. |
● Response Context Headers
Client와 마찬가지로 Server 역시 요청 Resource와 관련된 추가적인 Contextual Data를 보낼 수 있으며 여기에는 아래 3가지가 사용됩니다.
Accept-Range | Client에게 Server가 부분적인 file download를 위해 지원하는 단위(범위)를 알려주는데 사용됩니다. 예를 들어 Accept-Ranges:bytes처럼 사용되며 자세한 사항은 RFC 7233을 참고하시기 바랍니다. |
Allow | Client에게 Server가 지원하는 HTTP Method를 알려주는데 사용됩니다. Allow:GET,POST와 같이 사용되며 자세한 사항은 RFC 7231을 참고하시기 바랍니다. |
Server | Client에게 Server가 HTTP요청을 처리하는데 어떤 Server가 사용되는지를 알려주는데 사용됩니다. Server:Kestrel와 같이 사용되며 자세한 사항은 RFC 7231을 참고하시기 바랍니다. |
요청 Header와 같이 자신만의 사용자 정의된 응답 Header를 사용할 수도 있습니다. 이러한 방법은 특정 Application에 대한 응답용이거나 혹은 다른 특별한 값을 추가시키는 용도로 사용될 수 있습니다.
(4) URI와 URL
① URI
위에서는 다른 사항을 설명하면서 여러 번 URI라는 명칭을 사용한 적이 있는데 URI는 Uniform Resource Idenfifier의 약자로 RFC 3986에서 아래와 같이 언급하고 있습니다.
a compact sequence of characters that identify an abstract or physical resource.
다시 말해 https://cliel.com과 같이 특정 Resource에 접근하기 위해 Browser에서 입력하는 주소를 의미합니다. 간단히 말하면 이렇게 표현할 수 있지만 URI를 좀 더 세분화하면 아래와 같이 나누어 볼 수 있습니다.
Scheme(필수) | 원격 Reource에 접근하기 위한 방법(사양)을 의미합니다. |
Authority(필수) | 사용자 정보(선택적), Server 주소(Host) 그리고 Port를 결합한 것으로 앞에 '://'형태의 접두사가 붙습니다. |
Path(선택적) | 특정 범위 안에서 Resource를 식별하기 위한 것이며 '/'문자로 분리됩니다. |
Query(선택적) | 특정 범위 안에서 Resource를 식별하기 위한 것이며 '?'문자로 분리됩니다. |
Fragment(선택적) | 요청된 Resource에서 특정 하위 Resource를 식별하기 위한 것으로 HTML page에서 사용되는 anchor를 예로 들 수 있습니다. |
위 구성요소를 그림으로는 다음과 같이 표현할 수 있습니다.
여기서 붉은색은 Authority로서 필수이며 파란색은 선택적인 부분입니다. 또한 Authority는 다시 다음과 같이 세분화할 수 있으며 여기서는 Host만이 필수에 해당합니다.
아래 표에서는 URI에 대한 몇 가지 예와 설명을 확인하실 수 있습니다.
https://cliel.com | 어떠한 Path, Query 또는 Fragment도 없이 Scheme와 Authority만이 존재합니다. |
https://cliel.com/?p=package | Query 매개변수가 사용되었습니다. |
https://cliel.com/?p=package#2nd | Fragment가 사용되었습니다. |
https://cliel.com/2024/09/09/article | Path가 사용되었습니다. |
자신의 Computer에서 개발이나 Test용도로 직접 Web Application을 동작시키는 경우 Host는 LocalHost가 될 수 있습니다.
URI에 대해 좀 더 자세히 알고자 한다면 RFC 3986을 참고하시기 바랍니다.
② URL
아마도 지금까지는(그리고 앞으로도) URI보다는 URL(Uniform Resource Locator)이라는 말을 더 많이 들어봤을 것입니다. URI와 URL은 서로 혼동하기 쉬운데, 큰 차이는 없지만 URI는 Resource의 식별자를 정의하며 URL은 Resource를 Scheme에 의해 정의된 특정 접근 Method로 연결하도록 합니다. 위 URI를 설명할 때 예제도 HTTP를 호출하는 http Scheme의 동일한 예제를 사용했는데 URL은 다음과 같이 다른 Protocol에서도 사용될 수 있습니다.
Protocol | Scheme |
File Transfer Protocol | FTP |
Gopher Protocol | Gopher |
Electronic Email Protocol | Mailto |
Usenet Protocol | news |
NNTP | nntp |
Telnet Protocol | telnet |
Wide Area Information Server Protocol | wais |
Host-specific File names Protocol | file |
Prospero Directory Service Protocol | Prospero |
이들 Protocol에 관한 더 자세한 사항은 RFC 1738(https://www.rfc-editor.org/rfc/rfc1738)을 참고하시기 바랍니다.
③ 그 외
흔히 사용되지는 않지만 URI 및 URL과 연관된 다른 2개의 약어도 존재합니다.
URN | Uniform Resource Names(RFC 1737) |
URC | Uniform Resource Characteristics(URC는 어떤 RFC에서도 정의되어 있지 않지만 https://datatracker.ietf.org/wg/urc/about/ 에서 일부 정보를 확인해 볼 수 있습니다.) |
(5) Parameter
매개변수(Parameter)는 Server상의 특정한 Resource를 검색하는 데 사용되는 것으로 아래와 같이 다양한 방법으로 사용될 수 있습니다. 매개변수에 대해 별도로 언급하지 않았으나 이전에 일부 예제에서도 매개변수를 사용하였습니다.
사용자 정의 header | 예를 들어 MyHeader:MyValue의 형태로 사용할 수 있습니다. 이러한 방법은 그리 권장되지 않지만 필요하다면 사용할 수 있습니다. |
URL 경로 | https://cliel.com/resource/no190 과 같이 사용할 수 있으며 여기서 no190이 대상 Resource의 ID에 해당합니다. 이와 같은 URL을 요청받으면 Server는 no190에 해당하는 HTML을 제공할 것입니다. |
Query | https://cliel.com/resource?no=190 과 같이 사용할 수 있습니다. 여기서 Query Parameter는 특정 Resource로의 접근 뿐만이 아니라 검색기준을 충족하는 일련의 모든 Resource에 대한 접근을 허용하는 것입니다. |
위에서 마지막 2가지 방식이 가장 많이 사용되는 방식이며 향후에도 여러 예제를 통해 자주 사용하게 될 것입니다.
(6) Error Handling
HTTP는 Error를 비교적 세련된 방법으로 처리할 수 있도록 잘 설계되었기 때문에 이를 HTTP응답에 적절하게 통합시키는 것이 매우 중요합니다. RFC 7807에서는 Error를 처리하는 것에 관해 정의하고 있는데 관련 내용은 https://datatracker.ietf.org/doc/html/rfc7807에서 확인하실 수 있습니다.
특히 해당 RFC에서는 Error가 발생한 경우 API 호출자에게 반환하는 Problem Details라는 이름의 JSON 구조를 정의하고 있는데 해당 JSON에서는 아래 표에서 서술한 요소를 포함하고 있습니다.
type(string) | Error가 발생한 문제의 유형을 식별하기 위한 URI참조입니다.(RFC 3986) 명세에서는 여기에 사람이 읽을 수 있는 유형의 문서(HTML)를 제공함으로서 발생한 문제점을 알리도록 권장하고 있는데 이 속성의 값이 제공되지 않으면 about:blank 로 인식합니다. |
title(string) | 발생한 문제유형을 요약한 제목에 해당합니다. |
status(number) | HTTP 상태 Code입니다.(RFC 7231) |
detail(string) | 발생한 문제점을 서술하는 영역입니다. |
Instance(string) | 특정 문제 발생을 식별하는 URI 참조입니다. |
Problem Details는 Error의 구조가 잘 설계되어 편리하게 사용할 수 있으며 향후 예제에서도 전반적으로 Error처리하기 위해 Problem Details를 사용할 것입니다.
(7) HTTPS, TLS, and HSTS
지금까지는 HTTP에 관해서만 언급해 왔습니다. HTTP는 Internet상에서 Client와 Server 간에 Data를 그대로 드러난 상태로 교환할 수 있기 때문에 보안적인 문제점을 안고 있습니다. 이것은 꽤 큰 문제인데 암호화되지 않은 Data를 전송하는 것은 아래와 같은 문제점을 야기할 수 있습니다.
- 악의적인 사용자(Hacker)가 Client와 Server간에 송수신되는 Data를 가로챌 수 있습니다.
- 악의적인 사용자(Hacker)가 Client와 Server간에 송수신되는 Data를 위/변조할 수 있습니다.
- HTTP만으로 Client가 요청한 Website와의 통신에서 신뢰성을 부여할 수 없습니다.
이것이 바로 HTTPS가 필요한 이유입니다. HTTPS는 HTTP의 보안 Version으로 S는 Secure를 의미합니다. 80 Port를 기본으로 사용하는 HTTP와 달리 HTTPS는 443 Port가 기본이며 필요하다면 ASP.NET Core에서는 이 번호를 변경하는 것도 가능합니다.
HTTPS는 TLS(Transport Layer Security)를 기반으로 하는데 TLS는 다음과 같은 이점을 제공함으로써 위에서 언급한 HTTP의 문제점을 극복할 수 있습니다.
암호화 | 주고받는 Data가 암호화되므로 투명하게 드러나지 않습니다. |
무결성 | 주고받는 Data의 무결성을 보장함으로서 Data의 위/변조를 방지합니다. |
신뢰성 | HTTPS자체로 Client가 요청한 Website와의 통신에서 신뢰성을 부여할 수 있습니다. |
TLS는 개선된 SSL(Secure Socket Layer) 암호화 기반 Protocol Version입니다. SSL은 종종 TLS와 같이 언급되기도 하는데 때문에 일반적으로 'SSL/TLS 암호화'라고 명명하기도 합니다. 하지만 실제로 TLS는 1996년 Update 된 이후로 SSL/TLS를 대체하고 있습니다.
그렇다면 실제로 SSL/TLS는 어떻게 동작할까? 이는 Client와 Server 간에 Key를 교환하는 것으로 이루어지며 이후 TLS handshake라고 하는 암호화연결이 수립합니다. TLS handshake가 실제 어떻게 동작하는지까지는 굳이 알 필요가 없지만 관심 있다면 아래 Link를 참고하시면 더 많은 정보를 확인하실 수 있습니다.
https://www.cloudflare.com/learning/ssl/what-happens-in-a-tls-handshake
복호화키를 보유한 Client와 Server만이 교환되는 Data를 암/복호화할 수 있습니다. 물론 이것이 가능하려면 Server는 CA(Certificate Authority)로부터 발급받은 SSL인증서가 설치되어 있어야 합니다(대부분 Server의 관리자나 개발자가 Server에 설치함). Visual Studio를 사용하는 경우라면 자동적으로(최초사용 시 SSL사용여부에 대한 동의를 요구함) 개발용으로 제공되는 자체 SSL인증서를 사용할 수 있는데, ASP.NET Core에서 HTTP Strict Transport Security(HSTS) 정책을 설정함으로써 Client가 URL을 호출할 때 HTTPS의 사용을 강제하는 방식을 같이 살펴볼 것입니다. HTTPS, TLS, HSTS등에 관한 좀 더 자세한 사항은 아래 Link를 통해 확인하실 수 있습니다.
HTTPS(RFC 2818) | https://datatracker.ietf.org/doc/html/rfc2818 |
TLS(RFC 8446) | https://datatracker.ietf.org/doc/html/rfc8446 |
HSTS(RFC 6797) | https://datatracker.ietf.org/doc/html/rfc844 |
HTTPS의 모든 측면을 이해해 보고자 한다면 위의 RFC를 읽어보는 것도 좋지만 반드시 알아야 할 것은 SSL인증서를 Server에 설치함으로써 Server와 Client가 암/복호화 key를 교환할 수 있게 되고 이를 통해 Data를 더 이상 일반적인 문자열로서 읽거나 위/변조할 수 없도록 한다는 것입니다.
그림은 꽤 단순하지만 이 그림을 통해 Server와 Client사이에서 무엇을 보호해야 하는지를 알 수 있습니다.
(8) 요약
HTTP에 대한 전체적인 개념은 RFC에서도 잘 설명하고 있습니다. 다루고자 하는 주요 주제는 HTML 혹은 기타 다른 Page가 아닌 API를 다루는 것으로 주로 MIME Type이 application/json인 JSON형식의 Server Data를 주로 접하게 될 것입니다. API가 통신하는 순서상으로는 우선 http://www.cliel.com/product/id URL과 같이 GET Method나 POST 등의 방식으로 호출하며 이때 application/json으로 필요한 Data 형식을 요청하고 en-CA language, 그리고 gzip, deflate, br(Brotli) encoding과 함께 기본 인증정보를 함께 전달합니다. 그런 뒤 Server응답은 요청된 Content-Type인 application/json과 Content-Length응답 그리고 Kestrel과 같은 Server정보 및 실제 JSON Data와 함께 200 OK 상태 Code를 반환할 것입니다.
2. REST Architecture
2000년 Computer 과학자인 Roy Fielding은 Web Service 개발에 사용되는 REST(Representational State Transfer)라는 Architectural Style을 정의하였습니다. HTTP는 RFC를 발행하는 위원회에서 정의된 Protocol이며 REST는 개발과정에서 적용되는 하나의 개념에 해당합니다. HTTP를 재정의하거나 기타 다른 특정한 기능을 추가적으로 부여하는 것이 아니며 HTTP와는 독립적으로 존재합니다. 하지만 Web개발에서 이 둘은 폭넓게 적용되는 개념이다 보니 종종 이 둘을 연결시키는 경우가 많습니다. HTTP는 Client와 Server 간 통신 Protocol에 해당하며 REST는 Server와 Client가 어떻게 통신할지에 대한 제약조건을 식별하는 것입니다. REST는 제약조건이자 모범적인 사례가 될 수 있습니다. REST제약조건을 따르는 것은 API REST원칙을 따르는 것이므로 이를 잘 이해하는 것이 중요합니다. 그러나 모범적인 사례를 참고하는 것은 좋은 것이지만 사례만 바라보면 REST를 준수하는데 놓치는 부분이 발생할 수 있으므로 주의가 필요합니다.
1) REST 제약조건
대부분의 Web Application은 일련의 개체 Entity(예를 들어 product라는 것을 하나의 Entity로 취급할 수 있습니다.)와 이에 대한 동작(예를 들어 ProductID를 통해 특정 Product의 정보를 가져오는 것)을 통해 Business Logic을 구현합니다. 이때 개체에 대한 연산처리는 4가지의 주요 동작(혹은 Method)을 통해 구현되는데 흔히 이를 CRUD(Create, Retrieve, Update, Delete)로 표현합니다. 이때 연산의 대상이 되는 Entity를 Resource라고 하며 JSON, XML이나 기타 여러 가지 형태로 표시되거나 표현될 수 있습니다. 그리고 Client는 HTTP(gRPC나 SOAP의 경우에는 전혀 다른 통신 Protocol이므로 해당하지 않습니다.)를 통해 Server로 CRUD기능을 호출하여 Server상의 Entity로 표현된 것을 조작하게 됩니다.
Client에서 Server로의 '상태이전'은 예를 들어 Client가 처음 Product 생성에 대한 동작을 호출한 후 Client가 호출할 수 있는 다음 Product 상태를 호출하는 것을 말합니다. 여기서 '상태'는 생성된 Product Data를 조회하거나 혹은 Product Data를 Update 하는 것이 될 수 있습니다.
Rest는 이것만으로 정의되지 않으며 다음 6가지 제약조건을 기반으로 합니다.
- Server와 Client 간 책임분리됩니다. (Client는 Data를 표시하고 Server는 이들을 계산합니다.)
- Client나 Server가 통신을 위해 상대방의 상태를 알아야 할 Session상태가 없습니다.(Stateless)
- Resource를 Caching 합니다.
- 식별가능한 Resource를 통한 지속적인 통신이 가능합니다. HTTP에서는 URL이 될 수 있는데 이때 응답은 본문(body)과 header를 포함합니다.
- Proxy와 같은 중간 계층이 추가될 수 있습니다.
- Client는 Server에게 Client가 실행할 Code 조각을 요청할 수 있습니다.
이 중에서 실제 처음 4가지 요소를 중점적으로 알아볼 텐데 이를 통해 REST API를 어떻게 설계할지를 생각해 볼 수 있습니다.
2) REST 구현할 때 좋은 습관
지금까지 REST를 구현하기 위해 필요한 사항들은 모두 알아보았습니다. 이제 위에서 언급한 각각의 요소들을 어떻게 정의하는 것이 좋을지에 대해서 몇 가지 보편적인 관례들을 살펴보고자 합니다. Project마다 각기 규칙을 따르되 세부적인 사항들이 정의되어 있지 않은 경우 아래에서 소개하는 관례를 따라 정의할 것을 권합니다.
(1) Base URL
Base URL은 모든 HTTP endpoint에서 가장 최상위의 URL을 의미합니다. 예를 들어 아래는 Domain명과 경로, 매개변수를 가진 일반적인 URL을 나타내고 있습니다. 경우에 따라 이런 URL은 길어질 수도 있고 복잡해질 수도 있습니다.
https://www.cliel.com/products/item12?code=123
어떤 URL이든 Web상에서 동작하는 데는 문제가 없지만 REST에서는 되도록 간소화를 유지하는 것이 좋습니다. 특히 API의 경우 의미론적으로 일반적인 URL과 분리하기 위해 아래와 같이 api Domain을 사용하는 경우가 많습니다.
(2) Media Type
대부분의 API에서는 Header Content Type이 application/json인 JSON형식을 선호합니다. JSON은 REST API에서 사용하는 가장 흔한 형식이자 가장 일반적으로 채택되는 형식입니다. 또 다른 형식으로 XML이 존재하기는 하지만 문법상으로도 JSON이 훨씬 자유로우며 text의 양이 비교적 적게 사용되기 때문에 성능면에서도 유리하게 작용하고 있습니다. 이와 같은 이치로 직렬화와 역직렬화를 수행 시에도 XML과 비교해 훨씬 효율적이기도 합니다. 직렬화와 역직렬화는 Data구조를 저장가능한 형식으로 전송하는데 많이 사용되는 것으로서 전송 시에는 직렬화를 수행하고 이를 받아 저장하는 경우 역직렬화를 수행하게 됩니다. 예를 들어 Product개체는 고유번호, 이름, 가격등의 정보를 포함하는 다음과 같은 구조가 될 수 있으며
{
"id" : 10,
"name" : Bag,
"price" : 50000
}
이를 XML로 나타내면 다음과 같을 것입니다.
<?xml version="1.0" encoding="utf-8"?>
<product xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Id>10</Id>
<Name>Bag</Name>
<Price>50000</Price>
</product>
예제를 통해 보기에도 JSON이 XML보다 훨씬 간소하다는 것을 알 수 있습니다. 같은 내용을 XML에 비해 훨씬 더 간소하게 유지할 수 있는 것입니다.
참고로 HTTP와 관련된 제약조건이 아니기 때문에 이 Header를 사용하면 Cross-Site Request Forgery(CSRF)와 같은 공격으로부터 보호할 수 있습니다. 이 공격은 Client 측에서 Code를 실행할 수 있도록 하는 것으로, 예를 들어 JavaScript는 Client측 Browser에서 원하는 않는 함수를 실행할 수 있는데, 악성 Code는 차단되고 강제로 직렬화를 수행하여 실행할 수 없도록 합니다. 더 자세한 사항은 아래 Link를 통해 확인하실 수 있습니다.
https://owasp.org/www-community/attacks/csrf
OWASP(Open Worldwide Application Security Project)는 비영리단체로서 개발자를 위한 Resource를 제공하여 보다 안전한 Application을 개발할 수 있도록 지원하고 있습니다.
(3) URL Naming
URL을 잘 작성하는 것은 매우 중요합니다. URL을 읽는 것만으로도 우리는 이 URL에 해당하는 API를 호출함으로써 다른 말을 덧붙이지 않고도 무엇을 할 수 있는지를 알 수 있기 때문입니다. 만약 Database에서 전체 Product 목록을 가져오고 싶다면 URL을 다음과 같이 작성할 수 있을 것입니다.
/getallproducts |
혹은 동일한 기능의 API URL을 아래와 같이 작성하면 훨씬 간단해질 수 있습니다.
/products |
어쩌면 단순히 Product의 복수형을 사용하는 것만으로도 의미를 전달하기에 충분할 수 있습니다. 조금 다른 경우를 예로 들면 만약 10개의 Product를 가져와야 한다고 가정했을 때 아래와 같이 URL을 작성하거나
/getproducts?count=10 |
혹은 이전과 동일한 URL에서 매개변수만 추가하여 작성할 수도 있습니다.
/products?count=10 |
Data를 가져오는 경우 외에도 임의의 Product를 생성하는 경우가 있을 수 있는데
/products |
이때는 위 URL에서 [POST] Method에 따라 동작을 분리할 수 있습니다. 의미론 적으로 부족함이 느껴진다면 단어를 URL에 추가하는 것도 도움이 될 수 있습니다.
/products/create |
POST의 경우에는 요청이 성공적으로 이루어진 경우 201 Created HTTP응답과 함께 생성된 Product의 ID를 반환할 수 있으며 이를 통해 ID값으로 GET요청을 보내 원하는 Product의 정보를 가져올 수 있습니다.
/products/{id} 혹은 /getproduct?id={id} |
같은 논리는 Product를 변경하는 경우에도 적용할 수 있으며 동일한 URL에 대해 POST Method와 Product를 삭제하기 위한 GET Method모두를 적용하여 Product에 대한 CRUD동작을 구현할 수 있습니다.
동작 | Method | URL |
CREATE | POST | /products |
RETRIEVE | GET | /products/{id} |
UPDATE | PUT/PATCH | /products/{id} |
DELETE | DELETE | /products/{id} |
이러한 방식은 연결된 Resource에도 동일하게 적용할 수 있습니다. Data구조가 서로 연결된 경우, 예를 들어 Product가 Category와 연결된 경우 Product는 특정 Category에 속할 수 있습니다. 이때 REST에서 URL은 특정 Category에 속한 Product에 접근하거나 조작하기 위한 접근방식에 따라 경로가 다음과 같이 분리될 수 있습니다.
/categories/{cateogry id}/products |
특정한 Category의 특정한 Product에 접근하거나 조작하는 경우라면 URL을 다음과 같은 구조로 만들 수 있습니다.
/categories/{cateogry id}/products/{product id} |
위 URL을 기반으로 특정한 Category에서 하나 또는 그 이상의 Product를 관리해야 하는 경우 Method에 따라 다음과 같이 동작을 분리합니다.
동작 | Method | URL |
CREATE | POST | /categories/{category id}/products |
RETRIEVE | GET | /categories/{category id}/products/{product id} |
UPDATE | PUT/PATCH | /categories/{category id}/products/{product id} |
DELETE | DELETE | /categories/{category id}/products/{product id} |
물론 Category를 구분하지 않고 product id만으로 처리가 가능한 경우가 있을 수 있지만(Project의 Business logic에 따라 달라질 수 있음), 특정 Product를 다루기 전에 어느 Category에 속하는 Product인지를 구분해야 한다면 Query 매개변수를 사용하는 것보다 위와 같은 방법이 훨씬 좋은 방법이 될 수 있습니다.
참고로 URL 경로에서 ID를 전달하는 것을 Routing이라고 하며 이때 Category ID나 Product ID를 Routing 매개변수라고 합니다.
(4) API Versioning
API는 기능의 추가나 기존 기능의 향상을 위해 수시로 변경/개발되는 경우가 많으며 이로 인해 API의 요청사양이 달라지기도 합니다.(즉, Client와 Server사이에 교 한 되는 Data구조가 변경되는 것입니다.) 이때 대부분의 Client는 변경사항을 빠르게 적용하지 못하는 경우가 많으므로 API는 Client의 늦은 대응에도 맞춰야 할 필요가 있습니다. 이는 최신의 API변경사항을 Client가 적용할 때까지 기존의 API가 그대로 유지되어야 함을 의미합니다. 이런 경우 이에 대한 해결책으로 API Versioning이 사용됩니다. API Versioning은 다수의 API가 Version별로 존재하는 것을 말하며 이런 방식은 URL에 직접 API의 Version을 삽입시키는 방법이 가장 많이 사용됩니다.
- https://api.cliel.com/v1/products
- https://api.cliel.com/v2/products
또한 URL에 직접 API의 Version을 특정하는 대신 API의 Version을 정의하기 위한 다른 방법, 예를 들어 Header를 통해 특정 API의 Version을 요청하는 방식등이 존재하기도 합니다. HTTP는 이를 위한 특정한 Header를 정의하고 있지 않으므로 자체적인 Header를 정의할 수 있습니다.
- GET https://api.cliel.com/products - API-Version: 1 |
개인적으로 Header를 통한 API Versioning을 잘 사용하지는 않지만 이런 방식이 결코 잘못된 것은 아니므로 경우에 따라 얼마든지 사용가능한 방법일 수 있습니다.
API Versioning을 위한 또 다른 방법으로 media type을 사용하기도 합니다. 그런데 이는 흔하지 않은 방식으로 잘 사용되지는 않습니다. Accept/Content-Type Header를 통해 API의 Version을 정의하는 방법으로 사용되는데, 이 방식에 대해서는 따로 알아보지 않을 것이지만 관심이 있다면 Microsoft의 문서를 참고해 볼 수 있습니다.
https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design#media-type-versioning
(5) API 문서화
API가 갖춰야 할 가장 중요한 것 중 하나로서 API를 문서화하는 것이 있습니다. 이 문서화는 API의 사양을 뜻하는 것으로 전용 Endpoint상에서 HTML이나 YAML 혹은 JSON을 통해 표현하며 여기에는 API상에서 사용되는 모든 URL과 필요한 매개변수, Data의 구조, HTTP 상태 Code 등이 포함될 수 있습니다. 이는 Client가 API를 정확하게 사용할 수 있도록 유도하기 위해 필요한 요소들입니다.
이러한 문서화를 수행할 때 가장 많이 언급되는 것으로 OpenAPI 사양이 있는데 이 사양은 일련의 개발 도구를 통해 구현되는 것으로 Swagger가 대표적입니다. Swagger는 별도로 다시 알아볼 예정이지만 OpenAPI에 대해 더 알아보고자 한다면 아래 OpenAPI주도 Website를 참고할 수 있습니다.
https://spec.openapis.org/oas/latest.html
YAML은 꽤 인기가 많은 직렬화 언어입니다. YAML은 잠재적으로 JSON을 대체할 수도 있다는 평가를 받고 있지만 점유율 면에서는 아직 JSON을 따라오지 못하고 있습니다.
https://yaml.org/
'.NET > ASP.NET' 카테고리의 다른 글
[ASP.NET Core 8] ASP.NET Core 8 (5) | 2024.11.04 |
---|---|
NET::ERR_CERT_INVALID 문제 (0) | 2023.11.27 |
ASP.NET Core - 21. ASP.NET Core Identity 사용 (2) | 2023.04.09 |
ASP.NET Core - 20. ASP.NET Core Identity (0) | 2023.04.04 |
ASP.NET Core - [Blazor] 6. DataBlazor Web Assembly (0) | 2023.03.29 |