<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>DoxB의 기록</title>
        <link>https://velog.io/</link>
        <description>아키텍처 설계부터 고민하는 개발자</description>
        <lastBuildDate>Thu, 01 Jan 2026 20:23:41 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>DoxB의 기록</title>
            <url>https://velog.velcdn.com/images/jg_doxb/profile/de3fbe5a-67aa-4540-979b-8f02de63fffc/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. DoxB의 기록. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jg_doxb" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[토이프로젝트] 모의투자플랫폼 - Websocket(3)]]></title>
            <link>https://velog.io/@jg_doxb/%ED%86%A0%EC%9D%B4%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%AA%A8%EC%9D%98%ED%88%AC%EC%9E%90%ED%94%8C%EB%9E%AB%ED%8F%BC-Websocket3</link>
            <guid>https://velog.io/@jg_doxb/%ED%86%A0%EC%9D%B4%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%AA%A8%EC%9D%98%ED%88%AC%EC%9E%90%ED%94%8C%EB%9E%AB%ED%8F%BC-Websocket3</guid>
            <pubDate>Thu, 01 Jan 2026 20:23:41 GMT</pubDate>
            <description><![CDATA[<h2 id="0-들어가며">0. 들어가며...</h2>
<p>이번 목표는 <strong>&quot;메시지브로커에 메시지가 발행이 될 때마다 가격을 업데이트해서 보여주는 것&quot;</strong>이다.
<img src="https://velog.velcdn.com/images/jg_doxb/post/2dba0a5f-ebf1-4af3-b964-c7f9b72f36bc/image.png" alt=""></p>
<p>여기서 중간에 서버를 또 거치면 지연시간이 발생할텐데, 메시지브로커에 발행된 메시지를 그냥 클라이언트에 직접 꽂으면 빠르지 않을까? 라는 생각이 들었다.</p>
<p><strong>Redis 보안 관리 공식문서</strong>
<a href="https://redis.io/docs/latest/operate/oss_and_stack/management/security/">https://redis.io/docs/latest/operate/oss_and_stack/management/security/</a></p>
<blockquote>
<p>For instance, in the common context of a web application implemented using Redis as a database, cache, or messaging system, the clients inside the front-end (web side) of the application will query Redis to generate pages or to perform operations requested or triggered by the web application user.
In this case, the web application mediates access between Redis and untrusted clients (the user browsers accessing the web application).</p>
</blockquote>
<p>신뢰할 수 없는 클라이언트로부터 Redis에 직접 쿼리를 보낼 수 있는 정보가 노출이 되고 (Redis 서버 정보), 이로 인해 보안, 안정성 문제가 발생한다. 라는 내용이다.</p>
<p><span style="color:red"><strong>그래서 백엔드를 통해 접근하는 방식으로 구현한다.</strong></span>
<del>이렇게 정리하고보니 Database를 프론트에 직접 연결해본적이 없다는 게 생각났다.</del></p>
<h3 id="필요한-건-무엇일까">필요한 건 무엇일까?</h3>
<p>우선 클라이언트에 실시간 시세정보가 보여지기까지 과정을 생각해보자.</p>
<blockquote>
<p><strong>과정</strong></p>
</blockquote>
<ol>
<li>메시지브로커로 활용하는 Redis로부터 price 채널 내에 발행된 메시지를 가져와야한다.</li>
<li>가져온 메시지를 비트코인, 이더리움 시세로 분리해야한다.</li>
<li>각각 웹소켓을 연결을 통해 클라이언트로 데이터를 전달한다.</li>
</ol>
<p>이러한 과정에서 구현에 있어 필요한 것은 무엇일까?</p>
<blockquote>
<p><strong>구현</strong></p>
</blockquote>
<ol>
<li>Redis 서버와 연결</li>
<li>Websocket 설정 및 메시지 발행</li>
<li>React로 Websocket 연결 구독</li>
</ol>
<h2 id="1-redis-서버와-연결">1. Redis 서버와 연결</h2>
<h3 id="redisconfigjava">RedisConfig.java</h3>
<pre><code class="language-java">package com.crpyto_trading.demo.config;

import com.crpyto_trading.demo.infra.redis.RedisPriceSubscriber;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate&lt;String, String&gt; redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate&lt;String, String&gt; template = new RedisTemplate&lt;&gt;();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        return template;
    }

    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(
            RedisConnectionFactory connectionFactory,
            RedisPriceSubscriber subscriber
    ) {
        RedisMessageListenerContainer container =
                new RedisMessageListenerContainer();

        container.setConnectionFactory(connectionFactory);
        container.addMessageListener(
                subscriber,
                new PatternTopic(&quot;price:*&quot;) // 티커별 구독
        );

        return container;
    }
}</code></pre>
<p>Redis Pub/Sub은 메시지가 발행되면 구독자에게 즉시 전달되는 구조로 메시지를 수신하기 위해 Listener를 등록한다.
<strong>Spring에서는 RedisMessageListenerContainer</strong>를 통해 Redis 서버와 연결하고, 특정 채널 패턴에 대해 MessageListener를 구현한 객체를 등록한다. 여기서 구현 객체는 subscriber이다.
이후 메시지가 발행되면 컨테이너가 해당 <span style="color:red"><strong>Listener의 <code>onMessage()</code> 메서드를 호출</strong></span>하여 메시지를 처리한다.</p>
<h3 id="redispricesubscriberjava">RedisPriceSubscriber.java</h3>
<pre><code class="language-java">package com.crpyto_trading.demo.infra.redis;

import com.crpyto_trading.demo.service.PriceStreamService;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;

@Component
public class RedisPriceSubscriber implements MessageListener {

    private final PriceStreamService priceStreamService;

    public RedisPriceSubscriber(PriceStreamService priceStreamService) {
        this.priceStreamService = priceStreamService;
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        String payload = new String(message.getBody(), StandardCharsets.UTF_8);
        String channel = new String(message.getChannel(), StandardCharsets.UTF_8);

        // 받아온 메시지를 처리 (Service에 처리 로직 구현)
        priceStreamService.onPriceMessage(channel, payload);
    }
}</code></pre>
<p>Redis PUB/SUB으로부터 받은 메시지를 처리하는 onMessage를 구현하는데,
Redis로부터 온 데이터를 RedisTemplate를 활용해서 다뤄야하는게 아닌가? 싶지만
Redis에 접근해서 저장, 조회를 하는 것이 아니니깐 필요없다.
priceStreamService를 통해 받아온 메시지를 처리한다.</p>
<h3 id="pricestreamservicejava">PriceStreamService.java</h3>
<pre><code class="language-java">package com.crpyto_trading.demo.service;

import com.crpyto_trading.demo.infra.websocket.outbound.PriceWebSocketPublisher;
import org.springframework.stereotype.Service;

@Service
public class PriceStreamService {

    private final PriceWebSocketPublisher publisher;

    public PriceStreamService(PriceWebSocketPublisher publisher) {
        this.publisher = publisher;
    }

    public void onPriceMessage(String channel, String payloadJson) {
        // channel: price:BTCUSDT
        String symbol = extractSymbol(channel);
        publisher.publish(symbol, payloadJson);
    }

    private String extractSymbol(String channel) {
        // &quot;price:BTCUSDT&quot; → &quot;BTCUSDT&quot;
        int idx = channel.indexOf(&#39;:&#39;);
        return (idx &gt; -1) ? channel.substring(idx + 1) : channel;
    }
}</code></pre>
<p>종목별 시세 구독을 처리하기 위해 extractSymbol로 종목 정보를 추출한다.
클라이언트에게 실시간 시세 정보를 전달하기 위해, onPriceMessage 메서드에서 <strong>PriceWebSocketPublisher</strong>를 통해 메시지를 발행한다.
메시지를 어떻게 발행하는지는 아래...</p>
<h2 id="2-websocket-설정-및-메시지-발행">2. Websocket 설정 및 메시지 발행</h2>
<h3 id="websocketconfigjava">WebSocketConfig.java</h3>
<pre><code class="language-java">package com.crpyto_trading.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint(&quot;/ws&quot;)
                .setAllowedOriginPatterns(&quot;*&quot;)
                .withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker(&quot;/topic&quot;);
        registry.setApplicationDestinationPrefixes(&quot;/app&quot;);
    }
}</code></pre>
<p>registerStompEndpoints 메서드를 통해 클라이언트와의 실시간 통신을 위한 WebSocket 엔드포인트를 설정하고,
configureMessageBroker 메서드를 통해 <span style="color:red"><strong>Spring의 STOMP 프로토콜</strong></span>을 기반으로 메시지 브로커를 활성화한다.</p>
<p>withSockJS()는 WebSocket 연결이 불가능한 환경에서도 통신을 유지하기 위해 HTTP 기반 대체 전송 방식을 제공한다.
enableSimpleBroker는 서버 -&gt; 클라이언트, setApplicationDestinationPrefixes 클라이언트 -&gt; 서버 방향으로 라우팅할때 필요한 prefix(접두사)를 세팅한다.</p>
<h3 id="pricewebsocketpublisherjava">PriceWebSocketPublisher.java</h3>
<pre><code class="language-java">package com.crpyto_trading.demo.infra.websocket.outbound;

import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Component;

@Component
public class PriceWebSocketPublisher {

    private final SimpMessagingTemplate messagingTemplate;

    public PriceWebSocketPublisher(SimpMessagingTemplate messagingTemplate) {
        this.messagingTemplate = messagingTemplate;
    }

    public void publish(String symbol, String payloadJson) {
        // 예: /topic/price/BTCUSDT
        messagingTemplate.convertAndSend(&quot;/topic/price/&quot; + symbol, payloadJson);
    }
}</code></pre>
<p>STOMP 프로토콜 위에 메시지를 발행하기위해 SimpMessagingTemplate을 활용한다.
publish 메서드에서 enableSimpleBroker에서 설정한 접두사가 포함된 목적지로 데이터를 전송하고,
이를 통해 <strong>STOMP 프로토콜을 활용하는 메시지 브로커</strong>가 해당 주소를 구독 중인 클라이언트들에게 실시간으로 메시지를 발행한다.</p>
<pre><code class="language-java">public void onPriceMessage(String channel, String payloadJson) {
    // channel: price:BTCUSDT
    String symbol = extractSymbol(channel);
    publisher.publish(symbol, payloadJson);
}</code></pre>
<p>목적지 구분을 통해 종목별 실시간 시세를 구분하려고 symbol, payloadJson 데이터를 publish에 넘겨준 것이다.</p>
<p>클라이언트(브라우저)에서 이걸 어떻게 구독할까? 개념 설명 이후에 있다.</p>
<hr>
<h4 id="간단한-개념만">간단한 개념만...</h4>
<blockquote>
<p><strong>STOMP (Simple/Stream Text Oriented Message Protocol)</strong></p>
</blockquote>
<ol>
<li>websocket 위에서 동작하는 문자 기반 메세징 프로토콜</li>
<li>클라이언트와 서버가 전송할 메세지의 유형, 형식, 내용들을 정의</li>
<li>pub/sub 구조</li>
</ol>
<p>역할은 Redis Pub/Sub과 비슷한데, 차이는 다음과 같다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>STOMP</th>
<th>Redis Pub/Sub</th>
</tr>
</thead>
<tbody><tr>
<td>역할</td>
<td>서버와 클라이언트 간 메시지 전달</td>
<td>서버와 서버 간 메시지 전달</td>
</tr>
<tr>
<td>연결 방식</td>
<td>Websocket 연결을 통해 전송</td>
<td>TCP 소켓 기반 연결</td>
</tr>
<tr>
<td>사용 프로토콜</td>
<td>STOMP (WebSocket 위에서 동작)</td>
<td>Redis 자체 프로토콜(RESP)</td>
</tr>
<tr>
<td>전송 계층</td>
<td>Websocket → TCP</td>
<td>TCP</td>
</tr>
</tbody></table>
<p>여기서 TCP라는 용어가 있는데, OSI 7계층의 <strong>전송계층에서 양방향 통신을 가능하게 해주는 프로토콜</strong>이다.
서로 연결하기 위해 3 Way Handshake, 전송 데이터 순서 보장... 등등 뭐 많은데</p>
<p><a href="https://velog.io/@jg_doxb/HTTP-HTTP-%EC%9D%B4%ED%95%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%82%AC%EC%A0%84-%EC%A7%80%EC%8B%9D">[HTTP] 1. HTTP 이해를 위한 사전 지식</a>
이전에 강의들으면서 정리해놓은 것 참고하면 된다.</p>
<p>전송 계층 부분에 Websocket → TCP 표시가 되어 있는데,
Websocket 프로토콜을 사용하여 클라이언트와 서버 간의 지속적인 TCP 연결을 수립, 이를 통해 실시간 양방향 데이터 전송을 가능하게 하는 것으로 이해하면 된다.</p>
<blockquote>
<p><strong>Websocket이 뭐길래?</strong>
HTTP 통신을 위해 생성된 TCP 연결을 WebSocket으로 업그레이드한 뒤, 해당 TCP 연결 위에서 지속적인 양방향 통신을 수행한다.
자세한 개념은 다음 게시글에 정리하겠다.</p>
</blockquote>
<hr>
<h2 id="3-react로-websocket-서버-구독">3. React로 Websocket 서버 구독</h2>
<h3 id="appjs">App.js</h3>
<pre><code class="language-javascript">import { useEffect, useState } from &quot;react&quot;;
import { Client } from &quot;@stomp/stompjs&quot;;
import SockJS from &quot;sockjs-client&quot;;
import &quot;./App.css&quot;;

function App() {
  const [btcPrice, setBtcPrice] = useState(&quot;-&quot;);
  const [ethPrice, setEthPrice] = useState(&quot;-&quot;);

  useEffect(() =&gt; {
    const client = new Client({
      webSocketFactory: () =&gt; new SockJS(&quot;http://localhost:8080/ws&quot;),
      reconnectDelay: 5000,

      onConnect: () =&gt; {
        // BTC 구독
        client.subscribe(&quot;/topic/price/BTCUSDT&quot;, (message) =&gt; {
          const data = JSON.parse(message.body);
          setBtcPrice(data.price);
        });

        // ETH 구독
        client.subscribe(&quot;/topic/price/ETHUSDT&quot;, (message) =&gt; {
          const data = JSON.parse(message.body);
          setEthPrice(data.price);
        });
      },
    });

    client.activate();

    return () =&gt; {
      client.deactivate();
    };
  }, []);

  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;div className=&quot;content&quot;&gt;
        &lt;div className=&quot;box&quot;&gt;
          &lt;h3&gt;비트코인 시세&lt;/h3&gt;
          &lt;p&gt;{btcPrice} USDT&lt;/p&gt;
        &lt;/div&gt;

        &lt;div className=&quot;box&quot;&gt;
          &lt;h3&gt;이더리움 시세&lt;/h3&gt;
          &lt;p&gt;{ethPrice} USDT&lt;/p&gt;
        &lt;/div&gt;

        &lt;div className=&quot;box asset-box&quot;&gt;나의 자산정보&lt;/div&gt;
        &lt;div className=&quot;box large&quot;&gt;수익률 랭킹&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre>
<p>STOMP 프로토콜 기반의 Websocket 연결을 통해 시세 정보를 구독하고, 메시지를 수신할 때마다 화면에 표시되는 시세 값을 업데이트한다.</p>
<p>그리고  React에서 소켓통신 그리고 STOMP 프로토콜을 활용하려면 패키지를 받아야한다.</p>
<pre><code class="language-shell">npm install sockjs-client @stomp/stompjs</code></pre>
<h2 id="4-결과">4. 결과</h2>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/e3d4cef1-426f-4f17-a39f-b02db81b6215/image.png" alt=""></p>
<p>이러한 구조가 완성이 되었다.</p>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/09d914d6-de7c-474a-85c1-4b690d75c675/image.gif" alt=""></p>
<p>그리고 구현 결과는 단순하긴한데, 이렇다.</p>
<h2 id="5-잡담">5. 잡담</h2>
<p>구현과정에서 Websocket 개념, STOMP가 뭔지 찾고
STOMP가 Redis PUB/SUB이랑 어떻게 다른지 이해하는 것이 어려웠다.
CS 지식이 여기서 활용되네...</p>
<p>Websocket 구현은 이후 개념들 정리한번해보면서 마무리할 것이다.
다음은 매수/매도를 구현하면서 트랜잭션, 동시성에 대한 고민을 해보겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[토이프로젝트] 모의투자플랫폼 - Websocket(2)]]></title>
            <link>https://velog.io/@jg_doxb/%ED%86%A0%EC%9D%B4%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%AA%A8%EC%9D%98%ED%88%AC%EC%9E%90%ED%94%8C%EB%9E%AB%ED%8F%BC-Websocket2</link>
            <guid>https://velog.io/@jg_doxb/%ED%86%A0%EC%9D%B4%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%AA%A8%EC%9D%98%ED%88%AC%EC%9E%90%ED%94%8C%EB%9E%AB%ED%8F%BC-Websocket2</guid>
            <pubDate>Sun, 21 Dec 2025 19:45:48 GMT</pubDate>
            <description><![CDATA[<h2 id="0-들어가며">0. 들어가며...</h2>
<p>이번 구현 목표는 <strong>&quot;외부 시스템으로부터 시세 정보를 웹소켓을 구독해서 메시지 브로커에 전달&quot;</strong>하는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/863e52f3-6008-40e4-a47a-ecefd072eef1/image.png" alt=""></p>
<h3 id="웹소켓-서버-언어-프레임워크-선택">웹소켓 서버 언어, 프레임워크 선택</h3>
<p>스프링을 백엔드로 하는 플랫폼 웹서버와 분리가 되었기 때문에 다른 언어와 프레임워크로 시세 수신 서버를 구현을 해도 된다는 선택지가 생겼다.
그래서 직접 벤치마크 테스트를 해서 언어와 프레임워크를 선택할까? 했지만, 너무 산으로 가는 것 같았다.
보통 어느 언어와 프레임워크를 사용하는 지 자료를 찾다가 웹소켓 벤치마크 관련 논문자료를 찾았다.</p>
<p><a href="https://www.researchgate.net/publication/348993267_An_Analysis_of_the_Performance_of_Websockets_in_Various_Programming_Languages_and_Libraries">https://www.researchgate.net/publication/348993267_An_Analysis_of_the_Performance_of_Websockets_in_Various_Programming_Languages_and_Libraries</a></p>
<p>여러 조건하에서 벤치마크 테스트를 한 내용이다.
Python은 커넥션이 많아질수록 높은 지연시간, C는 특정구간에서 안정적이지 못한 결과를 가져왔다.
결론으로 Python은 소켓통신으로 비추천이라고 한다.</p>
<p>서비스 환경에 맞게 조건들 조정하고, 자체적으로 테스트해보는 게 확실한데...
<span style="color:red"><strong>참고만하고 그냥 Java, Spring로 가자.</strong></span></p>
<h3 id="메세지-브로커-프레임워크-선택">메세지 브로커 프레임워크 선택</h3>
<p>눈에 보이는 것들은 Redis, Kafka가 있는데, 토스 세미나 영상에서 메세지 브로커 처리 속도를 비교한 것이 있다.
<img src="https://velog.velcdn.com/images/jg_doxb/post/50b9f6a0-d697-44eb-9179-6af08674381c/image.png" alt="">
출처: <a href="https://www.youtube.com/watch?v=SF7eqlL0mjw">https://www.youtube.com/watch?v=SF7eqlL0mjw</a></p>
<p>Redis가 Kafka에 비해 낮은 지연시간, Queue가 없는 PUB/SUB구조라는 장점이 있는데,
대신 Redis로부터 데이터를 수신받는 서버가 처리를 제때 못한다면 메시지가 유실된다는 단점이 있다.
토스는 이걸 Spring의 Redis 라이브러리 구조를 분석하고 이벤트 루프를 구현해서 해결한 것 같은데...
<span style="color:red"><strong>Redis로 가자</strong></span></p>
<h3 id="뭐-구현할꺼임">뭐 구현할꺼임?</h3>
<p>필요한 것은 무언인지 생각을 해보자.</p>
<blockquote>
<p><strong>구현 요소</strong></p>
</blockquote>
<ol>
<li>서버 내에서 외부 시스템으로부터 데이터를 불러오기</li>
<li>불러온 데이터에서 필요한 데이터만 가공</li>
<li>가공된 데이터를 메시지 브로커로 Publish</li>
</ol>
<p>이 3가지 기능만 제대로 작동하면 시세수신 서버 구현은 완료다.</p>
<h2 id="1-spring-project-생성">1. Spring Project 생성</h2>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/bb9190d7-ba44-4ac1-8275-b0199365d320/image.png" alt=""></p>
<p>Websocket 의존성만 추가해서 생성했다.
Redis 의존성도 추가해야하는데, 서버 내에서 외부 시스템으로부터 데이터를 불러오는 과정을 먼저 구현해보고 눈으로 보고 싶어서 Webscoket만 추가했다.
Redis 의존성은 있다가 추가한다.</p>
<h2 id="2-bybit-websocket-명세">2. Bybit Websocket 명세</h2>
<p><a href="https://bybit-exchange.github.io/docs/v5/websocket/public/ticker">https://bybit-exchange.github.io/docs/v5/websocket/public/ticker</a></p>
<p>실시간 시세정보를 받고 싶은 코인 티커를 토픽으로 서버에 전달하면 관련 정보를 웹소켓을 통해 수신할 수 있다.
모의투자플랫폼에서 지원하는 거래는 현물거래가 아닌 선물거래로 진행할 예정이라 &quot;Linear/Inverse&quot; 기준으로
ts, symbol, lastPrice 이 3가지를 가져올 것이다.</p>
<blockquote>
<p><strong>설명</strong></p>
</blockquote>
<ol>
<li>ts: 시점 (timestamp)</li>
<li>symbol: 코인 이름 + 거래 코인단위</li>
<li>lastPrice: 마지막 가격</li>
</ol>
<p>실제로 받아오는 데이터 로그를 찍어보면 시세변동정보뿐만이 아니라 실제 주문이 등록되는 정보들도 수신된다.
<span style="color:red"><strong>시세 정보가 수신되었을때만 Publish 하는 것이 필요하다.</strong></span></p>
<h2 id="3-websocket-publish-구현">3. Websocket Publish 구현</h2>
<h3 id="bybitwebsocketclientjava--외부-websocket-구독">BybitWebSocketClient.java : 외부 Websocket 구독</h3>
<pre><code class="language-java">package com.example.receivePrice.infra.websocket.bybit;

import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.client.WebSocketClient;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;

@Component
public class BybitWebSocketClient {
    private static final String BYBIT_WS_URL = &quot;wss://stream.bybit.com/v5/public/linear&quot;;
    private final WebSocketClient webSocketClient = new StandardWebSocketClient();
    private final BybitWebSocketHandler handler;

    public BybitWebSocketClient(BybitWebSocketHandler handler) {
        this.handler = handler;
    }

    @PostConstruct
    public void connect() {
        webSocketClient.execute(handler, BYBIT_WS_URL);
        System.out.println(&quot;Connecting to Bybit WebSocket...&quot;);
    }
}
</code></pre>
<p>외부 웹소켓을 연결하는 부분이다.
BybitWebSocketClient 클래스는 스프링에서 @Component로 선언되어 서버가 올라갈때, 스프링 컨테이너 내에 Bean으로 등록이 된다.
<strong>Bean 생성과 의존성 주입이 완료된 직후 @PostConstruct가 붙은 메서드가 실행되고, 이 시점에 외부 Bybit WebSocket 서버로의 연결이 자동으로 이루어진다.</strong></p>
<p>생성자에서 BybitWebSocketHandler를 주입받는데, 이는 BybitWebSocketHandler가 Spring 컨테이너에 Bean으로 등록되어 있어 Spring이 미리 생성해 둔 Bean 인스턴스를 자동으로 주입해준다.</p>
<h3 id="bybitwebsockethandlerjava--외부-websocket-구독-이후-처리부">BybitWebSocketHandler.java : 외부 Websocket 구독 이후 처리부</h3>
<pre><code class="language-java">package com.example.receivePrice.infra.websocket.bybit;

import com.example.receivePrice.infra.redis.RedisPricePublisher;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;

import java.util.List;
import java.util.Map;

@Component
public class BybitWebSocketHandler extends TextWebSocketHandler {
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final RedisPricePublisher publisher;

    public BybitWebSocketHandler(RedisPricePublisher publisher) {
        this.publisher = publisher;
    }

    // 소켓 연결 이후 서버에 보낼 메세지
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // subscribe 메세지 (소켓 구독 방법: 문서 참조)
        Map&lt;String, Object&gt; subscribeMsg = Map.of(
                &quot;op&quot;, &quot;subscribe&quot;,
                &quot;args&quot;, List.of(&quot;tickers.BTCUSDT&quot;, &quot;tickers.ETHUSDT&quot;)
        );
        // 연결된 소켓에 메세지 보내기
        session.sendMessage(
                new TextMessage(objectMapper.writeValueAsString(subscribeMsg))
        );
        System.out.println(&quot;Subscribed to BTCUSDT, ETHUSDT ticker&quot;);
    }

    // 수신 받은 메세지 가공
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) {
        try {
            JsonNode root = objectMapper.readTree(message.getPayload());

            // 0. ts 노드 확인
            JsonNode timeNode = root.get(&quot;ts&quot;);
            if (timeNode == null) return;
            String receiveTime = timeNode.asString();

            // 1. data 노드 없으면 무시
            JsonNode dataNode = root.get(&quot;data&quot;);
            if (dataNode == null) return;

            // 2. symbol
            JsonNode symbolNode = dataNode.get(&quot;symbol&quot;);
            if (symbolNode == null) return;
            String symbol = symbolNode.asString();

            // 3. lastPrice
            JsonNode lastPriceNode = dataNode.get(&quot;lastPrice&quot;);
            if (lastPriceNode == null) return;
            String lastPrice = lastPriceNode.asString();

            System.out.println(&quot;symbol=&quot; + symbol + &quot;, price=&quot; + lastPrice + &quot;, time=&quot; + receiveTime);

            // Redis publish
            publisher.publish(symbol, lastPrice, receiveTime);


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) {
        System.err.println(&quot;WebSocket error: &quot; + exception.getMessage());
    }
}</code></pre>
<p>TextWebSocketHandler를 상속받아와서 구현하였다.</p>
<p><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/socket/handler/TextWebSocketHandler.html">https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/socket/handler/TextWebSocketHandler.html</a>
TextWebSocketHandler에서 상속받는 메서드들은 여기서 확인할 수 있다.</p>
<p>상속받는 메서드들 중 3가지 메서드를 재정의했다.</p>
<blockquote>
<p><strong>재정의 메서드 3가지</strong></p>
</blockquote>
<ul>
<li><strong>afterConnectionEstablished</strong>
WebSocket 연결이 성공적으로 완료된 직후 구독 요청 메세지를 보낸다.
연결 session을 저장한다.</li>
<li><strong>handleTextMessage</strong>
WebSocket으로 텍스트 메시지를 수신할 때마다 실시간으로 반복 호출되는 부분으로 수신 데이터를 처리한다.</li>
<li><strong>handleTransportError</strong>
WebSocket 통신 중 에러 발생 시 처리하는 부분으로 session을 닫거나 연결을 재호출한다.</li>
</ul>
<h4 id="afterconnectionestablished">afterConnectionEstablished</h4>
<pre><code class="language-java">@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
    // subscribe 메세지 (소켓 구독 방법: 문서 참조)
    Map&lt;String, Object&gt; subscribeMsg = Map.of(
            &quot;op&quot;, &quot;subscribe&quot;,
            &quot;args&quot;, List.of(&quot;tickers.BTCUSDT&quot;, &quot;tickers.ETHUSDT&quot;)
    );
    // 연결된 소켓에 메세지 보내기
    session.sendMessage(
            new TextMessage(objectMapper.writeValueAsString(subscribeMsg))
    );
    System.out.println(&quot;Subscribed to BTCUSDT, ETHUSDT ticker&quot;);
}</code></pre>
<p>웹소켓 연결이후 구독 정보를 외부 서버로 보내고, 그 연결 세션을 저장하는 부분이다.
Json 형태로 구독 정보를 보내야되는데, Map을 활용해 Bybit에서 요구하는 형태를 맞추었다.</p>
<h4 id="handletextmessage">handleTextMessage</h4>
<pre><code class="language-java">// 수신 받은 메세지 가공
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
    try {
        JsonNode root = objectMapper.readTree(message.getPayload());

        // 0. ts 노드 확인
        JsonNode timeNode = root.get(&quot;ts&quot;);
        if (timeNode == null) return;
        String receiveTime = timeNode.asString();

        // 1. data 노드 없으면 무시
        JsonNode dataNode = root.get(&quot;data&quot;);
        if (dataNode == null) return;

        // 2. symbol
        JsonNode symbolNode = dataNode.get(&quot;symbol&quot;);
        if (symbolNode == null) return;
        String symbol = symbolNode.asString();

        // 3. lastPrice
        JsonNode lastPriceNode = dataNode.get(&quot;lastPrice&quot;);
        if (lastPriceNode == null) return;
        String lastPrice = lastPriceNode.asString();

       System.out.println(&quot;symbol=&quot; + symbol + &quot;, price=&quot; + lastPrice + &quot;, time=&quot; + receiveTime);

        // Redis publish
        publisher.publish(symbol, lastPrice, receiveTime);


    } catch (Exception e) {
        e.printStackTrace();
    }
}</code></pre>
<p>수신받은 데이터를 처리하는 부분이다.
Json형태의 데이터를 받아오기 때문에 JsonNode 객체형태로 전처리를 했고, 전처리된 데이터를 Redis로 Publish 했다.
<strong>publisher는 어디서 구현되었냐고? Redis 연동파트에서 다시 언급하겠다.</strong></p>
<blockquote>
<p><strong>트러블슈팅</strong>
String symbol = symbolNode.toString();
-&gt; String symbol = symbolNode.<span style="color:red">asString()</span>;
toString()으로 하니깐 값에 &quot;&quot; 큰따옴표가 붙어 Redis-cli 창에서 채널을 구독이 정상적으로 되지않았다.
JsonNode 객체의 값만 그대로 사용하고 싶으면 asText()로 변환한다고 한다.
API 정리를 위해 asString() 같은 새로운 이름이 도입되었고, asText()와 같은 역할을 하기때문에,
asString()으로 변환해주었다.</p>
</blockquote>
<h4 id="handletransporterror">handleTransportError</h4>
<pre><code class="language-java">@Override
public void handleTransportError(WebSocketSession session, Throwable exception) {
   System.err.println(&quot;WebSocket error: &quot; + exception.getMessage());
}</code></pre>
<p>일단 시세수신서버를 분리해놓기도 했고, 데이터가 메세지브로커까지 가는 과정을 확인해보는 것이 1순위라 연결 오류 정보만 띄울 수 있게 해놓았다. <del>재연결로직은 나중에...</del></p>
<h2 id="4-redis-연동">4. Redis 연동</h2>
<h3 id="redis-의존성-추가-properties-정보-추가">Redis 의존성 추가, properties 정보 추가</h3>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;
&lt;/dependency&gt;</code></pre>
<p>프로젝트에 Redis 의존성을 추가해준다.</p>
<pre><code class="language-shell"># Setting Redis Connection
spring.data.redis.host=${REDIS_HOST}
spring.data.redis.port=${REDIS_PORT}
spring.data.redis.password=${REDIS_PASSWORD}</code></pre>
<p>Redis 서버 정보를 properties에 추가해준다.</p>
<p>Spring Boot의 Redis Auto Configuration이 해당 설정을 기반으로 RedisConnectionFactory를 생성한다.
이후 RedisConfig에서는 이 RedisConnectionFactory를 주입받아 Redis와의 연결을 수행하는 RedisTemplate를 구성한다.</p>
<blockquote>
<p><strong>properties → Auto Configuration → RedisConnectionFactory → RedisTemplate</strong></p>
</blockquote>
<p>RedisConfig가 뭔데? 아래있다.</p>
<h3 id="redisconfigjava">RedisConfig.java</h3>
<pre><code class="language-java">package com.example.receivePrice.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate&lt;String, String&gt; redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate&lt;String, String&gt; template = new RedisTemplate&lt;&gt;();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        return template;
    }
}</code></pre>
<p>Redis 서버와 통신할때 필요한 정보를 사전에 정의한 부분이다.
서버 정보, 데이터 저장, 전송 형태가 정의되어있다.</p>
<h3 id="span-stylecolorredredispricepublisherjavaspan"><span style="color:red">RedisPricePublisher.java</span></h3>
<pre><code class="language-java">package com.example.receivePrice.infra.redis;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import tools.jackson.databind.ObjectMapper;

import java.util.Map;

@Component
public class RedisPricePublisher {
    private final RedisTemplate&lt;String, String&gt; redisTemplate;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public RedisPricePublisher(RedisTemplate&lt;String, String&gt; redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public void publish(String symbol, String price, String ts) throws Exception {
        String channel = &quot;price:&quot; + symbol;

        Map&lt;String, String&gt; payload = Map.of(
                &quot;symbol&quot;, symbol,
                &quot;price&quot;, price,
                &quot;ts&quot;, ts
        );

        redisTemplate.convertAndSend(
                channel,
                objectMapper.writeValueAsString(payload)
        );
    }

}
</code></pre>
<p>Redis Pub/Sub을 통해 시세 데이터를 발행하는 Publisher 부분이다.
코인별로 채널을 분리하고, JSON 형태의 메시지를 Map으로 구성한 후 RedisTemplate를 사용해 Redis로 전송한다.</p>
<h2 id="5-결과">5. 결과</h2>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/00b6ea4b-b15f-4a16-9a87-27046c3bf940/image.png" alt=""></p>
<p>비트코인, 이더리움 실시간 시세정보가 Redis PUB/SUB으로 올라가는 걸 확인할 수 있다.</p>
<h2 id="6-잡담">6. 잡담</h2>
<p>Redis-cli에서 원하는 데이터가 올라올때 신기했다. 이게되네...
코드 작성은 GPT, 구글의 힘으로 어렵진 않았는데,
왜 이런것들을 쓰는지, 다른 방법은 없는지 납득을 하는 과정이 오래 걸렸다.</p>
<p>그리고 코드 내에 의존성 분리를 해야하는 부분들이 보인다.
일단 다 만들어놓고 이후 수정하는 방향으로 기술 부채를 쌓아두자.
지금부터 건들이면 완주가 안된다.</p>
<p><del>번외: 정리 금방 끝날 줄 알았는데...시간... 토익 언제함</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[토이프로젝트] 모의투자플랫폼 - Websocket(1)]]></title>
            <link>https://velog.io/@jg_doxb/%ED%86%A0%EC%9D%B4%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%AA%A8%EC%9D%98%ED%88%AC%EC%9E%90%ED%94%8C%EB%9E%AB%ED%8F%BC-Websocket1</link>
            <guid>https://velog.io/@jg_doxb/%ED%86%A0%EC%9D%B4%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%AA%A8%EC%9D%98%ED%88%AC%EC%9E%90%ED%94%8C%EB%9E%AB%ED%8F%BC-Websocket1</guid>
            <pubDate>Sun, 14 Dec 2025 18:35:56 GMT</pubDate>
            <description><![CDATA[<h2 id="0-들어가며">0. 들어가며...</h2>
<p>프론트는 React로 구성을 마쳤다. (GPT 없었으면 불가능)
유튜브로 공개된 강의듣고, 코드를 읽고 원하는 대로 바꿀 수 있을 정도로만 익혔다.
React의 특징으로 서버에서 렌더링을 하는 것이 아닌 클라이언트단에서 렌더링이 진행이되면서 이걸로 얻을 수 있는 장점들이 여럿 있는 것 같은데, 페이지 넘어갈 때 깜빡이지 않는다 정도로만 이해하고 넘어갔다. 가상돔, 컴포넌트 뭐 이래저래 많은데, 프로젝트 완성하고 깊게 다뤄보는 방향으로 가자.
<del>여기서부터 파보면 한도 끝도 없다...</del></p>
<h2 id="1-websocket-구상">1. Websocket 구상</h2>
<h3 id="단순한-생각">단순한 생각</h3>
<p>먼저, 프론트에 웹소켓을 구현해서 실시간 시세를 받아오는 경우를 생각해보았다.</p>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/bbd7c839-166f-482b-8596-d4da2e690935/image.png" alt=""></p>
<p>사용자가 늘어날 때마다 실시간 시세를 받아오는 Websocket 연결이 늘어날텐데</p>
<blockquote>
<ol>
<li>시세제공 서버로부터 API key 밴부터 먹고 시작하지않을까?</li>
<li>그리고 외부 웹소켓 구독에 필요한 API key가 다 노출될텐데?</li>
</ol>
</blockquote>
<p><span style="color:red"><strong>그럼 백엔드 서버 1대에서 실시간 시세 Websocket 연결을 관리하고,
클라이언트와 연결하는 Websocket를 각각 생성해서 실시간 시세를 뿌려주자</strong></span></p>
<h3 id="조금-깊은-생각">조금 깊은 생각</h3>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/3bef1bac-2054-4cea-8f72-0ff85f43ca2b/image.png" alt=""></p>
<p>그래서 그냥 스프링으로 외부 Websocket 구독하고 스프링에서 웹소켓 열어서 클라이언트에 뿌려주면 되겠다.
<span style="color:red"><strong>라고 쉽게 생각했다.</strong></span> 여기서 이유는 모르겠는데 이질감이 들었다.</p>
<blockquote>
<ol>
<li>클라이언트가 많아지면 서버 한대가 그 많은 웹소켓 연결을 관리할 수 있을까?</li>
</ol>
<p>-&gt; Scale-out으로 백엔드 서버 갯수를 늘리면 감당이 된다.
2. 그러면 시세제공 서버와 Websocket 연결도 늘어나겠네?
3. 그런데 각 서버가 받는 데이터가 모두 일치할까?
4. 비즈니스로직을 처리하는 백엔드서버에서 실시간 시세도 받는다?</p>
</blockquote>
<p>이제서야 이렇게 4가지로 고민의 흔적을 정리하는데, 이질감이 들었을땐 그냥 막막해서 바로 구현에 들어가기보다 웹소켓을 활용해 실시간 서비스를 구현한 자료를 많이 찾아보았다.
많은 블로그 자료를 통해 메시지 브로커라는 존재를 알았다. 근데 왜 쓰는 거냐에 대해 확실하게 와닿는 글들은 없었다. <del>공식문서가 아니니깐...</del></p>
<p><strong>[ 토스ㅣSLASH 23 - 실시간 시세 데이터 안전하고 빠르게 처리하기 ]</strong>
<a href="https://www.youtube.com/watch?v=SF7eqlL0mjw">https://www.youtube.com/watch?v=SF7eqlL0mjw</a>
토스증권에서 실시간 시세 데이터를 다룬 방법에 대해 고민한 영상이 가장 도움되었다.
메시지 브로커를 도입하는 이유에 대해 Scale-out, MSA 구조들을 예시로 들어가며 설명해주는데, 메시지 브로커를 사용하는 이유에 대한 고민이 해결되었다.</p>
<blockquote>
<p><strong>도움이 된 부분</strong>
수신부가 늘어난 처리부에 일일 모두 데이터를 보내주어야하는데 이 때 수신부의 성능이 떨어진다.
<strong>해결: 메시지 브로커 도입을 통해 수신부와 처리부의 결합도를 낮추고, 수신부는 데이터를 한번만 보내면된다.</strong></p>
</blockquote>
<h3 id="결론">결론</h3>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/8cde2ca8-44e9-4c6f-9306-3a1324ef310d/image.png" alt=""></p>
<blockquote>
<p><strong>메시지 브로커 도입으로 해결</strong></p>
</blockquote>
<ol>
<li>외부 시세 수신 로직과 비즈니스 로직의 결합도를 낮춘다.</li>
<li>Scale-out으로 늘어나는 백엔드 서버로 넘어가는 데이터 일관성도 확보할 수 있다.</li>
<li>그리고 외부 시세제공 서버와 WebSocket 연결을 1개만 유지할 수 있다.</li>
<li>시세를 수신하는 서버에서 데이터를 메시지 브로커에 한번만 보낼 수 있다.</li>
</ol>
<h2 id="2-잡담">2. 잡담</h2>
<p><del>꿀벌집인줄 알고 건들였더니 말벌집이다.</del>
&quot;실시간 정보를 처리할 때는 웹소켓이란 기술을 활용한다.&quot;만 알고 덤볐는데 존재만 알고 있던 Redis Pub/Sub, Kafka와 같은 메시지 브로커 역할을 하는 기술들에 대해 접할 수 있는 기회였다.
<del>구현 언제함.</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[토이프로젝트] 모의투자 플랫폼 - 기획]]></title>
            <link>https://velog.io/@jg_doxb/%ED%86%A0%EC%9D%B4%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%AA%A8%EC%9D%98%ED%88%AC%EC%9E%90-%ED%94%8C%EB%9E%AB%ED%8F%BC-%EA%B8%B0%ED%9A%8D</link>
            <guid>https://velog.io/@jg_doxb/%ED%86%A0%EC%9D%B4%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%AA%A8%EC%9D%98%ED%88%AC%EC%9E%90-%ED%94%8C%EB%9E%AB%ED%8F%BC-%EA%B8%B0%ED%9A%8D</guid>
            <pubDate>Fri, 05 Dec 2025 10:18:03 GMT</pubDate>
            <description><![CDATA[<h2 id="0-들어가며">0. 들어가며...</h2>
<p>하반기 IT 신입 공고가 시들해지고, 날도 추워져서 1주일간 동면에 들어가면서 부족했던 부분에 대해 생각을 해보았다.</p>
<p>스타트업을 나오고 부트캠프를 시작으로 이력서 부분을 채울 수 있는 활동을 해왔다. 그리고 원하는 기업으로 취업까지 이어졌으면 정말 좋았겠지만, 그렇게 쉽게 될리가 없었다. 연속된 실패에도 억지로 루틴지켜가며 취업준비를 이어가려고 했으나 한계다. 그렇게 1주일간 동면에 들어갔다.</p>
<p>그동안의 성과들을 돌이켜보면 AI 기술을 써서 완성한 성과들이 많은데, Web, DB에서 다루게되는 트랜잭션, 동시성, 웹소켓통신, 인증/인가에 대해 고민해본적이 없다.
그래서 그걸 채워보고자 이 모의투자 플랫폼 프로젝트를 진행하려고 한다.</p>
<h2 id="1-모의투자를-종목을-코인으로">1. 모의투자를 종목을 코인으로</h2>
<p>미국주식, 국내주식도 많은데 왜 굳이 코인으로 하냐면, 실시간으로 가격과 오더북을 제공하는 웹소켓 API가 의외로 코인쪽이 누구나 쓸 수 있게 오픈되어있다. 그리고 24시간 오픈되어있어 무언가 테스트를 할 때, 시간에 제약을 받지않을 것이라 생각하여 코인으로 설정하였다.
한국투자증권에서도 제공하는 웹소켓 API가 잘되어 있긴하지만, 시간 제약 조건을 뛰어넘기에는 그렇게 와닿진 않았다.</p>
<h2 id="2-요구사항-명세서">2. 요구사항 명세서</h2>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/1d91d9d1-5d18-4f84-abe9-ba7574f347ae/image.png" alt=""></p>
<p>부트캠프 교육에서 배웠던 문서화 방법을 통해 어느정도 정리하면서 프로젝트를 진행하려고 한다. 그냥 머릿속에 있는 내용을 그대로 구현으로 옮기기에는 날마다 보는 관점이 달라지니깐 프로젝트가 산으로 가는 것을 방지하려고 명세서부터 작성해본다.</p>
<h2 id="3-와이어프레임">3. 와이어프레임</h2>
<table>
<thead>
<tr>
<th>메인페이지</th>
<th>로그인페이지</th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/jg_doxb/post/b69f9cd4-c45e-4d75-9140-e4fbada3e157/image.png" alt=""></td>
<td><img src="https://velog.velcdn.com/images/jg_doxb/post/34677805-bc9a-4778-8bcc-9ace13eda587/image.png" alt=""></td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>회원가입페이지</th>
<th>이체페이지</th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/jg_doxb/post/cc0bbe74-04b6-45a0-87d2-679c1ac35fd4/image.png" alt=""></td>
<td><img src="https://velog.velcdn.com/images/jg_doxb/post/7fa06465-51fe-4153-99ea-d23c35876f42/image.png" alt=""></td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>마이페이지</th>
<th>기록페이지</th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/jg_doxb/post/0bca487d-a7a1-4f8d-9384-19fcc60855ae/image.png" alt=""></td>
<td><img src="https://velog.velcdn.com/images/jg_doxb/post/d06161ad-44dd-47fe-834a-ef634cba75b9/image.png" alt=""></td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>종목페이지</th>
<th>주문페이지</th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/jg_doxb/post/16bf6cfb-176c-4025-8101-4a7cd01dfd4d/image.png" alt=""></td>
<td><img src="https://velog.velcdn.com/images/jg_doxb/post/1a2278cd-717d-43cb-a8e5-15c8e94f2be8/image.png" alt=""></td>
</tr>
</tbody></table>
<p>화면은 단순하다. React를 써볼지는 좀만 더 고민해보자.</p>
<h2 id="4-e-r-다이어그램">4. E-R 다이어그램</h2>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/1c7c0ea1-218a-4a91-a260-c2b3788a3059/image.png" alt=""></p>
<p>ERD까지해서 테이블 정의까지 할까? 개발하면서 추가되고 제거될 것들이 많아질 것 같아 일단 다이어그램으로만 정리했다.</p>
<h2 id="5-잡담">5. 잡담</h2>
<p>이 프로젝트를 통해 확실하게 건져갈 것은 <span style="color: red"><strong>트랜잭션, 동시성, 웹소켓통신, 인증/인가</strong></span>이다.
이게 취업에 도움이 되는가? 그건 모르겠다. 그냥 하자. 뭐라도 도움되겠지.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] (Java) 2. DFS - 백트래킹]]></title>
            <link>https://velog.io/@jg_doxb/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Java-2.-DFS-%EB%B0%B1%ED%8A%B8%EB%9E%98%ED%82%B9</link>
            <guid>https://velog.io/@jg_doxb/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Java-2.-DFS-%EB%B0%B1%ED%8A%B8%EB%9E%98%ED%82%B9</guid>
            <pubDate>Thu, 27 Nov 2025 15:25:10 GMT</pubDate>
            <description><![CDATA[<p>그래도 해야지.
백준 17136번 색종이 붙이기를 바탕으로 백트래킹을 이해해보자.</p>
<h2 id="1-dfs---백트래킹">1. DFS - 백트래킹</h2>
<p>코딩테스트에서 백트래킹을 활용하여 풀어야하는 문제는 종종 나온다.
개인적으로 BFS, DFS는 개념적인 어려움보단 구현에 있어 신경써야하는 것들이 좀 있고,
풀기위해서 세팅해야하는 것들이 많아 구현만 어느정도 익숙해지면 놓치기 아까운 문제라고 생각한다.
DFS를 활용한 백트래킹을 어떻게 구현하는 지 단계별로 정리해보자.</p>
<pre><code class="language-java">import java.io.*;
import java.util.StringTokenizer;

// 각 종류 색종이 5개씩

public class Main {
    private static int[][] adj = new int[10][10];
    private static int[] paper = {0, 5, 5, 5, 5, 5};
    private static int answer = Integer.MAX_VALUE;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st;

        for (int i = 0; i &lt; 10; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j &lt; 10; j++) {
                adj[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        dfs(0, 0);
        if (answer == Integer.MAX_VALUE) {
            bw.write(&quot;-1&quot;);
        } else {
            bw.write(Integer.toString(answer));
        }

        bw.flush();
        br.close();
        bw.close();
    }

    private static void dfs(int yx, int count) {
        if (count &gt;= answer) return;
        if (yx == 100) {
            if (answer &gt; count) {
                answer = count;
            }
            return;
        }
        int y = yx / 10;
        int x = yx % 10;

        if (adj[y][x] == 1) {
            for (int i = 1; i &lt;= 5; i++) {
                if (paper[i] &gt; 0 &amp;&amp; check(y, x, i)) {
                    fill(y, x, i, 0);
                    paper[i]--;
                    dfs(yx + 1, count + 1);
                    fill(y, x, i, 1);
                    paper[i]++;
                }
            }
        } else {
            dfs(yx + 1, count);
        }
    }

    private static boolean check(int y, int x, int size) {
        if ((y + size) &gt; 10 || (x + size) &gt; 10) return false;
        for (int i = y; i &lt; y + size; i++) {
            for (int j = x; j &lt; x + size; j++) {
                if (adj[i][j] == 0) return false;
            }
        }
        return true;
    }

    private static void fill(int y, int x, int size, int flag) {
        for (int i = y; i &lt; y + size; i++) {
            for (int j = x; j &lt; x + size; j++) {
                adj[i][j] = flag;
            }
        }
    }
}</code></pre>
<h3 id="그래프-및-공유할-변수들">그래프 및 공유할 변수들</h3>
<pre><code class="language-java">private static int[][] adj = new int[10][10];
private static int[] paper = {0, 5, 5, 5, 5, 5};
private static int answer = Integer.MAX_VALUE;</code></pre>
<p>전역으로 설정하자. 정신건강을 위해서.
메서드 파라미터로 넘길 수 있는데, 머리아프다.
문제 푸는데 의의를 두고 전역으로 두자.
파이썬은 global 변수로 관리해야하는데, 자바는 클래스 내에서 전역으로 설정하면 클래스 내에서 공유가 되니 이건 파이썬보다 훨씬 편하다.</p>
<h3 id="탐색-위치-체크-및-방문-여부-체크-그리고-액션">탐색 위치 체크 및 방문 여부 체크 그리고 액션</h3>
<pre><code class="language-java">private static boolean check(int y, int x, int size) {
    if ((y + size) &gt; 10 || (x + size) &gt; 10) return false;
    for (int i = y; i &lt; y + size; i++) {
        for (int j = x; j &lt; x + size; j++) {
            if (adj[i][j] == 0) return false;
        }
    }
    return true;
}</code></pre>
<p>그래프를 인접행렬로 구현했다면 필수적으로 해야하는 부분이다.
만약, 그래프를 인접리스트로 구현했다면 방문여부만 체크해주면 된다.</p>
<pre><code class="language-java">if (adj[i][j] == 0) return false;</code></pre>
<p>이 문제에서는 방문여부를 색종이로 채웠냐 안채웠냐로 체크를 한다.
다른 문제에서는 방문여부를 별도의 boolean 배열을 전역으로 설정해서 체크해주는 경우가 많다.</p>
<pre><code class="language-java">private static void fill(int y, int x, int size, int flag) {
    for (int i = y; i &lt; y + size; i++) {
        for (int j = x; j &lt; x + size; j++) {
            adj[i][j] = flag;
        }
    }
}</code></pre>
<p>방문을 했을 경우 취할 액션이다.
색종이 붙이기 문제에서는 그래프에 종이를 덮는다 가 액션이다.
해당 문제에서는 <span style="color: red"><strong>방문여부를 true로 변환해주는 액션을 이렇게 변형</strong></span>을 한 것이다.</p>
<h3 id="백트래킹-구현">백트래킹 구현</h3>
<pre><code class="language-java">private static void dfs(int yx, int count) {
    if (count &gt;= answer) return;
    if (yx == 100) {
        if (answer &gt; count) {
            answer = count;
        }
        return;
    }
    int y = yx / 10;
    int x = yx % 10;

    if (adj[y][x] == 1) {
        for (int i = 1; i &lt;= 5; i++) {
            if (paper[i] &gt; 0 &amp;&amp; check(y, x, i)) {
                fill(y, x, i, 0);
                paper[i]--;
                dfs(yx + 1, count + 1);
                fill(y, x, i, 1);
                paper[i]++;
            }
        }
    } else {
        dfs(yx + 1, count);
    }
}</code></pre>
<p>단순히 짧은 경로를 탐색할 때는 재귀에서 빠져나오는 경우, 복원해주야하는 요소가 없다.
목표지점까지 경로를 카운팅해주고, 최소값을 답으로 던져주면 되니깐.
그런데 백트래킹은 다르다.
<span style="color: red"><strong>재귀에서 빠져나올때 방문여부나 변화된 변수들 상태들을 복원해주어야한다.</strong></span>
다른 방법으로 같은 경로를 접근할 때, 이전에 했던 방법이 영향을 주면 안되니깐.</p>
<pre><code class="language-java">fill(y, x, i, 0);
paper[i]--;
dfs(yx + 1, count + 1);
fill(y, x, i, 1);
paper[i]++;</code></pre>
<p>색종이 붙이기 문제애서는 방문시 액션이 종이를 채우는 것이다. 그리고 가지고 있는 종이 갯수를 소모하는 것.
재귀에서 빠져나올 때 이 액션을 복원해주는 것이다.
다른 액션을 취할 때 영향을 주면 안되니깐.</p>
<pre><code class="language-java">if (count &gt;= answer) return;
if (yx == 100) {
    if (answer &gt; count) {
        answer = count;
    }
    return;
}</code></pre>
<p>조건을 만족하는 경우 재귀를 마무리하는 것을 상단에 구현한다.
재귀에 들어왔을 때, 현재 위치가 최종 종착지인지 여부를 체크해준다.
그리고 최소값을 구하는 건데, 이미 현재 카운팅하고 있는 값이 최소값보다 크다면 그 경로는 더이상 탐색을 하지 않아도 되니, <span style="color: red"><strong>가지치기</strong></span>를 해준다! (최적화)</p>
<blockquote>
<p><strong>DFS - 백트래킹 구조</strong></p>
</blockquote>
<ol>
<li>그래프 및 공유할 변수들 설정</li>
<li>탐색 위치 체크 및 방문 여부 체크 그리고 액션</li>
<li>백트래킹 구현: 복원과 가지치기</li>
</ol>
<h2 id="2-정리하며">2. 정리하며...</h2>
<p>DFS, BFS 기본 문제를 많이 풀어서 자신있다고 생각했는데, 색종이 붙이기 문제는 신경써야할 것들이 많이 변형되어서 많이 당황스러웠다.
방문시 취해야할 액션들을 하나하나 정리해서 그대로 구현에만 옮긴다면 큰 문제는 없을 것으로 생각된다.
계속 나아가자, 될 때까지.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] (Java) 1. 분할정복 - 병합정렬]]></title>
            <link>https://velog.io/@jg_doxb/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Java-1.-%EB%B6%84%ED%95%A0%EC%A0%95%EB%B3%B5-%EB%B3%91%ED%95%A9%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@jg_doxb/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Java-1.-%EB%B6%84%ED%95%A0%EC%A0%95%EB%B3%B5-%EB%B3%91%ED%95%A9%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Sun, 09 Nov 2025 15:06:17 GMT</pubDate>
            <description><![CDATA[<p>몇일간 Python으로 단련된 코테실력을 Java로 바꿔가는데 집중하고 있었다.
Do it, 알고리즘 코딩테스트 Java편으로 한바퀴 돌리면서 개념과 구현에 익숙해지는 것을 목적으로 공부중에 있다.
이해를 제대로 하지 못하고 넘겼던 개념들을 차근차근 정리해볼계획이다.</p>
<h2 id="1-분할정복---병합정렬">1. 분할정복 - 병합정렬</h2>
<p>병합정렬은 분할정복 방식을 사용해서 데이터를 분할하고 분할한 집합을 정렬해가며 합치는 알고리즘이다.
병합 정렬의 <span style="color: red"><strong>시간 복잡도 평균값은 O(nlogn)</strong></span>이다.
잘게 분할하고 합쳐가면서 정렬, 개념은 이해된다. 이걸 어떻게 구현하는 것인지 아래 코드를 보면서 이해해보자.</p>
<pre><code class="language-java">import java.io.*;
import java.util.StringTokenizer;

public class Main {
    public static int[] arr;
    public static long answer = 0;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        int n = Integer.parseInt(br.readLine());
        arr = new int[n];
        StringTokenizer st = new StringTokenizer(br.readLine());
        for (int i = 0; i &lt; n; i++) {
            arr[i] = Integer.parseInt(st.nextToken());
        }

        sort(arr, 0, n - 1);

        bw.write(Long.toString(answer));
        bw.flush();
        br.close();
        bw.close();
    }

    // 분할 정복 : 재귀로 구현
    private static void sort(int[] arr, int left, int right) {
        // 길이가 1이면 빠져나오기
        if (left == right) {
            return;
        }

        int mid = (left + right) / 2;

        // 왼쪽 분할
        sort(arr, left, mid);
        // 오른쪽 분할
        sort(arr, mid + 1, right);
        // 정렬
        merge(arr, left, mid, right);
    }

    private static void merge(int[] arr, int left, int mid, int right) {
        int leftLength = mid - left + 1;
        int rightLength = right - mid;

        int[] leftTmp = new int[leftLength];
        int[] rightTmp = new int[rightLength];

        for (int i = 0; i &lt; leftLength; i++) {
            leftTmp[i] = arr[left + i];
        }
        for (int i = 0; i &lt; rightLength; i++) {
            rightTmp[i] = arr[mid + 1 + i];
        }

        int leftIdx = 0;
        int rightIdx = 0;
        int oriIdx = left;

        // 분할 이후 합병 정렬
        while (leftIdx &lt; leftLength &amp;&amp; rightIdx &lt; rightLength) {
            if (leftTmp[leftIdx] &lt;= rightTmp[rightIdx]) {
                arr[oriIdx] = leftTmp[leftIdx];
                if ((left + leftIdx) &gt; oriIdx) {
                    answer += (left + leftIdx) - oriIdx;
                }
                leftIdx++;
            } else {
                arr[oriIdx] = rightTmp[rightIdx];
                if ((rightIdx + mid + 1) &gt; oriIdx) {
                    answer += (rightIdx + mid + 1) - oriIdx;
                }
                rightIdx++;
            }
            oriIdx++;
        }

        // 왼쪽 배열 나머지 채우기
        while (leftIdx &lt; leftLength) {
            arr[oriIdx] = leftTmp[leftIdx];
            leftIdx++;
            oriIdx++;
        }

        // 오른쪽 배열 나머지 채우기
        while (rightIdx &lt; rightLength) {
            arr[oriIdx] = rightTmp[rightIdx];
            rightIdx++;
            oriIdx++;
        }
    }
}</code></pre>
<h3 id="분할">분할</h3>
<p>전체코드는 이렇다. <strong>재귀호출로 분할을 하고, 재귀에서 빠져나오면서 정렬</strong>이 되는 방식으로 구현하였다.
재귀호출로 분할하고 빠져나오는 부분은 다음과 같다.</p>
<pre><code class="language-java">    private static void sort(int[] arr, int left, int right) {
        // 길이가 1이면 빠져나오기
        if (left == right) {
            return;
        }

        int mid = (left + right) / 2;

        // 왼쪽 분할
        sort(arr, left, mid);
        // 오른쪽 분할
        sort(arr, mid + 1, right);
        // 정렬
        merge(arr, left, mid, right);
    }</code></pre>
<h4 id="이해해보기">이해해보기</h4>
<h4 id="1st">1st</h4>
<p>sort라는 메서드를 재귀호출하는 과정을 생각해보면</p>
<ol>
<li>배열의 중간값을 계산</li>
<li>왼쪽, 오른쪽으로 분할</li>
<li>분할할 배열 길이가 1이 될때까지 왼쪽 분할
로 이어진다.</li>
</ol>
<h4 id="2nd">2nd</h4>
<p>길이가 1이되고 나서 재귀에서 빠져나오면 오른쪽 분할 재귀로 넘어가는데,
이때 전달된 왼쪽, 오른쪽 인덱스 파라미터를 생각해보면 길이가 1짜리인 오른쪽 배열이 새롭게 정렬 대상이 된다.
그렇게 merge라는 메소드를 만나게 되면서 분할된 왼쪽과 오른쪽이 만나면서 병합 정렬이 일어난다.</p>
<h3 id="병합-및-정렬">병합 및 정렬</h3>
<pre><code class="language-java">private static void merge(int[] arr, int left, int mid, int right) {
        int leftLength = mid - left + 1;
        int rightLength = right - mid;

        int[] leftTmp = new int[leftLength];
        int[] rightTmp = new int[rightLength];

        for (int i = 0; i &lt; leftLength; i++) {
            leftTmp[i] = arr[left + i];
        }
        for (int i = 0; i &lt; rightLength; i++) {
            rightTmp[i] = arr[mid + 1 + i];
        }

        int leftIdx = 0;
        int rightIdx = 0;
        int oriIdx = left;

        // 분할 이후 합병 정렬
        while (leftIdx &lt; leftLength &amp;&amp; rightIdx &lt; rightLength) {
            if (leftTmp[leftIdx] &lt;= rightTmp[rightIdx]) {
                arr[oriIdx] = leftTmp[leftIdx];
                if ((left + leftIdx) &gt; oriIdx) {
                    answer += (left + leftIdx) - oriIdx;
                }
                leftIdx++;
            } else {
                arr[oriIdx] = rightTmp[rightIdx];
                if ((rightIdx + mid + 1) &gt; oriIdx) {
                    answer += (rightIdx + mid + 1) - oriIdx;
                }
                rightIdx++;
            }
            oriIdx++;
        }

        // 왼쪽 배열 나머지 채우기
        while (leftIdx &lt; leftLength) {
            arr[oriIdx] = leftTmp[leftIdx];
            leftIdx++;
            oriIdx++;
        }

        // 오른쪽 배열 나머지 채우기
        while (rightIdx &lt; rightLength) {
            arr[oriIdx] = rightTmp[rightIdx];
            rightIdx++;
            oriIdx++;
        }
    }</code></pre>
<ol>
<li>원본 배열에서 인덱스를 파라미터로 받아 왼쪽 배열과 오른쪽 배열을 임시적으로 만들고</li>
<li>왼쪽, 오른쪽 배열 투포인터 방식으로 인덱스를 옮겨가며 원본배열에 값을 다시 채운다.</li>
<li>한쪽이 배열의 끝까지 탐색이 끝났으면, 나머지 한쪽 배열을 그대로 이어붙인다.
이 때, 양쪽 배열 모두 각각 정렬이 끝난 상태이니 나머지 요소를 그대로 이어붙여도 정렬 상태가 깨지지 않는다.</li>
</ol>
<h2 id="2-etc">2. etc...</h2>
<pre><code class="language-java">    private static boolean isPrime(int num) {
        if (num &lt; 2) {
            return false;
        }
        for (int i = 2; i &lt;= (int) Math.sqrt(num); i++) {
            if (num % i == 0) {
                return false;
            }
        }
        return true;
    }</code></pre>
<p>시간 복잡도를 생각한 소수 판별 메서드인데 <strong>for (int i = 2; i &lt;= (int) Math.sqrt(num); i++)</strong> 이부분 암기해놓자. 종종 쓰인다.</p>
<h2 id="3-정리하며">3. 정리하며...</h2>
<p>분할정복에 관한 아이디어는 코테 문제를 풀면서 떠오르긴하지만, 어떻게 구현을 했더라에서 막힌 경험이 여러번 있었다.
O(nlogn)이라는 시간복잡도로 최적화에 많은 도움을 줄 듯하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CS] 2. 프로그래밍 패러다임]]></title>
            <link>https://velog.io/@jg_doxb/CS-2.-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%ED%8C%A8%EB%9F%AC%EB%8B%A4%EC%9E%84</link>
            <guid>https://velog.io/@jg_doxb/CS-2.-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%ED%8C%A8%EB%9F%AC%EB%8B%A4%EC%9E%84</guid>
            <pubDate>Tue, 28 Oct 2025 07:33:08 GMT</pubDate>
            <description><![CDATA[<p>프로그래밍 패러다임은 프로그래밍의 관점을 갖게 해주는 개발 방법론이다.</p>
<h2 id="1-선언형-함수형-프로그래밍">1. 선언형-함수형 프로그래밍</h2>
<ol>
<li>무엇을 풀어내는가에 집중하는 패러다임</li>
<li><span style="color: red"><strong>프로그램은 함수로 이루어진 것</strong></span>이다 라는 명제가 담겨 있는 패러다임</li>
<li>순수 함수를 블록처럼 쌓아 로직을 구현, 고차 함수를 통해 재사용성을 높인 패러다임</li>
</ol>
<ul>
<li>순수 함수: 출력이 입력에만 의존하는 것</li>
<li>고차 함수: 함수가 함수를 값처럼 <strong>매개변수로 받아 로직을 생성할 수 있는 것</strong></li>
</ul>
<blockquote>
<p>고차 함수를 쓰기위해서는 해당 언어가 일급 객체라는 특징을 가져야한다.
<strong>일급객체</strong>
변수나 메서드에 함수를 할당할 수 있다.
함수 안에 함수를 매개변수로 담을 수 있다.
함수가 함수를 반환할 수 있다.</p>
</blockquote>
<h2 id="2-객체지향-프로그래밍">2. 객체지향 프로그래밍</h2>
<p>OOP, Object-Oriented Programming으로</p>
<ol>
<li><strong>객체들의 집합</strong>으로 프로그램의 상호 작용을 표현</li>
<li><strong>데이터를 객체</strong>로 취급</li>
<li>객체 <strong>내부에 선언된 메서드</strong>를 활용
하는 방식을 말한다.</li>
</ol>
<h3 id="특징">특징</h3>
<ol>
<li><strong>추상화</strong>: 복잡한 시스템으로부터 <span style="color: red"><strong>핵심적인 개념 또는 기능을 간추려 내는 것</strong></span></li>
<li><strong>캡슐화</strong>: 객체의 <span style="color: red"><strong>속성과 메서드</strong></span>를 하나로 묶고 일부를 외부에 감추어 <span style="color: red"><strong>은닉</strong></span></li>
<li><strong>상속성</strong>: <strong>상위 클래스</strong>의 특성을 <strong>하위 클래스</strong>가 이어받아서 재사용하거나 추가, 확장</li>
<li><strong>다형성</strong>: 하나의 메서드나 클래스가 다양한 방법으로 동작하는 것을 말함 (대표적으로 <span style="color: red"><strong>오버로딩, 오버라이딩</strong></span>)</li>
</ol>
<ul>
<li><strong>오버로딩</strong>: <strong>같은 이름</strong>을 가진 메서드를 여러 개 두는 것 <strong>(메서드의 타입, 매개변수 유형, 개수 등으로 여러개 둘 수 있다.)</strong>, 컴파일 중에 발생하는 정적 다형성</li>
<li><strong>오버라이딩</strong>: 주로 메서드 오버라이딩을 말하고, 상위 클래스로부터 상속받은 메서드를 <strong>하위 클래스가 재정의</strong>하는 것 의미, 이는 런타임 중에 발생하는 동적 다형성!</li>
</ul>
<h3 id="설계-원칙">설계 원칙</h3>
<p>객체지향 프로그래밍을 설계할 때 <strong>SOLID 원칙</strong>을 지켜야한다.</p>
<ol>
<li><p><strong>SRP (Single Responsibility Principle)</strong>: 단일 책임의 원칙
 모든 클래스는 각각 하나의 책임만 가져야하는 원칙</p>
</li>
<li><p><strong>OCP (Open Closed Principle)</strong>: 개방-폐쇄의 원칙
 유지 보수 사항이 있으면 코드를 쉽게 확장하고 수정할 때는 닫혀 있어야 하는 원칙</p>
</li>
<li><p><strong>LSP (Liskov Substitution Principle):</strong> 리스코프 치환 원칙
 프로그램의 정확성을 깨뜨리지 않으면서 <span style="color: red"><strong>하위 타입의 인스턴스로 바꿀 수 있어야하는 것</strong></span>
 ex) 부모, 자식 계층 관계에서 <strong>부모 객체에 자식 객체를 넣어도 시스템이 문제없이</strong> 돌아가게 만드는 것</p>
</li>
<li><p><strong>ISP (Interface Segregation Principle)</strong>: 인터페이스 분리 원칙
 하나의 일반적인 인터페이스보다 <span style="color: red"><strong>구체적인 여러 개의 인터페이스</strong></span>를 만들어야 하는 원칙</p>
</li>
<li><p><strong>DIP (Dependency Inversion Principle)</strong>: 의존 역전 원칙
 <span style="color: red"><strong>자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위 클래스에 두어 변하기 쉬운 것의 변화에 영향을 받지 않게 하는 원칙</strong></span>
 ex) 타이어를 갈아끼울 수 있는 틀 만들고, 다양한 타이어를 교체 가능해야함</p>
<pre><code> -&gt; 상위 계층은 하위 계층의 변화에 대한 구현으로부터 독립</code></pre></li>
</ol>
<h2 id="3-절차형-프로그래밍">3. 절차형 프로그래밍</h2>
<p>로직이 수행되어야 할 연속적인 계산 과정으로 이루어져 있다.
<strong>일이 진행되는 방식</strong>으로 그저 코드를 구현</p>
<ul>
<li>코드의 가독성이 좋고 실행속도가 빠르다.</li>
<li>계산이 많은 작업에 쓰인다.</li>
</ul>
<p>단점으로 모듈화가 어렵고 유지 보수성이 떨어진다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CS] 1. 디자인 패턴]]></title>
            <link>https://velog.io/@jg_doxb/CS-1.-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@jg_doxb/CS-1.-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Tue, 28 Oct 2025 07:13:46 GMT</pubDate>
            <description><![CDATA[<p>CS 면접 질문 대비해서 CS 공부를 하고자한다.
공부할 때, 참고하는 책은 &quot;면접을 위한 CS 전공지식 노트&quot;이다.
한 단원씩 읽고, 메모장으로 정리하고 다음날 다시 정리해보면서 공부할 예정이다.</p>
<h2 id="1-싱글톤-패턴">1. 싱글톤 패턴</h2>
<h3 id="싱글톤-패턴-개념">싱글톤 패턴 개념</h3>
<p><span style="color: red"><strong>하나의 클래스에서 오직 하나의 인스턴스</strong></span>만 가지는 패턴이다.
하나의 클래스에서 여러개의 개별적인 인스턴스를 만들어낼 수 있지만, 하나만 생성해서 관리하는 것이다.
ex) 데이터베이스 연결</p>
<blockquote>
<p><strong>클래스, 객체, 인스턴스</strong></p>
</blockquote>
<ol>
<li><strong>클래스</strong>: 변수와 메서드를 가지는 명세서, 설계도</li>
<li><strong>객체</strong>: 클래스로 구현할 어떤 것</li>
<li><strong>인스턴스</strong>: 객체를 실체화 시킨 것</li>
</ol>
<p>인스턴스를 하나만 생성해서 관리하니깐, 구현과 유지보수가 쉬워보이지만 아니다.
단점은 다음과 같다.</p>
<ol>
<li>TDD에서 걸림돌이 된다.
단위 테스트 위주로 하는 테스트 주도 개발에서 테스트가 서로 <strong>독립적</strong>이고, 테스트를 <strong>어떤 순서</strong>로도 실행할 수 있어야되는데, <span style="color: red"><strong>미리 생성된 인스턴스를 기반</strong></span>으로 구현하는 패턴이라 독립적인 인스턴스를 만들기가 어렵다.</li>
<li>결합도 문제
모듈간의 결합도를 강하게 만든다. 이 때, <span style="color: red"><strong>DI (Dependency Injection)</strong></span>을 활용하여 결합도를 느슨하게 만들 수 있다.</li>
<li><strong>Java의 멀티쓰레드 환경</strong>
싱글톤 패턴으로 인스턴스를 생성할 때, 생성된 인스턴스가 있는지 먼저 검사를 하는 방식으로 구현이 되어있는 경우, 여러개의 쓰레드가 해당 인스턴스를 여러개 생성할 수 있다. <span style="color: red"><strong>(Thread Safe 문제 발생)</strong></span></li>
</ol>
<blockquote>
<p><strong>DI (Dependency Injection)</strong>
메인 모듈이 직접 하위 모듈에 의존성을 주기보단, 별도의 의존성 주입자 (Dependency Injecter)가 이 부분을 가로채 메인 모듈이 간접적으로 의존성을 주입하는 방식</p>
</blockquote>
<ul>
<li>A가 B에 의존성이 있다는 것은 B의 변경 사항에 A도 변해야 된다는 것을 의미</li>
</ul>
<h3 id="의존성-주입의-장점">의존성 주입의 장점</h3>
<ol>
<li>상위 모듈이 하위 모듈에 대한 의존성이 감소된다.</li>
<li>모듈을 쉽게 교체할 수 있는 구조가 된다. (테스팅, 마이그레이션 수월)</li>
<li>추상화 레이어를 기반으로 구현체를 넣기 때문에 애플리케이션의 의존성 방향이 일관적 (쉬운 기능 추론, 모듈 간의 관계 명확)</li>
</ol>
<h3 id="의존성-주입의-단점">의존성 주입의 단점</h3>
<ol>
<li>모듈들이 분리되어 클래스 수가 늘어나 <span style="color: red"><strong>복잡성이 증가 (약간의 런타임 패널티 발생!)</strong></span></li>
</ol>
<blockquote>
<p><strong>참고</strong>
SOLID에서 DIP (의존성 주입 원칙)
상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 않아야 한다.
둘 다 추상화에 의존! 추상화는 세부 사항에 의존하지 말아야 한다.</p>
</blockquote>
<h3 id="thread-safe-문제해결방법">Thread Safe 문제해결방법</h3>
<ol>
<li>싱글톤 패턴으로 인스턴스를 생성하는 메서드에 synchronized 접근제어자를 추가하여 쓰레드 하나가 접근 중일 때, 다른 쓰레드는 접근을 하지 못하게 락을 걸 수 있다. 대신, 인스턴스를 가져올 때마다 락이 걸리기 때문에 불필요한 오버헤드가 발생한다는 단점이 있다.</li>
<li>정적멤버방식으로 JVM이 클래스를 로드할 때 미리 싱글톤 객체를 생성할 수도 있다. 다만, 이는 자원낭비로 이어질 수 있다.</li>
</ol>
<p>다른 여러가지 방법도 많지만, 여기까지 정리하겠다.</p>
<h2 id="2-팩토리-패턴">2. 팩토리 패턴</h2>
<p><span style="color: red"><strong>객체를 생성하는 부분을 따로 분리</strong></span>하여 구현하는 패턴이다.
상속관계에 있는 두 클래스에서 상위 클래스는 객체 생성에 필요한 중요한 뼈대, 하위 클래스에서는 객체 생성에 관한 구체적인 내용을 결정한다.</p>
<blockquote>
<p><strong>팩토리 패턴 종류</strong></p>
</blockquote>
<ol>
<li>단순 팩토리 패턴</li>
<li>팩토리 메서드 패턴</li>
<li>추상 팩토리 패턴</li>
</ol>
<h3 id="java-단순-팩토리-패턴">Java 단순 팩토리 패턴</h3>
<pre><code class="language-java">enum CoffeeType {
    LATTE,
    ESPRESSO
}

abstract class Coffee {
    protected String name;

    public String getName() {
        return name
    }
}

class Latte extends Coffee {
    public Latte() {
        name = &quot;latte&quot;;
    }
}

class Espresso extends Coffee {
    public Espresso() {
        name = &quot;Espresso&quot;
    }
}

class CoffeeFactory {
    public static Coffee createCoffee(CoffeeType type) {
        switch (type) {
            case LATTE:
                return new Latte();
            case ESPRESSO:
                return new Espresso();
            default:
                throw new IllegalArgumentException(&quot;Invalid coffee type: &quot; + type);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Coffee coffee = CoffeeFactory.createCoffee(CoffeeType.LATTE);
        System.out.println(coffee.getName()); // latte
    }
}</code></pre>
<p>팩토리 밑에 객체 뼈대를 담당하는 상위 클래스를 두고, 해당 클래스를 상속하여 객체 생성을 담당하는 하위 클래스를 두어 구현한다.
팩토리에서는 생성할 객체의 타입을 받아 분기처리해서 객체를 반환한다.</p>
<h3 id="단순-팩토리-패턴-장점">단순 팩토리 패턴 장점</h3>
<ol>
<li>상위 클래스와 하위 클래스가 분리되기 때문에 느슨합 결합을 가짐</li>
<li>상위 클래스에서는 인스턴스 생성 방식에 대해 전혀 알 필요가 없어 더 많은 유연성을 가지게 됨</li>
<li>객체 생성 로직이 분리되어 있어서 유지 보수성이 증가</li>
</ol>
<h3 id="단순-팩토리-패턴-단점">단순 팩토리 패턴 단점</h3>
<ol>
<li>새로운 클래스를 추가하거나 제거할 때마다 조건문을 수정해야 한다.</li>
</ol>
<p><strong>SOLID의 OCP 위반 (확장에는 열려있으나, 수정에는 닫혀있어야한다.)</strong></p>
<p>그래서 팩토리 메서드 패턴과 추상 팩토리 패턴이 나왔다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>설명</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td><strong>팩토리 메서드 패턴</strong></td>
<td>객체 생성 책임을 <strong>하위(서브) 팩토리 클래스</strong>에 위임</td>
<td>- 새로운 클래스 추가·제거 시 <strong>클라이언트 코드 변경 없음</strong><br>- <strong>객체 생성과 사용 분리</strong>로 유연성 향상</td>
<td>- <strong>객체 종류 증가 시 팩토리 클래스 수도 증가</strong><br>- 구조 복잡도 상승</td>
</tr>
<tr>
<td><strong>추상 팩토리 패턴</strong></td>
<td><strong>관련된 객체의 그룹(제품군)</strong> 을 생성하는 <strong>인터페이스</strong> 제공</td>
<td>- <strong>제품군 일관성 유지</strong><br>- <strong>객체 생성 책임 분산</strong>으로 단일 팩토리의 과부하 방지</td>
<td>- <strong>구현 복잡</strong><br>- <strong>코드량 증가</strong></td>
</tr>
</tbody></table>
<h2 id="3-전략-패턴">3. 전략 패턴</h2>
<p>정책 패턴이라고도 하며, 객체의 행위를 바꾸고 싶은 경우 직접 수정하지 않고, 전략이라고 부르는 <span style="color: red"><strong>캡슐화한 알고리즘</strong></span>을 컨텍스트 안에서 바꿔주면서 상호 교체가 가능하게 만드는 패턴</p>
<p>ex) 결제 시스템 (카카오페이? 토스페이?), 로그인 방식 (서비스 내? OAuth?)</p>
<h2 id="4-옵저버-패턴">4. 옵저버 패턴</h2>
<p><span style="color: red"><strong>주체가 어떤 객체의 상태 변화를 관찰</strong></span>하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 <span style="color: red"><strong>옵저버들에게 변화를 알려주는</strong></span> 패턴</p>
<blockquote>
<p>주체: 객체의 상태 변화를 보고 있는 관찰자
옵저버: 객체 상태 변화에 따라 추가 변화가 생기는 객체들
ex) 유튜브 알림, 트위터 알림, MVC</p>
</blockquote>
<ul>
<li>MVC: 모델에서 변경 사항이 생겨, update로 뷰에 알려주고 컨트롤러가 작동한다.</li>
<li>주체와 객체를 분리하는 경우와 한번에 두는 경우가 있다.</li>
</ul>
<pre><code class="language-java">import java.util.ArrayList;
import java.util.List;

interface Subject {
    public void register(Observer obj);
    public void unregister(Observer obj);
    public void notifyObservers();
    public Object getUpdate(Observer obj);
}

interface Observer {
    public void update();
}

class Topic implements Subject {
    private List&lt;Observer&gt; observers;
    private String massage;

    public Topic() {
        this.observers = new ArrayList&lt;&gt;();
        this.message = &quot;&quot;;
    }

    @Override
    public void register(Observer obj) {
        if (!observers.contains(obj)) observers.add(obj);
    }

    @Override
    public void unregister(Observer obj) {
        observers.remove(obj);
    }

    @Override
    public void notifyObservers() {
        this.observers.forEach(Observer::update);
    }

    @Override
    public Object getUpdate(Observer obj) {
        return this.message;
    }

    public void postMessage(String msg) {
        System.out.println(&quot;Message sended to Topic: &quot; + msg);
        this.message = msg;
        notifyObservers(); // 옵저버에게 상태변화 알림
    }
}

class TopicSubscriber implements Observer {
    private String name;
    private Subject topic;

    public TopicSubscriber(String name, Subject topic) {
        this.name = name;
        this.topic = topic;
    }

    // 구독중인 옵저버들에게 상태 변화에 대한 알림이 감
    @Override
    public void update() {
        String msg = (String) topic.getUpdate(this);
        System.out.println(name + &quot;:: got message &gt;&gt; &quot; + msg);
    }
}

public class HelloWorld {
    public static void main(String[] args) {
        Topic topic = new Topic();
        Observer a = new TopicSubscriber(&quot;a&quot;, topic);
        Observer b = new TopicSubscriber(&quot;b&quot;, topic);
        Observer c = new TopicSubscriber(&quot;c&quot;, topic);
        topic.register(a);
        topic.register(b);
        topic.register(c);

        topic.postMessage(&quot;hello everyone!&quot;);
    }
}

/*
Message sended to Topic: hello everyone!
a:: got message &gt;&gt; hello everyone!
b:: got message &gt;&gt; hello everyone!
c:: got message &gt;&gt; hello everyone!
*/</code></pre>
<blockquote>
<p><strong>자바 상속과 구현</strong></p>
</blockquote>
<ol>
<li><strong>상속(extends)</strong>: 자식 클래스가 부모 클래스의 메서드 등을 상속받아 사용하며 <span style="color: red"><strong>자식 클래스에서 추가 및 확장</strong></span>을 할 수 있는 것을 말한다. 이를 통해 재사용성, 중복성의 최소화가 이루어 짐</li>
<li><strong>구현(implements)</strong>: 부모 인터페이스를 <span style="color: red"><strong>자식 클래스에서 재정의</strong></span>하여 구현하는 것, 상속과는 달리 반드시 부모클래스의 메서드를 재정의하여 구현해야한다.</li>
</ol>
<ul>
<li>상속은 일반 클래스, abstract 클래스 기반으로 구현, 구현은 인터페이스 기반으로 구현</li>
</ul>
<h2 id="5-프록시-패턴">5. 프록시 패턴</h2>
<p>대상 객체에 접근하기 전에 그 접근에 대한 <span style="color: red"><strong>흐름을 가로채</strong></span> 해당 접근을 필터링하거나 수정하는 역할을 하는 계층이 있는 패턴</p>
<ol>
<li>객체의 속성, 변환 등을 보완</li>
<li>보안, 데이터 검증, 캐싱, 로깅에 사용</li>
</ol>
<h3 id="프록시-서버">프록시 서버</h3>
<p>서버와 클라이언트 사이에서 클라이언트가 자신을 통해 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해주는 컴퓨터 시스템이나 응용 프로그램</p>
<blockquote>
<p><strong>예시</strong></p>
</blockquote>
<ol>
<li>nginx: 비동기 이벤트 기반의 구조와 다수의 연결을 효과적으로 처리 가능한 웹 서버</li>
</ol>
<p>-&gt; 서버 앞단의 프록시 서버로 활용
-&gt; 실제 포트를 숨기고, 정적 자원 gzip으로 압축, 메인 서버 앞단에서 로깅 가능
2. CloudFlare: 웹 서버 앞단에 프록시 서버로 두어 DDOS 공격 방어나 HTTPS 구축에 쓰인다.
-&gt; 의심스러운 트래픽은 CAPTCHA를 기반으로 일정부분 막아줌
3. CDN :각 사용자가 인터넷에 접속하는 곳과 가까운 곳에서 콘텐츠를 캐싱 또는 배포하는 서버 네트워크
-&gt; 이를 통해 웹 서버로 부터 콘텐츠를 다운로드하는 시간을 줄일 수 있음</p>
<h3 id="cors와-프론트엔드의-프록시-서버">CORS와 프론트엔드의 프록시 서버</h3>
<p>CORS(Cross-Origin Resource Sharing): 서버가 웹 브라우저에서 리소스를 로드할 때, <strong>다른 오리진을 통해 로드하지 못하게</strong> 하는 HTTP 헤더 기반 메커니즘</p>
<ul>
<li>오리진: 프로토콜과 호스트 이름, 포트의 조합을 말한다.</li>
</ul>
<p>프론트엔드 포트와 백엔드 포트가 다르면 테스팅 중 CORS 에러가 난다.
프록시 서버를 통해 프론트엔드에서 요청되는 <span style="color: red"><strong>오리진을 동일하게 수정</strong></span>해주면 테스팅이 진행이 된다.</p>
<h2 id="6-이터레이터-패턴">6. 이터레이터 패턴</h2>
<p><span style="color: red"><strong>이터레이터</strong></span>를 사용하여 컬렉션의 요소들에 접근하는 패턴이다.
순회할 수 있는 여러 가지 자료형의 구조와 상관없이 이터레이터라는 하나의 인터페이스로 순회가 가능</p>
<ul>
<li><strong>이터레이터 프로토콜</strong>: 이터러블한 객채들을 순회할 때 쓰이는 규칙</li>
<li><strong>이터러블한 객체</strong>: 반복 가능한 객체로 배열을 일반화한 객체</li>
</ul>
<h2 id="7-노출모듈-패턴">7. 노출모듈 패턴</h2>
<p>즉시 실행 함수를 통해 private, public 같은 접근제어자를 만드는 패턴
ex) 자바스크립트는 접근 제어자가 없어, 전역 범위에서 스크립트가 실행된다.
-&gt; 해당 패턴으로 접근 제어자를 구현하기도 한다.</p>
<blockquote>
<p><strong>접근 제어자 정리</strong></p>
</blockquote>
<ol>
<li><strong>public</strong>: 클래스에 정의된 함수에서 접근 가능, 자식 클래스와 <strong>외부 클래스에서 접근 가능</strong></li>
<li><strong>protected</strong>: 클래스에 정의된 함수에서 접근 가능, 자식 클래스는 접근 가능, <strong>외부 클래스는 접근 불가</strong></li>
<li><strong>private</strong>: 클래스에 정의된 함수에서 접근 가능, <strong>자식 클래스와 외부 클래스에서 접근 불가</strong></li>
<li><strong>즉시 실행 함수</strong>: 함수를 정의하자마자 바로 호출하는 함수, 초기화 코드, 라이브러리 내 전역 변수의 충돌 방지 등에 사용한다.</li>
</ol>
<h2 id="8-mvc-패턴">8. MVC 패턴</h2>
<p><strong>Model, View, Controller</strong>로 이루어진 패턴
애플리케이션의 구성 요소를 세 가지 역할로 구분하여 개발 프로세스에서 각각의 구성 요소에만 집중해서 개발할 수 있다.</p>
<ul>
<li>장점: 재사용성과 확장성이 용이</li>
<li>단점: 애플리케이션이 복잡해질수록 모델과 뷰의 관계가 복잡</li>
</ul>
<h3 id="model">Model</h3>
<ol>
<li>애플리케이션의 데이터인 데이터베이스, 상수, 변수 등을 의미</li>
<li><strong>뷰에서 데이터를 생성하거나 수정하면 컨트롤러를 통해 모델을 생성하거나 갱신</strong></li>
</ol>
<h3 id="view">View</h3>
<ol>
<li>사용자 인터페이스 요소 -&gt; 모델을 기반으로 사용자가 볼 수 있는 화면</li>
<li>모델이 가지고 있는 정보를 따로 저장X, <strong>단순히 화면에 표시하는 정보만</strong> 가지고 있어야 한다.</li>
<li><strong>변경이 일어나면 컨트롤러에 이를 전달</strong></li>
</ol>
<h3 id="controller">Controller</h3>
<ol>
<li>하나 이상의 모델과 하나 이상의 뷰를 잇는 다리 역할 -&gt; <strong>이벤트 등 메인 로직을 담당</strong></li>
<li>모델과 뷰의 생명주기 관리, 모델이나 뷰의 변경 통지를 받으면 이를 해석해서 각각의 구성 요소에 해당 내용을 알려준다.</li>
</ol>
<p>Spring: 디스패처 서블릿의 요청 처리 과정을 이해해보자...</p>
<blockquote>
<p><strong>MVC로부터 파생된 패턴</strong></p>
</blockquote>
<ul>
<li><strong>MVP 패턴</strong></li>
</ul>
<ol>
<li>MVC의 Controller가 <strong>Presenter</strong>로 교체된 패턴</li>
<li>뷰와 프레젠터는 <span style="color: red"><strong>일대일 관계</strong></span>여서 MVC 패턴보다 더 강한 결합을 지닌 패턴</li>
</ol>
<ul>
<li><strong>MVVM 패턴</strong></li>
</ul>
<ol>
<li>MVC의 Controller가 <strong>뷰모델(view model)</strong>로 바뀐 패턴</li>
<li>뷰모델은 뷰를 더 추상화한 계층, MVC패턴과 다르게 <span style="color: red"><strong>커맨드와 데이터 바인딩</strong></span>을 가짐</li>
<li>뷰와 뷰모델 사이의 양방향 데이터 바인딩 지원, UI를 별도의 코드 수정 없이 재사용가능, 단위 테스팅하기 쉽다는 장점</li>
<li>값 대입만으로 변수가 변경, 양방향 바인딩, html을 토대로 컴포넌트를 구축, 재사용 가능한 컴포넌트를 기반으로 UI를 구축가능 (Vue.js)</li>
</ol>
<ul>
<li><strong>커맨드</strong>: 여러 가지 요소에 대한 처리를 하나의 액션으로 처리할 수 있게 하는 기법</li>
<li><strong>데이터 바인딩</strong>: 화면에 보이는 데이터와 웹 브라우저의 메모리 데이터를 일치, <strong>뷰모델을 변경하면 뷰가 변경</strong>된다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[호기심 천국] 1. inthiswork 사이트는 왜 로딩 시간이 오래걸릴까?]]></title>
            <link>https://velog.io/@jg_doxb/%ED%98%B8%EA%B8%B0%EC%8B%AC-%EC%B2%9C%EA%B5%AD-1.-inthiswork-%EC%82%AC%EC%9D%B4%ED%8A%B8%EB%8A%94-%EC%99%9C-%EB%A1%9C%EB%94%A9-%EC%8B%9C%EA%B0%84%EC%9D%B4-%EC%98%A4%EB%9E%98%EA%B1%B8%EB%A6%B4%EA%B9%8C</link>
            <guid>https://velog.io/@jg_doxb/%ED%98%B8%EA%B8%B0%EC%8B%AC-%EC%B2%9C%EA%B5%AD-1.-inthiswork-%EC%82%AC%EC%9D%B4%ED%8A%B8%EB%8A%94-%EC%99%9C-%EB%A1%9C%EB%94%A9-%EC%8B%9C%EA%B0%84%EC%9D%B4-%EC%98%A4%EB%9E%98%EA%B1%B8%EB%A6%B4%EA%B9%8C</guid>
            <pubDate>Sun, 26 Oct 2025 10:02:32 GMT</pubDate>
            <description><![CDATA[<p>HTTP 강의를 완강하고, 배웠던 내용을 기반으로 항상 이용하던 채용사이트인 인디스워크가 다른 사이트에 비해 로드속도가 오래걸리는 이유를 파악해보고자 나름대로 원인을 분석을 해본다.</p>
<h2 id="1-자소설닷컴-vs-인디스워크-응답속도">1. 자소설닷컴 vs 인디스워크 응답속도</h2>
<table>
<thead>
<tr>
<th>자소설닷컴 응답속도</th>
<th>인디스워크 응답속도</th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/jg_doxb/post/4b90b4bb-c002-436a-b146-8de4f5949ca4/image.png" alt=""></td>
<td><img src="https://velog.velcdn.com/images/jg_doxb/post/84bfc356-e1f5-4f98-acd8-6121a9f9868c/image.png" alt=""></td>
</tr>
</tbody></table>
<p>자소설닷컴 응답대기 시간은 95.22ms, 인디스워크 응답대기 시간은 2.18s 이다.
채용사이트이고 다루는 데이터가 비슷한데, 응답대기 시간은 20배가 넘게 차이가 난다.
캐시를 하는 데이터가 없어서 그런지 응답들을 확인해보았으나, 이미지 같은 것들은 캐시 데이터에서 활용해서 페이지를 로드하는 것을 알 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/f0cfe461-6c16-4078-ad78-b71184e98a9d/image.png" alt="">
인디스워크는 ETag 전략으로 캐시 데이터를 관리한다는 것도 추가적으로 알 수 있었다.
그럼 똑같이 한국에서 서비스를 하는데, 왜 서버 응답대기 시간이 20배 넘게 차이가 날까?</p>
<p>프록시 캐시에 대한 개념을 배우면서, 서버와 클라이언트간에 물리적인 거리로 인해 응답지연이 발생하고 그것을 해결하기 위해 프록시 서버를 둔다는 사실을 알게 되었다.
이 덕분에 <span style="color: red"><strong>서버의 물리적인 거리 차이</strong></span>에서 응답대기 시간의 차이를 만든다는 생각으로 이어질 수 있었다.
그래서 서버의 위치를 찾아봤다.</p>
<h2 id="2-서버-위치-찾기">2. 서버 위치 찾기</h2>
<p>물리적인 서버 위치를 찾기에는 정보가 없으니깐... 서버의 대략적인 위치를 추측해보기 위해 도메인으로 서버 위치를 찾아봤다.</p>
<p><a href="http://www.ipipipip.net/">http://www.ipipipip.net/</a></p>
<p>이 링크에서 두 사이트를 찍어보았다.</p>
<table>
<thead>
<tr>
<th>자소설닷컴 서버위치</th>
<th>인디스워크 서버위치</th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/jg_doxb/post/1eac0ae4-1a14-40bc-b964-6826f8dca0a8/image.png" alt=""></td>
<td><img src="https://velog.velcdn.com/images/jg_doxb/post/92036f8f-b09a-49c7-9fd9-ce1d60b4b385/image.png" alt=""></td>
</tr>
</tbody></table>
<p>자소설닷컴은 도메인을 국내 호스팅 업체의 네임서버, 인디스워크는 해외 호스팅 업체의 네임서버를 활용한다는 것을 알 수 있었다.
서버의 물리적인 거리 차이로 인해 응답대기시간이 길어지고, 같은 국내 사이트더라도 <strong>네임서버 위치에 따라 응답속도 차이가 발생</strong>할 수 있다는 것을 확인했다.</p>
<h2 id="3-결론">3. 결론</h2>
<p>자소설닷컴은 국내, 인디스워크는 해외 호스팅 업체를 통해 도메인을 할당받았고, 호스팅 업체의 네임서버의 물리적인 위치차이로 인해 20배가 넘는 응답속도 차이가 발생한다고 추측해본다. 그리고 인디스워크가 이용하는 해외 호스팅 업체는 국내에 프록시 서버가 없는 듯하다.</p>
<p>응답속도를 개선하기 위해서는 호스팅 업체를 국내로 바꾸는 방법이 제일 쉬운 방법으로 보인다...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] 8. HTTP 헤더 - 캐시와 조건부 요청]]></title>
            <link>https://velog.io/@jg_doxb/HTTP-8.-HTTP-%ED%97%A4%EB%8D%94-%EC%BA%90%EC%8B%9C%EC%99%80-%EC%A1%B0%EA%B1%B4%EB%B6%80-%EC%9A%94%EC%B2%AD</link>
            <guid>https://velog.io/@jg_doxb/HTTP-8.-HTTP-%ED%97%A4%EB%8D%94-%EC%BA%90%EC%8B%9C%EC%99%80-%EC%A1%B0%EA%B1%B4%EB%B6%80-%EC%9A%94%EC%B2%AD</guid>
            <pubDate>Sat, 25 Oct 2025 05:52:58 GMT</pubDate>
            <description><![CDATA[<h2 id="1-캐시-기본-동작">1. 캐시 기본 동작</h2>
<p>캐시가 없을 때는 같은 요청을 반복하더라도 같은 용량의 데이터를 전송한다.</p>
<blockquote>
<p><strong>캐시가 없을 경우</strong></p>
</blockquote>
<ol>
<li>데이터가 변경되지 않아도 계속 네트워크를 통해서 데이터를 다운로드 받아야 한다.</li>
<li>인터넷 네트워크는 매우 느리고 비싸다.</li>
<li>브라우저 로딩 속도가 느리다.</li>
<li>느린 사용자 경험
<span style="color: red"><strong>느리고, 그만큼 네트워크에 불필요한 부하가 걸린다</strong></span></li>
</ol>
<h3 id="캐시">캐시</h3>
<p>cache-control 헤더를 통해 <strong>캐시 유효시간</strong>을 설정하여 첫 번째 요청을 처리하면, 시간 내에 같은 요청이 들어오면 캐시 데이터를 활용한다.
단. 캐시 시간이 초과되었을 경우, 서버를 통해 데이터를 다시 조회하고, 캐시를 갱신한다. 이 때, 다시 네트워크 다운로드가 발생한다.</p>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/107d092c-658c-452f-8105-b2c06fc2439f/image.png" alt=""></p>
<blockquote>
<p><strong>캐시가 있을 경우</strong></p>
</blockquote>
<ol>
<li>캐시 덕분에 캐시 가능 시간동안 네트워크를 사용하지 않아도 된다.</li>
<li>비싼 네트워크 사용량을 줄일 수 있다.</li>
<li>브라우저 로딩 속도가 매우 빠르다.</li>
<li>빠른 사용자 경험
<span style="color: red"><strong>빠르고, 그만큼 네트워크에 불필요한 부하를 줄일 수 있다</strong></span></li>
</ol>
<h3 id="캐시-시간-초과">캐시 시간 초과</h3>
<p>캐시 유효 시간이 초과해서 서버에 다시 요청하면 다음 두 가지 상황이 나타난다.</p>
<ol>
<li>서버에서 기존 데이터를 변경함</li>
<li>서버에서 기존 데이터를 변경하지 않음</li>
</ol>
<blockquote>
<p><strong>서버에서 데이터를 변경하지 않았을 경우</strong></p>
</blockquote>
<ol>
<li>생각해보면 데이터를 전송하는 대신에 저장해 두었던 캐시를 재사용 할 수 있다.</li>
<li>단, <span style="color: red"><strong>클라이언트의 데이터와 서버의 데이터가 같다는 사실</strong></span>을 확인할 수 있는 방법 필요 -&gt; <span style="color: red"><strong>검증 헤더</strong></span></li>
</ol>
<p><strong>검증 헤더</strong>를 통해 캐시 시간 초과되었을 경우에도, 캐시를 재활용할 수 있게 한다.
예시는 다음과 같다.
<img src="https://velog.velcdn.com/images/jg_doxb/post/9854924d-a72f-448a-99a2-61642377ddcd/image.png" alt=""></p>
<p><strong>Last-Modified</strong> 헤더 추가를 통해 데이터의 최종 수정일 정보도 기록한다.</p>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/f771c761-3068-4b8d-818f-d4c4c4b3ab91/image.png" alt=""></p>
<p><strong>If-Modified-Since</strong> (조건부 요청) 요청 헤더를 붙여서 서버로 넘기고, 데이터 최종 수정일이 변경된 사항이 없으면 만료된 캐시를 재활용할 수 있다.
<strong>304 Not Modified 응답</strong>, 캐시 유효시간 재설정, HTTP 바디 없이 헤더 정보만 전송한다.</p>
<blockquote>
<p><strong>조건에 부합하는 데 왜 304?</strong>
modified: 수정된, since: ~이후에
<span style="color: red"><strong>if-modified-since: 이 날짜 이후에 수정되었니?</strong></span>
서버에서 기존 데이터 변경사항이 없으면 수정이 안된거다.
이 날짜 이후에 수정되었니?에 대한 답변은 <strong>아니오.</strong> 이므로 304 응답이 오고, 캐시를 재활용한다.</p>
</blockquote>
<p>캐시 시간 초과일 경우 정리를 하면 다음과 같다.
<span style="color: red"><strong>1. 캐시 유효 시간이 초과해도, 서버의 데이터가 갱신되지 않으면 -&gt; 304 Not Modified + 헤더 메타 정보만 응답 (바디X)</strong></span>
2. 클라이언트는 서버가 보낸 응답 헤더 정보로 캐시의 메타 정보를 갱신
3. 클라이언트는 캐시에 저장되어 있는 데이터 재활용
4. 결과적으로 네트워크 다운로드가 발생하지만 용량이 적은 헤더 정보만 다운로드</p>
<h2 id="2-검증-헤더와-조건부-요청-헤더">2. 검증 헤더와 조건부 요청 헤더</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>설명</th>
<th>관련 헤더</th>
<th>동작 결과</th>
</tr>
</thead>
<tbody><tr>
<td><strong>검증 헤더</strong></td>
<td>캐시 데이터와 서버 데이터가 같은지 검증하기 위한 데이터</td>
<td><code>Last-Modified</code>, <code>ETag</code></td>
<td>캐시 유효성 판단 근거 제공</td>
</tr>
<tr>
<td><strong>조건부 요청 헤더</strong></td>
<td>검증 헤더를 사용해 서버에 조건부 요청을 수행</td>
<td><code>If-Modified-Since</code> (<code>Last-Modified</code> 기반), <code>If-None-Match</code> (<code>ETag</code> 기반)</td>
<td>조건 만족 시 <strong>200 OK</strong>, 불만족 시 <strong>304 Not Modified</strong></td>
</tr>
</tbody></table>
<blockquote>
<p><strong>예시</strong>
If-Modified-Since: 이후에 데이터가 수정되었으면?</p>
</blockquote>
<ul>
<li>데이터 미변경 예시<ul>
<li>캐시: 2020년 11월 10일 10:00:00 vs 서버: 2020년 11월 10일 10:00:00</li>
<li>304 Not Modified, 헤더 데이터만 전송(Body 미포함)</li>
<li>전송 용량 0.1M (헤더 0.1M, 바디 1.0M)</li>
</ul>
</li>
<li>데이터 변경 예시<ul>
<li>캐시: 2020년 11월 10일 10:00:00 vs 서버: 2020년 11월 10일 <span style="color: red"><strong>11</strong></span>:00:00</li>
<li>200 OK, 모든 데이터 전송 (Body 포함)</li>
<li>전송 용량 1.1M (헤더 0.1M, 바디 1.0M)</li>
</ul>
</li>
</ul>
<h3 id="last-modified-if-modified-since-단점">Last-Modified, If-Modified-Since 단점</h3>
<ol>
<li>1초 미만 (0.x초) 단위로 캐시 조정이 불가능</li>
<li>날짜 기반의 로직 사용</li>
<li>데이터를 수정해서 날짜가 다르지만, 같은 데이터를 수정해서 데이터 결과가 똑같은 경우</li>
<li>서버에서 별도의 캐시 로직을 관리하고 싶은 경우
 예) 스페이스나 주석처럼 크게 영향이 없는 변경에서 캐시를 유지하고 싶은 경우</li>
</ol>
<h3 id="etag-if-none-match">ETag, If-None-Match</h3>
<ol>
<li><span style="color: red"><strong>캐시용 데이터에 임의의 고유한 버전</strong></span> 이름을 달아둠
예) ETag: &quot;v1.0&quot;, ETag: &quot;studyhttp&quot;</li>
<li>데이터가 변경되면 이 이름을 바꾸어서 변경함 (Hash를 다시 생성)
예) ETag: &quot;aaaaa&quot; -&gt; ETag: &quot;bbbbb&quot;</li>
<li>단순하게 <span style="color: red"><strong>ETag만 보내서</strong></span> 같으면 유지, 다르면 다시 받기!</li>
</ol>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/0ff02b2a-a6b2-4a35-acaa-1b9cdff0cd25/image.png" alt=""></p>
<p><strong>ETag</strong> 헤더 추가를 통해 데이터의 고유버전을 저장</p>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/a6f4d761-2dff-415a-a9ed-7b3f9175e343/image.png" alt=""></p>
<p><strong>If-None-Match</strong> (조건부 요청) 요청 헤더를 붙여서 서버로 넘기고, 데이터 고유버전 변경된 사항이 없으면 만료된 캐시를 재활용할 수 있다.</p>
<blockquote>
<p><strong>ETag 활용시</strong></p>
</blockquote>
<ol>
<li>캐시 제어 로직을 서버에서 완전히 관리</li>
<li>클라이언트는 단순히 이 값을 서버에 제공(클라이언트는 캐시 메커니즘을 모름)
 예시 1) 서버는 베타 오픈 기간인 3일 동안 파일이 변경되어도 ETag를 동일하게 유지
 예시 2) 애플리케이션 배포 주기에 맞추어 ETag 모두 갱신</li>
</ol>
<h2 id="3-캐시-제어-헤더">3. 캐시 제어 헤더</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>헤더</th>
<th>설명</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Cache-Control</strong></td>
<td><code>Cache-Control: max-age=&lt;초&gt;</code></td>
<td>캐시 유효 시간(초 단위) 설정</td>
<td>최신 HTTP 권장 방식</td>
</tr>
<tr>
<td></td>
<td><code>Cache-Control: no-cache</code></td>
<td>캐시 저장은 가능하지만, 사용 전 원 서버 검증 필요</td>
<td>검증 후 사용</td>
</tr>
<tr>
<td></td>
<td><code>Cache-Control: no-store</code></td>
<td>민감 데이터로 저장 불가, 메모리 사용 후 즉시 삭제</td>
<td>보안 목적</td>
</tr>
<tr>
<td><strong>Pragma</strong></td>
<td><code>Pragma: no-cache</code></td>
<td>HTTP/1.0 하위 호환용 캐시 제어</td>
<td>구버전 호환용</td>
</tr>
<tr>
<td><strong>Expires</strong></td>
<td>Expires: 날짜 지정 <br>(예: <code>Expires: Mon, 01 Jan 1990 00:00:00 GMT</code>)</td>
<td>캐시 만료일을 절대 시간으로 지정</td>
<td>HTTP/1.0부터 사용, <code>Cache-Control: max-age</code> 우선</td>
</tr>
</tbody></table>
<h2 id="4-프록시-캐시">4. 프록시 캐시</h2>
<p>원 서버에 접근하기에는 거리에 비례히여 <strong>응답속도가 느려진다</strong> -&gt; <span style="color: red"><strong>프록시 캐시 도입</strong></span></p>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/1645a288-043c-41c8-afb8-e5583b1b545a/image.png" alt=""></p>
<blockquote>
<p><strong>Cache-Control 캐시 지시어 (directives) - 기타</strong></p>
</blockquote>
<ol>
<li>Cache-Control: public
 응답이 public 캐시에 저장되어도 됨</li>
<li>Cache-Control: private
 응답이 해당 사용자만을 위한 것임, private 캐시에 저장해야 함(기본값)</li>
<li>Cache-Control: s-maxage
 프록시 캐시에만 적용되는 max-age</li>
<li>Age: 60 (HTTP 헤더)
 오리진 서버에서 응답 후 프록시 캐시 내에 머문 시간(초)</li>
</ol>
<h2 id="5-캐시-무효화">5. 캐시 무효화</h2>
<pre><code>Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache</code></pre><p>이걸 다 넣어주어야 <strong>확실한 캐시 무효화</strong> 응답이 된다.</p>
<ol>
<li>Cache-Control: no-cache
 데이터는 캐시해도 되지만, 항상 원 서버에 검증하고 사용(이름에 주의!)</li>
<li>Cache-Control: no-store
 데이터에 민감한 정보가 있으므로 저장하면 안됨
 (메모리에서 사용하고 최대한 빨리 삭제)</li>
<li><span style="color: red"><strong>Cache-Control: must-revalidate</strong></span>
 캐시 만료후 최초 조회시 원 서버에 검증해야함
 원 서버 접근 실패시 반드시 오류가 발생해야함 - <strong>504(Gateway Timeout)</strong>
 must-revalidate는 캐시 유효 시간이라면 캐시를 사용함</li>
<li>Pragma: no-cache
 HTTP 1.0 하위 호환</li>
</ol>
<h3 id="must-revalidate가-필요한-이유">must-revalidate가 필요한 이유</h3>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/807ff5ce-ed16-4dc7-a74d-2825a25346bc/image.png" alt=""></p>
<p><strong>no-cache</strong>는 프록시 캐시 서버가 원 서버와 순간 네트워크가 단절되면, <strong>오류 보다는 오래된 데이터라도 보여주는 경우가 많다.</strong>
<span style="color: red"><strong>must-revalidate는 504 Gateway Timeout을 띄운다.</strong><span></p>
<h2 id="6-마무리하며">6. 마무리하며...</h2>
<p>HTTP 강의를 완강하면서, 데이터가 네트워크를 통해 전달되는 과정, 그리고 그 상태에 따라 서비스를 설계 하는 전략, 개발자의 시선으로 통신을 최적화하는 개발 방법에 대해 알게 되었다...
API 서버를 띄우고 Postman으로 API 기능을 검사할 때, 뜨는 응답 값들이 이러한 깊은 의미들을 가지고 있다는 것을 배울 수 있었다.</p>
<p>하반기 취업준비전략 변경과 동시에 시작한 웹 관련 강의듣고 포스팅하기... 이런저런 이유로 중간중간 포기를 하고 싶을 때가 많았다. 그리고 최근 최악의 1주일을 보내게 되면서 그동안 노력했던 것들에 대한 허무함이 다가왔다. 그래도 버틸 수 있는 건 여러가지 이유가 있지만, 강의 듣고 포스팅이라는 루틴이 그 중 하나다. 이러한 루틴이 있기 때문에 실패를 하더라도 변경한 전략에 대해 흔들리지 않을 수 있다는 생각이 든다.</p>
<p>근데, <del>지금은 진짜 큰일남</del>. 다음 포스팅부터 웹 강의듣고 내용 정리방식은 잠시 멈출 것이다. 면접을 위한 CS 준비, 그리고 자바 코딩테스트 준비 위주로 정리를 할 예정이다.</p>
<p>일단, 취업을 위한 공부에 집중하자... 그래야 Next가 있다.</p>
<hr>
<p>이 링크를 통해 구매하시면 제가 수익을 받을 수 있어요. 🤗</p>
<p><a href="https://inf.run/YZxop">https://inf.run/YZxop</a></p>
<p>강의 자료 중 일부를 포스팅에 활용해서 출처용으로 겸사겸사 링크를 달아두었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] 7. HTTP 헤더 - 일반 헤더]]></title>
            <link>https://velog.io/@jg_doxb/HTTP-7.-HTTP-%ED%97%A4%EB%8D%94-%EC%9D%BC%EB%B0%98-%ED%97%A4%EB%8D%94</link>
            <guid>https://velog.io/@jg_doxb/HTTP-7.-HTTP-%ED%97%A4%EB%8D%94-%EC%9D%BC%EB%B0%98-%ED%97%A4%EB%8D%94</guid>
            <pubDate>Mon, 20 Oct 2025 05:17:56 GMT</pubDate>
            <description><![CDATA[<h2 id="1-http-헤더-개요">1. HTTP 헤더 개요</h2>
<ol>
<li>HTTP 전송에 필요한 모든 부가정보</li>
<li>메시지 바디의 내용, 메시지 바디의 크기, 압축, 인증, 요청 클라이언트, 서버 정보, 캐시 관리 정보 등등</li>
<li>표준 헤더가 너무 많음</li>
<li>필요시 임의의 헤더 추가 가능</li>
</ol>
<blockquote>
<h3 id="과거-표준-rfc-2616">과거 표준 (RFC 2616)</h3>
<p><strong>HTTP 헤더</strong>
General 헤더: 메시지 전체에 적용되는 정보, ex) Connection: close
Request 헤더: 요청 정보, ex) User-Agent: Mozilla/5.0
Response 헤더: 응답 정보, ex) Server: Apache
<strong>Entity 헤더</strong>: 엔티티 바디 정보, ex) Content-Type: text/html, Content-Length: 3423
<br>
<strong>HTTP 바디</strong>
메시지 본문(message body)은 <strong>엔티티 본문(entity body)</strong>을 전달하는데 사용
엔티티 본문은 요청이나 응답에서 전달할 실제 데이터
엔티티 헤더는 엔티티 본문의 데이터를 해석할 수 있는 정보 제공</p>
</blockquote>
<p>1999년 RFC2616 (폐기)
<strong>2014년 RFC7230~7235 (등장)</strong></p>
<blockquote>
<h3 id="현재-표준-rfc-7230">현재 표준 (RFC 7230)</h3>
<p><strong>HTTP 헤더</strong>
엔티티(Entity) -&gt; <span style="color: red"><strong>표현(Representation)</strong></span>으로 개념이 바뀌었다.
<strong>Representation</strong> = representation Metadata + Representation Data
<br>
<strong>HTTP 바디</strong>
메시지 본문(message body)을 통해 <span style="color: red"><strong>표현 데이터</strong></span> 전달
메시지 본문 = 페이로드(payload) - 보내고자하는 데이터 그 자체
표현은 요청이나 응답에서 전달할 실제 데이터
표현 헤더는 표현 데이터를 해석할 수 있는 정보 제공
<strong>참고</strong>: 표현 헤더는 표현 메타데이터와, 페이로드 메시지를 구분해야하지만, 여기서 생략</p>
</blockquote>
<p>페이로드(payload)는 택배를 예시로 들자면,
택배를 보낼 물건 - payload임
택배와 관련된 송장 정보들 - payload가 아님</p>
<h2 id="2-표현-representation">2. 표현 (representation)</h2>
<table>
<thead>
<tr>
<th>헤더</th>
<th>의미</th>
<th>상세 설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Content-Type</strong></td>
<td>표현 데이터의 형식</td>
<td>전송되는 데이터의 <strong>미디어 타입</strong>과 <strong>문자 인코딩 방식</strong>을 지정</td>
<td><code>text/html; charset=utf-8</code><br><code>application/json</code><br><code>image/png</code></td>
</tr>
<tr>
<td><strong>Content-Encoding</strong></td>
<td>표현 데이터의 압축 방식</td>
<td>데이터를 전송 전 <strong>압축할 때 사용한 인코딩 방식</strong>을 지정. <br>수신 측은 이 정보를 사용해 압축을 해제함</td>
<td><code>gzip</code><br><code>deflate</code><br><code>identity</code> <em>(압축 없음)</em></td>
</tr>
<tr>
<td><strong>Content-Language</strong></td>
<td>표현 데이터의 자연 언어</td>
<td>콘텐츠가 작성된 <strong>언어 또는 지역 코드</strong>를 지정</td>
<td><code>ko</code><br><code>en</code><br><code>en-US</code></td>
</tr>
<tr>
<td><strong>Content-Length</strong></td>
<td>표현 데이터의 길이</td>
<td>본문의 <strong>크기(바이트 단위)</strong>를 지정. <br>단, <code>Transfer-Encoding</code> 사용 시에는 포함하지 않음</td>
<td><code>348</code></td>
</tr>
<tr>
<td>표현 헤더는 전송, 응답 둘다 사용</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h2 id="3-협상-content-negotiation">3. 협상 (content negotiation)</h2>
<p><span style="color: red"><strong>클라이언트가 선호하는 표현 요청</strong></span></p>
<ol>
<li>Accept: 클라이언트가 선호하는 미디어 타입 전달</li>
<li>Accept-Charset: 클라이언트가 선호하는 문자 인코딩</li>
<li>Accept-Encoding: 클라이언트가 선호하는 압축 인코딩</li>
<li>Accept-Language: 클라이언트가 선호하는 자연 언어
협상 헤더는 요청시에만 사용</li>
</ol>
<p><strong>협상의 <span style="color: red">우선 순위</span>를 정하기 위해서 Quality Values(q) 값 사용한다.</strong></p>
<h3 id="협상과-우선순위1">협상과 우선순위1</h3>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/a57aedec-50e8-405e-adb4-57f60a644e0a/image.png" alt="">
<strong>0~1 사이의 값을 가지고, 클수록 높은 우선순위를 가진다.</strong>
생략하면 1이다.</p>
<blockquote>
<p><strong>예시</strong>
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7</p>
</blockquote>
<ol>
<li>ko-KR;q=1 (생략)</li>
<li>ko;q=0.9</li>
<li>en-US;q=0.8</li>
<li>en;q=0.7</li>
</ol>
<h3 id="협상과-우선순위2">협상과 우선순위2</h3>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/51d36bbd-ea8c-4235-8929-18a1ee3a5165/image.png" alt="">
<strong>구체적인 것이 우선한다.</strong></p>
<blockquote>
<p><strong>예시</strong>
Accept: text/*, text/plain, text/plain;format=flowed, */*</p>
</blockquote>
<ol>
<li>text/plain;format=flowed</li>
<li>text/plain</li>
<li>text/*</li>
<li>*/*</li>
</ol>
<h3 id="협상과-우선순위3">협상과 우선순위3</h3>
<p><strong>구체적인 것을 기준으로 미디어 타입을 맞춘다.</strong></p>
<blockquote>
<p><strong>예시</strong>
Accept: text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5</p>
</blockquote>
<table>
<thead>
<tr>
<th>MIME 타입</th>
<th>품질 지수(q값)</th>
</tr>
</thead>
<tbody><tr>
<td><strong>text/html;level=1</strong></td>
<td>1.0</td>
</tr>
<tr>
<td><strong>text/html</strong></td>
<td>0.7</td>
</tr>
<tr>
<td><strong>text/plain</strong></td>
<td>0.3</td>
</tr>
<tr>
<td><strong>image/jpeg</strong></td>
<td>0.5</td>
</tr>
<tr>
<td><strong>text/html;level=2</strong></td>
<td>0.4</td>
</tr>
<tr>
<td><strong>text/html;level=3</strong></td>
<td>0.7</td>
</tr>
</tbody></table>
<h2 id="4-전송방식">4. 전송방식</h2>
<table>
<thead>
<tr>
<th>전송 방식</th>
<th>관련 헤더</th>
<th>설명</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td><strong>단순 전송</strong></td>
<td><code>Content-Length</code></td>
<td>콘텐츠의 전체 길이를 미리 알고 있을 때 사용.<br><strong>한 번에 전송</strong>하고 <strong>한 번에 수신</strong>함</td>
<td>정적 파일 등 고정 길이 데이터</td>
</tr>
<tr>
<td><strong>압축 전송</strong></td>
<td><code>Content-Encoding</code></td>
<td>데이터를 전송 전에 <strong>압축</strong>함.<br>클라이언트는 <code>Content-Encoding</code> 정보를 이용해 <strong>압축 해제</strong></td>
<td><code>gzip</code>, <code>deflate</code>, <code>br</code> 등</td>
</tr>
<tr>
<td><strong>분할 전송</strong></td>
<td><code>Transfer-Encoding: chunked</code></td>
<td>데이터의 전체 크기를 알 수 없을 때 <strong>조각(chunk)</strong> 단위로 전송</td>
<td><code>Content-Length</code> 사용 금지</td>
</tr>
<tr>
<td><strong>범위 전송</strong></td>
<td><code>Range</code>, <code>Content-Range</code></td>
<td>요청 시 또는 응답 시 <strong>일부 데이터만 선택적으로 전송</strong></td>
<td>대용량 파일 다운로드, 이어받기 기능 등</td>
</tr>
</tbody></table>
<h2 id="5-일반-정보">5. 일반 정보</h2>
<table>
<thead>
<tr>
<th>헤더</th>
<th>의미</th>
<th>상세 설명</th>
<th>사용 위치</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>From</strong></td>
<td>유저 에이전트의 이메일 정보</td>
<td>요청을 보낸 사용자나 에이전트의 이메일 주소를 표시. 일반적으로 잘 사용되지 않으며 검색 엔진 등에서 활용</td>
<td>요청(Request)</td>
<td><code>From: user@example.com</code></td>
</tr>
<tr>
<td><strong>Referer</strong></td>
<td>이전 웹 페이지 주소</td>
<td>현재 페이지로 이동하기 전 방문한 페이지의 URL. <strong>유입 경로 분석</strong>에 활용 가능. (원래 표기는 <em>Referrer</em>지만 오타가 표준이 됨)</td>
<td>요청(Request)</td>
<td><code>Referer: https://example.com/pageA</code></td>
</tr>
<tr>
<td><strong>User-Agent</strong></td>
<td>유저 에이전트 애플리케이션 정보</td>
<td>요청을 보낸 클라이언트의 <strong>브라우저, OS, 디바이스 정보</strong>를 포함. 통계나 장애 분석에 활용</td>
<td>요청(Request)</td>
<td><code>User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)</code></td>
</tr>
<tr>
<td><strong>Server</strong></td>
<td>오리진 서버의 소프트웨어 정보</td>
<td>응답을 생성한 서버의 소프트웨어 종류 및 버전을 명시</td>
<td>응답(Response)</td>
<td><code>Server: Apache/2.2.22 (Debian)</code><br><code>Server: nginx</code></td>
</tr>
<tr>
<td><strong>Date</strong></td>
<td>메시지 생성 일시</td>
<td>HTTP 메시지가 생성된 <strong>날짜와 시간(GMT 기준)</strong>을 명시</td>
<td>응답(Response)</td>
<td><code>Date: Tue, 15 Nov 1994 08:12:31 GMT</code></td>
</tr>
</tbody></table>
<h2 id="6-특별한-정보">6. 특별한 정보</h2>
<table>
<thead>
<tr>
<th>헤더</th>
<th>의미</th>
<th>상세 설명</th>
<th>사용 위치</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Host</strong></td>
<td>요청한 호스트 정보(도메인)</td>
<td>요청이 전송되는 <strong>호스트명과 포트번호</strong>를 지정.<br>하나의 서버가 여러 도메인을 처리할 때 필수</td>
<td>요청(Request)</td>
<td><code>Host: www.example.com</code></td>
</tr>
<tr>
<td><strong>Location</strong></td>
<td>페이지 리다이렉션</td>
<td>브라우저가 이동해야 할 <strong>리소스의 새 위치</strong>를 지정. <br>• <code>201 Created</code> → 생성된 리소스의 URI <br>• <code>3xx</code> 응답 → 리다이렉션 대상 URI</td>
<td>응답(Response)</td>
<td><code>Location: https://example.com/new-page</code></td>
</tr>
<tr>
<td><strong>Allow</strong></td>
<td>허용 가능한 HTTP 메서드</td>
<td>요청된 리소스에 대해 <strong>허용된 HTTP 메서드 목록</strong>을 명시.<br><code>405 Method Not Allowed</code> 응답 시 필수</td>
<td>응답(Response)</td>
<td><code>Allow: GET, HEAD, PUT</code></td>
</tr>
<tr>
<td><strong>Retry-After</strong></td>
<td>재요청 대기 시간</td>
<td>클라이언트가 <strong>다음 요청 전 대기해야 하는 시간</strong>을 명시.<br>주로 <code>503 Service Unavailable</code> 응답 시 사용</td>
<td>응답(Response)</td>
<td><code>Retry-After: Fri, 31 Dec 1999 23:59:59 GMT</code><br><code>Retry-After: 120</code></td>
</tr>
<tr>
<td><strong>Authorization</strong></td>
<td>인증 정보 전달</td>
<td>클라이언트가 서버에 인증 정보를 전달</td>
<td>요청(Request)</td>
<td><code>Authorization: Basic xxxxxxxxxx</code></td>
</tr>
<tr>
<td><strong>WWW-Authenticate</strong></td>
<td>인증 방식 정의</td>
<td>보호된 리소스에 접근할 때 필요한 <strong>인증 방법</strong>을 서버가 명시</td>
<td>응답(Response)</td>
<td><code>WWW-Authenticate: Basic realm=&quot;Access to the site&quot;</code></td>
</tr>
</tbody></table>
<p><strong>TCP/IP는 IP로만 통신</strong>을 하기 때문에, 도메인 정보가 담긴 <strong>Host가 필수적</strong>으로 있어야한다.</p>
<h2 id="7-쿠키">7. 쿠키</h2>
<p><strong>Set-Cookie</strong>: 서버에서 클라이언트로 쿠키 전달(응답)
<strong>Cookie</strong>: 클라이언트가 서버에서 받은 쿠키를 저장하고, HTTP 요청시 서버로 전달</p>
<blockquote>
<p><strong>쿠키가 필요한 이유 (로그인 예시)</strong>
<strong>Stateless</strong>한 HTTP 특성상 로그인한 상태 서버는 모른다.
대안: 모든 요청에 사용자 정보 포함
        -&gt; 모든 요청에 사용자 정보가 포함되도록 개발 해야함
        -&gt; 브라우저를 완전히 종료하고 다시 열면?
        문제가 많다...</p>
</blockquote>
<h3 id="쿠키-흐름">쿠키 흐름</h3>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/d73ac262-204c-4928-9598-c0c4ebdca990/image.png" alt=""></p>
<ol>
<li>서버에서 <strong>Set-Cookie를 통해 로그인 정보를 쿠키로 말고</strong>, 클라이언트에서 쿠키 저장소에 저장한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/34920eda-0d04-4b33-842c-9028c4133fbc/image.png" alt="">
2. 클라이언트는 이후 <strong>쿠키 저장소에서 조회해서 Cookie 정보에 로그인 정보를 넣어서</strong> 요청을 보낸다.</p>
<blockquote>
<p><strong>사용처</strong></p>
</blockquote>
<ol>
<li>사용자 로그인 세션 관리</li>
<li>광고 정보 트래킹<br></li>
</ol>
<p><strong>쿠키 정보는 항상 서버에 전송됨</strong></p>
<ol>
<li>네트워크 트래픽 추가 유발</li>
<li>최소한의 정보만 사용(세션 id, 인증 토큰)</li>
<li>서버에 전송하지 않고, 웹 브라우저 내부에 데이터를 저장하고 싶으면 웹 스토리지 (localStorage, sessionStorage)<br></li>
</ol>
<p><strong>주의!</strong>
<span style="color: red"><strong>보안에 민감한 데이터는 저장하면 안됨 (주민번호, 신용카드 번호 등등)</strong></span></p>
<h3 id="쿠키---생명주기">쿠키 - 생명주기</h3>
<ol>
<li><strong>Set-Cookie: expires</strong>=Sat, 26-Dec-2020 04:39:21 GMT
만료일이 되면 쿠키 삭제</li>
<li><strong>Set-Cookie: max-age</strong>=3600 (3600초)
0이나 음수를 지정하면 쿠키 삭제</li>
<li><strong>세션 쿠키</strong>: 만료 날짜를 생략하면 브라우저 종료시까지만 유지</li>
<li><strong>영속 쿠키</strong>: 만료 날짜를 입력하면 해당 날짜까지 유지</li>
</ol>
<h3 id="쿠키---도메인">쿠키 - 도메인</h3>
<ol>
<li><strong>명시</strong>: 명시한 문서 기준 도메인 + 서브 도메인 포함
 domain=example.org를 지정해서 쿠키 생성<pre><code> example.org는 물론이고
 dev.example.org도 쿠키 접근</code></pre></li>
<li><strong>생략</strong>: 현재 문서 기준 도메인만 적용
 example.org에서 쿠키를 생성하고 domain 지정을 생략<pre><code> example.org에서만 쿠키 접근
 dev.example.org는 쿠키 미접근</code></pre></li>
</ol>
<p>도메인을 명시하는 것과 안하는 것으로 쿠키 활용 범위가 달라진다.</p>
<h3 id="쿠키---경로">쿠키 - 경로</h3>
<p><strong>Path</strong>
이 경로를 포함한 하위 경로 페이지만 쿠키 접근
일반적으로 <strong>path=/루트</strong> 로 지정</p>
<h3 id="쿠키---보안">쿠키 - 보안</h3>
<ol>
<li><strong>Secure</strong>
 쿠키는 http, https를 구분하지 않고 전송
 Secure를 적용하면 https인 경우에만 전송</li>
<li><strong>HttpOnly</strong>
 XSS 공격 방지
 자바스크립트에서 접근 불가 (document.cookie)
 HTTP 전송에만 사용</li>
<li><strong>SameSite</strong>
 XSRF 공격 방지
 요청 도메인과 쿠키에 설정된 도메인이 같은 경우만 쿠키 전송</li>
</ol>
<h2 id="8-정리하며">8. 정리하며...</h2>
<p>현재 기억에 남는 것은 쿠키의 흐름과 활용방식에 대해서만 기억이 난다.
용어가 많아 한눈에 안 들어오는데, 중요해보이는 쿠키는 확실하게 남기고 가자...</p>
<hr>
<p>이 링크를 통해 구매하시면 제가 수익을 받을 수 있어요. 🤗
<a href="https://inf.run/YZxop">https://inf.run/YZxop</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] 6. HTTP 상태코드]]></title>
            <link>https://velog.io/@jg_doxb/HTTP-6.HTTP-%EC%83%81%ED%83%9C%EC%BD%94%EB%93%9C</link>
            <guid>https://velog.io/@jg_doxb/HTTP-6.HTTP-%EC%83%81%ED%83%9C%EC%BD%94%EB%93%9C</guid>
            <pubDate>Tue, 14 Oct 2025 04:03:39 GMT</pubDate>
            <description><![CDATA[<h2 id="1-상태코드">1. 상태코드</h2>
<p>HTTP 상태코드란? 클라이언트가 보낸 요청의 처리 상태를 응답에서 알려주는 기능이다.
이를 통해 처리 상태에 대한 응답에 따라 다음 작업을 설계하거나, 서버의 응답이 성공인지 실패인지, 실패라면 그 원인이 클라이언트의 문제인지 서버의 문제인지 파악할 수 있다.</p>
<blockquote>
<p><strong>상위 상태코드와 그 의미</strong>
<strong>1xx (Informational)</strong>: 요청이 수신되어 처리중
<strong>2xx (Successful)</strong>: 요청 정상 처리
<strong>3xx (Redirection)</strong>: 요청을 완료하려면 추가 행동이 필요
<strong>4xx (Client Error)</strong>: 클라이언트 오류, 잘못된 문법등으로 서버가 요청을 수행할 수 없음
<strong>5xx (Server Error)</strong>: 서버 오류, 서버가 정상 요청을 처리하지 못함</p>
</blockquote>
<p>만약 모르는 상태 코드가 나타나면? 클라이언트가 인식할 수 없는 상태코드를 서버가 반환하면?
<span style="color: red"><strong>클라이언트는 상위 상태코드로 해석해서 처리한다.</strong></span> 그래서 미래에 새로운 상태 코드가 추가되어도 클라이언트를 변경하지 않아도 된다.</p>
<h3 id="1xx-informational">1xx (Informational)</h3>
<p>요청이 수신되어 처리중</p>
<ul>
<li>거의 사용하지 않으므로 생략</li>
</ul>
<h3 id="2xx-successful">2xx (Successful)</h3>
<table>
<thead>
<tr>
<th>상태 코드</th>
<th>의미</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>200 OK</strong></td>
<td>요청 성공</td>
<td>요청이 성공적으로 처리되었으며 응답 본문에 결과가 포함됨</td>
</tr>
<tr>
<td><strong>201 Created</strong></td>
<td>리소스 생성 성공</td>
<td>요청이 성공적으로 처리되어 새로운 리소스가 생성됨</td>
</tr>
<tr>
<td><strong>202 Accepted</strong></td>
<td>요청 수락</td>
<td>요청이 수락되었으나 아직 처리되지 않음 (비동기 작업 등)</td>
</tr>
<tr>
<td><strong>204 No Content</strong></td>
<td>요청 성공 (내용 없음)</td>
<td>요청이 성공했으나 반환할 콘텐츠가 없음</td>
</tr>
</tbody></table>
<h4 id="200-ok">200 OK</h4>
<ul>
<li>요청 성공<h4 id="201-created">201 Created</h4>
</li>
<li>요청 성공해서 새로운 리소스가 생성됨</li>
<li>생성된 리소스는 응답의 <span style="color: red"><strong>Location 헤더 필드</strong></span>로 식별<h4 id="202-accepted">202 Accepted</h4>
</li>
<li>요청이 접수되었으나 처리가 완료되지 않았음</li>
<li>배치 처리 같은 곳에서 사용
  ex) 요청 접수 후 1시간 뒤에 배치 프로세스가 요청을 처리함<h4 id="204-no-content">204 No Content</h4>
</li>
<li>서버가 요청을 성공적으로 수행했지만, 응답 페이로드 본문에 보낼 데이터가 없음
  ex) 웹 문서 편집기에서 save 버튼
  save 버튼의 결과로 아무 내용이 없어도 된다.
  save 버튼을 눌러도 같은 화면을 유지해야 한다.
  결과 내용이 없어도 <strong>204 메시지(2xx)만으로 성공을 인</strong>식할 수 있다.</li>
</ul>
<p>다양한 상태코드가 많지만, <strong>개발 시작전 사용할 코드를 협의를 한다.</strong>
200만 사용하는 경우가 많고, 201까지 사용하는 경우도 있다.</p>
<h3 id="3xx-redirection">3xx (Redirection)</h3>
<table>
<thead>
<tr>
<th>상태 코드</th>
<th>의미</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>300 Multiple Choices</strong></td>
<td>다중 선택</td>
<td>요청된 리소스에 대해 여러 가지 응답이 가능함</td>
</tr>
<tr>
<td><strong>301 Moved Permanently</strong></td>
<td>영구 이동</td>
<td>요청한 리소스가 새 URL로 영구적으로 이동됨</td>
</tr>
<tr>
<td><strong>302 Found</strong></td>
<td>임시 이동</td>
<td>요청한 리소스가 일시적으로 다른 URL에 있음</td>
</tr>
<tr>
<td><strong>303 See Other</strong></td>
<td>다른 위치 보기</td>
<td>요청 결과를 다른 URI에서 GET 메서드로 확인해야 함</td>
</tr>
<tr>
<td><strong>304 Not Modified</strong></td>
<td>수정되지 않음</td>
<td>캐시된 리소스를 그대로 사용해도 됨</td>
</tr>
<tr>
<td><strong>307 Temporary Redirect</strong></td>
<td>임시 리다이렉트</td>
<td>요청한 리소스가 임시적으로 다른 URI에 있으며, 메서드는 변경되지 않음</td>
</tr>
<tr>
<td><strong>308 Permanent Redirect</strong></td>
<td>영구 리다이렉트</td>
<td>요청한 리소스가 영구적으로 다른 URI로 이동되었으며, 메서드는 유지됨</td>
</tr>
</tbody></table>
<h3 id="리다이렉션-이해">리다이렉션 이해</h3>
<p>웹 브라우저는 <span style="color: red"><strong>3xx 응답의 결과에 Location 헤더가 있으면, Location 위치로 자동 이동</strong></span> (리다이렉트)</p>
<h4 id="자동-리다이렉트-흐름">자동 리다이렉트 흐름</h4>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/eccaf13a-51f5-4b8a-9721-8cd96627b817/image.png" alt=""></p>
<p>여기서 301은 요청 메서드가 GET으로 바뀐다.</p>
<blockquote>
<p><strong>종류</strong>
<strong>1. 영구 리다이렉션</strong>: 특정 리소스의 URI가 영구적으로 이동
    ex) /members -&gt; /users
    ex) /event -&gt; /new-event
<strong>2. 일시 리다이렉션</strong>: 일시적인 변경
    주문 완료 후 주문 내역 화면으로 이동
    <span style="color: red"><strong>PRG: Post/Redirect/Get</strong></span>
<strong>3. 특수 리다이렉션</strong>
    결과 대신 캐시를 사용</p>
</blockquote>
<h4 id="영구-리다이렉션">영구 리다이렉션</h4>
<p>301, 308</p>
<ul>
<li>리소스의 URI가 영구적으로 이동</li>
<li>원래의 URL를 사용X, 검색 엔진 등에서도 변경 인지</li>
<li>301 Moved Permanently
  <span style="color: red"><strong>리다이렉트시 요청 메서드가 GET으로 변하고, 본문이 제거될 수 있음(MAY)</strong></span></li>
<li>308 Permanent Redirect
  301과 기능은 같음
  <span style="color: red"><strong>리다이렉트시 요청 메서드와 본문 유지</strong></span>(처음 POST를 보내면 리다이렉트도 POST 유지)</li>
</ul>
<blockquote>
<p><strong>308을 왜 쓰냐?</strong>
301은 등록을 위해 데이터를 POST를 보냈으나, 리다이렉트 후 보내는 요청은 메시지 바디가 빈 GET 요청
<strong>처음부터 등록하려던 것을 처음부터 입력해야한다.</strong>
이걸 308로 해결할 수 있다.
그런데 다른 페이지로 돌릴 때, 대부분 받는 정보가 다 달라 301로 하는 경우가 많다.</p>
</blockquote>
<h4 id="일시적인-리다이렉션">일시적인 리다이렉션</h4>
<p>302, 307, 303</p>
<ul>
<li>리소스의 URI가 일시적으로 변경</li>
<li>따라서 검색 엔진 등에서 URL을 변경하면 안됨</li>
<li>302 Found
  리다이렉트시 <span style="color: red"><strong>요청 메서드가 GET으로 변하고, 본문이 제거될 수 있음(MAY)</strong></span></li>
<li>307 Temporary Redirect
  302와 기능은 같음
  리다이렉트시 <span style="color: red"><strong>요청 메서드와 본문 유지</strong></span>(요청 메서드를 변경하면 안된다. MUST NOT)</li>
<li>303 See Other
  302와 기능은 같음
  리다이렉트시 <span style="color: red"><strong>요청 메서드가 GET으로 변경</strong></span></li>
</ul>
<h4 id="prg-postredirectget">PRG: Post/Redirect/Get</h4>
<p>일시적인 리다이렉션 - 예시</p>
<ul>
<li>POST로 주문 후에 웹 브라우저를 새로고침하면?</li>
<li>새로고침은 다시 요청</li>
<li>중복 주문이 될 수 있다.
<img src="https://velog.velcdn.com/images/jg_doxb/post/55c81305-5df1-441d-8fb3-289b16060290/image.png" alt=""></li>
</ul>
<p>PRG 패턴으로 해결!</p>
<ul>
<li>POST로 주문 후에 새로 고침으로 인한 중복 주문 방지</li>
<li>POST로 주문 후에 주문 결과 화면을 GET 메서드로 리다이렉트</li>
<li>새로고침해도 결과 화면을 GET으로 조회</li>
<li>중복 주문 대신에 결과 화면만 GET으로 다시 요청
Location 태그가 포함된 302로 리다이렉션이 키포인트.</li>
</ul>
<blockquote>
<p><strong>PRG 이후 리다이렉트</strong>
    URL이 이미 POST -&gt; GET으로 리다이렉트 됨
    새로 고침 해도 GET으로 결과 화면만 조회</p>
</blockquote>
<h4 id="기타-리다이렉션">기타 리다이렉션</h4>
<ul>
<li>300 Multiple Choices: 안쓴다.</li>
<li>304 Not Modified
  캐시를 목적으로 사용
  <span style="color: red"><strong>클라이언트에게 리소스가 수정되지 않았음을 알려준다.</strong></span> 따라서 클라이언트는 로컬 PC에 저장된 캐시를 재사용한다. (캐시로 리다이렉트 한다.)
  304 응답은 응답에 메시지 바디를 포함하면 안된다. (로컬 캐시를 사용해야 하므로)
  조건부 GET, HEAD 요청시 사용</li>
</ul>
<h3 id="4xx-client-error">4xx (Client Error)</h3>
<blockquote>
<p><strong>클라이언트 오류</strong>
클라이언트의 요청에 잘못된 문법등으로 서버가 요청을 수행할 수 없음
<span style="color: red"><strong>오류의 원인이 클라이언트에 있음</strong></span>
중요! 클라이언트가 이미 잘못된 요청, 데이터를 보내고 있기 때문에, 똑같은 재시도가 실패함</p>
</blockquote>
<h4 id="400-bad-request">400 Bad Request</h4>
<p>클라이언트가 잘못된 요청을 해서 서버가 요청을 처리할 수 없음</p>
<ul>
<li>요청 구문, 메시지 등등 오류</li>
<li>클라이언트는 요청 내용을 다시 검토하고, 보내야함</li>
<li>ex) 요청 파라미터가 잘못되거나, API 스펙이 맞지 않을 때</li>
</ul>
<h4 id="401-unauthorized">401 Unauthorized</h4>
<p>클라이언트가 해당 리소스에 대한 인증이 필요함</p>
<ul>
<li>인증(Authentucation) 되지 않음</li>
<li>401 오류 발생시 응답에 WWW-Authenticate 헤더와 함께 인증 방법을 설명</li>
<li>참고
  인증(Authentucation): 본인이 누구인지 확인, (로그인)
  인가(Authorization): 권한부여 (ADMIN 권한처럼 특정 리소스에 접근할 수 있는 권한, 인증이 있어야 인가가 있음)
  오류 메시지가 Unauthorized 이지만 인증 되지 않음 (이름이 아쉬움)</li>
</ul>
<h4 id="403-forbidden">403 Forbidden</h4>
<p>서버가 요청을 이해했지만 승인을 거부함</p>
<ul>
<li>주로 인증 자격 증명은 있지만, 접근 권한이 불충분한 경우</li>
<li>ex) 어드민 등급이 아닌 사용자가 로그인은 했지만, 어드민 등급의 리소스에 접근하는 경우</li>
</ul>
<h4 id="404-not-found">404 Not Found</h4>
<p>요청 리소스를 찾을 수 없음</p>
<ul>
<li>요청 리소스가 서버에 없음</li>
<li>또는 클라이언트가 권한이 부족한 리소스에 접근할 때 해당 리소스를 숨기고 싶을 때</li>
</ul>
<h3 id="5xx-server-error">5xx (Server Error)</h3>
<blockquote>
<p><strong>서버 오류</strong>
<span style="color: red"><strong>서버 문제로 오류 발생</strong></span>
서버에 문제가 있기 때문에 재시도 하면 성공할 수도 있음 (복구가 되거나 등등)</p>
</blockquote>
<h4 id="500-internal-server-error">500 Internal Server Error</h4>
<p>서버 문제로 오류 발생, 애매하면 500 오류</p>
<ul>
<li>서버 내부 문제로 오류 발생</li>
<li>애매하면 500 오류</li>
</ul>
<h4 id="503-service-unavailable">503 Service Unavailable</h4>
<p>서비스 이용 불가</p>
<ul>
<li>서버가 일시적인 과부하 또는 예정된 작업으로 잠시 요청을 처리할 수 없음</li>
<li>Retry-After 헤더 필드로 얼마뒤에 복구되는지 보낼 수도 있음</li>
</ul>
<p><span style="color: red"><strong>서버에 비즈니스 로직에서 예외 케이스가 발생했을 때, 5xx으로 내면 안된다!</strong></span>
ex) 고객의 잔고가 부족할 경우
진짜 서버에 문제가 있을 때, 5xx를 사용한다.</p>
<hr>
<p>이 링크를 통해 구매하시면 제가 수익을 받을 수 있어요. 🤗</p>
<p><a href="https://inf.run/YZxop">https://inf.run/YZxop</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] 5. HTTP 메서드 활용]]></title>
            <link>https://velog.io/@jg_doxb/HTTP-5.-HTTP-%EB%A9%94%EC%84%9C%EB%93%9C-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@jg_doxb/HTTP-5.-HTTP-%EB%A9%94%EC%84%9C%EB%93%9C-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Fri, 03 Oct 2025 16:20:40 GMT</pubDate>
            <description><![CDATA[<p>HTTP API를 설계하기 위해서는 데이터를 <strong>전송하는 방식</strong>과 <strong>전송하는 상황</strong>에 대한 이해가 필요하다.</p>
<h2 id="1-클라이언트--서버-데이터-전송">1. 클라이언트 &gt; 서버 데이터 전송</h2>
<h3 id="전송하는-방식">전송하는 방식</h3>
<ol>
<li><strong>쿼리 파라미터를 통한 데이터 전송</strong><ul>
<li>GET</li>
<li>정렬 필터 (검색어)</li>
</ul>
</li>
<li><strong>메시지 바디를 통한 데이터 전송</strong><ul>
<li>POST, PUT, PATCH</li>
<li>회원 가입, 상품 주문, 리소스 등록, 리소스 변경</li>
</ul>
</li>
</ol>
<h3 id="전송하는-상황">전송하는 상황</h3>
<ol>
<li><p><strong>정적 데이터 조회</strong></p>
<ul>
<li>이미지, 정적 텍스트 문서</li>
<li>조회는 GET 사용</li>
<li>정적 데이터는 일반적으로 <span style="color: red"><strong>쿼리 파라미터 없이</strong></span> 리소스 경로로 단순하게 조회 가능</li>
</ul>
</li>
<li><p><strong>동적 데이터 조회</strong></p>
<ul>
<li>주로 검색, 게시판 목록에서 정렬 필터 (검색어)</li>
<li><span style="color: red"><strong>조회 조건을 줄여주는 필터, 조회 결과를 정렬하는 정렬 조건</strong></span>에 주로 사용</li>
<li>조회는 GET 사용</li>
<li>GET은 쿼리 파라미터 사용해서 데이터를 전달</li>
</ul>
</li>
<li><p><strong>HTML Form을 통한 데이터 전송</strong></p>
<ul>
<li>회원 가입, 상품 주문, 데이터 변경<blockquote>
<p><strong>1. HTML Form submit시 POST 전송</strong>
  Content-Type: application/x-www-form-urlencoded 사용
  <span style="color: red">form의 내용을 메시지 바디</span>를 통해서 전송(key=value, 쿼리 파라미터 형식)
  전송 데이터를 url encoding 처리<br>
<strong>2. HTML Form은 <span style="color: red">조회 목적</span>일 경우 GET 전송도 가능</strong><br>
<strong>3. HTML Form은 여러 형태의 데이터를 한꺼번에 전송 가능 (POST)</strong>
  Content-Type: multipart/form-data
  파일 업로드와 같은 바이너리 데이터 전송시 사용
  다른 종류의 여러 파일과 폼의 내용 함께 전송 가능(그래서 이름이 multipart)<br>
<strong>4. 참고</strong>
  HTML Form 전송은 GET, POST만 지원    </p>
</blockquote>
</li>
</ul>
</li>
<li><p><strong>HTTP API를 통한 데이터 전송</strong></p>
<ul>
<li>회원 가입, 상품 주문, 데이터 변경</li>
<li>서버 to 서버 (백엔드 시스템 통신)</li>
<li>앱 클라이언트 (아이폰, 안드로이드)</li>
<li>웹 클라이언트
HTML에서 Form 전송 대신 <span style="color: red"><strong>자바 스크립트를 통한 통신에 사용(AJAX)</strong></span>
React, Vue.js 같은 웹 클라이언트와 API 통신</li>
<li>Content-Type: application/json</li>
</ul>
</li>
</ol>
<h2 id="2-http-api-설계-예시">2. HTTP API 설계 예시</h2>
<p>API 설계를 할 때, 단순 조회도 중요하지만, 데이터를 변경하는 상황인 데이터 등록, 그리고 HTML Form을 활용하여 데이터를 다루는 상황을 중점으로 정리해보자.
회원 관리 시스템을 예시로 정리해보겠다.</p>
<h3 id="api-설계---post-기반-등록">API 설계 - POST 기반 등록</h3>
<table>
<thead>
<tr>
<th>기능</th>
<th>URI</th>
<th>메서드</th>
</tr>
</thead>
<tbody><tr>
<td>회원 목록</td>
<td><code>/members</code></td>
<td>GET</td>
</tr>
<tr>
<td>회원 등록</td>
<td><code>/members</code></td>
<td>POST</td>
</tr>
<tr>
<td>회원 조회</td>
<td><code>/members/{id}</code></td>
<td>GET</td>
</tr>
<tr>
<td>회원 수정</td>
<td><code>/members/{id}</code></td>
<td>PATCH, PUT, POST</td>
</tr>
<tr>
<td>회원 삭제</td>
<td><code>/members/{id}</code></td>
<td>DELETE</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>POST - 신규 자원 등록 특징</strong>
<span style="color: red"><strong>1. 클라이언트는 등록될 리소스의 URI를 모른다.</strong></span>
    회원 등록 /members -&gt; POST
    POST /members
<strong>2. 서버가 새로 등록된 리소스 URI를 생성해준다.</strong>
    HTTP/1.1 201 Created
    Location: /members/100
<strong>3. 컬렉션(Collection)</strong>
    서버가 관리하는 리소스 디렉토리
    <span style="color: red">서버가 리소스의 URI를 생성하고 관리</span>
    여기서 컬렉션은 /members</p>
</blockquote>
<h3 id="api-설계---put-기반-등록">API 설계 - PUT 기반 등록</h3>
<table>
<thead>
<tr>
<th>기능</th>
<th>URI</th>
<th>메서드</th>
</tr>
</thead>
<tbody><tr>
<td>파일 목록</td>
<td><code>/files</code></td>
<td>GET</td>
</tr>
<tr>
<td>파일 조회</td>
<td><code>/files/{filename}</code></td>
<td>GET</td>
</tr>
<tr>
<td>파일 등록</td>
<td><code>/files/{filename}</code></td>
<td>PUT</td>
</tr>
<tr>
<td>파일 삭제</td>
<td><code>/files/{filename}</code></td>
<td>DELETE</td>
</tr>
<tr>
<td>파일 대량 등록</td>
<td><code>/files</code></td>
<td>POST</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>PUT - 신규 자원 등록 특징</strong>
<span style="color: red"><strong>1. 클라이언트가 리소스 URI를 알고 있어야 한다.</strong></span>
    파일 등록 /files/{filename} -&gt; PUT
    PUT /files/star.jpg
<strong>2. 클라이언트가 직접 리소스의 URI를 지정한다.</strong>
<strong>3. 스토어(Store)</strong>
    클라이언트가 관리하는 리소스 저장소
    <span style="color: red">클라이언트가 리소스의 URI를 알고 관리</span>
    여기서 스토어는 /files</p>
</blockquote>
<h3 id="api-설계---html-form-사용">API 설계 - HTML Form 사용</h3>
<p>HTML Form은 GET, POST만 지원
AJAX 같은 기술을 사용해서 해결 가능 -&gt; 회원 API 참고
여기서는 순수 HTML, HTML Form 이야기
GET, POST만 지원하므로 제약이 있음</p>
<table>
<thead>
<tr>
<th>기능</th>
<th>URI</th>
<th>메서드</th>
</tr>
</thead>
<tbody><tr>
<td>회원 목록</td>
<td><code>/members</code></td>
<td>GET</td>
</tr>
<tr>
<td>회원 등록 폼</td>
<td><code>/members/new</code></td>
<td>GET</td>
</tr>
<tr>
<td>회원 등록</td>
<td><code>/members/new</code>, <code>/members</code></td>
<td>POST</td>
</tr>
<tr>
<td>회원 조회</td>
<td><code>/members/{id}</code></td>
<td>GET</td>
</tr>
<tr>
<td>회원 수정 폼</td>
<td><code>/members/{id}/edit</code></td>
<td>GET</td>
</tr>
<tr>
<td>회원 수정</td>
<td><code>/members/{id}/edit</code>, <code>/members/{id}</code></td>
<td>POST</td>
</tr>
<tr>
<td>회원 삭제</td>
<td><code>/members/{id}/delete</code></td>
<td>POST</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>HTML Form - 신규 자원 등록 특징</strong>
<strong>1. HTML Form은 GET, POST만 지원</strong>
<span style="color: red"><strong>2. 컨트롤 URI</strong></span>
    GET, POST만 지원하므로 제약이 있음
    이런 제약을 해결하기 위해 동사로 된 리소스 경로 사용
    POST의 /new, /edit, /delete가 컨트롤 URI
    HTTP 메서드로 해결하기 애매한 경우 사용 (HTTP API 포함)</p>
</blockquote>
<h2 id="3-uri-설계-개념">3. URI 설계 개념</h2>
<p><strong>1. 문서(document)</strong>
    단일 개념(파일 하나, 객체 인스턴스, 데이터베이스 row)
    ex) /members/100, /files/star.jpg</p>
<p><strong>2. 컬렉션(collection)</strong>
    <span style="color: red">서버가 관리하는 리소스 디렉터리</span>
    서버가 리소스의 URI를 생성하고 관리
    ex) /members</p>
<p><strong>3. 스토어(store)</strong>
    <span style="color: red">클라이언트가 관리하는 자원 저장소</span>
    클라이언트가 리소스의 URI를 알고 관리
    ex) /files</p>
<p><strong>4. 컨트롤러(controller), 컨트롤 URI</strong>
    문서, 컬렉션, 스토어로 해결하기 어려운 추가 프로세스 실행
    <span style="color: red">동사를 직접 사용</span>
    ex) /members/{id}/delete</p>
<h2 id="4-정리하며">4. 정리하며...</h2>
<p>POST와 PUT의 차이, 그리고 순수 HTML Form과 ajax를 활용했을 때 차이를 중점으로 가져가보자.
PUT은 실무에서 많이 쓰이지는 않지만, 게시판 수정과 같은 기능에서 어울리는 방법이다.
HTML Form은 HTML코드에서 GET과 POST요청을 생성하여 전송하지만, GET과 POST만 지원한다는 한계가 있다.
이 한계를 해결하기위해 동사로 명명한 컨트롤러 URI를 통해 추가 프로세스를 실행하는 방법과 자바스크립트에서 실행되는 ajax를 활용하여 한계를 해결할 수 있다.</p>
<p>이정도 남기고, 다음으로 넘어가자...</p>
<hr>
<p>이 링크를 통해 구매하시면 제가 수익을 받을 수 있어요. 🤗
<a href="https://inf.run/YZxop">https://inf.run/YZxop</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] 4. HTTP 메서드]]></title>
            <link>https://velog.io/@jg_doxb/HTTP-4.-HTTP-%EB%A9%94%EC%84%9C%EB%93%9C</link>
            <guid>https://velog.io/@jg_doxb/HTTP-4.-HTTP-%EB%A9%94%EC%84%9C%EB%93%9C</guid>
            <pubDate>Sat, 27 Sep 2025 16:58:50 GMT</pubDate>
            <description><![CDATA[<h2 id="1-http-api-만들기">1. HTTP API 만들기</h2>
<p>HTTP API를 만들 때, 요구사항을 식별하고 그에 맞는 URI를 설계하는 것이 필요하다.
다음 예시로 그 과정을 들어보겠다.</p>
<h3 id="요구사항---api-uri-설계-1">요구사항 - API URI 설계 (1)</h3>
<table>
<thead>
<tr>
<th>기능</th>
<th>URI</th>
</tr>
</thead>
<tbody><tr>
<td>회원 목록 조회</td>
<td>/read-member-list</td>
</tr>
<tr>
<td>회원 조회</td>
<td>/read-member-by-id</td>
</tr>
<tr>
<td>회원 등록</td>
<td>/create-member</td>
</tr>
<tr>
<td>회원 수정</td>
<td>/update-member</td>
</tr>
<tr>
<td>회원 삭제</td>
<td>/delete-member</td>
</tr>
</tbody></table>
<p><strong>이게 정말 좋은 URI일까?</strong>
URI 설계에서 가장 중요한 것은 <span style="color: red"><strong>리소스 식별</strong></span>이다.</p>
<blockquote>
<p><strong>API URI 고민사항</strong></p>
</blockquote>
<ul>
<li>리소스의 의미
회원을 등록하고 수정하고 조회하는게 리소스가 아니다!
ex) 미네랄을 캐라! -&gt; 미네랄이 리소스</li>
<li><em>회원*</em>이라는 개념 자체가 바로 리소스다.</li>
<li>리소스 식별 방법
  회원을 등록하고 수정하고 조회하는 것을 모두 배제
  <span style="color: red"><strong>회원이라는 리소스만 식별</strong></span>하면 된다 -&gt; 회원 리소스를 URI에 매핑</li>
</ul>
<h3 id="요구사항---api-uri-설계-2">요구사항 - API URI 설계 (2)</h3>
<table>
<thead>
<tr>
<th>기능</th>
<th>URI</th>
</tr>
</thead>
<tbody><tr>
<td>회원 목록 조회</td>
<td>/members</td>
</tr>
<tr>
<td>회원 조회</td>
<td>/members/{id}</td>
</tr>
<tr>
<td>회원 등록</td>
<td>/members/{id}</td>
</tr>
<tr>
<td>회원 수정</td>
<td>/members/{id}</td>
</tr>
<tr>
<td>회원 삭제</td>
<td>/members/{id}</td>
</tr>
</tbody></table>
<p>리소스 식별 중심으로 URI 설계를 했다.
<strong>그런데, 회원 조회, 회원 등록, 회원 수정, 회원 삭제는 어떻게 구분하지???</strong></p>
<blockquote>
<p><strong>리소스와 행위 분리</strong></p>
</blockquote>
<ul>
<li>리소스와 해당 리소스를 대상으로 하는 행위를 분리
리소스: 회원
행위: 조회, 등록, 삭제, 변경</li>
<li>리소스는 명사, 행위는 동사
<span style="color: red"><strong>행위는 어떻게 구분함? -&gt; GET, POST 같은 HTTP 메서드</strong></span></li>
</ul>
<h3 id="요구사항---api-uri-설계-최종">요구사항 - API URI 설계 (최종)</h3>
<table>
<thead>
<tr>
<th>기능</th>
<th>Method</th>
<th>URI</th>
</tr>
</thead>
<tbody><tr>
<td>회원 목록 조회</td>
<td>GET</td>
<td>/members</td>
</tr>
<tr>
<td>회원 조회</td>
<td>GET</td>
<td>/members/{id}</td>
</tr>
<tr>
<td>회원 등록</td>
<td>POST</td>
<td>/members/{id}</td>
</tr>
<tr>
<td>회원 수정</td>
<td>PUT</td>
<td>/members/{id}</td>
</tr>
<tr>
<td>회원 삭제</td>
<td>DELETE</td>
<td>/members/{id}</td>
</tr>
</tbody></table>
<p>최종 모습은 이렇게 될 것이다...</p>
<h2 id="2-http-메서드---get-post-put-patch-delete">2. HTTP 메서드 - GET, POST, PUT, PATCH, DELETE</h2>
<h3 id="메서드-종류">메서드 종류</h3>
<table>
<thead>
<tr>
<th>Method</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>GET</td>
<td>리소스 <strong>조회</strong></td>
</tr>
<tr>
<td>POST</td>
<td><strong>요청 데이터 처리</strong>, 주로 등록에 사용 <span style="color: red"><strong>(사실상 만능)</strong></span></td>
</tr>
<tr>
<td>PUT</td>
<td>리소스를 <strong>대체</strong>, 해당 리소스가 없으면 생성</td>
</tr>
<tr>
<td>PATCH</td>
<td>리소스 <strong>부분 변경</strong></td>
</tr>
<tr>
<td>DELETE</td>
<td>리소스 <strong>삭제</strong></td>
</tr>
<tr>
<td>HEAD</td>
<td>GET과 동일하지만 메시지 본문 제외, <strong>상태 줄과 헤더만 반환</strong></td>
</tr>
<tr>
<td>OPTIONS</td>
<td>대상 리소스의 <strong>통신 가능 옵션(메서드)</strong> 설명, 주로 CORS에서 사용</td>
</tr>
<tr>
<td>CONNECT</td>
<td>대상 자원 서버에 대한 터널 설정 (거의 사용하지 않음)</td>
</tr>
<tr>
<td>TRACE</td>
<td>대상 리소스 경로를 따라 메시지 루프백 테스트 수행 (거의 사용하지 않음)</td>
</tr>
</tbody></table>
<h3 id="get">GET</h3>
<p><strong>리소스 조회</strong></p>
<ol>
<li>서버에 전달하고 싶은 데이터는 <span style="color: red"><strong>query(쿼리 파라미터, 쿼리 스트링)</strong></span>를 통해서 전달</li>
<li>메시지 바디를 사용해서 데이터를 전달할 수 있지만, 지원하지 않는 곳이 많아서 권장하지 않음</li>
</ol>
<h3 id="post">POST</h3>
<p><strong>요청 데이터 처리</strong></p>
<ol>
<li><span style="color: red"><strong>메시지 바디</strong></span>를 통해 서버로 요청 데이터 전달</li>
<li>서버는 요청 데이터를 처리 - 메시지 바디를 통해 들어온 데이터를 처리하는 모든 기능을 수행한다.</li>
<li>주로 전달된 데이터로 신규 리소스 등록, 프로세스 처리에 사용<blockquote>
<p><strong>참고</strong>
응답 코드로 200 ok 대신에 <strong>201 Created</strong> 쓰기도 한다.
단, 이 때는 리소스가 위치하는 정보인 <strong>Location: /members/100</strong> 정보도 함께 보내준다.</p>
</blockquote>
</li>
</ol>
<p>요청 데이터 처리한다는 것의 의미
<strong>공식 스펙: POST 메서드는 대상 리소스가 리소스의 고유한 의미 체계에 따라 요청에 포함된 표현을 처리하도록 요청합니다.</strong></p>
<blockquote>
<p><strong>예시</strong></p>
</blockquote>
<ul>
<li>HTML 양식에 입력된 필드와 같은 데이터 블록을 데이터 처리 프로세스에 제공
HTML FORM에 입력한 정보로 회원 가입, 주문 등에서 사용</li>
<li>게시판, 뉴스 그룹, 메일링 리스트, 블로그 또는 유사한 기사 그룹에 메시지 게시
게시판 글쓰기, 댓글 달기</li>
<li>서버가 아직 식별하지 않은 새 리소스 생성
신규 주문 생성</li>
<li>기존 자원에 데이터 추가
한 문서 끝에 내용 추가하기</li>
</ul>
<p><span style="color: red"><strong>POST 요청이 오면 요청 데이터를 어떻게 처리할지 리소스마다 따로 정해야 함 즉, 정해진 것이 없다</strong></span></p>
<h4 id="post-정리">POST 정리</h4>
<ol>
<li>새 리소스 생성(등록)</li>
</ol>
<ul>
<li>서버가 아직 식별하지 않은 새 리소스 생성</li>
</ul>
<ol start="2">
<li>요청 데이터 처리</li>
</ol>
<ul>
<li>단순히 데이터를 생성하거나, 변경하는 것을 넘어서 프로세스를 처리해야 하는 경우
ex) 주문에서 결제완료 -&gt; 배달시작 -&gt; 배달완료 처럼 단순히 값 변경을 넘어 프로세스의 상태가 변경되는 경우</li>
<li>POST의 결과로 새로운 리소스가 생성되지 않을 수도 있음
ex) POST /oreders/{orderId}/start-delivery (컨트롤 URI) &lt;- 행위의 URI가 나올 수도 있다!</li>
</ul>
<ol start="3">
<li>다른 메서드로 처리하기 애매한 경우
ex) JSON으로 조회 데이터를 넘겨야 하는데, GET 메서드를 사용하기 어려운 경우</li>
</ol>
<p>애매하면 조회라도 POST로 가능하다.
메시지를 내부에 담아서 할 수 있는 것들, <span style="color: red"><strong>POST는 모든 것을 할 수 있다.</strong></span>
단, 조회는 캐싱 때문에 GET이 유리, <strong>POST는 캐싱을 하기 어렵다.</strong></p>
<h3 id="put">PUT</h3>
<p><strong>리소스를 대체</strong></p>
<ol>
<li>리소스가 있으면 대체</li>
<li>리소스가 없으면 생성</li>
</ol>
<p><strong>쉽게 이야기해서 덮어버림</strong></p>
<p>POST와 차이점으로 클라이언트가 리소스를 식별한다.
클라이언트가 리소스 위치를 알고, URI 지정하여 요청을 보낸다.</p>
<blockquote>
<p><strong>주의</strong>
<span style="color: red"><strong>리소스를 완전히 대체한다.</strong></span>
여러 필드 중에 하나만 수정하고 싶다고 하나만 넣으면, 나머지 기존 필드 데이터는 날아간다.</p>
</blockquote>
<h3 id="patch">PATCH</h3>
<p><strong>리소스 부분 변경</strong>
PATCH가 지원이 안되는 서버가 있다. 그런 경우는 POST를 쓰면 된다!</p>
<h3 id="delete">DELETE</h3>
<p><strong>리소스 제거</strong></p>
<h2 id="3-http-메서드의-속성">3. HTTP 메서드의 속성</h2>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/38aa8901-1b01-4dc9-8069-74157ab3d199/image.png" alt=""></p>
<h3 id="safe안전">Safe(안전)</h3>
<p>호출해도 리소스를 변경하지 않는다.
Q: 그래도 계속 호출해서, 로그 같은게 쌓여서 장애가 발생하면?
A: 안전은 해당 리소스만 고려한다. 그런 부분까지 고려하지 않는다
<span style="color: red">안전을 판단할 땐, <strong>리소스만!</strong></span></p>
<h3 id="idempotent멱등">Idempotent(멱등)</h3>
<p>f(f(x)) = f(x)
한 번 호출하든 두 번 호출하든 100번 호출하든 결과가 똑같다.</p>
<p><strong>멱등 메서드</strong>
GET: 한 번 조회하든, 두 번 조회하든 같은 결과가 조회된다.
PUT: 결과를 대체한다. 따라서 같은 요청을 여러번 해도 최종 결과는 같다.
DELETE: 결과를 삭제한다. 같은 요청을 여러번 해도 삭제된 결과는 똑같다.</p>
<p><strong>멱등이 아닌 메서드</strong>
POST: 멱등이 아니다. 두 번 호출하면 같은 결제가 중복해서 발생할 수 있다.</p>
<blockquote>
<p><strong>멱등 개념 활용 분야</strong>
자동 복구 메커니즘
서버가 TIMEOUT 등으로 정상 응답을 못주었을 때, 클라이언트가 <span style="color: red"><strong>같은 요청을 다시 해도 되는가</strong></span>에 대한 판단 근거가 된다.</p>
</blockquote>
<p>Q: 재요청 중간에 다른 곳에서 리소스를 변경해버리면?
사용자1: GET -&gt; username: A, age: 20
사용자2: PUT -&gt; username: A, age: 30
사용자3: GET -&gt; username: A, age: 30
A: 멱등은 <span style="color: red"><strong>외부 요인으로 중간에 리소스가 변경되는 것까지는 고려하지는 않는다.</strong></span></p>
<h3 id="cacheable캐시가능">Cacheable(캐시가능)</h3>
<p><strong>응답 결과 리소스를 캐시</strong>
GET, HEAD, POST, PATCH 캐시가능
실제로 는 GET, HEAD 정도만 캐시로 사용
POST, PATCH는 <strong>본문 내용까지 캐시 키로 고려</strong>해야 하는데, 구현이 쉽지 않다.</p>
<hr>
<p>이 링크를 통해 구매하시면 제가 수익을 받을 수 있어요. 🤗
<a href="https://inf.run/YZxop">https://inf.run/YZxop</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] 3. HTTP 기본]]></title>
            <link>https://velog.io/@jg_doxb/HTTP-3.-HTTP-%EA%B8%B0%EB%B3%B8</link>
            <guid>https://velog.io/@jg_doxb/HTTP-3.-HTTP-%EA%B8%B0%EB%B3%B8</guid>
            <pubDate>Sun, 21 Sep 2025 11:22:41 GMT</pubDate>
            <description><![CDATA[<h2 id="1-http-hypertext-transfer-protocol">1. HTTP (HyperText Transfer Protocol)</h2>
<p>HyperText란? 문서 간에 링크를 통해서 연결할 수 있는 것!</p>
<p>HTTP 메시지에 모든 것을 전송할 수 있다.</p>
<ol>
<li>HTML, TEXT</li>
<li>IMAGE, 음성, 영상, 파일</li>
<li>JSON, XML (API)</li>
</ol>
<p>거의 모든 형태의 데이터 전송 가능하다.
서버간에 데이터를 주고 받을 때도 대부분 HTTP 사용한다.</p>
<blockquote>
<p><strong>참고</strong>
<strong>HTTP/1.1</strong>: 가장 많이 사용
HTTP/2, HTTP/3 (진행중) - <strong>성능 개선 초첨</strong>
HTTP/3는 TCP 대신 UDP 사용</p>
</blockquote>
<h3 id="기반-프로토콜">기반 프로토콜</h3>
<p>HTTP가 패킷을 전달할 때, 기반으로하는 프로토콜은 버전별로 다음과 같다.</p>
<p><strong>1. TCP: HTTP/1.1, HTTP/2</strong>
2. UDP: HTTP/3</p>
<h3 id="http-특징">HTTP 특징</h3>
<ol>
<li>클라이언트 서버 구조</li>
<li>무상태 프로토콜(Stateless), 비연결성</li>
<li>HTTP 메시지</li>
<li>단순함, 확장성</li>
</ol>
<p>아래 내용부터 이 특징들을 하나씩 정리해보겠다.</p>
<h2 id="2-클라이언트-서버-구조">2. 클라이언트 서버 구조</h2>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/8082c6b7-b3c0-4b1c-9696-cc1b861efde7/image.png" alt=""></p>
<ol>
<li>Request Response 구조</li>
<li>클라이언트는 <strong>서버에 요청을 보내고</strong>, 응답을 대기</li>
<li>서버가 요청에 대한 결과를 만들어서 <strong>응답</strong></li>
</ol>
<p><span style="color: red"><strong>클라이언트와 서버가 개념이 분리되고, 비즈니즈 로직은 서버, UI/UX는 클라이언트에 집중(독립)할 수 있다는 것이 중요!</strong></span></p>
<h2 id="3-무상태-프로토콜-stateless">3. 무상태 프로토콜 (Stateless)</h2>
<ol>
<li>서버가 클라이언트의 상태를 보존X</li>
<li>장점: 서버 확장성 높음 (스케일 아웃)</li>
<li>단점: 클라이언트가 추가 데이터 전송</li>
</ol>
<h3 id="stateful---예시">Stateful - 예시</h3>
<h4 id="stateful---점원이-일정한-경우">Stateful - 점원이 일정한 경우</h4>
<table>
<thead>
<tr>
<th>대화 주체</th>
<th>발화 내용</th>
<th>점원이 기억하는 상태</th>
</tr>
</thead>
<tbody><tr>
<td>고객</td>
<td>노트북 얼마?</td>
<td>-</td>
</tr>
<tr>
<td>점원</td>
<td>100만원</td>
<td>(노트북)</td>
</tr>
<tr>
<td>고객</td>
<td>2개 구매함</td>
<td>(노트북, 2개)</td>
</tr>
<tr>
<td>점원</td>
<td>200만원임, 신용카드, 현금 중에 뭘로 구매?</td>
<td>(노트북, 2개)</td>
</tr>
<tr>
<td>고객</td>
<td>신용카드로 ㄱ</td>
<td>(노트북, 2개, 신용카드)</td>
</tr>
<tr>
<td>점원</td>
<td>200만원 결제완료</td>
<td>(노트북, 2개, 신용카드)</td>
</tr>
</tbody></table>
<h4 id="stateful---점원이-중간에-바뀌는-경우">Stateful - 점원이 중간에 바뀌는 경우</h4>
<table>
<thead>
<tr>
<th>대화 주체</th>
<th>발화 내용</th>
<th>점원이 기억하는 상태</th>
</tr>
</thead>
<tbody><tr>
<td>고객</td>
<td>노트북 얼마?</td>
<td>-</td>
</tr>
<tr>
<td>점원A</td>
<td>100만원</td>
<td>(노트북)</td>
</tr>
<tr>
<td>고객</td>
<td>2개 구매함</td>
<td></td>
</tr>
<tr>
<td>점원B</td>
<td>??? 뭘 2개???</td>
<td>(2개)</td>
</tr>
<tr>
<td>고객</td>
<td>신용카드로 ㄱ</td>
<td></td>
</tr>
<tr>
<td>점원C</td>
<td>??? 뭘 신용카드로???</td>
<td>(신용카드)</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>Stateful</strong>
항상 같은 서버가 유지가 되어야 한다.
응답해주던 서버가 맛이 가면 클라이언트는 처음부터 다시 해야한다.</p>
</blockquote>
<h3 id="stateless---예시">Stateless - 예시</h3>
<table>
<thead>
<tr>
<th>대화 주체</th>
<th>발화 내용</th>
<th>점원이 기억하는 상태</th>
</tr>
</thead>
<tbody><tr>
<td>고객</td>
<td><strong>노트북</strong> 얼마?</td>
<td>-</td>
</tr>
<tr>
<td>점원</td>
<td>100만원</td>
<td>-</td>
</tr>
<tr>
<td>고객</td>
<td><strong>노트북 2개</strong> 구매함</td>
<td>-</td>
</tr>
<tr>
<td>점원</td>
<td>노트북 2개는 200만원임, 신용카드, 현금 중에 뭘로 구매?</td>
<td>-</td>
</tr>
<tr>
<td>고객</td>
<td><strong>노트북 2개를 신용카드</strong>로 구매함</td>
<td>-</td>
</tr>
<tr>
<td>점원</td>
<td>200만원 결제완료</td>
<td>-</td>
</tr>
</tbody></table>
<p>이 경우에는 중간에 점원이 바뀌더라도, <strong>요청마다 필요한 정보를 모두 전달</strong>하므로 응답에 문제가 없다.
즉, 고객이 무엇을 요청해왔는 지, <span style="color: red"><strong>서버에서 그 상태를 저장할 필요없이 설계하는 것이 Stateless이다.</strong></span></p>
<blockquote>
<p><strong>Stateless</strong>
상태를 보관하지 않으니깐, 아무 서버나 호출해도 된다.
<span style="color: red"><strong>그래서 스케일 아웃(수평 확장)에 유리하다.</strong></span></p>
</blockquote>
<h3 id="stateful-stateless-차이">Stateful, Stateless 차이</h3>
<p>Stateful: 중간에 다른 점원으로 바뀌면 안된다.
(중간에 다른 점원으로 바뀔 때, 상태 정보를 다른 점원에게 미리 알려줘야 한다.)</p>
<p>Stateless: 중간에 다른 점원으로 바뀌어도 된다.</p>
<ul>
<li>갑자기 고객이 증가해도 점원을 대거 투입할 수 있다.</li>
<li>갑자기 클라이언트 요청이 증가해도 서버를 대거 투입할 수 있다.</li>
</ul>
<p>무상태는 응답 서버를 쉽게 바꿀 수 있다. -&gt; <span style="color: red"><strong>무한한 서버 증설 가능</strong></span></p>
<h3 id="stateless-실무-한계">Stateless 실무 한계</h3>
<p>모든 것을 무상태로 설계 할 수 있는 경우도 있고 없는 경우도 있다.</p>
<ul>
<li>Stateless로 가능한 것 -&gt; 로그인이 필요 없는 단순한 서비스 소개 화면</li>
<li>Stateful이 필요한 것 -&gt; 로그인</li>
</ul>
<p>로그인한 사용자의 경우 로그인 했다는 상태를 서버에 유지한다.
일반적으로 브라우저 쿠키와 서버 세션등을 사용해서 상태 유지한다.</p>
<p><span style="color: red">따라서, <strong>Stateful은 최소한만 사용하는 방향으로 설계하는 것이 중요하다.</strong></span>
그리고 Stateless의 단점으로 보내야하는 <strong>데이터가 너무 많다는 단점</strong>이 있다.</p>
<blockquote>
<p><strong>Stateless를 구현하기 정말 어려운 상황</strong>
정말 같은 시간에 딱! 맞추어서 발생하는 대용량 트래픽
ex) 선착순 이벤트, 명절 KTX 예약, 학과 수업 등록
ex) 저녁 6:00 선착순 1000명 치킨 할인 이벤트 -&gt; 수만명 동시 요청
항상 Stateless하게 설계하는 것이 중요하다! <del>머리를 갈아넣더라도...</del></p>
</blockquote>
<h2 id="4-비연결성-connectionless">4. 비연결성 (connectionless)</h2>
<h4 id="연결을-유지하는-모델-특징">연결을 유지하는 모델 특징</h4>
<ul>
<li>TCP 연결이후 작업이 끝나고 연결 유지한다.</li>
<li>서버자원 소모한다.</li>
</ul>
<h4 id="연결을-유지하지-않는-모델-특징">연결을 유지하지 않는 모델 특징</h4>
<ul>
<li>TCP 연결이후 작업이 끝나면 연결 해제한다.</li>
<li>최소한의 자원 사용한다.</li>
</ul>
<blockquote>
<p><strong>HTTP는 기본이 연결을 유지하지 않는 모델이다.</strong></p>
</blockquote>
<ul>
<li>일반적으로 초 단위의 이하의 빠른 속도로 응답</li>
<li>1시간 동안 수천명이 서비스를 사용해도 실제 서버에서 동시에 처리하는 요청은 수십개 이하로 매우 작음
ex) 웹 브라우저에서 계속 연속해서 검색 버튼을 누르지는 않는다.</li>
<li>서버 자원을 매우 효율적으로 사용할 수 있다.</li>
</ul>
<p>그런데, 연결을 유지하지 않는 모델이 최소한의 자원만 사용하는 것 같아 장점만 있는 것 같지만 아니다.</p>
<ul>
<li>HTTP 요청을 보낼 때마다 TCP/IP 연결을 새로 맺어야한다.</li>
<li>웹 브라우저로 사이트를 요청하면 HTML 뿐만 아니라 자바스크립트, css, 추가 이미지등 수많은 자원들이 함께 다 다운로드된다.</li>
</ul>
<p>이러한 문제들은 응답시간을 늦추는 한계가 된다. 이를 해결하기 위해 <span style="color: red"><strong>HTTP 지속 연결 (Persistent Connections)</strong></span>로 문제를 해결하고 있고, HTTP/2, HTTP/3에서 더 많은 최적화가 이루어지고 있다.</p>
<table>
<thead>
<tr>
<th>HTTP 초기</th>
<th>HTTP 지속 연결 적용</th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/jg_doxb/post/092af344-cdda-4e09-8b24-c6b0d8d5ac10/image.png" alt=""></td>
<td><img src="https://velog.velcdn.com/images/jg_doxb/post/1edb235f-7a07-441c-a586-406a6fc5075d/image.png" alt=""></td>
</tr>
</tbody></table>
<h2 id="5-http-메시지">5. HTTP 메시지</h2>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/f7868785-ddcf-49ab-85b7-7a7cd1162385/image.png" alt="">
empty line(CRLF) 무조건 있어야한다.
공식 스펙에 명시되어 있다.
<br></p>
<h3 id="start-line">start-line</h3>
<table>
<thead>
<tr>
<th>요청 메시지 (request-line)</th>
<th>응답 메시지 (status-line)</th>
</tr>
</thead>
<tbody><tr>
<td><strong>예시</strong><br><code>GET /search?q=hello&amp;hl=ko HTTP/1.1</code></td>
<td><strong>예시</strong><br><code>HTTP/1.1 200 OK</code></td>
</tr>
<tr>
<td><strong>형식</strong><br>start-line = request-line(요청)<br>request-line = <strong>method</strong> SP <strong>request-target</strong> SP HTTP-version CRLF</td>
<td><strong>형식</strong><br>start-line = status-line(응답)<br>status-line = HTTP-version SP <strong>status-code</strong> SP <strong>reason-phrase</strong> CRLF</td>
</tr>
<tr>
<td><strong>method</strong><br>- GET, POST, PUT, DELETE…<br>- 서버가 수행해야 할 동작 지정<br> • GET: 리소스 조회<br> • POST: 요청 내역 처리</td>
<td><strong>status-code</strong><br>- 요청 성공, 실패를 나타냄<br> • 200: 성공<br> • 400: 클라이언트 요청 오류<br> • 500: 서버 내부 오류</td>
</tr>
<tr>
<td><strong>request-target</strong><br>- absolute-path<code>[?query]</code><br>- 절대경로 &quot;/&quot;로 시작<br>- 참고: <code>*</code>, <code>http://...?x=y</code> 등 가능</td>
<td><strong>reason-phrase</strong><br>- 사람이 이해할 수 있는 짧은 상태 코드 설명 글</td>
</tr>
</tbody></table>
<h3 id="header">header</h3>
<pre><code class="language-http">Content-Type: text/html;charset=UTF-8
Content-Length: 3423</code></pre>
<p>header-field = <strong>field-name &quot;:&quot;</strong> OWS <strong>field-value</strong> OWS (OWS:띄어쓰기 허용)
field-name은 대소문자 구분 없음</p>
<h4 id="용도">용도</h4>
<ul>
<li>HTTP 전송에 필요한 모든 부가정보가 다 있다. (body를 제외한 필요한 모든 메타데이터가 여기 있다.)</li>
<li>표준 헤더가 많다</li>
<li>필요시 임의의 헤더도 추가가 가능하다.</li>
</ul>
<h3 id="message-body">message body</h3>
<h4 id="용도-1">용도</h4>
<ul>
<li>실제 전송할 데이터</li>
<li>HTML 문서, 이미지, 영상, JSON 등 byte로 표현할 수 있는 모든 데이터 전송 가능</li>
</ul>
<h2 id="정리하며">정리하며...</h2>
<p>오늘 정리한 게 난잡해보여서 맘에 들지가 않는다...
일단 가져갈 것은
<strong>1. Stateless의 구조로 수평적인 확장이 자유로운 것</strong>
<strong>2. 비연결성의 구조지만 지속 연결이라는 최적화를 통해 한계를 해결</strong>
<strong>3. HTTP 구조가 단순해서 여기저기 확장이 가능하다</strong>
정도 확실하게 가져가자.</p>
<hr>
<p>이 링크를 통해 구매하시면 제가 수익을 받을 수 있어요. 🤗</p>
<p><a href="https://inf.run/YZxop">https://inf.run/YZxop</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] 2. URI, 웹 브라우저 요청 흐름]]></title>
            <link>https://velog.io/@jg_doxb/HTTP-URI-%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/@jg_doxb/HTTP-URI-%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>Sun, 14 Sep 2025 15:04:09 GMT</pubDate>
            <description><![CDATA[<p>HTTP 개념에 들어가기 직전, HTTP 프로토콜을 활용하여 데이터를 주고 받을 때, 요청하는 방식과 그 흐름에 대해서 짚고 넘어간다.</p>
<h2 id="1-uri-uniform-resource-identifier">1. URI (Uniform Resource Identifier)</h2>
<p>URI는 로케이터(locator), 이름(name) 또는 둘 다 추가로 분류 될 수 있다.</p>
<blockquote>
<p><strong>U</strong>niform: 리소스 식별하는 통일된 방식
<strong>R</strong>esource: 자원, URI로 식별할 수 있는 모든 것 (제한 없음)
<strong>I</strong>dentifier: 다른 항목과 구분하는 데 필요한 정보</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/75235cc6-8110-4cfa-a3e4-b69f35c4ac72/image.png" alt="">
큰 개념으로 <strong>URI(리소스를 식별)</strong>, 그안에 작은 개념으로 <strong>URL(리소스 위치)</strong>, <strong>URN(리소스 이름)</strong>이 있다.</p>
<blockquote>
<p><strong>URL - Uniform Resource Locator</strong>
foo://example.com:8042/over/there?name=ferret#nose
<br><strong>URN - Uniform Resource Name</strong>
urn:example:animal:ferret:nose
!! 이름만 부여한 것, <span style="color: red">실제 리소스의 위치를 잡아주려면 별도의 맵핑 과정이 필요함.</span></p>
</blockquote>
<h3 id="urn-특징">URN 특징</h3>
<ol>
<li>위치는 변할 수 있지만, 이름은 변하지 않는다.</li>
<li>urn:isbn:8960777331 (어떤 책의 isbn URN)</li>
<li>URN 이름만으로 실제 리소스를 찾을 수 있는 방법은 보편화 되지 않음</li>
<li><strong>URI 와 URL을 같은 의미로 보고 서술</strong></li>
</ol>
<h3 id="전체문법">전체문법</h3>
<blockquote>
<p><code>https://www.google.com/search?q=hello&amp;hl=ko</code></p>
</blockquote>
<ul>
<li>프로토콜: https</li>
<li>호스트명: <a href="http://www.google.com">www.google.com</a></li>
<li>포트번호: 443</li>
<li>패스: /search</li>
<li>쿼리 파라미터(q=hello&amp;hl=ko)</li>
</ul>
<p><code>scheme://[userinfo@]host[:port][/path][?query][#fragment]</code></p>
<h4 id="scheme">scheme</h4>
<ol>
<li>주로 프로토콜 사용</li>
<li>어떤 방식으로 자원에 접근 할 것인가 하는 약속 규칙</li>
<li>http, https, ftp 등</li>
<li>http - 80, https - 443 포트를 주로 사용, 포트는 생략 가능</li>
<li>https는 http에 보안 추가 (HTTP Secure)</li>
</ol>
<h4 id="userinfo">userinfo@</h4>
<ol>
<li>URL에 사용자정보를 포함해서 인증</li>
<li>거의 사용하지 않음</li>
</ol>
<h4 id="host">host</h4>
<ol>
<li>호스트명</li>
<li>도메인명 또는 IP 주소를 직접 입력</li>
</ol>
<h4 id="port">PORT</h4>
<ol>
<li>접속 포트</li>
<li>일반적으로 생략, 생략시 http는 80, https는 443</li>
</ol>
<h4 id="path">/path</h4>
<ol>
<li>리소스 경로(path), 계층적 구조<blockquote>
<p>/home/file1.jpg
/members
/members/100, /items/iphone12</p>
</blockquote>
</li>
</ol>
<h4 id="query">?query</h4>
<ol>
<li>key=value 형태</li>
<li>?로 시작, &amp;로 추가 가능 ?keyA=valueA&amp;keyB=valueB</li>
<li>query parameter, query string 등으로 불림, <strong>웹서버에 제공하는 파라미터</strong>, 문자 형태</li>
</ol>
<h4 id="fragment">#fragment</h4>
<ol>
<li>잘 사용하진 않음</li>
<li>html 내부 북마크 등에 사용 &lt;- 써본 기억이 있다....</li>
<li>서버에 전송되는 정보는 아님</li>
</ol>
<h2 id="2-웹-브라우저-요청-흐름">2. 웹 브라우저 요청 흐름</h2>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/03861d82-6cee-4613-a44f-112994d75559/image.png" alt=""></p>
<p><strong>1. 데이터 생성 (클라이언트)</strong></p>
<ul>
<li><span style="color: red"><strong>DNS 서버 조회, PORT 정보 조회 -&gt; HTTP 요청 메시지 생성</strong></span></li>
<li>SOCKET 라이브러리를 통해 전달</li>
<li>TCP/IP 패킷 생성, HTTP 메시지 포함</li>
</ul>
<p><strong>2. 서버에 전송</strong></p>
<p><strong>3. HTTP 응답 메시지 생성 (서버)</strong></p>
<ul>
<li>1번 데이터 생성 과정과 동일</li>
<li>HTTP 메시지가 응답 메시지로 바뀐 것일 뿐....</li>
</ul>
<p><strong>4. 클라이언에 전송</strong></p>
<p><strong>5. 웹 브라우저 HTML 렌더링 (우리가 보는 화면)</strong></p>
<p>위에 그림처럼 클라이언트에서 HTTP 메시지를 생성해서 패킷을 서버로 전송하고, 위의 단계와 같이 HTML 정보를 수신하여 렌더링을 하면, 우리가 보는 화면이 나오게 된다.</p>
<p>HTTP 메시지에는 어떤 것들이 포함되어 있는 지, 어떻게 다뤄야하는 지 다음 글부터 정리하겠다.</p>
<hr>
<p>이 링크를 통해 구매하시면 제가 수익을 받을 수 있어요. 🤗</p>
<p><a href="https://inf.run/YZxop">https://inf.run/YZxop</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] 1. HTTP 이해를 위한 사전 지식]]></title>
            <link>https://velog.io/@jg_doxb/HTTP-HTTP-%EC%9D%B4%ED%95%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%82%AC%EC%A0%84-%EC%A7%80%EC%8B%9D</link>
            <guid>https://velog.io/@jg_doxb/HTTP-HTTP-%EC%9D%B4%ED%95%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%82%AC%EC%A0%84-%EC%A7%80%EC%8B%9D</guid>
            <pubDate>Sat, 06 Sep 2025 14:34:19 GMT</pubDate>
            <description><![CDATA[<h3 id="들어가기-앞서">들어가기 앞서...</h3>
<p>많은 웹 서비스는 HTTP 프로토콜 위에서 데이터를 주고 받는다. 웹 개발 공부를 하다보면 HTTP 용어가 많이 등장하는 데, 이 때 깊이있는 이해를 위해서는 HTTP에 대한 이해가 필요하다. 그리고 웹 개발을 하다보면 API URL, PUT, POST, HTTP 상태코드와 관련된 고민을 하게 되는 데, 이때 HTTP에 대한 지식이 활용된다.
그래서 웹 개발만 한다고 해서 HTTP와 같은 CS 공부를 무시할 수 없다.
<del>이론적인 공부라 정말 하기 힘들지만 해야되는 이유를 되새기며 정리를 시작해본다.</del></p>
<p>애플리케이션 계층에 있는 HTTP를 이해하기 위해서는 그 아래 계층에 있는 인터넷 네트워크에 대한 이해가 필요하다.
따라서, 먼저 인터넷 네트워크와 관련된 내용을 정리하고자 한다.</p>
<h2 id="1-인터넷-통신">1. 인터넷 통신</h2>
<p>클라이언트와 서버가 직접적으로 연결이 되어 있다면, 중간과정 없이 클라이언트에서 요청을 보내면 서버에서 보낸 응답을 직접 받게 된다. 하지만, 다수의 클라이언트와 서버, 멀리 떨어진 클라이언트와 서버들과 통신을 하기 위해 중간에 인터넷이라는 것을 거치면서 요청과 응답을 처리할 수 있게 되었다.
여기서 인터넷망은 여러개의 노드들로 되게 복잡하게 이루어져 있는데, 통신을 어떻게 하는 것인가?
<span style="color: red">그렇게 등장한 것이 <strong>IP</strong> 이다.</span></p>
<h2 id="2-ip-internet-protocol">2. IP (Internet Protocol)</h2>
<p>클라이언트와 서버가 인터넷 망을 거쳐서 통신을 하기위해서 각각 IP 주소를 부여받아야한다.</p>
<blockquote>
<p><strong>IP의 역할</strong>
지정한 IP 주소(IP Address)에 데이터 전달
패킷(Packet)이라는 통신 단위로 데이터 전달</p>
</blockquote>
<p>이 때, IP 패킷에는 <span style="color: red"><strong>출발지 IP, 목적지 IP</strong></span> 정보가 포함되어 있으며 다음과 같은 과정에서 활용된다.
노드끼리 목적지를 위한 경로를 찾아주며 목적지 IP에 전송 데이터가 도달된다.
도착 이후 목적지에서는 성공적으로 받았다는 응답을 역으로 출발지 IP로 던져준다.</p>
<p>데이터를 주고받는 데 문제가 없어보이지만, IP로만 통신하기에는 다음과 같은 한계점이 존재한다.</p>
<blockquote>
<p><strong>IP 프로토콜의 한계점</strong></p>
</blockquote>
<ul>
<li>비연결성
패킷을 받을 대상이 없거나 서비스 불능 상태여도 패킷이 전송된다.
ex) 클라이언트는 서버의 상태를 모르는 상태에서 패킷을 보낸다.</li>
<li>비신뢰성
중간에 패킷이 사라질 경우
패킷이 순서대로 안오는 경우
ex) 인터넷 망 중간 노드가 문제가 생길 경우 패킷이 그대로 소실된다.</li>
<li>프로그램 구분
같은 IP를 사용하는 서버에서 통신하는 애플리케이션이 둘 이상일 경우
ex) 1500byte가 넘어가게 되면 패킷을 쪼개서 보내는 데, 이 때 각각의 패킷이 다른 노드를 거치게 되면서 순서가 바뀔 수도 있다.</li>
</ul>
<p><span style="color: red"><strong>그래서 등장한 것이 TCP 이다.</strong></span></p>
<h2 id="3-tcp-transmission-contril-protocol">3. TCP (Transmission Contril Protocol)</h2>
<p>TCP는 IP 프로토콜이 가진 문제점들을 해결해준다.</p>
<p>TCP를 이해를 위해 인터넷 프로토콜 스택의 4계층은 다음과 같다.</p>
<blockquote>
<ol>
<li>애플리케이션 계층 - HTTP, FTP</li>
<li>전송 계층 - TCP, UDP</li>
<li>인터넷 계층 - IP</li>
<li>네트워크 인터페이스 계층 - LAN 장비, LAN 드라이버</li>
</ol>
</blockquote>
<h3 id="프로토콜-계층">프로토콜 계층</h3>
<p>한눈에 들어어는 것은 애플리케이션, OS, 네트워크 인터페이스가 끝이다.
채팅 프로그램을 예시로 들면 다음과 같다.
<img src="https://velog.velcdn.com/images/jg_doxb/post/e2826c7e-ad8e-4d1c-8e22-9aead5c5c4ff/image.png" alt=""></p>
<p>참고: 이더넷 프레임은 MAC 주소와 같이 물리적인 주소를 포함하고 있다.</p>
<h3 id="tcpip-패킷-정보">TCP/IP 패킷 정보</h3>
<p><strong>출발지 IP, 목적지 IP</strong> 정보가 있는 IP 패킷 안에 TCP 세그먼트 정보가 들어가는데, 여기에 <span style="color: red"><strong>출발지 PORT, 목적지 PORT, 전송 제어, 순서, 검증 정보</strong></span> 등이 있다.
이렇게 IP 프로토콜을 보완해주는 TCP 특징은 다음과 같다.</p>
<blockquote>
<p><strong>TCP 특징</strong>
연결지향 - TCP 3 way handshake (가상 연결)
데이터 전달 보증
순서 보장</p>
</blockquote>
<h4 id="연결지향---tcp-3-way-handshake">연결지향 - TCP 3 way handshake</h4>
<p>connect, 연결과정은 다음과 같다.
<img src="https://velog.velcdn.com/images/jg_doxb/post/3f87a65d-3349-4bc3-82de-6b3513b679a5/image.png" alt=""></p>
<p>3번째 과정에서 ACK와 함께 데이터 전송이 가능하다.
SYN: 접속 요청
ACK: 요청 수락</p>
<h4 id="데이터-전달-보증">데이터 전달 보증</h4>
<p>다음과 같은 순서로 보증이 된다.</p>
<ol>
<li>클라이언트 - 데이터 전송</li>
<li>서버 - 데이터 잘 받았음</li>
</ol>
<h4 id="순서-보장">순서 보장</h4>
<p>다음과 같은 순서로 보장이 된다.</p>
<ol>
<li>클라이언트 - 패킷1, 패킷2, 패킷3 순서로 전송</li>
<li>서버 - 패킷1, 패킷3, 패킷2 순서로 도착</li>
<li>서버 - 패킷2부터 다시 보내</li>
</ol>
<h2 id="4-udp-user-datagram-protocl">4. UDP (User Datagram Protocl)</h2>
<p>UDP는 TCP와는 달리 거의 기능이 없어 특징이 다음과 같다.</p>
<blockquote>
<p><strong>UDP 특징</strong>
연결 지향 X
데이터 전달 보증 X
순서 보장 X
단순하고 빠르다.</p>
</blockquote>
<p>IP와 비교하면 기능이 거의 같지만, PORT, Checksum 정도만 추가된다.
PORT는 하나의 IP에서 여러 애플리케이션이 동작하고 있을 때, 패킷이 도달할 애플리케이션을 구분할 때 활용된다.
Checksum은 전송된 데이터가 제대로 된 데이터인지 확인할 때 활용된다.</p>
<p>TCP는 이미 많은 것이 구축되어 있어 연결 지향 과정에서 오래걸리고, 패킷이 크다해서 무언가 건드리기 힘들다.
UDP는 아무것도 안되어 있기 때문에, 애플리케이션 단에서 UDP를 처리할 무언가를 만들면 된다.</p>
<h2 id="5-port">5. PORT</h2>
<p>한번에 둘 이상 연결해야하는 경우, 여러 애플리케이션을 활용하고 있는 경우, IP만 활용하면 패킷을 구분하기 힘들다.
그래서 TCP/IP 패킷정보는 출발지 IP, PORT와 도착지 IP, PORT 정보를 담고 있다.
<span style="color: red"><strong>같은 IP 내에서 프로세스를 구분하는 역할을 수행한다.</strong></span> 즉, IP가 아파트이면 PORT는 동, 호수 이다.</p>
<blockquote>
<p><strong>PORT 잡지식</strong>
0 ~ 65535 할당 가능
0 ~ 1023: 많이 쓰는 포트, 임의로 사용하지 않는 것이 좋음
FTP - 20, 21
TELNET - 23
HTTP - 80
HTTPS - 443</p>
</blockquote>
<h2 id="6-dns-domain-name-system">6. DNS (Domain Name System)</h2>
<p>IP 주소는 기억하기 힘들고, 변경이 될 수 있다.
그래서 도메인 네임시스템이 등장하였고, 이는 전화번호부처럼 <span style="color: red"><strong>IP 주소에 대한 도메인 명을 등록</strong></span>해서 해당하는 IP 주소를 찾아준다.
IP 주소 변경이 될 경우 DNS 서버에 등록된 정보만 수정해주면 된다.</p>
<hr>
<p>이 링크를 통해 구매하시면 제가 수익을 받을 수 있어요. 🤗
<a href="https://inf.run/YZxop">https://inf.run/YZxop</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[kubernetes] 1. Why? Kubernetes]]></title>
            <link>https://velog.io/@jg_doxb/1.-Why-Kubernetes</link>
            <guid>https://velog.io/@jg_doxb/1.-Why-Kubernetes</guid>
            <pubDate>Sat, 30 Aug 2025 03:50:09 GMT</pubDate>
            <description><![CDATA[<p>시스템 엔지니어에 가까운 인프라 인턴을 진행하면서 공부한 내용들을 인프라 파트에 정리할 생각이다.</p>
<h3 id="1-배포-방식의-변화">1. 배포 방식의 변화</h3>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/86228fb3-c7ca-4477-8a8e-135535f11f17/image.svg" alt="">
위 그림은 쿠버네티스 공식자료에 게시된 그림이다.
전통적인 방식의 배포방식부터 컨테이너 기반의 배포방식까지 시스템 구조를 나타낸 그림이다.</p>
<h4 id="1-1-전통적인-배포">1-1. 전통적인 배포</h4>
<p>기존 전통적인 방식으로 베어메탈 위에 OS를 올리고, 바로 App를 배포하는 방식은 단점이 있었다.</p>
<p>서로 다른 App들이 OS와 자원을 공유하면서 발생하는 단점들이다.</p>
<blockquote>
<ol>
<li>OS를 공유하고 있어 패키지 버전 문제로 App 호환성 문제가 발생하기 쉽다.</li>
<li>자원을 공유하고 있어 App 사용하는 자원에 따라 다른 App 성능에 영향을 줄 수 있다.</li>
</ol>
</blockquote>
<h4 id="1-2-가상화-기반-배포">1-2. 가상화 기반 배포</h4>
<p>그렇게 나온 것이 가상화 기반 배포 방식이다.</p>
<blockquote>
<p>&lt;참고&gt;
위에 그림에는 가상화 Type2로 베어메탈-OS-Hypervisor 순으로 올라가 있는데, 베어메탈 위에 Hypervisor를 직접 올리는 Type1도 있다.</p>
</blockquote>
<p>이는 하나의 큰 서버를 작은 단위의 VM으로 나누어 사용하여 서버를 좀 더 효율적으로 사용할 수 있게 도와주고, VM에 떠 있는 App이 다른 VM 위에 떠 있는 App에 영향을 주지 않는다는 장점이 있다.</p>
<p>하지만 이것도 단점이 있다.</p>
<blockquote>
<ol>
<li>VM마다 OS를 설치를 하기 때문에, 오버헤드가 발생한다.</li>
<li>VM이 할당받은 자원을 모두 활용하는 것이 아니기 때문에, 의미 없이 남는 자원이 발생한다.</li>
<li>무겁다.</li>
</ol>
</blockquote>
<h4 id="1-3-컨테이너-기반-배포">1-3. 컨테이너 기반 배포</h4>
<p>그렇게 나온 것이 컨테이너 기반 배포방식이다.</p>
<p>VM은 OS부터 격리하였지만, 컨테이너는 OS를 공유한다. 다만, VM과 마찬가지로 컨테이너는 자체 파일 시스템, CPU 점유율, 메모리, 프로세스는 격리가 되어있어 클라우드나 OS 배포본에 모두 이식할 수 있다. 그리고 가볍다.</p>
<p>하지만, 단점은 다음과 같다.</p>
<blockquote>
<ol>
<li>OS를 공유하고 있어 컨테이너 하나라고 보안에 취약점이 발생하면 전체가 위험해진다.</li>
<li>컨테이너는 프로세스이므로 데이터 영구 저장을 위해 별도의 스토리지 솔루션이 필요하다</li>
<li>컨테이너 간의 네트워크 설정이 복잡하다.</li>
<li>이식은 용이한데, 다른 OS와 호환성 문제가 발생할 수 있다.</li>
</ol>
</blockquote>
<p>배포할 서비스의 규모, 형태, 지불할 수 있는 비용과 같이 여러 조건을 따져보고 배포방식을 선정하면 된다.
그래도 최근 클라우드 환경이 떠오르면서 가장 마지막에 나온 컨테이너 기반 배포방식을 많이 활용하고 있다.</p>
<p>여기서 컨테이너가 많아지고, 여러 서버에 띄워진 컨테이너들이 서로 상호작용을 하며 하나의 서비스를 이룰 때, 이걸 어떻게 관리를 할까?</p>
<p>서버 하나하나 들어가서 컨테이너에 접근해 이슈를 파악하고, 컨테이너 간의 통신을 위한 네트워크 설정도 모두 일일이 다 하고 있으면 부서진 모니터와 키보드만 늘어날 것이다.</p>
<p>그래서 나온 것이 바로 <span style="color: red"><strong>Kubernetes (K8s)</strong></span> 이다.</p>
<h3 id="2-kubernetes">2. Kubernetes</h3>
<p>바로 쿠버네티스의 구성요소로 들어가기 전에 전체적인 그림을 보겠다.
<img src="https://velog.velcdn.com/images/jg_doxb/post/099156c2-5faa-4e8e-92b5-5ad2f710f6f7/image.png" alt="">
위에 그림에서 컨테이너 배포환경을 가져와 쿠버네티스의 구조를 그린 것이다.
여러 대의 서버가 쿠버네티스를 통해 한 클러스터로 묶여 각각의 서버는 노드의 역할을 하고 있다.
가상화 기반의 배포환경보다 가볍고 효율적으로 보인다.</p>
<p>그런데 큰 서버에 쿠버네티스 환경을 구성할 때 전체적인 그림은 다음과 같다.
<img src="https://velog.velcdn.com/images/jg_doxb/post/b1c8c07d-f9d0-4580-a338-5ed78b933e18/image.png" alt="">
이러한 방식 때문에 쿠버네티스를 사용하는 이유를 납득하는 데 오래 걸렸다.</p>
<p>가볍고 효율적인 컨테이너 배포방식을 채택하고, 그 컨테이너를 잘 관리하려고 쿠버네티스를 사용하는 것이 아니였나?
하나의 베어메탈 위에 하이퍼바이저를 올리고, 여러대의 VM을 각각 노드로 취급해서 쿠버네티스로 묶으면 성능 저하가 있지 않나? ← <span style="color: red"><strong>여기서 막혔다.</strong></span></p>
<p>단점은 다음과 같다.</p>
<blockquote>
<ol>
<li>이중 오버헤드: VM 오버헤드, 컨테이너 런타임 오버 헤드</li>
<li>자원 낭비: VM이 자체 커널과 OS를 가져야되서 기본 메모리/디스크 소비가 증가</li>
<li>복잡성 증가: VM관리, Kubernetes 관리</li>
</ol>
</blockquote>
<p>성능 저하가 있음에도 VM 위에 쿠버네티스를 띄울 때, 장점은 다음과 같다.</p>
<blockquote>
<ol>
<li><strong>보안</strong>: 하이퍼바이저를 통해 커널까지 분리되어 컨테이너 간 취약점 전파 위험이 어느정도 해소</li>
<li><strong>클러스터 관리</strong>: 노드를 VM으로 쉽게 생성, 삭제, 스냅샷 복원을 할 수 있어 운영이 간편</li>
<li><strong>노드 자원 관리</strong>: VM 자체에서 자원을 명확하게 제한 가능</li>
</ol>
</blockquote>
<p>보안, 클러스터 및 노드 관리에 이점이 있기 때문에 어느정도 성능 감소를 부담하고, VM 위에 쿠버네티스를 띄워 활용한다.</p>
<p>가상화 기반 배포환경의 단점이였던 낭비되는 자원의 문제는 클러스터 내의 노드의 <span style="color: red"><strong>자원 상황에 맞추어 Pod가 뜨기 때문에</strong></span>, 남는 자원 문제는 해결된다. (VM에 남는 자원이 영영 안쓰이는 것이 아니다)</p>
<p>오히려 큰 서버를 하나의 노드로 사용하는 것이 성능에서 이점이 있을 수 있지만, <strong>운영상의 장점</strong>이 있어서 VM 위에 쿠버네티스를 띄워 활용한다.</p>
<p>그리고 서버를 여러대 사서 하나로 묶는 것보다 큰 서버 하나를 사서 나누어 구성하는 게 유지보수, 비용적 측면에서 유리하다.</p>
<h3 id="３-kubernetes-장단점">３. Kubernetes 장단점</h3>
<p>VM 위에 쿠버네티스 납득완료. 그러면 쿠버네티스를 왜 쓸까?</p>
<p>컨테이너 배포환경에서 여러 개의 컨테이너를 관리하려고 쓰는 것이다. 그에 따른 장단점은 다음과 같다.</p>
<h4 id="장점">장점</h4>
<ol>
<li>고가용성: Pod, 노드, 컨트롤 플레인 장애 시 자동 복구</li>
<li>자동화: 배포, 스케일링, 업데이트 등을 자동으로 관리</li>
<li>서비스 디스커버리 및 로드밸런싱: ClusterIP, Ingress 등으로 트래픽 자동 분산</li>
<li>자원 최적화: 노드에 적절히 Pod를 배치하고 자원 낭비 줄임</li>
<li>이식성/플랫폼 독립성: 어떤 클라우드/OS에서도 동일한 방식으로 실행</li>
<li>멀티테넌시 및 격리: 네임스페이스, RBAC 등으로 논리적 격리</li>
</ol>
<h4 id="단점">단점</h4>
<ol>
<li>러닝커브: 러닝커브 각도가 가파르다 못해 수직이다. 어렵다.</li>
<li>운영 복잡성: 설치, 보안, 업데이트 등 손이 많이 감</li>
<li>자원 오버헤드: kubelet, kube-proxy, controller-manager 등 기본 데몬들이 자원 소모</li>
<li>어려운 디버깅: 컨테이너 하나가 죽었을 때, 원인 파악을 위해 <code>kubectl logs</code>, <code>kubectl describe</code>, events, metrics 등 <strong>여러 단계를 거쳐야 함</strong></li>
</ol>
<p>결국, 단점들은 고수가 등장하면 해결되는 일들이라 어느정도 규모가 있는 프로젝트라면 쿠버네티스를 활용하지 않을 이유가 없다.
쿠버네티스를 활용하는 이유는 이해가 되었었지만, VM위에 쿠버네티스를 세팅하는 이유가 이해가 안되서 초입에서 길을 잃었다.</p>
<p>다음은 쿠버네티스의 구성요소에 대해 다뤄보겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[7. Spring Bean (1)]]></title>
            <link>https://velog.io/@jg_doxb/7.-Spring-Bean-1</link>
            <guid>https://velog.io/@jg_doxb/7.-Spring-Bean-1</guid>
            <pubDate>Sat, 23 Aug 2025 14:56:28 GMT</pubDate>
            <description><![CDATA[<p>의존관계 주입을 하여 스프링 컨테이너에 등록이 된 스프링 빈을 조회하는 방법에 대해 정리하겠다.
기술적인 내용들이라 이런 것이 있구나, 이렇게 활용하면 되는구나 정도 짚고 넘어가겠다.</p>
<h2 id="1-spring-bean-출력">1. Spring Bean 출력</h2>
<pre><code class="language-java">public class ApplicationContextInfoTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName(&quot;모든 빈 출력하기&quot;)
    void findAllBean() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            Object bean = ac.getBean(beanDefinitionName);
            System.out.println(&quot;name = &quot; + beanDefinitionName + &quot;object = &quot; + bean );
        }
    }

    @Test
    @DisplayName(&quot;애플리케이션 빈 출력하기&quot;)
    void findApplicationBean() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);


            if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                Object bean = ac.getBean(beanDefinitionName);
                System.out.println(&quot;name = &quot; + beanDefinitionName + &quot;object = &quot; + bean );
            }
        }
    }
}</code></pre>
<p>별 다른 조건없이 출력을 하게 될 경우, 스프링 내부에서 사용하는 빈이 모두 함께 출력이 된다.
BeanDefinition 조건을 걸어 직접 등록한 애플리케이션 빈, 스프링이 내부에서 사용하는 빈을 구분하여 출력할 수 있다.</p>
<blockquote>
<p><strong>참고</strong>
ROLE_APPLICATION: 직접 등록한 애플리케이션 빈
ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈</p>
</blockquote>
<p>그런데, 이렇게 실제로 다 출력해서 확인할 일은 별로 없다.</p>
<h2 id="2-spring-bean-조회">2. Spring Bean 조회</h2>
<p>한번에 다 출력하고, 일일이 눈으로 확인해보는 것은 비효율적이다.
원하는 빈을 선택하여 조회하는 방법이 있다.</p>
<blockquote>
<p>.getBean(빈이름, 타입)
.getBean(타입)</p>
</blockquote>
<p>해당 메서드로 스프링 컨테이너에 등록된 빈을 꺼내올 수 있다.
조회 대상 빈이 없으면 예외 발생한다.</p>
<pre><code class="language-shell">NoSuchBeanDefinitionException: No bean named &#39;xxxxx&#39; available</code></pre>
<p>오류명을 알아야 테스트 코드를 작성할 때, 실패 케이스에 대한 테스트가 가능하다.</p>
<h3 id="1-일반적인-조회">1. 일반적인 조회</h3>
<pre><code class="language-java">public class ApplicationContextBasicFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName(&quot;빈 이름으로 조회&quot;)
    void findBeanByName() {
        MemberService memberService = ac.getBean(&quot;memberService&quot;, MemberService.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

    @Test
    @DisplayName(&quot;이름 없이 타입으로만 조회&quot;)
    void findBeanByType() {
        MemberService memberService = ac.getBean(MemberService.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

    @Test
    @DisplayName(&quot;구체 타입으로 조회&quot;)
    void findBeanByName2() {
        MemberService memberService = ac.getBean(&quot;memberService&quot;, MemberServiceImpl.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

    @Test
    @DisplayName(&quot;빈 이름으로 조회X&quot;)
    void findBeanByNameX() {
        // ac.getBean(&quot;xxxxx&quot;, MemberService.class);
        assertThrows(NoSuchBeanDefinitionException.class,
                () -&gt; ac.getBean(&quot;xxxxx&quot;, MemberService.class));
    }
}</code></pre>
<p>빈 이름으로 조회해서 원하는 구현 객체로 빈이 생성되었는 지 확인할 수 있다.
즉, 인터페이스를 조회를 하면 검증의 대상으로 인터페이스의 구현체가 대상이 된다.</p>
<p>직접 구현체를 조회해도 된다.
다만, 구현체를 직접 등록하면 역할과 구현을 구분하고 역할에 의존하지 않기 때문에 유연성이 떨어진다.
가끔 필요할 때가 있으니 가능하다는 정도로만 알고 있자.</p>
<p>실패 케이스는 junit Assertions의 assertThrow를 활용, 람다를 활용하여 예외가 터지는 케이스를 정의한다.</p>
<h3 id="2-동일한-타입-둘-이상일-경우-조회">2. 동일한 타입 둘 이상일 경우 조회</h3>
<p>타입으로 조회시 같은 타입의 빈이 둘이상이면 오류가 발생한다. 이 때는 빈 이름을 지정한다.
.getBeanOfType()을 사용하면 해당 타입의 모든 빈을 조회할 수 있다.
만약 타입으로만 빈을 조회했으나, 같은 타입의 빈이 둘 이상이면 NoUniqueBeanDefinitionException 에러가 발생한다.</p>
<pre><code class="language-java">public class ApplicationContextSameBeanFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);

    @Test
    @DisplayName(&quot;타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다&quot;)
    void findBeanByTypeDuplicate() {
        assertThrows(NoUniqueBeanDefinitionException.class,
                () -&gt; ac.getBean(MemberRepository.class));
    }

    @Test
    @DisplayName(&quot;타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다&quot;)
    void findBeanByType() {
        MemberRepository memberRepository = ac.getBean(&quot;memberRepository1&quot;, MemberRepository.class);
        assertThat(memberRepository).isInstanceOf(MemoryMemberRepository.class);
    }

    @Test
    @DisplayName(&quot;특정 타입을 모두 조회하기&quot;)
    void findAllBeanByType() {
        Map&lt;String, MemberRepository&gt; beansOfType = ac.getBeansOfType(MemberRepository.class);
        for (String key : beansOfType.keySet()) {
            System.out.println(&quot;key = &quot; + key + &quot; value = &quot; + beansOfType.get(key));
        }
        System.out.println(&quot;beansOfType = &quot; + beansOfType);
        assertThat(beansOfType).hasSize(2);
    }


    @Configuration
    static class SameBeanConfig {

        @Bean
        public MemberRepository memberRepository1() {
            return new MemoryMemberRepository();
        }

        @Bean
        public MemberRepository memberRepository2() {
            return new MemoryMemberRepository();
        }
    }
}</code></pre>
<p>특정 타입의 빈을 모두 조회하는 경우가 있는데, 이는 @Autowired에서 자동으로 의존관계 주입과 관련이 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[6. Spring 컨테이너]]></title>
            <link>https://velog.io/@jg_doxb/6.-Spring-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88</link>
            <guid>https://velog.io/@jg_doxb/6.-Spring-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88</guid>
            <pubDate>Sat, 16 Aug 2025 11:00:36 GMT</pubDate>
            <description><![CDATA[<p>객체지향의 장점을 활용하기 위한 DIP, OCP 원칙을 지키기위해 AppConfig를 통해 직접 DI관리를 했다.
스프링 프레임워크는 스프링 컨테이너를 통해 DI를 관리한다.
Configuration, Bean 어노테이션을 통해 스프링 컨테이너에 객체를 등록한다.</p>
<h2 id="1-configuration-bean">1. @Configuration, @Bean</h2>
<pre><code class="language-java">@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(
                memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(
                memberRepository(),
                discountPolicy());
    }

    @Bean
    public DiscountPolicy discountPolicy() {
//        return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }
}</code></pre>
<p>스프링 컨테이너에 등록할 때, 구성파일로 활용될 AppConfig를 스프링 컨테이너가 등록할 객체들을 인식할 수 있게 <span style="color: red">@Configuration</span> 어노테이션을 달아준다.</p>
<blockquote>
<p>스프링 컨테이너는 @Configuration이 붙어있는 클래스를 자동으로 빈으로 등록,
해당 클래스에서 @Bean이 있는 메소드를 찾아서 빈 생성</p>
</blockquote>
<pre><code class="language-java">public class MemberApp {
    public static void main(String[] args) {
//        AppConfig appConfig = new AppConfig();
//        MemberService memberService = appConfig.memberService();
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = applicationContext.getBean(&quot;memberService&quot;, MemberService.class);

        Member member = new Member(1L, &quot;memberA&quot;, Grade.VIP);
        memberService.join(member);

        Member findMember = memberService.findMember(1L);
        System.out.println(&quot;new Member = &quot; + member.getName());
        System.out.println(&quot;find Member = &quot; + findMember.getName());
    }
}</code></pre>
<p>스프링 컨테이너를 활용하기 전에는 기존에는 개발자가 AppConfig를 사용해서 직접 객체를 생성하고 DI를 진행했다.</p>
<p>여기서 스프링 컨테이너는 @Configuration이 붙은 AppConfig를 설정 정보로 사용하고, @Bean이라고 붙은 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다.
이렇게 등록된 객체를 스프링 빈이라고 한다. 즉, <span style="color: red"><strong>DI가 된 객체</strong></span>들이다.</p>
<p>정리하면, AppConfig에서 직접 호출해서 조회하는 방식에서 스프링 컨테이너에서 applicationContext.getBean() 메서드를 사용하여 찾는 방식으로 바뀌었다.</p>
<h2 id="2-스프링-컨테이너-생성-과정">2. 스프링 컨테이너 생성 과정</h2>
<p>메인에서 이렇게 등록된 객체를 불러올 때, ApplicationContext를 호출해서 Bean을 가져오는데,
<span style="color: red"><strong>ApplicationContext</strong></span>를 스프링 컨테이너라고 한다.</p>
<p>더 정확히는 스프링 컨테이너를 부를 때 BeanFactory, ApplicationContext 로 구분한다.
하지만, BeanFactory를 직접 사용하는 경우는 거의 없어 ApplicationContext를 스프링 컨테이너라고 한다.</p>
<h3 id="1-스프링-컨테이너-생성">1. 스프링 컨테이너 생성</h3>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/5d0c199f-03d9-4521-9c70-19b24d83eff2/image.png" alt=""></p>
<pre><code class="language-java">new AnnotationConfigApplicationContext(AppConfig.class)</code></pre>
<p>이를 통해 스프링 컨테이너가 생성된다.
스프링 컨테이너를 생성할 때는 구성 정보를 지정해주어야하는 데, AppConfig.class가 구성 정보이다.</p>
<h3 id="2-스프링-빈-등록">2. 스프링 빈 등록</h3>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/2e6957c0-cf7f-40a4-916c-0c6956c809e1/image.png" alt="">
스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용해서 스프링 빈을 등록한다.</p>
<blockquote>
<p><strong>참고</strong>
빈 이름은 메서드 이름을 사용한다.
@Bean(name=&quot;이름&quot;) 방식으로 직접 부여도 가능하다.
%% 주의: 빈 이름은 항상 다른 이름은 부여! 같은 이름을 부여하게 될 경우 충돌난다.</p>
</blockquote>
<h3 id="3-스프링-빈-의존관계-설정---준비">3. 스프링 빈 의존관계 설정 - 준비</h3>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/c86e95e1-9299-4160-8fdf-96343c51a769/image.png" alt="">
스프링 컨테이너에는 빈 들이 등록이 되어있다.</p>
<h3 id="4-스프링-빈-의존관계-설정---완료">4. 스프링 빈 의존관계 설정 - 완료</h3>
<p><img src="https://velog.velcdn.com/images/jg_doxb/post/740a587b-8a15-4c41-9bba-060c2d866b06/image.png" alt="">
스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다.
단순 자바 코드 호출처럼 보이지만, 싱글톤 컨테이너와 관련있다.</p>
<blockquote>
<p><strong>참고</strong>
정리된 글에서는 스프링은 빈을 생성하고, 의존관계를 주입하는 단계가 나누어져 있다.
그런데 자바 코드로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리된다.
이후 의존관계 자동 주입에서 자세히 다룰 예정이다...</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>