<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>cloud_park.log</title>
        <link>https://velog.io/</link>
        <description>Now in progress of untitled advance</description>
        <lastBuildDate>Sun, 08 Oct 2023 05:14:43 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. cloud_park.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/cloud_park" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Redis를 다중화하고, Sentinel 구성을 해보자]]></title>
            <link>https://velog.io/@cloud_park/Redis%EB%A5%BC-%EB%8B%A4%EC%A4%91%ED%99%94%ED%95%98%EA%B3%A0-Sentinel-%EA%B5%AC%EC%84%B1%EC%9D%84-%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@cloud_park/Redis%EB%A5%BC-%EB%8B%A4%EC%A4%91%ED%99%94%ED%95%98%EA%B3%A0-Sentinel-%EA%B5%AC%EC%84%B1%EC%9D%84-%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sun, 08 Oct 2023 05:14:43 GMT</pubDate>
            <description><![CDATA[<p>현재의 django 어플리케이션에서 Redis의 정보가 사라지면 데이터가 빠르게 없어진다.</p>
<p>따라서 Redis를 다중화하고, Fail-over 할수 있는 방법이 필요하다고 생각했다.</p>
<p>사실상 Cache에 올라가는 내용은 API에서 받거나, DB에서 받는 내용이므로 운영/개발계 모두 동일한 내용을 Fork해도 된다고 생각했다. 이후 필요할경우 세션에 대한 부분을 Redis내부에서 분류하여서 변경하지 않을예정</p>
<h1 id="master-slave-구성">master-slave 구성</h1>
<p>master-slave 구성은 간단하다. <a href="https://velog.io/@cloud_park/Django%EC%97%90-Redis%EB%A5%BC-%EC%97%B0%EA%B2%B0%ED%95%B4%EB%B3%B4%EC%9E%90">기존의 글</a> 에서 redis.conf 파일에 아래 한줄만 추가해주면 된다.</p>
<pre><code>...
slaveof [IP주소] [포트번호]
...

</code></pre><p>이후 redis-cli에서 <code>info replication</code>을 통해 아래와 같이 &#39;master:link_status&#39;를 확인해보면 된다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_park/post/6fd8fcf5-f06d-4ce2-a3e4-45ee67cfb575/image.png" alt=""></p>
<p>Master에서는 같은 명령어가 아래와 같이 보인다. slave에 대하여 slave를 할 수 있는데, 그렇게 2단계에 걸친 slave된 노드들은 여기서 보이지 않는다. 
<img src="https://velog.velcdn.com/images/cloud_park/post/a548b75c-6df0-4ad7-be1b-ccf3e4609d2f/image.png" alt=""></p>
<h1 id="redis-sentinel-구성">Redis-sentinel 구성</h1>
<p>bitnami에서 제공하는 redis-sentienl을 사용할 예정.  sentinel에서 slave를 master로 승격하는 투표를 하는데, 이 투표의 기준선이 &#39;쿼럼&#39;이라고 보면된다. 쿼럼이 2이상이여야하기 때문에 sentienl구성은 sentinel 노드가 2개 이상이여한다. 하드웨어적으로 아예 분리된 상태에서 3개여야 HA구성의 의미가 있다. 
자세한 설정은 아래를 참고하자 
<a href="https://hub.docker.com/r/bitnami/redis-sentinel/">https://hub.docker.com/r/bitnami/redis-sentinel/</a></p>
<p>-v 폴더내의 아래 파일이 있어야한다. 따라서 아래 기준으로 아래 폴더가 있어야한다. 또한 파일에 대한 권한이 있어야한다. </p>
<pre><code>chmod -R 777 /var/myredis/persistence/
/var/myredis/persistence/redis-sentinel/conf/sentinel.conf
</code></pre><pre><code> docker run -d \
  -e REDIS_MASTER_HOST=[Master의 IP주소] \
  -e REDIS_MASTER_PORT_NUMBER=6379 \
  -v /var/myredis/persistence:/bitnami \
  -p 26379:26379 \
  --network redis-net \
  --name redis_sentinel \
  --restart=always \
  bitnami/redis-sentinel:latest</code></pre><pre><code>docker exec -it redis_sentinel redis-cli -p 26379 </code></pre><p>그런데.. slave에서는 sentienl이 있다고 나오지만, master에서는 sentinel이 Master node의 1개라고 나온다. Master는 Oracle Cloud에 있고, slave 2개는 내 Local망에 있기 때문이다. NAT 설정을 해줘야한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django DRF에서 JWT로 인증하기]]></title>
            <link>https://velog.io/@cloud_park/Django-DRF%EC%97%90%EC%84%9C-JWT%EB%A1%9C-%EC%9D%B8%EC%A6%9D%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@cloud_park/Django-DRF%EC%97%90%EC%84%9C-JWT%EB%A1%9C-%EC%9D%B8%EC%A6%9D%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 30 Sep 2023 06:52:34 GMT</pubDate>
            <description><![CDATA[<h3 id="drf의-token">DRF의 Token</h3>
<ol>
<li>각 User와 1:1 매칭이고 유효기간이 없다 -&gt; Token이 탈취되면 안된다.</li>
<li>Database에 Token을 가진 유저를 매칭해야한다.</li>
</ol>
<h3 id="jwt-json-web-token">JWT (JSON Web Token)</h3>
<p>데이터베이스를 조회하지 않아도, 로직만으로 인증이 가능하다.</p>
<p>Format :<code>헤더</code>.<code>내용</code>.<code>서명</code>
서버에서 토큰 발급시에 비밀키(settings.py에 있는 비밀키 혹은 원하는 비밀키)로 서명을 하고, 발급시간을 저장. 
서명은 암호화가 아니다.</p>
<p>Claim : 담는 정보의 한 조각. &quot;Key/Value&quot;의 형식.</p>
<p>비밀키 : settings.SECRET_KEY를 활용하거나, JWT_SECRET_KEY 설정을 합니다.
갱신 : Token 유효기간 내에 갱신하거나, username/password를 통해 재인증해야함.
<strong>이미 발급된 token을 폐기하는것은 불가능함.</strong></p>
<h3 id="djangorestframework-simplejwt를-사용하자">djangorestframework-simplejwt를 사용하자.</h3>
<p>원래 있던 djangorestframework-jwt는 <a href="https://stackoverflow.com/questions/72102911/could-not-import-rest-framework-jwt-authentication-jsonwebtokenauthentication">프로젝트가 종료되어 해당 지원이 종료되었다</a>.
이를 이어받아 계속 maintaining하고있는 simplejwt를 사용하자.</p>
<pre><code class="language-python">pip install djangorestframwork-simplejwt

REST_FRAMEWORK = {
    &#39;DEFAULT_AUTHENTICATION_CLASSES&#39;: [
        &#39;rest_framework.authentication.SessionAuthentication&#39;,
        # &#39;rest_framework.authentication.TokenAuthentication&#39;, #Token인증을 사용하지 않을시 빼버리는게 낫다. 성능에 영향을 주기때문.
        &#39;rest_framework_jwt.authentication.JSONWebTokenAuthentication&#39;,
    ],

 from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
    TokenVerifyView,
)

urlpatterns += [
    path(&#39;token/&#39;, TokenObtainPairView.as_view(), name=&#39;token_obtain_pair&#39;),
    path(&#39;token/refresh/&#39;, TokenRefreshView.as_view(), name=&#39;token_refresh&#39;),
    path(&#39;token/verify/&#39;, TokenVerifyView.as_view(), name=&#39;token_verify&#39;),
]</code></pre>
<h2 id="문제해결">문제해결</h2>
<p>셋업 자체는 어렵지 않았는데, 토큰 자체는 유효한데, 자꾸 시도를 하니 오류가 났다.
이유는... Http 헤더에 따옴표를 붙인것.. 나만... 해맸던거야?..</p>
<pre><code class="language-json">{
  &quot;detail&quot;: &quot;이 토큰은 모든 타입의 토큰에 대해 유효하지 않습니다&quot;,
  &quot;code&quot;: &quot;token_not_valid&quot;,
  &quot;messages&quot;: [
    {
      &quot;token_class&quot;: &quot;AccessToken&quot;,
      &quot;token_type&quot;: &quot;access&quot;,
      &quot;message&quot;: &quot;유효하지 않거나 만료된 토큰&quot;
    }
  ]
}</code></pre>
<pre><code class="language-python">GET http://localhost:8000/[주소]
Accept: application/json
#성공
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjk2MDU2NzQ3LCJpYXQiOjE2OTYwNTQ5NDcsImp0aSI6IjM5Y2IyNTgwYjMxYTQ1MjdhOGU5YzI3YTc3MDFjNjQyIiwidXNlcl9pZCI6MX0.Ek-tHvxJdxDnvgxJdxZRHN4Y9fyY4OBvEQwmTRgmDTM

#실패
Authorization: Bearer &quot;eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjk2MDU2NzQ3LCJpYXQiOjE2OTYwNTQ5NDcsImp0aSI6IjM5Y2IyNTgwYjMxYTQ1MjdhOGU5YzI3YTc3MDFjNjQyIiwidXNlcl9pZCI6MX0.Ek-tHvxJdxDnvgxJdxZRHN4Y9fyY4OBvEQwmTRgmDTM&quot;
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django DRF에서 Token 인증 적용하기]]></title>
            <link>https://velog.io/@cloud_park/Django-DRF%EC%97%90%EC%84%9C-Token-%EC%9D%B8%EC%A6%9D-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@cloud_park/Django-DRF%EC%97%90%EC%84%9C-Token-%EC%9D%B8%EC%A6%9D-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 30 Sep 2023 04:57:20 GMT</pubDate>
            <description><![CDATA[<h1 id="drf에서-지원하는-인증">DRF에서 지원하는 인증</h1>
<ul>
<li><code>SessionAuthentication</code> : 
  rest_framework.authentication.SesstionAuthentication
  웹과 장고가 같은 호스트를 쓴다면 사용 가능. 하지만 외부 서비스/앱에서는 사용불가</li>
<li><code>BasicAuthetication</code> : 
  rest_framework.authentication.BasicAuthentication
  외부 서비스/앱에서 매번 username/password를 넘기는건 무서운 일</li>
<li><code>TokenAuthentication</code> : 
  초기에 username/password로 Token을 발급받고, 이 Token을 API요청에 담아서 인증처리</li>
</ul>
<h3 id="token을-생성받는-방법">Token을 생성받는 방법</h3>
<h4 id="1-obatainauthtoekn-뷰를-통한-획득-및-생성---url-mapping필요">1. ObatainAuthToekn 뷰를 통한 획득 및 생성 -&gt; URL Mapping필요</h4>
<pre><code>rest_framework/authtoken/views.py에 있음. 
class ObtainAuthToekn(APIView):
    def post(self, request, *args, **kwargs): 
    ....
</code></pre><pre><code class="language-python">from rest_framework.authtoken.views import obtain_auth_token

urlpatterns += [
    path(r&#39;api-token-auth/&#39;, obtain_auth_token),
]</code></pre>
<pre><code class="language-python">settings.py

INSTALLED_APPS=[
    ...
    &#39;rest_framework&#39;,    # Third Apps
    &#39;rest_framework.authtoken&#39;,
]

REST_FRAMEWORK = {

    &#39;DEFAULT_AUTHENTICATION_CLASSES&#39;: [
        &#39;rest_framework.authentication.SessionAuthentication&#39;,
        &#39;rest_framework.authentication.TokenAuthentication&#39;,
    ],
    }</code></pre>
<p>이후 Migration을 통해 기본 User모델에 Token 필드를 만들어야한다. </p>
<h4 id="2-signal을-통한-자동생성">2. Signal을 통한 자동생성</h4>
<pre><code class="language-:일종의">@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwards):
    if created:
        Token.objects.create(user=instance)</code></pre>
<h4 id="3-management-명령을-통한-생성">3. Management 명령을 통한 생성</h4>
<pre><code>python3 manage.py drf_create_token &lt;username&gt;

python3 manage.py drf_create_token -r &lt;username&gt;</code></pre><h3 id="token과-loginrequiredmixin은-같이-사용이-불가능하다">Token과 loginRequiredMixin은 같이 사용이 불가능하다!?</h3>
<p>Token은 rest_framework에서 지원하고, loginRequriedMixin은 Django에서 지원하는 것이라 서로가 호환이 안되어, Token을 제공했음에도 불구하고 로그인화면을 리턴해버림. 
역시 <a href="https://stackoverflow.com/questions/42829300/loginrequiredmixin-fails-but-user-is-authenticated">스택오버플로우에는 같은 질문</a>이 있었고, <a href="https://gist.github.com/jsmedmar/d846eee063fa23148f8a87313dd590a3">그걸 해결해준 사람</a>이 있었다.</p>
<p>간단하지만 명료하다. Token이 있으면 loginRequiredMixin을 무시하고, 해당 Token에 대한 인증을 ViewSet에서 수행한다. DRF와 Django의 구현이 다르고, Template과 RESTfulAPI 모두를 사용하게되면 계속 겪게될 문제이니 잘 참고하자.</p>
<pre><code class="language-python">views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from rest_framework.authentication import SessionAuthentication, TokenAuthentication

class TokenLoginRequiredMixin(LoginRequiredMixin):
    def dispatch(self, request, *args, **kwargs):
        &quot;&quot;&quot;If token was provided, ignore authenticated status.&quot;&quot;&quot;
        http_auth = request.META.get(&quot;HTTP_AUTHORIZATION&quot;)
        if http_auth and &quot;Token&quot; in http_auth:
            pass
        elif not request.user.is_authenticated:
            return self.handle_no_permission()
        return super(LoginRequiredMixin, self).dispatch(
            request, *args, **kwargs)

class MybookWishListViewSet(TokenLoginRequiredMixin, viewsets.ViewSet):
    login_url = f&#39;{settings.FORCE_SCRIPT_NAME}/accounts/login&#39;
    authentication_classes = [
        SessionAuthentication,
        TokenAuthentication,
        ]
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django DRF의 Throttling (최대 호출횟수 제한)]]></title>
            <link>https://velog.io/@cloud_park/Django-DRF%EC%9D%98-Throttling-%EC%B5%9C%EB%8C%80-%ED%98%B8%EC%B6%9C%ED%9A%9F%EC%88%98-%EC%A0%9C%ED%95%9C</link>
            <guid>https://velog.io/@cloud_park/Django-DRF%EC%9D%98-Throttling-%EC%B5%9C%EB%8C%80-%ED%98%B8%EC%B6%9C%ED%9A%9F%EC%88%98-%EC%A0%9C%ED%95%9C</guid>
            <pubDate>Sat, 30 Sep 2023 04:03:13 GMT</pubDate>
            <description><![CDATA[<p>OPEN API 서비스등을 한다면..? 필요할지도...
<a href="https://www.django-rest-framework.org/api-guide/throttling/">https://www.django-rest-framework.org/api-guide/throttling/</a></p>
<h1 id="용어-정리">용어 정리</h1>
<ul>
<li>Rate : 지정 기간 내에 허용할 최대 호출 횟수</li>
<li>Scope : 각 Rate에 대한 별칭 (alias)</li>
<li>Throttle : 특정 조건 하에 최대 호출횟수를 결정하는 로직이 구현된 클래스 </li>
</ul>
<h2 id="기본-제공-throttle">기본 제공 Throttle</h2>
<ul>
<li>AnonRateThrottle : 인증요청에는 제한을 두지 않고, 비읹으 요청에는 IP단위로 횟수 제한
scope : &#39;anon&#39;</li>
<li>UserRateThrottle : 인증에는 유저단위로 횟수를 제한하고, 비인증 요청에는 IP단위로 횟수제한
scope : &#39;user&#39;</li>
<li>ScopedRateThrottle : 인증 요청에는 유저단위로 횟수를 제한하고, 비인증 요청에는 IP단위로 횟수 제한. 각 APIView 내에 throttle_scope 설정을 익어 APIView별로 서로 다른 Scope 적용.</li>
</ul>
<h2 id="설정-예시">설정 예시</h2>
<pre><code class="language-python">settings.py
REST_FRAMEWORK = {
    &#39;DEFAULT_THROTTLE_CLASSES&#39; : [
        &#39;rest_framework.throttling.UserRateThrottle&#39;,
    ],
    &#39;DEFAULT_THROTTLE_RATES&#39; : {
        &#39;user&#39; : &#39;10/day&#39;,     
                # Rate는 숫자/간격으로 간격은 첫글자만 사용. 
                # d,day,ddd 모두 Day, s,m,h,d가 가능 
    }
}</code></pre>
<pre><code class="language-python">views.py
from rest_framework.throttling import AnonRateThrottle

class PostViewSet(ViewSet):
    throttle_classes = AnonRateThrottle</code></pre>
<h3 id="최대-호출-횟수-제한을-넘긴다면">최대 호출 횟수 제한을 넘긴다면?</h3>
<p><code>429 Too many Requests</code> 응답. 
예외 메시지에 API 활용이 가능한 시점을 알려준다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django DRF의 페이징]]></title>
            <link>https://velog.io/@cloud_park/Django-DRF%EC%9D%98-%ED%8E%98%EC%9D%B4%EC%A7%95</link>
            <guid>https://velog.io/@cloud_park/Django-DRF%EC%9D%98-%ED%8E%98%EC%9D%B4%EC%A7%95</guid>
            <pubDate>Sat, 30 Sep 2023 03:21:42 GMT</pubDate>
            <description><![CDATA[<h2 id="장고에서-지원하는-페이징-방식">장고에서 지원하는 페이징 방식</h2>
<ol>
<li>PageNumberPagination : page/pagesize인자를 통한 페이징 처리 </li>
<li>LimitoffsetPagination : offset/limit 인자를 통한 페이징 처리 (몇번째부터 몇번까지)</li>
</ol>
<h3 id="pagenumberpagination">PageNumberPagination</h3>
<pre><code class="language-python">settings.py
REST_FRAMEWORK = {
    &quot;PAGE_SIZE&quot; : 10,
    &quot;DEFAULT_PAGINATION_CLASS&quot;: &#39;rest_framework.pagination.PageNumberPagination&#39;,
    } #Settings.py의 디폴트 전역설정 


from rest_framework.pagination import PageNumberPagination

class MyPageNumberPagination(PageNumberPagination):
    page_size = 20 #내가 원하는 사이즈로 지정. 

class APIViewWithpage(APIview):
    pagination_class = PageNumberPagination</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django DRF의 Filtering]]></title>
            <link>https://velog.io/@cloud_park/Django-DRF%EC%9D%98-Filtering</link>
            <guid>https://velog.io/@cloud_park/Django-DRF%EC%9D%98-Filtering</guid>
            <pubDate>Sat, 30 Sep 2023 03:13:49 GMT</pubDate>
            <description><![CDATA[<p>Django View에서의 filtering </p>
<pre><code class="language-python">class PostListAPIView(generics.ListAPIView):
    queryset = Post.obejects.all()

    def get_queryset(self):
        q = self.request.query_params.get(&#39;q&#39;, &#39;&#39;)
        qs = super().get_queryset()
        if q:
            qs = qs.filter(titled__icontains=q)
        return qs</code></pre>
<pre><code class="language-python">from rest_framework.filters import SearchFilter, OrderingFilter

class PostModelViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

    filter_backends = [SearchFilter, OrderingFilter]
    search_fields = [&#39;message&#39;] //문자열필드에만 가능.
    ordering_fields = [&#39;id&#39;] //지정하지않으면 Serializer의 모든 필드가 가능.
    ordering = [&#39;id&#39;]
</code></pre>
<p> <code>/myurl/?search=검색어&amp;ordering=-pk</code>
 <code>/myurl/?search=검색어&amp;ordering=-created_at, pk</code> 등으로 사용가능.</p>
<h2 id="search_fields에서-사용-가능한-것들">Search_fields에서 사용 가능한 것들</h2>
<p> <a href="https://www.django-rest-framework.org/api-guide/filtering/">DRF 공식문서</a></p>
<h3 id="문자열-패턴">문자열 패턴</h3>
<ul>
<li>&quot;^&quot; : Starts-with : ~로 시작</li>
<li>&#39;=&#39; : Exact matched  : 정확히 일치</li>
<li>&quot;@&quot; : Full-Text Search : (단어/구문에 대한 검색. </li>
<li>&quot;$&quot; : Regex search : 정규표현식</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[라즈베리파이 4 (Rpi4)를 SSD로 깔때 유의할점]]></title>
            <link>https://velog.io/@cloud_park/%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B4-4-Rpi4%EB%A5%BC-SSD%EB%A1%9C-%EA%B9%94%EB%95%8C-%EC%9C%A0%EC%9D%98%ED%95%A0%EC%A0%90</link>
            <guid>https://velog.io/@cloud_park/%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B4-4-Rpi4%EB%A5%BC-SSD%EB%A1%9C-%EA%B9%94%EB%95%8C-%EC%9C%A0%EC%9D%98%ED%95%A0%EC%A0%90</guid>
            <pubDate>Mon, 25 Sep 2023 14:44:19 GMT</pubDate>
            <description><![CDATA[<p>새로운 라즈베리파이를 DB서버로 활용하기위해 들이면서 일어난 일.
분명 SD카드로는 부팅이 잘 되는데, SSD로는 부팅이 되다 만다.</p>
<p>SD카드일때도 부팅이 될때가 있고 안될때가 있었는데</p>
<p>이때는..</p>
<p>전원을 확인한다.</p>
<p>USB-A의 경우 2.5A 이상을 출력하는경우가 드물다.
어댑터를 사용하지 않을경우 USB-C to C를 이용해서 충분한 출력을 제공하자.</p>
<p>SSD로 Cloning할때도 속도가 느렸는데, 전원어댑터를 제대로 끼우고 작동하니 빠름...... ㅂㄷㅂㄷ...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django에 Celery를 연결해보자]]></title>
            <link>https://velog.io/@cloud_park/Django%EC%97%90-Celery%EB%A5%BC-%EC%97%B0%EA%B2%B0%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@cloud_park/Django%EC%97%90-Celery%EB%A5%BC-%EC%97%B0%EA%B2%B0%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Tue, 19 Sep 2023 11:35:19 GMT</pubDate>
            <description><![CDATA[<h2 id="celery">Celery?</h2>
<p>어떻게 읽어야할지 무언가 생소한 이 단어는, &#39;샐러리&#39;라고 보통 읽는듯 하다. </p>
<h3 id="why-celery">Why Celery?</h3>
<p>Django에서는 View의 처리가 완료되어야 Render가 시작된다. 하지만 Heavy한 작업이 필요한 경우 View에서 작업이 지연된다면, 사용자가 응답받을때까지 시간이 지연된다. 따라서 비동기적으로 작업을 처리할 수 있는 방법이 필요하다.</p>
<h3 id="whats-role-of-celery">What&#39;s role of Celery?</h3>
<p>이러한 비동기적인 작업을 처리하는 방식에서, <code>작업(Job)</code>을 만드는 <code>발행자(Publisher)</code>와 <code>작업자(Worker)</code>의 역활이 생긴다. </p>
<p>Django에서 Celery를 통해 <code>작업(Job)</code>을 발행하면, 이 작업은 <code>작업대</code>인 <code>메시지 큐</code>에 쌓인다. 이 메시지 큐로는 <code>RabbitMQ</code> 혹은 <code>Redis</code>가 사용된다.</p>
<p><code>작업자</code>는 <code>작업대(메시지큐)</code>의 메시지(작업)을 소비하고, 메시지큐에 작업이 완료되었다는 표식을 남긴다. 이러한 과정을 도와주는 역활이 샐러리이다. 그중 작업대 역활을 하는 <code>메시지 큐</code>는 <code>브로커</code>라고 불린다. </p>
<h3 id="왜-이번-프로젝트에서-celery를-사용">왜 이번 프로젝트에서 Celery를 사용?</h3>
<p>이번 프로젝트는 API 콜을 하는데, API의 응답속도가 빠르지 않았다. 따라서 정보를 DB에 저장해주어야 할 필요가 있었다. 비동기적 작업을 하지 않을경우 이러한 작업이 사용자에게 응답이 만들어 지기전 이루어져, 사용자의 응답은 느려지게된다.</p>
<p>따라서 아래의 프로세스를 통해 사용자경험을 극대화하려한다. Celery를 사용하지 않으면 View가 종료되기 전 5번이 실행되어야하며, 이러한 과정에서 DB의 병목 및 응답시간 증가가 해소될 것으로 보인다.</p>
<hr>
<p>1.사용자 요청
2.사용자 요청에 대해 API Call 이전 보유여부 확인
    2-1) 사용자 요청에 맞는 정보가 Cache에 있는가?
    2-2) 사용자 요청에 맞는 정보가 DB에 있는가?
3.Caching/DB에 없으면 API Call을 통해 정보를 갱신한다.
4.받아온 정보를 재가공하여 사용자에게 전달한다. (View 종료)
*<em>5. 데이터가공, DB 저장 및 Cache를 위한 Celery 테스크를 발행한다. *</em></p>
<hr>
<blockquote>
<p>도움을 받은 글들
<a href="https://devlog.jwgo.kr/2019/07/02/using-celery-with-django-1/">장고(Django)에서 셀러리(Celery) 사용하기 1편</a>
<a href="https://docs.celeryq.dev/en/latest/getting-started/first-steps-with-celery.html#first-steps">Celery 공식 문서</a></p>
</blockquote>
<h2 id="celery-설치하기">Celery 설치하기</h2>
<p>기존에는 Django와 결합된 별도의 패키지를 설치해야했지만, 이제는 Celery Stand-only로 설치가능하다.</p>
<pre><code class="language-bash">pip install celery
pip install redis
pip install django-celery-beat
pip install django-celery-results 

django-celery-beat는 특정한 기간마다 작업이 실행하게하는 인터페이스,
django-celery-results는 작업결과를 알려줍니다. </code></pre>
<p><a href="https://docs.celeryq.dev/en/latest/django/first-steps-with-django.html#using-celery-with-django">Celery 공식문서 - Django와 사용하기</a></p>
<h2 id="settingspy-설정">Settings.py 설정</h2>
<p>settings.py에서 아래 설정을 추가합니다.</p>
<pre><code class="language-python">INSTALLED_APPS = [
    ....
    &#39;django_celery_beat&#39;,
    &#39;django_celery_results&#39;,
    ...
]


# Celery
CELERY_ALWAYS_EAGER = True
CELERY_BROKER_URL = &#39;redis://localhost:6379&#39;
CELERY_RESULT_BACKEND = &#39;redis://localhost:6379&#39;
CELERY_ACCEPT_CONTENT = [&#39;application/json&#39;]
CELERY_TAST_SERIALIZER = &#39;json&#39;
CELERY_RESULT_SERIALIZER = &#39;json&#39;
CELERY_TIMEZONE = &#39;Asia/Seoul&#39;</code></pre>
<h2 id="celerypy-생성">celery.py 생성</h2>
<p>Settings.py가 있는 프로젝트 (App이 아님!) 폴더에 생성해야합니다.</p>
<pre><code>- proj/
  - manage.py
  - proj/ &lt;이 폴더의 하위폴더!
    - __init__.py
    - settings.py
    - urls.py</code></pre><pre><code class="language-python">import os

from celery import Celery

# Set the default Django settings module for the &#39;celery&#39; program.
os.environ.setdefault(&#39;DJANGO_SETTINGS_MODULE&#39;, &#39;proj.settings&#39;)

app = Celery(&#39;proj&#39;)

# Using a string here means the worker doesn&#39;t have to serialize
# the configuration object to child processes.
# - namespace=&#39;CELERY&#39; means all celery-related configuration keys
#   should have a `CELERY_` prefix.
app.config_from_object(&#39;django.conf:settings&#39;, namespace=&#39;CELERY&#39;)

# Load task modules from all registered Django apps.
app.autodiscover_tasks()


@app.task(bind=True, ignore_result=True)
def debug_task(self):
    print(f&#39;Request: {self.request!r}&#39;)
</code></pre>
<pre><code class="language-python">proj/proj/__init__.py:
from .celery import app as celery_app

__all__ = (&#39;celery_app&#39;,)</code></pre>
<h3 id="celery-구동-확인하기">celery 구동 확인하기</h3>
<pre><code>celery -A proj worker -l INFO</code></pre><p>manage.py가 있는 폴더에서 실행해야합니다. 그렇지 않는다면 Module &#39;proj&#39; has no attribute &#39;celery&#39; 같은 에러가 나옵니다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_park/post/20f4b3e0-6a0c-4da5-9427-efd5bf8acb88/image.png" alt="">
<img src="https://velog.velcdn.com/images/cloud_park/post/7a7220d9-0678-4315-8e42-abf088135d5e/image.png" alt=""></p>
<p>잘 되었다면, <code>python3 maange.py shell</code>을 통해 날린 add 명령이 잘 동작해야합니다.</p>
<h3 id="redis-확인하기">REDIS 확인하기</h3>
<p>REDIS CLI를 통해 어떻게 명령이 들어갔는지 확인해봅시다. <a href="https://velog.io/@cloud_park/Django%EC%97%90-Redis%EB%A5%BC-%EC%97%B0%EA%B2%B0%ED%95%B4%EB%B3%B4%EC%9E%90">REDIS 설정하기</a>를 통해 설정한 Docker Redis 기반입니다.</p>
<pre><code class="language-docker">docker run -it --network redis-net --rm redis redis-cli -h django_redis</code></pre>
<pre><code>scan 0 
KEYS *을 사용하는 방법도 있지만, Redis는 Single-Thread 기반이여서, 재귀적으로 호출하는 scan0 를 사용하는것을 권장합니다. 

GET [내가 찾은 celery task 키]  </code></pre><p><img src="https://velog.velcdn.com/images/cloud_park/post/d546d210-3a1c-4378-a3a0-7db3934207b4/image.png" alt=""></p>
<p>값이 잘 들어간 것을 확인할 수 있습니다.</p>
<h3 id="ignore_result-사용하기">ignore_result 사용하기</h3>
<pre><code>res = add.apply_async((1,2), ignore_result=True)
res.get()
res = add.apply_async((9,10))
res.get()
</code></pre><p>실제 실행결과는 아래와 같습니다. 
<img src="https://velog.velcdn.com/images/cloud_park/post/d701c028-046b-428f-9e7a-53f72af44dac/image.png" alt=""></p>
<p>Celery는 잘 실행을 했지만, 해당 결과값에 대해 저장을 하지 않으므로 값은 Redis에 남지 않습니다.
<img src="https://velog.velcdn.com/images/cloud_park/post/2489eb43-9740-4b81-b78f-53126f5e5ea2/image.png" alt=""></p>
<p>실제로 마지막에 실행한 0b89로 끝나는 Task만 Redis에 남아있습니다. 
<img src="https://velog.velcdn.com/images/cloud_park/post/063c7b27-2ece-441c-8106-be78b4e77b8e/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django에 Redis를 연결해보자]]></title>
            <link>https://velog.io/@cloud_park/Django%EC%97%90-Redis%EB%A5%BC-%EC%97%B0%EA%B2%B0%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@cloud_park/Django%EC%97%90-Redis%EB%A5%BC-%EC%97%B0%EA%B2%B0%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Mon, 18 Sep 2023 03:02:07 GMT</pubDate>
            <description><![CDATA[<p>Django에 Docker를 이용하여 Redis 서버를 올려보자.
Docker는 설치되어있다고 가정합니다.</p>
<blockquote>
<p>아래의 글을 참고하였습니다.
<a href="https://dingrr.com/blog/post/redis-%EB%8F%84%EC%BB%A4docker%EB%A1%9C-redis-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0">[Redis] 도커(Docker)로 Redis 설치하기</a>
<a href="https://dingrr.com/blog/post/django-seo-%EB%8D%94-%EB%B9%A0%EB%A5%B4%EA%B2%8C-cache%EC%99%80-%EC%95%95%EC%B6%95">[Django SEO] 더 빠르게! - Cache와 압축 </a></p>
</blockquote>
<h3 id="redis-내부의-데이터를-보기위하여-redis-net-네트워크-구성">redis 내부의 데이터를 보기위하여, redis-net 네트워크 구성</h3>
<pre><code>docker network create redis-net</code></pre><h3 id="redis-컨테이너-생성">redis 컨테이너 생성</h3>
<pre><code class="language-bash">docker run -d --name django_redis \
            --network redis-net \
            -p 6379:6379 \
            redis redis-server</code></pre>
<pre><code class="language-bash">docker run -d --name django_redis \
            -v /var/myredis:/usr/local/etc/redis \
            -v /var/myredis/data:/data \
            --network redis-net \
            -p 6379:6379 \
            redis redis-server \
            /usr/local/etc/redis/redis.conf #컨테이너 내부에서의 conf 주소.</code></pre>
<p>/usr/local/etc/redis/redis.conf의 conf 파일로 Redis 설정을 당겨올 수 있다. 
<a href="https://redis.io/docs/management/config/">redis 공식 conf 문서</a></p>
<h3 id="cache로-사용하기위한-redisconf">cache로 사용하기위한 redis.conf</h3>
<pre><code>appendonly yes
appendfilename &quot;redis_appendonly.aof&quot;
appendfsync everysec
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
stop-writes-on-bgsave-error no
maxmemory 500mb
maxmemory-policy allkeys-lru</code></pre><p>AOF 방식으로 저장한다. 
[기본적으로 32bit일경우 maxmemory는 3GB로 설정되며, 64bit는 무한으로 설정된다.] (<a href="https://redis.io/docs/reference/eviction/">https://redis.io/docs/reference/eviction/</a>)</p>
<h3 id="redis-cli로-접속하기">redis CLi로 접속하기</h3>
<pre><code class="language-bash">docker run -it --network redis-net --rm redis redis-cli -h django_redis</code></pre>
<h3 id="django-redis-설치">django-redis 설치</h3>
<pre><code class="language-bash">pip install django-redis</code></pre>
<h3 id="settingspy-설정">Settings.py 설정</h3>
<p>BACKEND&#39;: &#39;redis_cache.RedisCache&#39;를 설정할 경우 <code>django.core.cache.backends.base.InvalidCacheBackendError: Could not find backend &#39;redis_cache.RedisCache&#39;: No module named &#39;redis_cache&#39;</code> 에러가 뜨게 됩니다. django_redis 패키지 내의 모듈이여야합니다. </p>
<pre><code class="language-python">settings.py

CACHES = {
    &#39;default&#39;: {
        &#39;BACKEND&#39;: &#39;redis_cache.RedisCache&#39;,
        &#39;LOCATION&#39;: &#39;localhost:6379&#39;,
    },
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django에 Docker를 이용한 PostgreSQL을 연동해보자]]></title>
            <link>https://velog.io/@cloud_park/Django%EC%97%90-Docker%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-PostgreSQL%EC%9D%84-%EC%97%B0%EB%8F%99%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@cloud_park/Django%EC%97%90-Docker%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-PostgreSQL%EC%9D%84-%EC%97%B0%EB%8F%99%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Mon, 18 Sep 2023 02:06:48 GMT</pubDate>
            <description><![CDATA[<h3 id="postgresql-비밀번호-환경변수-셋팅">postgreSQL 비밀번호 환경변수 셋팅</h3>
<pre><code class="language-bash">export POSTGRES_PASSWORD=&#39;my_password&#39;
echo $POSTGRES_PASSWORD</code></pre>
<h3 id="varlibpostgresqldata-local-머신의-postgresql-저장소-생성">/var/lib/postgresql/data (Local 머신의 Postgresql 저장소) 생성</h3>
<h3 id="docker-배포">Docker 배포</h3>
<pre><code class="language-bash">#! /bin/bash

docker run -d -p 5432:5432 \
    --name postgres \
    --restart=always \
    -v /var/lib/postgresql/data:/var/lib/postgresql/data \
    -e POSTGRES_PASSWORD=$POSTGRES_PASSWORD \
     postgres
</code></pre>
<h3 id="postgres-접속">Postgres 접속</h3>
<pre><code class="language-bash">docker exec --it postgres bash</code></pre>
<h3 id="psql-접속">psql 접속</h3>
<pre><code class="language-bash">psql -U postgres</code></pre>
<h3 id="database-생성">DATABASE 생성</h3>
<pre><code>CREATE DATABASE [my_service];
</code></pre><p><code>\l</code> 을 통해 my_service 생성을 확인할 수 있다.</p>
<h3 id="user-생성-및-권한부여">USER 생성 및 권한부여</h3>
<pre><code class="language-SQL">CREATE USER [my_user_name] WITH PASSWORD &#39;password&#39;; 
-- 해당 &#39;password&#39;부분은 따옴표로 감싸져있어야한다. 
ALTER ROLE [my_user_name] set client_encoding to &#39;utf-8&#39;;
ALTER ROLE [my_user_name] set timezone to &#39;Asia/Seoul&#39;;
ALTER ROLE [my_user_name] SET default_transaction_isolation TO &#39;read committed&#39;
grant all privileges on database [my_service] to [my_user_name];

--Postgresql 15부터 public schema에 대한 권한도 줘야하며, DB의 owner가 되어야한다. 
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO [my_user_name];
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO [my_user_name];</code></pre>
<h3 id="setttingspy-셋팅">setttings.py 셋팅</h3>
<pre><code class="language-python">settings.py
import os
DATABASES = {
    &#39;default&#39;: {
        &#39;ENGINE&#39;: &#39;django.db.backends.postgresql&#39;,
        &#39;NAME&#39;: [my_service],
        &#39;USER&#39;: [my_user_name],
        &#39;PASSWORD&#39;: os.environ.get(&#39;POSTGRES_PASSWORD&#39;),
        &#39;HOST&#39; : `localhost`,
        &#39;PORT&#39; : &#39;5432&#39;,
    }
}
</code></pre>
<h3 id="postgresql-드라이버-설치">postgresql 드라이버 설치</h3>
<pre><code>pip install psycopg2</code></pre><h3 id="permission-denied-for-schema-public-해결">permission denied for schema public 해결</h3>
<pre><code>python3 manage.py migrate

django.db.migrations.exceptions.MigrationSchemaMissing: Unable to create the django_migrations table (permission denied for schema public
LINE 1: CREATE TABLE &quot;django_migrations&quot; (&quot;id&quot; bigserial NOT NULL PR...</code></pre><p><a href="https://stackoverflow.com/questions/74110708/postgres-15-permission-denied-for-schema-public">https://stackoverflow.com/questions/74110708/postgres-15-permission-denied-for-schema-public</a></p>
<p>이유는 DB의 모든 권한은 주었다고 하더라도, DB의 &#39;Owner&#39;가 되어야 CREATE를 할 수 있게 변경되었다고 한다. 따라서 DB의 주인을 바꿔준다. </p>
<pre><code>ALTER DATABASE [my_service] OWNER TO [my_user];</code></pre><p>잘 된다. 오예. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Serializer를 통한 유효성 검사 및 저장]]></title>
            <link>https://velog.io/@cloud_park/Serializer%EB%A5%BC-%ED%86%B5%ED%95%9C-%EC%9C%A0%ED%9A%A8%EC%84%B1-%EA%B2%80%EC%82%AC-%EB%B0%8F-%EC%A0%80%EC%9E%A5</link>
            <guid>https://velog.io/@cloud_park/Serializer%EB%A5%BC-%ED%86%B5%ED%95%9C-%EC%9C%A0%ED%9A%A8%EC%84%B1-%EA%B2%80%EC%82%AC-%EB%B0%8F-%EC%A0%80%EC%9E%A5</guid>
            <pubDate>Thu, 14 Sep 2023 03:25:24 GMT</pubDate>
            <description><![CDATA[<p>data=인자가 주어질때
.is_valid()가 호출되고
.initioal_data 필드에 접근할 수 있고, .validated_data를 통해 유효성 검증에 통과한 값들이 Save()시에 사용
.errors -&gt; 유효성 검증 후에 오류내역,
.data -&gt; 유효성 검증 후에 갱신된 인스턴스에 대한 필드값</p>
<h2 id="serializersave">serializer.save</h2>
<p>1.DB에 저장한 관련 Instance를 리턴,
.valiated과 kwargs 사전에 합친 데이터를 DB에 저장하려고함.</p>
<p>form과는 다르게 .save할때 바로 업데이트 할때 kwarg를 넘기면 됨
.instance를 줬을때는 update, 아니면 create</p>
<h1 id="drf의-valitedor">DRF의 valitedor</h1>
<p>UniqueValidator
    모델에 unique.True를 지정하면 모델 index로 자동으로 지정되며, Validator도 자동으로 작동한다.</p>
<p>DRFㅇ의 validator는 다른 Validator를 사용한다. (Form의 Validator와 다르다!)
validate_필드명으로 유효성 검사 수행가능.
class 기반 ViewSet등에서 perform_create 등으로 재정의할 수 있다.</p>
<pre><code class="language-python">
class PostViewSet(ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

    def perform_create(self, serializer):
        author = self.request.user
        ip = self.request.META[&#39;REMOTE_ADDR&#39;]
        serializer.save(author=author, ip=ip) 

        #form에서는 .save(commit=False)를 통해 임시저장을 한후, 
        #추가필드에 대해 지정한 후 commit했으나, 
        #serializer는 kwargs로 전달하여 바로 commit이 가능하다. 
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[console.log의 그룹화]]></title>
            <link>https://velog.io/@cloud_park/console.log%EC%9D%98-%EA%B7%B8%EB%A3%B9%ED%99%94</link>
            <guid>https://velog.io/@cloud_park/console.log%EC%9D%98-%EA%B7%B8%EB%A3%B9%ED%99%94</guid>
            <pubDate>Thu, 14 Sep 2023 02:24:43 GMT</pubDate>
            <description><![CDATA[<p>console.log는 정말 자주쓰인다. 그래서 나는 console에는 log만 있는줄알았다. JAVA에서 println만 있는 줄 아는것 처럼.</p>
<pre><code class="language-javascript">console.group(&quot;안녕하세요 테스트&quot;);
console.log(&quot;hello&quot;);
    console.group(&quot;하위그룹으로 나눌수 있습니다&quot;);
        console.log(&quot;하위그룹의 Log&quot;);
    console.groupEnd();
console.error(&quot;error&quot;);
console.warn(&quot;Warning&quot;);
console.info(&quot;info&quot;);
console.groupEnd(&quot;안녕하세요 테스트끝&quot;); #GroupEnd에 적힌 내용은 반영되않습니다.</code></pre>
<p>IE11 디버거에서도 잘 동작한다.</p>
<p><img src="https://velog.velcdn.com/images/cloud_park/post/f90e3a25-de87-49de-982a-f9516cf9b1e5/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django의 FORCE_SCRIPT_NAME을 설정하자]]></title>
            <link>https://velog.io/@cloud_park/Django%EC%9D%98-FORCESCRIPTNAME%EC%9D%84-%EC%84%A4%EC%A0%95%ED%95%98%EC%9E%90</link>
            <guid>https://velog.io/@cloud_park/Django%EC%9D%98-FORCESCRIPTNAME%EC%9D%84-%EC%84%A4%EC%A0%95%ED%95%98%EC%9E%90</guid>
            <pubDate>Thu, 14 Sep 2023 02:17:02 GMT</pubDate>
            <description><![CDATA[<p>1편에서 nginx를 설정해서, django를 설정한다면 바로 redirect가 될거같지만... 아니다!</p>
<p>django에서는 url에 자동으로 접미사를 붙여주는 <code>FORCE_SCRIPT_NAME</code>라는 설정이 있다.</p>
<pre><code class="language-python">#settings.py
FORCE_SCRIPT_NAME = &#39;/app&#39;</code></pre>
<p>이러면...될...거같지만...
앱은 접속이 되지만 css, js가 안뜰꺼다. (특히 DRF같이 third Party App들의 Static들이 404가 뜰것이다. FORCE_SCRIPT_NAME의 구현은 보지 못했지만, urls.py의 middleware처럼 작용하는것으로 보인다. 따라서 따라서 root Url의 urlpatterns에 아래를 추가해준다.</p>
<pre><code class="language-pyhotn">#urls.py
urlpatterns += static(settings.MEDIA_URL,
                      document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL,
                      document_root=settings.STATIC_ROOT)

</code></pre>
<pre><code class="language-bash">python manage.py collectstatic</code></pre>
<p>이후에는 정적파일을 모아준다.
만세!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nginx의 리버스 프록시를 활용하자.]]></title>
            <link>https://velog.io/@cloud_park/Nginx%EC%9D%98-%EB%A6%AC%EB%B2%84%EC%8A%A4-%ED%94%84%EB%A1%9D%EC%8B%9C%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%9E%90</link>
            <guid>https://velog.io/@cloud_park/Nginx%EC%9D%98-%EB%A6%AC%EB%B2%84%EC%8A%A4-%ED%94%84%EB%A1%9D%EC%8B%9C%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%9E%90</guid>
            <pubDate>Thu, 14 Sep 2023 02:09:48 GMT</pubDate>
            <description><![CDATA[<p>내 개발서버인 라즈베리파이에는 2가지 서비스가 등록되어있다. 하나는 code-server라는 IDE, 하나는 내가 만들 앱이 배포되는 공간. 항상 앱을 만들고 포트를 열어주는게 번거로워서, nginx를 통해 설정하려는데 쉽지 않았다. </p>
<p>nginx는 현재 Https 포워딩을 위한 ssl 인증서가 탑재되어있고, 80번 포트로 응답이 오면 https로 승격한다.</p>
<p>아래의 예시는 Django에서 <code>/app</code>루트라는 하위 디렉토리를 8000번 포트에 리다이렉트하는 코드이다.</p>
<pre><code class="language-nginx">upstream code-server {
        server 192.168.0.9:8888;
}

map $remote_addr $internal_request {
        default 0;
        192.168.0.9/24 1;
}

server {
        client_max_body_size 10M; #엡서버 업로드 용량 확정
        location / {
                proxy_pass http://192.168.0.9:8888;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection upgrade;
                proxy_set_header Host $host;
                proxy_set_header Accept-Encoding gzip;

        }

        location /app/ {

                rewrite ^/app(/.*)$ $1 break;

                proxy_pass http://192.168.0.9:8000;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection upgrade;
                proxy_set_header Host $host;
                proxy_set_header Accept-Encoding gzip;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_redirect default;


        }

            listen [::]:443 ssl ipv6only=on; # managed by Certbot
            listen 443 ssl; # managed by Certbot
            ssl_certificate /etc/letsencrypt/live/[my_site]/fullchain.pem; # managed by Certbot
            ssl_certificate_key /etc/letsencrypt/live/[my_site]/privkey.pem; # managed by Certbot
            include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
            ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}



server {
    if ($host = [my_site]) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


        listen 80 ;
        listen [::]:80 ;
    server_name [my_site];
    return 404; # managed by Certbot


}</code></pre>
<p>rewrite를 통해 /app/뒤의 파라미터를 추출하고, redirect하면서 해당 파라미터를 8000번에 서비스하고 있는 앱에 넘겨준다. </p>
]]></description>
        </item>
    </channel>
</rss>