여러 추가 기능 구현과 JS를 이용한 토큰 리프레시 구현을 하였다.
1. Pagination과 generics
페이지네이션을 구현하기 위해서는 두가지 방법을 사용할 수 있다.
Django에서 지원하는 paginator 사용
from django.core.paginator import Paginator
...
# get view
all_post = Posting.objects.all()
all_post = all_post.order_by('-created_at')
page = int(request.GET.get('page', '1'))
paginator = Paginator(all_post, 5)
post_list = paginator.get_page(page)
# 이후 시리얼라이즈
...
paginator.get_page() 메서드는 입력받은 번호의 페이지에 해당하는 객체의 리스트를 반환한다. 이를 시리얼라이저로 처리해 전달할 수 있다.
DRF의 generics의 View들 활용하기
generics.ListAPIView나 ListCreateAPIView를 사용하면 pagination을 편리하게 적용할 수 있다.
class ArticleView(generics.ListCreateAPIView):
"""
ListCreateAPIView는 GET요청일 때 목록으로 조회, POST요청일 때 새로운 데이터를 생성하는 역할을 합니다.
def get의 역할을 ListCreateAPIView에서 지원하기에 따로 지정해 줄 필요가 없습니다.
queryset을 통해 article의 내용을 가져옵니다.
필요한 것을 명시해주면 간단히 get 요청을 구현할 수 있습니다.
"""
paginations_class = ArticlePagination
serializer_class = ArticleListSerializer
queryset = Article.objects.all().order_by("-created_at")
post 요청이 필요없다면 ListAPIView를 사용하면 된다.
페이지네이션 클래스는 따로 지정해주
def get_queryset(self):
articles = self.request.user.bookmarked_articles.all()
return articles.order_by("-created_at")
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(
get_object_or_404(User, id=kwargs.get("user_id"))
.article_set.all()
.order_by("-created_at")
)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
또한 query_set을 지정할 때 request나 path_variable을 사용해야한다면 get_queryset 메서드를 오버라이딩해서 커스텀하거나 list메서드를 오버라이딩 하여 커스텀 할 수 있다. 단, 이경우 path_variable을 사용하기보단 request 에서 바로 까볼 수 있는 query string에 해당 값을 전달하는 것이 나을 것 같다.
페이지네이션을 위해 해당 클래스를 사용할시 get 요청시 응답 값음 다음의 형태를 띈다.
{
"count": 17,
"next": "http://localhost:8000/article/?page=2",
"previous": null,
"results": [
{"title": "ㅁㄴㅇㄹㅁㄴㅁㅇㄹ", "author": "abcdefg"},
{"title": "asfkweqmrsaf", "author": "abcdefg1"},
{"title": "asfkweqmrsaf", "author": "abcdefg1"},
{"title": "asfkweqmrsaf", "author": "abcdefg1"},
{"title": "asfkweqmrsaf", "author": "abcdefg1"}
]
}
2. JS - token refresh
유효기간이 짧은 Access Token을 갱신(재발급)하기 위해서는 refresh 토큰을 제출하여 검증 후 access 토큰을 응답으로 받아야한다. 매 요청을 보낼 때 마다 이러한 과정이 발생하면, refresh 토큰의 사용이 사실상 access 토큰과 다를바가 없어지니, 이는 지양해야할 것이다.
따라서 언제 refresh 요청을 보낼지 결정해야한다.
방식 1. - 요청을 보낼 때 마다, 프론트에서 exp(만료기간)을 확인해 지났으면 갱신한다.
프론트 엔드에서 비교하여 시기를 정하므로 요청의 횟수를 가장 많이 줄일 수 있지만 형식문제로 인해 제대로 구현하지 못했다.
방식 2. - 요청을 일단 보내고, 401이 뜨면 토큰을 갱신한다.
필요한 요청만 보내므로 요청횟수가 비교적 적지만 토큰이 필요한 요청이 여러개 일 때 중복된 코드를 어떻게 처리해야할지 감을 잡지 못하여 구현하지 못했다.
방식 3. - verify API에 먼저 요청을 보내 401이 뜨면 토큰을 갱신하고 원하는 곳에 요청을 보낸다.
필요한 요청보다 한번 더 보내므로 요청 횟수가 많지만, 함수 하나 만들어서 불러오면 되는 아주 쉬운 구현 난이도를 가지고 있어 이것으로 시도 하였다.
async function get_access_token() {
const token = localStorage.getItem('access')
if (token == null) {
return null
}
else {
const response = await fetch(`${backend_base_url}/user/verify/`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ "token": token })
})
if (response.status == 200) {
return token
}
else {
return await refresh()
}
}
}
먼저 verify 에 access토큰을 실어 post 요청을 보낸다. 응답으로 200이 나오면 유효한것이고 아니면 유효하지 않은 토큰이므로 refresh 함수를 실행해 요청을 보낸다.
async function refresh() {
const refresh_token = localStorage.getItem("refresh")
if (refresh_token == null) {
return null
}
const response = await fetch(`${backend_base_url}/user/refresh/`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
"refresh": refresh_token
})
})
if (response.status == 401) {
alert("로그아웃되었습니다 다시 로그인해주세요")
localStorage.removeItem("payload")
localStorage.removeItem("access")
localStorage.removeItem("refresh")
window.location.href=`${frontend_base_url}`
return null
}
const response_json = await response.json();
const jsonPayload = await parse_payload(response_json.access);
localStorage.setItem("payload", jsonPayload);
localStorage.setItem("access", response_json.access);
return localStorage.getItem("access")
}
재발급 요청을 보낼 때 응답이 401 이면 로그아웃처리된다. 아닐경우 새 access 토큰과 페이로드를 파싱해 로컬 스토리지에 저장한다.
3. EC2로 백엔드 배포
먼저 EC2 인스턴스를 생성한 뒤 다음 명령어를 입력해 시스템 소프트웨어들을 최신화한다.
sudo apt update
sudo apt upgrade
이후 다음 명령어를 입력해 python 의 관리를 용이하게 한다.
apt install python3-pip python3-dev python3-venv
가상환경을설정하고, 거기서 필요한 패키지들을 requrements.txt에 의해 설치한다.
깃헙에 업로드한 백엔드 어플리케이션을 clone 해온다. 이때, ssh키를 생성하고 이를 등록하여 보안을 확보한다.
ssh-keygen -t rsa -b 4096
cat /home/ubuntu/.ssh/id_rsa.pub
settings.py를 수정한다.
- DEBUG = False : 디버그 모드를 끄면 더이상 요청에 대해 에러 페이지로 응답하지 않는다. 또한 여러 보안 스펙 등이 달라진다고 한다.
- AllowHost : EC2의 public IP 주소와 추후 사용할 도메인을 함께 등록해둔다.
- CORS_ALLOWED_ORIGINS : EC2 public IP와 앞으로 사용할 프론트엔드의 IP, 도메인등을 등록할 수 있다.
- static 과 media 파일 관련 설정이 있는지 확인한다.
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "static"
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"
이후 gunicorn과 nginx를 설치한다.
- gunicorn:
- gunicorn은 Django와 같은 웹 애플리케이션을 실행하는 서버이다. runserver의 성능적 부족을 해결한다.
- 여러 개의 동시 요청을 처리하기 위해 여러 프로세스를 사용하며 안정적이고 빠른 서비스를 제공한다.
- 주로 프로덕션 환경에서 Django 애플리케이션을 실행하는 데 사용한다.
- Nginx:
- Nginx는 웹 서버로, 정적 파일 서비스와 프록시 서버 기능을 수행한다.
- 정적 파일 서비스: 웹 사이트의 이미지, CSS, JavaScript와 같은 정적 파일을 빠르게 제공
- 프록시 서버는 클라이언트 요청을 받아 애플리케이션 서버(예: gunicorn)로 전달
- 또한 로드 밸런싱과 SSL/TLS 암호화와 같은 기능도 제공한다. 그외 보안, 성능 및 부하 분산을 위해 주로 프로덕션 환경에서 사용된다.
pip install gunicorn
sudo vim /etc/systemd/system/gunicorn.service
[Unit]
Description=gunicorn daemon
After=network.target
[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/{루트폴더이름}
ExecStart=/home/ubuntu/{루트폴더이름}/venv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/home/ubuntu/{루트폴더이름}/{프로젝트이름}.sock {프로젝트이름}.wsgi:application
[Install]
WantedBy=multi-user.target
위명령어 실행 및 파일작성을 통해 프로젝트명.socket 이 만들어진다. 이수 sudo systemctl status gunicorn 이라고 하면 잘 작동하는 지 확인할 수 있다. .env파일로 시크릿키를 숨긴경우 EnvironmentFiles
sudo apt install nginx
sudo vim /etc/nginx/sites-available/drf_project
```jsx
server {
listen 80;
server_name your_domain_or_ip;
location / {
include proxy_params;
proxy_pass http://unix:/home/ubuntu/{루트폴더이름}/{프로젝트이름}.sock;
}
location /static {
root /home/ubuntu/{루트폴더이름}/;
}
location /media {
root /home/ubuntu/{루트폴더이름}/;
}
}
```
위 과정을 통해 Django와 gunicorn, nginx를 연결할 수 있다. 이후 권한 문제로 오류가 발생할 시 다음과 같이 입력한다.
chmod 755 ~/django_deploy/
chmod 755 ~
권한 설정을 이렇게 할 경우 소유자에게 읽기, 쓰기, 실행 권한, 그룹 및 기타 사용자에게 읽기 및 실행 권한을 부여받는다.
'TIL' 카테고리의 다른 글
23.05.16 TIL (0) | 2023.05.17 |
---|---|
23.05.15 TIL (0) | 2023.05.17 |
23.05.11 TIL (0) | 2023.05.12 |
23.05.10 TIL (0) | 2023.05.11 |
23.05.09 TIL (0) | 2023.05.10 |