들어가기 전에하기 포스팅은 "스프링부트 시작하기(김인우 저)" 책을 공부하며 적은 포스팅입니다. 이번 포스팅에서는 RESTful에 대해 살펴보도록 하겠습니다. REST란?REST란 REpresentational State Tranfer의 약자로, HTTP 창시자 중 한 사람인 로이 필딩(Roy Fielding)이 2000년에 발표한 박사 학위 논문에서 소개되었습니다. 로이 필딩은 기존 웹 아키텍처가 HTTP 본래의 우수성을 잘 활용하지 못한다고 생각하여 HTTP의 장점을 최대한 활용할 수 있는 아키텍처로 REST를 소개했습니다. 잘 표현된 HTTP URI로 리소스를 정의하고, HTTP 메소드로 리소스에 대한 행위를 정의합니다. 리소스는 JSON, XML과 같은 여러 언어로 표현할 수 있습니다. REST의 특징을 지키는 API를 'RESTful하다'라고 표현하기도 합니다. 리소스리소스는 서비스를 제공하는 시스템의 자원을 의미하며 URI(Uniform Resource Identifier)로 정의됩니다. 즉, REST API의 URI는 리소스의 자원을 표현해야 합니다. REST API의 URI 설계 시 일반적으로 아래와 같은 규칙을 적용합니다.
HTTP 메소드HTTP에는 여러 메소드가 있는데 REST 서비스에서는 CRUD에 해당하는 4개의 메소드를 사용합니다. CRUD란, Create/Read/Update/Delete의 약자로 소프트웨어의 기본적인 데이터 처리 기능을 나타냅니다.
REST API에서는 4개의 메소드를 이용해 리소스에 대한 행위를 정의합니다. RESTful 게시판으로 변경하기컨트롤러 작성하기이전까지 만든 BoardController 클래스와 비교해서 확인할 수 있도록 RESTful 컨트롤러를 새로 만들겠습니다. 앞서 만든 게시글 목록 및 상세 화면, 수정, 삭제, 첨부파일 관련 기능까지 한번에 작성합니다.
controller 패키지에 RestBoardController 클래스를 만들고 아래와 같이 코드를 작성합니다.
서비스, 매퍼, 쿼리 영역서비스, 매퍼, 쿼리는 기존에 만들었던 클래스 및 쿼리를 동일하게 사용하므로 따로 수정하지 않았습니다. 뷰 템플릿뷰 템플릿의 경우, 기존 뷰와 비교했을 때 호출하는 주소가 변경된 것 외에는 큰 변경사항이 없습니다. 따라서, 기존 뷰 템플릿을 복사하여 동일한 파일을 만들고 몇 가지 수정하면 됩니다. 먼저 templates.board 폴더 밑의 기존 템플릿을 복사하여 각각 restBoardList.html, restBoardWrite.html, restBoardDetail.html 파일을 생성합니다. 게시글 목록 게시글 목록 화면에서는 상세 화면 및 작성 화면의 링크 주소와 변수들의 이름만 변경하면 됩니다.
게시글 작성 화면 게시글 작성 화면은 데이터를 전송하는 폼의 주소만 변경되면 됩니다.
게시글 상세 화면
application.properties 속성 추가하기 HiddenHttpMethodFilter을 사용하기 위해서는 아래 속성이 true여야 합니다. 기본적으로 HiddenHttpMethodFilter는 SpringBoot 2.2부터는 내장되어 있지만, default값으로 false가 설정되어 있기 때문에 _method를 통해 form 태그를 PUT/DELETE 방식으로 사용하기 위해서는 하기 속성 true로 바꿔주어야 합니다.
application.yml을 사용한다면 아래처럼 작성해줍니다.
게시글 작성 확인하기먼저, localhost:8080/board/를 호출하여 게시글 목록 화면으로 이동합니다. 그 후 "글 쓰기" 버튼을 클릭하여 게시판 등록 화면을 띄웁니다. 테스트 내용 및 첨부파일을 넣어 저장 버튼을 클릭하여 게시글 목록에 신규 게시글이 생성되었는지 확인합니다. 신규 게시글을 열어보면 이전에 썼던 내용이 잘 들어가있음을 확인할 수 있습니다. RESTful로 작성하여, 주소창에 간결하게 URI가 뜨는 것을 확인할 수 있습니다. 게시글 수정, 삭제 확인하기다음으로 게시글의 수정 및 삭제가 정상적으로 동작하는지 확인하겠습니다. 게시글 상세, 수정, 삭제 기능은 모두 /board/글 번호로 URI가 동일하고 요청 방식만 구분되어 있습니다. HTML에서 form의 DELETE, PUT 요청 방식을 지원하지 않아 HiddenHttpMethodFilter를 등록하고 화면에서 _method 파라미터를 사용했던 점을 생각하면서 확인해보겠습니다. RESTful 게시판 생성 시 직면할 수 있는 문제점org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported 에러 책을 보고 한 실습이지만, 아래와 같이 org.sprngframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported 에러에 직면했습니다. 처음에는 위 restBoardDetail.html 내용에 적어둔 URI의 undefined 문제인 줄 알았으나, 해당 문제를 해결해도 아래와 같이 에러를 만났습니다. 일차적으로 해당 에러가 나왔다는 것은, /board/게시글번호 URI를 가진 컨트롤러 메소드 중 POST를 허용하는 메소드가 없어 발생했다는 것입니다. 현재, 컨트롤러상에서는 POST가 아닌, GET/PUT/DELETE용 메소드만 생성했기 때문에 POST를 허용하지 않았습니다. HiddenHttpMethodFilter를 사용하면, POST로 form을 보내되, name이 _method인 태그의 값(value)을 찾아 해당 값의 HTTP 방식을 허용하는 컨트롤러 메소드를 찾게 됩니다. 위 상황상 _method에 HTTP 방식(예: put, delete)을 넣어서 보냈으나 무슨 이유 때문인지 해당 값을 인지하지 못하는 것으로 보였습니다. 관련하여 찾아보니, 기존 게시글에 파일 추가하여 수정할 수 있도록 코드를 변경할 때 파일 업로드를 위해 form 태그의 속성 중 enctype="multipart/form-data"를 사용한 것이 문제였습니다. 기본적으로 Apache에서 나온 FileUpload의 ServletFileUpload의 isMultipartContent 내용을 보면 아래와 같습니다.
위와 같이 하드코딩으로 직접 POST만 되도록 구현한 이유는 아래 두 글을 참고하면 좋을 것 같습니다. 해당 사이트에 들어가면 HTTP 창시자 중 한 사람인 로이 필딩이 관련해서 코멘드를 남겼고 해당 코멘드를 보면 이유를 알 수 있습니다. PUT 메소드 자체가 리소스 값을 교체한다는 의미이기 때문에, form 안의 모든 데이터를 multipart로 보내지 말고 이미지와 같은 파일의 경우 리소스 URI를 따로 빼서 PUT 요청을 하라는 의미로 보면 될 것 같습니다. SpringBoot를 좀 더 잘 사용할 수 있다면, form을 두개로 나누어 보내보겠지만, 아직은 조금 부족한 감이 있어 일단 게시글 수정 시 파일을 추가 업로드 하는 기능은 빼고 구현했습니다. 게시글 수정 시 파일을 추가 업로드 하는 기능을 빼면 form 태그의 enctype 속성을 뺄 수 있어 문제 없이 구현이 가능합니다. blog.outsider.ne.kr/1001 multipart는 HTTP POST로만 전송해야 한다 :: Outsider's Dev Story 작업을 하다가 `multipart/form-data`로 PUT 전송을 할 일이 생겼다. 예를 들어 일반적인 회원가입 폼을 생각해 보면 회원가입시에는 `/signup`에 POST로 폼 전송을 하지만 회원 가입 후 회원정보를 갱신한 blog.outsider.ne.kr issues.apache.org/jira/browse/FILEUPLOAD-197
[FILEUPLOAD-197] ServletFileUpload isMultipartContent method does not support HTTP PUT - ASF JIRA This method explicitly checks for method POST. I believe the PUT method can also have multipart requests, and there may be others. In our case we are receiving rest calls using Spring Framework's CommonsMultipartResolver which in turn uses this method of t issues.apache.org |