<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>HEll0THere?</title>
        <link>https://velog.io/</link>
        <description>안녕하세요:)</description>
        <lastBuildDate>Wed, 11 Oct 2023 08:58:44 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>HEll0THere?</title>
            <url>https://velog.velcdn.com/images/easy_on7/profile/04a8debf-dc1e-4c4f-8d88-cff40eb48405/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. HEll0THere?. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/easy_on7" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Java] Objects.requireNonNull이란? ]]></title>
            <link>https://velog.io/@easy_on7/Java-Objects.requireNonNull%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@easy_on7/Java-Objects.requireNonNull%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Wed, 11 Oct 2023 08:58:44 GMT</pubDate>
            <description><![CDATA[<p>이펙티브 자바를 읽다가 <code>null</code>에 대한 체크를 위해 <code>Objects</code> 클래스의 <code>requireNonNull</code> 을 사용하는 것을 알게 되었다. 따라서, 이에 대해 정리하고 왜 사용하는지에 대해 알아보자. </p>
<h1 id="requirenonnull">requireNonNull</h1>
<p><code>requireNonNull</code> 은 </p>
<blockquote>
<p>Java 7에 추가된 <code>Objects</code> 클래스에서 제공하는 <strong>Null 체크를 위한 메서드</strong></p>
</blockquote>
<p>이다.</p>
<p>파라미터로 입력된 값이 null이라면 <strong>NullPointerException(NPE)</strong>이 발생하고, 그렇지 않다면 입력값을 그래도 반환한다. </p>
<p><strong>Java 11</strong>을 기준으로 requireNonNull 은 다음과 같이 3가지로 오버로딩 되어있다.<br><img src="https://velog.velcdn.com/images/easy_on7/post/a38d976d-c470-4c2f-aa7b-c68091547873/image.png" alt=""></p>
<table>
<thead>
<tr>
<th>리턴 타입(Return Type)</th>
<th>메서드(Method)</th>
</tr>
</thead>
<tbody><tr>
<td>T</td>
<td>requireNonNull(T obj)</td>
</tr>
<tr>
<td>T</td>
<td>requireNonNull(T obj, String message)</td>
</tr>
<tr>
<td>T</td>
<td>requireNonNull(T obj, Supplier<String> messageSupplier)</td>
</tr>
</tbody></table>
<h2 id="requirenonnullt-obj">requireNonNull(T obj)</h2>
<p>첫 번째 메서드는 <code>null</code> 을 전달하면 오류메시지가 비어있는 <strong>NPE</strong>를 던진다.  </p>
<pre><code class="language-java">Objects.requireNonNull(null);
// 결과 : java.lang.NullPointerException

String normalStr = Objects.requireNonNull(&quot;일반적인 객체&quot;);
System.out.println(normalStr);
// 결과 : 일반적인 객체</code></pre>
<h2 id="requirenonnullt-obj-string-message">requireNonNull(T obj, String message)</h2>
<p>두 번째 메서드는 <code>null</code> 을 전달하면, 두 번째 파라미터로 전달된 문자열을 오류메시지로 갖는 <strong>NPE</strong>를 던진다. </p>
<pre><code class="language-java">Objects.requireNonNull(null, &quot;null은 입력될 수 없습니다.&quot;);

// 결과 : java.lang.NullPointerException: null은 입력될 수 없습니다.</code></pre>
<h2 id="requirenonnullt-obj-supplierstring-messagesupplier">requireNonNull(T obj, Supplier<String> messageSupplier)</h2>
<p>마지막 메서드는  <code>null</code> 을 전달하면, 두 번째 파라미터로 전달한 <strong>Supplier를 구현한 익명 함수의 반환값</strong>을 오류메시지로 갖는 <strong>NPE</strong>를 던진다.  </p>
<pre><code class="language-java">Objects.requireNonNull(null, () -&gt; &quot;null은 입력될 수 없습니다.&quot;);

// 결과 : java.lang.NullPointerException: null은 입력될 수 없습니다.</code></pre>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/071d39bd-1463-4d4d-bde4-23755369d661/image.png" alt=""></p>
<p>실제 구현된 코드를 확인해보면, 위와 같다. </p>
<h1 id="❓사용하는-이유">❓사용하는 이유</h1>
<p>위 코드의 내용을 보면 너무 당연한 내용이다. null이면 NPE를 던지고, 아니라면 그대로 반환하기 때문이다. 그렇다면 <code>requireNonNull</code>을 왜 사용할까?</p>
<h2 id="fail-fast">Fail-Fast</h2>
<p>첫 번째 이유로 <strong>Fail-Fast</strong>를 들 수 있다. <code>Fail-Fast</code>란, 장애가 발생한 시점에서 즉시 파악할 수 있는 것을 말한다. 디버깅을 쉽게 하기 위해서 문제가 발생한 경우 즉각적으로 감지할 필요가 있다. 문제의 원인과 발생 지점이 멀리 떨어져 있다면, 오류를 찾는데 걸리는 시간이 오래 걸릴 것이다. 즉, 시스템이 복잡해 질 수록 장애를 발견하기 어렵게 된다. </p>
<h2 id="명시성">명시성</h2>
<p>다음 두 가지 예시 코드를 보자.</p>
<h3 id="수동-null-체크">수동 null 체크</h3>
<pre><code class="language-java">String nullString = null;

if(nullString == null) {
    throw new NullPointerException(&quot;입력값이 null입니다.&quot;);
}</code></pre>
<h3 id="requirenonnull을-이용한-null-체크">requireNonNull을 이용한 null 체크</h3>
<pre><code class="language-java">String nullString = null;
Objects.requireNonNull(nullString, &quot;입력값이 null입니다.&quot;);</code></pre>
<p>위 두 코드를 확인해보면, <code>requireNonNull</code>을 사용한 코드가 훨씬 가독성이 좋고, 명시적인 표현 방법인 것을 알 수 있다.</p>
<h3 id="참고">참고</h3>
<ul>
<li><a href="https://hudi.blog/java-requirenonnull/">https://hudi.blog/java-requirenonnull/</a></li>
<li><a href="https://devdocs.io/openjdk~11/java.base/java/util/objects">https://devdocs.io/openjdk~11/java.base/java/util/objects</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective_Java] Item 4 : 인스턴스화를 막으려거든 private 생성자를 사용하라]]></title>
            <link>https://velog.io/@easy_on7/EffectiveJava-Item-4-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4%ED%99%94%EB%A5%BC-%EB%A7%89%EC%9C%BC%EB%A0%A4%EA%B1%B0%EB%93%A0-private-%EC%83%9D%EC%84%B1%EC%9E%90%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@easy_on7/EffectiveJava-Item-4-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4%ED%99%94%EB%A5%BC-%EB%A7%89%EC%9C%BC%EB%A0%A4%EA%B1%B0%EB%93%A0-private-%EC%83%9D%EC%84%B1%EC%9E%90%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</guid>
            <pubDate>Mon, 09 Oct 2023 08:38:46 GMT</pubDate>
            <description><![CDATA[<p>단순히 정적 메서드와 정적 필드만을 담은 클래스를 만들고 싶을 때가 있다. 나름의 쓰임새가 있다. </p>
<p>예를 들어, <code>java.lang.Math</code>나 <code>java.util.Arrays</code>처럼 기본 타입 값이나 배열 관련 메서드들을 모아 놓을 수 있다. 또한, <code>java.util.Collections</code>처럼 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드를 모아 놓을 수 있다. </p>
<p><strong>정적 맴버만 담은 유틸리티 클래스</strong>는 <u>인스턴스로 만들어 쓰려고 설계한 것이 <strong>아니다</strong>.</u> </p>
<p>하지만, 생성자를 명시하지 않으면 컴파일러가 자동으로 기본 생성자를 만들어준다. 매개변수를 받지 않는 public 생성자가 만들어지며, 사용자는 이 생성자가 자동 생성된 것인지 구분할 수 없다. </p>
<p>즉, <strong>추상 클래스로 만드는 것으로는 인스턴스화를 막을 수 없다.</strong> 하위 클래스를 만들어 인스턴스화하면 그만이기 때문이다. </p>
<p>이를 막기 위해 </p>
<blockquote>
<p><code>private</code> 생성자를 추가하면 클래스의 인스턴스화를 막을 수 있다.</p>
</blockquote>
<pre><code class="language-java">public class UtilityClass {
    // 기본 생성자가 만들어지는 것을 막는다 = 인스턴스화 방지용
    private UtilityClass() {
        throw new AssertionError();
    }
    ...
}</code></pre>
<p>위 코드를 살펴보면, 명시적 생성자가 <code>private</code>이니 클래스 바깥에서는 접근할 수 없다. 또한, AssertionError를 통해 클래스 안에서 실수로라도 생성자를 호출하지 않도록 해준다. </p>
<p><code>java.util.Arrays</code> 의 코드를 보면 다음과 같이 생성자를 private로 선언한 것을 볼 수 있다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/af533368-6649-40d6-8772-14f7b0502ed6/image.png" alt=""></p>
<p><code>java.util.Collections</code> 또한 마찬가지이다.</p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/cf0feda9-6ce1-45ca-bd32-6026ba714d4a/image.png" alt=""></p>
<p>또한, 이 방식은 <strong>상속을 불가능하게 하는 효과</strong>도 있다. 모든 생성자는 명시적이든 묵시적이든 상위 클래스의 생성자를 호출하게 되는데, 이를 private로 선언했으니 하위 클래스가 상위 클래스의 생성자에 접근할 길이 막혀버린다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective_Java] Item 3 : private 생성자나 열거 타입으로 싱글턴임을 보장하라.]]></title>
            <link>https://velog.io/@easy_on7/EffectiveJava-Item-3-private-%EC%83%9D%EC%84%B1%EC%9E%90%EB%82%98-%EC%97%B4%EA%B1%B0-%ED%83%80%EC%9E%85%EC%9C%BC%EB%A1%9C-%EC%8B%B1%EA%B8%80%ED%84%B4%EC%9E%84%EC%9D%84-%EB%B3%B4%EC%9E%A5%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@easy_on7/EffectiveJava-Item-3-private-%EC%83%9D%EC%84%B1%EC%9E%90%EB%82%98-%EC%97%B4%EA%B1%B0-%ED%83%80%EC%9E%85%EC%9C%BC%EB%A1%9C-%EC%8B%B1%EA%B8%80%ED%84%B4%EC%9E%84%EC%9D%84-%EB%B3%B4%EC%9E%A5%ED%95%98%EB%9D%BC</guid>
            <pubDate>Sat, 07 Oct 2023 10:00:24 GMT</pubDate>
            <description><![CDATA[<h1 id="싱글턴singleton">싱글턴(Singleton)</h1>
<p>Springboot를 공부하다 한 번 쯤은 들어봤을 싱글턴. 싱글턴이란 무엇일까요?</p>
<p><code>싱글턴(Singleton)</code>은</p>
<blockquote>
<p>인스턴스를 오직 하나만 생성할 수 있는 클래스</p>
</blockquote>
<p>를 의미합니다.
그렇다면, Springboot에서 말하는 싱글턴 패턴이란, 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴입니다. 또, 설계상 유일해야 하는 시스템 컴포넌트와 같은 것을 예로 들 수 있겠네요.</p>
<p>하지만, 클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 <strong>테스트하기가 어려워 질 수 있습니다.</strong> 타입을 인터페이스로 정의한 뒤 그 인스턴스를 구현해서 만든 싱글턴이 아니라면 싱글턴 인스턴스를 가짜(mock) 구현으로 대체할 수 없기 때문이죠.</p>
<h1 id="싱글턴을-만드는-방식">싱글턴을 만드는 방식</h1>
<p>싱글턴을 만드는 방식은 보통 둘 중 하나입니다. 두 방식 모두 <strong>생성자는 <code>private</code>로 감춰</strong>놓고, 유일한 <strong>인스턴스에 접근할 수 있는 수단</strong>으로 <strong><code>public static</code> 맴버</strong>를 마련해둡니다.
하나씩 살펴봅시다.</p>
<h2 id="1-public-static-final-필드-방식">1. public static final 필드 방식</h2>
<p>우선 <code>public static</code> 맴버가 <strong><code>final</code></strong> 필드인 방식을 살펴봅시다.</p>
<pre><code class="language-java">// 1. public static final 필드 방식의 싱글턴
public class Elvis {
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() { }

    public void leaveTheBuilding() {
        System.out.println(&quot;Whoa baby, I&#39;m outta here!&quot;);
    }
}

class Main {
        public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}</code></pre>
<p>위 코드에서 <code>private</code> 생성자는 public static final 필드인 <strong>Elvis.INSTANCE</strong>를 초기화할 때 딱 한 번만 호출됩니다. <code>public</code>이나 <code>protected</code> 생성자가 없어 Elvis 클래스가 초기화될 때 만들어진 인스턴스가 전체 시스템에서 하나뿐임이 보장됩니다.  </p>
<blockquote>
<p>물론 예외가 존재합니다. 
권한이 있는 클라이언트가 리플랙션 API인 <code>AccessibleObject.setAccessible</code>을 사용하면 private 생성자를 호출할 수 있습니다. 이를 방어하기 위해서는 생성자를 수정해 두 번째 객체가 생성되려 할 때 예외를 던지면 됩니다. 하지만, 여기서는 이 내용에 대해 자세히 다루지 않겠습니다.</p>
</blockquote>
<h3 id="✅-장점">✅ 장점</h3>
<ol>
<li>해당 클래스가 싱글턴임이 API에 명백히 들어납니다. 필드가 final이기 때문에 절대 다른 객체를 참조할 수 없습니다. </li>
<li>간결합니다.</li>
</ol>
<h2 id="2-정적-팩토리-메서드-방식">2. 정적 팩토리 메서드 방식</h2>
<p>두 번째 방식은 정적 팩토리 메서드를 <code>public static</code> 맴버로 제공하는 것입니다. </p>
<pre><code class="language-java">// 2. 정적 팩터리 방식의 싱글턴
public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() { }
    public static Elvis getInstance() { return INSTANCE; }

    public void leaveTheBuilding() {
        System.out.println(&quot;Whoa baby, I&#39;m outta here!&quot;);
    }
}

class Main {
        public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}</code></pre>
<p>1번과 다르게 <strong>INSTANCE</strong>가 <code>private</code>로 감춰져 있습니다. </p>
<p><strong><code>Elvis.getInstance</code></strong>는 항상 같은 객체의 참조를 반환하기 때문에 제 2의 인스턴스는 만들어 질 수 없습니다. </p>
<h3 id="✅-장점-1">✅ 장점</h3>
<ol>
<li>API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있습니다. 유일한 인스턴스를 반환하던 팩토리 메서드가 호출하는 스레드별로 다른 인스턴스를 넘겨주게 할 수 있습니다. </li>
<li>원한다면 정적 팩토리를 제네릭 싱글턴 팩토리로 만들 수 있습니다. </li>
<li>정적 팩토리의 메서드 참조를 공급자(supplier)로 사용할 수 있습니다. 가령, <code>Elvis::getInstance</code>를 <code>Supplier&lt;Elvis&gt;</code>로 사용하는 방식입니다. </li>
</ol>
<p>→ 이러한 장점들이 굳이 필요하지 않다면, <strong>1번 방식인 public 필드 방식</strong>이 좋습니다. </p>
<h2 id="3-열거-타입-방식">3. 열거 타입 방식</h2>
<p>세 번째 방법은 원소가 하나인 <strong>열거 타입(<code>Enum Type</code>)</strong>을 선언하는 것입니다. </p>
<pre><code class="language-java">// 3. 열거 타입 방식의 싱글턴 - 바람직한 방법
public enum Elvis {
    INSTANCE;

    public void leaveTheBuilding() {
        System.out.println(&quot;기다려 자기야, 지금 나갈께!&quot;);
    }    
}

class Main {
        public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}</code></pre>
<p>1번의 <code>public</code> 필드 방식과 유사합니다. </p>
<h3 id="✅-장점-2">✅ 장점</h3>
<ol>
<li>더 간결합니다.</li>
<li>추가적인 노력없이 직렬화할 수 있습니다. 심지어 아주 복잡한 직렬화 상황이나 리플랙션 공격에서도 제 2의 인스턴스가 생기는 일을 완벽히 막을 수 있습니다. </li>
</ol>
<h1 id="💡정리">💡정리</h1>
<p>대부분의 상황에서는 원소가 하나뿐인 <code>Enum Type</code>이 싱글턴을 만드는 가장 좋은 방법입니다. 하지만, 만들려는 싱글턴이 Enum 외의 클래스를 상속해야 한다면 이 방법은 사용할 수 없습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective_Java] Item 2 : 생성자에 매개변수가 많다면 빌더를 고려하라.]]></title>
            <link>https://velog.io/@easy_on7/EffectiveJava-Item-2-%EC%83%9D%EC%84%B1%EC%9E%90%EC%97%90-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98%EA%B0%80-%EB%A7%8E%EB%8B%A4%EB%A9%B4-%EB%B9%8C%EB%8D%94%EB%A5%BC-%EA%B3%A0%EB%A0%A4%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@easy_on7/EffectiveJava-Item-2-%EC%83%9D%EC%84%B1%EC%9E%90%EC%97%90-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98%EA%B0%80-%EB%A7%8E%EB%8B%A4%EB%A9%B4-%EB%B9%8C%EB%8D%94%EB%A5%BC-%EA%B3%A0%EB%A0%A4%ED%95%98%EB%9D%BC</guid>
            <pubDate>Sat, 07 Oct 2023 09:13:10 GMT</pubDate>
            <description><![CDATA[<p>이전에 설명했던 정적 팩토리와 생성자에는 공통적인 제약이 존재합니다. 바로, </p>
<blockquote>
<p>선택적 매개변수가 많을 때 적절히 대응하기 어렵다.</p>
</blockquote>
<p>는 것입니다.</p>
<p>이럴 때 프로그래머들은 <code>점층적 생성자 패턴(Telescoping constructor pattern)</code>을 즐겨 사용했습니다.</p>
<h1 id="점층적-생성자-패턴">점층적 생성자 패턴</h1>
<p>이는 필수 매개변수만 받는 생성자, 필수 매개변수와 선택 매개변수 1개를 받는 생성자, 선택 매개변수를 2개까지 받는 생성자 ... 형태로 선택 매개변수를 전부 받는 생성자까지 늘려가는 방식입니다. </p>
<p>아래는 식품 포장의 영양정보를 표현하는 클래스입니다. 이때 점층적 생성자 패턴을 적용시켜 작성했습니다.</p>
<pre><code class="language-java">
// 점층적 생성자 패턴 - 확장하기 어렵다!
public class NutritionFacts {
    private final int servingSize;  // (mL, 1회 제공량)     필수
    private final int servings;     // (회, 총 n회 제공량)   필수
    private final int calories;     // (1회 제공량당)       선택
    private final int fat;          // (g/1회 제공량)       선택
    private final int sodium;       // (mg/1회 제공량)      선택
    private final int carbohydrate; // (g/1회 제공량)       선택

    public NutritionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0);
    }

    public NutritionFacts(int servingSize, int servings,
                          int calories) {
        this(servingSize, servings, calories, 0);
    }

    public NutritionFacts(int servingSize, int servings,
                          int calories, int fat) {
        this(servingSize, servings, calories, fat, 0);
    }

    public NutritionFacts(int servingSize, int servings,
                          int calories, int fat, int sodium) {
        this(servingSize, servings, calories, fat, sodium, 0);
    }

    public NutritionFacts(int servingSize, int servings,
                          int calories, int fat, int sodium, int carbohydrate) {
        this.servingSize  = servingSize;
        this.servings     = servings;
        this.calories     = calories;
        this.fat          = fat;
        this.sodium       = sodium;
        this.carbohydrate = carbohydrate;
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola =
                new NutritionFacts(240, 8, 100, 0, 35, 27);
    }

}</code></pre>
<p>하지만, 이 방식은</p>
<blockquote>
<p>매개변수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.</p>
</blockquote>
<p>는 명확한 단점이 존재합니다. </p>
<p>매개변수가 몇 개인지 주의해서 세어 보아야 하고, 타입이 같은 매개변수가 연달아 늘어서 있으면 찾기도 어려울 뿐더러, 실수로 순서를 바꿔 건네주면 엉뚱한 동작을 하거나 버그로 이어질 가능성이 매우 크기 때문이죠.</p>
<p>두 번째 대안으로는 <code>자바빈즈 패턴(JavaBeans Pattern)</code>이 있습니다. </p>
<h1 id="자바빈즈-패턴javabeans-pattern">자바빈즈 패턴(JavaBeans Pattern)</h1>
<p>자바빈즈 패턴은,</p>
<blockquote>
<p>매개변수가 없는 생성자로 객체를 만든 후, 새터(Setter) 메서드들을 호출해 원하는 매개변수의 값을 설정</p>
</blockquote>
<p>하는 방식입니다. </p>
<p>위의 식품영양 정보를 표현하는 클래스를 자바빈즈 패턴으로 다시 작성해봅시다. </p>
<pre><code class="language-java">
// 자바빈즈 패턴 - 일관성이 깨지고, 불변으로 만들 수 없다.
public class NutritionFacts {
    // 매개변수들은 (기본값이 있다면) 기본값으로 초기화된다.
    private int servingSize  = -1; // 필수; 기본값 없음
    private int servings     = -1; // 필수; 기본값 없음
    private int calories     = 0;
    private int fat          = 0;
    private int sodium       = 0;
    private int carbohydrate = 0;

    public NutritionFacts() { }

    // Setters
    public void setServingSize(int val)  { servingSize = val; }
    public void setServings(int val)     { servings = val; }
    public void setCalories(int val)     { calories = val; }
    public void setFat(int val)          { fat = val; }
    public void setSodium(int val)       { sodium = val; }
    public void setCarbohydrate(int val) { carbohydrate = val; }

    public static void main(String[] args) {
        NutritionFacts cocaCola = new NutritionFacts();
        cocaCola.setServingSize(240);
        cocaCola.setServings(8);
        cocaCola.setCalories(100);
        cocaCola.setSodium(35);
        cocaCola.setCarbohydrate(27);
    }
}</code></pre>
<p>이전의 점층적 생성자 패턴의 단점들은 보이지 않습니다. </p>
<p>하지만, 여기도 치명적인 단점이 존재합니다. 바로,</p>
<blockquote>
<p>객체 하나를 만들기위해 <u>메서드를 여러 개 호출</u>해야 하고, 객체가 완전히 생성되기 전까지는 <u>일관성이 무너진 상태</u>에 놓이게 된다. </p>
</blockquote>
<p>는 것입니다. 이 때문에, <strong>자바빈즈 패턴에서는 클래스를 불변으로 만들 수 없</strong>습니다.
<br></p>
<p>이러한 모든 문제들을 해결하기 위해 점층적 생성자 패턴의 안전성과 자바빈즈 패턴의 가독성을 겸비한 <strong><code>빌더 패턴(Builder Pattern)</code></strong> 을 사용합니다. </p>
<h1 id="빌더-패턴builder-pattern">빌더 패턴(Builder Pattern)</h1>
<p>빌더 패턴(Builder Pattern)에선 다음과 같은 과정을 통해 객체를 얻습니다. </p>
<ol>
<li><p>클라이언트는 필요한 객체를 직접 만드는 대신, <strong>필수 매개변수만으로 생성자(or 정적 팩토리)를 호출</strong>해 <u>빌더 객체</u>를 얻습니다.</p>
</li>
<li><p>빌더 객체가 제공하는 일종의 <strong>Setter 메서드</strong>들로 원하는 선택 매개변수들을 설정합니다.</p>
</li>
<li><p>매개변수가 없는 build 메서드를 호출해 필요한(보통은 불변인) 객체를 얻습니다.</p>
</li>
</ol>
<p><strong><code>빌더(Builder)</code></strong>는 생성할 클래스 안에 정적 맴버 클래스로 만들어 둡니다. </p>
<p>어떻게 동작하는지 아래 코드를 살펴 봅시다.</p>
<pre><code class="language-java">// 빌더 패턴 - 점층적 생성자 패턴과 자바빈즈 패턴의 장점만 취했다. 
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    // Builder -&gt; 정적 맴버 클래스
    public static class Builder {
        // 필수 매개변수
        private final int servingSize;
        private final int servings;

        // 선택 매개변수 - 기본값으로 초기화한다.
        private int calories      = 0;
        private int fat           = 0;
        private int sodium        = 0;
        private int carbohydrate  = 0;

        // 1번 과정 : 필수 매개변수만으로 생성자를 호출
        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings    = servings;
        }

        // 2번 과정 : Setter 메서드들로 원하는 선택 매개변수들을 설정
        public Builder calories(int val)
        { calories = val;      return this; }
        public Builder fat(int val)
        { fat = val;           return this; }
        public Builder sodium(int val)
        { sodium = val;        return this; }
        public Builder carbohydrate(int val)
        { carbohydrate = val;  return this; }

        // 3번 과정 : 매개변수가 없는 build 메서드를 호출
        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
        .calories(100).sodium(35).carbohydrate(27).build();
    }
}</code></pre>
<p>위 Builder의 setter 메서드들은 builder 자신을 반환(<code>return this</code>)하기 때문에 연쇄적으로 호출이 가능한 것입니다. </p>
<blockquote>
<p>이러한 방식을 메서드 호출이 흐르듯 연결된다는 뜻으로 <code>플루언트 API(fluent API)</code> or <code>메서드 연쇄(method chaining)</code>이라고 합니다. </p>
</blockquote>
<h3 id="장점">장점</h3>
<p>이 빌더 패턴을 사용한 클라이언트 코드는 </p>
<blockquote>
<p>쓰기 쉽고, 읽기 쉽다.
빌더 하나로 여러 객체를 순회하면서 만들 수 있다.
빌더에 넘기는 매개변수에 따라 다른 객체를 만들 수 있다.</p>
</blockquote>
<p>는 확실한 장점이 존재합니다. 또한, 객체마다 부여되는 일렬번호와 같은 특정 필드는 빌더가 알아서 채우도록 할 수도 있겠죠. </p>
<h3 id="단점">단점</h3>
<p>하지만, 빌더 패턴에 장점만 있는 것은 아닙니다. 
객체를 만들기 위해 그에 앞서 빌더부터 만들어야 합니다. 빌더 생성 비용이 크지는 않지만 성능에 민감한 상황에서는 문제가 될 수도 있겠죠. </p>
<h1 id="💡정리">💡정리</h1>
<blockquote>
<p>생성자나 정적 팩토리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는 것이 낫다. 특히 매개변수 중 다수가 필수가 아니거나 같은 타입이면 더욱 그렇다. 빌더 패턴은 점층적 생성자 패턴보다 <strong>코드를 읽고 쓰기가 훨씬 간결</strong>하고, 자바빈즈 패턴보다 <strong>훨씬 안전</strong>하다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Socket Programming] 주소체계와 데이터 정렬]]></title>
            <link>https://velog.io/@easy_on7/Socket-Programming-%EC%A3%BC%EC%86%8C%EC%B2%B4%EA%B3%84%EC%99%80-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@easy_on7/Socket-Programming-%EC%A3%BC%EC%86%8C%EC%B2%B4%EA%B3%84%EC%99%80-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Sat, 23 Sep 2023 07:26:50 GMT</pubDate>
            <description><![CDATA[<h2 id="소켓에-할당되는-ip주소와-port번호">소켓에 할당되는 IP주소와 PORT번호</h2>
<h3 id="인터넷-주소internet-address">인터넷 주소(Internet Address)</h3>
<p>인터넷 상에서 컴퓨터를 구분하는 목적으로 사용되는 주소를 말한다. <strong>4바이트 주소체계인 IPv4</strong>와 <strong>16바이트 주소체계인 IPv6</strong>가 존재한다. 소켓을 생성할 때 기본적인 프로토콜을 지정해야 한다. </p>
<p>인터넷 주소는 <strong>네트워크 주소</strong>와 <strong>호스트 주소</strong>로 나뉘게 되는데, 네트워크 주소를 이용해 네트워크를 찾고, 호스트 주소를 이용해 호스트를 구분한다.</p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/d866cc81-9c93-42a4-8dd5-17955cccfe95/image.png" alt=""></p>
<h3 id="port-번호">PORT 번호</h3>
<p>IP는 컴퓨터에 부여하는 값으로 컴퓨터를 구분하는 용도로 사용되고, PORT번호는 소켓에 부여하는 값으로 <strong>소켓을 구분하는 용도</strong>로 사용된다. </p>
<p>하나의 프로그램 내에서는 둘 이상의 소켓이 존재할 수 있기 때문에, 둘 이상의 PORT가 하나의 프로그램에 의해 할당될 수 있다.</p>
<p>0~1023은 Well-known PORT로 Server가 사용하며, 이미 용도가 결정되어 있다.</p>
<p>Client는 큰 Port번호를 사용한다. → Random Number</p>
<h2 id="주소정보의-표현">주소정보의 표현</h2>
<h3 id="ipv4-기반의-주소표현을-구한-구조체">IPv4 기반의 주소표현을 구한 구조체</h3>
<p>IP주소와 PORT번호는 <strong>구조체 sockaddr_in</strong>의 변수에 담아서 표현한다.</p>
<pre><code class="language-c">struct sockaddr_in{
    sa_family_t     sin_family; //주소 체계
    uint16_t        sin_port;   //PORT 번호
    struct in_addr  sin_addr;   //32비트 IP 주소
    char            sin_zero[8];//사용되지 않음
};</code></pre>
<ul>
<li><strong><code>sin_family</code></strong> : 주소체계 정보를 저장<ul>
<li>AF_INET : IPv4</li>
<li>AF_INET6 : IPv6</li>
<li>AF_LOCAL : 로컬 통신을 위한 unix protocol</li>
</ul>
</li>
<li><strong><code>sin_port</code></strong> : 16비트 PORT번호 저장</li>
<li><strong><code>sin_addr</code></strong> : 32비트 IP주소정보 저장</li>
<li><strong><code>sin_zero</code></strong> : 특별한 의미X, 반드시 0으로 채워야 한다. <strong><em>구조체 sockaddr_in의 크기를 구조체 sockaddr와 일치시키기 위해 필요</em></strong>하다.</li>
</ul>
<pre><code class="language-c">struct sockaddr{
    sa_family_t sin_family;  //주소체계(Address family)
    char        sa_data[14]; //주소 정보
};</code></pre>
<blockquote>
<p>💡 구조체 sockaddr은 다양한 주소체계의 주소정보를 담을 수 있도록 정의되었다. 이로 인해, IPv4의 주소정보를 담기가 불편하다. 그래서 동일한 바이트 열을 구성하는 구조체 sockaddr_in이 정의되었으며, 이를 이용해 쉽게 IPv4의 주소정보를 담을 수 있다.</p>
</blockquote>
<pre><code class="language-c">struct sockaddr_in serv_addr;
...
if(bind(serv_sock, **(struct sockaddr*)**&amp;serv_addr, sizeof(serv_addr)) == -1)
    perror(&quot;bind() error&quot;);
...</code></pre>
<p>구조체 변수 sockaddr_in은 bind함수의 인자로 전달되는데, 매개변수 형이 sockaddr이기 때문에, 형 변환(casting)이 필요하다.</p>
<h2 id="네트워크-바이트-순서와-인터넷-주소-변환">네트워크 바이트 순서와 인터넷 주소 변환</h2>
<p>CPU에 따라 상위 바이트를 하위 메모리 주소에 저장하기도 하고, 상위 바이트를 상위 메모리 주소에 저장하기도 한다. → CPU마다 데이터를 표현 및 해석하는 방식이 다르다.</p>
<p>Big Endian(빅 엔디안) → 상위 바이트의 값을 작은 번지수에 저장</p>
<table>
<thead>
<tr>
<th>0x20번지</th>
<th>0x21번지</th>
<th>0x22번지</th>
<th>0x23번지</th>
</tr>
</thead>
<tbody><tr>
<td>0x12</td>
<td>0x34</td>
<td>0x56</td>
<td>0x78</td>
</tr>
</tbody></table>
<p>→정수 0x12345678</p>
<p>Little Endian(리틀 엔디안) → 상위 바이트의 값을 큰 번지수에 저장</p>
<table>
<thead>
<tr>
<th>0x20번지</th>
<th>0x21번지</th>
<th>0x22번지</th>
<th>0x23번지</th>
</tr>
</thead>
<tbody><tr>
<td>0x78</td>
<td>0x56</td>
<td>0x34</td>
<td>0x12</td>
</tr>
</tbody></table>
<p>→정수 0x12345678</p>
<p>호스트 바이트 순서 : CPU별 데이터 저장방식을 의미한다.</p>
<p>네트워크 바이트 순서 : 통일된 데이터 송수신 기준을 의미한다. <strong>Big Endian 기준</strong>이다.</p>
<h3 id="바이트-순서의-변환">바이트 순서의 변환</h3>
<pre><code class="language-c">unsigned short htons(unsigned short); //short = 2 byte
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);   //long = 4 byte
unsigned long ntohl(unsigned long);</code></pre>
<p>htons</p>
<ul>
<li><code>h</code> = 호스트 바이트 순서</li>
<li><code>n</code> = 네트워크 바이트 순서</li>
<li><code>s</code> = 자료형 short</li>
<li><code>l</code>  = 자료형 long</li>
</ul>
<h2 id="인터넷-주소의-초기화와-할당">인터넷 주소의 초기화와 할당</h2>
<h3 id="문자열-정보를-네트워크-바이트-순서의-정수로-변환">문자열 정보를 네트워크 바이트 순서의 정수로 변환</h3>
<pre><code class="language-c">#include &lt;arpa/inet.h&gt;
in_addr_t **inet_addr**(const char* string);
//성공 시 big endian으로 변환된 32비트 정수 값, 실패 시 INADDR_NONE return</code></pre>
<p>→ “211.214.107.99”와 같이 점이 찍힌 10진수로 표현된 문자열을 전달하면, 해당 문자열 정보를 참조해 <strong>IP주소정보를 32비트 정수형</strong>으로 반환한다.</p>
<p>1byte는 0~255의 범위를 가지고 있다. 따라서 범위를 벗어나는(e.g. “... .256”) 문자열이 오면 Error발생</p>
<pre><code class="language-c">e.g.
char *addr1 = &quot;1.2.3.4&quot;;
unsigned long conv_addr = inet_addr(addr1);
//0x4030201(네트워크 바이트 순서로 정렬)과 같이 32비트의 정수 형태로 IP주소를 변환한다. </code></pre>
<h3 id="inet_aton">inet_aton</h3>
<p>ASCII를 숫자로 변환한다.</p>
<pre><code class="language-c">#include &lt;arpa/inet.h&gt;
int inet_aton(const char* string, struct in_addr* addr);
//성공 시 1, 실패 시 0 return</code></pre>
<ul>
<li><code>string</code> : 변환할 IP주소 정보를 담고 있는 문자열의 주소 값</li>
<li><code>addr</code> : 변환된 정보를 저장할 in_addr 구조체 변수의 주소 값</li>
</ul>
<h3 id="inet_ntoa">inet_ntoa</h3>
<p>숫자에서 ASCII로 변환한다.</p>
<pre><code class="language-c">#include &lt;arpa/inet.h&gt;
char* inet_ntoa(struct in_addr adr);
//성공 시 변환된 문자열의 주소 값, 실패 시 -1 return</code></pre>
<h3 id="인터넷-주소의-초기화">인터넷 주소의 초기화</h3>
<p>일반적인 인터넷 주소의 초기화 과정은 다음과 같다.</p>
<pre><code class="language-c">struct sockaddr_in addr;
char *serv_ip = &quot;211.217.168.13&quot;;
char *serv_port = &quot;8080&quot;;

memset(&amp;addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(serv_ip); //문자열 기반의 IP주소 초기화
addr.sin_port = htons(atoi(serv_port));    //문자열 기반의 PORT번호 초기화</code></pre>
<p><strong>INADDR_ANY</strong></p>
<p>현재 실행 중인 컴퓨터의 IP를 소켓에 부여할 때 사용된다. 주로 서버 프로그램의 구현에 사용된다.</p>
<pre><code class="language-c">...
addr.sin_addr.s_addr = htonl(INADDR_ANY);//내가 쓰는 IP 중 아무거나 갖다 써라!
...</code></pre>
<p>→ 이 경우, 소켓의 주소는 INADDR_ANY가 지정되어 소켓의 PORT번호만 인자를 통해 전달하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Effective_Java] Item 1 : 생성자 대신 정적 팩터리 메서드를 고려하라.]]></title>
            <link>https://velog.io/@easy_on7/EffectiveJava-Item-1-%EC%83%9D%EC%84%B1%EC%9E%90-%EB%8C%80%EC%8B%A0-%EC%A0%95%EC%A0%81-%ED%8C%A9%ED%84%B0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9C%EB%A5%BC-%EA%B3%A0%EB%A0%A4%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@easy_on7/EffectiveJava-Item-1-%EC%83%9D%EC%84%B1%EC%9E%90-%EB%8C%80%EC%8B%A0-%EC%A0%95%EC%A0%81-%ED%8C%A9%ED%84%B0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9C%EB%A5%BC-%EA%B3%A0%EB%A0%A4%ED%95%98%EB%9D%BC</guid>
            <pubDate>Sat, 23 Sep 2023 07:19:56 GMT</pubDate>
            <description><![CDATA[<h1 id="🔥들어가며">🔥들어가며...</h1>
<p>개발자라면 혹은 개발자를 꿈꾼다면 한 번 쯤은 들어봤을 그 책! Effective Java를 읽고 공부하며 정리하는 느낌으로 글을 쓰려 합니다. 
어렵다고 소문이 나 있지만, 일단 때려 박아보는게 맞지 않겠습니까 하하😂
날카로운 지적은 언제든 환영입니다 :)</p>
<p>그럼 Item 90까지 달리는 그 날을 향해 시작해봅시다!</p>
<hr>
<h1 id="생성자-대신-정적-팩터리-메서드를-고려하라">생성자 대신 정적 팩터리 메서드를 고려하라.</h1>
<p>클라이언트가 클래스의 인스턴스를 얻는 전통적인 수단은 public 생성자 입니다. 하지만 이와 별도로 <code>정적 팩토리 메서드(Static factory method)</code>를 사용하는 방법이 있습니다. 
들어가기 전에, 과연 정적 팩토리 메서드가 무엇일까요?</p>
<h1 id="정적-팩토리-메서드static-factory-method란">정적 팩토리 메서드(Static Factory Method)란?</h1>
<p>정적 팩토리 메서드는</p>
<blockquote>
<p>해당 클래스의 인스턴스를 반환하는 단순한 정적 메서드</p>
</blockquote>
<p>입니다. 
쉽게 말해서, </p>
<blockquote>
<p>객체 생성의 역할을 하는 클래스 메서드</p>
</blockquote>
<p>인 것이죠.</p>
<p>자바에서 <strong>객체를 생성할 때는 <code>new</code> 키워드를 사용</strong>합니다. 하지만, 메서드를 통해 객체를 만든다고 했을 때, <code>new</code> 키워드를 직접적으로 사용하지 않을 뿐, 정적 팩토리 메서드 내부의 <code>new</code>를 이용해 객체를 생성하고 반환하기 때문에 간접적으로 사용한다고 볼 수 있습니다. </p>
<p>이해를 위해 예시를 하나 들어보겠습니다. 
다음은 java.time 패키지 안에 포함된 LocalTime 클래스의 정적 팩토리 메서드 입니다. </p>
<pre><code class="language-java">// LocalTime.class 내부의 of 클래스
...
public static LocalTime of(int hour, int minute){
  ChronoField.HOUR_OF_DAY.checkValidValue((long)hour);
  if (minute == 0) {
    return HOURS[hour];
  } 
  else {
    ChronoField.MINUTE_OF_HOUR.checkValidValue((long)minute);
    return new LocalTime(hour, minute, 0, 0);
  }
}
...

// 아래와 같이 사용할 수 있다.
LocalTime currentTime = LocalTime.of(7, 30);</code></pre>
<p>위 코드처럼 LocalTime 클래스의 <code>of</code> 메서드처럼 직접적으로 생성자를 통해 객체를 생성하는 것이 아닌 메서드를 통해 객체를 생성하는 것을 <strong>정적 팩토리 메서드</strong>라고 합니다. 또한, 간접적으로 메서드 내부에서 <code>new</code> 키워드를 사용하고 있는 것을 확인할 수 있습니다. <br>
따라서, 클래스는 클라이언트에 <u>public 생성자</u> 대신 <strong>정적 팩토리 메서드</strong>를 제공할 수 있습니다. 이 방식의 장단점에 대해 알아봅시다. </p>
<h1 id="😄-정적-팩토리-메서드의-장점">😄 정적 팩토리 메서드의 장점</h1>
<h2 id="1-이름을-가질-수-있다">1. 이름을 가질 수 있다.</h2>
<p>정적 팩토리 메서드 방식의 첫 번째 장점은 <strong>이름을 가질 수 있다</strong>는 것입니다. </p>
<p>생성자를 사용하게 되면 생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 제대로 설명하기 힘듭니다. 반면, 정적 팩토리 메서드는 이름만 잘 지으면 반환될 객체의 특성을 쉽게 묘사할 수 있습니다. </p>
<p>따라서, 한 클래스에 시그니처가 같은 생성자가 여러 개 필요할 것 같으면, 생성자를 정적 팩토리 메서드로 바꾸고 각각의 차이를 잘 들어내는 이름을 지어줍시다. </p>
<blockquote>
<p>참고) <strong>메서드 시그니처</strong>(Method Signature)란?
메서드에서 메서드 명과 파라미터 리스트(순서, 타입, 개수)를 의미합니다. 이때, return 타입과 exception은 포함되지 않습니다. </p>
</blockquote>
<h2 id="2-호출될-때마다-인스턴스를-새로-생성하지-않아도-된다">2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.</h2>
<p>이 덕분에 <strong>불변 클래스</strong>는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱해 재활용하는 식으로 불필요한 객체 생성을 피할 수 있습니다. 따라서 같은 객체가 자주 요청되는 상황이라면 성능을 상당히 끌어올릴 수 있습니다. </p>
<p>여기서 불변 클래스란, 변경이 불가능한 클래스이며 가변적이지 않은 클래스입니다. 대표적으로 <code>String</code>, <code>Boolean</code>, <code>Integer</code>, <code>Long</code> ...등이 있습니다. 자세한 내용은 여기를 참고하시면 됩니다. </p>
<p>또한, 인스턴스를 통제해 클래스를 싱글톤(singleton)으로 만들 수도, 인스턴스화 불가로 만들 수도 있습니다. </p>
<blockquote>
<p>반복되는 요청에 같은 객체를 반환하는 식으로 정적 팩터리 방식의 클래스는 언제 어느 인스턴스를 살아 있게 할지를 철저히 통제할 수 있는데, 이런 클래스를 <strong>인스턴스 통제(Instance-controlled) 클래스</strong>라고 합니다. </p>
</blockquote>
<h2 id="3-반환-타입의-하위-타입-객체를-반환할-수-있는-능력이-있다">3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.</h2>
<p>이 능력으로 반환할 객체의 클래스를 자유롭게 선택할 수 있습니다. 특히, API를 만들 때 이 유연성을 응용해 구현 클래스를 공개하지 않고도 그 객체를 반환할 수 있어 API를 작게 유지할 수 있습니다.</p>
<p><strong>인터페이스를 사용해 하위타입 객체를 반환</strong>하는 것이죠.</p>
<h2 id="4-입력-매개변수에-따라-매번-다른-클래스의-객체를-반환할-수-있다">4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.</h2>
<p>반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관이 없습니다. </p>
<p>하나의 예시로, <code>EnumSet</code> 클래스의 경우 public 생성자 없이 오직 정적 팩토리만 제공합니다. 만약 원소가 64개 이하라면 원소들을 long 변수 하나로 관리하는 <code>RegularEnumSet</code>의 인스턴스를, 65개 이상이면 long 배열로 관리하는 <code>JumboEnumSet</code>의 인스턴스를 반환합니다. 클라이언트는 팩토리가 건네주는 객체가 어느 클래스의 인스턴스인지 알 수도 알 필요도 없습니다. 그저 EnumSet의 하위 클래스이기만 하면 되는 것이죠.</p>
<h2 id="5-정적-팩토리-메서드를-작성하는-시점에는-반환할-객체의-클래스가-존재하지-않아도-된다">5. 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.</h2>
<p>이러한 유연함은 서비스 제공자 프레임워크(Service provider framework)를 만드는 근간이 됩니다. </p>
<p>대표적인 서비스 제공자 프레임워크로는 JDBC가 있습니다.</p>
<h1 id="😢-정적-팩토리-메서드의-단점">😢 정적 팩토리 메서드의 단점</h1>
<p>이제 단점을 알아 봅시다. </p>
<h2 id="1-상속을-위해선-public이나-protected-생성자가-필요해-정적-팩토리-메서드만-제공하면-하위-클래스를-만들-수-없다">1. 상속을 위해선 public이나 protected 생성자가 필요해 정적 팩토리 메서드만 제공하면 하위 클래스를 만들 수 없다.</h2>
<p>컬렉션 프레임워크의 유틸리티 구현 클래스는 private 생성자만 제공해 상속이 불가능합니다. 하지만 이러한 제약은 상속보다 컴포지션을 사용하도록 유도되어 오히려 장점으로 작용하기도 합니다. </p>
<h2 id="2-정적-팩토리-메서드는-프로그래머가-찾기-어렵다">2. 정적 팩토리 메서드는 프로그래머가 찾기 어렵다.</h2>
<p>생성자처럼 API 설명에 명확히 드러나지 않으니 사용자는 정적 팩토리 메서드 방식 클래스를 인스턴스화할 방법을 찾아야 합니다. </p>
<p>따라서, 개발자는 메서드 이름을 널리 알려진 규약을 따라 짓는 식으로 문제를 완화합니다. 다음은 정적 팩토리 메서드에 흔히 사용하는 명명 방식들입니다. </p>
<h3 id="정적-팩토리-메서드-명명-방식">정적 팩토리 메서드 명명 방식</h3>
<ul>
<li><p><strong><code>from</code></strong> : 매개변수를 하나 받아 해당 타입의 인스턴스를 반환하는 형변환 메서드 
  ex) <code>Date d = Date.from(instant);</code></p>
</li>
<li><p><strong><code>of</code></strong> : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
  ex) <code>Set&lt;Rank&gt; faceCards = EnumSet.of(JACK, QUEEN, KING);</code></p>
</li>
<li><p><strong><code>valueOf</code></strong> : <code>from</code>과 <code>of</code>의 더 자세한 버전
  ex) <code>BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);</code></p>
</li>
<li><p><strong><code>instance</code></strong> or <strong><code>getInstance</code></strong> : 매개변수로 명시한 인스턴스를 반환. 하지만, 같은 인스턴스임을 보장하지는 않는다. 
  ex) <code>StackWalker luke = StackWalker.getInstance(options);</code></p>
</li>
<li><p><strong><code>create</code></strong> or <strong><code>newInstance</code></strong> : <code>instance</code>와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장
  ex) <code>Object newArray = Array.newInstance(classObject, arrayLen);</code></p>
</li>
<li><p><strong><code>getType</code></strong> : <code>getInstance</code>와 같지만, 생성할 클래스가 아닌 다른 클래스에 메서드를 정의할 때 사용. 여기서 &quot;Type&quot; 부분은 팩터리 메서드가 반환할 객체의 타입이다. 
  ex) <code>FileStore fs = Files.getFileStore(path);</code></p>
</li>
<li><p><strong><code>newType</code></strong> : <code>newInstance</code>와 같지만, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용. 여기서 &quot;Type&quot; 부분은 팩터리 메서드가 반환할 객체의 타입이다. 
  ex) <code>BufferedReader br = Files.newBufferedReader(path);</code></p>
</li>
<li><p><strong><code>type</code></strong> : <code>getType</code>과 <code>newType</code>의 간결한 버전
  ex) <code>List&lt;Complaint&gt; litany = Collections.list(legacyLitany);</code></p>
</li>
</ul>
<h1 id="🗒️-정리">🗒️ 정리</h1>
<blockquote>
<p>정적 팩토리 메서드와 public 생성자는 각자의 쓰임새가 있으니 상대적인 장단점을 이해하고 사용하자! 그렇다 하더라도 정적 팩토리 메서드를 사용하는 게 유리한 경우가 더 많으므로 무작정 public 생성자를 제공하던 습관이 있다면 이번 기회에 고쳐보자!!</p>
</blockquote>
<hr>
<h3 id="reference">Reference</h3>
<ul>
<li><a href="https://tecoble.techcourse.co.kr/post/2020-05-26-static-factory-method/">https://tecoble.techcourse.co.kr/post/2020-05-26-static-factory-method/</a></li>
<li><a href="https://velog.io/@cjh8746/%EC%A0%95%EC%A0%81-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9CStatic-Factory-Method">https://velog.io/@cjh8746/%EC%A0%95%EC%A0%81-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9CStatic-Factory-Method</a></li>
<li><a href="https://ildann.tistory.com/7">https://ildann.tistory.com/7</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CI/CD] Jenkins + Docker를 이용한 GCP 환경 Springboot 애플리케이션 배포 자동화]]></title>
            <link>https://velog.io/@easy_on7/CICD-Jenkins-Docker%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-GCP-%ED%99%98%EA%B2%BD-Springboot-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94</link>
            <guid>https://velog.io/@easy_on7/CICD-Jenkins-Docker%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-GCP-%ED%99%98%EA%B2%BD-Springboot-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94</guid>
            <pubDate>Mon, 11 Sep 2023 09:59:39 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가며">들어가며</h1>
<p>새로운 프로젝트를 진행하며 서버 배포 자동화를 위해 Jenkins를 도입하기로 결정했습니다. 구글링하며 시도하면 간단할 줄 알았지만, 정말 고생을 많이 했습니다 ㅠㅠ,, 몇 날 며칠을 밤을 새우며 결국 완성해냈고, 그 과정을 공유하고자 합니다! 다른 분들은 부디 편히 완성하시길 바라며, 들어가보겠습니다 :)</p>
<hr>
<h1 id="왜-jenkins를-사용했을까">왜 Jenkins를 사용했을까?</h1>
<p>이전 다수의 프로젝트 서버 배포는 로컬에서 개발한 springboot 프로젝트를 수동으로 빌드하는 방식을 사용했었습니다. 따라서, 프로젝트에서 수정사항이 생길 때마다 매번 재빌드해 배포해야하는 번거로움이 있었습니다. 이는 정말 불편하게 느껴집니다. 따라서, 배포 자동화에 관심이 생겨 공부를 진행하다 CI/CD 를 접하게 되었고, 이번 프로젝트에 적용하기로 결정했습니다. <del>사실 한 번 써보고 싶었던게 제일 클지도...</del></p>
<h1 id="프로젝트-구조">프로젝트 구조</h1>
<p>저희 프로젝트의 서버 아키텍처는 다음과 같이 배포 자동화 시스템을 구축했습니다.</p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/bb820d87-69d3-450b-8b42-6258aaa385ee/image.png" alt=""></p>
<p>Jenkins와 Docker 를 이용해 배포가 자동화되는 과정을 간단히 살펴보자면, 다음과 같습니다. </p>
<ol>
<li>로컬에서 작업한 내용을 Jenkins와 연동된 Github 레포지토리에 push 합니다. </li>
<li>push된 내용은 Webhook을 통해 Jenkins에 전달되고 Jenkins 서버에서 Gradle을 통해 Build를 실행합니다.</li>
<li>gradlew build 를 통해 Jar 파일이 생성되고 이를 기반으로 도커 이미지(Docker Image)를 build 합니다. </li>
<li>build된 도커 이미지가 개인 DockerHub에 push 됩니다. </li>
<li>Springboot 프로젝트를 배포할 인스턴스(서버)에서 DockerHub에 올라간 도커 이미지를 pull 받습니다. </li>
<li>pull 받은 도커 이미지를 기반으로 도커 컨테이너를 실행시킵니다.</li>
</ol>
<h1 id="📖-dockerfile-작성">📖 Dockerfile 작성</h1>
<p>우선, 개발을 진행할 Springboot 프로젝트를 생성해 <code>Dockerfile</code>을 생성합니다. 이를 기반으로 이미지 빌드를 하고, 실행하게 됩니다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/80fb30d5-6057-4bc0-b4d0-8c8afbd03797/image.png" alt=""></p>
<p>파일 이름은 <strong>반드시</strong> <code>Dockerfile</code>이어야 합니다. DockerFile, dockerfile 등 다른 이름이면 인식이 안됩니다. </p>
<p><code>Dockerfile</code>의 내용은 다음과 같이 작성합니다. </p>
<pre><code class="language-Dockerfile">FROM openjdk:11
LABEL authors=&quot;USER_NAME&quot;
ARG JAR_FILE=build/libs/jenkins-0.0.1-SNAPSHOT.jar
ADD ${JAR_FILE} docker-springboot.jar
ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;/docker-springboot.jar&quot;, &quot;&gt;&quot;, &quot;app.log&quot;]</code></pre>
<p>Dockerfile 내용에 대해서는 여기서 자세히 다루지 않겠습니다. 자세히 알고 싶다면 아래 블로그를 참고하면 좋을 것 같습니다:)
<a href="https://toramko.tistory.com/entry/docker-%EB%8F%84%EC%BB%A4%ED%8C%8C%EC%9D%BCDockerfile-%EC%9D%98-%EA%B0%9C%EB%85%90-%EC%9E%91%EC%84%B1-%EB%B0%A9%EB%B2%95%EB%AC%B8%EB%B2%95-%EC%9E%91%EC%84%B1-%EC%98%88%EC%8B%9C">도커파일 작성법</a></p>
<p>하나 주의할 점은 <code>ARG</code>의 jar 파일에서 jenkins-0.0.1-SNAPSHOT의 jenkins 부분에 본인의 프로젝트 이름을 넣어주시면 됩니다. </p>
<h1 id="🛠️-jenkins-서버-환경-구축">🛠️ Jenkins 서버 환경 구축</h1>
<p>저는 GCP 환경에서 배포를 진행하기 때문에 GCP를 기준으로 설명하겠습니다. 우선 Jenkins 서버를 위한 인스턴스를 하나 생성하고 Docker를 통해 Jenkins를 빌드하겠습니다. 
<br>
GCP → Compute Engine → VM 인스턴스로 들어가 상단의 <code>인스턴스 만들기</code>로 Jenkins를 위한 인스턴스를 생성합시다. 저는 부팅 디스크의 운영체제를 Ubuntu로 선택했습니다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/12b73b51-f621-45ba-9130-5f4279756aba/image.png" alt=""></p>
<p>또한, 방화벽 탭에서 HTTP, HTTPS 트래픽을 모두 허용시켜 줍니다. 
생성하고, 접속해봅시다!
<img src="https://velog.velcdn.com/images/easy_on7/post/af8507cc-face-4640-897c-0d719db28263/image.png" alt=""></p>
<h2 id="docker-설치하기">Docker 설치하기</h2>
<p>Jenkins를 Docker를 통해 실행하기 위해, 우선 Docker를 설치해줍시다. 
저는 Ubuntu 환경에서 진행했기 때문에 아래와 같이 설치하겠습니다. 
다른 운영체제를 사용 중이라면, 아래 docker docs에서 자신에게 맞는 운영체제를 선택해 따라 설치해주면 됩니다. 
<a href="https://docs.docker.com/engine/install/">Docker docs</a></p>
<pre><code class="language-shell"># Uninstall all conflicting packages
for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do sudo apt-get remove $pkg; done

# Add Docker&#39;s official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add the repository to Apt sources:
echo \
  &quot;deb [arch=&quot;$(dpkg --print-architecture)&quot; signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  &quot;$(. /etc/os-release &amp;&amp; echo &quot;$VERSION_CODENAME&quot;)&quot; stable&quot; | \
  sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null
sudo apt-get update

# Install the latest version
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin</code></pre>
<p>도커 설치를 마치고 도커 명령어를 실행했을 때,</p>
<pre><code>permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get &quot;http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json&quot;: dial unix /var/run/docker.sock: connect: permission denied</code></pre><p>이와 같은 오류가 발생한다면 도커 그룹에 USER를 추가해줍시다. </p>
<pre><code class="language-shell">sudo usermod -aG docker USERNAME</code></pre>
<p>USERNAME 부분에 현재 사용자의 이름을 넣고 실행하면 됩니다. 
재접속 후, <code>docker ps</code> 명령어를 사용하면 다음과 같은 결과가 나올 것입니다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/19e1a39c-51bf-4fb6-a73a-0309977257eb/image.png" alt=""></p>
<p>도커 설치를 마쳤습니다. </p>
<h2 id="jenkins-image-pull--run">Jenkins Image Pull + Run</h2>
<p>도커 설치를 마쳤다면, Jenkins 이미지를 도커허브로부터 내려 받고, 해당 이미지를 컨테이너로 실행시켜 봅시다. </p>
<pre><code>docker pull jenkins/jenkins:lts
docker run --privileged -d -p 8080:8080 -p 50000:50000 --name jenkins jenkins/jenkins:lts</code></pre><p>도커 컨테이너는 기본적으로 Unprivileged 모드로 실행되어, 시스템 주요 자원에 접근할 수 있는 권한이 부족합니다. 따라서 <code>--privileged</code> 옵션을 사용해 Privileged 모드로 실행하겠습니다. 자세한 내용은 아래 링크에서 확인하실 수 있습니다. 
<a href="https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities">Docker docs - Runtime privilege</a></p>
<p>또한, Jenkins의 기본 포트인 8080으로 접속하기 위해 컨테이너 포트를 8080으로 실행시켜줍니다. </p>
<p>도커 컨테이너를 실행시켰다면, <code>docker ps</code> 명령어를 통해 jenkins가 실행 중인 것을 확인할 수 있습니다. </p>
<h2 id="jenkins-접속">Jenkins 접속</h2>
<p>컨테이너가 실행되었다면, <code>http://외부IP주소:8080</code>으로 Jenkins에 접속해줍시다. </p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/28ecc2e7-86d3-4d4f-a92f-99a54135dd4e/image.png" alt=""></p>
<p>위 사진과 같이 초기 비밀번호를 입력하는 창이 나올 것입니다. 이를 확인하기 위해 다시 GCP shell 화면으로 돌아가서 Jenkins 컨테이너에 접속해봅시다. 아래 명령어를 통해 컨테이너에 접속합니다. </p>
<pre><code>docker exec -it jenkins /bin/bash</code></pre><p>컨테이너에 접속했다면, docker container 내부 쉘에서 다음과 같이 초기 비밀번호를 확인해줍니다. </p>
<pre><code>cat /var/jenkins_home/secrets/initialAdminPassword</code></pre><p>내용을 복사해 넣어주고, Continue를 선택합니다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/e0a1d2c1-c4ef-4bd1-ac17-428fa5c20289/image.png" alt="">
그럼 위와 같이 어떤 방식으로 플러그인을 설치할 것인지 선택하는 창이 나옵니다. Install suggested plugins을 선택해 필수적인 플러그인을 모두 설치받도록 합시다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/53ddc2f2-97d9-43fd-83b7-494ce033ec6f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/99902c6a-fd26-4e6b-af89-fa317511973a/image.png" alt=""></p>
<p>설치가 완료되면, Admin 계정을 생성하는 페이지가 나오게 됩니다. 여기서 설정한 계정을 통해 앞으로 Jenkins 에 접속하게 될 것입니다. 계정을 생성하고 Save and Continue를 선택하면 기본 URL을 설정하는 화면이 나오는데 <code>외부IP주소:8080</code>으로 설정되어 있을 것입니다. 앞으로 이 URL을 통해 Jenkins를 접속하게 될 것입니다. 변경하지 말고 Sava and Finish를 누르면 Jenkins 메인페이지가 나오게 됩니다. </p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/5d394db7-16e7-4238-89de-8e92cfb1d9c0/image.png" alt="">
여기까지 Jenkins를 설치하고 접속까지 완료했습니다. </p>
<h1 id="🛠️-ci-구축-github--jenkins-연동">🛠️ CI 구축: Github + Jenkins 연동</h1>
<p>젠킨스 메인페이지에서 <code>✚ 새로운 Item</code>을 선택하고 item 이름을 설정하고 <code>Freestyle Project</code>를 생성합니다. 
<em>item 이름에 공백을 넣지 맙시다. 이것 때문에 꽤나 고생했습니다..ㅠㅠ</em>
<img src="https://velog.velcdn.com/images/easy_on7/post/984312ca-0dd2-47ca-a6ca-0723fffd8ef6/image.png" alt=""></p>
<p><strong>General</strong> 탭에서 <code>Github project</code>를 체크해 활성화 시켜줍니다. 그리고 연동하고자 하는 Github repository의 URL을 입력합니다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/87a48321-1f82-405b-89dd-8700a717da2e/image.png" alt=""></p>
<p>다음으로 <strong>소스 코드 관리</strong> 탭에서 <strong>Git</strong>을 선택하고 내용을 입력합니다.
<img src="https://velog.velcdn.com/images/easy_on7/post/39b8aa7a-c70a-4e9d-ac89-340bd560caff/image.png" alt=""></p>
<p>Repository URL은 위와 같은 주소이고, Credentials를 설정해줄껀데, 이는 Jenkins와 Github 사이에 데이터를 주고 받을 때의 인증 방식을 의미합니다. SSH-key 인증 방식을 많이 이용하지만, 테스트를 위해 Github 계정 인증 방식을 사용하겠습니다. 추후에 변경해주면 됩니다!</p>
<p>아래의 <strong>Add</strong>를 클릭하고 <strong>Jenkins</strong>를 선택하면 다음과 같은 창이 뜹니다.
<img src="https://velog.velcdn.com/images/easy_on7/post/d630dcb2-b38f-4ac5-ae1b-44c9392ac2c5/image.png" alt=""></p>
<p>이곳에 자신의 Github 계정을 입력해주면 됩니다. 입력을 마치고 Add를 통해 등록했다면, Credentials이 <code>-none-</code>으로 되어 있는 것을 설정한 계정으로 바꾸어주면 됩니다. </p>
<p>Branches to build에서 Github에 push 가 될 때 build가 실행될 브랜치를 선택할 수 있습니다. 현재 제 repository의 default branch는 main branch 이기 때문에 main으로 선택하겠습니다. </p>
<p>다음으로 <strong>빌드 유발</strong> 탭에서 아래와 같이 체크합니다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/dff3b59a-6fc0-4d34-af9d-30e1cec46d10/image.png" alt=""></p>
<h2 id="build-steps---execute-shell">Build Steps - Execute shell</h2>
<p>그 다음으로 <strong>Build Steps</strong> 탭에서 어떤 방식으로 빌드를 수행할지 설정할 수 있는데 shell을 통한 방법을 선택하겠습니다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/ffe90c5d-d632-48f7-9160-46ee79ab714c/image.png" alt=""></p>
<p>shell 안에 다음과 같이 빌드할 내용을 지정합니다. </p>
<pre><code>chmod +x gradlew
./gradlew clean build</code></pre><p><img src="https://velog.velcdn.com/images/easy_on7/post/c1ff020d-b73a-47f8-9d30-e40ad9fd34ab/image.png" alt=""></p>
<blockquote>
<h3 id="️-주의">‼️ 주의</h3>
<p>현재 제 repository의 폴더 구조는 다음과 같습니다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/60517ed9-f957-4d45-8405-7ba36b67cb5e/image.png" alt="">
상위 폴더 없이 바로 노출되어 있는 구조입니다. 
만약 상위 폴더 안에 프로젝트 파일이 존재한다면, 위 명령어를 수행하기 전에 <code>cd ...</code> 명령어를 통해 프로젝트로 들어간 후 위 명령어를 작성하면 됩니다. 
예시)</p>
</blockquote>
<pre><code>cd project_name
chmod +x gradlew
./gradlew clean build</code></pre><h1 id="🔗-webhook-연동">🔗 Webhook 연동</h1>
<p>다음으로 연동하고자 했던 repository에 접속해 Webhook을 설정합시다. 해당 repository의 <code>Settings → Webhooks</code>로 들어갑니다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/a51b7af2-6ccd-41b7-9005-4b91003b9d13/image.png" alt=""></p>
<p><strong>Add webhook</strong>으로 webhook을 추가합시다. 구성 내용은 아래와 같이 설정합니다. </p>
<ul>
<li>Payload URL = Jenkins IP주소:8080/github-webhook/
  예시) <code>http://123.45.678:8080/github-webhook/</code> (맨 뒤의 /를 꼭 붙여야 합니다!)</li>
<li>Content type : application/json</li>
</ul>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/5d0f38e8-fa3d-4cac-a306-f2f22ea07273/image.png" alt=""></p>
<p>작성을 마쳤다면, Add webhook을 통해 생성합니다. </p>
<blockquote>
<p>만약 앞서 Jenkins 설정 시 Credentials를 Github 로그인 방식이 아닌 SSH-key 방식을 사용했다면, github Deploy 란에서도 shh-key를 등록해야 연동이 가능해집니다. </p>
</blockquote>
<h1 id="🔌-jenkins와-springboot-배포-서버-연동">🔌 Jenkins와 Springboot 배포 서버 연동</h1>
<p>다음으로, springboot 프로젝트가 올라가는 VM 인스턴스(서버)와 연동하는 방법에 대해 알아봅시다! 
간략하게 과정은 다음과 같습니다.</p>
<ul>
<li>Jenkins 서버에서 빌드한 JAR 파일을 통해 생성된 도커 이미지(Docker image)가 DockerHub에 push 되고, 이 이미지를 springboot 서버에서 pull 을 받아 실행시킵니다. </li>
<li>때문에, jenkins 서버와 springboot 서버가 연동되어야 하고 이를 SSH-key 방식을 이용해 진행하겠습니다. </li>
</ul>
<h2 id="ssh-key-생성하기">SSH key 생성하기</h2>
<p>Jenkins 서버와 springboot 서버를 연동하기 위해, PEM 형식의 key를 생성합니다. </p>
<blockquote>
<p>만약 아래 방법으로 연동 시 BapPublisherException와 같은 오류가 발생한다면, 아래 블로그 링크를 통해 해결해보시기 바랍니다. 
간단히 말하자면, OpenSSH 8.8부터 SHA-1 해시 알고리즘을 사용하는 RSA 시그니처를 지원하지 않기로 결정해 ECDSA를 사용해야 한다고 하네요. 
<a href="https://osg.kr/archives/718">Jenkins Publish over SSH 인증시 BapPublisherException 오류</a></p>
</blockquote>
<p>Jenkins 서버에서 아래와 같이 ssh-key를 생성합니다. </p>
<pre><code class="language-shell">ssh-keygen -t rsa -C &quot;key_name&quot; -m PEM -f ~/.ssh/&quot;key_name&quot;

예시) ssh-keygen -t rsa -C &quot;id_rsa&quot; -m PEM -f ~/.ssh/id_rsa</code></pre>
<p>그리고 엔터 두 번을 입력해주시면 ssh-key 생성이 완료됩니다. </p>
<h2 id="springboot-서버-public-key-등록">springboot 서버 public key 등록</h2>
<p>앞서 생성한 key 중 public key를 springboot 배포 서버에 추가하는 작업을 해봅시다. </p>
<p>GCP에서는 아래와 같이 메타데이터에 들어가 SSH 키를 등록해주면 됩니다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/4b863f31-a962-4439-a654-72a97a5d104f/image.png" alt=""></p>
<p>메타데이터 창에 들어가 <strong>SSH키</strong> 탭을 누르고 상단의 <strong>수정</strong> 버튼을 눌러 추가합니다. 
이때, 넣을 key는 public key로, 위에서 생성한 key 중 <code>.pub</code>가 붙은 key를 복사해 추가하면됩니다. 
아래 코드에서 확인할 수 있습니다. </p>
<pre><code class="language-shell">cat ~/.ssh/id_rsa.pub</code></pre>
<blockquote>
<p>만약 위 방법으로 연동이 되지 않는다면, springboot 배포 서버에 접속한 뒤, <code>.ssh</code> 폴더로 들어가면 
<code>authorized_keys</code> 파일이 있을 것입니다. 이곳에 vi를 통해 붙여 넣어주시면 됩니다. 
참고) GCP에서는 메타데이터에 SSH Key를 추가하면 자동으로 <code>authorized_keys</code>파일에 추가가 됩니다. </p>
</blockquote>
<p>참고로, springboot 배포를 위한 VM 인스터스는 위의 Jenkins 서버와 같은 방식으로 생성했습니다. 똑같은 Ubuntu 환경입니다.
<br>
또한, 배포 서버는 권한 문제로 root 환경으로 모든 작업을 수행했습니다. 아래 방법으로 root 계정으로 접속할 수 있습니다. </p>
<pre><code class="language-shell"># root 비밀번호 설정하기
sudo passwd

# root로 접속하기 
su -</code></pre>
<p>springboot 배포 서버에서 DockerHub를 통해 이미지를 실행시켜야하기 때문에 Jenkins 서버와 같은 방식으로 Docker를 설치해 줍시다. </p>
<h2 id="publish-over-ssh-플러그인-사용하기">Publish Over SSH 플러그인 사용하기</h2>
<p>위의 과정을 모두 마쳤다면, Jenkins의 Publish Over SSH 플러그인을 통해 두 인스턴스를 연동해봅시다. </p>
<h3 id="플러그인-설치">플러그인 설치</h3>
<p>우선 플러그인 설치를 위해, Jenkins 메인화면에서 <strong>Jenkins 관리</strong> → System Configuration의 <strong>Plugins</strong> → Available plugins 탭에 들어가 검색창에 &quot;ssh&quot;를 입력하고 <code>Publish Over SSH</code>를 선택해 설치해줍니다. </p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/e5f917cd-5e6f-433b-a026-b08ed6b4dea5/image.png" alt=""></p>
<p>설치했다면, 맨 아래 &quot;설치가 끝나고 실행중인 작업이 없으면 Jenkins 재시작.&quot;를 체크하여 Jenkins를 재시작해줍니다. </p>
<p>제가 여러 번 실행해본 결과, 이를 통해 재시작을 하면 docker로 실행한 jenkins가 종료됩니다. 따라서, Jenkins 서버에 접속해 아래 명령어를 통해 다시 jenkins를 실행시켜 주면 됩니다. </p>
<pre><code class="language-shell">docker start jenkins</code></pre>
<h3 id="플러그인-연동">플러그인 연동</h3>
<p>플러그인 설치를 마쳤다면 <strong>Jenkins 관리</strong> → System Configuration의 <strong>System</strong> 에 들어가 아래의 <strong>Publish over SSH</strong>에서 연동 작업을 진행합시다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/edf899ac-1131-470f-9f73-7cf56fe6b1fb/image.png" alt=""></p>
<p>Key 부분에 앞서 생성한 SSH key의 private key를 넣어주시면 됩니다. </p>
<pre><code>-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----</code></pre><p>이때, 위처럼 모든 부분을 넣어야 합니다. </p>
<p>그리고 바로 아래의 SSH Servers 추가를 선택합니다. 그럼 다음과 같은 창이 뜨게 됩니다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/719fe847-368a-480e-9867-414e7dcb1b81/image.png" alt=""></p>
<ul>
<li>Name : 사용할 서버 이름을 넣어주시면 됩니다. </li>
<li>Hostname : springboot 배포 서버의 내부 IP 주소입니다.</li>
<li>Username : 사용자 이름이 들어가는 부분입니다. 저희는 root에서 작업하기로 했으므로 여기서는 root가 될 것입니다. </li>
</ul>
<p>위의 내용을 모두 작성하고 Test Configuration을 눌렀을 때, 다음과 같이 Success가 나온다면 연동이 완료된 것입니다. </p>
<h1 id="🐳-dinddocker-in-docker--jenkins-컨테이너에-도커-설치--권한-설정">🐳 DinD(Docker in Docker) : Jenkins 컨테이너에 도커 설치 + 권한 설정</h1>
<p>Jenkins에서 도커 이미지를 build 하기 위해, Jenkins 컨테이너 안에 Docker를 설치하는 과정이 필요합니다. 이를 도커 안에 도커를 설치한다고 하여, Docker in Docker 즉, DinD라 합니다.</p>
<blockquote>
<p>도커 측에서는 DinD 방식보다 DooD(Docker out of Docker) 방식을 더 권장하지만, 여기서는 DinD 방식을 사용하겠습니다. </p>
</blockquote>
<p>우선, Jenkins 서버에서 Jenkins 컨테이너를 root로 접속합니다. </p>
<pre><code class="language-shell">docker exec -itu 0 jenkins /bin/bash</code></pre>
<p>그리고 아래 코드를 통해 Docker를 설치해줍시다. </p>
<pre><code class="language-shell"># Docker 설치
## - Old Version Remove
apt-get remove docker docker-engine docker.io containerd runc

## - Setup Repo
apt-get update

apt-get install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

mkdir -p /etc/apt/keyrings

curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg

echo \
  &quot;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
  $(lsb_release -cs) stable&quot; | tee /etc/apt/sources.list.d/docker.list &gt; /dev/null

## - Install Docker Engine
apt-get update

apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin</code></pre>
<p>설치가 완료됐다면, 아래 명령어로 도커데몬을 실행시켜주면 됩니다. </p>
<pre><code class="language-shell">service docker start</code></pre>
<h2 id="jenkins--docker-그룹-추가">Jenkins &amp; Docker 그룹 추가</h2>
<h3 id="1-docker-그룹에-root-계정-추가">1. Docker 그룹에 root 계정 추가</h3>
<pre><code class="language-shell">usermod -aG docker root
su - root</code></pre>
<h3 id="2-추가되었는지-확인">2. 추가되었는지 확인</h3>
<pre><code class="language-shell">id -nG
# &quot;root docker&quot;가 뜨면 정상</code></pre>
<h3 id="3-dockersock-권한-변경">3. docker.sock 권한 변경</h3>
<pre><code class="language-shell">chmod 666 /var/run/docker.sock</code></pre>
<h3 id="4-root-에서-docker-로그인">4. root 에서 Docker 로그인</h3>
<p>Jenkins 에서 DockerHub에 빌드된 도커 이미지를 push 할 수 있도록, root로 접속해 도커 로그인을 합시다. 이때, 아이디와 비밀번호는 DockerHub의 아이디와 비밀번호를 입력하면 됩니다. </p>
<pre><code class="language-shell">su - root
docker login</code></pre>
<p>결과로, &quot;Login Succeeded&quot;가 뜨면 정상적으로 로그인이 된 것입니다. </p>
<h1 id="💡-docker를-활용해-jenkins-서버에서-springboot-서버로-배포-자동화하기">💡 Docker를 활용해 Jenkins 서버에서 Springboot 서버로 배포 자동화하기</h1>
<p>드디어 마지막 단계입니다! 
Jenkins 메인 화면에서 생성했던, Item(Freestyle Project)에 들어갑니다. 접속한 뒤, 왼쪽의 <strong>구성</strong> 탭을 클릭합니다. </p>
<p>이전에 작성해두었던 Execute shell 아래에 Add build step을 선택하고, <strong>Execute shell</strong>을 하나 더 추가한 뒤, 아래 코드를 작성합시다. </p>
<pre><code class="language-shell">docker login -u &#39;도커허브아아디&#39; -p &#39;도커허브비번&#39; docker.io
docker build -t [dockerHub UserName]/[dockerHub Repository] .
docker push [dockerHub UserName]/[dockerHub Repository]</code></pre>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/07ecd891-0e11-4477-847b-ef87d47fa87f/image.png" alt=""></p>
<blockquote>
<p>build 명령어 수행 시, 마지막에 &quot;.&quot;을 꼭 붙여주셔야 합니다!</p>
</blockquote>
<p>이 과정을 수행하면 Jenkins에서 Docker image를 만들어 DockerHub에 push 하는 과정이 끝이 난 것입니다. 
<br>
이제 springboot 배포 서버에서 이를 pull 받아 실행시키는 작업을 해봅시다. </p>
<p>그 바로 아래, <strong>빌드 후 조치</strong> 탭에서 <strong>Send build artifacts over SSH</strong>를 선택해줍니다. 그리고 다음과 같이 작성합시다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/5867dd2a-74b6-4729-a918-ce08596f9556/image.png" alt=""></p>
<p>모든 작성이 완료되었다면 저장하고 <strong>지금 빌드</strong> 탭을 선택해 빌드를 시작해봅시다!</p>
<h1 id="🤣-build-및-배포-테스트">🤣 Build 및 배포 테스트</h1>
<p>왼쪽의 빌드 결과에 들어가 <strong>Console Output</strong> 을 선택해 콘솔 결과를 확인해보면,
<img src="https://velog.velcdn.com/images/easy_on7/post/28833989-4399-4193-a71c-4b4329112706/image.png" alt=""></p>
<p>빌드가 성공적으로 수행된 것을 확인할 수 있고 
<img src="https://velog.velcdn.com/images/easy_on7/post/2a5898ad-2222-40a5-bc6d-c37c15b65a0f/image.png" alt=""></p>
<p>결과적으로 모든 과정이 성공적으로 진행된 것을 확인할 수 있습니다!</p>
<p>배포를 테스트하기 위해, springboot 프로젝트에 다음과 같은 클래스를 만들어 main branch에 push를 진행해 보겠습니다. </p>
<pre><code class="language-java">import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(&quot;/api/test&quot;)
public class TestController {

    @GetMapping(&quot;/hello&quot;)
    public HelloResponse getHello(){
        return new HelloResponse(1L, &quot;Hello World New Version&quot;);
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    static class HelloResponse{
        private Long id;
        private String content;
    }
}</code></pre>
<p>이제 springboot 배포 서버의 외부IP주소를 이용해, <code>http://외부IP주소:8080/api/test/hello</code>에 접속해보면!!</p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/8c64b88c-4b88-4fb5-98b6-daa1d9e02932/image.png" alt=""></p>
<p>다음과 같이 정상적으로 결과가 뜨는 것을 확인할 수 있습니다..!</p>
<h1 id="마치며">마치며..</h1>
<p>지금까지 Jenkins + Docker를 이용한 CI/CD 구축에 대해 알아보았습니다. </p>
<p>프로젝트를 수행하며 Jenkins를 사용해보고 싶다는 마음에 무턱대고 시도했지만, 역시나 한 번에 성공하는 것은 없더라구요. 수많은 오류를 만나고(발생할 수 있는 오류란 오류는 모두 만났던 것 같네요...) 수많은 구글링을 거치며 좌절도 했지만, 결국 해냈을 때의 쾌감은 ㅠㅠ.. 말로 설명할 수 없었습니다. 
<br>
혹시 구글링 도중 이 포스트를 발견해 참고하시는 분들은 부디 무탈히 구축하셨으면 하는 바람입니다. 궁금한 점이나 잘못된 부분이 있다면 언제든 말씀해주세요. 감사합니다 :)</p>
<h3 id="참고">참고</h3>
<p><a href="https://docs.docker.com/engine/install/">Docker 설치</a>
<a href="https://taewooblog.tistory.com/entry/docker-push-%EC%97%90%EB%9F%AC-An-image-does-not-exist-locally-with-the-tag-1013cmcoupangapi">Docker push 에러</a>
<a href="https://genie247.tistory.com/entry/jenkins-server-%EC%97%B0%EB%8F%99-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0">Jenkins 서버 연동 오류</a>
<a href="https://velog.io/@msung99/CICD-Jenkins-Docker-%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-SpringBoot-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94-%EA%B5%AC%EC%B6%95">Jenkins를 이용한 Docker 기반 스프링부트 배포 자동화</a>
<a href="https://dev-gorany.tistory.com/345">[Jenkins] GCP + Docker + Jenkins를 이용한 CI / CD</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java/Spring] ModelMapper]]></title>
            <link>https://velog.io/@easy_on7/JavaSpring-ModelMapper</link>
            <guid>https://velog.io/@easy_on7/JavaSpring-ModelMapper</guid>
            <pubDate>Thu, 10 Aug 2023 06:02:14 GMT</pubDate>
            <description><![CDATA[<p>Spring을 이용한 프로젝트를 진행하던 중에 <code>ModelMapper</code>를 사용하게 되어 이에 대해 한 번 정리해보려 합니다. </p>
<h1 id="modelmapper란">ModelMapper란?</h1>
<p><code>ModelMapper</code>는 </p>
<blockquote>
<p>어떠한 Object에 있는 필드 값들을 원하는 Object에 <strong>자동으로 mapping</strong> 시켜주는 라이브러리이다. 
즉, Source Object → Destination Object 를 자동으로 해준다고 생각하면 된다. </p>
</blockquote>
<p>그렇다면, 어떤 경우에 <code>ModelMapper</code>를 사용할까?</p>
<p>보통 DTO를 통해 데이터를 받아 다른 Object(엔티티 등...)에 넣거나 이와 반대로 객체를 DTO로 변환할 때, getter와 setter를 이용해 원하는 필드들을 일일이 넣는다. 아니면, <code>of</code> 와 <code>from</code>이라는 정적 메서드(static method)를 만들어 사용한다.</p>
<p>이 작업들은 솔직히 귀찮은 일이고, 실수도 발생할 수 있다. 번거로운게 제일 큰 것 같다...</p>
<p>이러한 단점을 해결해줄 수 있는 것이 바로 <code>ModelMapper</code> 라이브러리이다!</p>
<h1 id="modelmapper-설정">ModelMapper 설정</h1>
<h2 id="modelmapper-의존성-추가">ModelMapper 의존성 추가</h2>
<p>우선 <code>ModelMapper</code>를 사용하기 위해 dependency를 추가해주자!</p>
<p>Maven 프로젝트의 경우, _pom.xml_에 다음 dependency를 추가하자.</p>
<pre><code class="language-xml">&lt;dependency&gt;
  &lt;groupId&gt;org.modelmapper&lt;/groupId&gt;
  &lt;artifactId&gt;modelmapper&lt;/artifactId&gt;
  &lt;version&gt;3.0.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
<p>Gradle 프로젝트의 경우, _build.gradle_에 다음 dependency를 추가하자.</p>
<pre><code class="language-gradle">dependencies{
    ...
    implementation &#39;org.modelmapper:modelmapper:3.0.0&#39;
    ...
}</code></pre>
<h2 id="클래스-정의">클래스 정의</h2>
<p>매핑 예시를 설명하기 위해 아래와 같은 클래스들을 선언해주자.</p>
<p><em>Source Model</em></p>
<pre><code class="language-java">// 각 클래스에 Getter와 Setter, Constructor가 구현되어 있다고 가정하자.
class Order {
  Customer customer;
  Address billingAddress;
}

class Customer {
  Name name;
}

class Name {
  String firstName;
  String lastName;
}

class Address {
  String street;
  String city;
}</code></pre>
<p><em>Destination Model</em></p>
<pre><code class="language-java">// Getter와 Setter, Constructor가 구현되어 있다고 가정하자.
class OrderDTO {
  String customerFirstName;
  String customerLastName;
  String billingStreet;
  String billingCity;
}</code></pre>
<h1 id="modelmapper-사용하기">ModelMapper 사용하기</h1>
<p>간단하게 다음과 같이 사용할 수 있다.</p>
<pre><code class="language-java">ModelMapper.map(Object source, Class&lt;D&gt; destinationType)</code></pre>
<p>이를 이용해 <code>order</code> 인스턴스를 <code>orderDTO</code>에 mapping 시켜보자. </p>
<pre><code class="language-java">// Class에 값 넣기
Order order = new Order(
    new Customer(new Name(&quot;LastName&quot;, &quot;FirstName&quot;)),
    new Address(&quot;Street&quot;, &quot;City&quot;)
);

// ModelMapper 선언
ModelMapper mapper = new ModelMapper();

// mapping 하기
OrderDTO orderDto = mapper.map(order, OrderDTO.class);</code></pre>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/fcdbf60e-0a05-4e4a-b3f2-04c0cf5980af/image.png" alt=""></p>
<p>실행해보면, 위와 같이 연관관계를 자동으로 판단해 필드값들이 mapping된 결과를 확인할 수 있다.</p>
<h4 id="어떻게-동작하지">어떻게 동작하지?</h4>
<p>이처럼, <code>ModelMapper</code>에서는 <code>map(source, destination)</code> 메서드가 호출되면 <code>source</code>와 <code>destination</code>의 타입을 분석해 <strong>매칭 전략</strong>이나 <strong>기타 설정값</strong>들에 따라 일치하는 속성을 결정하게 된다. <br>
위의 예시처럼, <code>source</code>와 <code>destination</code>의 객체 타입이나 프로퍼티가 다른 경우에도 <code>ModelMapper</code>는 설정된 매칭 전략에 따라 최선의 mapping 과정을 수행한다. <br>
하지만, <code>ModelMapper</code>는 <code>source</code> 및 <code>destination</code>을 암묵적으로 일치시키기 위해 최선을 다함에도 속성 간의 매핑을 명시적으로 정의해야 하는 경우도 있다.</p>
<p>아래에서 이와 관련된 내용을 좀 더 자세하게 예시를 통해 알아보자.</p>
<hr>
<h1 id="typemap">TypeMap</h1>
<p>아래와 같은 객체 매핑이 필요한 경우를 예시로 들어보자. </p>
<pre><code class="language-java">// Getter와 Setter, Constructor가 구현되어 있다고 가정하자.
class Item {
    private String name;
    private Integer stock;
    private Integer price;
    private Boolean sale;
}
class Bill {
    private String itemName;
    private Integer qty;
    private Integer singlePrice;
    private Double discount;
}</code></pre>
<pre><code class="language-java">Item item = new Item(&quot;item&quot;, 10, 1500, true);
Bill bill = mapper.map(item, Bill.class);</code></pre>
<p>처음의 예제보다 간단해 보이지만, <code>mapper.map()</code>의 결과는 다음과 같다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/86cb3c6a-6e1c-49e6-815a-5aa4ce32f361/image.png" alt=""></p>
<blockquote>
<p>ModelMapper의 기본 매칭 전략으로는 모호한 연관 관계들이 매핑되지 않는다. </p>
</blockquote>
<p>이를 해결하기 위해, <code>TypeMap</code>을 제공한다. </p>
<h2 id="typemaps-d">TypeMap&lt;S, D&gt;</h2>
<blockquote>
<p><code>TypeMap</code> 인터페이스를 구현해 <code>ModelMapper</code> 객체의 매핑 관계를 설정해줄 수 있다. </p>
</blockquote>
<p>사용법은 아래와 같다. </p>
<pre><code class="language-java">TypeMap&lt;BaseScr, BaseDest&gt; typeMap = 
        modelMapper.createTypeMap(BaseScr.class, BaseDest.class)
                .addMapping(BaseScr::getFirstName, BaseDest::setName);
typeMap.include(ScrA.class, DesTA.class);</code></pre>
<p><code>addMapping()</code> 메서드를 이용하여 <code>source</code>의 <code>getter</code>와 <code>destination</code>의 <code>setter</code>를 연결시켜주면 된다.
<br></p>
<p>그럼 이를 이용해 위의<code>Item</code>과 <code>Bill</code>을 mapping 시켜보자.
우리가 원하는 매핑 전략은 다음과 같다. </p>
<ul>
<li><code>Item.stock</code> → <code>Bill.qty</code></li>
<li><code>Item.price</code> → <code>Bill.singlePrice</code></li>
<li><code>Item.sale</code> → <code>Bill.discount</code></li>
</ul>
<p><code>수량</code>과 <code>가격</code>의 경우는 아래와 같이 메서드 레퍼런스를 통해 간단히 설정이 가능하다. </p>
<pre><code class="language-java">mapper.typeMap(Item.class, Bill.class).addMappings(m -&gt; {
    m.map(Item::getStock, Bill::setQty);
    m.map(Item::getPrice, Bill::setSinglePrice);
});

Bill bill2 = mapper.map(item, Bill.class);</code></pre>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/7dadfeee-82f8-4314-861c-61b29062f07b/image.png" alt=""></p>
<p>위의 결과에서 볼 수 있듯 임의로 설정한 <code>수량</code>과 <code>가격</code>의 매핑 관계가 정상적으로 적용된 것을 확인할 수 있다. </p>
<p>하지만, <code>Item.sale</code>과 <code>Bill.discount</code>와 같이 클래스 타입이 다른 경우에는 추가적인 방법이 필요하다. </p>
<h1 id="파라미터-타입-변환--converter">파라미터 타입 변환 : Converter</h1>
<p>매핑하고자 하는 <code>source</code>와 <code>destination</code>의 타입이 다른 경우, <code>Converter</code> 인터페이스를 사용해 값을 설정해줄 수 있다. </p>
<p>위의 예제에서 <code>Item.sale == true</code>일 경우에 <code>Bill.discount</code> 값을 30.0으로 설정한다고 가정하자. </p>
<p><code>mapper.using(Converter&lt;S, D&gt;)</code>의 패턴을 이용하면 유연한 타입 변환이 가능하다.</p>
<pre><code class="language-java">mapper.typeMap(Item.class, Bill.class).addMappings(m -&gt; {
    m.map(Item::getStock, Bill::setQty);
    m.map(Item::getPrice, Bill::setSinglePrice);
    m.using((Converter&lt;Boolean, Double&gt;) ctx -&gt; ctx.getSource() ? 30.0 : 0.0)
        .map(Item::isSale, Bill::setDiscount);
});

Bill bill2 = mapper.map(item, Bill.class);</code></pre>
<p>실행 결과는 아래와 같다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/28569a58-1e54-4c33-b3d6-e9c29594a6eb/image.png" alt=""></p>
<p><code>Converter</code>를 이용해 정상적으로 타입이 변환된 것을 확인할 수 있다. </p>
<h1 id="매핑-skip">매핑 skip</h1>
<p>매핑을 원하지 않을 경우 <code>skip</code>을 통해 매핑이 이루어지지 않도록 설정할 수도 있다.
아래와 같이 사용할 수 있다.</p>
<pre><code class="language-java">typeMap.addMapping(mapper -&gt; mapper.skip(Destination::setName));</code></pre>
<p>위의 예시에서는 아래와 같이 사용할 수 있다.</p>
<pre><code class="language-java">mapper.typeMap(Item.class, Bill.class).addMappings(m -&gt; {
    m.map(Item::getStock, Bill::setQty);
    m.map(Item::getPrice, Bill::setSinglePrice);
    m.using((Converter&lt;Boolean, Double&gt;) ctx -&gt; ctx.getSource() ? 30.0 : 0.0)
        .map(Item::isSale, Bill::setDiscount);
    m.skip(Bill::setItemName);
});</code></pre>
<h2 id="null인-속성-값만-매핑-skip">Null인 속성 값만 매핑 skip</h2>
<p>객체에 새로운 값들을 한꺼번에 업데이트해줄 때, <code>ModelMapper</code>의 기본 매칭 전략을 사용하면 null 값까지 함께 업데이트되는 문제가 발생하기 때문에 이를 위해 매핑 설정을 할 수 있다. </p>
<pre><code class="language-java">ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setSkipNullEnabled(true);</code></pre>
<h1 id="validation">Validation</h1>
<p><code>ModelMapper</code>는 기본적으로 매칭 전략에 맞지 않는 속성들은 null 값으로 초기화한다. 이때 개발하는 입장에선 어떤 객체에 대해 모든 속성이 올바르게 매핑되었는지 검증이 필요할 때가 있다. </p>
<p>이때 <code>ModelMapper().validate()</code>를 이용해 매핑 검증이 실패하는 경우 예외 처리를 해주기 때문에 추가적인 Exception Handling이 가능하다. </p>
<pre><code class="language-java">...
try{
    modelMapper.validate();
} catch(ValidationException e){
    // Exception Handling
}
...</code></pre>
<h1 id="strategies">Strategies</h1>
<p>앞서 설정한 여러 가지 조건들에 의해 <code>ModelMapper</code>는 지능적인 Object Mapping을 수행한다. </p>
<p>아래와 같이 <code>STANDARD</code>, <code>LOOSE</code>, <code>STRICT</code> 전략이 있다.</p>
<pre><code class="language-java">modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STANDARD);
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.LOOSE);
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);</code></pre>
<h2 id="standard">STANDARD</h2>
<p>가장 기본적인 매칭 전략으로써, <code>STANDARD</code> 전략을 사용하면 <code>source</code>와 <code>destination</code>의 속성들을 지능적으로 매칭시킬 수 있다.</p>
<ul>
<li>토큰은 어떤 순서로든 일치될 수 있다.</li>
<li>모든 <code>destination</code> 속성 이름 토큰이 일치해야 한다.</li>
<li>모든 <code>source</code> 속성 이름은 일치하는 토큰이 하나 이상 있어야 한다. </li>
</ul>
<p>위 조건들을 충족하지 못하는 경우 매칭에 실패하여 null을 반환한다. </p>
<h2 id="loose">LOOSE</h2>
<p>느슨한 매칭 전략으로, <code>LOOSE</code> 전략을 사용하면 계층 구조의 마지막 <code>destination</code> 속성만 일치하도록 요구하여 <code>source</code>와 <code>destination</code>을 느슨하게 매치시킬 수 있다. </p>
<ul>
<li>토큰은 어떤 순서로든 일치될 수 있다.</li>
<li>마지막 <code>destination</code> 속성 이름에는 모든 토큰이 일치해야 한다.</li>
<li>마지막 <code>source</code> 속성 이름은 일치하는 토큰이 하나 이상 있어야 한다. </li>
</ul>
<p><code>LOOSE</code> 전략은 속성 계층 구조가 매우 다른 <code>source</code>, <code>destination</code> 객체에 사용하는데에 이상적이다. </p>
<p>→ 처음 예시의 <code>Order</code>, <code>OrderDTO</code>와 같이 객체의 속성이 계층 구조를 가지는 경우에 효과적이다. </p>
<h2 id="strict">STRICT</h2>
<p>엄격한 매칭 전략으로써, <code>STRICT</code> 전략을 사용하면 <code>source</code> 속성을 <code>destination</code> 속성과 엄격하게 일치시킬 수 있다. 따라서 불일치나 모호성이 발생하지 않도록 완벽한 일치 정확도를 얻을 수 있다. 이때 <code>source</code>와 <code>destination</code>의 속성 이름들이 서로 <strong>정확하게</strong> 일치해야 한다. </p>
<ul>
<li>토큰들은 엄격한 순서로 일치해야 한다.</li>
<li>모든 <code>destination</code> 속성 이름 토큰이 일치해야 한다.</li>
<li>모든 <code>source</code> 속성 이름에는 모든 토큰이 일치해야 한다.</li>
</ul>
<p><code>STRICT</code> 전략을 통해 앞서 다룬 <code>TypeMap</code>을 사용하지 않고도 모호함이나 예기치 않은 매핑이 발생하지 않도록 하는 경우에 간편하게 사용이 가능하다. 하지만, 반드시 매칭되어야 하는 속성의 이름들이 서로 정확하게 일치해야 한다!</p>
<hr>
<h1 id="references">References</h1>
<ul>
<li><a href="https://modelmapper.org/getting-started/">https://modelmapper.org/getting-started/</a></li>
<li><a href="https://devwithpug.github.io/java/java-modelmapper/">https://devwithpug.github.io/java/java-modelmapper/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] 람다식(Lambda expression)]]></title>
            <link>https://velog.io/@easy_on7/Java-%EB%9E%8C%EB%8B%A4%EC%8B%9DLambda-expression</link>
            <guid>https://velog.io/@easy_on7/Java-%EB%9E%8C%EB%8B%A4%EC%8B%9DLambda-expression</guid>
            <pubDate>Mon, 10 Jul 2023 13:50:57 GMT</pubDate>
            <description><![CDATA[<h1 id="람다식">람다식</h1>
<p>자바가 처음 등장한 이후로 두 번의 큰 변화가 있었는데, 한번은 JDK1.5부터 추가된 <code>제네릭스(Generics)</code>의 등장이고, 또 한 번은 JDK1.8부터 추가된 <code>람다식(Lambda expression)</code>이다. 
 이 람다식의 등장으로 인해 자바는 객체지향언어인 동시에 <strong>함수형 언어</strong>가 되었다. </p>
<p>이 람다식에 대해 자세히 알아보자.</p>
<h2 id="람다식이란">람다식이란?</h2>
<p>람다식은 간단히 말해서 </p>
<blockquote>
<p>메서드를 하나의 <strong>식(Expression)</strong>으로 표현한 것</p>
</blockquote>
<p>람다식은 함수를 간략하면서도 명확한 식으로 표현할 수 있게 해준다. </p>
<p>간단한 예제를 살펴보면, 다음과 같은 메서드를</p>
<pre><code class="language-java">int method(){
    return (int)(Math.random()*5) + 1;
}</code></pre>
<p>아래의 <code>Arrays.setAll</code>의 두 번째 인자와 같이 바꿀 수 있다.</p>
<pre><code class="language-java">int[] arr = new int[5];
Arrays.setAll(arr, (i) -&gt; (int)(Math.random()*5) + 1);</code></pre>
<br>

<p>모든 메서드는 클래스에 포함되어야 하므로 클래스도 새로 만들어야 하고, 객체도 생성해야만 위의 메서드를 호출할 수 있다. 하지만, <strong>람다식은 이 모든 과정없이 오직 람다식 자체만으로도 이 메서드의 역할을 대신</strong>할 수 있다. </p>
<p>게다가, 람다식은
✔ 메서드의 매개변수로 전달되어지는 것이 가능
✔ 메서드의 결과로 반환될 수 있다</p>
<h2 id="람다식-작성하기">람다식 작성하기</h2>
<p>람다식은</p>
<blockquote>
<p>메서드에서 이름과 반환타입을 제거하고 매개변수 선언부와 몸통{} 사이에 &#39;-&gt;&#39;를 추가한다.</p>
</blockquote>
<pre><code>반환타입 메서드이름(매개변수 선언){
    문장들
}
            ⬇
(매개변수 선언) -&gt; {
    문장들
}</code></pre><p>예를 들어, 두 값 중 큰 값을 반환하는 메서드 max를 람다식으로 변환하면 다음과 같다.</p>
<pre><code class="language-java">int max(int a, int b){
    return a &gt; b ? a : b;
}
            ⬇
(int a, int b) -&gt; {
    return a &gt; b ? a : b;
}</code></pre>
<blockquote>
<p>반환값이 있는 메서드의 경우,
<code>return</code>문 대신 <code>식(expression)</code>으로 대신 할 수 있다. 식의 연산결과가 자동적으로 반환되는 것이다. 
이때는 <code>문장</code>이 아닌 <code>식</code>이므로 끝에 <strong><code>;</code>을 붙이지 않는다.</strong></p>
</blockquote>
<pre><code class="language-java">(int a, int b) -&gt; {return a&gt;b ? a:b;} ➡ (int a, int b) -&gt; a&gt;b ? a:b</code></pre>
<p>람다식에 선언된 매개변수의 타입은 추론이 가능한 경우 생략할 수 있다(대부분의 경우 생략 가능)</p>
<pre><code class="language-java">(int a, int b) -&gt; a&gt;b ? a:b ➡ (a, b) -&gt; a&gt;b ? a:b</code></pre>
<p><em>두 매개변수 중 하나의 타입만 생략하는 것은 불가능</em></p>
<br>

<p>아래와 같이 선언된 매개변수가 하나뿐인 경우에는 괄호( )를 생략할 수 있다. </p>
<pre><code class="language-java">(a) -&gt; a * a
(int a) -&gt; a * a
        ⬇
a -&gt; a * a
int a -&gt; a * a</code></pre>
<p>마찬가지로 괄호 { } 안의 문장이 하나일 때는 생략할 수 있는데, 이 때 문장의 끝에 <code>;</code>을 붙이지 않아야 한다는 것에 주의하자!</p>
<pre><code class="language-java">(String name, int i) -&gt; {
    System.out.println(name + &quot;=&quot; + i);
}
                        ⬇
(String name, int i) -&gt; System.out.println(name + &quot;=&quot; + i)</code></pre>
<blockquote>
<p>하지만, 괄호 { } 안의 문장이 <code>return</code>문일 경우 괄호{ }를 <strong>생략할 수 없다.</strong></p>
</blockquote>
<h2 id="함수형-인터페이스functional-interface">함수형 인터페이스(Functional Interface)</h2>
<p>자바에서 모든 메서드는 클래스 내에 포함되어야 하는데, 람다식은 어떤 클래스에 포함되는 것일까? 위에서 람다식이 메서드와 동등한 것처럼 설명했지만, 사실 람다식은 <strong>익명 클래스의 객체</strong>와 동등하다.</p>
<pre><code class="language-java">(int a, int b) -&gt; a&gt;b ? a:b
            ⬇
new Object() {
    int max(int a, int b){
        return a&gt;b ? a:b;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Socket Programming] 소켓의 타입과 프로토콜의 설정]]></title>
            <link>https://velog.io/@easy_on7/Socket-Programming-%EC%86%8C%EC%BC%93%EC%9D%98-%ED%83%80%EC%9E%85%EA%B3%BC-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C%EC%9D%98-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@easy_on7/Socket-Programming-%EC%86%8C%EC%BC%93%EC%9D%98-%ED%83%80%EC%9E%85%EA%B3%BC-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C%EC%9D%98-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Wed, 05 Jul 2023 11:32:48 GMT</pubDate>
            <description><![CDATA[<h2 id="소켓의-프로토콜과-그에-따른-데이터-전송-특성">소켓의 프로토콜과 그에 따른 데이터 전송 특성</h2>
<h2 id="protocol"><strong>Protocol</strong></h2>
<blockquote>
<p>컴퓨터 상호간의 데이터 송수신에 필요한 통신규약</p>
</blockquote>
<p>소켓을 생성할 때 기본적인 프로토콜을 지정해야 한다.</p>
<pre><code class="language-c">#include &lt;sys/socket.h&gt;
int socket(int domain, int type, int protocol);</code></pre>
<p>✔ <strong><code>domain</code></strong> : 소켓이 사용할 프로토콜 체계(Protocol Family) 정보 전달
✔ <strong><code>type</code></strong> : 소켓의 데이터 전송방식에 대한 정보 전달
✔ <strong><code>protocol</code></strong> : 두 컴퓨터간 통신에 사용되는 프로토콜 정보 전달</p>
<p>→ 모두 프로토콜 정보와 관련있다.</p>
<h2 id="protocol-family프로토콜-체계">Protocol Family(프로토콜 체계)</h2>
<p>프로토콜도 그 <strong><em>종류에 따라서 부류</em></strong>가 나뉘는데, 그 부류를 가리켜 <strong>프로토콜 체계</strong>라 한다.</p>
<p><em>int domain에 들어가는 부분</em></p>
<table>
<thead>
<tr>
<th>이름</th>
<th>protocol family</th>
</tr>
</thead>
<tbody><tr>
<td>PF_INET</td>
<td>IPv4 인터넷 프로토콜 체계</td>
</tr>
<tr>
<td>PF_INET6</td>
<td>IPv6 인터넷 프로토콜 체계</td>
</tr>
</tbody></table>
<h2 id="type소켓의-타입">Type(소켓의 타입)</h2>
<blockquote>
<p><strong>데이터 전송 방식</strong>을 의미한다.</p>
</blockquote>
<p>소켓이 생성될 때 소켓의 타입도 결정되어야 한다.</p>
<p>PF_INET의 대표적인 소켓 타입</p>
<ul>
<li><strong>TCP</strong>(연결 지향형 소켓 타입)</li>
<li><strong>UDP</strong>(비연결 지향형 소켓 타입)</li>
</ul>
<h3 id="tcp">TCP</h3>
<ul>
<li>중간에 데이터가 소멸되지 않는다.</li>
<li>Error Control이 존재한다.</li>
<li>전송 순서대로 데이터가 수신된다.</li>
<li>소켓 대 소켓의 연결은 반드시 <strong>1대1</strong> 구조이다.’</li>
<li>Stream 기반으로, 대용량 Data를 한 번에 전송한다.</li>
</ul>
<pre><code class="language-c">int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
//IPPROTO_TCP : protocol number로 TCP는 6번, UDP는 17번이다.
//0으로 설정 시, default값으로 자동으로 할당된다.</code></pre>
<h3 id="udp">UDP</h3>
<ul>
<li>전송순서에 상관없이 빠른 속도의 전송을 지향한다.</li>
<li>데이터 손실 및 파손의 우려가 있다.</li>
<li>한 번에 전송할 수 있는 데이터의 크기가 제한된다.(작은 data)</li>
<li>Message를 기반으로 Data를 Packet단위로 나누어 보낸다.</li>
</ul>
<pre><code class="language-c">int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
//첫 번째, 두 번째 인자로 전달된 정보를 토대로 소켓의 프로토콜은 결정되기 때문에,
//세 번째 인자를 0으로 전달해도 상관없다.</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[헤로쿠(Heroku) 배포 + Springboot와  PostgreSQL 연결하기]]></title>
            <link>https://velog.io/@easy_on7/%ED%97%A4%EB%A1%9C%EC%BF%A0Heroku-%EB%B0%B0%ED%8F%AC-Springboot%EC%99%80-PostgreSQL-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@easy_on7/%ED%97%A4%EB%A1%9C%EC%BF%A0Heroku-%EB%B0%B0%ED%8F%AC-Springboot%EC%99%80-PostgreSQL-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 29 Jun 2023 22:13:41 GMT</pubDate>
            <description><![CDATA[<p>혼자 간단한 sns 애플리케이션을 만들어보면서 배포까지 공부해보기 위해 헤로쿠를 사용해보았다. </p>
<h1 id="헤로쿠-유료로-바꼈는데-왜-씀">헤로쿠 유료로 바꼈는데 왜 씀?</h1>
<p>원래 헤로쿠는 무료로 사용이 가능했는데, 얼마전 정책이 변경되면서(<del>무료 버전을 사용 중인 것들의 유지가 힘들다고 하던가</del>) 무료 버전이 사라지고 유료만 남게 되었다. 그래서 헤로쿠 대신에 <a href="https://fly.io/">fly.io</a>를 사용하려 시도를 하다가 예상치 못한 난관을 만나기도 하고 결국 실패를 해서 돈을 좀 주더라도 잘 알려져 있는 헤로쿠를 이용해보기로 했다. 월 최대 5$로 사용이 가능하니 배움의 입장에서는 그리 치명적인 금액은 아니라고 생각했다!! 또 뭐 그리 대단한 애플리케이션이라고 리소스를 그리 많이 쓸 것 같지도 않고!
<br></p>
<p>아무튼, 나는 <code>PostgreSQL</code> 데이터베이스를 사용했다! 헤로쿠에 <code>PostgreSQL</code>을 올리고 이를 Springboot와 연결하는 방법을 알아보자. </p>
<hr>
<h1 id="헤로쿠heroku에-배포하기">헤로쿠(Heroku)에 배포하기</h1>
<h2 id="github에-heroku-api-key-등록하기">Github에 Heroku API key 등록하기</h2>
<p>Github action을 이용해 main 브랜치에 push가 일어나면 자동으로 배포를 진행하기 위해 우선 Heroku에 등록한 앱의 API key를 등록시키자.</p>
<p>헤로쿠 사이트로 들어가 오른쪽 상단의 프로필을 클릭해 <strong>Account settings</strong>으로 들어간다. 
그리고 창을 쭈욱 내려 맨 아래로 가면 아래 그림과 같이 API Key 부분이 있다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/d13dbc70-a321-44c5-b67e-6359a2e3b3cf/image.png" alt=""></p>
<p>이를 복사해 Github에 넣어줄 것이다. </p>
<p>이제 Github로 와서 프로젝트를 진행하고 있는 repository의 setting으로 들어간다. </p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/1e5e52cb-9365-45cb-b62a-bc356f1ac2f9/image.png" alt=""></p>
<p>그 중 Sercrets and variables 아래의 Actions에 들어가면 이 API Key를 등록할 수 있다. 
왼쪽 상단의  <code>New repository secret</code>을 선택해 Key의 이름과 함께 헤로쿠에서 복사한 키를 붙여넣고 저장해주면 위 사진과 같이 Key 등록이 완료된다.</p>
<h2 id="deployyml-파일-작성하기">deploy.yml 파일 작성하기</h2>
<p>그리고 Github action을 통한 배포를 위해 <code>deploy.yml</code> 파일 설정이 필요하다. 
애플리케이션에 <code>.github</code> 폴더를 만들어 주고 <code>workflows</code> 폴더를 만들어 그 곳에 <code>deploy.yml</code> 파일을 생성하자.
<img src="https://velog.velcdn.com/images/easy_on7/post/800342f9-ecb6-432d-9741-9ba7a6aacde9/image.png" alt=""></p>
<p><code>deploy.yml</code> 파일의 내용은 다음과 같다.</p>
<pre><code class="language-yaml"># This is a basic workflow to help you get started with Actions

name: Deploy

# Controls when the workflow will run
on:
  # Triggers the workflow on push or pull request events but only for the main branch
  push:
    branches: [ main ]
  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called &quot;build&quot;
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v3

      - name: Deploy to Heroku
        uses: AkhileshNS/heroku-deploy@v3.12.12
        with:
          # This will be used for authentication. You can find it in your heroku homepage account settings
          heroku_api_key: ${{ secrets.HEROKU_DEPLOY_KEY }}
          # Email that you use with heroku
          heroku_email:
          # The appname to use for deploying/updating
          heroku_app_name: </code></pre>
<p><code>name</code>은 단순히 이름을 의미한다. 
<code>on</code>은 언제 트리거가 될지를 명시해주는 부분이다. 여기에서는 main 브랜치에 push가 일어났을 때 트리거가 된다. 
<code>steps</code>의 <code>uses</code> 부분을 보면 <code>git checkout</code>을 수행하게 되고 헤로쿠에 deploy하게 된다. 
<code>heroku_api_key</code>에 위에서 우리가 등록했던 HEROKU_DEPLOY_KEY 이름을 적어주면 된다. <code>email</code> 부분과 <code>app_name</code>은 헤로쿠 계정의 이메일과 헤로쿠에 만든 앱의 이름을 적어주면 된다. <br></p>
<h2 id="systemproperties-파일-작성">system.properties 파일 작성</h2>
<p>프로젝트 폴더 바로 아래 <code>system.properites</code> 파일을 하나 생성하고 아래와 같이 작성하자.</p>
<pre><code>java.runtime.version = 11</code></pre><p>이는 자바의 버전을 명시해주는 것이다. 자바 11버전부터는 이와 같이 버전을 명시해주어야 헤로쿠에서 문제가 발생하지 않는다. </p>
<h2 id="procfile-파일-작성">Procfile 파일 작성</h2>
<p>마찬가지로 프로젝트 폴더 바로 아래 <code>Procfile</code> 파일을 만들어주자.</p>
<pre><code>web: java -Dserver.port=$PORT $JAVA_OPTS -jar build/libs/SNS-0.0.1-SNAPSHOT.jar</code></pre><p>이는 처음에 헤로쿠 앱이 떴을 때, 어떤 동작을 수행할지 명령어를 명시해주기 위해 필요하다. </p>
<blockquote>
<p>여기서 SNS 부분에는 프로젝트 이름이 들어가야 한다. </p>
</blockquote>
<p>위와 같이 배포 시나리오를 작성해주면 Github의 main에 push가 될 때마다 배포를 자동으로 수행하게 된다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/c79d7e5c-a355-4f77-8fb2-6c8fa7cbfd0f/image.png" alt=""></p>
<hr>
<h1 id="spring-boot-설정">Spring boot 설정</h1>
<p>우선 <code>Spring boot</code>와 <code>postgreSQL</code>을 연결하기 위한 작업이 필요하다.</p>
<p>당연히 <code>build.gradle</code> 파일 안에 아래와 같은 postgresql 사용을 위한 dependency를 추가해주자.</p>
<pre><code class="language-java">dependencies {
    ...
    runtimeOnly &#39;org.postgresql:postgresql&#39;
    ...
}</code></pre>
<p>다음으로 <code>application.yaml</code> 파일을 아래와 같이 설정하자.</p>
<pre><code class="language-yaml">spring:
  jpa:
    database: postgresql
    hibernate:
      dialect: org.hibernate.dialect.PostgreSQLDialect
      ddl-auto: update
    properties:
      hibernate:
        format_sql: true
    show-sql: true

  datasource:
    hikari:
      maximum-pool-size: 4
    url: jdbc:postgresql://
    username:
    password:
    driver-class-name: org.postgresql.Driver
  sql:
    init:
      platform: postgres</code></pre>
<p> 여기서 <code>url</code>, <code>username</code>, <code>password</code>를 설정해야한다. </p>
<p> 해로쿠로 들어가자.
 <img src="https://velog.velcdn.com/images/easy_on7/post/4d2b6e06-04bc-4678-8801-29e39f733a48/image.png" alt=""></p>
<p>만들어진 앱에 들어가 Installed add-ons의 Configure Add-ons를 눌러 들어가자. 
들어가면 헤로쿠에서 지원하는 다양한 add-on들이 뜨게 되는데, Postgresql을 사용할 것이기 때문에 <code>Heroku Postgre</code>를 선택해 설치해주자.
<img src="https://velog.velcdn.com/images/easy_on7/post/acb50c45-3278-493f-ac48-2322d73eab1f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/6dcdbed1-b54b-4260-8b67-db9f422e63e3/image.png" alt="">
App to provision to 부분에 연결할 헤로쿠 앱을 적어주면 된다. </p>
<p><em>이게 원래 Add-on plan에 Hobby Dev - Free라고 무료 플랜이 있었는데, 사라지고 없다. ㅠㅠ
대신 제일 저렴한 가격이 월 5$이다. 공부를 위해 이 정도 쯤이야... 라고 생각하자.</em></p>
<p>그럼 다음과 같이 적용된 것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/765ff020-5329-44fa-9d4a-7557306c19cd/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/90d5ad3d-e6c3-4203-be57-8c746112ab9a/image.png" alt=""></p>
<p>여기에 들어가 View Credentials...에 들어가면 Database의 정보들이 나오게 된다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/36d2f8ba-e49c-471c-ae6c-d4d9fc1dfcd8/image.png" alt=""></p>
<p>내용들은 지웠는데, Host에 해당하는 부분을 <code>url</code>의 <code>jdbc:postgresql://</code> 뒤에 넣고 /를 붙이고 Database에 해당하는 부분을 넣자. 즉,</p>
<pre><code class="language-yaml">url: jdbc:postgresql://{Host}/{Database}</code></pre>
<p>와 같은 형식이다. </p>
<p>User는 <code>username</code>, Password는 <code>password</code>에 값을 넣으면 된다. </p>
<hr>
<p>이제 애플리케이션을 실행을 시켜보면 정상적으로 동작하는 것을 확인할 수 있다!!</p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/67a3b60f-a099-4164-9266-62eb0a968647/image.png" alt=""></p>
<p>이렇게 CREATE 문도 잘 동작하는 것을 확인할 수 있고, Postman을 이용해 API 테스트를 진행해보면, 데이터가 정상적으로 들어가고</p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/055adf5e-1583-4243-9646-91f4711639e1/image.png" alt=""></p>
<p>헤로쿠의 Postgresql에서도 확인이 되는 모습이다.
<img src="https://velog.velcdn.com/images/easy_on7/post/6b356449-ae88-43d1-81ce-f24a3aac7cf3/image.png" alt=""></p>
<hr>
<h4 id="">+</h4>
<p>이것저것 실험해보다가 뭔지 모르고 구독도 하고 그런 것 같은데.. 돈을 꽤나 쓴 것 같다... 다음 달에 청구가 되려나 겁이 좀 납니다.. 그냥 다른 무료 플랜을 제공해주는 배포 환경을 찾아 떠나는게 나을지도 모른다는 생각이 듭니다ㅠㅠ,. 애플리케이션을 끝까지 제작해보고 무료 환경인 fly.io로 넘어가는걸 시도해보겠습니다. </p>
<h4 id="결론">결론</h4>
<p>유료라서 괜히 찝찝하다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Socket Programming] 네트워크 프로그래밍과 소켓의 이해]]></title>
            <link>https://velog.io/@easy_on7/Socket-Programming-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EA%B3%BC-%EC%86%8C%EC%BC%93%EC%9D%98-%EC%9D%B4%ED%95%B4</link>
            <guid>https://velog.io/@easy_on7/Socket-Programming-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EA%B3%BC-%EC%86%8C%EC%BC%93%EC%9D%98-%EC%9D%B4%ED%95%B4</guid>
            <pubDate>Thu, 29 Jun 2023 18:48:59 GMT</pubDate>
            <description><![CDATA[<h1 id="네트워크-프로그래밍">네트워크 프로그래밍</h1>
<blockquote>
<p>네트워크로 연결된 둘 이상의 컴퓨터 사이에서의 데이터 송수신 프로그램의 작성</p>
</blockquote>
<h4 id="socket소켓">Socket(소켓)</h4>
<p>✔ 네트워크(인터넷)의 연결 도구
✔ OS에 의해 제공되는 소프트웨어적인 장피
✔ 소켓을 프로그래머에게 데이터 송수신에 대한 물리적, 소프트웨어적으로 자세한 내용을 신경 쓰지 않게 한다.</p>
<h1 id="server">Server</h1>
<h2 id="socket">socket</h2>
<blockquote>
<p>TCP 소켓은 전화기에 비유될 수 있다. 소켓은 socket함수의 호출을 통해 <strong>생성</strong>한다.</p>
</blockquote>
<pre><code class="language-c">#include &lt;sys/socket.h&gt;
int socket(int domain, int type, int protocol);
//성공 시 파일 디스크립터, 실패 시 -1 return</code></pre>
<h2 id="bind">bind</h2>
<blockquote>
<p>전화기에 전화번호가 부여되듯이 소켓에도 <strong>주소정보가 할당</strong>된다. 소켓의 주소정보는 IP와 PORT번호로 구성 된다.</p>
</blockquote>
<pre><code class="language-c">#include &lt;sys/socket.h&gt;
int bind(int sockfd, struct sockaddr* myaddr, socklen_t addrlen);
//성공 시 0, 실패 시 -1 return</code></pre>
<h2 id="listen">listen</h2>
<blockquote>
<p><strong>연결요청이 가능한 상태</strong>의 소켓으로, 걸려오는 전화를 받을 수 있는 상태에 비유할 수 있다. 연결 요청을 하는 소켓(client)에서는 필요하지 않다. 연결 요청을 받는 소켓(<strong>server</strong>)에서 필요한 상태이다.</p>
</blockquote>
<pre><code class="language-c">#include &lt;sys/socket.h&gt;
int listen(int sockfd, int backlog); // backlog = 동시에 처리 가능한 client 수
//성공 시 0, 실패 시 -1 return</code></pre>
<h2 id="accept">accept</h2>
<blockquote>
<p>걸려오는 전화에 대한 수락의 의미로 수화기를 드는 것에 비유할 수 있다. <strong>연결요청이 수락</strong>되어야 데이터의 송수신이 가능하다. 수락된 이후에 데이터의 송수신은 양방향으로 가능하다.</p>
</blockquote>
<pre><code class="language-c">#include &lt;sys/socket.h&gt;
int accept(int sockfd, struct sockaddr* addr, socklen_t *addrlen);
//성공 시 파일 디스크립터, 실패 시 -1 return</code></pre>
<p>accept함수 호출 이후에 데이터의 송수신이 가능하다. 단, 연결요청이 있을 경우에만 accept함수가 반환을 한다.</p>
<p>연결요청을 허용하는 프로그램을 일반적으로 Server라 한다. 서버는 연결을 요청하는 클라이언트보다 먼저 실행되어야 한다.</p>
<p>Server에서 <strong>연결요청을 수락하는 소켓의 생성 과정</strong>은 아래와 같다.</p>
<ol>
<li>소켓의 생성(<code>socket</code>)</li>
<li>IP &amp; PORT# 할당(<code>bind</code>)</li>
<li>연결요청 가능상태로 변경(<code>listen</code>)</li>
<li>연결요청에 대한 수락(<code>accept</code>)</li>
</ol>
<hr>
<h1 id="client">Client</h1>
<h2 id="connect">connect</h2>
<blockquote>
<p>연결을 요청하는 소켓의 구현으로, 전화를 거는 상황에 비유할 수 있다.</p>
</blockquote>
<pre><code class="language-c">#include &lt;sys/socket.h&gt;
int connect(int sockfd, struct sockaddr* serv_addr, socklen_t addrlen);
//성공 시 0, 실패 시 -1 return</code></pre>
<blockquote>
<p>💡 Socket을 생성하는 순간에는 Server socket과 Client socket으로 나누어지지 않는다.
<strong>bind, listen 함수의 호출</strong>이 이어지면 <strong>Server socket</strong>이 되고, <strong>connect함수가 호출</strong>되면 <strong>Client socket</strong>이 된다.</p>
</blockquote>
<hr>
<h1 id="리눅스-기반-파일-조작">리눅스 기반 파일 조작</h1>
<blockquote>
<p>리눅스는 소켓도 파일로 간주하기 때문에 저 수준 파일 입출력 함수를 기반으로 소켓 기반의 데이터 송수신이 가능하다.</p>
</blockquote>
<h4 id="file-descriptor파일-디스크립터">File descriptor(파일 디스크립터)</h4>
<p>✔ OS가 만든 파일(and socket)을 구분하기 위한 일종의 숫자
✔ 저수준 파일 입출력 함수는 입출력을 목적으로 fd를 요구한다.
✔ 저수준 파일 입출력 함수에게 소켓의 fd를 전달하면, 소켓을 대상으로 입출력을 진행한다.</p>
<table>
<thead>
<tr>
<th>File descriptor</th>
<th>대상</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>표준 입력 : stdin</td>
</tr>
<tr>
<td>1</td>
<td>표준 출력 : stdout</td>
</tr>
<tr>
<td>2</td>
<td>표준 에러 : stderr</td>
</tr>
</tbody></table>
<h2 id="write">write</h2>
<blockquote>
<p>파일에 데이터 쓰기</p>
</blockquote>
<p>Socket을 통해서 다른 컴퓨터에 data를 전송할 경우에 사용할 수 있다.</p>
<pre><code class="language-c">#include &lt;unistd.h&gt;
ssize_t write(int fd, const void* buf, size_t nbytes);
//성공 시 전달한 byte 수, 실패 시 -1 return</code></pre>
<p>✔ <code>fd</code> = 데이터 전송대상을 나타내는 file descriptor
✔ <code>buf</code> = 전송할 데이터가 저장된 버퍼의 주소 값
✔ <code>nbytes</code> = 전송할 데이터의 바이트 수</p>
<h2 id="read">read</h2>
<blockquote>
<p>파일에 저장된 데이터 읽기</p>
</blockquote>
<pre><code class="language-c">#include &lt;unistd.h&gt;
ssize_t read(int fd, void* buf, size_t nbytes);
//성공 시 수신한 바이트 수(단, 파일의 끝을 만나면 0), 실패 시 -1 return</code></pre>
<p>✔ <code>fd</code> = 데이터 수신대상을 나타내는 file descriptor
✔ <code>buf</code> =  수신한 데이터를 저장할 버퍼의 주소 값
✔ <code>nbytes</code> = 수신할 최대 바이트 수</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] JDK와 JRE의 차이점]]></title>
            <link>https://velog.io/@easy_on7/JDK%EC%99%80-JRE%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</link>
            <guid>https://velog.io/@easy_on7/JDK%EC%99%80-JRE%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</guid>
            <pubDate>Thu, 29 Jun 2023 14:01:23 GMT</pubDate>
            <description><![CDATA[<p>결론부터 얘기하자면!</p>
<blockquote>
<p> JDK = 자바 개발도구
JRE = 자바 실행환경 </p>
</blockquote>
<h1 id="jdk">JDK</h1>
<p>JDK는 Java Development Kit의 약어로</p>
<blockquote>
<p>자바 애플리케이션을 개발하기 위한 환경을 지원한다.</p>
</blockquote>
<p>JDK 안에는 <strong>개발 시 필요한 라이브러리</strong>들과 javac, javadoc 등의 개발 도구들이 포함되어 있다. 또한, 개발을 하기 위해서는 당연히 실행도 필요하기에 <strong>JRE도 함께 포함되어 있다</strong>.</p>
<h1 id="jre">JRE</h1>
<p>JRE는 Jave Runtime Environment의 약어로</p>
<blockquote>
<p>자바로 만들어진 프로그램을 실행시키는데 필요한 라이브러리들을 포함한다.</p>
</blockquote>
<p>쉽게 말해 <strong>자바 애플리케이션 실행 환경</strong>이다. JRE는 JVM 뿐만 아니라 Java binaries, Java 클래스 라이브러리 등을 포함한다. 하지만, 컴파일러나 디버거 등의 도구는 포함하지 않는다.</p>
<p>정리하자면,
<U>Java로 프로그램을 직접 개발</U>하려면 <strong>JDK</strong>가 필요하고 <U>Java로 만들어진 프로그램을 실행</U>시키려면 <strong>JRE</strong>가 필요한 것이다.</p>
<p>이 둘의 관계를 그림으로 보면 아래와 같다.
<img src="https://velog.velcdn.com/images/easy_on7/post/82f18ac1-a287-483b-b7be-2a5dc8d31903/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] HTTP 헤더 - 일반 헤더]]></title>
            <link>https://velog.io/@easy_on7/HTTP-HTTP-%ED%97%A4%EB%8D%94-%EC%9D%BC%EB%B0%98-%ED%97%A4%EB%8D%94</link>
            <guid>https://velog.io/@easy_on7/HTTP-HTTP-%ED%97%A4%EB%8D%94-%EC%9D%BC%EB%B0%98-%ED%97%A4%EB%8D%94</guid>
            <pubDate>Wed, 28 Jun 2023 11:18:57 GMT</pubDate>
            <description><![CDATA[<p>HTTP 헤더는 정말 많기 때문에 크게 <code>일반헤더</code>와 <code>캐시, 조건부요청</code> 이렇게 두 가지로 나누어 공부해봅시다!</p>
<h1 id="http-헤더">HTTP 헤더</h1>
<p>앞선 포스팅에서 공부한 것을 잠깐 다시 살펴보면 <code>header-field</code>는 다음과 같이 구성된다.</p>
<pre><code class="language-http">field_name &quot;:&quot; OWS field_value OWS</code></pre>
<p><em>OWS = 띄어쓰기 허용</em></p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/14961a5c-4d1f-41e3-8f4c-3c7720f8aab6/image.png" alt=""></p>
<h2 id="http-헤더의-용도">HTTP 헤더의 용도</h2>
<blockquote>
<p>시작 라인(HTTP/1.1 200 OK 와 같은)을 제외한 HTTP 전송에 필요한 모든 부가정보를 담고 있다.</p>
</blockquote>
<p>예를 들어, 메시지 바디의 내용, 크기, 압축, 인증, 요청 클라이언트, 서버 정보, 캐시 관리 정보 ... 등이 있다.
표준 헤더 필드도 매우 많다. 필요 시에는 임의의 헤더도 추가할 수 있다.</p>
<hr>
<h1 id="http-body">HTTP BODY</h1>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/c1d87e3f-4738-4b79-8887-1471212597a0/image.png" alt=""></p>
<blockquote>
<p>메시지 본문(message body)을 통해 표현 데이터를 전달
메시지 본문 = 페이로드(payload)</p>
</blockquote>
<p><strong>표현(Representation)</strong>은 요청이나 응답에서 전달할 실제 데이터를 의미한다.</p>
<ul>
<li>데이터 유형(html, json ...), 데이터 길이, 압축 정보 ...등</li>
</ul>
<p><strong>표현 헤더</strong>는 표현 데이터를 해석할 수 있는 정보를 제공한다.</p>
<h1 id="표현representation">표현(Representation)</h1>
<p>표현 헤더에는 다음과 같은 것들이 있다.
✔ <code>Content-Type</code> : 표현 데이터 형식
✔ <code>Content-Encoding</code> : 표현 데이터의 압축 방식
✔ <code>Content-Language</code> : 표현 데이터의 자연 언어
✔ <code>Content-Length</code> : 표현 데이터의 길이</p>
<p>표현 헤더는 전송, 응다 둘 다 사용한다.
하나 씩 살펴보자.</p>
<h2 id="content-type">Content-Type</h2>
<blockquote>
<p>표현 데이터의 형식 설명</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/b738716d-4b33-48ad-9730-d1da18060719/image.png" alt=""></p>
<p>미디어 타입, 문자 인코딩의 내용이 들어간다. </p>
<ul>
<li>text/html; charset=utf-8</li>
<li>application/json</li>
<li>image/png</li>
</ul>
<h2 id="content-encoding">Content-Encoding</h2>
<blockquote>
<p>표현 데이터의 인코딩
표현 데이터를 압축하기 위해 사용</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/51f11623-0542-4f5e-85e6-b8ac0d1991d2/image.png" alt=""></p>
<p>데이터를 전달하는 곳에서 압축 후 인코딩 헤더에 추가한다. 데이터를 읽는 쪽에서는 인코딩 헤더의 정보로 압축을 해제한다.</p>
<h2 id="content-language">Content-Language</h2>
<blockquote>
<p>표현 데이터의 자연 언어를 표현한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/3c9252fd-6663-4247-b700-d112bbd85b4a/image.png" alt=""></p>
<ul>
<li>ko</li>
<li>en</li>
<li>en-US</li>
</ul>
<h2 id="content-length">Content-Length</h2>
<blockquote>
<p>표현 데이터의 길이. <U>바이트</U> 단위</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/99e1dd98-074b-407f-a6da-028d300a6332/image.png" alt=""></p>
<p><strong>Transfer-Encoding(전송 코딩)</strong>을 사용하면 <code>Content-Length</code>를 사용하면 안된다.</p>
<hr>
<h1 id="협상콘텐츠-네고시에이션">협상(콘텐츠 네고시에이션)</h1>
<blockquote>
<p>클라이언트가 선호하는 표현을 서버에게 요청하는 것</p>
</blockquote>
<p>✔ <code>Accept</code> : 클라이언트가 선호하는 미디어 타입 전달
✔ <code>Accept-Charset</code> : 클라이언트가 선호하는 문자 인코딩
✔ <code>Accept-Encoding</code> : 클라이언트가 선호하는 압축 인코딩
✔ <code>Accept-Language</code> : 클라이언트가 선호하는 자연 언어</p>
<blockquote>
<p>협상 헤더는 <strong>요청 시에만</strong> 사용한다!</p>
</blockquote>
<p>예시를 살펴보자.
<img src="https://velog.velcdn.com/images/easy_on7/post/8c8ec9e7-bd4d-4454-befa-296f721765e9/image.png" alt=""></p>
<p>우리가 한국어 브라우저를 사용한다고 해보자. 이때 다중 언어를 지원하는 외국의 /event 사이트에 접속한다하면, 이때 <code>Accept-Language</code> 를 사용해 원하는 언어를 지정해주면 서버는 message-body에 한국어를 넣어 전송해준다.
<img src="https://velog.velcdn.com/images/easy_on7/post/4ae41bbe-1bb8-4e2a-9b8d-9cd7b6d0c979/image.png" alt=""></p>
<p>하지만 위와 같이 서버에서 지원하지 않는 자연 언어를 요청하게 된다면 기본 언어인 독일어를 전송하게 된다. <del>독일어보다는 차라리 영어가 낫지만.</del> </p>
<p>이 문제를 해결하기 위해, 우선 순위를 정해줄 필요가 있다.</p>
<h2 id="우선순위-1">우선순위 1</h2>
<blockquote>
<p>Quality Values(q) 값을 사용해 우선 순위를 정해준다.
0 ~ 1 사이의 값으로 정할 수 있고, <strong>클수록 높은 우선순위</strong>이다.
생략하면 1이다!</p>
</blockquote>
<pre><code class="language-http">GET /event
Accept-Language: ko-KR, ko;q=0.9, en-US;q=0.8, en;q=0.7</code></pre>
<p>위 HTTP 메시지를 보면 <code>ko-KR</code>의 경우 q가 생략되어 있으므로 실제로는 <code>ko-KR;q=1</code>이다.
결국 자연 언어의 우선 순위는 ko-kR → ko → en-US → en 이 된다.</p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/da284ae2-e944-4032-a9db-a6207d797e43/image.png" alt=""></p>
<p>실제로 Google 검색의 Request Header를 살펴보면 위와 같이 <code>Accept-Language</code> 필드가 설정되어 있는 것을 확인할 수 있다. </p>
<h2 id="우선순위-2">우선순위 2</h2>
<blockquote>
<p>더욱 구체적인 것이 우선한다.</p>
</blockquote>
<p>쉽게 얘기하자면, 더 많이, 더 자세하게 적은 것이 우선이라는 말이다. </p>
<pre><code class="language-http">GET /event
Accept : text/* ,text/plain, text/plain;format=flowed, */*</code></pre>
<p>위 HTTP 메시지에서 요청의 우선 순위는 text/plain;format=flowed → text/plain → text/* → */* 이 된다. </p>
<hr>
<h1 id="전송-방식">전송 방식</h1>
<p>전송 방식은 단순하게 4가지로 분류할 수 있다.</p>
<ol>
<li>단순 전송</li>
<li>압축 전송</li>
<li>분할 전송</li>
<li>범위 전송</li>
</ol>
<h2 id="1-단순-전송">1. 단순 전송</h2>
<blockquote>
<p>요청을 하면 단순하게 <code>Content-Length</code>에 바이트 크기를 명시해 전송하는 것</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/d1251190-f289-4b76-958d-a0d926777a6a/image.png" alt=""></p>
<h2 id="2-압축-전송">2. 압축 전송</h2>
<blockquote>
<p><code>Content-Encoding</code> 필드에 압축 방법을 명시해 압축하여 전송하는 방법</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/ec8156e7-3b0b-4d23-a20f-a89224083604/image.png" alt=""></p>
<h2 id="3-분할-전송">3. 분할 전송</h2>
<blockquote>
<p><code>Transfer-Encoding</code> 필드를 사용하여 chunck로 나누어 전송하는 것이다. </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/7ade6fa9-541c-4bf9-a7f8-2738f6bcc80f/image.png" alt=""></p>
<p>위 예시에서 5는 바이트를 의미하여 5바이트인 &quot;Hello&quot;를 먼저 전송하고 다음 5바이트인 &quot;World&quot;를 전송하고 &quot;\r\n&quot;을 만나면 끝을 의미한다. </p>
<p>용량이 매우 커서 한 번에 보면 대기 시간이 오래 걸려 내용을 먼저 받아볼 경우 사용할 수 있다.</p>
<blockquote>
<p>분할 전송 시에는 <code>Content-Length</code> 필드를 사용할 수 없다.
message-body 안에 chunked마다 바이트의 크기가 정해져 있다.</p>
</blockquote>
<h2 id="4-범위-전송">4. 범위 전송</h2>
<blockquote>
<p>특정한 범위를 지정해 그 범위에 해당하는 부분만 전송하는 방법</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/8ef6742c-a938-416d-93d8-6b40bc77ebc1/image.png" alt=""></p>
<hr>
<h1 id="일반-정보">일반 정보</h1>
<p>다음은 정보성 헤더들이다.
✔ <code>From</code> : 유저 에이전트의 이메일 정보
✔ <code>Referer</code> : 이전 웹 페이지 주소
✔ <code>User-Agent</code> : 유저 에이전트 애플리케이션 정보
✔ <code>Server</code> : 요청을 처리하는 오리진 서버의 소프트웨어 정보
✔ <code>Date</code> : 메시지가 생성된 날짜</p>
<h2 id="from">From</h2>
<blockquote>
<p>유저 에이전트의 이메일 정보</p>
</blockquote>
<p>일반적으로 잘 사용하지 않지만, 검색 엔진 같은 곳에서 주로 사용한다. <strong>요청</strong>에서 사용하는 필드이다.</p>
<h2 id="referer">Referer</h2>
<blockquote>
<p>현재 요청된 페이지의 이전 웹 페이지 주소</p>
</blockquote>
<p>A → B 로 이동하는 경우 B를 요청할 때 <code>Referer : A</code>를 포함해서 요청한다. 
보통, <code>Referer</code>를 사용해서 <strong>유입 경로를 분석</strong>할 때 주로 사용한다. <strong>요청</strong>에서 사용한다.</p>
<h2 id="user-agent">User-Agent</h2>
<blockquote>
<p>클라이언트의 애플리케이션 정보(웹 브라우저 정보 ... 등)</p>
</blockquote>
<p>서버에서 어떤 종류의 브라우저에서 장애가 발생하는지 파악하는데 사용된다. <strong>요청</strong>에서 사용한다.</p>
<h2 id="server">Server</h2>
<blockquote>
<p>요청을 처리하는 ORIGIN 서버의 소프트웨어 정보</p>
</blockquote>
<p><code>Server : Apache/2.2.22(Debian)</code>, <code>Server : nginx</code>와 같이 사용된다. <strong>응답</strong>에서 사용한다.</p>
<p>_HTTP 요청을 보내면 중간에 여러 많은 proxy 서버를 거치게 된다. 이 proxy 서버말고 HTTP 응답을 해주는 &quot;진짜&quot; 서버를 <strong>ORIGIN 서버</strong>라고 한다. _</p>
<h2 id="date">Date</h2>
<blockquote>
<p>메시지가 발생한 날짜와 시간</p>
</blockquote>
<p><code>Date : Tue, 15 Nov 1994 08:12:31 GMT</code>와 같이 사용한다. <strong>응답</strong>에서 사용한다.</p>
<hr>
<h1 id="특별한-정보">특별한 정보</h1>
<p>위 내용보다는 좀 특별한 헤더들에 대해 알아보자.
✔ <code>Host</code> : 요청한 호스트 정보(도메인)
✔ <code>Location</code> : 페이지 리다이렉션
✔ <code>Allow</code> : 허용 가능한 HTTP 메서드
✔ <code>Retry-After</code> : 유저 에이전트가 다음 요청을 하기까지 기다려야 하는 시간</p>
<h2 id="host">Host</h2>
<blockquote>
<p>요청한 호스트의 정보를 나타낸다.</p>
</blockquote>
<pre><code class="language-http">GET /search?q=hello&amp;hl=ko HTTP/1.1
Host : www.google.com</code></pre>
<p>요청에서 사용하는데, _<strong>필수값</strong>_이다. 하나의 서버가 여러 도메인을 처리할 때나 하나의 IP 주소에 여러 도메인이 적용되어 있을 때 구분을 해준다.</p>
<p>예시를 통해 간단히 살펴보자.
<img src="https://velog.velcdn.com/images/easy_on7/post/caa44f8b-d3dd-415f-ac69-c671355569db/image.png" alt=""></p>
<p><code>가상 호스트</code>라는 개념이 있다. 하나의 서버에 200.200.200.2라는 IP가 있는데 이 서버 안에 여러 애플리케이션이 다른 도메인으로 구동이 되고 있을 수 있다. </p>
<p>만약 <code>Host</code> 필드가 없다면 
<img src="https://velog.velcdn.com/images/easy_on7/post/2b273bb0-80bc-4255-aa91-e155fb4a662a/image.png" alt="">
위 그림과 같이 요청을 보내면, IP로만 통신을 하기 때문에 /hello가 3개의 도메인 중 어느 곳에 해당하는지 알 수가 없다.</p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/be8eed5e-aad3-405c-8367-3efd49c428eb/image.png" alt="">
따라서 이와 같이 <code>Host</code> 헤더 필드를 사용하여 어떤 도메인에 해당하는지까지 명시해주어야 한다.</p>
<h2 id="location">Location</h2>
<blockquote>
<p>웹 브라우저에서 3xx 응답의 결과에 <code>Location</code> 헤더가 있다면, <code>Location</code> 위치로 자동 이동한다.</p>
</blockquote>
<p><code>201(Created)</code> 응답에도 사용할 수 있는데, 이때 <code>Location</code> 값은 <strong>요청에 의해 생성된 리소스의 URI</strong>를 나타낸다.</p>
<h2 id="allow">Allow</h2>
<blockquote>
<p>허용 가능한 HTTP 메서드
405(Method Not Allowed)에서 응답에 포함해야 한다.</p>
</blockquote>
<p><code>Allow : GET, HEAD, PUT</code> 과 같은 형식으로 사용되는데, 서버에서 그렇게 많이 구현되어 있지는 않다.</p>
<h2 id="retry-after">Retry-After</h2>
<blockquote>
<p>유저 에이전트가 다음 요청을 하기까지 기다려야 하는 시간</p>
</blockquote>
<p><code>503(Service Unavailable)</code>에 사용되어 서비스가 언제까지 불능인지 알려줄 수 있다.</p>
<pre><code class="language-http">Retry-After : Fri, 31 Dec 1999 23:59:59 GMT</code></pre>
<p>와 같은 형식으로 사용된다. 날짜 단위로 표현하거나 초단위로 표기할 수 있다.</p>
<hr>
<h1 id="인증-헤더">인증 헤더</h1>
<p>인증과 관련된 헤더들이다.
✔ <code>Authorization</code> : 클라이언트 인증 정보를 서버에 전달
✔ <code>WWW-Authenticate</code> : 리소스 접근 시 필요한 인증 방법 정의</p>
<h2 id="authorization">Authorization</h2>
<blockquote>
<p>클라이언트 인증 정보를 서버에 전달하는 것이다.</p>
</blockquote>
<pre><code class="language-http">Authorization : Basic xxxxxxxxxxxxxxxx</code></pre>
<p>인증과 관련해서는 인증에 대한 메커니즘마다 Value 값이 다 다르다.</p>
<h2 id="www-authenticate">WWW-Authenticate</h2>
<blockquote>
<p>리소스 접근 시 필요한 인증 방법을 정의한다.</p>
</blockquote>
<pre><code class="language-http">WWW-Authenticate: Newauth realm=&quot;apps&quot;, type=1,
                  title=&quot;Login to \&quot;apps\&quot;&quot;, Basic realm=&quot;simple&quot;</code></pre>
<p><code>401(Unauthorized)</code> 응답과 함께 사용된다.</p>
<hr>
<h1 id="쿠키">쿠키</h1>
<p>많이 사용하고 중요한 쿠키에 대해 알아보자. 
쿠키를 사용할 땐 두 개의 헤더를 사용한다.
✔ <code>Set-Cookie</code> : 서버에서 클라이언트로 쿠키 전달(응답)
✔ <code>Cookie</code> : 클라이언트가 서버에서 받은 쿠키를 저장하고, HTTP 요청 시 서버로 전달</p>
<h4 id="쿠키를-사용하지-않는-경우">쿠키를 사용하지 않는 경우</h4>
<p>우선 쿠키를 사용하지 않는 경우를 살펴보자. </p>
<p>처음 welcome 페이지에 접근하는 상황이다.
<img src="https://velog.velcdn.com/images/easy_on7/post/380a8798-1b63-42ff-9a2f-983be0cd17a1/image.png" alt=""></p>
<p>이때 로그인을 아래와 같이 시도한다 해보자.
<img src="https://velog.velcdn.com/images/easy_on7/post/a37e3ba5-a18c-423c-87e3-31b65bed425d/image.png" alt="">
서버는 로그인에 대한 응답을 줄 것이다.</p>
<p>아래는 로그인을 한 상태에서 다시 welcome 페이지에 들어가는 상황이다.
<img src="https://velog.velcdn.com/images/easy_on7/post/d9b896f1-23b8-4e27-a719-da20f8452d6f/image.png" alt=""></p>
<p>이때 서버는 처음과 같이 &quot;손님&quot;으로 응답을 줄 것이다. 왜냐하면 서버에서는 로그인한 사용자인지 아닌지 구분할 수 있는 방법이 없기 때문이다.
즉, HTTP는 <code>Stateless</code>하기 때문이다. 서버와 클라이언트가 요청과 응답을 주고 받으면 연결이 끊어진다. 그래서 다시 요청하면 서버는 이전의 요청을 기억하지 못한다. <br>
이에 대한 대안으로 모든 요청에 사용자 정보를 포함해 요청을 보내는 방법이 있지만, 모든 요청과 링크에 사용자 정보를 포함해야 하기 때문에 개발, 보안 ... 등에서 많은 문제가 발생한다.<br></p>
<p>이 문제를 해결하기 위해 <code>Cookie(쿠키)</code>를 사용한다.</p>
<h4 id="쿠키를-사용하는-경우">쿠키를 사용하는 경우</h4>
<p>그럼, 쿠키를 사용하는 경우를 살펴보자.
<img src="https://velog.velcdn.com/images/easy_on7/post/86ed5c7f-1eff-4002-b3b4-50c7cc7a8dce/image.png" alt=""></p>
<p>웹 브라우저가 <code>POST</code>로 로그인 정보를 보내며 로그인을 시도한다. 이때 서버는 이를 받고 <code>Set-Cookie</code> 필드에 로그인 정보를 담아 응답을 한다. 웹 브라우저 내부에는 <code>쿠키 저장소</code>가 있는데 이 곳에 서버가 응답에서 만든 <code>Set-Cookie</code> 필드의 Key, Value 정보를 저장한다.</p>
<p>아래는 로그인 이후에 다시 welcome 페이지에 접속하는 상황이다.
<img src="https://velog.velcdn.com/images/easy_on7/post/8d276e35-018e-4d79-9973-d5852d1930eb/image.png" alt="">
이때 웹 브라우저는 서버에 요청을 보낼 때마다 자동으로 <code>쿠키 저장소</code>에서 값을 찾아 <code>Cookie</code> 헤더 필드에 담아 서버에 전송한다. 서버는 이 <code>Cookie</code>를 확인해 정보를 전달한다. </p>
<p>즉, 다음과 같이 모든 요청에 쿠키 정보가 자동으로 포함되는 것이다.
<img src="https://velog.velcdn.com/images/easy_on7/post/83d7de0c-6077-405a-b472-4a62b38c0c38/image.png" alt=""></p>
<hr>
<h4 id="참고">참고</h4>
<p>인프런 김영한님의 강의 - 모든 개발자를 위한 HTTP 웹 기본 지식</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] HTTP 상태코드]]></title>
            <link>https://velog.io/@easy_on7/HTTP-HTTP-%EC%83%81%ED%83%9C%EC%BD%94%EB%93%9C</link>
            <guid>https://velog.io/@easy_on7/HTTP-HTTP-%EC%83%81%ED%83%9C%EC%BD%94%EB%93%9C</guid>
            <pubDate>Mon, 26 Jun 2023 08:55:43 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 HTTP 상태 코드에 대해서 알아봅시다!</p>
<h1 id="상태-코드">상태 코드</h1>
<blockquote>
<p>클라이언트가 보낸 요청의 처리 상태를 <strong>응답(Response)</strong>에서 알려주는 기능</p>
</blockquote>
<p>상태 코드는 아래와 같이 크게 5 가지로 분류된다. </p>
<ol>
<li><code>1xx</code>(Information) : 요청이 수신되어 처리 중</li>
<li><code>2xx</code>(Successful) : 요청 정상 처리</li>
<li><code>3xx</code>(Redirection) : 요청을 완료하려면 추가 행동이 필요</li>
<li><code>4xx</code>(Client Error) : 클라이언트 오류, 잘못된 문법 등으로 서버가 요청을 수행할 수 없음</li>
<li><code>5xx</code>(Server Error) : 서버 오류, 서버가 정상 요청을 처리하지 못함</li>
</ol>
<p>하나 씩 살펴보자.</p>
<h1 id="1xx">1xx</h1>
<blockquote>
<p>요청이 수신되어 처리 중인 상태</p>
</blockquote>
<p><em>거의 사용되지 않으므로 생략하겠다.</em></p>
<hr>
<h1 id="2xx---성공">2xx - 성공</h1>
<blockquote>
<p>클라이언트의 요청을 성공적으로 처리한 상태</p>
</blockquote>
<p>✔️ <strong>200 OK</strong>
✔️ <strong>201 Created</strong>
✔️ <strong>202 Accepted</strong>
✔️ <strong>204 No Content</strong></p>
<h2 id="200-ok">200 OK</h2>
<blockquote>
<p>요청 성공</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/d86f7545-294f-44a9-97eb-33a4db3ec27f/image.png" alt=""></p>
<h2 id="201-created">201 Created</h2>
<blockquote>
<p>요청 성공해서 새로운 리소스가 생성됨</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/671d2f16-5f48-4ad3-b26f-a06e3962904f/image.png" alt=""></p>
<h2 id="202-accepted">202 Accepted</h2>
<blockquote>
<p>요청이 접수되었으나 처리가 완료되지 않았음</p>
</blockquote>
<p>배치 처리 같은 곳에서 사용한다. 예를 들어, 요청 접수 후 1시간 뒤에 배치 프로세스가 요청을 처리하는 것과 같은 상황이다. <em>잘 사용하지 않는다.</em></p>
<h2 id="204-no-content">204 No Content</h2>
<blockquote>
<p>서버가 요청을 성공적으로 수행했지만, 응답 페이로드 본문에 보낼 데이터가 없음</p>
</blockquote>
<p>예를 들어, 웹 문서 편집기에서 save 버튼에 대해 생각해보자. 이 버튼을 누르면 데이터가 <code>POST</code>를 통해 서버로 넘어가게 될 것이다. 서버는 이 내용을 저장한 뒤 클라이언트에게 따로 보낼 데이터는 없다. save 버튼의 결과로 아무 내용이 없어도 되고, 눌러도 같은 화면을 유지해야 한다. 이때 <strong>결과 내용이 없어도 204 메시지만으로 성공을 인식</strong>할 수 있다. </p>
<hr>
<h1 id="3xx---리다이렉션">3xx - 리다이렉션</h1>
<blockquote>
<p>요청을 완료하기 위해 유저 에이전트(웹 브라우저와 같은 클라이언트 프로그램)의 추가 조치 필요</p>
</blockquote>
<p>✔️ <strong>300 Multiple Choices</strong>
✔️ <strong>301 Moved Permanently</strong>
✔️ <strong>302 Found</strong>
✔️ <strong>303 See Other</strong>
✔️ <strong>304 Not Modified</strong>
✔️ <strong>307 Temporary Redirect</strong>
✔️ <strong>308 Permanent Redirect</strong></p>
<h2 id="리다이렉션redirect이란">리다이렉션(Redirect)이란?</h2>
<blockquote>
<p>웹 브라우저는 3xx 응답의 결과에 Location 헤더가 있으면, Location 위치로 자동으로 이동힌다.</p>
</blockquote>
<p>예시를 살펴보자.
<img src="https://velog.velcdn.com/images/easy_on7/post/6b592489-e147-49c2-b5fa-207949a34480/image.png" alt=""></p>
<p>원래 /event의 경로인 페이지를 사용했었는데, 이를 /new-event 경로로 변경했다고 생각해보자. 기존 사용자들이 /event를 입력해 들어왔다. 이때 <code>301 Moved Permanently</code>를 이용해 바뀐 경로를 전달할 수 있다. 클라이언트는 이를 확인해 Location에 있는 URL로 다시 요청을 보내 화면을 띄우게 된다. </p>
<p>리다이렉션에는 크게 3가지 종류가 있다.</p>
<h2 id="1-영구적인-리다이렉션">1. 영구적인 리다이렉션</h2>
<blockquote>
<p>특정 리소스의 URI가 영구적으로 이동
원래의 URL을 사용하지 않고, 검색 엔진 등에서도 변경을 인지한다.</p>
</blockquote>
<h3 id="301-moved-permanently">301 Moved Permanently</h3>
<blockquote>
<p>리다이렉트 시 <strong>요청 메서드가 <code>GET</code>으로 변하</strong>고 본문이 제거될 수 있다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/3daf3753-8430-4ac8-8c26-1ddba57c1530/image.png" alt=""></p>
<h3 id="308-permanent-redirect">308 Permanent Redirect</h3>
<blockquote>
<p>301과 기능은 같지만, 요청 메서드와 본문을 유지한다.
(처음 <code>POST</code>를 보내면 리다이렉트도 <code>POST</code>를 유지)</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/a0922473-e0a7-4780-b113-5b5cba90d74a/image.png" alt=""></p>
<h2 id="2-일시적인-리다이렉션">2. 일시적인 리다이렉션</h2>
<blockquote>
<p>리소스의 URI가 일시적으로 변경(<em>가장 많이 사용한다.</em>)
따라서, 검색 엔진 등에서 URL을 변경하면 안됨.</p>
</blockquote>
<h3 id="302-found">302 Found</h3>
<blockquote>
<p>리다이렉트 시 <strong>요청 메서드가 <code>GET</code>으로 변하</strong>고 본문이 제거될 수 있다. 안될 수도 있음.</p>
</blockquote>
<h3 id="307-temporary-redirect">307 Temporary Redirect</h3>
<blockquote>
<p>302과 기능은 같지만, 요청 메서드와 본문을 유지한다. 요청 메서드를 변경하면 안된다.</p>
</blockquote>
<h3 id="303-see-other">303 See Other</h3>
<blockquote>
<p>302와 기능이 같고, 리다이렉트 시 <strong>요청 메서드가 <code>GET</code>으로 변경</strong>된다.</p>
</blockquote>
<p>그렇다면 일시적인 리다이렉션을 언제 사용할까? 예시를 통해 살펴보자.</p>
<h3 id="prg--postredirectget">PRG : Post/Redirect/Get</h3>
<p>만약에 상품 주문 페이지에서 <code>POST</code>로 주문 후에 웹 브라우저를 새로고침하면 어떻게 될까? 새로고침은 다시 요청을 하는 것이므로 서버에 중복 주문이 될 수 있다. 요청했던 것을 새로고침하기 때문에 <code>POST</code>요청을 한 번 더 하기 때문이다. </p>
<h4 id="prg-사용-전">PRG 사용 전</h4>
<p>기존의 경우를 살펴보자.</p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/33e21fa8-e4d8-47b4-8547-d13054f75267/image.png" alt=""></p>
<p>HTML Form에 주문할 상품들을 넣고 <code>POST</code> 요청을 보냈다. 그럼 서버는 이를 받아 DB에 저장할 것이다. 그리고 이에 대한 응답으로 <code>200 OK</code>를 보낼 것이다. 이때 클라이언트가 새로고침을 누르면 <code>POST</code> 요청을 다시 보내게되어, 주문 데이터 한 건이 더 저장되어 버린다. </p>
<p>이를 해결하기 위해, 리다이렉션을 사용한다. <code>POST</code>로 주문 후에 새로 고침으로 인한 중복 저장을 방지한다. </p>
<blockquote>
<p><code>POST</code>로 요청 후에 결과 화면을 <code>GET</code> 메서드로 리다이렉트</p>
</blockquote>
<p>이렇게 되면 새로고침을 해도 결과 화면을 <code>GET</code>으로 조회할 수 있다. 중복 주문 대신에 결과 화면만 <code>GET</code>으로 다시 요청하게 되는 것이다. 
<img src="https://velog.velcdn.com/images/easy_on7/post/782c08ec-534d-4ab0-b727-e32376ed6f0f/image.png" alt=""></p>
<p>위 예시 사진을 살펴보면, 클라이언트가 처음 요청을 보내 서버는 이를 받아 데이터를 DB에 저장하고 응답을 보낼 때, <strong><code>302 Found</code></strong>를 전달한다. </p>
<p>즉, 이미 URL이 이미 <code>POST</code> → <code>GET</code>으로 리다이렉트 된 것이다. 새로고침해도 <code>GET</code>으로 결과 화면만 조회된다. </p>
<h2 id="3-기타-리다이렉션">3. 기타 리다이렉션</h2>
<h3 id="300-multiple-choices">300 Multiple Choices</h3>
<p><em>잘 사용하지 않는다.</em></p>
<h3 id="304-not-modified">304 Not Modified</h3>
<blockquote>
<p><strong>캐시</strong>를 목적으로 사용한다.
클라이언트에게 리소스가 수정되지 않았음을 알려준다. 따라서 클라이언트는 로컬PC에 저장된 캐시를 재사용한다(캐시로 리다이렉트 한다).</p>
</blockquote>
<p>304 응답은 로컬 캐시를 사용해야 하기 때문에 <strong>응답에 메시지 바디를 포함하면 안된다</strong>..!
조건부 <code>GET</code>, <code>HEAD</code> 요청 시에 사용한다. </p>
<p>_캐시에 대한 내용은 여기서 더 자세히 알아볼 수 있다. _</p>
<hr>
<h1 id="4xx---클라이언트-오류">4xx - 클라이언트 오류</h1>
<blockquote>
<p>클라이언트의 요청에 잘못된 문법 등의 문제로 서버가 요청을 수행할 수 없는 경우
<U>오류의 원인</U>이 <strong>클라이언트</strong>에 있다!</p>
</blockquote>
<p>여기 4xx 오류와 5xx 오류의 가장 큰 차이점이 있다. </p>
<blockquote>
<p>4xx 오류는 클라이언트가 이미 잘못된 요청, 데이터를 보내고 있기 때문에, 똑같은 재시도가 실패한다. 5xx 오류는 서버에서 DB 등의 문제가 해결된다면 똑같은 재시도 시 성공할 가능성이 있다. </p>
</blockquote>
<h2 id="400-bad-request">400 Bad Request</h2>
<blockquote>
<p>클라이언트가 잘못된 요청을 해서 서버가 요청을 처리할 수 없는 상태</p>
</blockquote>
<p>주로 요청 파라미터가 잘못되거나, API 스펙이 맞지 않을 때 발생한다.</p>
<h2 id="401-unauthorized">401 Unauthorized</h2>
<blockquote>
<p>클라이언트가 해당 리소스에 대한 인증이 필요한 경우</p>
</blockquote>
<p>인증(Authentication) 되지 않은 경우이다. 401 오류 발생 시 응답에 <code>WWW-Authenticate</code> 헤더와 함께 인증 방법을 설명해야 한다.</p>
<h2 id="403-forbidden">403 Forbidden</h2>
<blockquote>
<p>서버가 요청을 이해했지만 승인을 거부하는 경우</p>
</blockquote>
<p>주로 인증 자격 증명은 있지만, 접근 권한이 불충분한 경우이다. ADMIN 등급이 아닌 사용자가 로그인은 했지만, ADMIN 등급의 리소스에 접근하는 것과 같은 경우이다. </p>
<h2 id="404-not-found">404 Not Found</h2>
<blockquote>
<p>요청 리소스를 찾을 수 없는 경우</p>
</blockquote>
<p>요청 리소스가 서버에 없거나, 클라이언트가 권한이 부족한 리소스의 접근할 때 해당 리소스를 숨기고 싶은 경우이다. </p>
<hr>
<h1 id="5xx---서버-오류">5xx - 서버 오류</h1>
<blockquote>
<p>서버 문제로 오류가 발생한 경우
서버에 문제가 있기 때문에 재시도하면 (서버가 복구된다면) 성공할 수도 있다.</p>
</blockquote>
<h2 id="500-internal-server-error">500 Internal Server Error</h2>
<blockquote>
<p>서버 문제로 오류 발생, 애매하면 500 오류</p>
</blockquote>
<p>말 그대로 서버 내부 문제로 오류가 발생한 경우이다. 따라서 백엔드에서 발생한 애매한 오류는 <code>500</code>으로 내면된다. </p>
<h2 id="503-service-unavailable">503 Service Unavailable</h2>
<blockquote>
<p>서비스 이용 불가
서버가 일시적인 과부하 또는 예정된 작업으로 잠시 요청을 처리할 수 없는 경우</p>
</blockquote>
<p>이때, <code>Retry-After</code> 헤더 필드를 이용해 얼마 뒤에 복구되는지 예상 시간을 보낼 수 있다.</p>
<p><em>웬만해서는 서버에서 5xx 오류를 만들면 안된다.</em></p>
<hr>
<h4 id="참고">참고</h4>
<p>인프런 김영한님의 강의 - 모든 개발자를 위한 HTTP 웹 기본 지식</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] HTTP API 설계]]></title>
            <link>https://velog.io/@easy_on7/HTTP-HTTP-API-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@easy_on7/HTTP-HTTP-API-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Sat, 24 Jun 2023 16:31:58 GMT</pubDate>
            <description><![CDATA[<p>HTTP API 설계를 몇 가지 예시를 통해 한 번 알아봅시다! 아래 3가지 예시를 다루겠습니다.</p>
<h4 id="1-http-api---컬랙션">1. HTTP API - 컬랙션</h4>
<ul>
<li><strong><code>POST</code></strong> 기반 등록</li>
<li>예) 회원 관리 API 제공<h4 id="2-http-api---스토어">2. HTTP API - 스토어</h4>
</li>
<li><strong><code>PUT</code></strong>  기반 등록</li>
<li>예) 정적 컨텐츠 관리, 원격 파일 관리<h4 id="3-html-form-사용">3. HTML FORM 사용</h4>
</li>
<li>웹 페이지 회원 관리</li>
<li><strong><code>GET</code></strong>, <strong><code>POST</code></strong>만 지원</li>
</ul>
<br>
이제 하나씩 살펴 봅시다.

<hr>
<h1 id="회원-관리-시스템컬렉션">회원 관리 시스템(컬렉션)</h1>
<p>회원 관리 시스템을 만들어야 한다고 가정하자. 이때, 아래와 같이 URI을 설계할 수 있다.</p>
<h2 id="api-설계---post-기반-등록">API 설계 - POST 기반 등록</h2>
<table>
<thead>
<tr>
<th align="left">기능</th>
<th align="left">URI</th>
<th align="left">Method</th>
</tr>
</thead>
<tbody><tr>
<td align="left">회원 목록</td>
<td align="left">/members</td>
<td align="left"><strong>GET</strong></td>
</tr>
<tr>
<td align="left">회원 등록</td>
<td align="left">/members</td>
<td align="left"><strong>POST</strong></td>
</tr>
<tr>
<td align="left">회원 조회</td>
<td align="left">/members/{id}</td>
<td align="left"><strong>GET</strong></td>
</tr>
<tr>
<td align="left">회원 수정</td>
<td align="left">/members/{id}</td>
<td align="left"><strong>PATCH, PUT, POST</strong></td>
</tr>
<tr>
<td align="left">회원 삭제</td>
<td align="left">/members/{id}</td>
<td align="left"><strong>DELETE</strong></td>
</tr>
</tbody></table>
<p>회원 목록을 조회하는 경우는 /members를 이용해 회원 데이터를 JSON 형식으로 보내주면 된다. 만약 정렬을 원한다면, 정렬 조건을 쿼리 파라미터에 넣어 보내면 된다.</p>
<p>회원 등록은 <code>POST</code>를 이용한다.</p>
<p>회원 조회는 /members 아래에 회원의 id를 넣어주면 <code>GET</code>을 통해 얻어 올 수 있다. 회원 삭제도 마찬가지.</p>
<p>회원 수정 부분을 살펴보면 <code>PATCH</code>를 주로 이용하는데, 부분 수정을 할 수 있기 때문이다. <code>PUT</code>을 이용하여 수정하려면 모든 회원 정보를 보내야한다. 따라서 <code>PUT</code> 은 게시글 수정과 같이 글 전체를 수정하는 경우 사용할 수 있다. </p>
<h2 id="post---신규-자원-등록-특징">POST - 신규 자원 등록 특징</h2>
<p>✔ 클라이언트는 등록될 리소스의 URI를 모른다.</p>
<ul>
<li>회원 등록 : /members → <code>POST</code></li>
<li><code>POST /members</code> </li>
</ul>
<p>✔ <strong>서버</strong>가 <U>새로 등록된 리소스 URI를 생성</U>해준다. </p>
<pre><code class="language-http">HTTP/1.1 201 Created
Location: /members/100</code></pre>
<p>✔ 컬렉션(Collection)</p>
<ul>
<li>위와 같은 형식을 <code>컬렉션</code>이라 한다.</li>
<li>서버가 관리하는 리소스 리렉토리</li>
<li>서버가 리소스의 URI를 생성하고 관리</li>
<li>여기서 컬렉션은 <code>/members</code></li>
</ul>
<hr>
<h1 id="파일-관리-시스템스토어">파일 관리 시스템(스토어)</h1>
<p>이번에는 <code>PUT</code> 기반의 API를 설계해보자.</p>
<h2 id="api-설계---put-기반-등록">API 설계 - PUT 기반 등록</h2>
<table>
<thead>
<tr>
<th align="left">기능</th>
<th align="left">URI</th>
<th align="left">Method</th>
</tr>
</thead>
<tbody><tr>
<td align="left">파일 목록</td>
<td align="left">/files</td>
<td align="left"><strong>GET</strong></td>
</tr>
<tr>
<td align="left">파일 조회</td>
<td align="left">/files/{filename}</td>
<td align="left"><strong>GET</strong></td>
</tr>
<tr>
<td align="left">파일 등록</td>
<td align="left">/files/{filename}</td>
<td align="left"><strong>PUT</strong></td>
</tr>
<tr>
<td align="left">파일 삭제</td>
<td align="left">/files/{filename}</td>
<td align="left"><strong>DELETE</strong></td>
</tr>
<tr>
<td align="left">파일 대량 등록</td>
<td align="left">/files</td>
<td align="left"><strong>POST</strong></td>
</tr>
</tbody></table>
<p>파일 등록하는 부분을 살펴보자. 클라이언트가 파일을 등록하기 때문에 filename은 클라이언트가 알고 있다. 이때 <code>PUT</code>을 사용한다.
<code>PUT</code>은 리소스가 없으면 생성하고, <strong>존재하면 기존의 리소스를 덮어</strong>버린다. 이와 같은 경우는 <code>PUT</code> 메서드가 제격이다!</p>
<h2 id="put---신규-자원-등록-특징">PUT - 신규 자원 등록 특징</h2>
<p>✔ 클라이언트가 리소스 URI를 <U>알고 있어야 한다.</U></p>
<ul>
<li>파일 등록 : /files/{filename} → <code>PUT</code></li>
<li><code>PUT /files/star.jpg</code></li>
</ul>
<p>✔ <U>클라이언트</U>가 <strong>직접 리소스의 URI를 지정</strong>한다.
✔ 스토어(Store)</p>
<ul>
<li>위와 같은 형식을 <code>스토어</code>라고 한다.</li>
<li>클라이언트가 리소스의 URI를 알고 관리</li>
<li>여기서 스토어는 <code>/files</code></li>
</ul>
<blockquote>
<p>대부분 <code>POST</code>기반의 <code>컬렉션</code>을 사용한다. <code>PUT</code>을 사용하는 비중은 매우 적다.  </p>
</blockquote>
<hr>
<h1 id="html-form-사용">HTML FORM 사용</h1>
<blockquote>
<p>HTML FORM은 기본적으로 <code>GET</code>, <code>POST</code>만 지원한다.</p>
</blockquote>
<p><em>물론, AJAX 같은 기술을 사용해 위의 회원 API에서 사용되는 메서드들을 모두 사용할 수 있긴하다. 여기서 이야기하는 것은 순수 HTML, HTML FORM을 의미한다.</em></p>
<h2 id="api-설계">API 설계</h2>
<table>
<thead>
<tr>
<th align="left">기능</th>
<th align="left">URI</th>
<th align="left">Method</th>
</tr>
</thead>
<tbody><tr>
<td align="left">회원 목록</td>
<td align="left">/members</td>
<td align="left"><strong>GET</strong></td>
</tr>
<tr>
<td align="left">회원 등록 폼</td>
<td align="left">/members/new</td>
<td align="left"><strong>GET</strong></td>
</tr>
<tr>
<td align="left">회원 등록</td>
<td align="left"><U>/members/new</U>, /members</td>
<td align="left"><strong>POST</strong></td>
</tr>
<tr>
<td align="left">회원 조회</td>
<td align="left">/members/{id}</td>
<td align="left"><strong>GET</strong></td>
</tr>
<tr>
<td align="left">회원 수정 폼</td>
<td align="left">/members/{id}/edit</td>
<td align="left"><strong>GET</strong></td>
</tr>
<tr>
<td align="left">회원 수정</td>
<td align="left"><U>/members/{id}/edit</U>, /members/{id}</td>
<td align="left"><strong>POST</strong></td>
</tr>
<tr>
<td align="left">회원 삭제</td>
<td align="left">/members/{id}/delete</td>
<td align="left"><strong>POST</strong></td>
</tr>
</tbody></table>
<p>회원 등록 버튼을 누르면, /members/new 라는 곳으로 들어가게 된다. 이때 회원 등록 폼을 <code>GET</code>으로 가져온다. 여기서 회원 정보를 입력하고 저장(제출) 버튼을 누르면 <code>POST</code>로 넘어가게 된다. 이때 위의 표에서 보는 것처럼 두 가지 URI 중 하나를 선택해 사용하면 된다(보통 <code>GET</code>과 맞추는 것을 선호).
회원 수정도 위와 같은 과정으로 진행된다. </p>
<p>회원 삭제의 경우, <code>DELETE</code>를 사용하지 못하기 때문에 <strong><code>컨트롤 URI</code></strong> 를 사용한다.</p>
<h3 id="컨트롤-uri">컨트롤 URI</h3>
<p>✔ HTML FORM은 <code>GET</code>, <code>POST</code>만 지원하므로 제약이 있다.
✔ 이런 제약을 해결하기 위해 동사로 된 리소스 경로를 사용한다.
✔ <code>POST</code>의 <code>/new</code>, <code>/edit</code>, <code>/delete</code>가 <strong>컨트롤 URI</strong>이다.
✔ HTTP 메서드로 해결하기 애매한 경우 사용한다(HTTP API 포함).</p>
<hr>
<h2 id="참고하면-좋은-uri-설계-개념">참고하면 좋은 URI 설계 개념</h2>
<p>✔ 문서(Document)</p>
<ul>
<li>단일 개념(파일 하나, 객체 인스턴스, 데이터베이스 row)</li>
<li>예) /members/100, /files/star.jpg</li>
</ul>
<p>✔ 컬랙션(Collection)</p>
<ul>
<li>서버가 관리하는 리소스 디렉토리</li>
<li>서버가 리소스의 URI를 생성하고 관리</li>
<li>예) /members</li>
</ul>
<p>✔ 스토어(Store)</p>
<ul>
<li>클라이언트가 관리하는 자원 저장소</li>
<li>클라이언트가 리소스의 URI를 알고 관리</li>
<li>예) /files</li>
</ul>
<p>✔ 컨트롤러(Controller), 컨트롤 URI</p>
<ul>
<li>문서, 컬렉션, 스토어로 해결하기 어려운 추가 프로세스 실행</li>
<li>동사를 직접 사요</li>
<li>예) /members/{id}/delete</li>
</ul>
<blockquote>
<p><a href="https://restfulapi.net/resource-naming/">https://restfulapi.net/resource-naming/</a></p>
</blockquote>
<hr>
<h4 id="참고">참고</h4>
<p>인프런 김영한님의 강의 - 모든 개발자를 위한 HTTP 웹 기본 지식</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] HTTP 메서드 활용]]></title>
            <link>https://velog.io/@easy_on7/HTTP-HTTP-%EB%A9%94%EC%84%9C%EB%93%9C-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@easy_on7/HTTP-HTTP-%EB%A9%94%EC%84%9C%EB%93%9C-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Sat, 24 Jun 2023 12:54:08 GMT</pubDate>
            <description><![CDATA[<p>이전 포스팅에서 살펴본 HTTP 메서드들이 개발에서 어떤 식으로 활용이 되는지를 알아봅시다!</p>
<p>먼저 클라이언트에서 서버로 어떤 식으로 데이터를 전송하는지 알아봅시다. 그리고 HTTP API를 어떻게 설계할 지 살펴보도록 합시다.</p>
<h1 id="클라이언트-→-서버-데이터-전송">클라이언트 → 서버 데이터 전송</h1>
<p>데이터를 전송하는 방식은 크게 2가지로 나눌 수 있다. </p>
<h3 id="1-쿼리-파라미터를-통한-데이터-전송">1. 쿼리 파라미터를 통한 데이터 전송</h3>
<blockquote>
<p>URI 끝에 쿼리 파라미터를 넣어 데이터를 전송하는 방식</p>
</blockquote>
<p>주로, <code>GET</code> 메서드와 사용하고, 정렬 필터(검색어) 같은 것을 사용할 때 이용한다. 
검색창에 검색어를 입력하거나, 게시판 리스트에 정렬 조건을 넣거나 할 때 주로 이용한다.</p>
<h3 id="2-메시지-바디를-통한-데이터-전송">2. 메시지 바디를 통한 데이터 전송</h3>
<blockquote>
<p>HTTP-message body에 데이터를 넣어 전송하는 방식</p>
</blockquote>
<p>이때는 <code>POST</code>, <code>PUT</code>, <code>PATCH</code> 메서드를 사용한다. 
회원 가입, 상품 주문, 리소스 등록 • 변경 등과 같은 경우 이용한다.
<br>
클라이언트에서 서버로 데이터를 전송하는 4가지 상황을 예시를 통해 알아보자.</p>
<h2 id="1-정적-데이터-조회">1. 정적 데이터 조회</h2>
<blockquote>
<p>이미지, 정적 텍스트 문서 등과 같은 정적 데이터를 전송하는 과정</p>
</blockquote>
<h3 id="쿼리-파라미터-미사용">쿼리 파라미터 미사용</h3>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/e006bb82-ef80-4648-9c30-d3bb9dcd3051/image.png" alt="">
위 예시는 서버가 &quot;별&quot; 이미지를 클라이언트에게 내려주는 과정이다. 이런 경우에는 추가적인 데이터를 전달하는 것이 없다. 클라이언트는 단순하게 URI 경로만 넣으면 서버는 이를 받아 이미지 리소스를 만들어 클라이언트에게 전달한다. 이때는 쿼리 파라미터를 사용하지 않는다. <br></p>
<p>정리를 하자면,</p>
<ul>
<li>이미지, 정적 텍스트 문서</li>
<li>조회는 <code>GET</code> 사용</li>
<li>정적 데이터는 일반적으로 쿼리 파라미터 없이 리소스 경로로 단순하게 조회 가능</li>
</ul>
<h2 id="2-동적-데이터-조회">2. 동적 데이터 조회</h2>
<blockquote>
<p>주로 검색, 게시판 목록 등에서 정렬 필터(검색어)와 같은 동적 데이터를 전송하는 과정</p>
</blockquote>
<h3 id="쿼리-파라미터-사용">쿼리 파라미터 사용</h3>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/e5f16940-47eb-4681-b079-6722acc830ea/image.png" alt="">
검색어와 같은 추가 데이터들을 전달하기 위해 쿼리 파라미터를 사용한다.</p>
<ul>
<li>조회 조건을 줄여주는 필터, 조회 결과를 정렬하는 정렬 조건에 주로 사용</li>
<li>조회는 <code>GET</code> 사용</li>
<li><code>GET</code>은 쿼리 파라미터를 사용해서 데이터를 전달</li>
</ul>
<h2 id="3-html-form-데이터-전송">3. HTML Form 데이터 전송</h2>
<blockquote>
<p>회원 가입, 상품 주문, 데이터 변경 등에서 사용</p>
</blockquote>
<h3 id="post-전송---저장">POST 전송 - 저장</h3>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/6e29ecba-e2a2-4472-917e-ad2cbe9b3f8e/image.png" alt="">
위의 예시에서 HTML로 작성된 폼을 통해 &quot;전송&quot; 버튼을 누르면, 웹 브라우저가 Form의 데이터를 읽어 왼쪽 그림과 같이 HTTP message를 생성해준다. </p>
<h3 id="get-전송---저장">GET 전송 - 저장</h3>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/a21777cb-3dff-4584-bf49-718cbe50e360/image.png" alt="">
HTML Form으로 데이터를 전송할 때, <code>GET</code> 메서드를 사용할 수도 있다. 이 경우에는 데이터를 <strong>쿼리 파라미터</strong>에 넣게 된다.</p>
<blockquote>
<p>정리하자면,
<strong><code>POST</code></strong>의 경우 : 데이터는 message body에 입력
<strong><code>GET</code></strong>의 경우 : 쿼리 파라미터로 입력</p>
</blockquote>
<h3 id="get-전송---조회">GET 전송 - 조회</h3>
<p>위의 예시는 잘못되었다! <code>GET</code>메서드는 다음과 같이 조회에만 사용되어야지, 리소스가 변경되는 곳(ex. 저장)에서 사용되면 안된다!
<img src="https://velog.velcdn.com/images/easy_on7/post/88bfa4cf-bb9f-4546-8c8e-26b14c3381d8/image.png" alt=""></p>
<h3 id="multipartform-data">multipart/form-data</h3>
<p>추가적으로, HMTL form 전송에서 <strong>파일과 같은 것들을 전송</strong>할 때 사용하는 타입이 있다. 바로 <code>multipart/form-data</code>이다.</p>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/06eef80a-f236-4351-af2c-73eac9348738/image.png" alt=""></p>
<p>왼쪽 HTTP 메시지를 보면, <code>Content-Type</code>은 <code>mulitpart/form-data</code>로 들어가고, <code>boundary</code>속성을 가지고 파싱하게 된다(웹 브라우저가 자동으로 만들어 준다). 
이렇게 여러 개의 part로 분리되어 들어가 <strong>multipart</strong>이다.
<br> 
<strong>HTTP Form 데이터 전송</strong>에 대한 정리를 해보면,</p>
<ul>
<li>HTML Form submit 시 <code>POST</code> 전송<ul>
<li>예) 회원 가입, 상품 주문, 데이터 변경</li>
</ul>
</li>
<li><code>Content-Type : application/x-www-form-urlencoded</code> 사용<ul>
<li>form의 내용을 메시지 바디를 통해서 전송(Key=Value 형태, 쿼리 파라미터 형식)</li>
<li>전송 데이터를 url encoding 처리<ul>
<li>예) abc김 → abc%EA%B9%80 (UTF-8)</li>
</ul>
</li>
</ul>
</li>
<li>HTML Form은 <code>GET</code> 전송도 가능</li>
<li><code>Content-Type : multipart/form-data</code><ul>
<li>파일 업로드 같은 바이너리 데이터 전송 시 사용</li>
<li>다른 종류의 여러 파일과 폼의 내용을 함께 전송 가능</li>
</ul>
</li>
<li>참고 : HTML Form 전송은 <code>GET</code>, <code>POST</code>만 지원</li>
</ul>
<h2 id="4-http-api-전송">4. HTTP API 전송</h2>
<p><img src="https://velog.velcdn.com/images/easy_on7/post/cd629cc6-e0ef-44bb-9d3f-a12c9510ad0c/image.png" alt="">
애플리케이션에서 클라이언트에서 서버로 바로 데이터를 전송하는 경우, 위의 예시처럼 직접 만들어 넘겨주면 된다. </p>
<ul>
<li>서버 to 서버<ul>
<li>백엔드 시스템 통신</li>
</ul>
</li>
<li>앱 클라이언트<ul>
<li>아이폰, 안드로이드</li>
</ul>
</li>
<li>웹 클라이언트<ul>
<li>HTML에서 Form 전송 대신 자바 스크립트를 통한 통신에 사용(AJAX)</li>
<li>예) React, VueJs 같은 웹 클라이언트와 API 통신</li>
</ul>
</li>
<li><code>POST</code>, <code>PUT</code>, <code>PATCH</code> : 메시지 바디를 통해 데이터 전송</li>
<li><code>GET</code> : 조회, 쿼리 파라미터로 데이터 전달</li>
<li><code>Content-Type : application/json</code> 을 주로 사용(사실상 표준)<ul>
<li>TEXT, XML, JSON 등등</li>
</ul>
</li>
</ul>
<hr>
<h4 id="참고">참고</h4>
<p>인프런 김영한님의 강의 - 모든 개발자를 위한 HTTP 웹 기본 지식</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] HTTP 메서드]]></title>
            <link>https://velog.io/@easy_on7/HTTP-HTTP-%EB%A9%94%EC%84%9C%EB%93%9C</link>
            <guid>https://velog.io/@easy_on7/HTTP-HTTP-%EB%A9%94%EC%84%9C%EB%93%9C</guid>
            <pubDate>Sat, 24 Jun 2023 06:16:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>HTTP 메서드는 클라이언트가 서버에게 무엇인가를 요청할 때 기대하는 행동이다. </p>
</blockquote>
<p>주로 사용하는 메서드는 다음과 같이 5가지가 있다. </p>
<ol>
<li><code>GET</code> : 리소스 조회</li>
<li><code>POST</code> : 요청 데이터 처리, 주로 등록에 사용</li>
<li><code>PUT</code> : 리소스를 대체, 해당 리소스가 없으면 생성</li>
<li><code>PATCH</code> : 리소스 부분 변경</li>
<li><code>DELETE</code> : 리소스 삭제</li>
</ol>
<p>이 외에도 HEAD, OPTIONS, CONNECT, TRACE와 같은 메서드들이 존재한다.</p>
<ul>
<li>HEAD : GET과 동일하지만 메시지 부분을 제외하고, 상태 줄과 헤더만 반환</li>
<li>OPTIONS : 대상 리소스에 대한 통신 가능 옵션(메서드)을 설명(주로 CORS에서 사용)</li>
<li>CONNECT : 대상 자원으로 식별되는 서버에 대한 터널을 설정</li>
<li>TRACE : 대상 리소스에 대한 경로를 따라 메시지 루프백 테스트를 수행</li>
</ul>
<br>
여기서는 맨 위의 5가지 메서드들에 대해서만 자세히 알아보자.

<hr>
<h1 id="get">GET</h1>
<blockquote>
<p>GET은 이름 그대로 <strong>리소스를 조회</strong>하는 것이다.</p>
</blockquote>
<p><img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F75wY5%2FbtrWcaZkU2y%2F33vQhEyxZEHop9yKMPmmD1%2Fimg.png" alt=""></p>
<p>서버에 전달하고 싶은 데이터는 <strong>query(쿼리 파라미터, 쿼리 스트링)를 통해서 전달</strong>한다. 메시지 바디를 사용해 데이터를 전달할 수 있지만, 지원하지 않는 곳이 많아서 권장하지 않는다. </p>
<p><img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDoEC1%2FbtrWcbYfYZR%2FZpKbmU8lxNyDpd8VRI4DR1%2Fimg.png" alt=""></p>
<p>위와 같은 상황을 보자.
클라이언트가 /members/100의 데이터를 달라는 요청을 보냈다. 서버는 이를 확인하고 내부의 데이터베이스를 조회해 오른쪽과 같은 JSON 메시지를 이용해 응답메시지를 만들어 클라이언트에 전송한다.
<img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIw0ES%2FbtrV7QhqBiS%2Ff7KmqG7woLyWSgydr6aTz0%2Fimg.png" alt=""></p>
<br>
# POST
> POST는 **요청 데이터를 처리**하는 것이다. 

<p>GET은 요청할 때 데이터를 전달하지 않는다. 하지만 POST는 클라이언트에서 서버로 요청을 보낼 때, <strong>메시지 바디를 통해 서버에서 요청 데이터를 처리하도록 전달하는 것</strong>이다. 서버는 이 요청 데이터를 처리한다(메시지 바디를 통해 들어온 데이터를 처리하는 모든 기능을 수행한다).
주로 전달된 데이터로 <U>신규 리소스 등록</U>, <U>프로세스 처리</U>에 사용된다.
<img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMb1a0%2FbtrV86c9uGI%2FdF4PQAFArftSlhUYWJHMh0%2Fimg.png" alt="">
위의 상황을 보자. 
클라이언트는 POST를 통해 /members라는 곳에 리소스를 전달한다. 이때 서버와 클라이언트는 /members에 POST로 전송하면 그 데이터는 서버가 저장을 하거나 내부 프로세스를 처리하는 데 사용할 것이라는 것을 미리 약속해 놓는다. 지금은 신규로 등록을 하는 것이라 가정하자.
<img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbMmtM%2FbtrV68bAI8o%2FlYCInKvAmVOkEoxRWs9Lw1%2Fimg.png" alt="">
서버는 이를 열어 확인해 보고 POST에 /members로 왔기 때문에 데이터베이스에 등록을 하고 신규 리소스 식별자를 생성하고 응답 메시지를 클라이언트에게 보내준다.
<img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcNy6E%2FbtrV67X4XKs%2FoK0j7wkH9MeRnRtasGPgTk%2Fimg.png" alt="">
신규로 자원이 생성되면 보통 HTTP 201 Created로 응답 메시지를 전송한다. 200으로 보내도 상관없다. <code>Location</code>은 <strong>자원이 생성된 URI 경로</strong>를 나타낸다.<br>
POST는 다음과 같은 기능에 사용된다. </p>
<ul>
<li>HTML 양식에 입력된 필드와 같은 데이터 블록을 데이터 처리 프로세스에 제공<ul>
<li>예) HTML FORM에 입력한 정보로 회원 가입, 주문 등에서 사용</li>
</ul>
</li>
<li>게시판, 뉴스 그룹, 메일링 리스트, 블로그 또는 유사한 기사 그룹에 메시지 게시<ul>
<li>예) 게시판 글쓰기, 댓글 달기</li>
</ul>
</li>
<li>서버가 아직 식별하지 않은 새 리소스 생성<ul>
<li>예) 신규 주문 생성</li>
</ul>
</li>
<li>기존 자원에 데이터 추가<ul>
<li>예) 한 문서 끝에 내용 추가하기</li>
</ul>
</li>
</ul>
<blockquote>
<p>이 리소스 URI에 POST 요청이 오면 요청 데이터를 어떻게 처리할지 정해진 것이 없이 리소스마다 따로 정해야 한다. </p>
</blockquote>
<h1 id="put">PUT</h1>
<blockquote>
<p>PUT은 <strong>리소스를 대체</strong>하는 것이다.</p>
</blockquote>
<p>폴더에 파일을 복사하는 것과 비슷하다. 이때 <strong>리소스가 있으면 대체</strong>하고 <strong>리소스가 없으면 생성</strong>한다. 쉽게 이야기하면 덮어버리는 것이다. 
<img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVPJ6R%2FbtrWkwiJBEJ%2F27V7cFfplrkoa1v0YwhnO0%2Fimg.png" alt="">
중요한 것은 구체적인 리소스 경로를 다 알고 있어 클라이언트가 리소스를 식별한다는 것이다. 이것이 POST와의 큰 차이점이다. </p>
<blockquote>
<p>클라이언트가 리소스 위치를 알고 URI를 지정</p>
</blockquote>
<p><img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLGBOj%2FbtrWpnFj7gT%2FY6uefkJQ1Z3KA3i5H8kCv1%2Fimg.png" alt="">
위의 상황과 같이 이미 /members/100에 데이터가 있는 상황이라면,
<img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCLjnT%2FbtrWqq2VgqR%2Fuwig9Gky3nrtrsd9BEhokk%2Fimg.png" alt="">
이렇게 리소스가 대체된다.</p>
<p>리소스가 없으면 신규 리소스가 생성될 것이다. <br>
여기서 중요하게 봐야할 점이 있다. 
<img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG9Tvo%2FbtrWq97jkMu%2F3PhNqKtt8e6RJ1mSzhyyYk%2Fimg.png" alt="">
위의 상황과 같이 &quot;young&quot; 데이터의 나이 데이터가 잘못되어 이를 수정하고자 PUT을 사용해 age 값만을 넘겨버리면 다음과 같은 문제가 발생한다. 
<img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftljvb%2FbtrWq28jspL%2F2g0n3Bjp8Buf1zxEfYwKp1%2Fimg.png" alt="">
PUT은 <strong>리소스를 완전히 대체</strong>하기 때문에 넘겨준 age만 저장되고 username 필드가 삭제되어 버린다.<br>
이럴 때 사용하는 메서드가 <strong>PATCH</strong>이다.</p>
<h1 id="patch">PATCH</h1>
<blockquote>
<p>PATCH는 리소스를 부분 변경하는 것이다.</p>
</blockquote>
<p><img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ83ww%2FbtrWqDgNDqQ%2FKZwhLN6dIw3NEAGfQYe2T1%2Fimg.png" alt="">
위에서 본 상황과 똑같은 상황이지만 메서드가 PUT에서 PATCH로 변경되어 전송된다.
<img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyp0OA%2FbtrWrayoyhZ%2FrEhrmYVSTkpb0WtIM2FTn1%2Fimg.png" alt="">
이 경우에는 부분적으로 리소스가 변경된다.</p>
<h1 id="delete">DELETE</h1>
<blockquote>
<p>DELETE는 리소스를 제거하는 것이다.</p>
</blockquote>
<p><img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH4L8z%2FbtrWqPON95f%2FqD0m88v38KNPWShwuQnIy0%2Fimg.png" alt="">
<img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxxo7p%2FbtrWjk3LjA6%2F0DvZi2XMwyzupow19JuKFK%2Fimg.png" alt=""></p>
<hr>
<h1 id="http-메서드-속성">HTTP 메서드 속성</h1>
<p><img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLMFtv%2FbtrWqH4ouwQ%2Fk0PKWueYM72Lq3mntMidmK%2Fimg.png" alt=""><em>출처: <a href="https://ko.wikipedia.org/wiki/HTTP">https://ko.wikipedia.org/wiki/HTTP</a></em>
<br>
HTTP 메서드 속성에는 다음 3가지가 있다.</p>
<h2 id="1-안전safe-methods">1. 안전(Safe Methods)</h2>
<blockquote>
<p>호출해도 리소스를 변경하지 않는 것</p>
</blockquote>
<p>위의 표에서 볼 수 있듯 GET 같은 경우 단순히 조회만이 목적이기 때문에 안전하다 볼 수 있다. 하지만, POST, PUT, DELETE와 같은 것들은 데이터의 변경이 일어나기 때문에 안전하다 볼 수 없다. </p>
<h2 id="2-멱등idempotent-methods">2. 멱등(Idempotent Methods)</h2>
<blockquote>
<p>한 번 호출하든 두 번 호출하든 몇 백번 호출하면 결과가 똑같은 것</p>
</blockquote>
<ul>
<li><code>GET</code> : 한 번 조회하든, 두 번 조회하든 같은 결과가 조회된다.</li>
<li><code>PUT</code> : 결과를 대체한다. 따라서 같은 요청을 여러 번 해도 최종 결과는 같다.</li>
<li><code>DELETE</code> : 결과를 삭제한다. 같은 요청을 여러번 해도 삭제된 결과는 최종적으로 같다. </li>
<li><code>POST</code> : <strong>멱등하지 않다!!!</strong> 두 번 호출하면 같은 결제가 중복해서 발생할 수 있다. </li>
</ul>
<p>이 멱등이 활용되는 예로 &#39;자동 복구 메커니즘&#39;을 들 수 있다.</p>
<p>DELETE를 호출했는데 서버에서 timeout 등의 이유로 정상 응답을 주지 못한 상황을 가정해 보자. 이때 클라이언트는 자동으로 DELETE를 재시도해도 상관없을 것이다. 멱등하기 때문에! 즉, 같은 요청을 두 번해도 괜찮기 때문이다. 하지만 POST 요청을 보낼 경우는 상황이 많이 달라질 것이다.</p>
<p>이처럼 클라이언트가 같은 요청을 다시 시도해도 되는가?에 대한 판단의 근거로 사용될 수 있다. </p>
<h2 id="3-캐시가능cacheable-methods">3. 캐시가능(Cacheable Methods)</h2>
<blockquote>
<p>응답 결과 리소스를 캐시해서 사용해도 되는가?</p>
</blockquote>
<p>GET, HEAD, POST, PATCH가 스펙 상 캐시가능하지만, 실제로는 GET, HEAD 정도만 캐시로 사용한다. POST, PATCH는 본문 내용까지 캐시 키로 고려해야 하는데 이 구현이 쉽지 않기 때문이다.</p>
<hr>
<h4 id="참고">참고</h4>
<p>인프런 김영한님의 강의 - 모든 개발자를 위한 HTTP 웹 기본 지식</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] HTTP 기본]]></title>
            <link>https://velog.io/@easy_on7/HTTP-HTTP-%EA%B8%B0%EB%B3%B8</link>
            <guid>https://velog.io/@easy_on7/HTTP-HTTP-%EA%B8%B0%EB%B3%B8</guid>
            <pubDate>Sat, 24 Jun 2023 05:39:44 GMT</pubDate>
            <description><![CDATA[<p>HTTP는 <strong>HyperText Transfer Protocol</strong>의 약어로, 처음에는 HTML을 전송하는 프로토콜로 시작했지만, 현재는 모든 것을 HTTP 메시지에 담아 전송한다. 아래와 같은 것들이 있다. </p>
<ul>
<li>HTML, TEXT</li>
<li>이미지, 영상, 음성, 파일 ...</li>
<li>JSON, XML(API)</li>
<li><strong>거의 모든 형태의 데이터 전송 가능</strong></li>
<li>서버 간에 데이터를 주고받을 때도 대부분 HTTP를 사용</li>
</ul>
<br>
가장 많이 사용하고, 우리에게 가장 중요한 버전은 HTTP/1.1이다. HTTP/1.1에 대부분의 모든 기능이 들어있고, HTTP/2나 HTTP/3은 성능 개선에 초점이 맞춰져 있는 버전이다.
<br>
<U>HTTP/1.1</U>, HTTP/2는 TCP 위에서 동작한다. 하지만, HTTP/3는 UDP 기반으로 동작한다. 

<hr>
<h1 id="http-특징">HTTP 특징</h1>
<p>HTTP의 특징들을 간단히 살펴보자. </p>
<h2 id="클라이언트-서버-구조">클라이언트-서버 구조</h2>
<p>HTTP는 <strong>클라이언트-서버 구조</strong>로 이루어져 있다. 클라이언트가 HTTP 메시지를 통해 서버에 요청(Request)을 보내고 서버에서 응답이 올 때까지 기다린다. 서버가 요청에 대한 결과를 만들어 응답(Response)을 보내면 클라이언트는 이를 받아 동작을 수행하게 된다.
<img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlNJ0M%2FbtrI6q1LxkI%2F8AkYAApJHhLHsETP7YVEZ0%2Fimg.png" alt=""></p>
<p>비즈니스 모델이나 데이터와 같은 것들은 모두 서버에 존재한다. 클라이언트는 사용성, UI와 같은 것들에 집중한다. 이와 같이 클라이언트와 서버가 분리되어 있어 각각 독립적으로 진화할 수 있다.<br></p>
<h2 id="stateless-protocol-무상태-프로토콜">Stateless Protocol (무상태 프로토콜)</h2>
<p>HTTP는 <strong>무상태 프로토콜</strong>을 지향한다. Stateless란, <U>서버가 클라이언트의 상태를 보존하지 않는다</U>는 것이다. History(이전에 수행했던 것)가 지금 상황에 영향을 끼치지 않는 것을 의미한다. 이렇게 되면 응답 서버를 쉽게 바꿀 수 있게 되고 무한히 서버를 증설할 수 있다.
이 때문에 HTTP는 처음 요청을 보낼 때 <strong>필요한 정보를 모두 담아서</strong> 보내게 된다. 이것(데이터를 모두 담아 보내면 데이터의 양이 증가한다)이 Stateless의 단점이기도 하다.<br>
하지만, 모든 것을 무상태로 설계할 수는 없다. 단순한 소개 화면 같은 것은 무상태로 설계해도 상관이 없지만, 로그인이 필요한 서비스는 로그인의 상태를 유지해야 하기 때문에 무상태로는 설계할 수 없다. 일반적으로는 이를 <U>브라우저 쿠키와 서버 세션 등을 사용해 상태를 유지</U>한다. <br> </p>
<h2 id="connectionless-비연결성">Connectionless (비연결성)</h2>
<p>TCP/IP 같은 경우는 기본적으로 연결을 유지한다. 만약 여러 클라이언트가 연결되었다면 서버는 모든 클라이언트와의 연결을 계속 유지할 것이고 이것은 서버의 자원을 소모하는 것이다. 
하지만 연결을 유지하지 않는 모델(Connectionless) 같은 경우, <strong>서버는 요청을 주고받을 때만 연결</strong>하고 이후에는 연결을 유지하지 않고 최소한의 자원만을 유지한다. 만약 이전의 클라이언트가 추가 데이터가 필요하다면 다시 요청을 시도해 연결한다.<br>
HTTP 관점에서 살펴보면,
HTTP는 기본이 연결을 유지하지 않는 모델이다. 일반적으로는 초 단위 이하의 빠른 속도로 응답을 하고 1시간 동안 수천 명이 서비스를 사용해도 실제 서버에서 동시에 처리하는 요청은 수십 개 이하로 매우 작다. 예를 들어, 브라우저에서 연속해서 검색 버튼을 누르지 않는 것과 같은 경우이다. 따라서 서버 자원을 매우 효율적으로 사용할 수 있다.<br>
이 비연결성도 단점과 한계가 존재하는데, 매번 TCP/IP 연결을 새로 맺어야 한다는 것이다. 즉, 3-way handshaking이 매 연결마다 필요하다. 또한, 웹 브라우저로 사이트를 요청하면 HTML 뿐만 아니라 JavaScript, css, 추가 이미지 등 수많은 자원이 함께 다운로드된다. 
지금은 <code>Persistent Connections</code>으로 이 문제를 해결한다. 간단하게 말하면, 단일 TCP 연결에 여러 Object가 전달될 수 있는 연결 방식이다. 아래 두 그림은 초기 HTTP와 Persistent Connections을 사용한 HTTP의 차이를 보여준다.</p>
<h4 id="초기-http">초기 HTTP</h4>
<p><img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkBir9%2FbtrI2VA8Ovr%2FxnkkQlTWK95OgVQkLkC0f0%2Fimg.png" alt=""></p>
<h4 id="persistent-connection">Persistent Connection</h4>
<p><img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd47QQ%2FbtrI4VnqNQM%2F8s08MRWQwtRe1KUpGfBItk%2Fimg.png" alt=""></p>
<hr>
<h1 id="http-메시지">HTTP 메시지</h1>
<p>HTTP 메시지의 구조는 다음과 같다. 
<img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlGb25%2FbtrJn7vbz6J%2F8J5Mn0waERCSMzlkqTLNZ1%2Fimg.png" alt="">
시작 라인으로 시작하고 헤더가 따라오며, 공백 라인이 있는데 반드시 필요한 라인이다. 그리고 마지막으로 메시지 바디가 들어간다. <br></p>
<h3 id="요청-메시지">요청 메시지</h3>
<p><img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPKnKP%2FbtrJn79PfG5%2FdEf74uZDF2Reae3yXYsVk1%2Fimg.png" alt="">
위 그림을 살펴보면 시작 라인과 헤더가 주어지고 공백 라인이 따라온다. 메시지 바디에 전송할 것이 없다면 비워놓고 전송하면 된다.</p>
<h3 id="응답-메시지">응답 메시지</h3>
<p><img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZToj5%2FbtrJo1g6trI%2FjUclIkxRyKlqAmacYVt6FK%2Fimg.png" alt="">
응답 메시지는 요청 메시지와 시작 라인이 다르다. 나머지는 동일하다.<br>
이제 하나씩 살펴보자.</p>
<h2 id="시작-라인start-line">시작 라인(Start line)</h2>
<p>시작 라인은 크게 <code>Request line</code>과 <code>Status line</code>으로 이루어지는데, <U>요청 메시지</U>는 Request line, <U>응답 메시지</U>는 Status line이라고 한다. 구조는 다음과 같다.</p>
<h4 id="요청-메시지의-구조">요청 메시지의 구조</h4>
<pre><code class="language-http">Request line = method SP(공백) request-target SP HTTP-version CRLF(엔터)</code></pre>
<ul>
<li><code>method</code> : GET, POST... 와 같은 것들로 <strong>서버가 수행해야 할 동작을 지정</strong>한다. GET은 리소스를 조회하라는 명령어이고, POST는 요청 내역을 처리해달라는 명령어이다. </li>
<li><code>request-target</code> : path(경로, 요청하는 대상)가 들어간다. 보통 절대 경로를 사용한다. </li>
<li><code>HTTP-version</code> : HTTP 버전</li>
</ul>
<h4 id="응답-메시지의-구조">응답 메시지의 구조</h4>
<pre><code>Status line = HTTP-version SP status-code SP reason-phrase CRLF</code></pre><p>HTTP-code는 상태 코드로, 200은 성공, 400은 클라이언트 요청 오류, 500은 서버 내부 오류를 뜻한다. </p>
<h2 id="http-헤더">HTTP-헤더</h2>
<pre><code>header-field = field-name&quot;:&quot; OWS field-value OWS</code></pre><p>구조는 위와 같이 이루어지고, OWS는 띄어쓰기를 해도 되고 안 해도 된다는 의미이다. <strong>field-name</strong>의 <U>대소문자는 구분하지 않는다.</U>
HTTP 헤더는 HTTP 전송에 필요한 모든 부가정보를 가지고 있다. 메시지 바디를 제외하고 필요한 메타데이터가 모두 들어있다 보면 된다.
표준 헤더가 매우 많고, 필요하다면 임의의 헤더를 추가할 수도 있다. </p>
<h2 id="http-message-body">HTTP-message body</h2>
<p>실제로 전송할 데이터가 들어가는 곳이다. HTML 문서, 이미지, 영상, JSON... 등 byte로 표현할 수 있는 모든 데이터를 전송할 수 있다. </p>
<hr>
<h4 id="참고">참고</h4>
<p>인프런 김영한님의 강의 - 모든 개발자를 위한 HTTP 웹 기본 지식</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] URI와 웹 브라우저 요청 흐름]]></title>
            <link>https://velog.io/@easy_on7/HTTP-URI%EC%99%80-%EC%9B%B9-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%9A%94%EC%B2%AD-%ED%9D%90%EB%A6%84</link>
            <guid>https://velog.io/@easy_on7/HTTP-URI%EC%99%80-%EC%9B%B9-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%9A%94%EC%B2%AD-%ED%9D%90%EB%A6%84</guid>
            <pubDate>Fri, 23 Jun 2023 09:43:46 GMT</pubDate>
            <description><![CDATA[<h1 id="uriuniform-resource-identifier">URI(Uniform Resource Identifier)</h1>
<p>우선 <strong>URI</strong>는 로케이터(Locator), 이름(Name) 또는 둘 다 추가로 분류될 수 있다. 
<img src="https://blog.kakaocdn.net/dn/djCHLr/btrIUnlQTRF/LCI1k3djYCUxsf4WXUBNu0/img.png" alt="">
그림을 보고 설명을 해보면, URI라는 가장 큰 개념이 있고 그 안에 크게 평소에 많이 들어봤을 <U>URL(리소스의 위치)</U>과 <U>URN(리소스의 이름)</U>이 있다. 
<br>
URL과 URN은 다음과 같이 생겼다. 
<img src="https://blog.kakaocdn.net/dn/englOK/btrI1VOMYmy/LOxyuyyPSWRHkCcKR5XfEk/img.png" alt=""></p>
<p>URL은 보다시피 우리가 흔히 인터넷에서 사용하는 형식이고, URN은 단순히 이름을 부여해 놓은 것이다. 그래서 URN은 거의 사용하지 않는다. 
<br>
그럼 돌아와서 _<strong>URI</strong>_의 뜻에 대해 살펴보면</p>
<ul>
<li><strong>Uniform</strong> : 리소스를 식별하는 통일된 방식</li>
<li><strong>Resource</strong> : 자원, URI로 식별할 수 있는 모든 것(제한 없음)</li>
<li><strong>Identifier</strong> : 다른 항목과 구분하는데 필요한 정보</li>
</ul>
<p>그래서 <span style="color:indianred"><strong>URL</strong></span>은 <span style="color:indianred">리소스가 있는 위치를 지정하는 것</span>이고, <span style="color:slateblue"><strong>URN</strong></span>은 <span style="color:slateblue">리소스에 이름을 부여하는 것</span>이다. <span style='background-color: #fff5b1'>리소스의 위치는 변할 수 있지만, 이름은 변하지 않는다.</span> URN 이름만으로 실제 리소스를 찾을 수 있는 방법은 보편화되어있지 않다. </p>
<h2 id="url-문법">URL 문법</h2>
<p>그럼 실제 URL을 보고 어떻게 이루어져 있는지 분석해보자. 
<img src="https://blog.kakaocdn.net/dn/bnE5sn/btrI2p9JbLs/XyCmqj2FuNhWHd1KniPdi0/img.png" alt="">
URL 형식은 이렇다.</p>
<pre><code>scheme://[userinfo@]host[:port][/path][?query][#fragment]</code></pre><ul>
<li><strong>scheme</strong> : 보통 http와 같은 <span style="color:indianred">프로토콜 정보</span>가 들어간다. http는 80 포트, https는 443 포트를 주로 사용하는데, 이는 생략이 가능하다.</li>
<li><strong>[userinfo@]</strong> : URL에 사용자 정보를 포함에서 인증할 때 사용하지만, 거의 사용하지 않는다. </li>
<li><strong>host</strong> : <span style="color:indianred">호스트명</span>으로 도메인 명이나 IP 주소를 직접 사용할 수 있다.</li>
<li><strong>[:Port]</strong> : <span style="color:indianred">포트 번호</span>로, 접속 포트를 나타내고 일반적으로 생략하고, 생략 시 http는 80, https는 443 포트이다. </li>
<li><strong>[/path]</strong> : <span style="color:indianred">리소스가 있는 경로.</span> 계층적 구조로 되어 있다. 예를 들어, /home/file.jpg</li>
<li><strong>[?query]</strong> : <span style="color:indianred">Key=Value</span>의 형태로 데이터가 들어간다. <U><strong>?</strong>로 시작</U>하고, <U><strong>&amp;</strong>로 파라미터를 추가</U>할 수 있다. 예를 들면, <strong>?KeyA=valueA&amp;KeyB=valueB</strong> 와 같이 사용 가능하다.  웹서버에 제공하는 파라미터, 문자 형태이기 때문에 보통 query parameter, query string으로 불린다.</li>
<li><strong>[#fragment]</strong> : <span style="color:indianred">html 내부 북마크</span> 등에 사용된다. 서버에 전송하는 정보는 아니다. </li>
</ul>
<hr>
<h1 id="웹-브라우저의-요청-흐름">웹 브라우저의 요청 흐름</h1>
<p>위의 URL을 전송하는 상황을 살펴보자.
<img src="https://blog.kakaocdn.net/dn/biT2lt/btrI2vWt2oX/KtbNrcJ9cmpqgPQC808x61/img.png" alt=""></p>
<p>DNS를 조회해 IP 주소를 받고, Port 번호가 생략되어있으므로 443번일 것이다(https). 이 정보를 바탕으로 <span style='background-color: #fff5b1'><strong>http 요청 메시지</strong></span>를 생성한다. </p>
<p><img src="https://blog.kakaocdn.net/dn/bbqBgH/btrI0eOZk3D/FjEgLcsnAYXn12ahMmyflk/img.png" alt="간단하게는 이와 같은 메시지를 전송">
<img src="https://blog.kakaocdn.net/dn/I8IdL/btrI2dogXkc/u1G0RwllKGENI8viE703WK/img.png" alt=""></p>
<p>웹 브라우저가 먼저 HTTP 메시지를 생성한다. 그리고 Socket 라이브러리를 통해 TCP/IP로 이를 전달하고 TCP/IP 패킷을 만들어 전송한다. 최종적으로는 이와 같은 패킷을 전송하는 것이다.
<img src="https://blog.kakaocdn.net/dn/zcM2T/btrIUl9rjNC/SDr6yNSEo7MuJoO0t3wUA0/img.png" alt=""></p>
<br>
이 패킷을 인터넷 망으로 던지면 수많은 인터넷 노드를 거쳐 해당 목적지 주소에 전달이 될 것이다. 이를 받은 서버는 HTTP 메시지만 추출하여 내용을 확인하고 해당하는 데이터를 응답 메시지와 함께 전달한다.

<p><img src="https://blog.kakaocdn.net/dn/eqCeSd/btrIY1h3q2N/rXwWM2oD9btfZC13TzuLvK/img.png" alt=""></p>
<hr>
<h4 id="참고">참고</h4>
<p>인프런 김영한님의 강의 - 모든 개발자를 위한 HTTP 웹 기본 지식</p>
]]></description>
        </item>
    </channel>
</rss>