<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>detailed_root.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 25 Feb 2024 14:06:16 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. detailed_root.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/detailed_root" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Python] else의 공통 의미론에 대한 생각]]></title>
            <link>https://velog.io/@detailed_root/Python-else%EC%9D%98-%EA%B3%B5%ED%86%B5-%EC%9D%98%EB%AF%B8%EB%A1%A0%EC%97%90-%EB%8C%80%ED%95%9C-%EC%83%9D%EA%B0%81</link>
            <guid>https://velog.io/@detailed_root/Python-else%EC%9D%98-%EA%B3%B5%ED%86%B5-%EC%9D%98%EB%AF%B8%EB%A1%A0%EC%97%90-%EB%8C%80%ED%95%9C-%EC%83%9D%EA%B0%81</guid>
            <pubDate>Sun, 25 Feb 2024 14:06:16 GMT</pubDate>
            <description><![CDATA[<p>파이썬 구문에는 else 가 존재한다.</p>
<p>조건문(if), 반복문(for/while), 예외처리문(try/except) 등에서 else를 같이 사용할 수 있다.</p>
<p>python에 대한 많은 도서에서 else를 사용할 수 있는 구문에 대한 설명들을 보았을 때, else의 의미론적 공통점이 없다고 느꼈다.</p>
<p>예를 들어 다음과 같다.</p>
<ul>
<li>if 문에서 else는 해당하는 조건이 없을 때 실행됩니다.</li>
<li>for/while 문에서 else는 break가 실행되지 않았을때 실행됩니다.</li>
<li>try/except 문에서 else는 except가 실행되지 않았을 때 실행됩니다.</li>
</ul>
<p>등이다.</p>
<p>마치 else는 각 구문에 따라 달라지며 공통된 의미론이 존재하지 않으며 각 구문에 따라 else의 용법을 달리 생각해야 한다고 느껴진다.</p>
<p>그러나 그렇지 않다고 생각한다. 만일 파이썬이 잘 만들어진 언어라면, else에 대해서도 세가지 모두의 구문에서 공통된 의미론이 존재해야 하며, 그렇지 않을 경우 else가 아닌 다른 구문이 존재했어야 한다고 생각한다.</p>
<p>파이썬은 잘 만들어진 언어라고 생각한다. 그리고 그랬으면 좋겠다.
그렇기에 내 나름대로 else의 공통된 의미론에 대해 기록해 보고자 한다.</p>
<p>else의 의미론은 &#39;<strong>적용하는 구문에서 발생하는 핵심 이벤트의 미발생</strong>&#39;이라 생각한다.</p>
<p>조건문을 살펴보자. 
조건문 사용에 대한 핵심 이벤트는 <strong>참(True)인 조건</strong> 이다.
반복문을 살펴보자.
반복문의 핵심 이벤트는 <strong>반복의 중단점</strong>이다.
예외처리문을 살펴보자.
예외처리문의 핵심 이벤트는 <strong>예외의 발생</strong>이다.</p>
<p>이러한 관점에서 살펴보면 각 구문에서 사용하는 else의 공통 된 의미를 부여할 수 있다.</p>
<p>조건문의 의미관점에서 핵심 이벤트는 참인 조건을 찾기위한 것이기에, else 문이 실행된다는 것은 조건문의 핵심 이벤트가 발생하지 않았음을 의미한다.</p>
<p>반복문의 의미관점에서 핵심 이벤트는 반복의 중단점을 찾기 위한 것이므로 else문이 실행된다는 것은 반복문 구문내에 <code>break</code>라는 중단 이벤트가 발생하지 않았음을 의미한다.</p>
<p>예외처리문의 의미관점에서 핵심 이벤트는 예외를 잡아내는 것이기에 else문이 실행된다는 것은 해당 구문에서 예외가 발생하지 않았음을 의미한다.</p>
<p>이러한 관점에서 else문을 바라볼 경우 한 문맥으로 else를 이해할 수 있는 방법 중 하나가 될것이라 생각한다.</p>
<p>과연 각 케이스마다 else에 서로다른 의미를 부여하여 케이스스터디로 다루어야 하는가? </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Python] input() 의 동작]]></title>
            <link>https://velog.io/@detailed_root/Python-input-%EC%9D%98-%EB%8F%99%EC%9E%91</link>
            <guid>https://velog.io/@detailed_root/Python-input-%EC%9D%98-%EB%8F%99%EC%9E%91</guid>
            <pubDate>Sat, 17 Feb 2024 03:24:49 GMT</pubDate>
            <description><![CDATA[<p>파이썬에서 <code>input()</code> 메서드는 모두가 익숙할 것입니다.</p>
<p>그저 터미널에서 사용자의 입력이 필요할 때 사용하죠. 단순히 <strong>&#39;키보드 입력 받을 때 쓰는 함수&#39;</strong>라고만 이해해선 안된다고 생각합니다. 
어떻게 동작하는지 이해해야 <code>input()</code> 을 사용하는 프로그램을 다른 방식으로도 다룰 수 있습니다.</p>
<p>아마 처음 언어를 배우고 이것을 활용하기 위해 간단한 CLI 기반 게임을 많이들 만들 겁니다. 오목게임이라든가, 숫자 야구게임 등등...</p>
<p>이런 프로그램을 만들기는 쉽습니다. 사용자의 입력을 받고, 로직을 검사하고, 출력을 만들어내죠.
사용자의 입력은 <code>input()</code> 메서드를 통해 만들겁니다.</p>
<p>하지만 문제에 봉착하는 순간은, 이렇게 만든 프로그램에 대한 <strong>테스트 코드</strong>를 작성할 때 나타날겁니다.</p>
<blockquote>
<p><strong><em>프로그램 실행 시, 어떻게 터미널에 입력할 내용을 자동으로 주입할까??</em></strong></p>
</blockquote>
<p><code>input()</code> 메서드를 통해 사용자 입력을 만드는건 아주 쉽습니다. 생각할 필요도 없죠. 하지만 테스트를 해야 한다면, <em>사람이 직접 터미널에 입력을 하는 것이 아니라, 시스템에서 자동으로 주어진 입력을 하도록</em> 만들어야 합니다.</p>
<p><strong>컴퓨팅 시스템</strong>을 왜 이해하는 것이 중요한 대목입니다. 사용자의 입력을 받는 함수가 어떻게 동작을 하는지, 어떤 컴퓨팅 자원과 시스템을 활용하는지 알아야 해당 문제에 접근하고 해결할 수 있을 것입니다.</p>
<hr>
<p>컴퓨터 관점에서 사용자 입력은 어떤 동작으로 구성될까? 제 가정은 다음과 같았습니다.</p>
<ul>
<li><code>input()</code> 메서드는 사용자의 키보드 입력(표준입력)을 기다리는 프로세스를 만든다.</li>
<li>사용자가 키보드를 통해 입력을 만들어 내면, 입력한 내용을 임시적인 버퍼에 담는다.</li>
<li>입력 프로세스는 입력 버퍼를 계속 감시하며, 버퍼에 엔터(\n)가 입력되는 순간 \n 전까지 입력된 내용들을 소비한다.</li>
</ul>
<hr>
<p>이 가정을 기반으로 <code>input()</code> 메서드에 사용자가 직접 입력하는 것이 아닌, 시스템상으로 자동으로 주입할 수 있을지 가정을 해봅시다.</p>
<ul>
<li><code>input()</code> 프로세스가 표준 입력을 감시하며, 감시는 표준 입력 버퍼를 통해 이루어 진다면,</li>
<li>표준 입력 버퍼에 특정 텍스트를 강제로 주입.</li>
<li>알아서 <code>input()</code> 메서드는 해당 버퍼를 읽고 이것은 사용자가 입력한 텍스트다 라고 인식할 것이다.</li>
</ul>
<hr>
<p>한 번 이 가정을 토대로 코드를 만들어 봅시다.</p>
<p>먼저, 사용자의 입력을 받는 함수입니다.</p>
<pre><code>def create_input():
    text = input(&quot;여기에 입력해 주세요 : &quot;)
    return text


input_text = create_input()
print(input_text)</code></pre><p>실행 시, 
<code>여기에 입력해 주세요 :</code> 가 나타나며, hello를 입력 시, hello를 출력합니다.</p>
<p>이 함수를 자동으로 테스트 하기 위해서는 사용자가 직접 입력하는 것이 아닌, 코드상으로 자동으로 주입될 수 있어야 합니다.
제 생각엔 아마 표준입력 버퍼에 텍스트를 강제로 밀어넣은 상태라면, 알아서 읽지 않을까 합니다.</p>
<p><code>sys.stdin</code> 모듈이 적합하겠군요. 파이썬의 표준입력에 대한 모듈입니다.
이걸 한번 이용해 봅시다.</p>
<pre><code>import sys


def create_input():
    text = input(&quot;여기에 입력해 주세요 : &quot;)
    return text


sys.stdin = &quot;hello&quot;


input_text = create_input()
print(input_text)</code></pre><p>실행결과, <code>AttributeError: &#39;str&#39; object has no attribute &#39;readline&#39;</code> 이 발생합니다.</p>
<p>이럴수가, 실패했네요.</p>
<p><code>sys.stdin</code>의 타입은 <code>TextIO</code> 입니다. 아마 버퍼를 읽는 과정에서 내부적으로 <code>readline</code>이 구현된 객체를 통해 입력을 처리하는가 봅니다.</p>
<p>그럼 텍스트가 아닌, <code>TextIO</code>와 호환되는 <code>io.StringIO</code> 객체에 텍스트를 넣은 상태로 <code>sys.stdin</code>에 주입해 봅시다.</p>
<pre><code>import sys
from io import StringIO


def create_input():
    text = input(&quot;여기에 입력해 주세요 : &quot;)
    return text


sys.stdin = StringIO(&quot;hello&quot;)


input_text = create_input()
print(input_text)</code></pre><p>실행 시, 출력은 다음과 같습니다.</p>
<blockquote>
<p>여기에 입력해 주세요 : hello</p>
</blockquote>
<p><code>create_input()</code> 메서드 호출하기 앞서서 <code>stdin</code>이 있어야 합니다. <code>stdin</code>이 뒤에 올 경우, <code>input</code> 함수는 그저 사용자의 입력을 받기 위해 무한히 대기하며 다음줄로 넘어가지 않기 때문입니다.  </p>
<p><code>input()</code> 을 통해 출력한 메시지인 <code>여기에 입력해 주세요 :</code> 와 <code>String.IO</code>를 통해 우리가 주입한 <code>hello</code> 라는 텍스트가 결합되어 출력된 것입니다.
예외가 발생하지 않고 잘 처리되었습니다. 
사용자가 입력할 필요도 없이 알아서 주입되었습니다.</p>
<hr>
<p>그렇다면 다중 입력이 필요한 경우는 어떨까요?
이제 <code>create_input()</code> 함수는 내부에 사용자 입력을 3번 받아야 하는 함수로 만들어 봅시다.</p>
<p>그리고 함수를 호출할때 자동으로 입력을 주입하기 위해서, 제 생각엔 <code>String.IO</code> 객체에 입력 텍스트를 <code>\n</code>으로 구분하여 &quot;hello\nworld\n!&quot; 로 주입하면,
<code>sys.stdin</code> 버퍼에서 해당 텍스트를 <code>\n</code> 을 기준으로 소비하며, 소비한 내용은 버퍼에서 없애주지 않을까 합니다. 
첫번째 소비는 <code>hello</code> 일 것이며, 두번째 소비는 <code>world</code>, 세번째 소비는 <code>!</code> 하게 되지 않을까 합니다.</p>
<pre><code>from io import StringIO


def create_input():
    text1 = input(&quot;여기에 입력해 주세요1 : &quot;)
    text2 = input(&quot;여기에 입력해 주세요2 : &quot;)
    text3 = input(&quot;여기에 입력해 주세요3 : &quot;)
    return text1 + text2 + text3


sys.stdin = StringIO(&quot;hello\nworld\n!&quot;)


input_text = create_input()
print(input_text)</code></pre><p>실행 시, 출력은 다음과 같습니다.</p>
<blockquote>
<p>여기에 입력해 주세요1 : 여기에 입력해 주세요2 : 여기에 입력해 주세요3 : <strong>helloworld!</strong></p>
</blockquote>
<p>제가 직접 입력하지 않아도 자동으로 입력되었으며, 예상대로 잘 동작했습니다.</p>
<p><code>input</code> 입력 문구들이 나란히 출력되는 이유는 아마 <code>readline()</code>함수가 텍스트를 읽음에 있어서 <code>\n</code>까지 읽고 <code>\n</code>은 없애버렸기 때문이지 않을까 싶습니다.</p>
<hr>
<p>이제 터미널 기반 프로그램을 작성한다면, 이를 이용해 테스팅 모듈을 작성할 수 있을 것입니다. 
사용자 입력을 받아야 한다면, 시스템상에서 자동으로 입력될 수 있도록 만들 수 있을 것입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Django] Message Framework]]></title>
            <link>https://velog.io/@detailed_root/Django-Message-Framework</link>
            <guid>https://velog.io/@detailed_root/Django-Message-Framework</guid>
            <pubDate>Fri, 16 Feb 2024 06:53:26 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/detailed_root/post/0794a93c-0c4a-4ec3-b834-796da4b7c00a/image.png" alt=""></p>
<h1 id="django-message-framework">Django Message Framework</h1>
<p>Django 메시지 프레임워크의 자세한 사항은 Django 공식 문서를 참고하는게 가장 좋습니다.
<a href="https://docs.djangoproject.com/en/5.0/ref/contrib/messages/">https://docs.djangoproject.com/en/5.0/ref/contrib/messages/</a></p>
<p>해당 포스팅은 메시지 프레임워크에 대한 구체적인 설명이 아닌, 메시지 프레임 워크를 이용한 몇개의 커스텀 팁입니다.</p>
<h2 id="default-message-level-변경">Default Message Level 변경</h2>
<hr>
<p>Django의 메세지 레벨은 5개로 분류됩니다.</p>
<ul>
<li>DEBUG(value: 10)</li>
<li>INFO(value: 20)</li>
<li>SUCCESS(value: 25)</li>
<li>WARNING(value: 30)</li>
<li>ERROR(value: 40)</li>
</ul>
<p>Django 에서의 <strong>기본 메시지 레벨은(Default level)은 INFO</strong> 이며, 따로 수정을 하지 않는다면, INFO 레벨 이상의 메시지만 출력합니다.</p>
<p>만일 DEBUG 레벨 메시지까지 출력하고자 한다면, 다음과 같이 설정할 경우 기본 메시지 레벨을 변경 할 수 있습니다.</p>
<pre><code>#settings.py
if DEBUG:
    from django.contrib.messages import constants as message

    MESSAGE_LEVEL = message.DEBUG</code></pre><br/>

<h2 id="view-endpoint-별-massage-level-제어">View Endpoint 별 Massage Level 제어</h2>
<hr>
<p>프로젝트 전반적인 메시지 레벨을 설정하는 것 이외에, <strong>각 view endpoint 마다 메시지 레벨을 커스텀 하게 변경</strong>해야 할 수도 있습니다.
이 경우, view 함수에서 <code>django.contrib.messages</code> 의 <code>set_level(request, level)</code> 함수를 통해 각 엔드포인트 마다 메시지 수준을 다르게 정의 할 수도 있습니다.</p>
<p>예를 들어, <code>settings.py</code>에 프로젝트 전반 메시지 수준을 DEBUG로 설정하였으나, 특정 view endpoint에서는 WARNING 이상의 메시지만 사용하도록 제어하고 싶다면, 다음과 같이 view 함수에 설정할 수 있습니다. 여기서는 <code>index</code> 라는 임의의 뷰 함수를 정의하였습니다.</p>
<pre><code>from django.contrib import messages

def index(request: HttpRequest) -&gt; HttpResponse:

    messages.set_level(request, level=messages.WARNING)

    messages.debug(request, message=&quot;디버그 메시지&quot;)
    messages.info(request, message=&quot;정보 메시지&quot;)
    messages.success(request, message=&quot;성공 메시지&quot;)
    messages.warning(request, message=&quot;경고 메시지&quot;)
    messages.error(request, message=&quot;에러 메시지&quot;)
    return render(request, &quot;index.html&quot;)</code></pre><p><code>messages.set_level(request, level=messages.WARNING)</code> 이므로, 해당 엔드포인트는 WARNING, ERROR 레벨의 메시지만 전달 됩니다. 그 보다 수준이 낮은 레벨들은 사용할 수 없습니다.</p>
<p>전역으로 지정한 메시지 레벨에 대하여, 각 view 함수의 레벨을 상이하게 설정할 수 있습니다.
전역으로 지정한 메시지 레벨보다 view 함수의 <code>messages.set_level(request, level)</code> 이 우선합니다.
즉, 전역 레벨은 ERROR라도, view 함수에서 <code>messages.set_level(request, DEBUG)</code> 라면, Debug 레벨의 메시지를 담을 수 있습니다.</p>
<br/>

<h2 id="message-tag-name-커스텀">Message Tag Name 커스텀</h2>
<p>HTML CSS 렌더링을 위해, 부트스트랩을 사용하며, alert 클래스를 통해 메시지를 화면에 출력하는 경우, 일부 메시지 태그는 부트스트랩 CSS가 적용되지 않을 수 있습니다.</p>
<p>장고 메시지 레벨은 debug, <strong>info</strong>, <strong>success</strong>, <strong>warning</strong>, error 임에 비하여,
부트스트랩 alert- 옵션은 primary, secondary, <strong>info</strong>, <strong>success</strong>, <strong>warning</strong>, danger 의 이름을 사용하기 때문입니다.</p>
<p>만일, django template에서 <code>{{ message.tag }}</code> 를 통해 메시지별 색상을 렌더링 하고자 한다면, </p>
<pre><code>&lt;div class=&quot;container&quot;&gt;
  {% for message in messages %}
      &lt;div class=&quot;alert
              {% if message.level_tag == &quot;debug&quot; %}
                  alert-secondary
              {% elif message.level_tag == &quot;error&quot; %}
                  alert-danger
              {% else %}
                  alert-{{ message.level_tag }}
              {% endif %}&quot;&gt;
          {{ message.message }}
      &lt;/div&gt;
  {% endfor %}
&lt;div&gt;</code></pre><p>와 같이, if문과 elif 문으로 적절히 변경해주는 작업이 필요할 수 있습니다.
이 경우 <strong>시각적으로 복잡</strong>하며 아주 <strong>귀찮은 작업</strong>입니다.
가장 간단하게 해결하기 위해서는, 부트스트랩을 사용한다면 <code>django-bootstrap</code> 서드파티 라이브러리를 설치하고,
<strong><code>{{ bootstrap_messages }}</code></strong> 를 사용하면 간단히 해결 가능합니다.</p>
<p>다음과 같이 태그를 적용해주면</p>
<pre><code>&lt;div class=&quot;container&quot;&gt;
    &lt;h1&gt;Django Message Framework 테스트&lt;/h1&gt;
    {% bootstrap_messages %}
&lt;/div&gt;</code></pre><p><img src="https://velog.velcdn.com/images/detailed_root/post/32c1d437-0130-4b72-a107-518897e5fab0/image.png" alt=""></p>
<p>과 같이 메시지가 쉽게 렌더링 됩니다.</p>
<p>또 다른 방법으로는 <code>django.contrib.messages.constants.py</code> 의 <code>DEFAULT_TAGS</code>를 오버라이딩 할 수도 있습니다.
<code>DEFAULT_TAGS</code> 는 다음과 같이 정의되어 있습니다.</p>
<pre><code># django.contrib.messages.constant.py

DEFAULT_TAGS = {
    DEBUG: &quot;debug&quot;,
    INFO: &quot;info&quot;,
    SUCCESS: &quot;success&quot;,
    WARNING: &quot;warning&quot;,
    ERROR: &quot;error&quot;,
}</code></pre><p>한번 이걸 변경해 봅시다. 
debug는 부트스트랩 alert-primary와 매칭시킬 것이고,
error는 부트스트랩 alert-danger와 매칭시킬 것입니다.
따라서 다음과 같이 변경해 봅시다.</p>
<pre><code># settings.py
if DEBUG:
    from django.contrib.messages import constants as message

    MESSAGE_LEVEL = message.DEBUG
    message.DEFAULT_TAGS.update({10: &quot;primary&quot;, 40: &quot;danger&quot;})</code></pre><p><code>DEFAULT_TAG</code> 딕셔너리의 레벨에 대한 값을 변경한 것입니다.
(<code>django.contrip.messages.constants.py</code>  에서, <code>DEBUG</code> 상수는 10, <code>ERROR</code> 상수는 40으로 세팅되어 있기 때문에, 키 값을 레벨 정수를 사용한 것입니다.)</p>
<p>이제, 부트스트랩 class 속성의 <code>alert alert-{{ message.level_tag }}</code> 는 다음과 같이 매칭 됩니다.</p>
<ul>
<li><strong>DEBUG -&gt; alert alert-primary</strong></li>
<li>INFO -&gt; alert alert-info </li>
<li>SUCCESS -&gt; alert alert-success</li>
<li>WARNING -&gt; alert alert-warning</li>
<li><strong>ERROR -&gt; alert alert-danger</strong></li>
</ul>
<p>아래와 같은 HTML은,</p>
<pre><code>&lt;div class=&quot;container&quot;&gt;
    &lt;h1&gt;Django Message Framework 테스트&lt;/h1&gt;
    {% for message in messages %}
        &lt;div class=&quot;alert alert-{{ message.level_tag }}&quot;&gt;{{ message.message }}&lt;/div&gt;
    {% endfor %}
&lt;/div&gt;</code></pre><p>이렇게 부트스트랩 렌더링 됩니다.
<img src="https://velog.velcdn.com/images/detailed_root/post/fdbea45a-68b9-4169-8092-63e0e76d5f00/image.png" alt=""></p>
<p>디버그 레벨의 이름은 &#39;primary&#39;로 변경되어, alert-primary 로 렌더링 되었으며,
에러 레벨의 이름은 &#39;danger&#39;로 변경되어, alert-danger 로 렌더링 된 것을 확인할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[도서 리뷰] RESTful Web API(웹 API를 위한 모범 전략 가이드)]]></title>
            <link>https://velog.io/@detailed_root/%EB%8F%84%EC%84%9C-%EB%A6%AC%EB%B7%B0-RESTful-Web-API%EC%9B%B9-API%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%AA%A8%EB%B2%94-%EC%A0%84%EB%9E%B5-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@detailed_root/%EB%8F%84%EC%84%9C-%EB%A6%AC%EB%B7%B0-RESTful-Web-API%EC%9B%B9-API%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%AA%A8%EB%B2%94-%EC%A0%84%EB%9E%B5-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Mon, 12 Feb 2024 05:28:07 GMT</pubDate>
            <description><![CDATA[<p><a href="https://product.kyobobook.co.kr/detail/S000001033018">RESTful Web API(교보문고) 링크</a></p>
<h2 id="restful-web-api-부제-웹-api를-위한-모범-전략-가이드">RESTful Web API (부제: 웹 API를 위한 모범 전략 가이드)</h2>
<p>레오나르도 리처드슨(Leonard Richardson), 마이크 애먼슨(Mike Amundsen), 샘 루비(Sam Ruby)</p>
<hr>
<p>처음 웹 개발을 입문하고 나서 공부를 시작하는 과정에서 RESTful API라는 개념을 접하게 되었다.
많은 웹개발 회사에서 RESTful API에 대한 지식을 요구하고 있었기에, 해당 개념이 뭔지 블로그를 통해 찾아보기도 했던 기억이 난다.</p>
<p>블로그를 통해 이에 대한 깊이있는 지식을 습득하기란 쉽지 않았던 것으로 기억한다. 대부분은 그저 큰 그림과 개요에 불과하였으며, 무려 <strong>&quot;RESTful API&quot;</strong> 라는 엄청나 보이는 추상적인 개념에 비해 너무나 쉽고 짧게 요약된 몇가지의 규칙정도만 나열되어 있었던 것으로 기억한다.</p>
<p>나는 평소 서점을 들르는 것을 좋아한다. 서점에 들른 어느 날, 개발 관련한 책을 살펴보며 해당 책을 발견하였다. 제목부터가 강렬한 <strong>RESTful API</strong>. 뭔가 이 책을 읽으면 RESTful에 대해 깊이있는 지식과 체계적으로 습득할 수 있을것 같다는 생각이 들어 구매하게 되었다.</p>
<p>집에 오는 길에 책을 읽는데....</p>
<p>이상하다. 책이 읽히지 않는다. 
나는 RESTful API를 알고 싶었는데, 앨리스가 광고판을 클릭해서 웹사이트를 여행하며, 갑자기 미로게임 API를 설명하고 처음 들어보는 &#39;Maze+XML&#39;, &#39;Collections+JSON&#39; 미디어 타입을 소개한다.</p>
<p>&#39;<strong>의미체계</strong>&#39;, &#39;<strong>하이퍼 미디어</strong>&#39; 등 추상적인 개념이 더해지며, 책 자체가 상당히 추상적이라는 생각이 들었다.</p>
<p>내가 생각했던 RESTful API 가이드는,</p>
<ol>
<li>RESTful한 URL은 이렇게 지으세요~</li>
<li>이러이러하게 HTTP 메서드를 써야 RESTful한 거랍니다~
같은 것을 기대했기 때문이다.</li>
</ol>
<p>이런 것을 기대했다면 이 책은 적합하지 않다고 본다.
처음 책을 읽고 몇개월 방치 한 후, 다시 이 책을 진득하게 읽어나가기 시작했다.</p>
<p>지금 생각해도 상당히 추상적인 책이지만, 개인적으로 만족스럽다. 좋은 책이라고 생각한다.</p>
<p>이 책은 단지 RESTful API를 위한 공식을 나열하는 책이 아니다. 이 보다는 오히려 <strong>하이퍼 미디어</strong>, <strong>리소스</strong>, <strong>리소스의 표현</strong> 등 다소 추상적이지만 생각해 볼만한 것들이 존재한다.
대부분의 웹개발을 처음 시작하는 이들은 하이퍼미디어 리소스라 하면 그저 <strong>HTML</strong>에 익숙할 것이다. 그리고 생각의 범위는 HTML으로 쉽게 귀속되며 국한된다.</p>
<p>이 책은 하이퍼 미디어 유형 중 하나일 뿐인 HTML에 귀속되지 않는다. 다양한 하이퍼미디어 타입을 소개하며 그저 HTML이 아닌, 하이퍼 미디어 자체에 대해 생각하게 해준다. 
RESTful API에 대해 리소스, 리소스의 표현, 의미체계(Semantic), 하이퍼미디어 유형 등 다소 추상적이라 할수 있는 개념들에 대해 <strong>생각</strong> 하고 <strong>고민</strong>해 볼 수 있는 시간을 선사한다.</p>
<p>그렇기에 좋은 책이라 생각한다. HTML이라는 나무에서 멀어져 하이퍼 미디어 라는 숲을 보게 도움을 주는 책이다.
처음 접했을 때에 너무 난해하여 읽다 말았지만, 어느정도 공부를 하고 이런저런 잡지식을 많이 쌓고 나서 다시 읽으니 좋은 책이라는 생각이 든다.
애초에 RESTful API의 개념 자체가 단순한 규칙의 집합이 아니며, 철학과 담론을 담고 있기에 추상적이다. RESTful이 그저 &#39;코딩 컨벤션&#39;과 같은 규칙의 나열이 아니기 때문이다.</p>
<p>웹에 대해 처음 접하는 이들에게 좋은 책은 아니라 생각이 든다. 이 경우 오히려 &#39;<strong>HTTP 완벽가이드</strong>&#39;와 같은 책이 훨씬 유익하며 도움이 될 것이다. HTTP라는 프로토콜, 동작, 상태코드 등 HTTP에 대한 기본 지식이 있는 상태에서 이 책을 읽는것이 보다 유용할 것이라 생각한다.</p>
<p>내가 이 책을 읽고나서, 웹어플리케이션을 작성함에 있어서 엄청나게 RESTful한 개념을 적용해서 멋진 뭔가를 해낼 수 있다는 생각은 들지 않는다.
그러나, &#39;리소스&#39;, &#39;리소스의 표현&#39;, &#39;프로토콜의 의미체계(Protocol semantic)&#39;, &#39;애플리케이션 의미체계(Application semantic)&#39; 등에 대해 고민하고 생각할 수 있을 것이다.</p>
<p><strong><em>공기라는 개념을 배우지 못했다면 그리고 생각해 본 적도 없다면, 허공에 팔을 휘저음에 있어서 미약한 공기의 저항에 대하여 무언가가 존재한다는 것을 생각 조차 할 수도 없지 않겠는가?</em></strong></p>
<p>도서 평을 보면 &#39;번역&#39;에 대해 불만을 가지는 평이 보이곤 하지만, 번역의 문제보다는 추상적인 개념들이 많기에 그럴 수 밖에 없다고 생각한다. &#39;Semantic&#39;, &#39;Representation of resource&#39;과 같은 다소 추상적인 개념을 어떻게 완벽히 이해할 수 있는 한국어 단어로 표현할 수 있겠는가?</p>
<p>이 책은 RESTful API의 이름을 달고 나왔으나, RESTful에 대한 비중이 높다고 생각하진 않는다. 그렇기에 RESTful 자체를 파고드는 책이라 생각하고 구매한다면 실망스러울 것이라 생각한다.
HTML 이라는 하이퍼 미디어 중 하나에 불과한 하이퍼 미디어 개념을 벗어나, 하이퍼 미디어에 대한 보다 넓은 개념과 이것이 지니는 의미론에 대해 고민해보고 싶다면 좋은 책일 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Django] form.is_valid() 의 동작]]></title>
            <link>https://velog.io/@detailed_root/Django-form.isvalid-%EC%9D%98-%EB%8F%99%EC%9E%91</link>
            <guid>https://velog.io/@detailed_root/Django-form.isvalid-%EC%9D%98-%EB%8F%99%EC%9E%91</guid>
            <pubDate>Mon, 05 Feb 2024 17:56:23 GMT</pubDate>
            <description><![CDATA[<p>Django에서는 사용자가 페이지의 폼을 작성하고, 작성한 폼 데이터는 <strong>장고 폼(Form)</strong>을 사용하여 각 데이터의 <strong>유효성을 검증</strong>하게 된다.</p>
<p>뷰 에서 장고 폼 객체를 사용하여, 데이터의 유효성 검증하는 로직은 장고를 통해 간단한 웹어플리케이션을 만들어 보았다면 익숙할 것이다.</p>
<p>주로 Fucntion Based View를 통해, 포스트(POST) 방식으로 전송된 데이터를 장고 폼 객체의 데이터로 바운드 시킨 후에, 데이터의 유효성을 <code>is_valid()</code>로 검증을 하곤 한다.</p>
<br/>

<p>여기서 <strong>장고 폼이 내부적으로 어떻게 필드의 유효성을 검사하는지</strong>에 대한 동작이 궁금해졌다.
따라서, 해당 포스팅을 통해 <strong>내부적으로 장고 폼이 유효성을 검사하는 로직</strong>에 대해 소개하고자 한다.</p>
<hr>
<p>먼저 장고 <code>BasedForm</code> 의 <strong><code>is_valid()</code></strong> 내부 구현을 살펴보면 다음과 같다.</p>
<pre><code>class BasedForm(RenderableFormMixin):
    ...
    def is_valid(self):
        return self.is_bound and not self.errors
    ...</code></pre><p>우리가 폼 데이터 유효성 검사를 위해 <code>is_valid()</code> 를 호출할 경우,</p>
<ol>
<li><strong>바운드 된 모델이 있는지(<code>self.is_bound</code>)</strong>, </li>
<li><strong>유효성 검증 후 발생한 에러를 가지지 않는지(<code>not self.errors</code>)</strong>
를 확인한다.</li>
</ol>
<hr>
<p>여기서, <strong><code>errors</code></strong>의 로직은 다음과 같이 <code>@property</code> 로 정의되어 있다.</p>
<pre><code>    @property
    def errors(self):
        if self._errors is None:
            self.full_clean()
        return self._errors</code></pre><p>즉, <code>BasedForm</code> 객체의 필드로 정의되어 있는 <code>_errors</code>가 비어있을 경우(Form 객체 생성시 None으로 초기화 되어있음), <code>full_clean()</code> 메서드를 호출하게 된다.
<code>full_clean()</code> 메서드가 <strong>유효성 검증 로직</strong>을 담당하며, 이 과정에서 <code>ValidationError</code>가 발생 시, 발생한 에러를 <code>self._errors</code> 에 추가하여, <code>errors</code>는 수집된 에러들을 반환하는 것이다.</p>
<hr>
<p>그럼 이제 <strong><code>full_clean()</code></strong> 메서드의 내부구현을 살펴보자.</p>
<pre><code>def full_clean(self):
    self._errors = ErrorDict()
    if not self.is_bound:  # Stop further processing.
        return
    self.cleaned_data = {}
        if self.empty_permitted and not self.has_changed():
            return

    self._clean_fields()
    self._clean_form()
    self._post_clean()</code></pre><p><code>full_cleaned()</code> 메서드는 내부에 <code>self.clean_data</code> 딕셔너리를 포함하며, 여기에 유효성이 검증된 데이터를 필드이름과 값의 키-값 으로 저장할 것이다.</p>
<p>그렇다면, 어떻게 <code>self.cleaned_data</code>에 값을 저장하는가? </p>
<p><code>self._clean_fields()</code>, <code>self._clean_forms()</code>, <code>self._post_clean()</code> 을 차례대로 호출하면서 유효성 검증 결과를 채워나가는 것이다.
즉, <strong><code>full_clean()</code> 메서드는 각 필드의 유효성 검증 순서를 정의하며, 유효성이 검증된 데이터를 관리</strong>한다.</p>
<p>우리가 <code>view</code> 에서 <code>form.is_valid()</code> 호출 후, 유효성이 검증된 데이터 사용을 위해 <code>cd = form.cleaned_data</code> 사용할 수 있는 것은,</p>
<ol>
<li><code>is_valid()</code> 호출 시, 내부적으로 <code>errors</code> 호출</li>
<li><code>erorrs</code> 호출 시 내부적으로 <code>full_clean()</code> 호출</li>
<li><code>full_clean()</code> 내부 <code>self.cleaned_data</code> 정의 및 유효성이 검증된 데이터를 추가.</li>
</ol>
<p>하는 단계를 거친다는 것이다.</p>
<hr>
<p><code>full_clean()</code> 메서드가 포함하는 clean 함수들 중 추가적으로 알아 볼만 한 것은 <code>_clean_fields()</code> 이다. 해당 코드는 다음과 같다.</p>
<pre><code>def _clean_fields(self):
    for name, bf in self._bound_items():
        field = bf.field
        value = bf.initial if field.disabled else bf.data
        try:
            if isinstance(field, FileField):
                value = field.clean(value, bf.initial)
            else:
                value = field.clean(value)
            self.cleaned_data[name] = value
            if hasattr(self, &quot;clean_%s&quot; % name):
                value = getattr(self, &quot;clean_%s&quot; % name)()
                self.cleaned_data[name] = value
        except ValidationError as e:
              self.add_error(name, e)</code></pre><p>해당 로직이 각 필드를 조회하여 유효성을 검증하는 로직을 가지며, 이 과정에서 유효성 검사가 실패할 경우, <code>self._errors</code>에 발생한 에러를 추가하고 그렇지 않을 경우 <code>self.cleaned_data</code>에 유효성 검증 완료 된 값을 추가한다.</p>
<p>조금 더 알아볼만한 것은, <strong>커스텀 필드 유효성 검증</strong> 또한 해당 로직에서 담당한다는 것이다.</p>
<p>예를 들어, <code>Form</code>에 email 필드가 정의되어 있으며, 해당 이메일은 중복되어서는 안된다고 해보자.
우리는 이 경우, <code>Form</code> 객체에 에 다음과 같은 커스텀 유효성 검증 로직을 추가할 수 있을 것이다.</p>
<pre><code>class MyForm(forms.Form):
    email = forms.EmailField()

    def clean_email(self):
        ## 이메일 중복 검사 로직
        return &lt;email 값&gt;
</code></pre><p>이와 같이 <code>cleand_&lt;field name&gt;()</code> 함수를 정의하여 커스텀 유효성 검증 로직을 추가할 수 있는 것은,
<code>cleaned_fields()</code> 에서 </p>
<pre><code>if hasattr(self, &quot;clean_%s&quot; % name):
    value = getattr(self, &quot;clean_%s&quot; % name)()
    self.cleaned_data[name] = value</code></pre><p>로직이 존재하기 때문이다.</p>
<p>따라서 우리는 특정 필드에 유효성 검증 로직을 커스텀 제어하고자 할 경우, <code>clean_&lt;field name&gt;()</code> 함수만 정의하면 내부적으로 알아서 해당 코드에 대한 유효성검증 로직이 수행된다는 것이다.  </p>
<hr>
<h3 id="form의-유효성-검증-과정-요약">Form의 유효성 검증 과정 요약</h3>
<ul>
<li><code>is_valid()</code> 호출 시, 내부에서 <code>errors</code> property logic을 호출</li>
<li><code>erros</code> 프로퍼티는 내부에서 <code>full_clean()</code> 호출</li>
<li><code>full_clean()</code> 은 내부에 <code>self.cleaned_data</code> 딕셔너리를 포함</li>
<li><code>full_clean()</code> 은 내부에 <code>_cleand_field()</code> 등 유효성 검증 로직을 수행</li>
<li><code>_cleand_field()</code> 등이 실제 유효성 검증 로직을 수행하며, 유효성을 만족할 경우 해당 데이터를 <code>self.cleaned_data</code>에 추가</li>
</ul>
<p>+ <code>clean_&lt;field name&gt;</code> 과 같이 커스텀 유효성 검증 함수를 추가할 수 있는 것 또한 <code>_clean_field()</code> 가 이를 인식할 수 있도록 만들어졌기 때문. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Django] @login_required 의 동작]]></title>
            <link>https://velog.io/@detailed_root/Django-loginrequired-%EC%9D%98-%EB%8F%99%EC%9E%91</link>
            <guid>https://velog.io/@detailed_root/Django-loginrequired-%EC%9D%98-%EB%8F%99%EC%9E%91</guid>
            <pubDate>Sat, 03 Feb 2024 21:33:26 GMT</pubDate>
            <description><![CDATA[<p>Django에서는 view endpoint를 로그인 유저에게만 허용되도록 <code>@login_required</code> 데코레이터를 통해 간편하게 적용할 수 있다.</p>
<p><code>@login_required</code> 데코레이터는 어떻게 동작할까?</p>
<p>root endpoint (<a href="http://localhost:8000/">http://localhost:8000/</a>) view에 <code>@login_required</code> 를 적용한다고 해보자.</p>
<p>이 경우, <a href="http://localhost:8000/">http://localhost:8000/</a> 은 로그인 하지 않은 상태에서는, 
<code>@login_reqired</code>에 의해 <a href="http://localhost:8000/accounts/login/?next=/">http://localhost:8000/accounts/login/?next=/</a> 으로 리다이렉트 된다.</p>
<p>난 로그인에 대한 그 어떤것도 작성하지 않았음에도, <code>@login_required</code>를 적용하면 /account/login/?next=/ 로 자동으로 페이지가 이동하는 것이다.</p>
<p>이유가 뭘까?</p>
<p>먼저, <code>@login_required</code>는 <code>settings.py</code>에 정의된 <code>LOGIN_URL</code> 값을 참조하여 url을 생성하여 해당 url로 이동하게 된다.</p>
<p><code>django.conf.global_settings.py</code>를 보면, 다음과 같이 Default <code>LOGIN_URL</code>이 정의되어 있다.</p>
<pre><code>LOGIN_URL = &quot;/accounts/login/&quot;</code></pre><p>따라서, login url에 대한 어떤 사용자 정의가 없어도, 해당 url(<a href="http://localhost:8000/accounts/login/)%EB%A1%9C">http://localhost:8000/accounts/login/)로</a> 이동하는 것이다.</p>
<p>그렇다면, <strong>?next=/</strong> 쿼리 스트링은 무엇인가?</p>
<p>해당 엔드포인트가 <code>@login_required</code>로 인해,  로그인 하지 않은 상태에서는, login 페이지로 자동으로 이동하게 된다.
로그인에 성공할 경우, 원래 접근하고자 했던 endpoint(<a href="http://localhost:8000/)%EB%A1%9C">http://localhost:8000/)로</a> <strong>다시 돌아갈 수 있어야</strong> 하는 것이 타당할 것이다.
이를 위해 next 라는 쿼리스트링을 자동으로 추가하여,로그인 후 처음에 접근하고자 했던 엔드포인트로 돌아가도록 만드는 것이다.</p>
<p>예를 들어, 
<a href="http://localhost/aaa/">http://localhost/aaa/</a> 엔드포인트가 <code>@login_required</code>로 보호된다고 해보자.
이 경우 엔드포인트 접근 시, 자동으로 <a href="http://localhost/accounts/login/?next=/aaa">http://localhost/accounts/login/?next=/aaa</a> 로 리다이렉트 되며,
해당 로그인 페이지에서 로그인이 성공할 경우, next=/aaa 쿼리스트링을 통해 다시 <a href="http://localhost/aaa">http://localhost/aaa</a> 로 리다이렉트 되는 것이다.</p>
<p>Class Based View인 <code>LoginView</code> 또한 내부적으로 url의 next 쿼리스트링으로 로그인 성공시 이동하는 페이지를 제어가능하다.</p>
<p>next 쿼리스트링이 존재하지 않을 경우, <code>django.conf.global_settings.py</code>에 정의 된 </p>
<pre><code>LOGIN_REDIRECT_URL = &quot;/accounts/profile/&quot;</code></pre><p>로 이동하나, 
url에 next 쿼리스트링이 존재할 경우, 이것이 우선한다.
즉, <strong>next 쿼리스트링을 통해 로그인 후 이동할 페이지에 대해 사용자 제어 가능</strong>하다는 것이다.</p>
<p><code>LoginView</code>를 사용할 경우, next는 템플릿 변수로 템플릿에 전달되며, html에서 {{next}}를 통해 사용 가능하다.
로그인 폼 html 페이지에서, </p>
<pre><code>&lt;input type=&quot;hidden&quot; name=&quot;next&quot; value=&quot;이동하고자 하는 위치&quot; /&gt;</code></pre><p>를 추가할 경우, 로그폼과 함께, next 변수는 &#39;이동하고자 하는 위치&#39;로 전달되며, <code>ListView</code>는 next 변수 대로, 이동하도록 할 수 있다.
즉, 로그인 후, <a href="http://localhost/%EC%9D%B4%EB%8F%99%ED%95%98%EA%B3%A0%EC%9E%90-%ED%95%98%EB%8A%94-%EC%9C%84%EC%B9%98">http://localhost/이동하고자-하는-위치</a> 로 리다이렉트 시킬 수 있는 것이다.</p>
<p>나는 처음에 Django 에서 next 변수를 왜 사용하는지 잘 이해가 되지 않았다. 
이제는 좀 알것같다. 그리고 이것을 통해 보다 유용하게 Django를 사용할 수 있을것 같다.</p>
<p>Django에서의 규칙이 존재할 경우, 이를 잘 활용할 수 있으며 적극적으로 이용해야 한다고 생각한다.</p>
<br />

<p><code>@login_required</code>를 정리해 보자면,</p>
<ul>
<li>해당 엔드포인트 접근 시, <code>settings.py</code> 에 정의된 <code>LOGIN_URL</code> 로 이동. (login page의 url 에는 로그인 성공 시 원래 페이지로 돌아가기 위한 next 쿼리스트링이 존재.)</li>
<li>로그인 성공 시, next 쿼리스트링에 포함된 페이지로 리다이렉트.</li>
<li><code>settings.py</code>의 <code>LOGIN_REDIRECT_URL</code> 과 next 쿼리스트링 중 next 가 우선.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Django] TextField의 max_length의 중요성 대하여]]></title>
            <link>https://velog.io/@detailed_root/Django-TextField%EC%9D%98-maxlength%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1-%EB%8C%80%ED%95%98%EC%97%AC</link>
            <guid>https://velog.io/@detailed_root/Django-TextField%EC%9D%98-maxlength%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1-%EB%8C%80%ED%95%98%EC%97%AC</guid>
            <pubDate>Sat, 03 Feb 2024 15:39:24 GMT</pubDate>
            <description><![CDATA[<p>파이썬 웹 프레임워크인 Django를 통해 웹을 개발하다 보면, <code>Model</code> 객체를 작성하는 과정에서 <code>models.TextField()</code>에 익숙할 것이다.</p>
<p>대부분의 Django 기초 혹은 입문 책과 강의를 접하여 학습 하는 과정에서, 대개 <code>TextField</code>의 <code>max_length</code> 옵션을 제한하지 않고 비워 둔 채로 사용하는 것을 심심찮게 경험하였다.
나 또한 아무 생각없이, <code>TextField</code>가 필요하다면, 그저 <code>some_field = models.TextField()</code>로 정의하여 사용하였다.</p>
<p>그러다, 김성렬님의 <strong>&#39;백엔드 개발을 위한 핸즈온 장고&#39;</strong> 책을 학습하던 중, </p>
<blockquote>
<p><strong>&quot;<code>TextField</code>는 기본적으로 DB에 따라 다르나 최대 1GB 까지 저장 될 수 있다.&quot;</strong></p>
</blockquote>
<p>라는 내용을 보고, <code>TextField</code>를 사용함에 있어 엄격하게 <code>max_length</code> 옵션을 사용해야 한다는 생각이 들었다.</p>
<blockquote>
<p>만일 <code>Model</code> 객체가 <code>TextField</code>를 포함하며, <code>max_length</code> 옵션이 지정되지 않았다면?</p>
</blockquote>
<p><strong><em>악의적인 사용자</em></strong> 가 존재할 경우, 해당 텍스트 필드가 허용하는 최대한 길의의 무의미한 텍스트를 전송하여, _<strong>의도적으로 Server 리소스를 낭비</strong>_시킬 수 있을 것이다.</p>
<p>이에 대해 간단히 테스트를 해보았다.</p>
<p><code>title(CharField)</code>과 <code>description(TextField)</code> 를 가지는 <code>Post</code> 객체를 만들어, 
sqlite3 DB에 대용량의 텍스트 데이터를 저장하는 테스트를 하였다.</p>
<p>파이썬 쉘을 통하여, 다음과 같은 텍스트를 DB에 저장해 보았다.</p>
<pre><code>&gt;&gt; text = &#39;a&#39; * (10**8)
&gt;&gt; post = Post(title=&#39;Test Title&#39;, description=text)
&gt;&gt; post.save()</code></pre><p>생성 된 Data를 확인 해 보니, 실제 DB에서는 약 200KB 의 데이터가 저장되었음을 확인하였다.
String을 Byte로 변환하여 저장하므로, 예상한 String의 크기보다 적은 용량이 저장되었다.</p>
<p>그렇다면, byte type text data를 전송 및 저장한다면?
저장할 텍스트를 바이트 데이터로 변경하여 저장 해보았으며. 1byte 크기 byte문자를 약 800MB 사이즈로 저장해 보았다.</p>
<pre><code>&gt;&gt; text = b&#39;a&#39; * (8*10**8)   # byte 문자열 800MB
&gt;&gt; post = Post(title=&#39;Test Title&#39;, description=text)
&gt;&gt; post.save()</code></pre><p>실제 DB에서도 약 800MB 사이즈의 크기를 가진 row 데이터가 생성되었으며, 
프로젝트 폴더의 크기가 단 한개의 row를 가짐에도 불구하고, 800MB의 크기를 가지는 것을 확인하였다. (sqlite 사용시 프로젝트 폴더 내, db 파일을 통해 데이터를 저장하므로)</p>
<p>만일 프로젝트가 <code>max_length</code>를 엄격하게 적용하지 않은 <code>TextField</code> 를 사용하는 경우, 악의적인 의도로 byte data를 1GB까지 꽉 채워서 서버에 저장하도록 할 수 있다는 것이다.
<strong>1000개만 보내더라도, 1TB의 저장공간을 간단하게 소모</strong>시킬 수 있을 것이다.</p>
<p>&#39;강건한&#39; 서버를 염두한다면, <code>TextField</code> 의 사용 또한 엄격하게 적용해야 할 것으로 생각된다.
나 또한 이 점을 기억하고 프로젝트를 함에 있어 엄격히 적용해야 함을 느끼게 되었다.</p>
<p>Client 측에서 입력 데이터의 크기를 제한할 수도 있을 것이나, 만약 변경에 의해 그러한 중요한 사항이 누락 된다면?
Server의 책임을 단지 Client에게 책임을 전가할 수 없을 것이다. </p>
]]></description>
        </item>
    </channel>
</rss>