-
POST 요청에서 form-data VS json네트워크 2022. 11. 30. 15:52
나는 DRF를 이용한 프로젝트들을 많이 진행했다.
최근에 DRF로 개발한 프로젝트를 Flask로 동일하게 개발하는 작업을 시작했다.
회원가입 api를 개발하다가, request body로 들어온 데이터를 꺼내는 과정에서 생긴 궁금증이 있다.
Flask에서 request body로 들어온 데이터를 가져오는 방법으로는 request.get_json() 과 request.form['key 이름'] 이렇게 두 가지가 있다는 것을 알게 되었다.
옛날에 맨 처음에 백엔드 서버를 개발한 프로젝트에서 Flask를 사용했는데, 그때는 client도 jinja template을 이용해서 함께 개발했기 때문에 request.form 방식을 사용했다.
그때는 request.form이 뭔지 request.get_json()이 뭔지도 모르고 그냥 썼던것 같다.
그런데 지금 보니, 둘의 쓰임에 차이가 있을것 같다는 생각이 들었다.
메소드가 구분되어 있고 이름도 다르니 당연한 이야기 이겠지만, 둘의 차이에 대해 궁금해졌다는 것만으로도 내가 조금은 시야가 넓어졌다는거 아닐까.
아무튼, 평소에 api 테스트는 테스트코드를 작성해서 하기도 하지만, 이번 프로젝트는 테스트 코드보다도 일단 flask를 이용해서 기존의 기능들을 다 개발할 수 있는지에 초점을 맞추었기 때문에 포스트맨으로 테스트를 해보았다.
포스트맨을 사용해봤다면 알겠지만, request body의 여러 content-type중에서 선택해서 요청을 보낼 수 있다.
DRF를 사용할때는 content-type을 신경쓰지 않고 form-data로 요청을 보내든, json으로 요청을 보내든 잘 동작했다.
그런데 Flask에서는 타입에 따라 위에 말했던 메소드를 타입에 적절하게 선택해야 한다. 그렇지 않으면 bad request 400에러가 난다.
내가 테스트 했던 코드인데, 위 이미지에서는 request에서 json 데이터를 가져오게 되어있기 때문에 요청을 보낼때 content-type을 json으로 해주어야 한다.
그리고 아래에 주석처리된 코드는 request의 form 데이터를 가져오기 때문에 해당코드를 주석해제해서 사용하고 싶다면 json을 가져오는 코드는 없애고 요청에서도 form-data로 요청을 보내야 한다.
여기서 생기는 내 의문은 두 가지 였다.
1. json과 form-data의 차이가 무엇이고, 어떤걸 선택해야 할까?
우선 json은 가장 흔하게 쓰이는 데이터타입이고, key-value 형식의 데이터이다. 파이썬의 딕셔너리와 동일한 구조이다.
그리고 form-data는 multipart/form-data 라고도 하는데, 이미지와 같은 파일을 웹에서 업로드 할때 form-data로 받는다.
multipart 에 초점을 두고 추가로 설명하자면, 우선 HTTP Request에서는 대부분 body에 한 종류의 content-type만 지정할 수 있다.
그런데 이미지를 업로드 한다고 했을때, 이미지 파일과 그 이미지에 대한 설명을 동시에 업로드하는 경우에 이 두개의 인풋은 content-type이 다르다.
이런 경우 두 개의 인풋 데이터를 구분해서 넣기 위해 multipart/form-data로 요청을 보내는 것이다.
나는 지금까지 이미지나 다른 파일을 요청에 담아 보내는 기능은 개발한적이 없고, 대부분 텍스트만 다뤘기 때문에 json 형태로 보내든, form-data 형태로 보내든 크게 상관이 없었다.
만약 이미지와 같은 파일을 업로드 해야하는 경우라면 multipart/form-data로 요청의 content-type을 지정하는것이 더 적절하고, 그게 아니라면 보편적으로 많이 쓰이는 json으로 지정하는게 더 적절하다는 나의 생각이다.
2. DRF에서는 content-type을 json으로 하든, form-data로 하든 에러가 나지 않았는데 왜 그랬을까?
다음으로는 이런 의문이 생겼다. 지금까지 텍스트 위주의 input만 받았어도, 어쨌거나 요청의 content-type에 따라 데이터를 가져오는 방식을 다르게 취해야 하는데, DRF에서는 이런걸 고려하지 않고 요청을 보내도 문제가 없었다.
왜 그랬을까?
Django가 Flask보다 무거운 프레임워크이고, 자체 구현된 기능이나 설정이 많다는건 알고 있었다.
그렇기 때문에 이것도 DRF가 뒤에서(?) 알아서 처리해줬기 때문에 내가 신경쓰지 않아도 기능이 잘 동작했을 거라고는 짐작할 수 있었다.
그럼, 어느 부분에서 이걸 처리해 주는걸까?
Django로 프로젝트를 시작하면 settings.py가 자동 생성되고 파일을 들여다 보면 미들웨어 부분이 있다.
이 미들웨어에 대해서는 기술면접에서 종종 질문을 받았는데, 제대로 답변하지 못했고 그 이후에 조금 알아본 결과로는
Django에서 요청을 받으면 미들웨어를 거쳐서 응답을 내준다는 것이다. 모든 요청에 대해 공통적으로 수행하고 싶은 일들은 미들웨어를 통해 적용할 수 있다.
(예를 들어서, response의 형식을 api view에서 각각 설정해주는 것이 아니라 공통적으로 적용하고 싶다면 response 형식을 맞춰주는 클래스를 만들어서 미들웨어로 적용시킬 수 있다.)
그래서 HTTP 요청에서 header 부분에 content-type에 대한 정보가 담겨 있는데, 그 정보를 통해 적절하게 데이터를 parsing 해서 DRF에서는 request.data 라는 코드 만으로 모든 타입의 데이터를 가져올 수가 있는 것이다.(라는 나의 짐작이다.)
DRF에서 회원가입을 처리하는 view의 코드이고, request.data를 통해 데이터를 가져온다. 그리고 이번에 또 새롭게 알게된 것이, DRF를 설치하고 settings.py에서 DRF관련한 설정을 보면 데이터 parser 클래스들을 지정할 수 있는 것이다.
내가 이전에 했던 DRF 프로젝트의 settings.py를 보니 이렇게 설정이 있었고, 보면 DEFAULT_PARSER_CLASSES에 json, form, multipart parser 클래스들이 모두 지정되어 있다.
내가 설정을 이렇게 해주었기 때문에 json도 form-data도 모두 처리할 수 있었던 것이다.
(사실 이 설정은 DRF 튜토리얼 찾아보면서 그냥 복붙한 설정이다. 😅)
https://www.django-rest-framework.org/api-guide/parsers/
Parsers - Django REST framework
www.django-rest-framework.org
DRF 공식 문서를 보면 parser class에 대해 더 자세히 알 수 있다.
만약 parser 클래스를 json만 남겨두고 form-data 요청을 보내면 아래와 같은 결과를 볼 수 있다.
그래서 결론은, DRF 설정에서 데이터 parser 클래스를 지정해둔거에 따라 미들웨어에서 요청의 body의 content-type을 보고 적절하게 처리해주는것이 아닐까?!
DRF의 설정과 미들웨어의 연관성에 대한 내용은 아직 찾지 못해서 나의 추측이라서 좀 더 알아보긴 해야한다.
아무튼, json과 form-data에 대해 알아보면서 Django가 얼마나 친절했는지(?) 깨닫게 되었다.
그리고 확실히 Django만 사용했을때는 미처 신경쓰지 못했던 부분들을 Flask를 사용하면서 알게되어서, 두 프레임워크를 비교해 보는 것이 큰 공부가 될것같다는 것도 체감했다.