HTTP의 요청 메세지를 특정짓는 메서드에 대해 알아보자. HTTP 메서드는 고작 8개로 수는 적지만 중요한 역할을 하고 있다. 메서드가 8개로 충분한가? 그 이유와 HTTP 설계상의 고심한 흔적들을 공부해보자
메서드는 클라이언트가 하고 싶은 처리를 서버에게 전달하는 중요한 임무가 있다. HTTP 1.1은 8개의 메서드가 정의되어 있고 그 중에서도 주로 사용하는 메서드는 5~6개이다.
일반적인 프로그래밍 언어의 감각으로는 왜 이렇게 메서드를 한정해놓았는지, 이정도로 충분한지 궁금해진다. 하지만, 이렇게 메서드 수를 줄여 놓았기 때문에 HTTP가 그리고 웹이 성공할 수 있었다.
이 장에서는 TRACE와 CONNECT를 제외한 6개의 메서드에 대해 알아본다.
HTTP 메서드 중 GET, POST, PUT, DELETE는 이들 4개의 메서드로 CRUD라는 성질을 충족시키고 있으므로 대표적인 메서드라고 할 수 있다.
CRUD는 데이터 조작의 기본이 되는 4가지 처리를 말한다.
GET은 지정한 URI의 정보를 가져온다. 가장 이용 빈도가 높은 메서드이기도 하다. 웹 페이지, 이미지, 동영상 등의 취득을 위해 우리들이 브라우저를 이용할 때는 언제나 수많은 GET을 발행한다.
상기의 요청과 URI에 대응하는 데이터를 응답으로 반환
POST는 GET 다음으로 이용 빈도가 높은 메서드이다. POST에는 3가지 역할이 있다.
POST의 대표적 기능은 어떤 리소스에 대한 서브 리소스의 작성이다. 블로그 기사의 투고 등 조작에 사용한다.
이 요청에서는 새로운 서브 리소스를 작성하도록 POST로 지시하고 있다. POST의 바디에는 새로 작성할 리소스의 내용이 들어가 있다.
응답에서는 201Created라는 스테이터스 코드가 반환되었다. 이 코드는 새로운 리소스를 생성했다는 것을 보여준다. Location에는 헤더에 새롭게 생성한 리소스의 URI가 들어 있다.
서브리소스의 생성만큼 일반적이지는 않지만 POST의 대표적인 기능의 두 번째는 기존의 리소스에 데이터를 추가하는 것이다.
응답에서는 201이 아니라 200이 반환되었다. 새로운 리소스의 작성이 아니라 데이터의 추가를 의미했기 때문이다. 데이터를 추가하기 위해 POST를 사용했을 때, 그 데이터를 리소스의 끝에 추가할지 처음에 추가할지는 서버의 구현에 의존한다.
또한, 처음부터 어떤 리소스에 요청된 POST가 작성을 의미하는 것인지 데이터의 추가를 의미하는 것인지도 구현에 달려있다.
즉, URI만 봐서는 POST가 어떻게 움직이는지 알 수가 없다. POST의 동작은 웹 서비스와 웹 API의 스펙시트 등으로 표현한다.
POST의 세 번째 기능은 다른 메서드로는 대응할 수 없는 처리의 실행이다.
예를 들어보자
일반적으로 이 URI를 GET 함으로써 검색을 실행하지만, 키워드가 매우 긴 경우엔 어떻게 될까?
URI의 길이는 키워드에 연동된다. 4장에서 언급했듯이 이론적으로 URI의 길이는 제한이 없지만 구현상 2,000자 등의 상한선이 존재했다. 그런 긴 키워드의 경우 URI에 키워드를 넣고 GET 하는 방식은 이용할 수 없다.
그런 경우엔 POST를 이용한다.
GET에서는 URI에 포함시켰던 키워드를 POST 요청 메세지의 바디에 넣었다. 이렇게 함으로써 아무리 긴 키워드라도 사용할 수 있다.
PUT은 두 가지의 기능을 가지고 있다. 리소스의 갱신과 리소스의 작성이다.
이 예에는 PUT으로의 응답에 리소스를 갱신한 결과를 나타내는 표현이 들어있다. PUT에 대한 응답은 이 예와 같이 바디에 결과를 넣어도 좋고, 바디에는 아무것도 넣지 않고 응답이 바디를 가지지 않는다는 것을 나타내는 204 No Content를 반환해도 상관 없다.
이 PUT은 존재하지 않는 URI에 대한 요청이기 때문에 서버는 리소스를 새로 작성한다고 해석하고 요청이 성공한 경우에는 201 Created를 반환한다.
POST의 경우는 새롭게 작성한 URI가 Location 헤더로 반환되었지만, PUT의 경우는 클라이언트가 이미 리소스의 URI를 알고 있기 떄문에 Location 헤더를 반환할 필요가 없다.
/newitem이 이미 존재하는 경우에는 앞에서 말했던 리소스를 갱신하는 처리가 된다.
이제 POST와 PUT을 활용하여 리소스를 작성할 수 있다는 것을 알았다. 그럼 두 가지를 어떻게 구분하여 사용해야하는가 ?
정답은 없지만 설계상의 지침으로서 다음과 같은 사실이 있다.
POST로 리소스를 작성할 경우, 클라이언트는 리소스의 URI를 지정할 수 없다. URI의 결정권은 서버에 있다.
반대로 PUT으로 리소스를 작성할 경우, 리소스의 URI는 클라이언트가 결정한다.
예를 들어, Twitter와 같이 포스팅한 POST를 이용하는 것이 일반적이다. 반대로 Wiki처럼 클라이언트가 결정한 타이틀이 그대로 URI가 되는 웹 서비스는 PUT을 사용하는 편이 적합하다. 단, PUT의 경우 리소스가 중복되어 덮어쓰기 되는 것을 방지하기 위해 클라이언트에서 사전에 URI의 존재를 체크해야만 할지도 모른다.
일반적으로 클라이언트가 리소스의 URI를 결정할 수 있다는 것은 클라이언트를 만드는 프로그래머가 서버의 내부구현을 숙지하고 있어야만 한다. 그 때문에 PUT쪽이 아무래도 서버와의 결합이 밀접하다. 특별한 이유가 없는 한 리소스의 작성은 POST로 수행하여 URI를 서버 측에서 결정하는 설계가 바람직하다.
말그대로 리소스를 삭제하는 메서드이다. 일반적으로 응답은 바디를 가지지 않는다. 때문에 응답의 스테이터스 코드에 바디가 없다는 의미의 204 NoContent가 사용되는 경우도 있다.
HEAD는 GET과 유사한 메서드이다. GET은 리소스를 취득하는 메서드지만, HEAD는 리소스의 헤더만을 취득한다.
HEAD에 응답에는 바디가 포함되지 않는다. 이 성질을 이용하여 네트워크의 대역을 절약하며 리소스의 크기를 조사하거나, 리소스의 갱신인자를 구할 수 있다.
마지막 메서드는 OPTION이다. 리소스가 지원하고 있는 메서드의 목록을 반환한다.
OPTIONS를 구현하는 경우, 많은 웹 어플리케이션 프레임워크에서는 리소스마다 대응하는 메서드를 반환하도록 직접 구현하지 않으면 안된다. Apache와 같은 WebDAV에 대응하는 웹 서버에서는 설정파일로 OPTIONS의 동작을 설정할 수 있다.
지금까지 설명했던 메서드들이 HTTP의 주요 메서드이다. 하지만, 현실적으로 가장 많이 이용되고 있는 것은 GET과 POST의 2가지이다. 이것은 HTML의 품에서 지정할 수 있는 메서드가 GET과 POST뿐이라는 제한에서 기인한다.
이러한 HTML의 제한에 의해 웹 애플리케이션에서는 GET과 POST만을 이용하는 시대가 오랜 기간 지속되었다. 하지만, 이런 제한은 Ajax의 발전과 함께 해소 되고있다. XMLHttpRequest라는 모듈을 이용하면 임의의 메서드를 발행할 수 있기 때문이다.
하지만, 여전히 GET과 POST만을 사용해야 하는 상황도 존재한다. XMLHttpRequest를 지원하지 않는 휴대전화용 브라우저에선 폼을 이용해야 하기 때문이다. 또한 보안상의 이유로 프록시 서버에서 GET과 POST 이외의 접근을 제한하는 경우도 있다.
우린 PUT이나 DELETE를 전달하는 2가지 방법을 알아보자
첫 번째는 _method 파라미터를 이용하는 방법이다. 폼의 숨겨진 파라미터에 _method라는 파라미터를 준비하고 그 곳에 원래 보내고 싶었던 메서드의 이름을 넣는다.
<form target="/List/item1" action = "POST">
<input type="hidden" id="_method" value="PUT">
<textarea id="body">...</textarea>
</form>
이 폼을 송신하면 다음의 요청이 보내진다.
HTTP 메서드와 갱신일자 등으로 헤더를 구성하면 메서드의 실행 여부를 리소스의 갱신일자를 조건으로 서버가 선택할 수 있다. 이러한 요청을 가리켜 '조건부 요청'이라고 한다.
예를 들어, GET에 리소스의 갱신일자를 조건으로 넣기 위해 If-Modified-Since 헤더를 사용한다. 이 헤더가 들어간 GET은 리소스가 이 시간 이후 갱신되어 있으면 GET한다는 의미가 된다.
통신 에러가 발생했을 때 요청을 어떻게 회복할 것인지는 HTTP에 있어서 중대한 과제이다. HTTP의 스펙에는 프로토콜의 스테이트리스성을 유지하며 이 문제를 해결하기 위한 아이디어가 도입되어있다.
표에는 멱등성과 안전이라는 성질이 등장한다.
멱등성이란 어떤 조작을 여러번 반복하더라도 결과가 동일한 것을 의미하는 수학용어이다. PUT과 DELETE는 멱등이므로 같은 리소스에 여러번 실행하여도 반드시 같은 결과를 얻을 수 있다.
안전성이란 조작대상의 리소스의 상태를 변화시키지 않는 것을 의미한다. 리소스의 상태에 변화를 부여하는 것을 부작용이라고 한다. 안전은 조작대상인 리소스에 부작용이 없는 것이라고도 한다. 예를 들어 GET에는 부작용이 없어서 GET을 같은 리소스에 여러번 실행하여도 리소스 상태는 변하지 않는다.
POST는 안전하지도 멱등하지도 않다. 다시 말해 요청의 결과로 무엇이 일어날지 모른다. 클라이언트는 POST를 여러 번 보내는 것에 신중하지 않으면 안된다.
쇼핑 사이트 등에서 브라우저의 뒤로 버튼을 조작했을 때 "다시 전송하겠냐 ?" 라는 다이얼로그가 나올 때가 있다.
이것은 POST를 재전송 하려는 경우이다. 이런 장치가 없다면 이중 주문과 같은 문제가 일어날 가능성이 있다.
웹 서비스와 웹 API의 설계를 잘못하면 이들 메서드가 안전하지 않게 되거나 멱등이 아니게 될 가능성이 생긴다.
GET의 목적은 리소스의 취득이다. 하지만, 잘못된 목적으로 GET을 이용하고 있는 웹 API가 무시할 수 없는 비율로 눈에 들어온다. 이것은 GET의 안정성을 파괴하는 사용법이다.
이 예에서는 /resources/1을 삭제하기 위해 resources/1/delete를 GET하고 있다. 이는 리소스를 가져오는 GET의 목적을 완전히 무시한 사용방법이다.
GET으로 리스트를 변경하거나 리소스를 삭제하는 것은 GET의 잘못된 사용방법이다. GET을 바르게 이용하고 있는 지에 대한 판단 기준은 GET의 실행 전후에 리소스에 변경이 가해지는지를 판단하면 된다. 또한, GET하려고 하는 리소스의 URI에 'update', 'delete', 'set'등의 동사가 들어있는 경우도 주의를 요한다. GET해야 하는 URI에 왜 동사가 들어가는가를 이것은 모순이다.
다른 메서드로 대응할 수 없는 처리를 하는 POST는 만능이다. 하지만 지나치면 오용이다. 그 밖에 적절한 메서드가 마련되어 있는데도 불구하고 POST로 그 기능을 실현하면 안된다.
특히 GET, PUT, DELETE로 가능한 기능을 POST로 구현하려는 것은 위험 신호이다. GET, PUT, DELETE가 가진 성질을 이용할 수 없게 된다.
POST 오용의 으뜸가는 예는 XML-RPC와 SOAP이다. 둘 다 RPC를 구현하기 위한 프로토콜이지만 모든 함수 호출을 POST로 구현하도록 설계되어 있다. 예를 들어, 그 함수가 데이터의 취득과 삭제를 하는 것이라도 POST를 사용한다.
<XML-RPC로 getCount 함수를 호출하는 예>
현재 토마토의 가격은 100원이다. PUT으로 토마토의 가격을 갱신해보겠다.
150원이 되었다 다시 한번 같은 요청을 보내보겠다.
200원이 되었다. 이처럼 PUT으로 리소스의 내용의 상대적인 차분을 전송하면 PUT은 멱등이 아니게 된다. PUT으로는 그 리소스의 완전한 표현을 전송하도록 해야한다.
알리어스를 예로 들겠다. 버전이 명시되지 않은채 실제 최신 버전의 리소스를 삭제해버린다면 어떻게 될까 ?
멱등이 아니게 된다. 최초의 DELETE에서는 1.2가 삭제되고 다음 DELETE에서 1.1이 삭제 되었다. 최초의 예와 같이 알리어스 리소스만 삭제할 수 있도록 설계하는 것도 해결책의 하나지만, 알리어스 리소스와 같은 특수한 리소스는 특별한 이유가 없는 한, 갱신과 삭제 등의 조작을 할 수 없도록 설계하는 것이 바람직하다.
-> 갓로토콜 HTTP